From efeb864cb547a2cbf96dc0053a8bdb4d9190b364 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 05:50:45 +0200 Subject: Merging upstream version 256. Signed-off-by: Daniel Baumann --- src/ac-power/ac-power.c | 3 +- src/analyze/analyze-architectures.c | 88 + src/analyze/analyze-architectures.h | 4 + src/analyze/analyze-capability.c | 6 +- src/analyze/analyze-cat-config.c | 5 +- src/analyze/analyze-critical-chain.c | 13 +- src/analyze/analyze-dot.c | 31 +- src/analyze/analyze-exit-status.c | 6 +- src/analyze/analyze-fdstore.c | 4 +- src/analyze/analyze-image-policy.c | 2 + src/analyze/analyze-pcrs.c | 2 +- src/analyze/analyze-plot.c | 20 +- src/analyze/analyze-security.c | 115 +- src/analyze/analyze-srk.c | 2 +- src/analyze/analyze-time-data.c | 56 +- src/analyze/analyze-time-data.h | 3 + src/analyze/analyze-unit-files.c | 2 +- src/analyze/analyze-unit-paths.c | 2 +- src/analyze/analyze-verify-util.c | 18 +- src/analyze/analyze.c | 26 +- src/analyze/meson.build | 1 + src/ask-password/ask-password.c | 19 +- src/backlight/backlight.c | 417 ++- src/basic/alloc-util.h | 8 +- src/basic/bitfield.h | 2 +- src/basic/build-path.c | 274 ++ src/basic/build-path.h | 8 + src/basic/build.c | 15 +- src/basic/capability-util.c | 47 +- src/basic/cgroup-util.c | 239 +- src/basic/cgroup-util.h | 33 +- src/basic/chase.c | 13 +- src/basic/compress.c | 423 ++- src/basic/compress.h | 24 + src/basic/conf-files.c | 99 +- src/basic/conf-files.h | 18 + src/basic/constants.h | 11 +- src/basic/devnum-util.c | 11 +- src/basic/dlfcn-util.c | 66 + src/basic/dlfcn-util.h | 82 + src/basic/efivars.c | 24 +- src/basic/env-file.c | 2 +- src/basic/env-util.c | 78 +- src/basic/env-util.h | 15 +- src/basic/errno-util.h | 3 +- src/basic/escape.c | 33 + src/basic/escape.h | 1 + src/basic/ether-addr-util.c | 14 +- src/basic/ether-addr-util.h | 2 + src/basic/extract-word.c | 35 +- src/basic/extract-word.h | 5 +- src/basic/fd-util.c | 200 +- src/basic/fd-util.h | 20 + src/basic/fileio.c | 76 +- src/basic/fileio.h | 5 +- src/basic/filesystems-gperf.gperf | 1 + src/basic/format-util.c | 21 +- src/basic/format-util.h | 8 + src/basic/fs-util.c | 127 +- src/basic/fs-util.h | 15 +- src/basic/gcrypt-util.c | 115 +- src/basic/gcrypt-util.h | 50 +- src/basic/getopt-defs.h | 2 + src/basic/glyph-util.c | 13 + src/basic/glyph-util.h | 6 + src/basic/hash-funcs.c | 8 +- src/basic/hashmap.c | 53 +- src/basic/hashmap.h | 8 + src/basic/hexdecoct.c | 12 +- src/basic/hexdecoct.h | 12 +- src/basic/in-addr-util.c | 108 +- src/basic/in-addr-util.h | 20 +- src/basic/initrd-util.c | 2 +- src/basic/iovec-util.c | 6 +- src/basic/iovec-util.h | 69 +- src/basic/keyring-util.c | 66 + src/basic/keyring-util.h | 12 + src/basic/label.c | 4 + src/basic/label.h | 1 + src/basic/linux/btrfs.h | 20 +- src/basic/linux/btrfs_tree.h | 74 +- src/basic/linux/fou.h | 56 +- src/basic/linux/if_bridge.h | 32 + src/basic/linux/if_link.h | 569 +++- src/basic/linux/in.h | 2 + src/basic/linux/in6.h | 2 +- src/basic/linux/magic.h | 106 + src/basic/linux/netfilter.h | 76 + src/basic/linux/netfilter/nf_tables.h | 37 +- src/basic/linux/netlink.h | 5 + src/basic/linux/nexthop.h | 45 + src/basic/linux/nl80211.h | 400 ++- src/basic/linux/pkt_sched.h | 152 +- src/basic/linux/rtnetlink.h | 20 +- src/basic/linux/stddef.h | 13 +- src/basic/locale-util.c | 9 +- src/basic/lock-util.c | 9 +- src/basic/lock-util.h | 2 +- src/basic/log.c | 142 +- src/basic/log.h | 18 +- src/basic/macro.h | 30 +- src/basic/memory-util.c | 16 + src/basic/memory-util.h | 3 + src/basic/meson.build | 55 +- src/basic/missing_audit.h | 20 +- src/basic/missing_capability.h | 9 + src/basic/missing_drm.h | 4 +- src/basic/missing_fs.h | 55 +- src/basic/missing_input.h | 33 +- src/basic/missing_ioprio.h | 34 +- src/basic/missing_keyctl.h | 102 +- src/basic/missing_loop.h | 15 +- src/basic/missing_magic.h | 196 +- src/basic/missing_mman.h | 18 +- src/basic/missing_mount.h | 6 +- src/basic/missing_prctl.h | 15 +- src/basic/missing_random.h | 14 +- src/basic/missing_resource.h | 6 +- src/basic/missing_sched.h | 27 +- src/basic/missing_socket.h | 25 +- src/basic/missing_timerfd.h | 6 +- src/basic/missing_type.h | 4 +- src/basic/missing_wait.h | 12 + src/basic/mkdir.c | 50 +- src/basic/mkdir.h | 7 +- src/basic/mountpoint-util.c | 69 +- src/basic/mountpoint-util.h | 11 +- src/basic/namespace-util.c | 290 +- src/basic/namespace-util.h | 20 +- src/basic/nulstr-util.c | 47 +- src/basic/nulstr-util.h | 7 +- src/basic/ordered-set.c | 5 +- src/basic/os-util.c | 13 +- src/basic/parse-util.c | 9 +- src/basic/parse-util.h | 2 +- src/basic/path-lookup.c | 76 +- src/basic/path-lookup.h | 3 +- src/basic/path-util.c | 80 +- src/basic/path-util.h | 52 +- src/basic/pidref.c | 135 +- src/basic/pidref.h | 29 +- src/basic/proc-cmdline.c | 12 +- src/basic/process-util.c | 270 +- src/basic/process-util.h | 57 +- src/basic/recurse-dir.c | 13 + src/basic/recurse-dir.h | 1 + src/basic/rlimit-util.c | 116 + src/basic/rlimit-util.h | 2 + src/basic/sha256.c | 50 + src/basic/sha256.h | 16 + src/basic/signal-util.c | 88 +- src/basic/signal-util.h | 9 +- src/basic/siphash24.h | 9 +- src/basic/socket-util.c | 138 +- src/basic/socket-util.h | 13 +- src/basic/special.h | 7 +- src/basic/stat-util.c | 263 +- src/basic/stat-util.h | 62 +- src/basic/stdio-util.h | 4 +- src/basic/string-table.h | 6 +- src/basic/string-util.c | 129 +- src/basic/string-util.h | 36 +- src/basic/strv.c | 120 +- src/basic/strv.h | 31 +- src/basic/syscall-list.txt | 6 + src/basic/syscalls-alpha.txt | 8 +- src/basic/syscalls-arc.txt | 6 + src/basic/syscalls-arm.txt | 6 + src/basic/syscalls-arm64.txt | 6 + src/basic/syscalls-i386.txt | 6 + src/basic/syscalls-loongarch64.txt | 6 + src/basic/syscalls-m68k.txt | 6 + src/basic/syscalls-mips64.txt | 6 + src/basic/syscalls-mips64n32.txt | 6 + src/basic/syscalls-mipso32.txt | 6 + src/basic/syscalls-parisc.txt | 6 + src/basic/syscalls-powerpc.txt | 6 + src/basic/syscalls-powerpc64.txt | 6 + src/basic/syscalls-riscv32.txt | 6 + src/basic/syscalls-riscv64.txt | 6 + src/basic/syscalls-s390.txt | 6 + src/basic/syscalls-s390x.txt | 6 + src/basic/syscalls-sparc.txt | 6 + src/basic/syscalls-x86_64.txt | 6 + src/basic/sysctl-util.c | 20 + src/basic/sysctl-util.h | 7 + src/basic/terminal-util.c | 352 +- src/basic/terminal-util.h | 27 +- src/basic/time-util.c | 48 +- src/basic/time-util.h | 11 +- src/basic/tmpfile-util.c | 52 +- src/basic/uid-alloc-range.c | 131 - src/basic/uid-alloc-range.h | 36 - src/basic/uid-classification.c | 131 + src/basic/uid-classification.h | 36 + src/basic/uid-range.c | 191 +- src/basic/uid-range.h | 72 +- src/basic/unit-def.c | 41 +- src/basic/unit-def.h | 5 +- src/basic/unit-name.c | 68 +- src/basic/unit-name.h | 11 +- src/basic/user-util.c | 481 ++- src/basic/user-util.h | 15 +- src/basic/utf8.c | 22 +- src/basic/virt.c | 51 +- src/battery-check/battery-check.c | 4 +- src/boot/bless-boot-generator.c | 10 +- src/boot/bless-boot.c | 31 +- src/boot/boot-check-no-failures.c | 3 +- src/boot/bootctl-install.c | 83 +- src/boot/bootctl-reboot-to-firmware.c | 39 +- src/boot/bootctl-reboot-to-firmware.h | 5 + src/boot/bootctl-set-efivar.c | 2 +- src/boot/bootctl-status.c | 96 +- src/boot/bootctl-status.h | 4 + src/boot/bootctl-util.c | 2 +- src/boot/bootctl-util.h | 4 - src/boot/bootctl.c | 44 +- src/boot/bootctl.h | 1 + src/boot/efi/UEFI_SECURITY.md | 57 +- src/boot/efi/boot.c | 40 +- src/boot/efi/cpio.c | 3 + src/boot/efi/cpio.h | 1 + src/boot/efi/efi-string.c | 8 +- src/boot/efi/efi.h | 2 + src/boot/efi/measure.c | 158 +- src/boot/efi/meson.build | 17 +- src/boot/efi/part-discovery.c | 2 +- src/boot/efi/proto/cc-measurement.h | 67 + src/boot/efi/proto/tcg.h | 34 - src/boot/efi/random-seed.c | 2 +- src/boot/efi/secure-boot.c | 73 +- src/boot/efi/stub.c | 98 +- src/boot/efi/util.c | 2 +- src/boot/efi/util.h | 4 +- src/boot/efi/vmm.h | 2 +- src/boot/measure.c | 165 +- src/busctl/busctl.c | 25 +- src/cgtop/cgtop.c | 2 +- src/core/automount.c | 75 +- src/core/bpf-devices.c | 82 +- src/core/bpf-firewall.c | 160 +- src/core/bpf-foreign.c | 21 +- src/core/bpf-lsm.c | 320 -- src/core/bpf-lsm.h | 28 - src/core/bpf-restrict-fs.c | 324 ++ src/core/bpf-restrict-fs.h | 23 + src/core/bpf-restrict-ifaces.c | 223 ++ src/core/bpf-restrict-ifaces.h | 16 + src/core/bpf-socket-bind.c | 51 +- src/core/bpf-socket-bind.h | 2 +- src/core/bpf-util.c | 3 +- src/core/cgroup.c | 1646 +++++++-- src/core/cgroup.h | 132 +- src/core/core-varlink.c | 105 +- src/core/core-varlink.h | 4 - src/core/crash-handler.c | 8 +- src/core/dbus-cgroup.c | 21 +- src/core/dbus-execute.c | 117 +- src/core/dbus-execute.h | 1 + src/core/dbus-job.c | 25 +- src/core/dbus-manager.c | 407 ++- src/core/dbus-mount.c | 31 +- src/core/dbus-scope.c | 24 +- src/core/dbus-service.c | 2 - src/core/dbus-socket.c | 4 + src/core/dbus-unit.c | 158 +- src/core/dbus-util.c | 7 +- src/core/dbus-util.h | 3 +- src/core/dbus.c | 92 +- src/core/device.c | 75 +- src/core/dynamic-user.c | 49 +- src/core/emergency-action.c | 32 +- src/core/emergency-action.h | 6 +- src/core/exec-credential.c | 256 +- src/core/exec-credential.h | 4 +- src/core/exec-invoke.c | 649 ++-- src/core/execute-serialize.c | 131 +- src/core/execute.c | 239 +- src/core/execute.h | 175 +- src/core/executor.c | 5 +- src/core/fuzz-execute-serialize.c | 2 +- src/core/generator-setup.c | 12 +- src/core/import-creds.c | 17 +- src/core/job.c | 42 +- src/core/job.h | 1 + src/core/kmod-setup.c | 48 +- src/core/load-fragment-gperf.gperf.in | 15 +- src/core/load-fragment.c | 353 +- src/core/load-fragment.h | 4 +- src/core/main.c | 246 +- src/core/main.h | 14 +- src/core/manager-dump.c | 2 +- src/core/manager-serialize.c | 97 +- src/core/manager.c | 671 ++-- src/core/manager.h | 63 +- src/core/meson.build | 7 +- src/core/mount.c | 353 +- src/core/mount.h | 1 + src/core/namespace.c | 333 +- src/core/path.c | 81 +- src/core/restrict-ifaces.c | 200 -- src/core/restrict-ifaces.h | 16 - src/core/scope.c | 95 +- src/core/scope.h | 1 + src/core/selinux-access.c | 5 +- src/core/service.c | 787 +++-- src/core/service.h | 4 + src/core/show-status.c | 4 +- src/core/slice.c | 147 +- src/core/slice.h | 2 + src/core/socket.c | 382 +-- src/core/socket.h | 4 +- src/core/swap.c | 257 +- src/core/swap.h | 1 + src/core/system.conf.in | 3 +- src/core/taint.c | 85 + src/core/taint.h | 4 + src/core/target.c | 57 +- src/core/timer.c | 89 +- src/core/transaction.c | 8 +- src/core/unit-printf.c | 59 +- src/core/unit-serialize.c | 279 +- src/core/unit.c | 1264 +++---- src/core/unit.h | 166 +- src/coredump/coredump-vacuum.c | 19 +- src/coredump/coredump.c | 21 +- src/coredump/coredumpctl.c | 55 +- src/coredump/meson.build | 15 +- src/creds/creds.c | 558 +++- src/creds/io.systemd.credentials.policy | 40 + src/creds/meson.build | 3 + src/cryptenroll/cryptenroll-fido2.c | 7 +- src/cryptenroll/cryptenroll-list.c | 2 +- src/cryptenroll/cryptenroll-password.c | 42 +- src/cryptenroll/cryptenroll-pkcs11.c | 86 +- src/cryptenroll/cryptenroll-tpm2.c | 269 +- src/cryptenroll/cryptenroll-tpm2.h | 10 +- src/cryptenroll/cryptenroll.c | 201 +- src/cryptenroll/cryptenroll.h | 1 + src/cryptsetup/cryptsetup-generator.c | 25 +- src/cryptsetup/cryptsetup-pkcs11.c | 6 +- src/cryptsetup/cryptsetup-pkcs11.h | 4 +- .../cryptsetup-token-systemd-fido2.c | 12 +- .../cryptsetup-token-systemd-pkcs11.c | 6 +- .../cryptsetup-token-systemd-tpm2.c | 65 +- src/cryptsetup/cryptsetup-tokens/luks2-fido2.c | 4 +- src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c | 4 +- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 44 +- src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 19 +- src/cryptsetup/cryptsetup-tpm2.c | 314 -- src/cryptsetup/cryptsetup-tpm2.h | 126 - src/cryptsetup/cryptsetup.c | 288 +- src/cryptsetup/meson.build | 4 - src/debug-generator/debug-generator.c | 182 +- src/delta/delta.c | 4 +- src/dissect/dissect.c | 496 ++- .../environment-d-generator.c | 7 +- src/firstboot/firstboot.c | 36 +- src/fsck/fsck.c | 4 +- src/fstab-generator/fstab-generator.c | 253 +- src/fundamental/efivars-fundamental.h | 1 + src/fundamental/macro-fundamental.h | 30 +- src/fundamental/meson.build | 2 +- src/fundamental/sha256-fundamental.c | 278 ++ src/fundamental/sha256-fundamental.h | 39 + src/fundamental/sha256.c | 285 -- src/fundamental/sha256.h | 39 - src/fundamental/string-util-fundamental.c | 16 +- src/fundamental/string-util-fundamental.h | 4 +- src/fundamental/uki.c | 1 + src/fundamental/uki.h | 1 + src/fuzz/fuzz.h | 4 +- src/getty-generator/getty-generator.c | 7 +- src/gpt-auto-generator/gpt-auto-generator.c | 69 +- src/hibernate-resume/hibernate-resume-config.c | 44 +- src/hibernate-resume/hibernate-resume-config.h | 29 +- src/hibernate-resume/hibernate-resume.c | 126 +- src/home/home-util.c | 7 + src/home/home-util.h | 11 + src/home/homectl-fido2.c | 1 + src/home/homectl-pkcs11.c | 183 +- src/home/homectl-pkcs11.h | 6 + src/home/homectl.c | 1094 ++++-- src/home/homed-bus.c | 70 + src/home/homed-bus.h | 4 +- src/home/homed-conf.c | 9 +- src/home/homed-home-bus.c | 187 +- src/home/homed-home-bus.h | 3 +- src/home/homed-home.c | 349 +- src/home/homed-home.h | 21 +- src/home/homed-manager-bus.c | 195 +- src/home/homed-manager.c | 71 +- src/home/homed-operation.h | 1 + src/home/homed.c | 2 +- src/home/homework-blob.c | 301 ++ src/home/homework-blob.h | 9 + src/home/homework-cifs.c | 4 +- src/home/homework-directory.c | 15 +- src/home/homework-fscrypt.c | 137 +- src/home/homework-fscrypt.h | 2 + src/home/homework-luks.c | 280 +- src/home/homework-password-cache.c | 42 +- src/home/homework-password-cache.h | 12 +- src/home/homework-quota.c | 4 +- src/home/homework.c | 340 +- src/home/homework.h | 2 +- src/home/meson.build | 10 + src/home/org.freedesktop.home1.conf | 28 + src/home/org.freedesktop.home1.policy | 9 + src/home/pam_systemd_home.c | 333 +- src/home/test-homed-regression-31896.c | 35 + src/home/user-record-sign.c | 4 +- src/home/user-record-util.c | 123 +- src/home/user-record-util.h | 7 + src/hostname/hostnamectl.c | 87 +- src/hostname/hostnamed.c | 415 ++- src/hwdb/hwdb.c | 6 +- src/id128/id128.c | 42 +- src/import/curl-util.c | 45 +- src/import/curl-util.h | 1 + src/import/export.c | 60 +- src/import/import-common.c | 7 +- src/import/import-common.h | 37 +- src/import/import-fs.c | 28 +- src/import/import-raw.c | 3 +- src/import/import-tar.c | 3 +- src/import/import.c | 42 +- src/import/importctl.c | 1245 +++++++ src/import/importd.c | 673 +++- src/import/meson.build | 30 +- src/import/org.freedesktop.import1.conf | 36 + src/import/org.freedesktop.import1.policy | 22 +- src/import/pull-common.c | 19 +- src/import/pull-common.h | 21 +- src/import/pull-job.c | 6 +- src/import/pull-raw.c | 168 +- src/import/pull-raw.h | 2 +- src/import/pull-tar.c | 137 +- src/import/pull-tar.h | 2 +- src/import/pull.c | 119 +- src/import/qcow2-util.c | 2 +- src/journal-remote/fuzz-journal-remote.c | 2 +- src/journal-remote/journal-gatewayd.c | 207 +- src/journal-remote/journal-remote-main.c | 28 +- src/journal-remote/journal-remote.c | 15 +- src/journal-remote/journal-remote.h | 5 +- src/journal-remote/journal-upload-journal.c | 2 +- src/journal-remote/journal-upload.c | 28 +- src/journal-remote/journal-upload.h | 2 +- src/journal-remote/meson.build | 10 +- src/journal/bsod.c | 123 +- src/journal/cat.c | 19 +- src/journal/fuzz-journald-audit.c | 8 +- src/journal/fuzz-journald-kmsg.c | 8 +- src/journal/fuzz-journald-native-fd.c | 11 +- src/journal/fuzz-journald-stream.c | 24 +- src/journal/fuzz-journald.c | 23 +- src/journal/journalctl-authenticate.c | 207 ++ src/journal/journalctl-authenticate.h | 16 + src/journal/journalctl-catalog.c | 51 + src/journal/journalctl-catalog.h | 5 + src/journal/journalctl-filter.c | 484 +++ src/journal/journalctl-filter.h | 6 + src/journal/journalctl-misc.c | 298 ++ src/journal/journalctl-misc.h | 12 + src/journal/journalctl-show.c | 477 +++ src/journal/journalctl-show.h | 4 + src/journal/journalctl-util.c | 120 + src/journal/journalctl-util.h | 11 + src/journal/journalctl-varlink.c | 142 + src/journal/journalctl-varlink.h | 9 + src/journal/journalctl.c | 1882 +---------- src/journal/journalctl.h | 99 + src/journal/journald-audit.c | 19 +- src/journal/journald-context.c | 6 +- src/journal/journald-gperf.gperf | 64 +- src/journal/journald-kmsg.c | 17 +- src/journal/journald-native.c | 171 +- src/journal/journald-native.h | 2 +- src/journal/journald-rate-limit.c | 205 +- src/journal/journald-rate-limit.h | 15 +- src/journal/journald-server.c | 354 +- src/journal/journald-server.h | 20 +- src/journal/journald-socket.c | 163 + src/journal/journald-socket.h | 7 + src/journal/journald-stream.c | 2 +- src/journal/journald-syslog.c | 12 +- src/journal/journald.c | 101 +- src/journal/journald.conf | 1 + src/journal/meson.build | 44 +- src/journal/test-journald-config.c | 128 + src/journal/test-journald-rate-limit.c | 40 + src/kernel-install/60-ukify.install.in | 41 +- src/kernel-install/90-loaderentry.install.in | 2 +- src/kernel-install/kernel-install.c | 157 +- src/kernel-install/test-kernel-install.sh | 5 +- src/libsystemd-network/dhcp-client-id-internal.h | 60 + src/libsystemd-network/dhcp-duid-internal.h | 83 + src/libsystemd-network/dhcp-identifier.c | 209 -- src/libsystemd-network/dhcp-identifier.h | 87 - src/libsystemd-network/dhcp-lease-internal.h | 6 +- src/libsystemd-network/dhcp-network.c | 9 +- src/libsystemd-network/dhcp-option.c | 4 +- src/libsystemd-network/dhcp-server-internal.h | 33 +- .../dhcp-server-lease-internal.h | 45 + src/libsystemd-network/dhcp6-internal.h | 10 +- src/libsystemd-network/dhcp6-network.c | 18 +- src/libsystemd-network/dhcp6-protocol.h | 8 +- src/libsystemd-network/fuzz-dhcp-client.c | 20 +- src/libsystemd-network/fuzz-dhcp-server.c | 41 +- src/libsystemd-network/fuzz-dhcp6-client.c | 4 +- src/libsystemd-network/fuzz-lldp-rx.c | 9 + src/libsystemd-network/fuzz-ndisc-rs.c | 74 +- src/libsystemd-network/icmp6-packet.c | 131 + src/libsystemd-network/icmp6-packet.h | 34 + src/libsystemd-network/icmp6-test-util.c | 46 + src/libsystemd-network/icmp6-test-util.h | 6 + src/libsystemd-network/icmp6-util-unix.c | 53 - src/libsystemd-network/icmp6-util-unix.h | 9 - src/libsystemd-network/icmp6-util.c | 129 +- src/libsystemd-network/icmp6-util.h | 24 +- src/libsystemd-network/lldp-neighbor.c | 69 +- src/libsystemd-network/lldp-neighbor.h | 2 + src/libsystemd-network/lldp-rx-internal.h | 3 + src/libsystemd-network/meson.build | 22 +- src/libsystemd-network/ndisc-internal.h | 1 + src/libsystemd-network/ndisc-neighbor-internal.h | 21 + src/libsystemd-network/ndisc-option.c | 1502 +++++++++ src/libsystemd-network/ndisc-option.h | 330 ++ src/libsystemd-network/ndisc-protocol.c | 34 - src/libsystemd-network/ndisc-protocol.h | 31 - src/libsystemd-network/ndisc-redirect-internal.h | 21 + src/libsystemd-network/ndisc-router-internal.h | 37 + .../ndisc-router-solicit-internal.h | 18 + src/libsystemd-network/ndisc-router.c | 913 ----- src/libsystemd-network/ndisc-router.h | 49 - src/libsystemd-network/radv-internal.h | 16 +- src/libsystemd-network/sd-dhcp-client-id.c | 196 ++ src/libsystemd-network/sd-dhcp-client.c | 248 +- src/libsystemd-network/sd-dhcp-duid.c | 288 ++ src/libsystemd-network/sd-dhcp-lease.c | 50 +- src/libsystemd-network/sd-dhcp-server-lease.c | 513 +++ src/libsystemd-network/sd-dhcp-server.c | 349 +- src/libsystemd-network/sd-dhcp6-client.c | 80 +- src/libsystemd-network/sd-dhcp6-lease.c | 3 +- src/libsystemd-network/sd-ipv4acd.c | 9 +- src/libsystemd-network/sd-ipv4ll.c | 3 +- src/libsystemd-network/sd-lldp-rx.c | 25 + src/libsystemd-network/sd-ndisc-neighbor.c | 126 + src/libsystemd-network/sd-ndisc-redirect.c | 128 + src/libsystemd-network/sd-ndisc-router-solicit.c | 83 + src/libsystemd-network/sd-ndisc-router.c | 344 ++ src/libsystemd-network/sd-ndisc.c | 329 +- src/libsystemd-network/sd-radv.c | 463 ++- src/libsystemd-network/test-acd.c | 5 +- src/libsystemd-network/test-dhcp-client.c | 25 +- src/libsystemd-network/test-dhcp-server.c | 46 +- src/libsystemd-network/test-dhcp6-client.c | 12 +- src/libsystemd-network/test-ipv4ll-manual.c | 5 +- src/libsystemd-network/test-ipv4ll.c | 35 +- src/libsystemd-network/test-ndisc-ra.c | 213 +- src/libsystemd-network/test-ndisc-rs.c | 272 +- src/libsystemd-network/test-ndisc-send.c | 449 +++ src/libsystemd/libsystemd.sym | 9 + src/libsystemd/meson.build | 8 +- src/libsystemd/sd-bus/bus-common-errors.c | 3 + src/libsystemd/sd-bus/bus-common-errors.h | 3 + src/libsystemd/sd-bus/bus-container.c | 2 +- src/libsystemd/sd-bus/bus-control.c | 139 +- src/libsystemd/sd-bus/bus-convenience.c | 38 +- src/libsystemd/sd-bus/bus-creds.c | 149 +- src/libsystemd/sd-bus/bus-creds.h | 18 +- src/libsystemd/sd-bus/bus-dump.c | 2 + src/libsystemd/sd-bus/bus-internal.h | 4 + src/libsystemd/sd-bus/bus-message.c | 4 +- src/libsystemd/sd-bus/bus-objects.c | 2 +- src/libsystemd/sd-bus/bus-socket.c | 112 +- src/libsystemd/sd-bus/bus-track.c | 2 +- src/libsystemd/sd-bus/sd-bus.c | 102 +- src/libsystemd/sd-bus/test-bus-chat.c | 3 +- src/libsystemd/sd-bus/test-bus-cleanup.c | 2 +- src/libsystemd/sd-bus/test-bus-creds.c | 24 +- src/libsystemd/sd-bus/test-bus-error.c | 4 +- src/libsystemd/sd-bus/test-bus-marshal.c | 4 +- src/libsystemd/sd-bus/test-bus-objects.c | 3 +- src/libsystemd/sd-bus/test-bus-peersockaddr.c | 55 +- src/libsystemd/sd-daemon/sd-daemon.c | 75 +- src/libsystemd/sd-device/device-enumerator.c | 10 +- src/libsystemd/sd-device/device-monitor.c | 23 +- src/libsystemd/sd-device/device-private.c | 125 +- src/libsystemd/sd-device/device-private.h | 3 + src/libsystemd/sd-device/device-util.c | 39 +- src/libsystemd/sd-device/device-util.h | 13 +- src/libsystemd/sd-device/sd-device.c | 113 +- src/libsystemd/sd-device/test-device-util.c | 78 +- src/libsystemd/sd-device/test-sd-device-monitor.c | 4 + src/libsystemd/sd-device/test-sd-device-thread.c | 3 + src/libsystemd/sd-device/test-sd-device.c | 10 +- src/libsystemd/sd-event/event-source.h | 3 + src/libsystemd/sd-event/event-util.c | 26 + src/libsystemd/sd-event/event-util.h | 6 + src/libsystemd/sd-event/sd-event.c | 62 +- src/libsystemd/sd-event/test-event.c | 48 +- src/libsystemd/sd-hwdb/sd-hwdb.c | 8 +- src/libsystemd/sd-id128/id128-util.c | 82 +- src/libsystemd/sd-id128/id128-util.h | 3 + src/libsystemd/sd-id128/sd-id128.c | 52 +- src/libsystemd/sd-journal/catalog.c | 91 +- src/libsystemd/sd-journal/catalog.h | 2 +- src/libsystemd/sd-journal/fsprg.c | 208 +- src/libsystemd/sd-journal/fsprg.h | 25 +- src/libsystemd/sd-journal/journal-authenticate.c | 68 +- src/libsystemd/sd-journal/journal-file.c | 104 +- src/libsystemd/sd-journal/journal-file.h | 4 +- src/libsystemd/sd-journal/journal-internal.h | 18 +- src/libsystemd/sd-journal/journal-send.c | 27 +- src/libsystemd/sd-journal/journal-send.h | 17 + src/libsystemd/sd-journal/journal-verify.c | 3 +- src/libsystemd/sd-journal/sd-journal.c | 566 ++-- src/libsystemd/sd-journal/test-journal-append.c | 4 +- src/libsystemd/sd-journal/test-journal-enum.c | 6 +- src/libsystemd/sd-journal/test-journal-flush.c | 92 +- src/libsystemd/sd-journal/test-journal-init.c | 11 +- .../sd-journal/test-journal-interleaving.c | 279 +- src/libsystemd/sd-journal/test-journal-match.c | 44 +- src/libsystemd/sd-journal/test-journal-stream.c | 20 +- src/libsystemd/sd-journal/test-journal-verify.c | 6 +- src/libsystemd/sd-journal/test-journal.c | 12 +- src/libsystemd/sd-login/sd-login.c | 7 +- src/libsystemd/sd-login/test-login.c | 17 +- src/libsystemd/sd-netlink/netlink-message-rtnl.c | 41 +- src/libsystemd/sd-netlink/netlink-message.c | 29 - src/libsystemd/sd-netlink/netlink-types-genl.c | 1 + src/libsystemd/sd-netlink/netlink-types-rtnl.c | 13 + src/libsystemd/sd-netlink/netlink-util.c | 421 ++- src/libsystemd/sd-netlink/netlink-util.h | 56 +- src/libsystemd/sd-netlink/sd-netlink.c | 8 +- src/libsystemd/sd-netlink/test-netlink.c | 14 + src/libsystemd/sd-network/network-util.c | 59 +- src/libsystemd/sd-network/network-util.h | 30 +- src/libsystemd/sd-network/sd-network.c | 2 +- src/libsystemd/sd-path/sd-path.c | 46 +- src/libudev/libudev-device.c | 3 +- src/libudev/test-libudev.c | 3 + src/libudev/test-udev-device-thread.c | 7 +- src/locale/localed-util.c | 35 +- src/locale/localed.c | 48 +- src/locale/xkbcommon-util.c | 23 +- src/locale/xkbcommon-util.h | 21 +- src/login/inhibit.c | 12 +- src/login/loginctl.c | 222 +- src/login/logind-action.c | 145 +- src/login/logind-action.h | 21 +- src/login/logind-core.c | 32 +- src/login/logind-dbus.c | 1219 ++++--- src/login/logind-dbus.h | 18 +- src/login/logind-device.h | 2 +- src/login/logind-gperf.gperf | 1 + src/login/logind-inhibit.c | 16 +- src/login/logind-inhibit.h | 4 +- src/login/logind-polkit.c | 5 +- src/login/logind-seat-dbus.c | 5 +- src/login/logind-seat.c | 13 +- src/login/logind-seat.h | 4 +- src/login/logind-session-dbus.c | 149 +- src/login/logind-session-dbus.h | 1 + src/login/logind-session-device.c | 54 +- src/login/logind-session-device.h | 2 +- src/login/logind-session.c | 452 +-- src/login/logind-session.h | 74 +- src/login/logind-user-dbus.c | 16 +- src/login/logind-user.c | 345 +- src/login/logind-user.h | 38 +- src/login/logind-wall.c | 12 +- src/login/logind.c | 274 +- src/login/logind.conf.in | 1 + src/login/logind.h | 4 +- src/login/meson.build | 6 +- src/login/org.freedesktop.login1.conf | 36 +- src/login/pam_systemd.c | 184 +- src/login/pam_systemd_loadkey.c | 6 +- src/login/test-login-tables.c | 17 + src/login/user-runtime-dir.c | 5 +- src/machine-id-setup/machine-id-setup-main.c | 6 +- src/machine/image-dbus.c | 94 +- src/machine/image-dbus.h | 2 + src/machine/machine-dbus.c | 77 +- src/machine/machine-dbus.h | 1 + src/machine/machine-varlink.c | 173 + src/machine/machine-varlink.h | 6 + src/machine/machine.c | 150 +- src/machine/machine.h | 9 +- src/machine/machinectl.c | 861 +---- src/machine/machined-dbus.c | 26 +- src/machine/machined-varlink.c | 62 +- src/machine/machined.c | 40 +- src/machine/machined.h | 3 +- src/machine/meson.build | 7 +- src/modules-load/meson.build | 2 +- src/modules-load/modules-load.c | 21 +- src/mount/mount-tool.c | 12 +- src/mountfsd/io.systemd.mount-file-system.policy | 70 + src/mountfsd/meson.build | 26 + src/mountfsd/mountfsd-manager.c | 277 ++ src/mountfsd/mountfsd-manager.h | 30 + src/mountfsd/mountfsd.c | 43 + src/mountfsd/mountwork.c | 703 ++++ src/network/generator/main.c | 51 +- src/network/generator/network-generator.c | 180 +- src/network/meson.build | 17 +- src/network/netdev/bond.c | 12 + src/network/netdev/bond.h | 3 + src/network/netdev/bridge.c | 3 +- src/network/netdev/fou-tunnel.c | 3 +- src/network/netdev/geneve.c | 3 +- src/network/netdev/ipvlan.c | 1 + src/network/netdev/macsec.c | 5 +- src/network/netdev/macvlan.c | 60 + src/network/netdev/macvlan.h | 2 + src/network/netdev/netdev-gperf.gperf | 3 + src/network/netdev/netdev.c | 22 +- src/network/netdev/tuntap.c | 10 +- src/network/netdev/veth.c | 5 +- src/network/netdev/vlan.c | 7 +- src/network/netdev/vrf.c | 3 +- src/network/netdev/vxlan.c | 3 +- src/network/netdev/wireguard.c | 155 +- src/network/networkctl-config-file.c | 632 ++++ src/network/networkctl-config-file.h | 8 + src/network/networkctl.c | 964 ++---- src/network/networkctl.h | 23 + src/network/networkd-address-generation.c | 257 +- src/network/networkd-address-generation.h | 15 +- src/network/networkd-address-label.c | 1 + src/network/networkd-address.c | 563 ++-- src/network/networkd-address.h | 24 +- src/network/networkd-bridge-mdb.c | 1 + src/network/networkd-bridge-vlan.c | 457 ++- src/network/networkd-bridge-vlan.h | 20 +- src/network/networkd-can.c | 1 + src/network/networkd-conf.c | 19 +- src/network/networkd-dhcp-common.c | 193 +- src/network/networkd-dhcp-common.h | 20 +- src/network/networkd-dhcp-prefix-delegation.c | 136 +- src/network/networkd-dhcp-prefix-delegation.h | 2 + src/network/networkd-dhcp-server-bus.c | 6 +- src/network/networkd-dhcp-server.c | 149 +- src/network/networkd-dhcp-server.h | 8 +- src/network/networkd-dhcp4.c | 206 +- src/network/networkd-dhcp6.c | 38 +- src/network/networkd-dns.c | 294 ++ src/network/networkd-dns.h | 28 + src/network/networkd-gperf.gperf | 9 + src/network/networkd-ipv4acd.c | 4 +- src/network/networkd-ipv4ll.c | 18 +- src/network/networkd-json.c | 371 +-- src/network/networkd-link-bus.c | 139 +- src/network/networkd-link.c | 358 +- src/network/networkd-link.h | 19 +- src/network/networkd-lldp-rx.c | 69 - src/network/networkd-lldp-rx.h | 1 - src/network/networkd-lldp-tx.c | 6 +- src/network/networkd-manager-bus.c | 46 +- src/network/networkd-manager-varlink.c | 314 ++ src/network/networkd-manager-varlink.h | 7 + src/network/networkd-manager.c | 138 +- src/network/networkd-manager.h | 25 +- src/network/networkd-ndisc.c | 1415 ++++++-- src/network/networkd-ndisc.h | 9 +- src/network/networkd-neighbor.c | 230 +- src/network/networkd-neighbor.h | 10 +- src/network/networkd-network-gperf.gperf | 110 +- src/network/networkd-network.c | 392 +-- src/network/networkd-network.h | 92 +- src/network/networkd-nexthop.c | 1092 +++--- src/network/networkd-nexthop.h | 44 +- src/network/networkd-ntp.c | 101 + src/network/networkd-ntp.h | 11 + src/network/networkd-queue.c | 264 +- src/network/networkd-queue.h | 57 +- src/network/networkd-radv.c | 348 +- src/network/networkd-radv.h | 33 +- src/network/networkd-route-metric.c | 483 +++ src/network/networkd-route-metric.h | 47 + src/network/networkd-route-nexthop.c | 1225 +++++++ src/network/networkd-route-nexthop.h | 57 + src/network/networkd-route-util.c | 38 +- src/network/networkd-route-util.h | 2 + src/network/networkd-route.c | 3520 +++++++------------- src/network/networkd-route.h | 127 +- src/network/networkd-routing-policy-rule.c | 122 +- src/network/networkd-routing-policy-rule.h | 5 +- src/network/networkd-setlink.c | 171 +- src/network/networkd-state-file.c | 96 +- src/network/networkd-state-file.h | 1 + src/network/networkd-sysctl.c | 177 +- src/network/networkd-sysctl.h | 7 +- src/network/networkd-util.c | 42 - src/network/networkd-util.h | 1 - src/network/networkd-wifi.c | 2 +- src/network/networkd-wiphy.c | 6 +- src/network/networkd.c | 19 +- src/network/networkd.conf | 10 + src/network/org.freedesktop.network1.policy | 11 + src/network/tc/qdisc.c | 58 +- src/network/tc/qdisc.h | 2 + src/network/tc/tclass.c | 57 +- src/network/tc/tclass.h | 2 + src/network/test-network-tables.c | 6 +- src/network/test-networkd-conf.c | 2 +- src/network/wait-online/manager.c | 44 +- src/network/wait-online/wait-online.c | 28 +- src/notify/notify.c | 123 +- src/nspawn/nspawn-bind-user.c | 6 +- src/nspawn/nspawn-cgroup.c | 95 +- src/nspawn/nspawn-cgroup.h | 3 +- src/nspawn/nspawn-gperf.gperf | 2 +- src/nspawn/nspawn-mount.c | 102 +- src/nspawn/nspawn-mount.h | 1 + src/nspawn/nspawn-network.c | 410 ++- src/nspawn/nspawn-network.h | 2 + src/nspawn/nspawn-oci.c | 44 +- src/nspawn/nspawn-register.c | 19 +- src/nspawn/nspawn-settings.c | 44 +- src/nspawn/nspawn-settings.h | 2 +- src/nspawn/nspawn-setuid.c | 13 +- src/nspawn/nspawn.c | 1119 +++++-- src/nspawn/nspawn.h | 1 + src/nspawn/test-nspawn-util.c | 2 +- src/nsresourced/bpf/userns_restrict/meson.build | 25 + .../bpf/userns_restrict/userns-restrict-skel.h | 17 + .../bpf/userns_restrict/userns-restrict.bpf.c | 179 + src/nsresourced/meson.build | 48 + src/nsresourced/nsresourced-manager.c | 647 ++++ src/nsresourced/nsresourced-manager.h | 40 + src/nsresourced/nsresourced.c | 46 + src/nsresourced/nsresourcework.c | 1782 ++++++++++ src/nsresourced/test-userns-restrict.c | 182 + src/nsresourced/userns-registry.c | 646 ++++ src/nsresourced/userns-registry.h | 42 + src/nsresourced/userns-restrict.c | 346 ++ src/nsresourced/userns-restrict.h | 22 + src/nss-resolve/nss-resolve.c | 11 +- src/nss-systemd/nss-systemd.c | 12 +- src/nss-systemd/userdb-glue.c | 2 +- src/oom/oomctl.c | 4 +- src/oom/oomd-manager-bus.c | 2 +- src/oom/oomd-manager.c | 25 +- src/oom/oomd-util.c | 30 +- src/oom/oomd.c | 11 +- src/partition/growfs.c | 20 +- src/partition/meson.build | 1 - src/partition/repart.c | 782 ++++- src/path/path.c | 4 +- src/pcrextend/pcrextend.c | 6 +- src/pcrlock/pcrlock-firmware.c | 8 +- src/pcrlock/pcrlock.c | 779 +++-- src/portable/portable.c | 213 +- src/portable/portable.h | 3 +- src/portable/portablectl.c | 77 +- src/portable/portabled-bus.c | 10 +- src/portable/portabled-image-bus.c | 19 +- src/portable/portabled.c | 29 +- src/portable/profile/default/service.conf | 2 +- src/portable/profile/trusted/service.conf | 2 +- src/pstore/meson.build | 6 +- src/pstore/pstore.c | 14 +- src/quotacheck/quotacheck.c | 34 +- src/resolve/dns-type.c | 2 +- src/resolve/dns-type.h | 2 +- src/resolve/fuzz-resource-record.c | 10 +- src/resolve/meson.build | 15 +- src/resolve/resolvectl.c | 124 +- src/resolve/resolved-bus.c | 55 +- src/resolve/resolved-conf.c | 44 +- src/resolve/resolved-conf.h | 9 + src/resolve/resolved-def.h | 4 + src/resolve/resolved-dns-answer.c | 2 +- src/resolve/resolved-dns-cache.c | 22 +- src/resolve/resolved-dns-cache.h | 2 + src/resolve/resolved-dns-dnssec.c | 26 +- src/resolve/resolved-dns-dnssec.h | 3 +- src/resolve/resolved-dns-packet.c | 312 +- src/resolve/resolved-dns-packet.h | 133 +- src/resolve/resolved-dns-query.c | 17 +- src/resolve/resolved-dns-query.h | 2 + src/resolve/resolved-dns-rr.c | 435 ++- src/resolve/resolved-dns-rr.h | 33 + src/resolve/resolved-dns-scope.c | 92 +- src/resolve/resolved-dns-scope.h | 7 +- src/resolve/resolved-dns-server.c | 23 +- src/resolve/resolved-dns-server.h | 9 +- src/resolve/resolved-dns-stream.c | 4 +- src/resolve/resolved-dns-stub.c | 27 +- src/resolve/resolved-dns-synthesize.c | 19 +- src/resolve/resolved-dns-synthesize.h | 2 + src/resolve/resolved-dns-transaction.c | 282 +- src/resolve/resolved-dns-transaction.h | 2 + src/resolve/resolved-dns-trust-anchor.c | 12 +- src/resolve/resolved-dns-zone.c | 4 +- src/resolve/resolved-dnssd-bus.c | 16 +- src/resolve/resolved-dnssd-gperf.gperf | 15 +- src/resolve/resolved-dnssd.c | 116 +- src/resolve/resolved-dnssd.h | 11 +- src/resolve/resolved-dnstls-openssl.c | 3 - src/resolve/resolved-etc-hosts.c | 19 +- src/resolve/resolved-link-bus.c | 91 +- src/resolve/resolved-link.c | 2 +- src/resolve/resolved-manager.c | 136 +- src/resolve/resolved-manager.h | 3 +- src/resolve/resolved-mdns.c | 43 + src/resolve/resolved-util.c | 2 +- src/resolve/resolved-varlink.c | 786 ++++- src/resolve/resolved.c | 2 +- src/resolve/test-resolved-dummy-server.c | 450 +++ src/resolve/test-resolved-packet.c | 187 ++ src/resolve/test-resolved-stream.c | 19 +- src/rpm/macros.systemd.in | 4 - src/run/meson.build | 14 + src/run/run.c | 609 +++- src/run/systemd-run0.in | 23 + src/shared/ask-password-api.c | 114 +- src/shared/ask-password-api.h | 18 +- src/shared/async.c | 2 +- src/shared/bitmap.c | 4 +- src/shared/blockdev-util.c | 98 +- src/shared/blockdev-util.h | 1 + src/shared/boot-entry.c | 36 +- src/shared/boot-entry.h | 4 +- src/shared/bootspec.c | 604 +++- src/shared/bootspec.h | 24 +- src/shared/bpf-compat.h | 1 + src/shared/bpf-dlopen.c | 106 +- src/shared/bpf-dlopen.h | 53 +- src/shared/bpf-link.c | 4 +- src/shared/bpf-program.c | 2 +- src/shared/btrfs-util.c | 69 +- src/shared/btrfs-util.h | 2 + src/shared/bus-map-properties.c | 11 +- src/shared/bus-polkit.c | 444 ++- src/shared/bus-polkit.h | 31 +- src/shared/bus-print-properties.c | 6 +- src/shared/bus-unit-util.c | 145 +- src/shared/bus-unit-util.h | 14 +- src/shared/bus-util.c | 249 +- src/shared/bus-util.h | 11 + src/shared/bus-wait-for-jobs.c | 216 +- src/shared/bus-wait-for-jobs.h | 15 +- src/shared/bus-wait-for-units.c | 76 +- src/shared/bus-wait-for-units.h | 21 +- src/shared/capsule-util.c | 17 + src/shared/capsule-util.h | 4 + src/shared/cgroup-setup.c | 122 +- src/shared/cgroup-setup.h | 2 + src/shared/cgroup-show.c | 17 +- src/shared/clean-ipc.c | 2 +- src/shared/clock-util.c | 7 +- src/shared/color-util.c | 80 + src/shared/color-util.h | 11 + src/shared/compare-operator.c | 11 +- src/shared/condition.c | 32 +- src/shared/conf-parser.c | 193 +- src/shared/conf-parser.h | 45 +- src/shared/copy.c | 15 +- src/shared/cpu-set-util.c | 77 + src/shared/cpu-set-util.h | 5 + src/shared/creds-util.c | 826 +++-- src/shared/creds-util.h | 32 +- src/shared/cryptsetup-fido2.c | 42 +- src/shared/cryptsetup-fido2.h | 24 +- src/shared/cryptsetup-tpm2.c | 302 ++ src/shared/cryptsetup-tpm2.h | 108 + src/shared/cryptsetup-util.c | 93 +- src/shared/cryptsetup-util.h | 85 +- src/shared/daemon-util.c | 16 + src/shared/daemon-util.h | 7 +- src/shared/data-fd-util.c | 40 +- src/shared/data-fd-util.h | 11 +- src/shared/dev-setup.c | 64 +- src/shared/discover-image.c | 499 ++- src/shared/discover-image.h | 4 + src/shared/dissect-image.c | 453 ++- src/shared/dissect-image.h | 18 +- src/shared/dlfcn-util.c | 66 - src/shared/dlfcn-util.h | 39 - src/shared/dns-domain.c | 41 +- src/shared/dns-domain.h | 1 + src/shared/dropin.c | 7 +- src/shared/edit-util.c | 151 +- src/shared/edit-util.h | 19 +- src/shared/efi-api.c | 41 +- src/shared/efi-loader.c | 2 +- src/shared/elf-util.c | 96 +- src/shared/ethtool-util.c | 8 +- src/shared/exec-util.c | 32 +- src/shared/fdset.c | 9 +- src/shared/find-esp.c | 40 +- src/shared/firewall-util-iptables.c | 16 +- src/shared/firewall-util-nft.c | 2 +- src/shared/firewall-util.h | 2 +- src/shared/format-table.c | 133 +- src/shared/format-table.h | 13 +- src/shared/fstab-util.c | 138 +- src/shared/fstab-util.h | 15 +- src/shared/generator.c | 106 +- src/shared/generator.h | 17 +- src/shared/group-record.c | 32 +- src/shared/hibernate-util.c | 51 +- src/shared/hibernate-util.h | 2 + src/shared/hostname-setup.c | 92 +- src/shared/hwdb-util.c | 16 +- src/shared/hwdb-util.h | 1 + src/shared/idn-util.c | 22 +- src/shared/idn-util.h | 14 +- src/shared/image-policy.c | 106 + src/shared/image-policy.h | 10 +- src/shared/in-addr-prefix-util.c | 6 +- src/shared/initreq.h | 10 +- src/shared/install-file.c | 2 +- src/shared/install-file.h | 2 - src/shared/install-printf.c | 17 +- src/shared/install.c | 505 +-- src/shared/install.h | 23 +- src/shared/journal-file-util.c | 33 +- src/shared/journal-file-util.h | 1 - src/shared/journal-importer.c | 14 +- src/shared/journal-util.c | 4 +- src/shared/journal-util.h | 2 +- src/shared/json.c | 187 +- src/shared/json.h | 77 +- src/shared/kbd-util.c | 45 +- src/shared/kbd-util.h | 8 +- src/shared/kernel-config.c | 72 + src/shared/kernel-config.h | 10 + src/shared/keyring-util.c | 35 - src/shared/keyring-util.h | 11 - src/shared/killall.c | 16 +- src/shared/label-util.c | 9 +- src/shared/label-util.h | 6 +- src/shared/libarchive-util.c | 67 + src/shared/libarchive-util.h | 45 + src/shared/libcrypt-util.c | 11 +- src/shared/libfido2-util.c | 143 +- src/shared/libfido2-util.h | 100 +- src/shared/local-addresses.c | 322 +- src/shared/local-addresses.h | 8 +- src/shared/logs-show.c | 642 ++-- src/shared/logs-show.h | 14 +- src/shared/loop-util.c | 65 +- src/shared/loop-util.h | 4 +- src/shared/loopback-setup.c | 6 +- src/shared/machine-credential.c | 100 +- src/shared/machine-credential.h | 16 +- src/shared/machine-id-setup.c | 61 +- src/shared/main-func.h | 49 +- src/shared/meson.build | 56 +- src/shared/mkfs-util.c | 13 +- src/shared/module-util.c | 125 +- src/shared/module-util.h | 50 +- src/shared/mount-setup.c | 285 +- src/shared/mount-setup.h | 8 +- src/shared/mount-util.c | 100 +- src/shared/mount-util.h | 10 +- src/shared/netif-naming-scheme.c | 83 +- src/shared/netif-naming-scheme.h | 7 + src/shared/netif-sriov.c | 2 +- src/shared/netif-util.c | 90 +- src/shared/netif-util.h | 6 + src/shared/nscd-flush.c | 2 +- src/shared/nsresource.c | 330 ++ src/shared/nsresource.h | 10 + src/shared/open-file.c | 12 +- src/shared/open-file.h | 10 +- src/shared/openssl-util.c | 357 +- src/shared/openssl-util.h | 29 + src/shared/pager.c | 2 +- src/shared/pam-util.c | 106 +- src/shared/pam-util.h | 9 + src/shared/parse-helpers.c | 37 +- src/shared/parse-helpers.h | 23 +- src/shared/password-quality-util-passwdqc.c | 17 +- src/shared/password-quality-util-passwdqc.h | 14 +- src/shared/password-quality-util-pwquality.c | 35 +- src/shared/password-quality-util-pwquality.h | 18 +- src/shared/pcre2-util.c | 19 +- src/shared/pcre2-util.h | 16 +- src/shared/pcrextend-util.c | 2 +- src/shared/pe-binary.c | 5 +- src/shared/pkcs11-util.c | 1033 +++++- src/shared/pkcs11-util.h | 47 +- src/shared/pretty-print.c | 198 +- src/shared/pretty-print.h | 7 + src/shared/ptyfwd.c | 516 ++- src/shared/ptyfwd.h | 17 +- src/shared/qrcode-util.c | 9 +- src/shared/reboot-util.c | 10 + src/shared/reboot-util.h | 1 + src/shared/resize-fs.c | 4 +- src/shared/rm-rf.c | 2 +- src/shared/seccomp-util.c | 2 +- src/shared/selinux-util.c | 22 +- src/shared/serialize.c | 2 +- src/shared/service-util.c | 25 +- src/shared/sleep-config.c | 78 +- src/shared/sleep-config.h | 24 +- src/shared/socket-netlink.c | 72 +- src/shared/socket-netlink.h | 2 + src/shared/specifier.c | 50 +- src/shared/switch-root.c | 27 +- src/shared/tests.c | 15 +- src/shared/tests.h | 242 +- src/shared/tpm2-util.c | 878 +++-- src/shared/tpm2-util.h | 32 +- src/shared/udev-util.c | 92 +- src/shared/udev-util.h | 5 +- src/shared/user-record-nss.c | 124 +- src/shared/user-record-show.c | 81 +- src/shared/user-record.c | 299 +- src/shared/user-record.h | 19 + src/shared/userdb.c | 10 +- src/shared/varlink-idl.c | 130 +- src/shared/varlink-io.systemd.BootControl.c | 59 + src/shared/varlink-io.systemd.BootControl.h | 6 + src/shared/varlink-io.systemd.Credentials.c | 42 + src/shared/varlink-io.systemd.Credentials.h | 6 + src/shared/varlink-io.systemd.Hostname.c | 39 + src/shared/varlink-io.systemd.Hostname.h | 6 + src/shared/varlink-io.systemd.Machine.c | 25 + src/shared/varlink-io.systemd.Machine.h | 6 + src/shared/varlink-io.systemd.MountFileSystem.c | 69 + src/shared/varlink-io.systemd.MountFileSystem.h | 6 + src/shared/varlink-io.systemd.NamespaceResource.c | 62 + src/shared/varlink-io.systemd.NamespaceResource.h | 6 + src/shared/varlink-io.systemd.Network.c | 58 + src/shared/varlink-io.systemd.Network.h | 6 + src/shared/varlink-io.systemd.PCRLock.c | 25 + src/shared/varlink-io.systemd.PCRLock.h | 6 + src/shared/varlink-io.systemd.Resolve.Monitor.c | 92 +- src/shared/varlink-io.systemd.Resolve.c | 134 +- src/shared/varlink-io.systemd.Resolve.h | 3 + src/shared/varlink.c | 731 +++- src/shared/varlink.h | 61 +- src/shared/verbs.c | 2 +- src/shared/vpick.c | 699 ++++ src/shared/vpick.h | 61 + src/shared/wall.c | 5 +- src/shared/watchdog.c | 44 +- src/shared/wifi-util.c | 2 +- src/shutdown/detach-dm.c | 2 +- src/shutdown/detach-md.c | 2 +- src/shutdown/shutdown.c | 36 +- src/shutdown/umount.c | 2 +- src/sleep/battery-capacity.c | 8 +- src/sleep/sleep.c | 90 +- src/sleep/sleep.conf | 1 + src/socket-activate/socket-activate.c | 4 +- src/socket-proxy/socket-proxyd.c | 3 +- src/ssh-generator/20-systemd-ssh-proxy.conf.in | 18 + src/ssh-generator/meson.build | 29 + src/ssh-generator/ssh-generator.c | 495 +++ src/ssh-generator/ssh-proxy.c | 102 + src/stdio-bridge/stdio-bridge.c | 4 +- src/storagetm/storagetm.c | 33 +- src/sysext/sysext.c | 1007 +++++- src/systemctl/fuzz-systemctl-parse-argv.c | 4 + src/systemctl/meson.build | 9 +- src/systemctl/systemctl-compat-shutdown.c | 2 +- src/systemctl/systemctl-compat-telinit.c | 8 - src/systemctl/systemctl-compat-telinit.h | 1 - src/systemctl/systemctl-edit.c | 34 +- src/systemctl/systemctl-enable.c | 61 +- src/systemctl/systemctl-kill.c | 27 +- src/systemctl/systemctl-list-jobs.c | 4 +- src/systemctl/systemctl-list-unit-files.c | 2 +- src/systemctl/systemctl-list-units.c | 68 +- src/systemctl/systemctl-logind.c | 11 +- src/systemctl/systemctl-mount.c | 2 +- src/systemctl/systemctl-show.c | 143 +- src/systemctl/systemctl-start-special.c | 24 +- src/systemctl/systemctl-start-unit.c | 63 +- src/systemctl/systemctl-sysv-compat.c | 2 +- src/systemctl/systemctl-util.c | 215 +- src/systemctl/systemctl-util.h | 7 +- src/systemctl/systemctl-whoami.c | 76 +- src/systemctl/systemctl.c | 57 +- src/systemctl/systemctl.h | 3 +- src/systemd/meson.build | 45 +- src/systemd/sd-bus.h | 5 +- src/systemd/sd-dhcp-client-id.h | 61 + src/systemd/sd-dhcp-client.h | 14 +- src/systemd/sd-dhcp-duid.h | 70 + src/systemd/sd-dhcp-lease.h | 4 +- src/systemd/sd-dhcp-option.h | 1 - src/systemd/sd-dhcp-server-lease.h | 33 + src/systemd/sd-dhcp-server.h | 1 + src/systemd/sd-dhcp6-client.h | 7 +- src/systemd/sd-event.h | 1 + src/systemd/sd-id128.h | 1 + src/systemd/sd-journal.h | 2 + src/systemd/sd-lldp-rx.h | 2 - src/systemd/sd-ndisc-neighbor.h | 50 + src/systemd/sd-ndisc-protocol.h | 87 + src/systemd/sd-ndisc-redirect.h | 46 + src/systemd/sd-ndisc-router-solicit.h | 40 + src/systemd/sd-ndisc-router.h | 92 + src/systemd/sd-ndisc.h | 94 +- src/systemd/sd-netlink.h | 4 +- src/systemd/sd-radv.h | 12 +- src/sysupdate/sysupdate-partition.c | 2 +- src/sysupdate/sysupdate-partition.h | 6 +- src/sysupdate/sysupdate-pattern.c | 2 +- src/sysupdate/sysupdate-resource.c | 9 +- src/sysupdate/sysupdate-transfer.c | 91 +- src/sysupdate/sysupdate.c | 18 +- src/sysupdate/sysupdate.h | 12 - src/sysusers/meson.build | 1 - src/sysusers/sysusers.c | 182 +- src/sysv-generator/sysv-generator.c | 4 +- src/test/meson.build | 52 +- src/test/test-acl-util.c | 28 +- src/test/test-af-list.c | 7 +- src/test/test-architecture.c | 35 +- src/test/test-argv-util.c | 5 + src/test/test-arphrd-util.c | 6 +- src/test/test-ask-password-api.c | 13 +- src/test/test-async.c | 70 +- src/test/test-aux-scope.c | 168 + src/test/test-barrier.c | 22 +- src/test/test-bitmap.c | 86 +- src/test/test-blockdev-util.c | 37 + src/test/test-boot-timestamps.c | 6 +- src/test/test-bootspec.c | 31 +- src/test/test-bpf-devices.c | 44 +- src/test/test-bpf-firewall.c | 57 +- src/test/test-bpf-foreign-programs.c | 40 +- src/test/test-bpf-lsm.c | 102 - src/test/test-bpf-restrict-fs.c | 102 + src/test/test-build-path.c | 20 + src/test/test-bus-util.c | 2 +- src/test/test-calendarspec.c | 26 +- src/test/test-cap-list.c | 12 +- src/test/test-capability.c | 38 +- src/test/test-cgroup-mask.c | 26 +- src/test/test-cgroup-setup.c | 30 +- src/test/test-cgroup-unit-default.c | 28 +- src/test/test-cgroup-util.c | 53 +- src/test/test-cgroup.c | 62 +- src/test/test-chase.c | 324 +- src/test/test-color-util.c | 63 + src/test/test-compress.c | 61 +- src/test/test-condition.c | 10 +- src/test/test-conf-parser.c | 116 +- src/test/test-copy.c | 68 +- src/test/test-core-unit.c | 16 +- src/test/test-cpu-set-util.c | 97 +- src/test/test-creds.c | 178 +- src/test/test-cryptolib.c | 8 +- src/test/test-data-fd-util.c | 14 +- src/test/test-date.c | 18 +- src/test/test-dev-setup.c | 1 + src/test/test-devnum-util.c | 2 +- src/test/test-dirent-util.c | 218 ++ src/test/test-dlopen-so.c | 28 + src/test/test-dns-domain.c | 36 +- src/test/test-ellipsize.c | 14 +- src/test/test-engine.c | 10 +- src/test/test-env-file.c | 52 +- src/test/test-env-util.c | 199 +- src/test/test-errno-list.c | 10 +- src/test/test-errno-util.c | 4 +- src/test/test-escape.c | 52 +- src/test/test-ether-addr-util.c | 2 +- src/test/test-exec-util.c | 27 +- src/test/test-execute.c | 339 +- src/test/test-exit-status.c | 4 +- src/test/test-extract-word.c | 344 +- src/test/test-fd-util.c | 92 +- src/test/test-fdset.c | 9 + src/test/test-fileio.c | 158 +- src/test/test-format-table.c | 39 +- src/test/test-format-util.c | 28 +- src/test/test-fs-util.c | 74 +- src/test/test-fstab-util.c | 26 +- src/test/test-glob-util.c | 10 +- src/test/test-gpt.c | 42 +- src/test/test-hashmap-plain.c | 110 +- src/test/test-hashmap.c | 14 +- src/test/test-hexdecoct.c | 62 +- src/test/test-hmac.c | 14 +- src/test/test-hostname-setup.c | 8 +- src/test/test-hostname-util.c | 34 +- src/test/test-id128.c | 44 +- src/test/test-image-policy.c | 33 + src/test/test-import-util.c | 4 +- src/test/test-in-addr-prefix-util.c | 8 +- src/test/test-in-addr-util.c | 63 +- src/test/test-install-root.c | 181 +- src/test/test-iovec-util.c | 57 + src/test/test-ip-protocol-list.c | 4 +- src/test/test-json.c | 209 +- src/test/test-label.c | 156 + src/test/test-list.c | 80 +- src/test/test-load-fragment.c | 58 +- src/test/test-local-addresses.c | 283 +- src/test/test-locale-util.c | 8 +- src/test/test-log.c | 21 + src/test/test-login-util.c | 22 + src/test/test-loop-block.c | 10 +- src/test/test-loopback.c | 2 + src/test/test-macro.c | 374 ++- src/test/test-manager.c | 19 - src/test/test-mempool.c | 8 +- src/test/test-mempress.c | 6 +- src/test/test-memstream-util.c | 6 +- src/test/test-mkdir.c | 35 +- src/test/test-mount-util.c | 73 +- src/test/test-mountpoint-util.c | 101 +- src/test/test-namespace.c | 34 +- src/test/test-net-naming-scheme.c | 2 +- src/test/test-netlink-manual.c | 28 +- src/test/test-nss-hosts.c | 2 +- src/test/test-nulstr-util.c | 10 +- src/test/test-open-file.c | 54 +- src/test/test-openssl.c | 4 +- src/test/test-ordered-set.c | 6 +- src/test/test-os-util.c | 73 +- src/test/test-parse-argument.c | 6 +- src/test/test-parse-helpers.c | 40 +- src/test/test-path-lookup.c | 8 +- src/test/test-path-util.c | 268 +- src/test/test-path.c | 3 +- src/test/test-pidref.c | 225 ++ src/test/test-pretty-print.c | 2 +- src/test/test-prioq.c | 12 +- src/test/test-proc-cmdline.c | 22 +- src/test/test-process-util.c | 107 +- src/test/test-progress-bar.c | 34 + src/test/test-recovery-key.c | 137 + src/test/test-replace-var.c | 4 +- src/test/test-rlimit-util.c | 52 +- src/test/test-sd-hwdb.c | 8 +- src/test/test-seccomp.c | 56 +- src/test/test-secure-bits.c | 10 +- src/test/test-serialize.c | 20 +- src/test/test-set.c | 18 +- src/test/test-sha256.c | 2 +- src/test/test-sleep-config.c | 8 +- src/test/test-socket-bind.c | 4 +- src/test/test-socket-netlink.c | 56 +- src/test/test-socket-util.c | 37 +- src/test/test-specifier.c | 12 +- src/test/test-stat-util.c | 39 +- src/test/test-strbuf.c | 30 +- src/test/test-string-util.c | 445 +-- src/test/test-strip-tab-ansi.c | 18 +- src/test/test-strv.c | 347 +- src/test/test-strxcpyx.c | 38 +- src/test/test-sysctl-util.c | 4 +- src/test/test-taint.c | 14 + src/test/test-terminal-util.c | 13 +- src/test/test-time-util.c | 72 +- src/test/test-tmpfile-util.c | 6 +- src/test/test-tpm2.c | 63 +- src/test/test-udev-util.c | 2 +- src/test/test-uid-alloc-range.c | 93 - src/test/test-uid-classification.c | 93 + src/test/test-uid-range.c | 51 +- src/test/test-unit-file.c | 22 +- src/test/test-unit-name.c | 44 +- src/test/test-user-util.c | 14 +- src/test/test-utf8.c | 4 +- src/test/test-varlink-idl.c | 36 +- src/test/test-varlink.c | 40 +- src/test/test-vpick.c | 171 + src/test/test-watch-pid.c | 8 +- src/test/test-xattr-util.c | 8 +- src/test/test-xml.c | 2 +- src/timedate/timedatectl.c | 26 +- src/timedate/timedated.c | 76 +- src/timesync/meson.build | 3 +- src/timesync/timesyncd-bus.c | 10 +- src/timesync/timesyncd-conf.c | 9 +- src/timesync/timesyncd-manager.c | 2 +- src/timesync/timesyncd-manager.h | 3 +- src/timesync/timesyncd-server.h | 4 +- src/timesync/timesyncd.c | 2 +- src/timesync/wait-sync.c | 2 +- src/tmpfiles/meson.build | 1 - src/tmpfiles/tmpfiles.c | 1064 +++--- src/tpm2-setup/meson.build | 6 + src/tpm2-setup/tpm2-generator.c | 80 + .../tty-ask-password-agent.c | 20 +- src/udev/ata_id/ata_id.c | 14 +- src/udev/cdrom_id/cdrom_id.c | 6 +- src/udev/dmi_memory_id/dmi_memory_id.c | 8 +- src/udev/fido_id/fido_id.c | 6 +- src/udev/meson.build | 4 +- src/udev/net/link-config-gperf.gperf | 4 + src/udev/net/link-config.c | 359 +- src/udev/net/link-config.h | 14 +- src/udev/scsi_id/scsi.h | 12 +- src/udev/scsi_id/scsi_id.c | 19 +- src/udev/test-udev-format.c | 10 +- src/udev/test-udev-rule-runner.c | 12 +- src/udev/test-udev-spawn.c | 10 +- src/udev/udev-builtin-blkid.c | 70 +- src/udev/udev-builtin-btrfs.c | 6 +- src/udev/udev-builtin-hwdb.c | 42 +- src/udev/udev-builtin-input_id.c | 89 +- src/udev/udev-builtin-keyboard.c | 7 +- src/udev/udev-builtin-kmod.c | 34 +- src/udev/udev-builtin-net_driver.c | 4 +- src/udev/udev-builtin-net_id.c | 191 +- src/udev/udev-builtin-net_setup_link.c | 47 +- src/udev/udev-builtin-path_id.c | 98 +- src/udev/udev-builtin-uaccess.c | 7 +- src/udev/udev-builtin-usb_id.c | 58 +- src/udev/udev-builtin.c | 35 +- src/udev/udev-builtin.h | 11 +- src/udev/udev-event.c | 146 +- src/udev/udev-event.h | 30 +- src/udev/udev-format.c | 6 +- src/udev/udev-format.h | 1 - src/udev/udev-manager.c | 90 +- src/udev/udev-manager.h | 1 + src/udev/udev-node.c | 7 +- src/udev/udev-rules.c | 121 +- src/udev/udev-rules.h | 5 +- src/udev/udev-spawn.c | 94 +- src/udev/udev-spawn.h | 7 +- src/udev/udev-watch.c | 5 +- src/udev/udev-worker.c | 37 +- src/udev/udev-worker.h | 3 + src/udev/udevadm-control.c | 111 +- src/udev/udevadm-hwdb.c | 2 +- src/udev/udevadm-info.c | 2 +- src/udev/udevadm-lock.c | 4 +- src/udev/udevadm-monitor.c | 2 +- src/udev/udevadm-test-builtin.c | 18 +- src/udev/udevadm-test.c | 94 +- src/udev/udevadm-wait.c | 2 +- src/udev/udevadm.c | 2 +- src/udev/udevd.c | 87 +- src/udev/v4l_id/v4l_id.c | 10 - src/ukify/test/test_ukify.py | 29 +- src/ukify/ukify.py | 59 +- src/update-utmp/update-utmp.c | 2 +- src/userdb/20-systemd-userdb.conf.in | 6 + src/userdb/meson.build | 17 + src/userdb/userdbctl.c | 79 +- src/userdb/userdbd-manager.c | 108 +- src/userdb/userdbd.c | 2 +- src/userdb/userwork.c | 32 +- src/varlinkctl/varlinkctl.c | 66 +- src/vconsole/vconsole-setup.c | 118 +- src/veritysetup/veritysetup-generator.c | 2 +- src/veritysetup/veritysetup.c | 6 +- src/version/version.h.in | 2 +- src/vmspawn/meson.build | 14 +- src/vmspawn/test-vmspawn-util.c | 28 + src/vmspawn/vmspawn-mount.c | 67 + src/vmspawn/vmspawn-mount.h | 19 + src/vmspawn/vmspawn-register.c | 86 + src/vmspawn/vmspawn-register.h | 15 + src/vmspawn/vmspawn-scope.c | 310 ++ src/vmspawn/vmspawn-scope.h | 23 + src/vmspawn/vmspawn-settings.c | 10 + src/vmspawn/vmspawn-settings.h | 17 + src/vmspawn/vmspawn-util.c | 369 +- src/vmspawn/vmspawn-util.h | 73 +- src/vmspawn/vmspawn.c | 2080 ++++++++++-- src/volatile-root/volatile-root.c | 2 +- src/vpick/meson.build | 9 + src/vpick/vpick-tool.c | 348 ++ .../xdg-autostart-generator.c | 2 +- 1476 files changed, 88221 insertions(+), 38263 deletions(-) create mode 100644 src/analyze/analyze-architectures.c create mode 100644 src/analyze/analyze-architectures.h create mode 100644 src/basic/build-path.c create mode 100644 src/basic/build-path.h create mode 100644 src/basic/dlfcn-util.c create mode 100644 src/basic/dlfcn-util.h create mode 100644 src/basic/keyring-util.c create mode 100644 src/basic/keyring-util.h create mode 100644 src/basic/linux/magic.h create mode 100644 src/basic/linux/netfilter.h create mode 100644 src/basic/missing_wait.h create mode 100644 src/basic/sha256.c create mode 100644 src/basic/sha256.h delete mode 100644 src/basic/uid-alloc-range.c delete mode 100644 src/basic/uid-alloc-range.h create mode 100644 src/basic/uid-classification.c create mode 100644 src/basic/uid-classification.h create mode 100644 src/boot/efi/proto/cc-measurement.h delete mode 100644 src/core/bpf-lsm.c delete mode 100644 src/core/bpf-lsm.h create mode 100644 src/core/bpf-restrict-fs.c create mode 100644 src/core/bpf-restrict-fs.h create mode 100644 src/core/bpf-restrict-ifaces.c create mode 100644 src/core/bpf-restrict-ifaces.h delete mode 100644 src/core/restrict-ifaces.c delete mode 100644 src/core/restrict-ifaces.h create mode 100644 src/core/taint.c create mode 100644 src/core/taint.h create mode 100644 src/creds/io.systemd.credentials.policy delete mode 100644 src/cryptsetup/cryptsetup-tpm2.c delete mode 100644 src/cryptsetup/cryptsetup-tpm2.h create mode 100644 src/fundamental/sha256-fundamental.c create mode 100644 src/fundamental/sha256-fundamental.h delete mode 100644 src/fundamental/sha256.c delete mode 100644 src/fundamental/sha256.h create mode 100644 src/home/homework-blob.c create mode 100644 src/home/homework-blob.h create mode 100644 src/home/test-homed-regression-31896.c create mode 100644 src/import/importctl.c create mode 100644 src/journal/journalctl-authenticate.c create mode 100644 src/journal/journalctl-authenticate.h create mode 100644 src/journal/journalctl-catalog.c create mode 100644 src/journal/journalctl-catalog.h create mode 100644 src/journal/journalctl-filter.c create mode 100644 src/journal/journalctl-filter.h create mode 100644 src/journal/journalctl-misc.c create mode 100644 src/journal/journalctl-misc.h create mode 100644 src/journal/journalctl-show.c create mode 100644 src/journal/journalctl-show.h create mode 100644 src/journal/journalctl-util.c create mode 100644 src/journal/journalctl-util.h create mode 100644 src/journal/journalctl-varlink.c create mode 100644 src/journal/journalctl-varlink.h create mode 100644 src/journal/journalctl.h create mode 100644 src/journal/journald-socket.c create mode 100644 src/journal/journald-socket.h create mode 100644 src/journal/test-journald-rate-limit.c create mode 100644 src/libsystemd-network/dhcp-client-id-internal.h create mode 100644 src/libsystemd-network/dhcp-duid-internal.h delete mode 100644 src/libsystemd-network/dhcp-identifier.c delete mode 100644 src/libsystemd-network/dhcp-identifier.h create mode 100644 src/libsystemd-network/dhcp-server-lease-internal.h create mode 100644 src/libsystemd-network/icmp6-packet.c create mode 100644 src/libsystemd-network/icmp6-packet.h create mode 100644 src/libsystemd-network/icmp6-test-util.c create mode 100644 src/libsystemd-network/icmp6-test-util.h delete mode 100644 src/libsystemd-network/icmp6-util-unix.c delete mode 100644 src/libsystemd-network/icmp6-util-unix.h create mode 100644 src/libsystemd-network/ndisc-neighbor-internal.h create mode 100644 src/libsystemd-network/ndisc-option.c create mode 100644 src/libsystemd-network/ndisc-option.h delete mode 100644 src/libsystemd-network/ndisc-protocol.c delete mode 100644 src/libsystemd-network/ndisc-protocol.h create mode 100644 src/libsystemd-network/ndisc-redirect-internal.h create mode 100644 src/libsystemd-network/ndisc-router-internal.h create mode 100644 src/libsystemd-network/ndisc-router-solicit-internal.h delete mode 100644 src/libsystemd-network/ndisc-router.c delete mode 100644 src/libsystemd-network/ndisc-router.h create mode 100644 src/libsystemd-network/sd-dhcp-client-id.c create mode 100644 src/libsystemd-network/sd-dhcp-duid.c create mode 100644 src/libsystemd-network/sd-dhcp-server-lease.c create mode 100644 src/libsystemd-network/sd-ndisc-neighbor.c create mode 100644 src/libsystemd-network/sd-ndisc-redirect.c create mode 100644 src/libsystemd-network/sd-ndisc-router-solicit.c create mode 100644 src/libsystemd-network/sd-ndisc-router.c create mode 100644 src/libsystemd-network/test-ndisc-send.c create mode 100644 src/machine/machine-varlink.c create mode 100644 src/machine/machine-varlink.h create mode 100644 src/mountfsd/io.systemd.mount-file-system.policy create mode 100644 src/mountfsd/meson.build create mode 100644 src/mountfsd/mountfsd-manager.c create mode 100644 src/mountfsd/mountfsd-manager.h create mode 100644 src/mountfsd/mountfsd.c create mode 100644 src/mountfsd/mountwork.c create mode 100644 src/network/networkctl-config-file.c create mode 100644 src/network/networkctl-config-file.h create mode 100644 src/network/networkctl.h create mode 100644 src/network/networkd-dns.c create mode 100644 src/network/networkd-dns.h create mode 100644 src/network/networkd-manager-varlink.c create mode 100644 src/network/networkd-manager-varlink.h create mode 100644 src/network/networkd-ntp.c create mode 100644 src/network/networkd-ntp.h create mode 100644 src/network/networkd-route-metric.c create mode 100644 src/network/networkd-route-metric.h create mode 100644 src/network/networkd-route-nexthop.c create mode 100644 src/network/networkd-route-nexthop.h create mode 100644 src/nsresourced/bpf/userns_restrict/meson.build create mode 100644 src/nsresourced/bpf/userns_restrict/userns-restrict-skel.h create mode 100644 src/nsresourced/bpf/userns_restrict/userns-restrict.bpf.c create mode 100644 src/nsresourced/meson.build create mode 100644 src/nsresourced/nsresourced-manager.c create mode 100644 src/nsresourced/nsresourced-manager.h create mode 100644 src/nsresourced/nsresourced.c create mode 100644 src/nsresourced/nsresourcework.c create mode 100644 src/nsresourced/test-userns-restrict.c create mode 100644 src/nsresourced/userns-registry.c create mode 100644 src/nsresourced/userns-registry.h create mode 100644 src/nsresourced/userns-restrict.c create mode 100644 src/nsresourced/userns-restrict.h create mode 100644 src/resolve/test-resolved-dummy-server.c create mode 100644 src/run/systemd-run0.in create mode 100644 src/shared/capsule-util.c create mode 100644 src/shared/capsule-util.h create mode 100644 src/shared/color-util.c create mode 100644 src/shared/color-util.h create mode 100644 src/shared/cryptsetup-tpm2.c create mode 100644 src/shared/cryptsetup-tpm2.h delete mode 100644 src/shared/dlfcn-util.c delete mode 100644 src/shared/dlfcn-util.h create mode 100644 src/shared/kernel-config.c create mode 100644 src/shared/kernel-config.h delete mode 100644 src/shared/keyring-util.c delete mode 100644 src/shared/keyring-util.h create mode 100644 src/shared/libarchive-util.c create mode 100644 src/shared/libarchive-util.h create mode 100644 src/shared/nsresource.c create mode 100644 src/shared/nsresource.h create mode 100644 src/shared/varlink-io.systemd.BootControl.c create mode 100644 src/shared/varlink-io.systemd.BootControl.h create mode 100644 src/shared/varlink-io.systemd.Credentials.c create mode 100644 src/shared/varlink-io.systemd.Credentials.h create mode 100644 src/shared/varlink-io.systemd.Hostname.c create mode 100644 src/shared/varlink-io.systemd.Hostname.h create mode 100644 src/shared/varlink-io.systemd.Machine.c create mode 100644 src/shared/varlink-io.systemd.Machine.h create mode 100644 src/shared/varlink-io.systemd.MountFileSystem.c create mode 100644 src/shared/varlink-io.systemd.MountFileSystem.h create mode 100644 src/shared/varlink-io.systemd.NamespaceResource.c create mode 100644 src/shared/varlink-io.systemd.NamespaceResource.h create mode 100644 src/shared/varlink-io.systemd.Network.c create mode 100644 src/shared/varlink-io.systemd.Network.h create mode 100644 src/shared/varlink-io.systemd.PCRLock.c create mode 100644 src/shared/varlink-io.systemd.PCRLock.h create mode 100644 src/shared/vpick.c create mode 100644 src/shared/vpick.h create mode 100644 src/ssh-generator/20-systemd-ssh-proxy.conf.in create mode 100644 src/ssh-generator/meson.build create mode 100644 src/ssh-generator/ssh-generator.c create mode 100644 src/ssh-generator/ssh-proxy.c create mode 100644 src/systemd/sd-dhcp-client-id.h create mode 100644 src/systemd/sd-dhcp-duid.h create mode 100644 src/systemd/sd-dhcp-server-lease.h create mode 100644 src/systemd/sd-ndisc-neighbor.h create mode 100644 src/systemd/sd-ndisc-protocol.h create mode 100644 src/systemd/sd-ndisc-redirect.h create mode 100644 src/systemd/sd-ndisc-router-solicit.h create mode 100644 src/systemd/sd-ndisc-router.h create mode 100644 src/test/test-aux-scope.c delete mode 100644 src/test/test-bpf-lsm.c create mode 100644 src/test/test-bpf-restrict-fs.c create mode 100644 src/test/test-build-path.c create mode 100644 src/test/test-color-util.c create mode 100644 src/test/test-dirent-util.c create mode 100644 src/test/test-iovec-util.c create mode 100644 src/test/test-label.c create mode 100644 src/test/test-login-util.c delete mode 100644 src/test/test-manager.c create mode 100644 src/test/test-pidref.c create mode 100644 src/test/test-progress-bar.c create mode 100644 src/test/test-recovery-key.c create mode 100644 src/test/test-taint.c delete mode 100644 src/test/test-uid-alloc-range.c create mode 100644 src/test/test-uid-classification.c create mode 100644 src/test/test-vpick.c create mode 100644 src/tpm2-setup/tpm2-generator.c create mode 100644 src/userdb/20-systemd-userdb.conf.in create mode 100644 src/vmspawn/test-vmspawn-util.c create mode 100644 src/vmspawn/vmspawn-mount.c create mode 100644 src/vmspawn/vmspawn-mount.h create mode 100644 src/vmspawn/vmspawn-register.c create mode 100644 src/vmspawn/vmspawn-register.h create mode 100644 src/vmspawn/vmspawn-scope.c create mode 100644 src/vmspawn/vmspawn-scope.h create mode 100644 src/vpick/meson.build create mode 100644 src/vpick/vpick-tool.c (limited to 'src') diff --git a/src/ac-power/ac-power.c b/src/ac-power/ac-power.c index fadf1da..af76b81 100644 --- a/src/ac-power/ac-power.c +++ b/src/ac-power/ac-power.c @@ -83,8 +83,7 @@ static int run(int argc, char *argv[]) { /* This is mostly intended to be used for scripts which want * to detect whether AC power is plugged in or not. */ - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/analyze/analyze-architectures.c b/src/analyze/analyze-architectures.c new file mode 100644 index 0000000..2d155d5 --- /dev/null +++ b/src/analyze/analyze-architectures.c @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "analyze.h" +#include "analyze-architectures.h" +#include "format-table.h" + +static int add_arch(Table *t, Architecture a) { + const char *c, *color; + int r; + + assert(t); + + if (a == native_architecture()) { + c = "native"; + color = ANSI_HIGHLIGHT_GREEN; + } else if (a == uname_architecture()) { + c = "uname"; + color = ANSI_HIGHLIGHT; +#ifdef ARCHITECTURE_SECONDARY + } else if (a == ARCHITECTURE_SECONDARY) { + c = "secondary"; + color = NULL; +#endif + } else { + c = "foreign"; + color = ANSI_GREY; + } + + r = table_add_many(t, + TABLE_INT, (int) a, + TABLE_STRING, architecture_to_string(a), + TABLE_STRING, c, + TABLE_SET_COLOR, color); + if (r < 0) + return table_log_add_error(r); + + return 0; +} + +int verb_architectures(int argc, char *argv[], void *userdata) { + _cleanup_(table_unrefp) Table *table = NULL; + int r; + + table = table_new("id", "name", "support"); + if (!table) + return log_oom(); + + (void) table_hide_column_from_display(table, (size_t) 0); + + if (strv_isempty(strv_skip(argv, 1))) + for (Architecture a = 0; a < _ARCHITECTURE_MAX; a++) { + r = add_arch(table, a); + if (r < 0) + return r; + } + else { + STRV_FOREACH(as, strv_skip(argv, 1)) { + Architecture a; + + if (streq(*as, "native")) + a = native_architecture(); + else if (streq(*as, "uname")) + a = uname_architecture(); + else if (streq(*as, "secondary")) { +#ifdef ARCHITECTURE_SECONDARY + a = ARCHITECTURE_SECONDARY; +#else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No secondary architecture."); +#endif + } else + a = architecture_from_string(*as); + if (a < 0) + return log_error_errno(a, "Architecture \"%s\" not known.", *as); + + r = add_arch(table, a); + if (r < 0) + return r; + } + + (void) table_set_sort(table, (size_t) 0); + } + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + + return EXIT_SUCCESS; +} diff --git a/src/analyze/analyze-architectures.h b/src/analyze/analyze-architectures.h new file mode 100644 index 0000000..06b9473 --- /dev/null +++ b/src/analyze/analyze-architectures.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int verb_architectures(int argc, char *argv[], void *userdata); diff --git a/src/analyze/analyze-capability.c b/src/analyze/analyze-capability.c index 8072175..7cdc0e3 100644 --- a/src/analyze/analyze-capability.c +++ b/src/analyze/analyze-capability.c @@ -46,11 +46,9 @@ int verb_capabilities(int argc, char *argv[], void *userdata) { (void) table_set_sort(table, (size_t) 1); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) - return r; + return log_error_errno(r, "Failed to output table: %m"); return EXIT_SUCCESS; } diff --git a/src/analyze/analyze-cat-config.c b/src/analyze/analyze-cat-config.c index 66bbbc1..b480d4a 100644 --- a/src/analyze/analyze-cat-config.c +++ b/src/analyze/analyze-cat-config.c @@ -4,7 +4,6 @@ #include "analyze-cat-config.h" #include "conf-files.h" #include "constants.h" -#include "nulstr-util.h" #include "path-util.h" #include "pretty-print.h" #include "strv.h" @@ -23,7 +22,7 @@ int verb_cat_config(int argc, char *argv[], void *userdata) { print_separator(); if (path_is_absolute(*arg)) { - NULSTR_FOREACH(dir, CONF_PATHS_NULSTR("")) { + FOREACH_STRING(dir, CONF_PATHS("")) { t = path_startswith(*arg, dir); if (t) break; @@ -35,7 +34,7 @@ int verb_cat_config(int argc, char *argv[], void *userdata) { } else t = *arg; - r = conf_files_cat(arg_root, t, arg_cat_flags); + r = conf_files_cat(arg_root, t, arg_cat_flags | CAT_FORMAT_HAS_SECTIONS); if (r < 0) return r; } diff --git a/src/analyze/analyze-critical-chain.c b/src/analyze/analyze-critical-chain.c index 4a7f452..7d78de3 100644 --- a/src/analyze/analyze-critical-chain.c +++ b/src/analyze/analyze-critical-chain.c @@ -23,23 +23,20 @@ static int list_dependencies_print( UnitTimes *times, BootTimes *boot) { - for (unsigned i = level; i != 0; i--) + for (unsigned i = level; i > 0; i--) printf("%s", special_glyph(branches & (1 << (i-1)) ? SPECIAL_GLYPH_TREE_VERTICAL : SPECIAL_GLYPH_TREE_SPACE)); printf("%s", special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH)); - if (times) { + if (times && times->activating >= boot->userspace_time) { if (timestamp_is_set(times->time)) - printf("%s%s @%s +%s%s", ansi_highlight_red(), name, + printf("%s%s @%s +%s%s\n", ansi_highlight_red(), name, FORMAT_TIMESPAN(times->activating - boot->userspace_time, USEC_PER_MSEC), FORMAT_TIMESPAN(times->time, USEC_PER_MSEC), ansi_normal()); - else if (times->activated > boot->userspace_time) - printf("%s @%s", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC)); else - printf("%s", name); + printf("%s @%s\n", name, FORMAT_TIMESPAN(times->activated - boot->userspace_time, USEC_PER_MSEC)); } else - printf("%s", name); - printf("\n"); + printf("%s\n", name); return 0; } diff --git a/src/analyze/analyze-dot.c b/src/analyze/analyze-dot.c index bf8aa81..9e92d59 100644 --- a/src/analyze/analyze-dot.c +++ b/src/analyze/analyze-dot.c @@ -13,14 +13,15 @@ static int graph_one_property( const UnitInfo *u, const char *prop, const char *color, - char *patterns[], - char *from_patterns[], - char *to_patterns[]) { + char **patterns, + char **from_patterns, + char **to_patterns) { _cleanup_strv_free_ char **units = NULL; bool match_patterns; int r; + assert(bus); assert(u); assert(prop); assert(color); @@ -51,7 +52,13 @@ static int graph_one_property( return 0; } -static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { +static int graph_one( + sd_bus *bus, + const UnitInfo *u, + char **patterns, + char **from_patterns, + char **to_patterns) { + int r; assert(bus); @@ -67,12 +74,19 @@ static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *fro r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); if (r < 0) return r; + r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); if (r < 0) return r; + + r = graph_one_property(bus, u, "BindsTo", "gold", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); if (r < 0) return r; + r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); if (r < 0) return r; @@ -85,6 +99,9 @@ static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { _cleanup_strv_free_ char **expanded_patterns = NULL; int r; + assert(bus); + assert(ret); + STRV_FOREACH(pattern, patterns) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *unit = NULL, *unit_id = NULL; @@ -110,10 +127,9 @@ static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { if (r < 0) return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); - if (!streq(*pattern, unit_id)) { + if (!streq(*pattern, unit_id)) if (strv_extend(&expanded_patterns, unit_id) < 0) return log_oom(); - } } *ret = TAKE_PTR(expanded_patterns); /* do not free */ @@ -128,8 +144,8 @@ int verb_dot(int argc, char *argv[], void *userdata) { _cleanup_strv_free_ char **expanded_patterns = NULL; _cleanup_strv_free_ char **expanded_from_patterns = NULL; _cleanup_strv_free_ char **expanded_to_patterns = NULL; - int r; UnitInfo u; + int r; r = acquire_bus(&bus, NULL); if (r < 0) @@ -170,6 +186,7 @@ int verb_dot(int argc, char *argv[], void *userdata) { log_info(" Color legend: black = Requires\n" " dark blue = Requisite\n" + " gold = BindsTo\n" " dark grey = Wants\n" " red = Conflicts\n" " green = After\n"); diff --git a/src/analyze/analyze-exit-status.c b/src/analyze/analyze-exit-status.c index 3a8d3f4..1032f1a 100644 --- a/src/analyze/analyze-exit-status.c +++ b/src/analyze/analyze-exit-status.c @@ -46,11 +46,9 @@ int verb_exit_status(int argc, char *argv[], void *userdata) { return table_log_add_error(r); } - pager_open(arg_pager_flags); - - r = table_print(table, NULL); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) - return r; + return log_error_errno(r, "Failed to output table: %m"); return EXIT_SUCCESS; } diff --git a/src/analyze/analyze-fdstore.c b/src/analyze/analyze-fdstore.c index 13db7f5..8ada6d4 100644 --- a/src/analyze/analyze-fdstore.c +++ b/src/analyze/analyze-fdstore.c @@ -81,12 +81,12 @@ static int dump_fdstore(sd_bus *bus, const char *arg) { if (r < 0) return r; - if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_get_rows(table) <= 0) + if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_isempty(table)) 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 r; } return EXIT_SUCCESS; diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 0146b50..7d4f549 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -94,6 +94,8 @@ int verb_image_policy(int argc, char *argv[], void *userdata) { p = &image_policy_sysext_strict; else if (streq(argv[i], "@confext")) p = &image_policy_confext; + else if (streq(argv[i], "@confext-strict")) + p = &image_policy_confext_strict; else if (streq(argv[i], "@container")) p = &image_policy_container; else if (streq(argv[i], "@service")) diff --git a/src/analyze/analyze-pcrs.c b/src/analyze/analyze-pcrs.c index ed907f7..43e415f 100644 --- a/src/analyze/analyze-pcrs.c +++ b/src/analyze/analyze-pcrs.c @@ -48,7 +48,7 @@ static int get_current_pcr(const char *alg, uint32_t pcr, void **ret, size_t *re if (r < 0) return log_error_errno(r, "Failed to read '%s': %m", p); - r = unhexmem(s, ss, &buf, &bufsize); + r = unhexmem_full(s, ss, /* secure = */ false, &buf, &bufsize); if (r < 0) return log_error_errno(r, "Failed to decode hex PCR data '%s': %m", s); diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c index 81fc25b..e271296 100644 --- a/src/analyze/analyze-plot.c +++ b/src/analyze/analyze-plot.c @@ -168,7 +168,7 @@ static void plot_tooltip(const UnitTimes *ut) { svg("%s:\n", ut->name); UnitDependency i; - VA_ARGS_FOREACH(i, UNIT_AFTER, UNIT_BEFORE, UNIT_REQUIRES, UNIT_REQUISITE, UNIT_WANTS, UNIT_CONFLICTS, UNIT_UPHOLDS) + FOREACH_ARGUMENT(i, UNIT_AFTER, UNIT_BEFORE, UNIT_REQUIRES, UNIT_REQUISITE, UNIT_WANTS, UNIT_CONFLICTS, UNIT_UPHOLDS) if (!strv_isempty(ut->deps[i])) { svg("\n%s:\n", unit_dependency_to_string(i)); STRV_FOREACH(s, ut->deps[i]) @@ -316,7 +316,10 @@ static int produce_plot_as_svg( strempty(host->virtualization)); svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); - svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); + if (boot->soft_reboots_count > 0) + svg_graph_box(m, 0, boot->finish_time); + else + svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); if (timestamp_is_set(boot->firmware_time)) { svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); @@ -344,6 +347,11 @@ static int produce_plot_as_svg( svg_text(true, boot->initrd_time, y, "initrd"); y++; } + if (boot->soft_reboots_count > 0) { + svg_bar("soft-reboot", 0, boot->userspace_time, y); + svg_text(true, 0, y, "soft-reboot"); + y++; + } for (u = times; u->has_data; u++) { if (u->activating >= boot->userspace_time) @@ -402,7 +410,7 @@ static int show_table(Table *table, const char *word) { assert(table); assert(word); - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { table_set_header(table, arg_legend); if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) @@ -414,10 +422,10 @@ static int show_table(Table *table, const char *word) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); - else + if (table_isempty(table)) printf("No %s.\n", word); + else + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); } return 0; diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 5f1b5e6..75508f4 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -215,20 +215,21 @@ static int assess_user( uint64_t *ret_badness, char **ret_description) { - _cleanup_free_ char *d = NULL; + const char *d; uint64_t b; + int r; assert(ret_badness); assert(ret_description); if (streq_ptr(info->user, NOBODY_USER_NAME)) { - d = strdup("Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services"); + d = "Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services"; b = 9; } else if (info->dynamic_user && !STR_IN_SET(info->user, "0", "root")) { - d = strdup("Service runs under a transient non-root user identity"); + d = "Service runs under a transient non-root user identity"; b = 0; } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) { - d = strdup("Service runs under a static non-root user identity"); + d = "Service runs under a static non-root user identity"; b = 0; } else { *ret_badness = 10; @@ -236,12 +237,11 @@ static int assess_user( return 0; } - if (!d) - return log_oom(); + r = strdup_to(ret_description, d); + if (r < 0) + return r; *ret_badness = b; - *ret_description = TAKE_PTR(d); - return 0; } @@ -254,7 +254,6 @@ static int assess_protect_home( const char *description; uint64_t badness; - char *copy; int r; assert(ret_badness); @@ -277,13 +276,11 @@ static int assess_protect_home( description = "Service has no access to home directories"; } - copy = strdup(description); - if (!copy) - return log_oom(); + r = strdup_to(ret_description, description); + if (r < 0) + return r; *ret_badness = badness; - *ret_description = copy; - return 0; } @@ -296,7 +293,6 @@ static int assess_protect_system( const char *description; uint64_t badness; - char *copy; int r; assert(ret_badness); @@ -319,13 +315,11 @@ static int assess_protect_system( description = "Service has limited write access to the OS file hierarchy"; } - copy = strdup(description); - if (!copy) - return log_oom(); + r = strdup_to(ret_description, description); + if (r < 0) + return r; *ret_badness = badness; - *ret_description = copy; - return 0; } @@ -370,9 +364,9 @@ static int assess_umask( uint64_t *ret_badness, char **ret_description) { - char *copy = NULL; const char *d; uint64_t b; + int r; assert(ret_badness); assert(ret_description); @@ -394,13 +388,11 @@ static int assess_umask( b = 0; } - copy = strdup(d); - if (!copy) - return log_oom(); + r = strdup_to(ret_description, d); + if (r < 0) + return r; *ret_badness = b; - *ret_description = copy; - return 0; } @@ -537,30 +529,30 @@ static int assess_system_call_architectures( uint64_t *ret_badness, char **ret_description) { - char *d; + const char *d; uint64_t b; + int r; assert(ret_badness); assert(ret_description); if (set_isempty(info->system_call_architectures)) { b = 10; - d = strdup("Service may execute system calls with all ABIs"); + d = "Service may execute system calls with all ABIs"; } else if (set_contains(info->system_call_architectures, "native") && set_size(info->system_call_architectures) == 1) { b = 0; - d = strdup("Service may execute system calls only with native ABI"); + d = "Service may execute system calls only with native ABI"; } else { b = 8; - d = strdup("Service may execute system calls with multiple ABIs"); + d = "Service may execute system calls with multiple ABIs"; } - if (!d) - return log_oom(); + r = strdup_to(ret_description, d); + if (r < 0) + return r; *ret_badness = b; - *ret_description = d; - return 0; } @@ -607,12 +599,12 @@ static int assess_system_call_filter( assert(a->parameter < _SYSCALL_FILTER_SET_MAX); const SyscallFilterSet *f = syscall_filter_sets + a->parameter; - _cleanup_free_ char *d = NULL; + char *d; uint64_t b; int r; if (!info->system_call_filter_allow_list && set_isempty(info->system_call_filter)) { - r = free_and_strdup(&d, "Service does not filter system calls"); + r = strdup_to(&d, "Service does not filter system calls"); b = 10; } else { bool bad; @@ -649,8 +641,8 @@ static int assess_system_call_filter( if (r < 0) return log_oom(); + *ret_description = d; *ret_badness = b; - *ret_description = TAKE_PTR(d); return 0; } @@ -664,36 +656,36 @@ static int assess_ip_address_allow( uint64_t *ret_badness, char **ret_description) { - char *d = NULL; + const char *d; uint64_t b; + int r; assert(info); assert(ret_badness); assert(ret_description); if (info->ip_filters_custom_ingress || info->ip_filters_custom_egress) { - d = strdup("Service defines custom ingress/egress IP filters with BPF programs"); + d = "Service defines custom ingress/egress IP filters with BPF programs"; b = 0; } else if (!info->ip_address_deny_all) { - d = strdup("Service does not define an IP address allow list"); + d = "Service does not define an IP address allow list"; b = 10; } else if (info->ip_address_allow_other) { - d = strdup("Service defines IP address allow list with non-localhost entries"); + d = "Service defines IP address allow list with non-localhost entries"; b = 5; } else if (info->ip_address_allow_localhost) { - d = strdup("Service defines IP address allow list with only localhost entries"); + d = "Service defines IP address allow list with only localhost entries"; b = 2; } else { - d = strdup("Service blocks all IP address ranges"); + d = "Service blocks all IP address ranges"; b = 0; } - if (!d) - return log_oom(); + r = strdup_to(ret_description, d); + if (r < 0) + return r; *ret_badness = b; - *ret_description = d; - return 0; } @@ -704,7 +696,7 @@ static int assess_device_allow( uint64_t *ret_badness, char **ret_description) { - char *d = NULL; + char *d; uint64_t b; assert(info); @@ -1651,7 +1643,7 @@ static uint64_t access_weight(const struct security_assessor *a, JsonVariant *po assert(a); val = security_assessor_find_in_policy(a, policy, "weight"); - if (val) { + if (val) { if (json_variant_is_unsigned(val)) return json_variant_unsigned(val); log_debug("JSON field 'weight' of policy for %s is not an unsigned integer, ignoring.", a->id); @@ -1666,7 +1658,7 @@ static uint64_t access_range(const struct security_assessor *a, JsonVariant *pol assert(a); val = security_assessor_find_in_policy(a, policy, "range"); - if (val) { + if (val) { if (json_variant_is_unsigned(val)) return json_variant_unsigned(val); log_debug("JSON field 'range' of policy for %s is not an unsigned integer, ignoring.", a->id); @@ -1681,7 +1673,7 @@ static const char *access_description_na(const struct security_assessor *a, Json assert(a); val = security_assessor_find_in_policy(a, policy, "description_na"); - if (val) { + if (val) { if (json_variant_is_string(val)) return json_variant_string(val); log_debug("JSON field 'description_na' of policy for %s is not a string, ignoring.", a->id); @@ -1696,7 +1688,7 @@ static const char *access_description_good(const struct security_assessor *a, Js assert(a); val = security_assessor_find_in_policy(a, policy, "description_good"); - if (val) { + if (val) { if (json_variant_is_string(val)) return json_variant_string(val); log_debug("JSON field 'description_good' of policy for %s is not a string, ignoring.", a->id); @@ -1711,7 +1703,7 @@ static const char *access_description_bad(const struct security_assessor *a, Jso assert(a); val = security_assessor_find_in_policy(a, policy, "description_bad"); - if (val) { + if (val) { if (json_variant_is_string(val)) return json_variant_string(val); log_debug("JSON field 'description_bad' of policy for %s is not a string, ignoring.", a->id); @@ -1764,15 +1756,14 @@ static int assess(const SecurityInfo *info, (void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7); } - for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) { - const struct security_assessor *a = security_assessor_table + i; + FOREACH_ELEMENT(a, security_assessor_table) { _cleanup_free_ char *d = NULL; uint64_t badness; void *data; uint64_t weight = access_weight(a, policy); uint64_t range = access_range(a, policy); - data = (uint8_t *) info + a->offset; + data = (uint8_t*) info + a->offset; if (a->default_dependencies_only && !info->default_dependencies) { badness = UINT64_MAX; @@ -2738,7 +2729,7 @@ static int offline_security_checks( /* When a portable image is analyzed, the profile is what provides a good chunk of * the security-related settings, but they are obviously not shipped with the image. - * This allows to take them in consideration. */ + * This allows them to be taken into consideration. */ if (profile) { _cleanup_free_ char *unit_name = NULL, *dropin = NULL, *profile_path = NULL; @@ -2828,7 +2819,6 @@ static int analyze_security(sd_bus *bus, for (;;) { UnitInfo info; - char *copy = NULL; r = bus_parse_unit_info(reply, &info); if (r < 0) @@ -2842,12 +2832,11 @@ static int analyze_security(sd_bus *bus, if (!GREEDY_REALLOC(list, n + 2)) return log_oom(); - copy = strdup(info.id); - if (!copy) - return log_oom(); + r = strdup_to(&list[n], info.id); + if (r < 0) + return r; - list[n++] = copy; - list[n] = NULL; + list[++n] = NULL; } strv_sort(list); diff --git a/src/analyze/analyze-srk.c b/src/analyze/analyze-srk.c index 6faf2c2..acfd8b0 100644 --- a/src/analyze/analyze-srk.c +++ b/src/analyze/analyze-srk.c @@ -38,7 +38,7 @@ int verb_srk(int argc, char *argv[], void *userdata) { "Refusing to write binary data to TTY, please redirect output to file."); if (fwrite(marshalled, 1, marshalled_size, stdout) != marshalled_size) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK to stdout: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write SRK to stdout."); r = fflush_and_check(stdout); if (r < 0) diff --git a/src/analyze/analyze-time-data.c b/src/analyze/analyze-time-data.c index 741cab3..1a26991 100644 --- a/src/analyze/analyze-time-data.c +++ b/src/analyze/analyze-time-data.c @@ -38,6 +38,7 @@ int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) { { "FinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, finish_time) }, { "SecurityStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_start_time) }, { "SecurityFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, security_finish_time) }, + { "ShutdownStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, shutdown_start_time) }, { "GeneratorsStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_start_time) }, { "GeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, generators_finish_time) }, { "UnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, unitsload_start_time) }, @@ -48,6 +49,7 @@ int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) { { "InitRDGeneratorsFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_generators_finish_time) }, { "InitRDUnitsLoadStartTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_start_time) }, { "InitRDUnitsLoadFinishTimestampMonotonic", "t", NULL, offsetof(BootTimes, initrd_unitsload_finish_time) }, + { "SoftRebootsCount", "t", NULL, offsetof(BootTimes, soft_reboots_count) }, {}, }; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -81,7 +83,25 @@ int acquire_boot_times(sd_bus *bus, bool require_finished, BootTimes **ret) { 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) { + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && times.soft_reboots_count > 0) { + /* On soft-reboot ignore kernel/firmware/initrd times as they are from the previous boot */ + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = + times.initrd_security_start_time = times.initrd_security_finish_time = + times.initrd_generators_start_time = times.initrd_generators_finish_time = + times.initrd_unitsload_start_time = times.initrd_unitsload_finish_time = 0; + times.reverse_offset = times.shutdown_start_time; + + /* Clamp all timestamps to avoid showing huge graphs */ + if (timestamp_is_set(times.finish_time)) + subtract_timestamp(×.finish_time, times.reverse_offset); + subtract_timestamp(×.userspace_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && timestamp_is_set(times.security_start_time)) { /* security_start_time is set when systemd is not running under container environment. */ if (times.initrd_time > 0) times.kernel_done_time = times.initrd_time; @@ -183,6 +203,8 @@ int pretty_boot_time(sd_bus *bus, char **ret) { return log_oom(); if (timestamp_is_set(t->initrd_time) && !strextend(&text, FORMAT_TIMESPAN(t->userspace_time - t->initrd_time, USEC_PER_MSEC), " (initrd) + ")) return log_oom(); + if (t->soft_reboots_count > 0 && strextendf(&text, "%s (soft reboot #%" PRIu64 ") + ", FORMAT_TIMESPAN(t->userspace_time, USEC_PER_MSEC), t->soft_reboots_count) < 0) + return log_oom(); if (!strextend(&text, FORMAT_TIMESPAN(t->finish_time - t->userspace_time, USEC_PER_MSEC), " (userspace) ")) return log_oom(); @@ -192,7 +214,13 @@ int pretty_boot_time(sd_bus *bus, char **ret) { return log_oom(); if (unit_id && timestamp_is_set(activated_time)) { - usec_t base = timestamp_is_set(t->userspace_time) ? t->userspace_time : t->reverse_offset; + usec_t base; + + /* On soft-reboot times are clamped to avoid showing huge graphs */ + if (t->soft_reboots_count > 0 && timestamp_is_set(t->userspace_time)) + base = t->userspace_time + t->reverse_offset; + else + base = timestamp_is_set(t->userspace_time) ? t->userspace_time : t->reverse_offset; if (!strextend(&text, "\n", unit_id, " reached after ", FORMAT_TIMESPAN(activated_time - base, USEC_PER_MSEC), " in userspace.")) return log_oom(); @@ -221,7 +249,7 @@ void unit_times_clear(UnitTimes *t) { if (!t) return; - FOREACH_ARRAY(d, t->deps, ELEMENTSOF(t->deps)) + FOREACH_ELEMENT(d, t->deps) *d = strv_free(*d); t->name = mfree(t->name); @@ -299,10 +327,28 @@ int acquire_time_data(sd_bus *bus, bool require_finished, UnitTimes **out) { return log_error_errno(r, "Failed to get timestamp properties of unit %s: %s", u.id, bus_error_message(&error, r)); + /* Activated in the previous soft-reboot iteration? Ignore it, we want new activations */ + if ((t->activated > 0 && t->activated < boot_times->shutdown_start_time) || + (t->activating > 0 && t->activating < boot_times->shutdown_start_time)) + continue; + subtract_timestamp(&t->activating, boot_times->reverse_offset); subtract_timestamp(&t->activated, boot_times->reverse_offset); - subtract_timestamp(&t->deactivating, boot_times->reverse_offset); - subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + /* If the last deactivation was in the previous soft-reboot, ignore it */ + if (boot_times->soft_reboots_count > 0) { + if (t->deactivating < boot_times->reverse_offset) + t->deactivating = 0; + else + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + if (t->deactivated < boot_times->reverse_offset) + t->deactivated = 0; + else + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + } else { + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + } if (t->activated >= t->activating) t->time = t->activated - t->activating; diff --git a/src/analyze/analyze-time-data.h b/src/analyze/analyze-time-data.h index 9049d87..e7ffd85 100644 --- a/src/analyze/analyze-time-data.h +++ b/src/analyze/analyze-time-data.h @@ -14,6 +14,7 @@ typedef struct BootTimes { usec_t initrd_time; usec_t userspace_time; usec_t finish_time; + usec_t shutdown_start_time; usec_t security_start_time; usec_t security_finish_time; usec_t generators_start_time; @@ -26,6 +27,8 @@ typedef struct BootTimes { usec_t initrd_generators_finish_time; usec_t initrd_unitsload_start_time; usec_t initrd_unitsload_finish_time; + /* Not strictly a timestamp, but we are going to show it next to the other timestamps */ + uint64_t soft_reboots_count; /* * If we're analyzing the user instance, all timestamps will be offset by its own start-up timestamp, diff --git a/src/analyze/analyze-unit-files.c b/src/analyze/analyze-unit-files.c index d9b3313..e0c4867 100644 --- a/src/analyze/analyze-unit-files.c +++ b/src/analyze/analyze-unit-files.c @@ -15,7 +15,7 @@ static bool strv_fnmatch_strv_or_empty(char* const* patterns, char **strv, int f int verb_unit_files(int argc, char *argv[], void *userdata) { _cleanup_hashmap_free_ Hashmap *unit_ids = NULL, *unit_names = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; char **patterns = strv_skip(argv, 1); const char *k, *dst; char **v; diff --git a/src/analyze/analyze-unit-paths.c b/src/analyze/analyze-unit-paths.c index bb00a4f..17f18e0 100644 --- a/src/analyze/analyze-unit-paths.c +++ b/src/analyze/analyze-unit-paths.c @@ -6,7 +6,7 @@ #include "strv.h" int verb_unit_paths(int argc, char *argv[], void *userdata) { - _cleanup_(lookup_paths_free) LookupPaths paths = {}; + _cleanup_(lookup_paths_done) LookupPaths paths = {}; int r; r = lookup_paths_init_or_warn(&paths, arg_runtime_scope, 0, NULL); diff --git a/src/analyze/analyze-verify-util.c b/src/analyze/analyze-verify-util.c index 6fbd6fa..8e83c9a 100644 --- a/src/analyze/analyze-verify-util.c +++ b/src/analyze/analyze-verify-util.c @@ -152,7 +152,7 @@ int verify_set_unit_path(char **filenames) { * Treat explicit empty path to mean that nothing should be appended. */ old = getenv("SYSTEMD_UNIT_PATH"); if (!streq_ptr(old, "") && - !strextend_with_separator(&joined, ":", old ?: "")) + !strextend_with_separator(&joined, ":", strempty(old))) return -ENOMEM; assert_se(set_unit_path(joined) >= 0); @@ -201,19 +201,15 @@ static int verify_executables(Unit *u, const char *root) { assert(u); - ExecCommand *exec = - u->type == UNIT_SOCKET ? SOCKET(u)->control_command : - u->type == UNIT_MOUNT ? MOUNT(u)->control_command : - u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; - RET_GATHER(r, verify_executable(u, exec, root)); - if (u->type == UNIT_SERVICE) - FOREACH_ARRAY(i, SERVICE(u)->exec_command, ELEMENTSOF(SERVICE(u)->exec_command)) - RET_GATHER(r, verify_executable(u, *i, root)); + FOREACH_ELEMENT(i, SERVICE(u)->exec_command) + LIST_FOREACH(command, j, *i) + RET_GATHER(r, verify_executable(u, j, root)); if (u->type == UNIT_SOCKET) - FOREACH_ARRAY(i, SOCKET(u)->exec_command, ELEMENTSOF(SOCKET(u)->exec_command)) - RET_GATHER(r, verify_executable(u, *i, root)); + FOREACH_ELEMENT(i, SOCKET(u)->exec_command) + LIST_FOREACH(command, j, *i) + RET_GATHER(r, verify_executable(u, j, root)); return r; } diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 021de65..cf4894a 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -13,6 +13,7 @@ #include "alloc-util.h" #include "analyze.h" +#include "analyze-architectures.h" #include "analyze-blame.h" #include "analyze-calendar.h" #include "analyze-capability.h" @@ -25,6 +26,7 @@ #include "analyze-exit-status.h" #include "analyze-fdstore.h" #include "analyze-filesystems.h" +#include "analyze-image-policy.h" #include "analyze-inspect-elf.h" #include "analyze-log-control.h" #include "analyze-malloc.h" @@ -41,7 +43,6 @@ #include "analyze-unit-files.h" #include "analyze-unit-paths.h" #include "analyze-verify.h" -#include "analyze-image-policy.h" #include "build.h" #include "bus-error.h" #include "bus-locator.h" @@ -224,6 +225,7 @@ static int help(int argc, char *argv[], void *userdata) { " capability [CAP...] List capability definitions\n" " syscall-filter [NAME...] List syscalls in seccomp filters\n" " filesystems [NAME...] List known filesystems\n" + " architectures [NAME...] List known architectures\n" " condition CONDITION... Evaluate conditions and asserts\n" " compare-versions VERSION1 [OP] VERSION2\n" " Compare two version strings\n" @@ -238,7 +240,7 @@ static int help(int argc, char *argv[], void *userdata) { " fdstore SERVICE... Show file descriptor store contents of service\n" " image-policy POLICY... Analyze image policy string\n" " pcrs [PCR...] Show TPM2 PCRs and their names\n" - " srk > FILE Write TPM2 SRK to stdout\n" + " srk [>FILE] Write TPM2 SRK (to FILE)\n" "\nOptions:\n" " --recursive-errors=MODE Control which units are verified\n" " --offline=BOOL Perform a security review on unit file(s)\n" @@ -270,6 +272,7 @@ static int help(int argc, char *argv[], void *userdata) { " specified time\n" " --profile=name|PATH Include the specified profile in the\n" " security review of the unit(s)\n" + " --unit=UNIT Evaluate conditions and asserts of unit\n" " --table Output plot's raw time data as a table\n" " -h --help Show this help\n" " --version Show package version\n" @@ -557,18 +560,21 @@ 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", "fdstore", "pcrs")) + if (arg_offline && optind >= argc - 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --offline= requires one or more units to perform a security review."); + + if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot", "fdstore", "pcrs", "architectures", "capability", "exit-status")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Option --json= is only supported for security, inspect-elf, plot, fdstore, pcrs right now."); + "Option --json= is only supported for security, inspect-elf, plot, fdstore, pcrs, architectures, capability, exit-status right now."); if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --threshold= is only supported for security right now."); - if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && - !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify")) + if (arg_runtime_scope == RUNTIME_SCOPE_GLOBAL && !streq_ptr(argv[optind], "unit-paths")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Option --global only makes sense with verbs dot, unit-paths, verify."); + "Option --global only makes sense with verb unit-paths."); if (streq_ptr(argv[optind], "cat-config") && arg_runtime_scope == RUNTIME_SCOPE_USER) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -596,7 +602,7 @@ static int parse_argv(int argc, char *argv[]) { if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); - if ((!arg_legend && !streq_ptr(argv[optind], "plot")) || + if ((!arg_legend && !STRPTR_IN_SET(argv[optind], "plot", "architectures")) || (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=."); @@ -650,6 +656,7 @@ static int run(int argc, char *argv[]) { { "image-policy", 2, 2, 0, verb_image_policy }, { "pcrs", VERB_ANY, VERB_ANY, 0, verb_pcrs }, { "srk", VERB_ANY, 1, 0, verb_srk }, + { "architectures", VERB_ANY, VERB_ANY, 0, verb_architectures }, {} }; @@ -673,7 +680,8 @@ static int run(int argc, char *argv[]) { arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_READ_ONLY, + DISSECT_IMAGE_READ_ONLY | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/analyze/meson.build b/src/analyze/meson.build index a505447..f150ed7 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -1,6 +1,7 @@ # SPDX-License-Identifier: LGPL-2.1-or-later systemd_analyze_sources = files( + 'analyze-architectures.c', 'analyze-blame.c', 'analyze-calendar.c', 'analyze-capability.c', diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index bf4c93e..b2c8ef7 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -226,20 +226,23 @@ static int run(int argc, char *argv[]) { usec_t timeout; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; - if (arg_timeout > 0) - timeout = usec_add(now(CLOCK_MONOTONIC), arg_timeout); - else - timeout = 0; + timeout = arg_timeout > 0 ? usec_add(now(CLOCK_MONOTONIC), arg_timeout) : 0; - r = ask_password_auto(arg_message, arg_icon, arg_id, arg_key_name, arg_credential_name ?: "password", timeout, arg_flags, &l); + AskPasswordRequest req = { + .message = arg_message, + .icon = arg_icon, + .id = arg_id, + .keyring = arg_key_name, + .credential = arg_credential_name ?: "password", + }; + + r = ask_password_auto(&req, timeout, arg_flags, &l); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c index b2032ad..2de6c20 100644 --- a/src/backlight/backlight.c +++ b/src/backlight/backlight.c @@ -11,7 +11,6 @@ #include "escape.h" #include "fileio.h" #include "main-func.h" -#include "mkdir.h" #include "parse-util.h" #include "percent-util.h" #include "pretty-print.h" @@ -20,6 +19,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "verbs.h" #define PCI_CLASS_GRAPHICS_CARD 0x30000 @@ -91,8 +91,8 @@ static int has_multiple_graphics_cards(void) { } static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) { - const char *subsystem, *sysname, *value; sd_device *parent; + const char *s; int r; assert(device); @@ -102,34 +102,29 @@ static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) { if (r < 0) return r; - r = sd_device_get_subsystem(parent, &subsystem); - if (r < 0) - return r; - - r = sd_device_get_sysname(parent, &sysname); - if (r < 0) - return r; + if (device_in_subsystem(parent, "drm")) { - if (streq(subsystem, "drm")) { - const char *c; + r = sd_device_get_sysname(parent, &s); + if (r < 0) + return r; - c = startswith(sysname, "card"); - if (!c) + s = startswith(s, "card"); + if (!s) return -ENODATA; - c += strspn(c, DIGITS); - if (*c == '-' && !STARTSWITH_SET(c, "-LVDS-", "-Embedded DisplayPort-", "-eDP-")) + s += strspn(s, DIGITS); + if (*s == '-' && !STARTSWITH_SET(s, "-LVDS-", "-Embedded DisplayPort-", "-eDP-")) /* A connector DRM device, let's ignore all but LVDS and eDP! */ return -EOPNOTSUPP; - } else if (streq(subsystem, "pci") && - sd_device_get_sysattr_value(parent, "class", &value) >= 0) { + } else if (device_in_subsystem(parent, "pci") && + sd_device_get_sysattr_value(parent, "class", &s)) { + unsigned long class; - r = safe_atolu(value, &class); + r = safe_atolu(s, &class); if (r < 0) - return log_warning_errno(r, "Cannot parse PCI class '%s' of device %s:%s: %m", - value, subsystem, sysname); + return log_device_warning_errno(parent, r, "Cannot parse PCI class '%s': %m", s); /* Graphics card */ if (class == PCI_CLASS_GRAPHICS_CARD) { @@ -137,7 +132,7 @@ static int find_pci_or_platform_parent(sd_device *device, sd_device **ret) { return 0; } - } else if (streq(subsystem, "platform")) { + } else if (device_in_subsystem(parent, "platform")) { *ret = parent; return 0; } @@ -176,7 +171,7 @@ static int same_device(sd_device *a, sd_device *b) { static int validate_device(sd_device *device) { _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerate = NULL; - const char *v, *sysname, *subsystem; + const char *v, *sysname; sd_device *parent; int r; @@ -184,7 +179,7 @@ static int validate_device(sd_device *device) { /* Verify whether we should actually care for a specific backlight device. For backlight devices * there might be multiple ways to access the same control: "firmware" (i.e. ACPI), "platform" - * (i.e. via the machine's EC) and "raw" (via the graphics card). In general we should prefer + * (i.e. via the machine's EC), and "raw" (via the graphics card). In general we should prefer * "firmware" (i.e. ACPI) or "platform" access over "raw" access, in order not to confuse the * BIOS/EC, and compatibility with possible low-level hotkey handling of screen brightness. The * kernel will already make sure to expose only one of "firmware" and "platform" for the same @@ -195,11 +190,8 @@ static int validate_device(sd_device *device) { if (r < 0) return log_device_debug_errno(device, r, "Failed to get sysname: %m"); - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to get subsystem: %m"); - if (!streq(subsystem, "backlight")) - return true; + if (!device_in_subsystem(device, "backlight")) + return true; /* We assume LED device is always valid. */ r = sd_device_get_sysattr_value(device, "type", &v); if (r < 0) @@ -211,15 +203,12 @@ static int validate_device(sd_device *device) { if (r < 0) return log_device_debug_errno(device, r, "Failed to find PCI or platform parent: %m"); - r = sd_device_get_subsystem(parent, &subsystem); - if (r < 0) - return log_device_debug_errno(parent, r, "Failed to get subsystem: %m"); - if (DEBUG_LOGGING) { - const char *s = NULL; + const char *s = NULL, *subsystem = NULL; (void) sd_device_get_syspath(parent, &s); - log_device_debug(device, "Found %s parent device: %s", subsystem, strna(s)); + (void) sd_device_get_subsystem(parent, &subsystem); + log_device_debug(device, "Found %s parent device: %s", strna(subsystem), strna(s)); } r = sd_device_enumerator_new(&enumerate); @@ -246,7 +235,7 @@ static int validate_device(sd_device *device) { if (r < 0) return log_debug_errno(r, "Failed to add sysattr match: %m"); - if (streq(subsystem, "pci")) { + if (device_in_subsystem(parent, "pci")) { r = has_multiple_graphics_cards(); if (r < 0) return log_debug_errno(r, "Failed to check if the system has multiple graphics cards: %m"); @@ -254,8 +243,8 @@ static int validate_device(sd_device *device) { /* If the system has multiple graphics cards, then we cannot associate platform * devices on non-PCI bus (especially WMI bus) with PCI devices. Let's ignore all * backlight devices that do not have the same parent PCI device. */ - log_debug("Found multiple graphics cards on PCI bus. " - "Skipping to associate platform backlight devices on non-PCI bus."); + log_debug("Found multiple graphics cards on PCI bus; " + "skipping deduplication of platform backlight devices not on PCI bus."); r = sd_device_enumerator_add_match_parent(enumerate, parent); if (r < 0) @@ -264,7 +253,6 @@ static int validate_device(sd_device *device) { } FOREACH_DEVICE(enumerate, other) { - const char *other_subsystem; sd_device *other_parent; /* OK, so there's another backlight device, and it's a platform or firmware device. @@ -289,13 +277,7 @@ static int validate_device(sd_device *device) { return false; } - r = sd_device_get_subsystem(other_parent, &other_subsystem); - if (r < 0) { - log_device_debug_errno(other_parent, r, "Failed to get subsystem, ignoring: %m"); - continue; - } - - if (streq(other_subsystem, "platform") && streq(subsystem, "pci")) { + if (device_in_subsystem(other_parent, "platform") && device_in_subsystem(parent, "pci")) { /* The other is connected to the platform bus and we are a PCI device, that also means we are out. */ if (DEBUG_LOGGING) { const char *other_sysname = NULL, *other_type = NULL; @@ -313,7 +295,8 @@ static int validate_device(sd_device *device) { return true; } -static int get_max_brightness(sd_device *device, unsigned *ret) { +static int read_max_brightness(sd_device *device, unsigned *ret) { + unsigned max_brightness; const char *s; int r; @@ -324,11 +307,22 @@ static int get_max_brightness(sd_device *device, unsigned *ret) { if (r < 0) return log_device_warning_errno(device, r, "Failed to read 'max_brightness' attribute: %m"); - r = safe_atou(s, ret); + r = safe_atou(s, &max_brightness); if (r < 0) return log_device_warning_errno(device, r, "Failed to parse 'max_brightness' \"%s\": %m", s); - return 0; + /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops + * with Asus mainboards that load the eeepc-wmi module. */ + if (max_brightness == 0) { + log_device_warning(device, "Maximum brightness is 0, ignoring device."); + *ret = 0; + return 0; + } + + log_device_debug(device, "Maximum brightness is %u", max_brightness); + + *ret = max_brightness; + return 1; /* valid max brightness */ } static int clamp_brightness( @@ -339,8 +333,6 @@ static int clamp_brightness( unsigned *brightness) { unsigned new_brightness, min_brightness; - const char *subsystem; - int r; assert(device); assert(brightness); @@ -350,14 +342,9 @@ static int clamp_brightness( * avoids preserving an unreadably dim screen, which would otherwise force the user to disable * state restoration. */ - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return log_device_warning_errno(device, r, "Failed to get device subsystem: %m"); - - if (streq(subsystem, "backlight")) - min_brightness = MAX(1U, (unsigned) ((double) max_brightness * percent / 100)); - else - min_brightness = 0; + min_brightness = (unsigned) ((double) max_brightness * percent / 100); + if (device_in_subsystem(device, "backlight")) + min_brightness = MAX(1U, min_brightness); new_brightness = CLAMP(*brightness, min_brightness, max_brightness); if (new_brightness != *brightness) @@ -372,19 +359,28 @@ static int clamp_brightness( return 0; } -static bool shall_clamp(sd_device *d, unsigned *ret) { - const char *s; +static bool shall_clamp(sd_device *device, unsigned *ret) { + const char *property, *s; + unsigned default_percent; int r; - assert(d); + assert(device); assert(ret); - r = sd_device_get_property_value(d, "ID_BACKLIGHT_CLAMP", &s); + if (device_in_subsystem(device, "backlight")) { + property = "ID_BACKLIGHT_CLAMP"; + default_percent = 5; + } else { + property = "ID_LEDS_CLAMP"; + default_percent = 0; + } + + r = sd_device_get_property_value(device, property, &s); if (r < 0) { if (r != -ENOENT) - log_device_debug_errno(d, r, "Failed to get ID_BACKLIGHT_CLAMP property, ignoring: %m"); - *ret = 5; /* defaults to 5% */ - return true; + log_device_debug_errno(device, r, "Failed to get %s property, ignoring: %m", property); + *ret = default_percent; + return default_percent > 0; } r = parse_boolean(s); @@ -395,9 +391,9 @@ static bool shall_clamp(sd_device *d, unsigned *ret) { r = parse_percent(s); if (r < 0) { - log_device_debug_errno(d, r, "Failed to parse ID_BACKLIGHT_CLAMP property, ignoring: %m"); - *ret = 5; - return true; + log_device_debug_errno(device, r, "Failed to parse %s property, ignoring: %m", property); + *ret = default_percent; + return default_percent > 0; } *ret = r; @@ -405,18 +401,14 @@ static bool shall_clamp(sd_device *d, unsigned *ret) { } static int read_brightness(sd_device *device, unsigned max_brightness, unsigned *ret_brightness) { - const char *subsystem, *value; + const char *value; unsigned brightness; int r; assert(device); assert(ret_brightness); - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return log_device_debug_errno(device, r, "Failed to get subsystem: %m"); - - if (streq(subsystem, "backlight")) { + if (device_in_subsystem(device, "backlight")) { r = sd_device_get_sysattr_value(device, "actual_brightness", &value); if (r == -ENOENT) { log_device_debug_errno(device, r, "Failed to read 'actual_brightness' attribute, " @@ -463,154 +455,219 @@ use_brightness: return 0; } -static int run(int argc, char *argv[]) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_free_ char *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL; - const char *sysname, *path_id, *ss, *saved; - unsigned max_brightness, brightness; +static int build_save_file_path(sd_device *device, char **ret) { + _cleanup_free_ char *escaped_subsystem = NULL, *escaped_sysname = NULL, *path = NULL; + const char *s; int r; - log_setup(); + assert(device); + assert(ret); - if (argv_looks_like_help(argc, argv)) - return help(); + r = sd_device_get_subsystem(device, &s); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get subsystem: %m"); - if (argc != 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires two arguments."); + escaped_subsystem = cescape(s); + if (!escaped_subsystem) + return log_oom(); - if (!STR_IN_SET(argv[1], "load", "save")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", argv[1]); + r = sd_device_get_sysname(device, &s); + if (r < 0) + return log_device_error_errno(device, r, "Failed to get sysname: %m"); - umask(0022); + escaped_sysname = cescape(s); + if (!escaped_sysname) + return log_oom(); - r = mkdir_p("/var/lib/systemd/backlight", 0755); - if (r < 0) - return log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m"); + if (sd_device_get_property_value(device, "ID_PATH", &s) >= 0) { + _cleanup_free_ char *escaped_path_id = cescape(s); + if (!escaped_path_id) + return log_oom(); - sysname = strchr(argv[2], ':'); - if (!sysname) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Requires a subsystem and sysname pair specifying a backlight device."); + path = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_subsystem, ":", escaped_sysname); + } else + path = strjoin("/var/lib/systemd/backlight/", escaped_subsystem, ":", escaped_sysname); + if (!path) + return log_oom(); - ss = strndupa_safe(argv[2], sysname - argv[2]); + *ret = TAKE_PTR(path); + return 0; +} - sysname++; +static int read_saved_brightness(sd_device *device, unsigned *ret) { + _cleanup_free_ char *path = NULL, *value = NULL; + int r; - if (!STR_IN_SET(ss, "backlight", "leds")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a backlight or LED device: '%s:%s'", ss, sysname); + assert(device); + assert(ret); + + r = build_save_file_path(device, &path); + if (r < 0) + return r; - r = sd_device_new_from_subsystem_sysname(&device, ss, sysname); + r = read_one_line_file(path, &value); if (r < 0) { - bool ignore = r == -ENODEV; + if (r != -ENOENT) + log_device_error_errno(device, r, "Failed to read %s: %m", path); + return r; + } - /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added. - * See issue #21997. */ - log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, r, - "Failed to get backlight or LED device '%s:%s'%s: %m", - ss, sysname, ignore ? ", ignoring" : ""); - return ignore ? 0 : r; + r = safe_atou(value, ret); + if (r < 0) { + log_device_warning_errno(device, r, + "Failed to parse saved brightness '%s', removing %s.", + value, path); + (void) unlink(path); + return r; } - /* If max_brightness is 0, then there is no actual backlight device. This happens on desktops - * with Asus mainboards that load the eeepc-wmi module. */ - if (get_max_brightness(device, &max_brightness) < 0) - return 0; + log_device_debug(device, "Using saved brightness %u.", *ret); + return 0; +} - if (max_brightness == 0) { - log_device_warning(device, "Maximum brightness is 0, ignoring device."); - return 0; - } +static int device_new_from_arg(const char *s, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_free_ char *subsystem = NULL; + const char *sysname; + int r; - log_device_debug(device, "Maximum brightness is %u", max_brightness); + assert(s); + assert(ret); - escaped_ss = cescape(ss); - if (!escaped_ss) - return log_oom(); + sysname = strchr(s, ':'); + if (!sysname) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Requires a subsystem and sysname pair specifying a backlight or LED device."); - escaped_sysname = cescape(sysname); - if (!escaped_sysname) + subsystem = strndup(s, sysname - s); + if (!subsystem) return log_oom(); - if (sd_device_get_property_value(device, "ID_PATH", &path_id) >= 0) { - escaped_path_id = cescape(path_id); - if (!escaped_path_id) - return log_oom(); + sysname++; - saved = strjoina("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname); - } else - saved = strjoina("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname); + if (!STR_IN_SET(subsystem, "backlight", "leds")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Not a backlight or LED device: '%s:%s'", + subsystem, sysname); - /* If there are multiple conflicting backlight devices, then their probing at boot-time might - * happen in any order. This means the validity checking of the device then is not reliable, - * since it might not see other devices conflicting with a specific backlight. To deal with - * this, we will actively delete backlight state files at shutdown (where device probing should - * be complete), so that the validity check at boot time doesn't have to be reliable. */ + r = sd_device_new_from_subsystem_sysname(&device, subsystem, sysname); + if (r == -ENODEV) { + /* Some drivers, e.g. for AMD GPU, removes acpi backlight device soon after it is added. + * See issue #21997. */ + log_debug_errno(r, "Failed to get backlight or LED device '%s:%s', ignoring: %m", subsystem, sysname); + *ret = NULL; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get backlight or LED device '%s:%s': %m", subsystem, sysname); - if (streq(argv[1], "load")) { - _cleanup_free_ char *value = NULL; - unsigned percent; - bool clamp; + *ret = TAKE_PTR(device); + return 1; /* Found. */ +} - if (!shall_restore_state()) - return 0; +static int verb_load(int argc, char *argv[], void *userdata) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + unsigned max_brightness, brightness, percent; + bool clamp; + int r; - if (validate_device(device) == 0) - return 0; + assert(argc == 2); - clamp = shall_clamp(device, &percent); + if (!shall_restore_state()) + return 0; - r = read_one_line_file(saved, &value); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to read %s: %m", saved); - if (r > 0) { - r = safe_atou(value, &brightness); - if (r < 0) { - log_warning_errno(r, "Failed to parse saved brightness '%s', removing %s.", - value, saved); - (void) unlink(saved); - } else { - log_debug("Using saved brightness %u.", brightness); - if (clamp) - (void) clamp_brightness(device, percent, /* saved = */ true, max_brightness, &brightness); - - /* Do not fall back to read current brightness below. */ - r = 1; - } - } - if (r <= 0) { - /* Fallback to clamping current brightness or exit early if clamping is not - * supported/enabled. */ - if (!clamp) - return 0; + r = device_new_from_arg(argv[1], &device); + if (r <= 0) + return r; - r = read_brightness(device, max_brightness, &brightness); - if (r < 0) - return log_device_error_errno(device, r, "Failed to read current brightness: %m"); + r = read_max_brightness(device, &max_brightness); + if (r <= 0) + return r; - (void) clamp_brightness(device, percent, /* saved = */ false, max_brightness, &brightness); - } + /* Ignore any errors in validation, and use the device as is. */ + if (validate_device(device) == 0) + return 0; - r = sd_device_set_sysattr_valuef(device, "brightness", "%u", brightness); - if (r < 0) - return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m"); + clamp = shall_clamp(device, &percent); - } else if (streq(argv[1], "save")) { - if (validate_device(device) == 0) { - (void) unlink(saved); + r = read_saved_brightness(device, &brightness); + if (r < 0) { + /* Fallback to clamping current brightness or exit early if clamping is not + * supported/enabled. */ + if (!clamp) return 0; - } r = read_brightness(device, max_brightness, &brightness); if (r < 0) return log_device_error_errno(device, r, "Failed to read current brightness: %m"); - r = write_string_filef(saved, WRITE_STRING_FILE_CREATE, "%u", brightness); - if (r < 0) - return log_device_error_errno(device, r, "Failed to write %s: %m", saved); + (void) clamp_brightness(device, percent, /* saved = */ false, max_brightness, &brightness); + } else if (clamp) + (void) clamp_brightness(device, percent, /* saved = */ true, max_brightness, &brightness); - } else - assert_not_reached(); + r = sd_device_set_sysattr_valuef(device, "brightness", "%u", brightness); + if (r < 0) + return log_device_error_errno(device, r, "Failed to write system 'brightness' attribute: %m"); + + return 0; +} + +static int verb_save(int argc, char *argv[], void *userdata) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_free_ char *path = NULL; + unsigned max_brightness, brightness; + int r; + + assert(argc == 2); + + r = device_new_from_arg(argv[1], &device); + if (r <= 0) + return r; + + r = read_max_brightness(device, &max_brightness); + if (r <= 0) + return r; + + r = build_save_file_path(device, &path); + if (r < 0) + return r; + + /* If there are multiple conflicting backlight devices, then their probing at boot-time might + * happen in any order. This means the validity checking of the device then is not reliable, + * since it might not see other devices conflicting with a specific backlight. To deal with + * this, we will actively delete backlight state files at shutdown (where device probing should + * be complete), so that the validity check at boot time doesn't have to be reliable. */ + if (validate_device(device) == 0) { + (void) unlink(path); + return 0; + } + + r = read_brightness(device, max_brightness, &brightness); + if (r < 0) + return log_device_error_errno(device, r, "Failed to read current brightness: %m"); + + r = write_string_filef(path, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_MKDIR_0755, "%u", brightness); + if (r < 0) + return log_device_error_errno(device, r, "Failed to write %s: %m", path); return 0; } +static int run(int argc, char *argv[]) { + static const Verb verbs[] = { + { "load", 2, 2, VERB_ONLINE_ONLY, verb_load }, + { "save", 2, 2, VERB_ONLINE_ONLY, verb_save }, + {} + }; + + log_setup(); + + if (argv_looks_like_help(argc, argv)) + return help(); + + umask(0022); + + return dispatch_verb(argc, argv, verbs, NULL); +} + DEFINE_MAIN_FUNCTION(run); diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h index 136d2b3..c215c33 100644 --- a/src/basic/alloc-util.h +++ b/src/basic/alloc-util.h @@ -20,7 +20,7 @@ typedef void* (*mfree_func_t)(void *p); * proceeding and smashing the stack limits. Note that by default RLIMIT_STACK is 8M on Linux. */ #define ALLOCA_MAX (4U*1024U*1024U) -#define new(t, n) ((t*) malloc_multiply((n), sizeof(t))) +#define new(t, n) ((t*) malloc_multiply(n, sizeof(t))) #define new0(t, n) ((t*) calloc((n) ?: 1, sizeof(t))) @@ -45,9 +45,9 @@ typedef void* (*mfree_func_t)(void *p); (t*) alloca0((sizeof(t)*_n_)); \ }) -#define newdup(t, p, n) ((t*) memdup_multiply(p, (n), sizeof(t))) +#define newdup(t, p, n) ((t*) memdup_multiply(p, n, sizeof(t))) -#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, (n), sizeof(t))) +#define newdup_suffix0(t, p, n) ((t*) memdup_suffix0_multiply(p, n, sizeof(t))) #define malloc0(n) (calloc(1, (n) ?: 1)) @@ -237,7 +237,7 @@ static inline size_t malloc_sizeof_safe(void **xp) { #define strndupa_safe(s, n) \ ({ \ const char *_t = (s); \ - (char*) memdupa_suffix0(_t, strnlen(_t, (n))); \ + (char*) memdupa_suffix0(_t, strnlen(_t, n)); \ }) /* Free every element of the array. */ diff --git a/src/basic/bitfield.h b/src/basic/bitfield.h index 25bc0eb..048e08d 100644 --- a/src/basic/bitfield.h +++ b/src/basic/bitfield.h @@ -27,7 +27,7 @@ ({ \ typeof(type) UNIQ_T(_mask, uniq) = (type)0; \ int UNIQ_T(_i, uniq); \ - VA_ARGS_FOREACH(UNIQ_T(_i, uniq), ##__VA_ARGS__) \ + FOREACH_ARGUMENT(UNIQ_T(_i, uniq), ##__VA_ARGS__) \ UNIQ_T(_mask, uniq) |= INDEX_TO_MASK(type, UNIQ_T(_i, uniq)); \ UNIQ_T(_mask, uniq); \ }) diff --git a/src/basic/build-path.c b/src/basic/build-path.c new file mode 100644 index 0000000..b597265 --- /dev/null +++ b/src/basic/build-path.c @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "build-path.h" +#include "errno-list.h" +#include "errno-util.h" +#include "macro.h" +#include "path-util.h" +#include "process-util.h" +#include "unistd.h" + +static int get_runpath_from_dynamic(const ElfW(Dyn) *d, ElfW(Addr) bias, const char **ret) { + size_t runpath_index = SIZE_MAX, rpath_index = SIZE_MAX; + const char *strtab = NULL; + + assert(d); + + /* Iterates through the PT_DYNAMIC section to find the DT_RUNPATH/DT_RPATH entries */ + + for (; d->d_tag != DT_NULL; d++) { + + switch (d->d_tag) { + + case DT_RUNPATH: + runpath_index = (size_t) d->d_un.d_val; + break; + + case DT_RPATH: + rpath_index = (size_t) d->d_un.d_val; + break; + + case DT_STRTAB: + /* On MIPS and RISC-V DT_STRTAB records an offset, not a valid address, so it has to be adjusted + * using the bias calculated earlier. */ + if (d->d_un.d_val != 0) + strtab = (const char *) ((uintptr_t) d->d_un.d_val +#if defined(__mips__) || defined(__riscv) + + bias +#endif + ); + break; + } + + /* runpath wins, hence if we have the table and runpath we can exit the loop early */ + if (strtab && runpath_index != SIZE_MAX) + break; + } + + if (!strtab) + return -ENOTRECOVERABLE; + + /* According to ld.so runpath wins if both runpath and rpath are defined. */ + if (runpath_index != SIZE_MAX) { + if (ret) + *ret = strtab + runpath_index; + return 1; + } + + if (rpath_index != SIZE_MAX) { + if (ret) + *ret = strtab + rpath_index; + return 1; + } + + if (ret) + *ret = NULL; + + return 0; +} + +static int get_runpath(const char **ret) { + unsigned long phdr, phent, phnum; + + /* Finds the rpath/runpath in the program headers of the main executable we are running in */ + + phdr = getauxval(AT_PHDR); /* Start offset of phdr */ + if (phdr == 0) + return -ENOTRECOVERABLE; + + phnum = getauxval(AT_PHNUM); /* Number of entries in phdr */ + if (phnum == 0) + return -ENOTRECOVERABLE; + + phent = getauxval(AT_PHENT); /* Size of entries in phdr */ + if (phent < sizeof(ElfW(Phdr))) /* Safety check, that our idea of the structure matches the file */ + return -ENOTRECOVERABLE; + + ElfW(Addr) bias = 0, dyn = 0; + bool found_bias = false, found_dyn = false; + + /* Iterate through the Phdr structures to find the PT_PHDR and PT_DYNAMIC sections */ + for (unsigned long i = 0; i < phnum; i++) { + const ElfW(Phdr) *p = (const ElfW(Phdr)*) (phdr + (i * phent)); + + switch (p->p_type) { + + case PT_PHDR: + if (p->p_vaddr > phdr) /* safety overflow check */ + return -ENOTRECOVERABLE; + + bias = (ElfW(Addr)) phdr - p->p_vaddr; + found_bias = true; + break; + + case PT_DYNAMIC: + dyn = p->p_vaddr; + found_dyn = true; + break; + } + + if (found_bias && found_dyn) + break; + } + + if (!found_dyn) + return -ENOTRECOVERABLE; + + return get_runpath_from_dynamic((const ElfW(Dyn)*) (bias + dyn), bias, ret); +} + +int get_build_exec_dir(char **ret) { + int r; + + /* Returns the build execution directory if we are invoked in a build environment. Specifically, this + * checks if the main program binary has an rpath/runpath set (i.e. an explicit directory where to + * look for shared libraries) to $ORIGIN. If so we know that this is not a regular installed binary, + * but one which shall acquire its libraries from below a directory it is located in, i.e. a build + * directory or similar. In that case it typically makes sense to also search for our auxiliary + * executables we fork() off in a directory close to our main program binary, rather than in the + * system. + * + * This function is supposed to be used when looking for "callout" binaries that are closely related + * to the main program (i.e. speak a specific protocol between each other). And where it's generally + * a good idea to use the binary from the build tree (if there is one) instead of the system. + * + * Note that this does *not* actually return the rpath/runpath but the instead the directory the main + * executable was found in. This follows the logic that the result is supposed to be used for + * executable binaries (i.e. stuff in bindir), not for shared libraries (i.e. stuff in libdir), and + * hence the literal shared library path would just be wrong. + * + * TLDR: if we look for callouts in this dir first, running binaries from the meson build tree + * automatically uses the right callout. + * + * Returns: + * -ENOEXEC → We are not running in an rpath/runpath $ORIGIN environment + * -ENOENT → We don't know our own binary path + * -NOTRECOVERABLE → Dynamic binary information missing? + */ + + static int runpath_cached = -ERRNO_MAX-1; + if (runpath_cached == -ERRNO_MAX-1) { + const char *runpath = NULL; + + runpath_cached = get_runpath(&runpath); + + /* We only care if the runpath starts with $ORIGIN/ */ + if (runpath_cached > 0 && !startswith(runpath, "$ORIGIN/")) + runpath_cached = 0; + } + if (runpath_cached < 0) + return runpath_cached; + if (runpath_cached == 0) + return -ENOEXEC; + + _cleanup_free_ char *exe = NULL; + r = get_process_exe(0, &exe); + if (r < 0) + return runpath_cached = r; + + return path_extract_directory(exe, ret); +} + +static int find_build_dir_binary(const char *fn, char **ret) { + int r; + + assert(fn); + assert(ret); + + _cleanup_free_ char *build_dir = NULL; + r = get_build_exec_dir(&build_dir); + if (r < 0) + return r; + + _cleanup_free_ char *np = path_join(build_dir, fn); + if (!np) + return -ENOMEM; + + *ret = TAKE_PTR(np); + return 0; +} + +static int find_environment_binary(const char *fn, const char **ret) { + + /* If a path such as /usr/lib/systemd/systemd-foobar is specified, then this will check for an + * environment variable SYSTEMD_FOOBAR_PATH and return it if set. */ + + _cleanup_free_ char *s = strdup(fn); + if (!s) + return -ENOMEM; + + ascii_strupper(s); + string_replace_char(s, '-', '_'); + + if (!strextend(&s, "_PATH")) + return -ENOMEM; + + const char *e; + e = secure_getenv(s); + if (!e) + return -ENXIO; + + *ret = e; + return 0; +} + +int invoke_callout_binary(const char *path, char *const argv[]) { + int r; + + assert(path); + + /* Just like execv(), but tries to execute the specified binary in the build dir instead, if known */ + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + if (r == O_DIRECTORY) /* Uh? */ + return -EISDIR; + + const char *e; + if (find_environment_binary(fn, &e) >= 0) { + /* If there's an explicit environment variable set for this binary, prefer it */ + execv(e, argv); + return -errno; /* The environment variable counts, let's fail otherwise */ + } + + _cleanup_free_ char *np = NULL; + if (find_build_dir_binary(fn, &np) >= 0) + execv(np, argv); + + execv(path, argv); + return -errno; +} + +int pin_callout_binary(const char *path) { + int r; + + assert(path); + + /* Similar to invoke_callout_binary(), but pins (i.e. O_PATH opens) the binary instead of executing it. */ + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + if (r == O_DIRECTORY) /* Uh? */ + return -EISDIR; + + const char *e; + if (find_environment_binary(fn, &e) >= 0) + return RET_NERRNO(open(e, O_CLOEXEC|O_PATH)); + + _cleanup_free_ char *np = NULL; + if (find_build_dir_binary(fn, &np) >= 0) { + r = RET_NERRNO(open(np, O_CLOEXEC|O_PATH)); + if (r >= 0) + return r; + } + + return RET_NERRNO(open(path, O_CLOEXEC|O_PATH)); +} diff --git a/src/basic/build-path.h b/src/basic/build-path.h new file mode 100644 index 0000000..6c38a4a --- /dev/null +++ b/src/basic/build-path.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int get_build_exec_dir(char **ret); + +int invoke_callout_binary(const char *path, char *const argv[]); + +int pin_callout_binary(const char *path); diff --git a/src/basic/build.c b/src/basic/build.c index c587ada..3ab25f7 100644 --- a/src/basic/build.c +++ b/src/basic/build.c @@ -138,6 +138,12 @@ const char* const systemd_features = " -LIBCRYPTSETUP" #endif +#if HAVE_LIBCRYPTSETUP_PLUGINS + " +LIBCRYPTSETUP_PLUGINS" +#else + " -LIBCRYPTSETUP_PLUGINS" +#endif + #if HAVE_LIBFDISK " +LIBFDISK" #else @@ -232,7 +238,12 @@ const char* const systemd_features = " -SYSVINIT" #endif - " default-hierarchy=" DEFAULT_HIERARCHY_NAME +#if HAVE_LIBARCHIVE + " +LIBARCHIVE" +#else + " -LIBARCHIVE" +#endif + ; static char *systemd_features_with_color(void) { @@ -276,7 +287,7 @@ int version(void) { if (colors_enabled()) b = systemd_features_with_color(); - printf("%ssystemd " STRINGIFY(PROJECT_VERSION) "%s (" GIT_VERSION ")\n%s\n", + printf("%ssystemd " PROJECT_VERSION_FULL "%s (" GIT_VERSION ")\n%s\n", ansi_highlight(), ansi_normal(), b ?: systemd_features); return 0; diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c index c3cf455..e9b41fe 100644 --- a/src/basic/capability-util.c +++ b/src/basic/capability-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include #include @@ -34,37 +35,38 @@ int have_effective_cap(int value) { } unsigned cap_last_cap(void) { - static thread_local unsigned saved; - static thread_local bool valid = false; - _cleanup_free_ char *content = NULL; - unsigned long p = 0; - int r; + static atomic_int saved = INT_MAX; + int r, c; - if (valid) - return saved; + c = saved; + if (c != INT_MAX) + return c; - /* available since linux-3.2 */ + /* Available since linux-3.2 */ + _cleanup_free_ char *content = NULL; r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); - if (r >= 0) { - r = safe_atolu(content, &p); - if (r >= 0) { - - if (p > CAP_LIMIT) /* Safety for the future: if one day the kernel learns more than + if (r < 0) + log_debug_errno(r, "Failed to read /proc/sys/kernel/cap_last_cap, ignoring: %m"); + else { + r = safe_atoi(content, &c); + if (r < 0) + log_debug_errno(r, "Failed to parse /proc/sys/kernel/cap_last_cap, ignoring: %m"); + else { + if (c > CAP_LIMIT) /* Safety for the future: if one day the kernel learns more than * 64 caps, then we are in trouble (since we, as much userspace * and kernel space store capability masks in uint64_t types). We * also want to use UINT64_MAX as marker for "unset". Hence let's * hence protect ourselves against that and always cap at 62 for * now. */ - p = CAP_LIMIT; + c = CAP_LIMIT; - saved = p; - valid = true; - return p; + saved = c; + return c; } } - /* fall back to syscall-probing for pre linux-3.2 */ - p = (unsigned long) MIN(CAP_LAST_CAP, CAP_LIMIT); + /* Fall back to syscall-probing for pre linux-3.2, or where /proc/ is not mounted */ + unsigned long p = (unsigned long) MIN(CAP_LAST_CAP, CAP_LIMIT); if (prctl(PR_CAPBSET_READ, p) < 0) { @@ -81,10 +83,9 @@ unsigned cap_last_cap(void) { break; } - saved = p; - valid = true; - - return p; + c = (int) p; + saved = c; + return c; } int capability_update_inherited_set(cap_t caps, uint64_t set) { diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index 18b16ec..553ee60 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -22,6 +22,7 @@ #include "log.h" #include "login-util.h" #include "macro.h" +#include "missing_fs.h" #include "missing_magic.h" #include "missing_threads.h" #include "mkdir.h" @@ -39,6 +40,38 @@ #include "user-util.h" #include "xattr-util.h" +int cg_path_open(const char *controller, const char *path) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path(controller, path, /* item=*/ NULL, &fs); + if (r < 0) + return r; + + return RET_NERRNO(open(fs, O_DIRECTORY|O_CLOEXEC)); +} + +int cg_cgroupid_open(int cgroupfs_fd, uint64_t id) { + _cleanup_close_ int fsfd = -EBADF; + + if (cgroupfs_fd < 0) { + fsfd = open("/sys/fs/cgroup", O_CLOEXEC|O_DIRECTORY); + if (fsfd < 0) + return -errno; + + cgroupfs_fd = fsfd; + } + + cg_file_handle fh = CG_FILE_HANDLE_INIT; + CG_FILE_HANDLE_CGROUPID(fh) = id; + + int fd = open_by_handle_at(cgroupfs_fd, &fh.file_handle, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return -errno; + + return fd; +} + static int cg_enumerate_items(const char *controller, const char *path, FILE **ret, const char *item) { _cleanup_free_ char *fs = NULL; FILE *f; @@ -62,7 +95,7 @@ int cg_enumerate_processes(const char *controller, const char *path, FILE **ret) return cg_enumerate_items(controller, path, ret, "cgroup.procs"); } -int cg_read_pid(FILE *f, pid_t *ret) { +int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags) { unsigned long ul; /* Note that the cgroup.procs might contain duplicates! See cgroups.txt for details. */ @@ -70,27 +103,33 @@ int cg_read_pid(FILE *f, pid_t *ret) { assert(f); assert(ret); - errno = 0; - if (fscanf(f, "%lu", &ul) != 1) { + for (;;) { + errno = 0; + if (fscanf(f, "%lu", &ul) != 1) { - if (feof(f)) { - *ret = 0; - return 0; + if (feof(f)) { + *ret = 0; + return 0; + } + + return errno_or_else(EIO); } - return errno_or_else(EIO); - } + if (ul > PID_T_MAX) + return -EIO; - if (ul <= 0) - return -EIO; - if (ul > PID_T_MAX) - return -EIO; + /* In some circumstances (e.g. WSL), cgroups might contain unmappable PIDs from other + * contexts. These show up as zeros, and depending on the caller, can either be plain + * skipped over, or returned as-is. */ + if (ul == 0 && !FLAGS_SET(flags, CGROUP_DONT_SKIP_UNMAPPED)) + continue; - *ret = (pid_t) ul; - return 1; + *ret = (pid_t) ul; + return 1; + } } -int cg_read_pidref(FILE *f, PidRef *ret) { +int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags) { int r; assert(f); @@ -99,14 +138,22 @@ int cg_read_pidref(FILE *f, PidRef *ret) { for (;;) { pid_t pid; - r = cg_read_pid(f, &pid); + r = cg_read_pid(f, &pid, flags); if (r < 0) - return r; + return log_debug_errno(r, "Failed to read pid from cgroup item: %m"); if (r == 0) { *ret = PIDREF_NULL; return 0; } + if (pid == 0) + return -EREMOTE; + + if (FLAGS_SET(flags, CGROUP_NO_PIDFD)) { + *ret = PIDREF_MAKE_FROM_PID(pid); + return 1; + } + r = pidref_set_pid(ret, pid); if (r >= 0) return 1; @@ -135,7 +182,7 @@ int cg_read_event( return r; for (const char *p = content;;) { - _cleanup_free_ char *line = NULL, *key = NULL, *val = NULL; + _cleanup_free_ char *line = NULL, *key = NULL; const char *q; r = extract_first_word(&p, &line, "\n", 0); @@ -154,12 +201,7 @@ int cg_read_event( if (!streq(key, event)) continue; - val = strdup(q); - if (!val) - return -ENOMEM; - - *ret = TAKE_PTR(val); - return 0; + return strdup_to(ret, q); } } @@ -234,20 +276,13 @@ int cg_read_subgroup(DIR *d, char **ret) { assert(ret); FOREACH_DIRENT_ALL(de, d, return -errno) { - char *b; - if (de->d_type != DT_DIR) continue; if (dot_or_dot_dot(de->d_name)) continue; - b = strdup(de->d_name); - if (!b) - return -ENOMEM; - - *ret = b; - return 1; + return strdup_to_full(ret, de->d_name); } *ret = NULL; @@ -317,14 +352,14 @@ static int cg_kill_items( if (r == -ENOENT) break; if (r < 0) - return RET_GATHER(ret, r); + return RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup items: %m")); for (;;) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = cg_read_pidref(f, &pidref); + r = cg_read_pidref(f, &pidref, flags); if (r < 0) - return RET_GATHER(ret, r); + return RET_GATHER(ret, log_debug_errno(r, "Failed to read pidref from cgroup '%s': %m", path)); if (r == 0) break; @@ -340,7 +375,7 @@ static int cg_kill_items( /* If we haven't killed this process yet, kill it */ r = pidref_kill(&pidref, sig); if (r < 0 && r != -ESRCH) - RET_GATHER(ret, r); + RET_GATHER(ret, log_debug_errno(r, "Failed to kill process with pid " PID_FMT " from cgroup '%s': %m", pidref.pid, path)); if (r >= 0) { if (flags & CGROUP_SIGCONT) (void) pidref_kill(&pidref, SIGCONT); @@ -379,6 +414,8 @@ int cg_kill( int r, ret; r = cg_kill_items(path, sig, flags, s, log_kill, userdata, "cgroup.procs"); + if (r < 0) + log_debug_errno(r, "Failed to kill processes in cgroup '%s' item cgroup.procs: %m", path); if (r < 0 || sig != SIGKILL) return r; @@ -393,9 +430,13 @@ int cg_kill( if (r == 0) return ret; - r = cg_kill_items(path, sig, flags, s, log_kill, userdata, "cgroup.threads"); + /* Opening pidfds for non thread group leaders only works from 6.9 onwards with PIDFD_THREAD. On + * older kernels or without PIDFD_THREAD pidfd_open() fails with EINVAL. Since we might read non + * thread group leader IDs from cgroup.threads, we set CGROUP_NO_PIDFD to avoid trying open pidfd's + * for them and instead use the regular pid. */ + r = cg_kill_items(path, sig, flags|CGROUP_NO_PIDFD, s, log_kill, userdata, "cgroup.threads"); if (r < 0) - return r; + return log_debug_errno(r, "Failed to kill processes in cgroup '%s' item cgroup.threads: %m", path); return r > 0 || ret > 0; } @@ -418,7 +459,7 @@ int cg_kill_kernel_sigkill(const char *path) { r = write_string_file(killfile, "1", WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) - return r; + return log_debug_errno(r, "Failed to write to cgroup.kill for cgroup '%s': %m", path); return 0; } @@ -455,7 +496,7 @@ int cg_kill_recursive( r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); if (r < 0) { if (r != -ENOENT) - RET_GATHER(ret, r); + RET_GATHER(ret, log_debug_errno(r, "Failed to enumerate cgroup '%s' subgroups: %m", path)); return ret; } @@ -465,7 +506,7 @@ int cg_kill_recursive( r = cg_read_subgroup(d, &fn); if (r < 0) { - RET_GATHER(ret, r); + RET_GATHER(ret, log_debug_errno(r, "Failed to read subgroup from cgroup '%s': %m", path)); break; } if (r == 0) @@ -476,6 +517,8 @@ int cg_kill_recursive( return -ENOMEM; r = cg_kill_recursive(p, sig, flags, s, log_kill, userdata); + if (r < 0) + log_debug_errno(r, "Failed to recursively kill processes in cgroup '%s': %m", p); if (r != 0 && ret >= 0) ret = r; } @@ -484,7 +527,7 @@ int cg_kill_recursive( if (FLAGS_SET(flags, CGROUP_REMOVE)) { r = cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, path); if (!IN_SET(r, -ENOENT, -EBUSY)) - RET_GATHER(ret, r); + RET_GATHER(ret, log_debug_errno(r, "Failed to remove cgroup '%s': %m", path)); } return ret; @@ -917,7 +960,7 @@ int cg_is_empty(const char *controller, const char *path) { if (r < 0) return r; - r = cg_read_pid(f, &pid); + r = cg_read_pid(f, &pid, CGROUP_DONT_SKIP_UNMAPPED); if (r < 0) return r; @@ -1125,44 +1168,29 @@ int cg_pid_get_path_shifted(pid_t pid, const char *root, char **ret_cgroup) { if (r < 0) return r; - if (c == raw) + if (c == raw) { *ret_cgroup = TAKE_PTR(raw); - else { - char *n; - - n = strdup(c); - if (!n) - return -ENOMEM; - - *ret_cgroup = n; + return 0; } - return 0; + return strdup_to(ret_cgroup, c); } int cg_path_decode_unit(const char *cgroup, char **ret_unit) { - char *c, *s; - size_t n; - assert(cgroup); assert(ret_unit); - n = strcspn(cgroup, "/"); + size_t n = strcspn(cgroup, "/"); if (n < 3) return -ENXIO; - c = strndupa_safe(cgroup, n); + char *c = strndupa_safe(cgroup, n); c = cg_unescape(c); if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) return -ENXIO; - s = strdup(c); - if (!s) - return -ENOMEM; - - *ret_unit = s; - return 0; + return strdup_to(ret_unit, c); } static bool valid_slice_name(const char *p, size_t n) { @@ -1431,7 +1459,7 @@ int cg_pid_get_machine_name(pid_t pid, char **ret_machine) { int cg_path_get_cgroupid(const char *path, uint64_t *ret) { cg_file_handle fh = CG_FILE_HANDLE_INIT; - int mnt_id = -1; + int mnt_id; assert(path); assert(ret); @@ -1445,6 +1473,20 @@ int cg_path_get_cgroupid(const char *path, uint64_t *ret) { return 0; } +int cg_fd_get_cgroupid(int fd, uint64_t *ret) { + cg_file_handle fh = CG_FILE_HANDLE_INIT; + int mnt_id = -1; + + assert(fd >= 0); + assert(ret); + + if (name_to_handle_at(fd, "", &fh.file_handle, &mnt_id, AT_EMPTY_PATH) < 0) + return -errno; + + *ret = CG_FILE_HANDLE_CGROUPID(fh); + return 0; +} + int cg_path_get_session(const char *path, char **ret_session) { _cleanup_free_ char *unit = NULL; char *start, *end; @@ -1467,17 +1509,10 @@ int cg_path_get_session(const char *path, char **ret_session) { if (!session_id_valid(start)) return -ENXIO; - if (ret_session) { - char *rr; - - rr = strdup(start); - if (!rr) - return -ENOMEM; - - *ret_session = rr; - } + if (!ret_session) + return 0; - return 0; + return strdup_to(ret_session, start); } int cg_pid_get_session(pid_t pid, char **ret_session) { @@ -1534,34 +1569,26 @@ int cg_path_get_slice(const char *p, char **ret_slice) { assert(p); assert(ret_slice); - /* Finds the right-most slice unit from the beginning, but - * stops before we come to the first non-slice unit. */ + /* Finds the right-most slice unit from the beginning, but stops before we come to + * the first non-slice unit. */ for (;;) { - size_t n; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (!valid_slice_name(p, n)) { - - if (!e) { - char *s; + const char *s; + int n; - s = strdup(SPECIAL_ROOT_SLICE); - if (!s) - return -ENOMEM; + n = path_find_first_component(&p, /* accept_dot_dot = */ false, &s); + if (n < 0) + return n; + if (!valid_slice_name(s, n)) + break; - *ret_slice = s; - return 0; - } + e = s; + } - return cg_path_decode_unit(e, ret_slice); - } + if (e) + return cg_path_decode_unit(e, ret_slice); - e = p; - p += n; - } + return strdup_to(ret_slice, SPECIAL_ROOT_SLICE); } int cg_pid_get_slice(pid_t pid, char **ret_slice) { @@ -1714,15 +1741,8 @@ int cg_slice_to_path(const char *unit, char **ret) { assert(unit); assert(ret); - if (streq(unit, SPECIAL_ROOT_SLICE)) { - char *x; - - x = strdup(""); - if (!x) - return -ENOMEM; - *ret = x; - return 0; - } + if (streq(unit, SPECIAL_ROOT_SLICE)) + return strdup_to(ret, ""); if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) return -EINVAL; @@ -2141,15 +2161,14 @@ int cg_kernel_controllers(Set **ret) { _cleanup_free_ char *controller = NULL; int enabled = 0; - errno = 0; if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { + if (ferror(f)) + return -errno; + if (feof(f)) break; - if (ferror(f)) - return errno_or_else(EIO); - return -EBADMSG; } diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h index d06eb6d..a887178 100644 --- a/src/basic/cgroup-util.h +++ b/src/basic/cgroup-util.h @@ -67,7 +67,7 @@ typedef enum CGroupMask { /* All real cgroup v2 controllers */ CGROUP_MASK_V2 = CGROUP_MASK_CPU|CGROUP_MASK_CPUSET|CGROUP_MASK_IO|CGROUP_MASK_MEMORY|CGROUP_MASK_PIDS, - /* All controllers we want to delegate in case of Delegate=yes. Which are prety much the v2 controllers only, as delegation on v1 is not safe, and bpf stuff isn't a real controller */ + /* All controllers we want to delegate in case of Delegate=yes. Which are pretty much the v2 controllers only, as delegation on v1 is not safe, and bpf stuff isn't a real controller */ CGROUP_MASK_DELEGATE = CGROUP_MASK_V2, /* All cgroup v2 BPF pseudo-controllers */ @@ -180,20 +180,25 @@ typedef enum CGroupUnified { * generate paths with multiple adjacent / removed. */ +int cg_path_open(const char *controller, const char *path); +int cg_cgroupid_open(int fsfd, uint64_t id); + +typedef enum CGroupFlags { + CGROUP_SIGCONT = 1 << 0, + CGROUP_IGNORE_SELF = 1 << 1, + CGROUP_REMOVE = 1 << 2, + CGROUP_DONT_SKIP_UNMAPPED = 1 << 3, + CGROUP_NO_PIDFD = 1 << 4, +} CGroupFlags; + int cg_enumerate_processes(const char *controller, const char *path, FILE **ret); -int cg_read_pid(FILE *f, pid_t *ret); -int cg_read_pidref(FILE *f, PidRef *ret); +int cg_read_pid(FILE *f, pid_t *ret, CGroupFlags flags); +int cg_read_pidref(FILE *f, PidRef *ret, CGroupFlags flags); int cg_read_event(const char *controller, const char *path, const char *event, char **ret); int cg_enumerate_subgroups(const char *controller, const char *path, DIR **ret); int cg_read_subgroup(DIR *d, char **ret); -typedef enum CGroupFlags { - CGROUP_SIGCONT = 1 << 0, - CGROUP_IGNORE_SELF = 1 << 1, - CGROUP_REMOVE = 1 << 2, -} CGroupFlags; - typedef int (*cg_kill_log_func_t)(const PidRef *pid, int sig, void *userdata); int cg_kill(const char *path, int sig, CGroupFlags flags, Set *s, cg_kill_log_func_t kill_log, void *userdata); @@ -218,7 +223,7 @@ int cg_is_delegated_fd(int fd); int cg_has_coredump_receive(const char *path); -typedef enum { +typedef enum { CG_KEY_MODE_GRACEFUL = 1 << 0, } CGroupKeyMode; @@ -267,6 +272,7 @@ int cg_is_empty_recursive(const char *controller, const char *path); int cg_get_root_path(char **path); int cg_path_get_cgroupid(const char *path, uint64_t *ret); +int cg_fd_get_cgroupid(int fd, uint64_t *ret); int cg_path_get_session(const char *path, char **ret_session); int cg_path_get_owner_uid(const char *path, uid_t *ret_uid); int cg_path_get_unit(const char *path, char **ret_unit); @@ -352,5 +358,10 @@ typedef union { uint8_t space[offsetof(struct file_handle, f_handle) + sizeof(uint64_t)]; } cg_file_handle; -#define CG_FILE_HANDLE_INIT { .file_handle.handle_bytes = sizeof(uint64_t) } +#define CG_FILE_HANDLE_INIT \ + (cg_file_handle) { \ + .file_handle.handle_bytes = sizeof(uint64_t), \ + .file_handle.handle_type = FILEID_KERNFS, \ + } + #define CG_FILE_HANDLE_CGROUPID(fh) (*(uint64_t*) (fh).file_handle.f_handle) diff --git a/src/basic/chase.c b/src/basic/chase.c index 9f5477e..4576e4b 100644 --- a/src/basic/chase.c +++ b/src/basic/chase.c @@ -641,8 +641,8 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, * 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. */ + * As a special case, chaseat() may return "." or "./", which are normalized too, + * but we need to drop "." before merging with root. */ if (empty_or_root(root)) assert(path_is_absolute(p)); @@ -651,7 +651,7 @@ int chase(const char *path, const char *root, ChaseFlags flags, char **ret_path, assert(!path_is_absolute(p)); - q = path_join(root, p + (*p == '.')); + q = path_join(root, p + STR_IN_SET(p, ".", "./")); if (!q) return -ENOMEM; @@ -741,12 +741,7 @@ int chase_extract_filename(const char *path, const char *root, char **ret) { return r; } - char *fname = strdup("."); - if (!fname) - return -ENOMEM; - - *ret = fname; - return 0; + return strdup_to(ret, "."); } int chase_and_open(const char *path, const char *root, ChaseFlags chase_flags, int open_flags, char **ret_path) { diff --git a/src/basic/compress.c b/src/basic/compress.c index ac0bfdf..33b27d3 100644 --- a/src/basic/compress.c +++ b/src/basic/compress.c @@ -12,11 +12,6 @@ #include #endif -#if HAVE_LZ4 -#include -#include -#endif - #if HAVE_ZSTD #include #include @@ -34,16 +29,52 @@ #include "unaligned.h" #if HAVE_LZ4 -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_compressionContext_t, LZ4F_freeCompressionContext, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_decompressionContext_t, LZ4F_freeDecompressionContext, NULL); +static void *lz4_dl = NULL; + +static DLSYM_FUNCTION(LZ4F_compressBegin); +static DLSYM_FUNCTION(LZ4F_compressBound); +static DLSYM_FUNCTION(LZ4F_compressEnd); +static DLSYM_FUNCTION(LZ4F_compressUpdate); +static DLSYM_FUNCTION(LZ4F_createCompressionContext); +static DLSYM_FUNCTION(LZ4F_createDecompressionContext); +static DLSYM_FUNCTION(LZ4F_decompress); +static DLSYM_FUNCTION(LZ4F_freeCompressionContext); +static DLSYM_FUNCTION(LZ4F_freeDecompressionContext); +static DLSYM_FUNCTION(LZ4F_isError); +DLSYM_FUNCTION(LZ4_compress_default); +DLSYM_FUNCTION(LZ4_decompress_safe); +DLSYM_FUNCTION(LZ4_decompress_safe_partial); +DLSYM_FUNCTION(LZ4_versionNumber); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_compressionContext_t, sym_LZ4F_freeCompressionContext, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(LZ4F_decompressionContext_t, sym_LZ4F_freeDecompressionContext, NULL); #endif #if HAVE_ZSTD -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_CCtx*, ZSTD_freeCCtx, NULL); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_DCtx*, ZSTD_freeDCtx, NULL); +static void *zstd_dl = NULL; + +static DLSYM_FUNCTION(ZSTD_CCtx_setParameter); +static DLSYM_FUNCTION(ZSTD_compress); +static DLSYM_FUNCTION(ZSTD_compressStream2); +static DLSYM_FUNCTION(ZSTD_createCCtx); +static DLSYM_FUNCTION(ZSTD_createDCtx); +static DLSYM_FUNCTION(ZSTD_CStreamInSize); +static DLSYM_FUNCTION(ZSTD_CStreamOutSize); +static DLSYM_FUNCTION(ZSTD_decompressStream); +static DLSYM_FUNCTION(ZSTD_DStreamInSize); +static DLSYM_FUNCTION(ZSTD_DStreamOutSize); +static DLSYM_FUNCTION(ZSTD_freeCCtx); +static DLSYM_FUNCTION(ZSTD_freeDCtx); +static DLSYM_FUNCTION(ZSTD_getErrorCode); +static DLSYM_FUNCTION(ZSTD_getErrorName); +static DLSYM_FUNCTION(ZSTD_getFrameContentSize); +static DLSYM_FUNCTION(ZSTD_isError); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_CCtx*, sym_ZSTD_freeCCtx, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ZSTD_DCtx*, sym_ZSTD_freeDCtx, NULL); static int zstd_ret_to_errno(size_t ret) { - switch (ZSTD_getErrorCode(ret)) { + switch (sym_ZSTD_getErrorCode(ret)) { case ZSTD_error_dstSize_tooSmall: return -ENOBUFS; case ZSTD_error_memory_allocation: @@ -54,6 +85,27 @@ static int zstd_ret_to_errno(size_t ret) { } #endif +#if HAVE_XZ +static void *lzma_dl = NULL; + +static DLSYM_FUNCTION(lzma_code); +static DLSYM_FUNCTION(lzma_easy_encoder); +static DLSYM_FUNCTION(lzma_end); +static DLSYM_FUNCTION(lzma_stream_buffer_encode); +static DLSYM_FUNCTION(lzma_stream_decoder); + +/* We can't just do _cleanup_(sym_lzma_end) because a compiler bug makes + * this fail with: + * ../src/basic/compress.c: In function ‘decompress_blob_xz’: + * ../src/basic/compress.c:304:9: error: cleanup argument not a function + * 304 | _cleanup_(sym_lzma_end) lzma_stream s = LZMA_STREAM_INIT; + * | ^~~~~~~~~ + */ +static inline void lzma_end_wrapper(lzma_stream *ls) { + sym_lzma_end(ls); +} +#endif + #define ALIGN_8(l) ALIGN_TO(l, sizeof(size_t)) static const char* const compression_table[_COMPRESSION_MAX] = { @@ -75,8 +127,33 @@ bool compression_supported(Compression c) { return c >= 0 && c < _COMPRESSION_MAX && FLAGS_SET(supported, 1U << c); } +#if HAVE_XZ +int dlopen_lzma(void) { + ELF_NOTE_DLOPEN("lzma", + "Support lzma compression in journal and coredump files", + COMPRESSION_PRIORITY_XZ, + "liblzma.so.5"); + + return dlopen_many_sym_or_warn( + &lzma_dl, + "liblzma.so.5", LOG_DEBUG, + DLSYM_ARG(lzma_code), + DLSYM_ARG(lzma_easy_encoder), + DLSYM_ARG(lzma_end), + DLSYM_ARG(lzma_stream_buffer_encode), + DLSYM_ARG(lzma_stream_decoder)); +} +#endif + int compress_blob_xz(const void *src, uint64_t src_size, void *dst, size_t dst_alloc_size, size_t *dst_size) { + + assert(src); + assert(src_size > 0); + assert(dst); + assert(dst_alloc_size > 0); + assert(dst_size); + #if HAVE_XZ static const lzma_options_lzma opt = { 1u << 20u, NULL, 0, LZMA_LC_DEFAULT, LZMA_LP_DEFAULT, @@ -88,12 +165,11 @@ int compress_blob_xz(const void *src, uint64_t src_size, }; lzma_ret ret; size_t out_pos = 0; + int r; - assert(src); - assert(src_size > 0); - assert(dst); - assert(dst_alloc_size > 0); - assert(dst_size); + r = dlopen_lzma(); + if (r < 0) + return r; /* Returns < 0 if we couldn't compress the data or the * compressed result is longer than the original */ @@ -101,7 +177,7 @@ int compress_blob_xz(const void *src, uint64_t src_size, if (src_size < 80) return -ENOBUFS; - ret = lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, + ret = sym_lzma_stream_buffer_encode((lzma_filter*) filters, LZMA_CHECK_NONE, NULL, src, src_size, dst, &out_pos, dst_alloc_size); if (ret != LZMA_OK) return -ENOBUFS; @@ -113,10 +189,35 @@ int compress_blob_xz(const void *src, uint64_t src_size, #endif } +#if HAVE_LZ4 +int dlopen_lz4(void) { + ELF_NOTE_DLOPEN("lz4", + "Support lz4 compression in journal and coredump files", + COMPRESSION_PRIORITY_LZ4, + "liblz4.so.1"); + + return dlopen_many_sym_or_warn( + &lz4_dl, + "liblz4.so.1", LOG_DEBUG, + DLSYM_ARG(LZ4F_compressBegin), + DLSYM_ARG(LZ4F_compressBound), + DLSYM_ARG(LZ4F_compressEnd), + DLSYM_ARG(LZ4F_compressUpdate), + DLSYM_ARG(LZ4F_createCompressionContext), + DLSYM_ARG(LZ4F_createDecompressionContext), + DLSYM_ARG(LZ4F_decompress), + DLSYM_ARG(LZ4F_freeCompressionContext), + DLSYM_ARG(LZ4F_freeDecompressionContext), + DLSYM_ARG(LZ4F_isError), + DLSYM_ARG(LZ4_compress_default), + DLSYM_ARG(LZ4_decompress_safe), + DLSYM_ARG(LZ4_decompress_safe_partial), + DLSYM_ARG(LZ4_versionNumber)); +} +#endif + int compress_blob_lz4(const void *src, uint64_t src_size, void *dst, size_t dst_alloc_size, size_t *dst_size) { -#if HAVE_LZ4 - int r; assert(src); assert(src_size > 0); @@ -124,13 +225,19 @@ int compress_blob_lz4(const void *src, uint64_t src_size, assert(dst_alloc_size > 0); assert(dst_size); +#if HAVE_LZ4 + int r; + + r = dlopen_lz4(); + if (r < 0) + return r; /* Returns < 0 if we couldn't compress the data or the * compressed result is longer than the original */ if (src_size < 9) return -ENOBUFS; - r = LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); + r = sym_LZ4_compress_default(src, (char*)dst + 8, src_size, (int) dst_alloc_size - 8); if (r <= 0) return -ENOBUFS; @@ -143,11 +250,38 @@ int compress_blob_lz4(const void *src, uint64_t src_size, #endif } +#if HAVE_ZSTD +int dlopen_zstd(void) { + ELF_NOTE_DLOPEN("zstd", + "Support zstd compression in journal and coredump files", + COMPRESSION_PRIORITY_ZSTD, + "libzstd.so.1"); + + return dlopen_many_sym_or_warn( + &zstd_dl, + "libzstd.so.1", LOG_DEBUG, + DLSYM_ARG(ZSTD_getErrorCode), + DLSYM_ARG(ZSTD_compress), + DLSYM_ARG(ZSTD_getFrameContentSize), + DLSYM_ARG(ZSTD_decompressStream), + DLSYM_ARG(ZSTD_getErrorName), + DLSYM_ARG(ZSTD_DStreamOutSize), + DLSYM_ARG(ZSTD_CStreamInSize), + DLSYM_ARG(ZSTD_CStreamOutSize), + DLSYM_ARG(ZSTD_CCtx_setParameter), + DLSYM_ARG(ZSTD_compressStream2), + DLSYM_ARG(ZSTD_DStreamInSize), + DLSYM_ARG(ZSTD_freeCCtx), + DLSYM_ARG(ZSTD_freeDCtx), + DLSYM_ARG(ZSTD_isError), + DLSYM_ARG(ZSTD_createDCtx), + DLSYM_ARG(ZSTD_createCCtx)); +} +#endif + int compress_blob_zstd( const void *src, uint64_t src_size, void *dst, size_t dst_alloc_size, size_t *dst_size) { -#if HAVE_ZSTD - size_t k; assert(src); assert(src_size > 0); @@ -155,8 +289,16 @@ int compress_blob_zstd( assert(dst_alloc_size > 0); assert(dst_size); - k = ZSTD_compress(dst, dst_alloc_size, src, src_size, 0); - if (ZSTD_isError(k)) +#if HAVE_ZSTD + size_t k; + int r; + + r = dlopen_zstd(); + if (r < 0) + return r; + + k = sym_ZSTD_compress(dst, dst_alloc_size, src, src_size, 0); + if (sym_ZSTD_isError(k)) return zstd_ret_to_errno(k); *dst_size = k; @@ -173,17 +315,22 @@ int decompress_blob_xz( size_t* dst_size, size_t dst_max) { -#if HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - lzma_ret ret; - size_t space; - assert(src); assert(src_size > 0); assert(dst); assert(dst_size); - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); +#if HAVE_XZ + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; + lzma_ret ret; + size_t space; + int r; + + r = dlopen_lzma(); + if (r < 0) + return r; + + ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); if (ret != LZMA_OK) return -ENOMEM; @@ -200,7 +347,7 @@ int decompress_blob_xz( for (;;) { size_t used; - ret = lzma_code(&s, LZMA_FINISH); + ret = sym_lzma_code(&s, LZMA_FINISH); if (ret == LZMA_STREAM_END) break; @@ -235,15 +382,19 @@ int decompress_blob_lz4( size_t* dst_size, size_t dst_max) { -#if HAVE_LZ4 - char* out; - int r, size; /* LZ4 uses int for size */ - assert(src); assert(src_size > 0); assert(dst); assert(dst_size); +#if HAVE_LZ4 + char* out; + int r, size; /* LZ4 uses int for size */ + + r = dlopen_lz4(); + if (r < 0) + return r; + if (src_size <= 8) return -EBADMSG; @@ -254,7 +405,7 @@ int decompress_blob_lz4( if (!out) return -ENOMEM; - r = LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); + r = sym_LZ4_decompress_safe((char*)src + 8, out, src_size - 8, size); if (r < 0 || r != size) return -EBADMSG; @@ -272,15 +423,20 @@ int decompress_blob_zstd( size_t *dst_size, size_t dst_max) { -#if HAVE_ZSTD - uint64_t size; - assert(src); assert(src_size > 0); assert(dst); assert(dst_size); - size = ZSTD_getFrameContentSize(src, src_size); +#if HAVE_ZSTD + uint64_t size; + int r; + + r = dlopen_zstd(); + if (r < 0) + return r; + + size = sym_ZSTD_getFrameContentSize(src, src_size); if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) return -EBADMSG; @@ -289,10 +445,10 @@ int decompress_blob_zstd( if (size > SIZE_MAX) return -E2BIG; - if (!(greedy_realloc(dst, MAX(ZSTD_DStreamOutSize(), size), 1))) + if (!(greedy_realloc(dst, MAX(sym_ZSTD_DStreamOutSize(), size), 1))) return -ENOMEM; - _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = ZSTD_createDCtx(); + _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); if (!dctx) return -ENOMEM; @@ -305,9 +461,9 @@ int decompress_blob_zstd( .size = MALLOC_SIZEOF_SAFE(*dst), }; - size_t k = ZSTD_decompressStream(dctx, &output, &input); - if (ZSTD_isError(k)) { - log_debug("ZSTD decoder failed: %s", ZSTD_getErrorName(k)); + size_t k = sym_ZSTD_decompressStream(dctx, &output, &input); + if (sym_ZSTD_isError(k)) { + log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); return zstd_ret_to_errno(k); } assert(output.pos >= size); @@ -351,11 +507,6 @@ int decompress_startswith_xz( size_t prefix_len, uint8_t extra) { -#if HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; - size_t allocated; - lzma_ret ret; - /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to * follow the prefix */ @@ -364,7 +515,17 @@ int decompress_startswith_xz( assert(buffer); assert(prefix); - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); +#if HAVE_XZ + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; + size_t allocated; + lzma_ret ret; + int r; + + r = dlopen_lzma(); + if (r < 0) + return r; + + ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); if (ret != LZMA_OK) return -EBADMSG; @@ -380,7 +541,7 @@ int decompress_startswith_xz( s.avail_out = allocated; for (;;) { - ret = lzma_code(&s, LZMA_FINISH); + ret = sym_lzma_code(&s, LZMA_FINISH); if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) return -EBADMSG; @@ -414,18 +575,22 @@ int decompress_startswith_lz4( size_t prefix_len, uint8_t extra) { -#if HAVE_LZ4 /* Checks whether the decompressed blob starts with the mentioned prefix. The byte extra needs to * follow the prefix */ - size_t allocated; - int r; - assert(src); assert(src_size > 0); assert(buffer); assert(prefix); +#if HAVE_LZ4 + size_t allocated; + int r; + + r = dlopen_lz4(); + if (r < 0) + return r; + if (src_size <= 8) return -EBADMSG; @@ -433,7 +598,7 @@ int decompress_startswith_lz4( return -ENOMEM; allocated = MALLOC_SIZEOF_SAFE(*buffer); - r = LZ4_decompress_safe_partial( + r = sym_LZ4_decompress_safe_partial( (char*)src + 8, *buffer, src_size - 8, @@ -447,7 +612,7 @@ int decompress_startswith_lz4( if (r < 0 || (size_t) r < prefix_len + 1) { size_t size; - if (LZ4_versionNumber() >= 10803) + if (sym_LZ4_versionNumber() >= 10803) /* We trust that the newer lz4 decompresses the number of bytes we * requested if available in the compressed string. */ return 0; @@ -482,24 +647,31 @@ int decompress_startswith_zstd( const void *prefix, size_t prefix_len, uint8_t extra) { -#if HAVE_ZSTD + assert(src); assert(src_size > 0); assert(buffer); assert(prefix); - uint64_t size = ZSTD_getFrameContentSize(src, src_size); +#if HAVE_ZSTD + int r; + + r = dlopen_zstd(); + if (r < 0) + return r; + + uint64_t size = sym_ZSTD_getFrameContentSize(src, src_size); if (IN_SET(size, ZSTD_CONTENTSIZE_ERROR, ZSTD_CONTENTSIZE_UNKNOWN)) return -EBADMSG; if (size < prefix_len + 1) return 0; /* Decompressed text too short to match the prefix and extra */ - _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = ZSTD_createDCtx(); + _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = sym_ZSTD_createDCtx(); if (!dctx) return -ENOMEM; - if (!(greedy_realloc(buffer, MAX(ZSTD_DStreamOutSize(), prefix_len + 1), 1))) + if (!(greedy_realloc(buffer, MAX(sym_ZSTD_DStreamOutSize(), prefix_len + 1), 1))) return -ENOMEM; ZSTD_inBuffer input = { @@ -512,9 +684,9 @@ int decompress_startswith_zstd( }; size_t k; - k = ZSTD_decompressStream(dctx, &output, &input); - if (ZSTD_isError(k)) { - log_debug("ZSTD decoder failed: %s", ZSTD_getErrorName(k)); + k = sym_ZSTD_decompressStream(dctx, &output, &input); + if (sym_ZSTD_isError(k)) { + log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(k)); return zstd_ret_to_errno(k); } assert(output.pos >= prefix_len + 1); @@ -559,16 +731,21 @@ int decompress_startswith( } int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { + assert(fdf >= 0); + assert(fdt >= 0); + #if HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; lzma_ret ret; uint8_t buf[BUFSIZ], out[BUFSIZ]; lzma_action action = LZMA_RUN; + int r; - assert(fdf >= 0); - assert(fdt >= 0); + r = dlopen_lzma(); + if (r < 0) + return r; - ret = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); + ret = sym_lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); if (ret != LZMA_OK) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize XZ encoder: code %u", @@ -603,7 +780,7 @@ int compress_stream_xz(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncom s.avail_out = sizeof(out); } - ret = lzma_code(&s, action); + ret = sym_lzma_code(&s, action); if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Compression failed: code %u", @@ -641,7 +818,7 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco #if HAVE_LZ4 LZ4F_errorCode_t c; - _cleanup_(LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; + _cleanup_(sym_LZ4F_freeCompressionContextp) LZ4F_compressionContext_t ctx = NULL; _cleanup_free_ void *in_buff = NULL; _cleanup_free_ char *out_buff = NULL; size_t out_allocsize, n, offset = 0, frame_size; @@ -651,11 +828,15 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco .frameInfo.blockSizeID = 5, }; - c = LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); - if (LZ4F_isError(c)) + r = dlopen_lz4(); + if (r < 0) + return r; + + c = sym_LZ4F_createCompressionContext(&ctx, LZ4F_VERSION); + if (sym_LZ4F_isError(c)) return -ENOMEM; - frame_size = LZ4F_compressBound(LZ4_BUFSIZE, &preferences); + frame_size = sym_LZ4F_compressBound(LZ4_BUFSIZE, &preferences); out_allocsize = frame_size + 64*1024; /* add some space for header and trailer */ out_buff = malloc(out_allocsize); if (!out_buff) @@ -665,8 +846,8 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco if (!in_buff) return -ENOMEM; - n = offset = total_out = LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); - if (LZ4F_isError(n)) + n = offset = total_out = sym_LZ4F_compressBegin(ctx, out_buff, out_allocsize, &preferences); + if (sym_LZ4F_isError(n)) return -EINVAL; log_debug("Buffer size is %zu bytes, header size %zu bytes.", out_allocsize, n); @@ -679,9 +860,9 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco return k; if (k == 0) break; - n = LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, + n = sym_LZ4F_compressUpdate(ctx, out_buff + offset, out_allocsize - offset, in_buff, k, NULL); - if (LZ4F_isError(n)) + if (sym_LZ4F_isError(n)) return -ENOTRECOVERABLE; total_in += k; @@ -700,8 +881,8 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco } } - n = LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); - if (LZ4F_isError(n)) + n = sym_LZ4F_compressEnd(ctx, out_buff + offset, out_allocsize - offset, NULL); + if (sym_LZ4F_isError(n)) return -ENOTRECOVERABLE; offset += n; @@ -724,18 +905,22 @@ int compress_stream_lz4(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unco } int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { + assert(fdf >= 0); + assert(fdt >= 0); #if HAVE_XZ - _cleanup_(lzma_end) lzma_stream s = LZMA_STREAM_INIT; + _cleanup_(lzma_end_wrapper) lzma_stream s = LZMA_STREAM_INIT; lzma_ret ret; uint8_t buf[BUFSIZ], out[BUFSIZ]; lzma_action action = LZMA_RUN; + int r; - assert(fdf >= 0); - assert(fdt >= 0); + r = dlopen_lzma(); + if (r < 0) + return r; - ret = lzma_stream_decoder(&s, UINT64_MAX, 0); + ret = sym_lzma_stream_decoder(&s, UINT64_MAX, 0); if (ret != LZMA_OK) return log_debug_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to initialize XZ decoder: code %u", @@ -761,7 +946,7 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { s.avail_out = sizeof(out); } - ret = lzma_code(&s, action); + ret = sym_lzma_code(&s, action); if (!IN_SET(ret, LZMA_OK, LZMA_STREAM_END)) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Decompression failed: code %u", @@ -801,15 +986,19 @@ int decompress_stream_xz(int fdf, int fdt, uint64_t max_bytes) { int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { #if HAVE_LZ4 size_t c; - _cleanup_(LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; + _cleanup_(sym_LZ4F_freeDecompressionContextp) LZ4F_decompressionContext_t ctx = NULL; _cleanup_free_ char *buf = NULL; char *src; struct stat st; - int r = 0; + int r; size_t total_in = 0, total_out = 0; - c = LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); - if (LZ4F_isError(c)) + r = dlopen_lz4(); + if (r < 0) + return r; + + c = sym_LZ4F_createDecompressionContext(&ctx, LZ4F_VERSION); + if (sym_LZ4F_isError(c)) return -ENOMEM; if (fstat(in, &st) < 0) @@ -830,8 +1019,8 @@ int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { size_t produced = LZ4_BUFSIZE; size_t used = st.st_size - total_in; - c = LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); - if (LZ4F_isError(c)) { + c = sym_LZ4F_decompress(ctx, buf, &produced, src + total_in, &used, NULL); + if (sym_LZ4F_isError(c)) { r = -EBADMSG; goto cleanup; } @@ -853,6 +1042,7 @@ int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { log_debug("LZ4 decompression finished (%zu -> %zu bytes, %.1f%%)", total_in, total_out, total_in > 0 ? (double) total_out / total_in * 100 : 0.0); + r = 0; cleanup: munmap(src, st.st_size); return r; @@ -863,28 +1053,33 @@ int decompress_stream_lz4(int in, int out, uint64_t max_bytes) { } int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_uncompressed_size) { + assert(fdf >= 0); + assert(fdt >= 0); + #if HAVE_ZSTD - _cleanup_(ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; + _cleanup_(sym_ZSTD_freeCCtxp) ZSTD_CCtx *cctx = NULL; _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; size_t in_allocsize, out_allocsize; size_t z; uint64_t left = max_bytes, in_bytes = 0; + int r; - assert(fdf >= 0); - assert(fdt >= 0); + r = dlopen_zstd(); + if (r < 0) + return r; /* Create the context and buffers */ - in_allocsize = ZSTD_CStreamInSize(); - out_allocsize = ZSTD_CStreamOutSize(); + in_allocsize = sym_ZSTD_CStreamInSize(); + out_allocsize = sym_ZSTD_CStreamOutSize(); in_buff = malloc(in_allocsize); out_buff = malloc(out_allocsize); - cctx = ZSTD_createCCtx(); + cctx = sym_ZSTD_createCCtx(); if (!cctx || !out_buff || !in_buff) return -ENOMEM; - z = ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); - if (ZSTD_isError(z)) - log_debug("Failed to enable ZSTD checksum, ignoring: %s", ZSTD_getErrorName(z)); + z = sym_ZSTD_CCtx_setParameter(cctx, ZSTD_c_checksumFlag, 1); + if (sym_ZSTD_isError(z)) + log_debug("Failed to enable ZSTD checksum, ignoring: %s", sym_ZSTD_getErrorName(z)); /* This loop read from the input file, compresses that entire chunk, * and writes all output produced to the output file. @@ -919,12 +1114,12 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unc * output to the file so we can reuse the buffer next * iteration. */ - remaining = ZSTD_compressStream2( + remaining = sym_ZSTD_compressStream2( cctx, &output, &input, is_last_chunk ? ZSTD_e_end : ZSTD_e_continue); - if (ZSTD_isError(remaining)) { - log_debug("ZSTD encoder failed: %s", ZSTD_getErrorName(remaining)); + if (sym_ZSTD_isError(remaining)) { + log_debug("ZSTD encoder failed: %s", sym_ZSTD_getErrorName(remaining)); return zstd_ret_to_errno(remaining); } @@ -968,22 +1163,26 @@ int compress_stream_zstd(int fdf, int fdt, uint64_t max_bytes, uint64_t *ret_unc } int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { + assert(fdf >= 0); + assert(fdt >= 0); + #if HAVE_ZSTD - _cleanup_(ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; + _cleanup_(sym_ZSTD_freeDCtxp) ZSTD_DCtx *dctx = NULL; _cleanup_free_ void *in_buff = NULL, *out_buff = NULL; size_t in_allocsize, out_allocsize; size_t last_result = 0; uint64_t left = max_bytes, in_bytes = 0; + int r; - assert(fdf >= 0); - assert(fdt >= 0); - + r = dlopen_zstd(); + if (r < 0) + return r; /* Create the context and buffers */ - in_allocsize = ZSTD_DStreamInSize(); - out_allocsize = ZSTD_DStreamOutSize(); + in_allocsize = sym_ZSTD_DStreamInSize(); + out_allocsize = sym_ZSTD_DStreamOutSize(); in_buff = malloc(in_allocsize); out_buff = malloc(out_allocsize); - dctx = ZSTD_createDCtx(); + dctx = sym_ZSTD_createDCtx(); if (!dctx || !out_buff || !in_buff) return -ENOMEM; @@ -1032,8 +1231,8 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { * for instance if the last decompression call returned * an error. */ - last_result = ZSTD_decompressStream(dctx, &output, &input); - if (ZSTD_isError(last_result)) { + last_result = sym_ZSTD_decompressStream(dctx, &output, &input); + if (sym_ZSTD_isError(last_result)) { has_error = true; break; } @@ -1059,7 +1258,7 @@ int decompress_stream_zstd(int fdf, int fdt, uint64_t max_bytes) { * on a frame, but we reached the end of the file! We assume * this is an error, and the input was truncated. */ - log_debug("ZSTD decoder failed: %s", ZSTD_getErrorName(last_result)); + log_debug("ZSTD decoder failed: %s", sym_ZSTD_getErrorName(last_result)); return zstd_ret_to_errno(last_result); } diff --git a/src/basic/compress.h b/src/basic/compress.h index 1b5c645..d15c189 100644 --- a/src/basic/compress.h +++ b/src/basic/compress.h @@ -6,6 +6,13 @@ #include #include +#if HAVE_LZ4 +#include +#include +#endif + +#include "dlfcn-util.h" + typedef enum Compression { COMPRESSION_NONE, COMPRESSION_XZ, @@ -63,6 +70,23 @@ 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); +#if HAVE_LZ4 +DLSYM_PROTOTYPE(LZ4_compress_default); +DLSYM_PROTOTYPE(LZ4_decompress_safe); +DLSYM_PROTOTYPE(LZ4_decompress_safe_partial); +DLSYM_PROTOTYPE(LZ4_versionNumber); + +int dlopen_lz4(void); +#endif + +#if HAVE_ZSTD +int dlopen_zstd(void); +#endif + +#if HAVE_XZ +int dlopen_lzma(void); +#endif + static inline int compress_blob( Compression compression, const void *src, uint64_t src_size, diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c index a56f82f..7fdcc71 100644 --- a/src/basic/conf-files.c +++ b/src/basic/conf-files.c @@ -9,7 +9,10 @@ #include "conf-files.h" #include "constants.h" #include "dirent-util.h" +#include "errno-util.h" #include "fd-util.h" +#include "fileio.h" +#include "glyph-util.h" #include "hashmap.h" #include "log.h" #include "macro.h" @@ -366,9 +369,103 @@ int conf_files_list_dropins( assert(dirs); suffix = strjoina("/", dropin_dirname); - r = strv_extend_strv_concat(&dropin_dirs, (char**) dirs, suffix); + r = strv_extend_strv_concat(&dropin_dirs, dirs, suffix); if (r < 0) return r; return conf_files_list_strv(ret, ".conf", root, 0, (const char* const*) dropin_dirs); } + +/** + * Open and read a config file. + * + * The argument may be: + * - '-', meaning stdin. + * - a file name without a path. In this case are searched. + * - a path, either relative or absolute. In this case is opened directly. + * + * This method is only suitable for configuration files which have a flat layout without dropins. + */ +int conf_file_read( + const char *root, + const char **config_dirs, + const char *fn, + parse_line_t parse_line, + void *userdata, + bool ignore_enoent, + bool *invalid_config) { + + _cleanup_fclose_ FILE *_f = NULL; + _cleanup_free_ char *_fn = NULL; + unsigned v = 0; + FILE *f; + int r = 0; + + assert(fn); + + if (streq(fn, "-")) { + f = stdin; + fn = ""; + + log_debug("Reading config from stdin%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + + } else if (is_path(fn)) { + r = path_make_absolute_cwd(fn, &_fn); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + fn = _fn; + + f = _f = fopen(fn, "re"); + if (!_f) + r = -errno; + else + log_debug("Reading config file \"%s\"%s", fn, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + + } else { + r = search_and_fopen(fn, "re", root, config_dirs, &_f, &_fn); + if (r >= 0) { + f = _f; + fn = _fn; + log_debug("Reading config file \"%s\"%s", fn, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + } + } + + if (r == -ENOENT && ignore_enoent) { + log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); + return 0; /* No error, but nothing happened. */ + } + if (r < 0) + return log_error_errno(r, "Failed to read '%s': %m", fn); + + r = 1; /* We entered the part where we may modify state. */ + + for (;;) { + _cleanup_free_ char *line = NULL; + bool invalid_line = false; + int k; + + k = read_stripped_line(f, LONG_LINE_MAX, &line); + if (k < 0) + return log_error_errno(k, "Failed to read '%s': %m", fn); + if (k == 0) + break; + + v++; + + if (IN_SET(line[0], 0, '#')) + continue; + + k = parse_line(fn, v, line, invalid_config ? &invalid_line : NULL, userdata); + if (k < 0 && invalid_line) + /* Allow reporting with a special code if the caller requested this. */ + *invalid_config = true; + else + /* The first error, if any, becomes our return value. */ + RET_GATHER(r, k); + } + + if (ferror(f)) + RET_GATHER(r, log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read from file %s.", fn)); + + return r; +} diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h index 566cc8f..cf89ee6 100644 --- a/src/basic/conf-files.h +++ b/src/basic/conf-files.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "macro.h" enum { @@ -29,3 +31,19 @@ int conf_files_list_dropins( const char *dropin_dirname, const char *root, const char * const *dirs); + +typedef int parse_line_t( + const char *fname, + unsigned line, + const char *buffer, + bool *invalid_config, + void *userdata); + +int conf_file_read( + const char *root, + const char **config_dirs, + const char *fn, + parse_line_t parse_line, + void *userdata, + bool ignore_enoent, + bool *invalid_config); diff --git a/src/basic/constants.h b/src/basic/constants.h index 6bb5f3c..e70817c 100644 --- a/src/basic/constants.h +++ b/src/basic/constants.h @@ -42,9 +42,6 @@ #define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) #define DEFAULT_START_LIMIT_BURST 5 -/* Wait for 1.5 seconds at maximum for freeze operation */ -#define FREEZE_TIMEOUT (1500 * USEC_PER_MSEC) - /* The default time after which exit-on-idle services exit. This * should be kept lower than the watchdog timeout, because otherwise * the watchdog pings will keep the loop busy. */ @@ -67,18 +64,12 @@ "/usr/local/lib/" n "\0" \ "/usr/lib/" n "\0" -#define CONF_PATHS_USR(n) \ +#define CONF_PATHS(n) \ "/etc/" n, \ "/run/" n, \ "/usr/local/lib/" n, \ "/usr/lib/" n -#define CONF_PATHS(n) \ - CONF_PATHS_USR(n) - -#define CONF_PATHS_USR_STRV(n) \ - STRV_MAKE(CONF_PATHS_USR(n)) - #define CONF_PATHS_STRV(n) \ STRV_MAKE(CONF_PATHS(n)) diff --git a/src/basic/devnum-util.c b/src/basic/devnum-util.c index f82e13b..652740c 100644 --- a/src/basic/devnum-util.c +++ b/src/basic/devnum-util.c @@ -58,21 +58,18 @@ int device_path_make_major_minor(mode_t mode, dev_t devnum, char **ret) { } int device_path_make_inaccessible(mode_t mode, char **ret) { - char *s; + const char *s; assert(ret); if (S_ISCHR(mode)) - s = strdup("/run/systemd/inaccessible/chr"); + s = "/run/systemd/inaccessible/chr"; else if (S_ISBLK(mode)) - s = strdup("/run/systemd/inaccessible/blk"); + s = "/run/systemd/inaccessible/blk"; else return -ENODEV; - if (!s) - return -ENOMEM; - *ret = s; - return 0; + return strdup_to(ret, s); } int device_path_make_canonical(mode_t mode, dev_t devnum, char **ret) { diff --git a/src/basic/dlfcn-util.c b/src/basic/dlfcn-util.c new file mode 100644 index 0000000..8022f55 --- /dev/null +++ b/src/basic/dlfcn-util.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dlfcn-util.h" + +static int dlsym_many_or_warnv(void *dl, int log_level, va_list ap) { + void (**fn)(void); + + /* Tries to resolve a bunch of function symbols, and logs an error about if it cannot resolve one of + * them. Note that this function possibly modifies the supplied function pointers if the whole + * operation fails. */ + + while ((fn = va_arg(ap, typeof(fn)))) { + void (*tfn)(void); + const char *symbol; + + symbol = va_arg(ap, typeof(symbol)); + + tfn = (typeof(tfn)) dlsym(dl, symbol); + if (!tfn) + return log_full_errno(log_level, + SYNTHETIC_ERRNO(ELIBBAD), + "Can't find symbol %s: %s", symbol, dlerror()); + *fn = tfn; + } + + return 0; +} + +int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { + va_list ap; + int r; + + va_start(ap, log_level); + r = dlsym_many_or_warnv(dl, log_level, ap); + va_end(ap); + + return r; +} + +int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { + _cleanup_(dlclosep) void *dl = NULL; + int r; + + if (*dlp) + return 0; /* Already loaded */ + + dl = dlopen(filename, RTLD_LAZY); + if (!dl) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "%s is not installed: %s", filename, dlerror()); + + log_debug("Loaded '%s' via dlopen()", filename); + + va_list ap; + va_start(ap, log_level); + r = dlsym_many_or_warnv(dl, log_level, ap); + va_end(ap); + + if (r < 0) + return r; + + /* Note that we never release the reference here, because there's no real reason to. After all this + * was traditionally a regular shared library dependency which lives forever too. */ + *dlp = TAKE_PTR(dl); + return 1; +} diff --git a/src/basic/dlfcn-util.h b/src/basic/dlfcn-util.h new file mode 100644 index 0000000..83ab320 --- /dev/null +++ b/src/basic/dlfcn-util.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +static inline void* safe_dlclose(void *dl) { + if (!dl) + return NULL; + + assert_se(dlclose(dl) == 0); + return NULL; +} + +static inline void dlclosep(void **dlp) { + safe_dlclose(*dlp); +} + +int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; +int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; + +#define dlsym_many_or_warn(dl, log_level, ...) \ + dlsym_many_or_warn_sentinel(dl, log_level, __VA_ARGS__, NULL) +#define dlopen_many_sym_or_warn(dlp, filename, log_level, ...) \ + dlopen_many_sym_or_warn_sentinel(dlp, filename, log_level, __VA_ARGS__, NULL) + +#define DLSYM_PROTOTYPE(symbol) \ + extern typeof(symbol)* sym_##symbol +#define DLSYM_FUNCTION(symbol) \ + typeof(symbol)* sym_##symbol = NULL + +/* Macro useful for putting together variable/symbol name pairs when calling dlsym_many_or_warn(). Assumes + * that each library symbol to resolve will be placed in a variable with the "sym_" prefix, i.e. a symbol + * "foobar" is loaded into a variable "sym_foobar". */ +#define DLSYM_ARG(arg) \ + ({ assert_cc(__builtin_types_compatible_p(typeof(sym_##arg), typeof(&arg))); &sym_##arg; }), STRINGIFY(arg) + +/* libbpf is a bit confused about type-safety and API compatibility. Provide a macro that can tape over that mess. Sad. */ +#define DLSYM_ARG_FORCE(arg) \ + &sym_##arg, STRINGIFY(arg) + +#define ELF_NOTE_DLOPEN_VENDOR "FDO" +#define ELF_NOTE_DLOPEN_TYPE UINT32_C(0x407c0c0a) +#define ELF_NOTE_DLOPEN_PRIORITY_REQUIRED "required" +#define ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED "recommended" +#define ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED "suggested" + +/* Add an ".note.dlopen" ELF note to our binary that declares our weak dlopen() dependency. This + * information can be read from an ELF file via "readelf -p .note.dlopen" or an equivalent command. */ +#define _ELF_NOTE_DLOPEN(json, variable_name) \ + __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ + struct { \ + uint32_t n_namesz, n_descsz, n_type; \ + } nhdr; \ + char name[sizeof(ELF_NOTE_DLOPEN_VENDOR)]; \ + _Alignas(sizeof(uint32_t)) char dlopen_json[sizeof(json)]; \ + } variable_name = { \ + .nhdr = { \ + .n_namesz = sizeof(ELF_NOTE_DLOPEN_VENDOR), \ + .n_descsz = sizeof(json), \ + .n_type = ELF_NOTE_DLOPEN_TYPE, \ + }, \ + .name = ELF_NOTE_DLOPEN_VENDOR, \ + .dlopen_json = json, \ + } + +#define _SONAME_ARRAY1(a) "[\""a"\"]" +#define _SONAME_ARRAY2(a, b) "[\""a"\",\""b"\"]" +#define _SONAME_ARRAY3(a, b, c) "[\""a"\",\""b"\",\""c"\"]" +#define _SONAME_ARRAY4(a, b, c, d) "[\""a"\",\""b"\",\""c"\"",\""d"\"]" +#define _SONAME_ARRAY5(a, b, c, d, e) "[\""a"\",\""b"\",\""c"\"",\""d"\",\""e"\"]" +#define _SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME +#define _SONAME_ARRAY(...) _SONAME_ARRAY_GET(__VA_ARGS__, _SONAME_ARRAY5, _SONAME_ARRAY4, _SONAME_ARRAY3, _SONAME_ARRAY2, _SONAME_ARRAY1)(__VA_ARGS__) + +/* The 'priority' must be one of 'required', 'recommended' or 'suggested' as per specification, use the + * macro defined above to specify it. + * Multiple sonames can be passed and they will be automatically constructed into a json array (but note that + * due to preprocessor language limitations if more than the limit defined above is used, a new + * _SONAME_ARRAY will need to be added). */ +#define ELF_NOTE_DLOPEN(feature, description, priority, ...) \ + _ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ)) diff --git a/src/basic/efivars.c b/src/basic/efivars.c index 9011ae2..8470d08 100644 --- a/src/basic/efivars.c +++ b/src/basic/efivars.c @@ -177,12 +177,13 @@ static int efi_verify_variable(const char *variable, uint32_t attr, const void * } int efi_set_variable(const char *variable, const void *value, size_t size) { + static const uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + struct var { uint32_t attr; char buf[]; } _packed_ * _cleanup_free_ buf = NULL; _cleanup_close_ int fd = -EBADF; - uint32_t attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; bool saved_flags_valid = false; unsigned saved_flags; int r; @@ -190,14 +191,14 @@ int efi_set_variable(const char *variable, const void *value, size_t size) { assert(variable); assert(value || size == 0); - const char *p = strjoina("/sys/firmware/efi/efivars/", variable); - /* size 0 means removal, empty variable would not be enough for that */ if (size > 0 && efi_verify_variable(variable, attr, value, size) > 0) { log_debug("Variable '%s' is already in wanted state, skipping write.", variable); return 0; } + const char *p = strjoina("/sys/firmware/efi/efivars/", variable); + /* Newer efivarfs protects variables that are not in an allow list with FS_IMMUTABLE_FL by default, * to protect them for accidental removal and modification. We are not changing these variables * accidentally however, hence let's unset the bit first. */ @@ -238,10 +239,7 @@ int efi_set_variable(const char *variable, const void *value, size_t size) { /* For some reason efivarfs doesn't update mtime automatically. Let's do it manually then. This is * useful for processes that cache EFI variables to detect when changes occurred. */ - if (futimens(fd, (struct timespec[2]) { - { .tv_nsec = UTIME_NOW }, - { .tv_nsec = UTIME_NOW } - }) < 0) + if (futimens(fd, /* times = */ NULL) < 0) log_debug_errno(errno, "Failed to update mtime/atime on %s, ignoring: %m", p); r = 0; @@ -398,16 +396,8 @@ int systemd_efi_options_variable(char **ret) { /* For testing purposes it is sometimes useful to be able to override this */ e = secure_getenv("SYSTEMD_EFI_OPTIONS"); - if (e) { - char *m; - - m = strdup(e); - if (!m) - return -ENOMEM; - - *ret = m; - return 0; - } + if (e) + return strdup_to(ret, e); r = read_one_line_file(EFIVAR_CACHE_PATH(EFI_SYSTEMD_VARIABLE(SystemdOptions)), ret); if (r == -ENOENT) diff --git a/src/basic/env-file.c b/src/basic/env-file.c index c2cbff4..2fff98f 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -125,7 +125,7 @@ static int parse_env_file_internal( state = VALUE; if (!GREEDY_REALLOC(value, n_value+2)) - return -ENOMEM; + return -ENOMEM; value[n_value++] = c; } diff --git a/src/basic/env-util.c b/src/basic/env-util.c index a97651d..9e74ba0 100644 --- a/src/basic/env-util.c +++ b/src/basic/env-util.c @@ -18,6 +18,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" +#include "syslog-util.h" #include "utf8.h" /* We follow bash for the character set. Different shells have different rules. */ @@ -244,9 +245,9 @@ static bool env_match(const char *t, const char *pattern) { return true; if (!strchr(pattern, '=')) { - size_t l = strlen(pattern); + t = startswith(t, pattern); - return strneq(t, pattern, l) && t[l] == '='; + return t && *t == '='; } return false; @@ -309,19 +310,17 @@ char **strv_env_delete(char **x, size_t n_lists, ...) { return TAKE_PTR(t); } -char **strv_env_unset(char **l, const char *p) { - char **f, **t; +char** strv_env_unset(char **l, const char *p) { + assert(p); if (!l) return NULL; - assert(p); - /* Drops every occurrence of the env var setting p in the * string list. Edits in-place. */ + char **f, **t; for (f = t = l; *f; f++) { - if (env_match(*f, p)) { free(*f); continue; @@ -334,14 +333,13 @@ char **strv_env_unset(char **l, const char *p) { return l; } -char **strv_env_unset_many(char **l, ...) { - char **f, **t; - +char** strv_env_unset_many_internal(char **l, ...) { if (!l) return NULL; /* Like strv_env_unset() but applies many at once. Edits in-place. */ + char **f, **t; for (f = t = l; *f; f++) { bool found = false; const char *p; @@ -349,12 +347,11 @@ char **strv_env_unset_many(char **l, ...) { va_start(ap, l); - while ((p = va_arg(ap, const char*))) { + while ((p = va_arg(ap, const char*))) if (env_match(*f, p)) { found = true; break; } - } va_end(ap); @@ -458,6 +455,35 @@ int strv_env_assign(char ***l, const char *key, const char *value) { return strv_env_replace_consume(l, p); } +int strv_env_assignf(char ***l, const char *key, const char *valuef, ...) { + int r; + + assert(l); + assert(key); + + if (!env_name_is_valid(key)) + return -EINVAL; + + if (!valuef) { + strv_env_unset(*l, key); + return 0; + } + + _cleanup_free_ char *value = NULL; + va_list ap; + va_start(ap, valuef); + r = vasprintf(&value, valuef, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + char *p = strjoin(key, "=", value); + if (!p) + return -ENOMEM; + + return strv_env_replace_consume(l, p); +} + int _strv_env_assign_many(char ***l, ...) { va_list ap; int r; @@ -500,18 +526,17 @@ int _strv_env_assign_many(char ***l, ...) { return 0; } -char *strv_env_get_n(char **l, const char *name, size_t k, ReplaceEnvFlags flags) { +char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags) { assert(name); if (k == SIZE_MAX) - k = strlen_ptr(name); + k = strlen(name); if (k <= 0) return NULL; STRV_FOREACH_BACKWARDS(i, l) - if (strneq(*i, name, k) && - (*i)[k] == '=') - return *i + k + 1; + if (strneq(*i, name, k) && (*i)[k] == '=') + return (char*) *i + k + 1; if (flags & REPLACE_ENV_USE_ENVIRONMENT) { const char *t; @@ -654,7 +679,7 @@ int replace_env_full( pu = ret_unset_variables ? &unset_variables : NULL; pb = ret_bad_variables ? &bad_variables : NULL; - for (e = format, i = 0; *e && i < n; e ++, i ++) + for (e = format, i = 0; *e && i < n; e++, i++) switch (state) { case WORD: @@ -938,7 +963,7 @@ int getenv_bool(const char *p) { return parse_boolean(e); } -int getenv_bool_secure(const char *p) { +int secure_getenv_bool(const char *p) { const char *e; e = secure_getenv(p); @@ -948,7 +973,7 @@ int getenv_bool_secure(const char *p) { return parse_boolean(e); } -int getenv_uint64_secure(const char *p, uint64_t *ret) { +int secure_getenv_uint64(const char *p, uint64_t *ret) { const char *e; assert(p); @@ -1002,6 +1027,17 @@ int setenv_systemd_exec_pid(bool update_only) { return 1; } +int setenv_systemd_log_level(void) { + _cleanup_free_ char *val = NULL; + int r; + + r = log_level_to_string_alloc(log_get_max_level(), &val); + if (r < 0) + return r; + + return RET_NERRNO(setenv("SYSTEMD_LOG_LEVEL", val, /* overwrite= */ true)); +} + int getenv_path_list(const char *name, char ***ret_paths) { _cleanup_strv_free_ char **l = NULL; const char *e; @@ -1104,9 +1140,7 @@ int setenvf(const char *name, bool overwrite, const char *valuef, ...) { return RET_NERRNO(unsetenv(name)); va_start(ap, valuef); - DISABLE_WARNING_FORMAT_NONLITERAL; r = vasprintf(&value, valuef, ap); - REENABLE_WARNING; va_end(ap); if (r < 0) diff --git a/src/basic/env-util.h b/src/basic/env-util.h index 34cf1f9..6610ca8 100644 --- a/src/basic/env-util.h +++ b/src/basic/env-util.h @@ -43,26 +43,28 @@ char** _strv_env_merge(char **first, ...); #define strv_env_merge(first, ...) _strv_env_merge(first, __VA_ARGS__, POINTER_MAX) char **strv_env_delete(char **x, size_t n_lists, ...); /* New copy */ -char **strv_env_unset(char **l, const char *p); /* In place ... */ -char **strv_env_unset_many(char **l, ...) _sentinel_; +char** strv_env_unset(char **l, const char *p); /* In place ... */ +char** strv_env_unset_many_internal(char **l, ...) _sentinel_; +#define strv_env_unset_many(l, ...) strv_env_unset_many_internal(l, __VA_ARGS__, NULL) 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_assignf(char ***l, const char *key, const char *valuef, ...) _printf_(3, 4); 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, ReplaceEnvFlags flags) _pure_; -static inline char *strv_env_get(char **x, const char *n) { +char* strv_env_get_n(char * const *l, const char *name, size_t k, ReplaceEnvFlags flags); +static inline char* strv_env_get(char * const *x, const char *n) { return strv_env_get_n(x, n, SIZE_MAX, 0); } char *strv_env_pairs_get(char **l, const char *name) _pure_; int getenv_bool(const char *p); -int getenv_bool_secure(const char *p); +int secure_getenv_bool(const char *p); -int getenv_uint64_secure(const char *p, uint64_t *ret); +int secure_getenv_uint64(const char *p, uint64_t *ret); /* Like setenv, but calls unsetenv if value == NULL. */ int set_unset_env(const char *name, const char *value, bool overwrite); @@ -71,6 +73,7 @@ int set_unset_env(const char *name, const char *value, bool overwrite); int putenv_dup(const char *assignment, bool override); int setenv_systemd_exec_pid(bool update_only); +int setenv_systemd_log_level(void); /* Parses and does sanity checks on an environment variable containing * PATH-like colon-separated absolute paths */ diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h index 27804e6..48b76e4 100644 --- a/src/basic/errno-util.h +++ b/src/basic/errno-util.h @@ -167,7 +167,8 @@ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { -EAFNOSUPPORT, -EPFNOSUPPORT, -EPROTONOSUPPORT, - -ESOCKTNOSUPPORT); + -ESOCKTNOSUPPORT, + -ENOPROTOOPT); } _DEFINE_ABS_WRAPPER(NOT_SUPPORTED); diff --git a/src/basic/escape.c b/src/basic/escape.c index 75a1d68..2067be4 100644 --- a/src/basic/escape.c +++ b/src/basic/escape.c @@ -451,6 +451,12 @@ char* octescape(const char *s, size_t len) { assert(s || len == 0); + if (len == SIZE_MAX) + len = strlen(s); + + if (len > (SIZE_MAX - 1) / 4) + return NULL; + t = buf = new(char, len * 4 + 1); if (!buf) return NULL; @@ -471,6 +477,33 @@ char* octescape(const char *s, size_t len) { return buf; } +char* decescape(const char *s, const char *bad, size_t len) { + char *buf, *t; + + /* Escapes all chars in bad, in addition to \ and " chars, in \nnn decimal style escaping. */ + + assert(s || len == 0); + + t = buf = new(char, len * 4 + 1); + if (!buf) + return NULL; + + for (size_t i = 0; i < len; i++) { + uint8_t u = (uint8_t) s[i]; + + if (u < ' ' || u >= 127 || IN_SET(u, '\\', '"') || strchr(bad, u)) { + *(t++) = '\\'; + *(t++) = '0' + (u / 100); + *(t++) = '0' + ((u / 10) % 10); + *(t++) = '0' + (u % 10); + } else + *(t++) = u; + } + + *t = 0; + return buf; +} + static char* strcpy_backslash_escaped(char *t, const char *s, const char *bad) { assert(bad); assert(t); diff --git a/src/basic/escape.h b/src/basic/escape.h index 318da6f..65caf0d 100644 --- a/src/basic/escape.h +++ b/src/basic/escape.h @@ -65,6 +65,7 @@ static inline char* xescape(const char *s, const char *bad) { return xescape_full(s, bad, SIZE_MAX, 0); } char* octescape(const char *s, size_t len); +char* decescape(const char *s, const char *bad, size_t len); char* escape_non_printable_full(const char *str, size_t console_width, XEscapeFlags flags); char* shell_escape(const char *s, const char *bad); diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c index 0a6a54f..4bf91f6 100644 --- a/src/basic/ether-addr-util.c +++ b/src/basic/ether-addr-util.c @@ -59,8 +59,8 @@ void hw_addr_hash_func(const struct hw_addr_data *p, struct siphash *state) { assert(p); assert(state); - siphash24_compress(&p->length, sizeof(p->length), state); - siphash24_compress(p->bytes, p->length, state); + siphash24_compress_typesafe(p->length, state); + siphash24_compress_safe(p->bytes, p->length, state); } DEFINE_HASH_OPS(hw_addr_hash_ops, struct hw_addr_data, hw_addr_hash_func, hw_addr_compare); @@ -106,7 +106,7 @@ int ether_addr_compare(const struct ether_addr *a, const struct ether_addr *b) { } static void ether_addr_hash_func(const struct ether_addr *p, struct siphash *state) { - siphash24_compress(p, sizeof(struct ether_addr), state); + siphash24_compress_typesafe(*p, state); } DEFINE_HASH_OPS(ether_addr_hash_ops, struct ether_addr, ether_addr_hash_func, ether_addr_compare); @@ -270,3 +270,11 @@ int parse_ether_addr(const char *s, struct ether_addr *ret) { *ret = a.ether; return 0; } + +void ether_addr_mark_random(struct ether_addr *addr) { + assert(addr); + + /* see eth_random_addr in the kernel */ + addr->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + addr->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ +} diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h index 83ed77d..187e4ef 100644 --- a/src/basic/ether-addr-util.h +++ b/src/basic/ether-addr-util.h @@ -113,3 +113,5 @@ static inline bool ether_addr_is_global(const struct ether_addr *addr) { extern const struct hash_ops ether_addr_hash_ops; extern const struct hash_ops ether_addr_hash_ops_free; + +void ether_addr_mark_random(struct ether_addr *addr); diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c index 160f771..012cee6 100644 --- a/src/basic/extract-word.c +++ b/src/basic/extract-word.c @@ -244,52 +244,43 @@ int extract_first_word_and_warn( * Let's make sure that ExtractFlags fits into an unsigned int. */ assert_cc(sizeof(enum ExtractFlags) <= sizeof(unsigned)); -int extract_many_words(const char **p, const char *separators, unsigned flags, ...) { +int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) { va_list ap; - char **l; - int n = 0, i, c, r; + unsigned n = 0; + int r; - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ + /* Parses a number of words from a string, stripping any quotes if necessary. */ assert(p); /* Count how many words are expected */ va_start(ap, flags); - for (;;) { - if (!va_arg(ap, char **)) - break; + while (va_arg(ap, char**)) n++; - } va_end(ap); - if (n <= 0) + if (n == 0) return 0; /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { + char **l = newa0(char*, n); + unsigned c; + for (c = 0; c < n; c++) { r = extract_first_word(p, &l[c], separators, flags); if (r < 0) { free_many_charp(l, c); return r; } - if (r == 0) break; } - /* If we managed to parse all words, return them in the passed - * in parameters */ + /* If we managed to parse all words, return them in the passed in parameters */ va_start(ap, flags); - for (i = 0; i < n; i++) { - char **v; - - v = va_arg(ap, char **); - assert(v); - - *v = l[i]; + FOREACH_ARRAY(i, l, n) { + char **v = ASSERT_PTR(va_arg(ap, char**)); + *v = *i; } va_end(ap); diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h index c82ad76..da4f6ae 100644 --- a/src/basic/extract-word.h +++ b/src/basic/extract-word.h @@ -19,4 +19,7 @@ typedef enum ExtractFlags { int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); -int extract_many_words(const char **p, const char *separators, unsigned flags, ...) _sentinel_; + +int extract_many_words_internal(const char **p, const char *separators, unsigned flags, ...) _sentinel_; +#define extract_many_words(p, separators, flags, ...) \ + extract_many_words_internal(p, separators, flags, ##__VA_ARGS__, NULL) diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c index 542acca..da4ee63 100644 --- a/src/basic/fd-util.c +++ b/src/basic/fd-util.c @@ -167,7 +167,23 @@ int fd_nonblock(int fd, bool nonblock) { if (nflags == flags) return 0; - return RET_NERRNO(fcntl(fd, F_SETFL, nflags)); + if (fcntl(fd, F_SETFL, nflags) < 0) + return -errno; + + return 1; +} + +int stdio_disable_nonblock(void) { + int ret = 0; + + /* stdin/stdout/stderr really should have O_NONBLOCK, which would confuse apps if left on, as + * write()s might unexpectedly fail with EAGAIN. */ + + RET_GATHER(ret, fd_nonblock(STDIN_FILENO, false)); + RET_GATHER(ret, fd_nonblock(STDOUT_FILENO, false)); + RET_GATHER(ret, fd_nonblock(STDERR_FILENO, false)); + + return ret; } int fd_cloexec(int fd, bool cloexec) { @@ -451,6 +467,53 @@ int close_all_fds(const int except[], size_t n_except) { return r; } +int pack_fds(int fds[], size_t n_fds) { + if (n_fds <= 0) + return 0; + + /* Shifts around the fds in the provided array such that they + * all end up packed next to each-other, in order, starting + * from SD_LISTEN_FDS_START. This must be called after close_all_fds(); + * it is likely to freeze up otherwise. You should probably use safe_fork_full + * with FORK_CLOSE_ALL_FDS|FORK_PACK_FDS set, to ensure that this is done correctly. + * The fds array is modified in place with the new FD numbers. */ + + assert(fds); + + for (int start = 0;;) { + int restart_from = -1; + + for (int i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i + 3) + continue; + + nfd = fcntl(fds[i], F_DUPFD, i + 3); + if (nfd < 0) + return -errno; + + safe_close(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here */ + if (nfd != i + 3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + assert(fds[0] == 3); + + return 0; +} + int same_fd(int a, int b) { struct stat sta, stb; pid_t pid; @@ -809,6 +872,46 @@ int fd_reopen(int fd, int flags) { return new_fd; } +int fd_reopen_propagate_append_and_position(int fd, int flags) { + /* Invokes fd_reopen(fd, flags), but propagates O_APPEND if set on original fd, and also tries to + * keep current file position. + * + * You should use this if the original fd potentially is O_APPEND, otherwise we get rather + * "unexpected" behavior. Unless you intentionally want to overwrite pre-existing data, and have + * your output overwritten by the next user. + * + * Use case: "systemd-run --pty >> some-log". + * + * The "keep position" part is obviously nonsense for the O_APPEND case, but should reduce surprises + * if someone carefully pre-positioned the passed in original input or non-append output FDs. */ + + assert(fd >= 0); + assert(!(flags & (O_APPEND|O_DIRECTORY))); + + int existing_flags = fcntl(fd, F_GETFL); + if (existing_flags < 0) + return -errno; + + int new_fd = fd_reopen(fd, flags | (existing_flags & O_APPEND)); + if (new_fd < 0) + return new_fd; + + /* Try to adjust the offset, but ignore errors. */ + off_t p = lseek(fd, 0, SEEK_CUR); + if (p > 0) { + off_t new_p = lseek(new_fd, p, SEEK_SET); + if (new_p < 0) + log_debug_errno(errno, + "Failed to propagate file position for re-opened fd %d, ignoring: %m", + fd); + else if (new_p != p) + log_debug("Failed to propagate file position for re-opened fd %d (%lld != %lld), ignoring.", + fd, (long long) new_p, (long long) p); + } + + return new_fd; +} + int fd_reopen_condition( int fd, int flags, @@ -853,6 +956,38 @@ int fd_is_opath(int fd) { return FLAGS_SET(r, O_PATH); } +int fd_verify_safe_flags_full(int fd, int extra_flags) { + int flags, unexpected_flags; + + /* Check if an extrinsic fd is safe to work on (by a privileged service). This ensures that clients + * can't trick a privileged service into giving access to a file the client doesn't already have + * access to (especially via something like O_PATH). + * + * O_NOFOLLOW: For some reason the kernel will return this flag from fcntl(); it doesn't go away + * immediately after open(). It should have no effect whatsoever to an already-opened FD, + * and since we refuse O_PATH it should be safe. + * + * RAW_O_LARGEFILE: glibc secretly sets this and neglects to hide it from us if we call fcntl. + * See comment in missing_fcntl.h for more details about this. + * + * If 'extra_flags' is specified as non-zero the included flags are also allowed. + */ + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL); + if (flags < 0) + return -errno; + + unexpected_flags = flags & ~(O_ACCMODE|O_NOFOLLOW|RAW_O_LARGEFILE|extra_flags); + if (unexpected_flags != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EREMOTEIO), + "Unexpected flags set for extrinsic fd: 0%o", + (unsigned) unexpected_flags); + + return flags & (O_ACCMODE | extra_flags); /* return the flags variable, but remove the noise */ +} + int read_nr_open(void) { _cleanup_free_ char *nr_open = NULL; int r; @@ -899,10 +1034,7 @@ int fd_get_diskseq(int fd, uint64_t *ret) { } int path_is_root_at(int dir_fd, const char *path) { - STRUCT_NEW_STATX_DEFINE(st); - STRUCT_NEW_STATX_DEFINE(pst); - _cleanup_close_ int fd = -EBADF; - int r; + _cleanup_close_ int fd = -EBADF, pfd = -EBADF; assert(dir_fd >= 0 || dir_fd == AT_FDCWD); @@ -914,60 +1046,74 @@ int path_is_root_at(int dir_fd, const char *path) { dir_fd = fd; } - r = statx_fallback(dir_fd, ".", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st.sx); - if (r == -ENOTDIR) - return false; + pfd = openat(dir_fd, "..", O_PATH|O_DIRECTORY|O_CLOEXEC); + if (pfd < 0) + return errno == ENOTDIR ? false : -errno; + + /* Even if the parent directory has the same inode, the fd may not point to the root directory "/", + * and we also need to check that the mount ids are the same. Otherwise, a construct like the + * following could be used to trick us: + * + * $ mkdir /tmp/x /tmp/x/y + * $ mount --bind /tmp/x /tmp/x/y + */ + + return fds_are_same_mount(dir_fd, pfd); +} + +int fds_are_same_mount(int fd1, int fd2) { + STRUCT_NEW_STATX_DEFINE(st1); + STRUCT_NEW_STATX_DEFINE(st2); + int r; + + assert(fd1 >= 0); + assert(fd2 >= 0); + + r = statx_fallback(fd1, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st1.sx); if (r < 0) return r; - r = statx_fallback(dir_fd, "..", 0, STATX_TYPE|STATX_INO|STATX_MNT_ID, &pst.sx); + r = statx_fallback(fd2, "", AT_EMPTY_PATH, STATX_TYPE|STATX_INO|STATX_MNT_ID, &st2.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)) + if (!statx_inode_same(&st1.sx, &st2.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 + /* Note, statx() does not provide the mount ID and path_get_mnt_id_at() does not work when an old * kernel is used. 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)) { + if (!FLAGS_SET(st1.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at_fallback(dir_fd, "", &mntid); + r = path_get_mnt_id_at_fallback(fd1, "", &mntid); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - st.nsx.stx_mnt_id = mntid; - st.nsx.stx_mask |= STATX_MNT_ID; + st1.nsx.stx_mnt_id = mntid; + st1.nsx.stx_mask |= STATX_MNT_ID; } - if (!FLAGS_SET(pst.nsx.stx_mask, STATX_MNT_ID)) { + if (!FLAGS_SET(st2.nsx.stx_mask, STATX_MNT_ID)) { int mntid; - r = path_get_mnt_id_at_fallback(dir_fd, "..", &mntid); + r = path_get_mnt_id_at_fallback(fd2, "", &mntid); if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return true; /* skip the mount ID check */ if (r < 0) return r; assert(mntid >= 0); - pst.nsx.stx_mnt_id = mntid; - pst.nsx.stx_mask |= STATX_MNT_ID; + st2.nsx.stx_mnt_id = mntid; + st2.nsx.stx_mask |= STATX_MNT_ID; } - return statx_mount_same(&st.nsx, &pst.nsx); + return statx_mount_same(&st1.nsx, &st2.nsx); } const char *accmode_to_string(int flags) { diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h index d3e9192..af17481 100644 --- a/src/basic/fd-util.h +++ b/src/basic/fd-util.h @@ -8,6 +8,7 @@ #include #include "macro.h" +#include "missing_fcntl.h" #include "stdio-util.h" /* maximum length of fdname */ @@ -52,6 +53,11 @@ static inline void fclosep(FILE **f) { safe_fclose(*f); } +static inline void* close_fd_ptr(void *p) { + safe_close(PTR_TO_FD(p)); + return NULL; +} + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, pclose, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); @@ -62,6 +68,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(DIR*, closedir, NULL); #define _cleanup_close_pair_ _cleanup_(close_pairp) int fd_nonblock(int fd, bool nonblock); +int stdio_disable_nonblock(void); + int fd_cloexec(int fd, bool cloexec); int fd_cloexec_many(const int fds[], size_t n_fds, bool cloexec); @@ -70,6 +78,8 @@ int get_max_fd(void); int close_all_fds(const int except[], size_t n_except); int close_all_fds_without_malloc(const int except[], size_t n_except); +int pack_fds(int fds[], size_t n); + int same_fd(int a, int b); void cmsg_close_all(struct msghdr *mh); @@ -101,8 +111,16 @@ static inline int make_null_stdio(void) { }) int fd_reopen(int fd, int flags); +int fd_reopen_propagate_append_and_position(int fd, int flags); int fd_reopen_condition(int fd, int flags, int mask, int *ret_new_fd); + int fd_is_opath(int fd); + +int fd_verify_safe_flags_full(int fd, int extra_flags); +static inline int fd_verify_safe_flags(int fd) { + return fd_verify_safe_flags_full(fd, 0); +} + int read_nr_open(void); int fd_get_diskseq(int fd, uint64_t *ret); @@ -117,6 +135,8 @@ static inline int dir_fd_is_root_or_cwd(int dir_fd) { return dir_fd == AT_FDCWD ? true : path_is_root_at(dir_fd, NULL); } +int fds_are_same_mount(int fd1, int fd2); + /* The maximum length a buffer for a /proc/self/fd/ path needs */ #define PROC_FD_PATH_MAX \ (STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)) diff --git a/src/basic/fileio.c b/src/basic/fileio.c index a050b61..5233781 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -28,10 +28,11 @@ #include "stdio-util.h" #include "string-util.h" #include "sync-util.h" +#include "terminal-util.h" #include "tmpfile-util.h" /* The maximum size of the file we'll read in one go in read_full_file() (64M). */ -#define READ_FULL_BYTES_MAX (64U*1024U*1024U - 1U) +#define READ_FULL_BYTES_MAX (64U * U64_MB - UINT64_C(1)) /* Used when a size is specified for read_full_file() with READ_FULL_FILE_UNBASE64 or _UNHEX */ #define READ_FULL_FILE_ENCODED_STRING_AMPLIFICATION_BOUNDARY 3 @@ -44,7 +45,7 @@ * exponentially in a loop. We use a size limit of 4M-2 because 4M-1 is the maximum buffer that /proc/sys/ * allows us to read() (larger reads will fail with ENOMEM), and we want to read one extra byte so that we * can detect EOFs. */ -#define READ_VIRTUAL_BYTES_MAX (4U*1024U*1024U - 2U) +#define READ_VIRTUAL_BYTES_MAX (4U * U64_MB - UINT64_C(2)) int fdopen_unlocked(int fd, const char *options, FILE **ret) { assert(ret); @@ -199,6 +200,19 @@ int write_string_stream_ts( return 0; } +static mode_t write_string_file_flags_to_mode(WriteStringFileFlags flags) { + + /* We support three different modes, that are the ones that really make sense for text files like this: + * + * → 0600 (i.e. root-only) + * → 0444 (i.e. read-only) + * → 0644 (i.e. writable for root, readable for everyone else) + */ + + return FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : + FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0444) ? 0444 : 0644; +} + static int write_string_file_atomic_at( int dir_fd, const char *fn, @@ -224,7 +238,7 @@ static int write_string_file_atomic_at( if (r < 0) goto fail; - r = fchmod_umask(fileno(f), FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0644); + r = fchmod_umask(fileno(f), write_string_file_flags_to_mode(flags)); if (r < 0) goto fail; @@ -287,7 +301,7 @@ int write_string_file_ts_at( (FLAGS_SET(flags, WRITE_STRING_FILE_CREATE) ? O_CREAT : 0) | (FLAGS_SET(flags, WRITE_STRING_FILE_TRUNCATE) ? O_TRUNC : 0) | (FLAGS_SET(flags, WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL) ? O_RDWR : O_WRONLY), - (FLAGS_SET(flags, WRITE_STRING_FILE_MODE_0600) ? 0600 : 0666)); + write_string_file_flags_to_mode(flags)); if (fd < 0) { r = -errno; goto fail; @@ -1313,33 +1327,31 @@ int read_timestamp_file(const char *fn, usec_t *ret) { return 0; } -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { - int r; - +int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space) { assert(s); + assert(space); - /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter - * when specified shall initially point to a boolean variable initialized to false. It is set to true after the - * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each - * element, but not before the first one. */ + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. + * The *space parameter when specified shall initially point to a boolean variable initialized + * to false. It is set to true after the first invocation. This call is supposed to be use in loops, + * where a separator shall be inserted between each element, but not before the first one. */ if (!f) f = stdout; - if (space) { - if (!separator) - separator = " "; + if (!separator) + separator = " "; - if (*space) { - r = fputs(separator, f); - if (r < 0) - return r; - } + if (*space) + if (fputs(separator, f) < 0) + return -EIO; - *space = true; - } + *space = true; + + if (fputs(s, f) < 0) + return -EIO; - return fputs(s, f); + return 0; } /* A bitmask of the EOL markers we know */ @@ -1459,7 +1471,7 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { * and don't call isatty() on an invalid fd */ flags |= READ_LINE_NOT_A_TTY; else - flags |= isatty(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY; + flags |= isatty_safe(fd) ? READ_LINE_IS_A_TTY : READ_LINE_NOT_A_TTY; } if (FLAGS_SET(flags, READ_LINE_IS_A_TTY)) break; @@ -1492,7 +1504,7 @@ int read_line_full(FILE *f, size_t limit, ReadLineFlags flags, char **ret) { int read_stripped_line(FILE *f, size_t limit, char **ret) { _cleanup_free_ char *s = NULL; - int r; + int r, k; assert(f); @@ -1501,23 +1513,17 @@ int read_stripped_line(FILE *f, size_t limit, char **ret) { return r; if (ret) { - const char *p; - - p = strstrip(s); + const char *p = strstrip(s); if (p == s) *ret = TAKE_PTR(s); else { - char *copy; - - copy = strdup(p); - if (!copy) - return -ENOMEM; - - *ret = copy; + k = strdup_to(ret, p); + if (k < 0) + return k; } } - return r; + return r > 0; /* Return 1 if something was read. */ } int safe_fgetc(FILE *f, char *ret) { diff --git a/src/basic/fileio.h b/src/basic/fileio.h index e0e0a45..03c3f3f 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -26,7 +26,8 @@ typedef enum { WRITE_STRING_FILE_NOFOLLOW = 1 << 8, WRITE_STRING_FILE_MKDIR_0755 = 1 << 9, WRITE_STRING_FILE_MODE_0600 = 1 << 10, - WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 11, + WRITE_STRING_FILE_MODE_0444 = 1 << 11, + WRITE_STRING_FILE_SUPPRESS_REDUNDANT_VIRTUAL = 1 << 12, /* And before you wonder, why write_string_file_atomic_label_ts() is a separate function instead of just one more flag here: it's about linking: we don't want to pull -lselinux into all users of write_string_file() @@ -142,7 +143,7 @@ int fflush_sync_and_check(FILE *f); int write_timestamp_file_atomic(const char *fn, usec_t n); int read_timestamp_file(const char *fn, usec_t *ret); -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); +int fputs_with_separator(FILE *f, const char *s, const char *separator, bool *space); typedef enum ReadLineFlags { READ_LINE_ONLY_NUL = 1 << 0, diff --git a/src/basic/filesystems-gperf.gperf b/src/basic/filesystems-gperf.gperf index 1cd66b5..c82fe98 100644 --- a/src/basic/filesystems-gperf.gperf +++ b/src/basic/filesystems-gperf.gperf @@ -28,6 +28,7 @@ afs, {AFS_FS_MAGIC, AFS_SUPER_MAGIC} anon_inodefs, {ANON_INODE_FS_MAGIC} autofs, {AUTOFS_SUPER_MAGIC} balloon-kvm, {BALLOON_KVM_MAGIC} +bcachefs, {BCACHEFS_SUPER_MAGIC} bdev, {BDEVFS_MAGIC} binder, {BINDERFS_SUPER_MAGIC} binfmt_misc, {BINFMTFS_MAGIC} diff --git a/src/basic/format-util.c b/src/basic/format-util.c index 9450185..445fecc 100644 --- a/src/basic/format-util.c +++ b/src/basic/format-util.c @@ -25,7 +25,7 @@ int format_ifname_full(int ifindex, FormatIfnameFlag flag, char buf[static IF_NA } int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret) { - char buf[IF_NAMESIZE], *copy; + char buf[IF_NAMESIZE]; int r; assert(ret); @@ -34,12 +34,7 @@ int format_ifname_full_alloc(int ifindex, FormatIfnameFlag flag, char **ret) { if (r < 0) return r; - copy = strdup(buf); - if (!copy) - return -ENOMEM; - - *ret = copy; - return 0; + return strdup_to(ret, buf); } char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { @@ -75,15 +70,17 @@ char *format_bytes_full(char *buf, size_t l, uint64_t t, FormatBytesFlag flag) { for (size_t i = 0; i < n; i++) if (t >= table[i].factor) { - if (flag & FORMAT_BYTES_BELOW_POINT) { + uint64_t remainder = i != n - 1 ? + (t / table[i + 1].factor * 10 / table[n - 1].factor) % 10 : + (t * 10 / table[i].factor) % 10; + + if (FLAGS_SET(flag, FORMAT_BYTES_BELOW_POINT) && remainder > 0) (void) snprintf(buf, l, "%" PRIu64 ".%" PRIu64 "%s", t / table[i].factor, - i != n - 1 ? - (t / table[i + 1].factor * UINT64_C(10) / table[n - 1].factor) % UINT64_C(10): - (t * UINT64_C(10) / table[i].factor) % UINT64_C(10), + remainder, table[i].suffix); - } else + else (void) snprintf(buf, l, "%" PRIu64 "%s", t / table[i].factor, diff --git a/src/basic/format-util.h b/src/basic/format-util.h index 8719df3..ba7cff6 100644 --- a/src/basic/format-util.h +++ b/src/basic/format-util.h @@ -18,6 +18,14 @@ assert_cc(sizeof(uid_t) == sizeof(uint32_t)); assert_cc(sizeof(gid_t) == sizeof(uint32_t)); #define GID_FMT "%" PRIu32 +/* Note: the lifetime of the compound literal is the immediately surrounding block, + * see C11 §6.5.2.5, and + * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ +#define FORMAT_UID(uid) \ + snprintf_ok((char[DECIMAL_STR_MAX(uid_t)]){}, DECIMAL_STR_MAX(uid_t), UID_FMT, uid) +#define FORMAT_GID(gid) \ + snprintf_ok((char[DECIMAL_STR_MAX(gid_t)]){}, DECIMAL_STR_MAX(gid_t), GID_FMT, gid) + #if SIZEOF_TIME_T == 8 # define PRI_TIME PRIi64 #elif SIZEOF_TIME_T == 4 diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 5bc7d2f..64d3093 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -325,12 +325,22 @@ int fchmod_opath(int fd, mode_t m) { int futimens_opath(int fd, const struct timespec ts[2]) { /* Similar to fchmod_opath() but for futimens() */ - if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, 0) < 0) { + assert(fd >= 0); + + if (utimensat(fd, "", ts, AT_EMPTY_PATH) >= 0) + return 0; + if (errno != EINVAL) + return -errno; + + /* Support for AT_EMPTY_PATH is added rather late (kernel 5.8), so fall back to going through /proc/ + * if unavailable. */ + + if (utimensat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), ts, /* flags = */ 0) < 0) { if (errno != ENOENT) return -errno; if (proc_mounted() == 0) - return -ENOSYS; /* if we have no /proc/, the concept is not implementable */ + return -ENOSYS; return -ENOENT; } @@ -405,17 +415,14 @@ int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gi ret = fchmod_and_chown(fd, mode, uid, gid); if (stamp != USEC_INFINITY) { - struct timespec ts[2]; + struct timespec ts; + timespec_store(&ts, stamp); - timespec_store(&ts[0], stamp); - ts[1] = ts[0]; - r = futimens_opath(fd, ts); + r = futimens_opath(fd, (const struct timespec[2]) { ts, ts }); } else - r = futimens_opath(fd, NULL); - if (r < 0 && ret >= 0) - return r; + r = futimens_opath(fd, /* ts = */ NULL); - return ret; + return RET_GATHER(ret, r); } int symlink_idempotent(const char *from, const char *to, bool make_relative) { @@ -1018,7 +1025,7 @@ int parse_cifs_service( return 0; } -int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { +int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode) { _cleanup_close_ int fd = -EBADF, parent_fd = -EBADF; _cleanup_free_ char *fname = NULL, *parent = NULL; int r; @@ -1054,7 +1061,7 @@ int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { path = fname; } - fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, /* xopen_flags = */ 0, mode); + fd = xopenat_full(dirfd, path, flags|O_CREAT|O_DIRECTORY|O_NOFOLLOW, xopen_flags, mode); if (IN_SET(fd, -ELOOP, -ENOTDIR)) return -EEXIST; if (fd < 0) @@ -1236,3 +1243,99 @@ int xopenat_lock_full( return TAKE_FD(fd); } + +int link_fd(int fd, int newdirfd, const char *newpath) { + int r; + + assert(fd >= 0); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(newpath); + + /* Try linking via /proc/self/fd/ first. */ + r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); + if (r != -ENOENT) + return r; + + /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a + * more recent kernel, but does not require /proc/ mounted) */ + if (proc_mounted() != 0) + return r; + + return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); +} + +int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + _cleanup_close_ int old_fd = -EBADF; + int r; + + assert(olddirfd >= 0 || olddirfd == AT_FDCWD); + assert(newdirfd >= 0 || newdirfd == AT_FDCWD); + assert(!isempty(newpath)); /* source path is optional, but the target path is not */ + + /* Like linkat() but replaces the target if needed. Is a NOP if source and target already share the + * same inode. */ + + if (olddirfd == AT_FDCWD && isempty(oldpath)) /* Refuse operating on the cwd (which is a dir, and dirs can't be hardlinked) */ + return -EISDIR; + + if (path_implies_directory(oldpath)) /* Refuse these definite directories early */ + return -EISDIR; + + if (path_implies_directory(newpath)) + return -EISDIR; + + /* First, try to link this directly */ + if (oldpath) + r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, newpath, 0)); + else + r = link_fd(olddirfd, newdirfd, newpath); + if (r >= 0) + return 0; + if (r != -EEXIST) + return r; + + old_fd = xopenat(olddirfd, oldpath, O_PATH|O_CLOEXEC); + if (old_fd < 0) + return old_fd; + + struct stat old_st; + if (fstat(old_fd, &old_st) < 0) + return -errno; + + if (S_ISDIR(old_st.st_mode)) /* Don't bother if we are operating on a directory */ + return -EISDIR; + + struct stat new_st; + if (fstatat(newdirfd, newpath, &new_st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + if (S_ISDIR(new_st.st_mode)) /* Refuse replacing directories */ + return -EEXIST; + + if (stat_inode_same(&old_st, &new_st)) /* Already the same inode? Then shortcut this */ + return 0; + + _cleanup_free_ char *tmp_path = NULL; + r = tempfn_random(newpath, /* extra= */ NULL, &tmp_path); + if (r < 0) + return r; + + r = link_fd(old_fd, newdirfd, tmp_path); + if (r < 0) { + if (!ERRNO_IS_PRIVILEGE(r)) + return r; + + /* If that didn't work due to permissions then go via the path of the dentry */ + r = RET_NERRNO(linkat(olddirfd, oldpath, newdirfd, tmp_path, 0)); + if (r < 0) + return r; + } + + r = RET_NERRNO(renameat(newdirfd, tmp_path, newdirfd, newpath)); + if (r < 0) { + (void) unlinkat(newdirfd, tmp_path, /* flags= */ 0); + return r; + } + + return 0; +} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 6a1e2e7..3e2db95 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -128,15 +128,18 @@ int posix_fallocate_loop(int fd, uint64_t offset, uint64_t size); int parse_cifs_service(const char *s, char **ret_host, char **ret_service, char **ret_path); -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); - typedef enum XOpenFlags { XO_LABEL = 1 << 0, XO_SUBVOLUME = 1 << 1, } XOpenFlags; +int open_mkdir_at_full(int dirfd, const char *path, int flags, XOpenFlags xopen_flags, mode_t mode); +static inline int open_mkdir_at(int dirfd, const char *path, int flags, mode_t mode) { + return open_mkdir_at_full(dirfd, path, flags, 0, mode); +} + +int openat_report_new(int dirfd, const char *pathname, int flags, mode_t mode, bool *ret_newly_created); + int xopenat_full(int dir_fd, const char *path, int open_flags, XOpenFlags xopen_flags, mode_t mode); static inline int xopenat(int dir_fd, const char *path, int open_flags) { return xopenat_full(dir_fd, path, open_flags, 0, 0); @@ -146,3 +149,7 @@ int xopenat_lock_full(int dir_fd, const char *path, int open_flags, XOpenFlags x static inline int xopenat_lock(int dir_fd, const char *path, int open_flags, LockType locktype, int operation) { return xopenat_lock_full(dir_fd, path, open_flags, 0, 0, locktype, operation); } + +int link_fd(int fd, int newdirfd, const char *newpath); + +int linkat_replace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); diff --git a/src/basic/gcrypt-util.c b/src/basic/gcrypt-util.c index 41c9362..4d68d2c 100644 --- a/src/basic/gcrypt-util.c +++ b/src/basic/gcrypt-util.c @@ -5,41 +5,130 @@ #include "gcrypt-util.h" #include "hexdecoct.h" -void initialize_libgcrypt(bool secmem) { - if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) - return; +static void *gcrypt_dl = NULL; - gcry_control(GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM); - assert_se(gcry_check_version("1.4.5")); +static DLSYM_FUNCTION(gcry_control); +static DLSYM_FUNCTION(gcry_check_version); +DLSYM_FUNCTION(gcry_md_close); +DLSYM_FUNCTION(gcry_md_copy); +DLSYM_FUNCTION(gcry_md_ctl); +DLSYM_FUNCTION(gcry_md_get_algo_dlen); +DLSYM_FUNCTION(gcry_md_open); +DLSYM_FUNCTION(gcry_md_read); +DLSYM_FUNCTION(gcry_md_reset); +DLSYM_FUNCTION(gcry_md_setkey); +DLSYM_FUNCTION(gcry_md_write); +DLSYM_FUNCTION(gcry_mpi_add); +DLSYM_FUNCTION(gcry_mpi_add_ui); +DLSYM_FUNCTION(gcry_mpi_cmp); +DLSYM_FUNCTION(gcry_mpi_cmp_ui); +DLSYM_FUNCTION(gcry_mpi_get_nbits); +DLSYM_FUNCTION(gcry_mpi_invm); +DLSYM_FUNCTION(gcry_mpi_mod); +DLSYM_FUNCTION(gcry_mpi_mul); +DLSYM_FUNCTION(gcry_mpi_mulm); +DLSYM_FUNCTION(gcry_mpi_new); +DLSYM_FUNCTION(gcry_mpi_powm); +DLSYM_FUNCTION(gcry_mpi_print); +DLSYM_FUNCTION(gcry_mpi_release); +DLSYM_FUNCTION(gcry_mpi_scan); +DLSYM_FUNCTION(gcry_mpi_set_ui); +DLSYM_FUNCTION(gcry_mpi_sub); +DLSYM_FUNCTION(gcry_mpi_subm); +DLSYM_FUNCTION(gcry_mpi_sub_ui); +DLSYM_FUNCTION(gcry_prime_check); +DLSYM_FUNCTION(gcry_randomize); +DLSYM_FUNCTION(gcry_strerror); + +static int dlopen_gcrypt(void) { + ELF_NOTE_DLOPEN("gcrypt", + "Support for journald forward-sealing", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libgcrypt.so.20"); + + return dlopen_many_sym_or_warn( + &gcrypt_dl, + "libgcrypt.so.20", LOG_DEBUG, + DLSYM_ARG(gcry_control), + DLSYM_ARG(gcry_check_version), + DLSYM_ARG(gcry_md_close), + DLSYM_ARG(gcry_md_copy), + DLSYM_ARG(gcry_md_ctl), + DLSYM_ARG(gcry_md_get_algo_dlen), + DLSYM_ARG(gcry_md_open), + DLSYM_ARG(gcry_md_read), + DLSYM_ARG(gcry_md_reset), + DLSYM_ARG(gcry_md_setkey), + DLSYM_ARG(gcry_md_write), + DLSYM_ARG(gcry_mpi_add), + DLSYM_ARG(gcry_mpi_add_ui), + DLSYM_ARG(gcry_mpi_cmp), + DLSYM_ARG(gcry_mpi_cmp_ui), + DLSYM_ARG(gcry_mpi_get_nbits), + DLSYM_ARG(gcry_mpi_invm), + DLSYM_ARG(gcry_mpi_mod), + DLSYM_ARG(gcry_mpi_mul), + DLSYM_ARG(gcry_mpi_mulm), + DLSYM_ARG(gcry_mpi_new), + DLSYM_ARG(gcry_mpi_powm), + DLSYM_ARG(gcry_mpi_print), + DLSYM_ARG(gcry_mpi_release), + DLSYM_ARG(gcry_mpi_scan), + DLSYM_ARG(gcry_mpi_set_ui), + DLSYM_ARG(gcry_mpi_sub), + DLSYM_ARG(gcry_mpi_subm), + DLSYM_ARG(gcry_mpi_sub_ui), + DLSYM_ARG(gcry_prime_check), + DLSYM_ARG(gcry_randomize), + DLSYM_ARG(gcry_strerror)); +} + +int initialize_libgcrypt(bool secmem) { + int r; + + r = dlopen_gcrypt(); + if (r < 0) + return r; + + if (sym_gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return 0; + + sym_gcry_control(GCRYCTL_SET_PREFERRED_RNG_TYPE, GCRY_RNG_TYPE_SYSTEM); + assert_se(sym_gcry_check_version("1.4.5")); /* Turn off "secmem". Clients which wish to make use of this * feature should initialize the library manually */ if (!secmem) - gcry_control(GCRYCTL_DISABLE_SECMEM); + sym_gcry_control(GCRYCTL_DISABLE_SECMEM); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + sym_gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + + return 0; } # if !PREFER_OPENSSL int string_hashsum(const char *s, size_t len, int md_algorithm, char **out) { - _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; + _cleanup_(sym_gcry_md_closep) gcry_md_hd_t md = NULL; gcry_error_t err; size_t hash_size; void *hash; char *enc; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; - hash_size = gcry_md_get_algo_dlen(md_algorithm); + hash_size = sym_gcry_md_get_algo_dlen(md_algorithm); assert(hash_size > 0); - err = gcry_md_open(&md, md_algorithm, 0); + err = sym_gcry_md_open(&md, md_algorithm, 0); if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) return -EIO; - gcry_md_write(md, s, len); + sym_gcry_md_write(md, s, len); - hash = gcry_md_read(md, 0); + hash = sym_gcry_md_read(md, 0); if (!hash) return -EIO; diff --git a/src/basic/gcrypt-util.h b/src/basic/gcrypt-util.h index 4c40cef..acb50e8 100644 --- a/src/basic/gcrypt-util.h +++ b/src/basic/gcrypt-util.h @@ -9,11 +9,59 @@ #if HAVE_GCRYPT #include +#include "dlfcn-util.h" #include "macro.h" -void initialize_libgcrypt(bool secmem); +DLSYM_PROTOTYPE(gcry_md_close); +DLSYM_PROTOTYPE(gcry_md_copy); +DLSYM_PROTOTYPE(gcry_md_ctl); +DLSYM_PROTOTYPE(gcry_md_get_algo_dlen); +DLSYM_PROTOTYPE(gcry_md_open); +DLSYM_PROTOTYPE(gcry_md_read); +DLSYM_PROTOTYPE(gcry_md_reset); +DLSYM_PROTOTYPE(gcry_md_setkey); +DLSYM_PROTOTYPE(gcry_md_write); +DLSYM_PROTOTYPE(gcry_mpi_add); +DLSYM_PROTOTYPE(gcry_mpi_add_ui); +DLSYM_PROTOTYPE(gcry_mpi_cmp); +DLSYM_PROTOTYPE(gcry_mpi_cmp_ui); +DLSYM_PROTOTYPE(gcry_mpi_get_nbits); +DLSYM_PROTOTYPE(gcry_mpi_invm); +DLSYM_PROTOTYPE(gcry_mpi_mod); +DLSYM_PROTOTYPE(gcry_mpi_mul); +DLSYM_PROTOTYPE(gcry_mpi_mulm); +DLSYM_PROTOTYPE(gcry_mpi_new); +DLSYM_PROTOTYPE(gcry_mpi_powm); +DLSYM_PROTOTYPE(gcry_mpi_print); +DLSYM_PROTOTYPE(gcry_mpi_release); +DLSYM_PROTOTYPE(gcry_mpi_scan); +DLSYM_PROTOTYPE(gcry_mpi_set_ui); +DLSYM_PROTOTYPE(gcry_mpi_sub); +DLSYM_PROTOTYPE(gcry_mpi_subm); +DLSYM_PROTOTYPE(gcry_mpi_sub_ui); +DLSYM_PROTOTYPE(gcry_prime_check); +DLSYM_PROTOTYPE(gcry_randomize); +DLSYM_PROTOTYPE(gcry_strerror); +int initialize_libgcrypt(bool secmem); + +static inline gcry_md_hd_t* sym_gcry_md_closep(gcry_md_hd_t *md) { + if (!md || !*md) + return NULL; + sym_gcry_md_close(*md); + + return NULL; +} DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(gcry_md_hd_t, gcry_md_close, NULL); + +/* Copied from gcry_md_putc from gcrypt.h due to the need to call the sym_ variant */ +#define sym_gcry_md_putc(h,c) \ + do { \ + gcry_md_hd_t h__ = (h); \ + if ((h__)->bufpos == (h__)->bufsize) \ + sym_gcry_md_write((h__), NULL, 0); \ + (h__)->buf[(h__)->bufpos++] = (c) & 0xff; \ + } while(false) #endif #if !PREFER_OPENSSL diff --git a/src/basic/getopt-defs.h b/src/basic/getopt-defs.h index 3efeb6d..9abef6f 100644 --- a/src/basic/getopt-defs.h +++ b/src/basic/getopt-defs.h @@ -26,6 +26,7 @@ ARG_CRASH_CHVT, \ ARG_CRASH_SHELL, \ ARG_CRASH_REBOOT, \ + ARG_CRASH_ACTION, \ ARG_CONFIRM_SPAWN, \ ARG_SHOW_STATUS, \ ARG_DESERIALIZE, \ @@ -61,6 +62,7 @@ { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, \ { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, \ { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, \ + { "crash-action", required_argument, NULL, ARG_CRASH_ACTION }, \ { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, \ { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, \ { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, \ diff --git a/src/basic/glyph-util.c b/src/basic/glyph-util.c index 803bdd9..d37be32 100644 --- a/src/basic/glyph-util.c +++ b/src/basic/glyph-util.c @@ -41,6 +41,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_TREE_SPACE] = " ", [SPECIAL_GLYPH_TREE_TOP] = ",-", [SPECIAL_GLYPH_VERTICAL_DOTTED] = ":", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = "-", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = "=", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = ">", [SPECIAL_GLYPH_BLACK_CIRCLE] = "*", [SPECIAL_GLYPH_WHITE_CIRCLE] = "*", @@ -74,6 +76,10 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_SPARKLES] = "*", [SPECIAL_GLYPH_LOW_BATTERY] = "!", [SPECIAL_GLYPH_WARNING_SIGN] = "!", + [SPECIAL_GLYPH_RED_CIRCLE] = "o", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = "o", + [SPECIAL_GLYPH_BLUE_CIRCLE] = "o", + [SPECIAL_GLYPH_GREEN_CIRCLE] = "o", }, /* UTF-8 */ @@ -87,6 +93,8 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { /* Single glyphs in both cases */ [SPECIAL_GLYPH_VERTICAL_DOTTED] = u8"┆", + [SPECIAL_GLYPH_HORIZONTAL_DOTTED] = u8"┄", + [SPECIAL_GLYPH_HORIZONTAL_FAT] = u8"━", [SPECIAL_GLYPH_TRIANGULAR_BULLET] = u8"‣", [SPECIAL_GLYPH_BLACK_CIRCLE] = u8"●", [SPECIAL_GLYPH_WHITE_CIRCLE] = u8"○", @@ -136,6 +144,11 @@ const char *special_glyph_full(SpecialGlyph code, bool force_utf) { [SPECIAL_GLYPH_WARNING_SIGN] = u8"⚠️", [SPECIAL_GLYPH_COMPUTER_DISK] = u8"💽", [SPECIAL_GLYPH_WORLD] = u8"🌍", + + [SPECIAL_GLYPH_RED_CIRCLE] = u8"🔴", + [SPECIAL_GLYPH_YELLOW_CIRCLE] = u8"🟡", + [SPECIAL_GLYPH_BLUE_CIRCLE] = u8"🔵", + [SPECIAL_GLYPH_GREEN_CIRCLE] = u8"🟢", }, }; diff --git a/src/basic/glyph-util.h b/src/basic/glyph-util.h index a770997..db8dbbf 100644 --- a/src/basic/glyph-util.h +++ b/src/basic/glyph-util.h @@ -13,6 +13,8 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_TREE_SPACE, SPECIAL_GLYPH_TREE_TOP, SPECIAL_GLYPH_VERTICAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_DOTTED, + SPECIAL_GLYPH_HORIZONTAL_FAT, SPECIAL_GLYPH_TRIANGULAR_BULLET, SPECIAL_GLYPH_BLACK_CIRCLE, SPECIAL_GLYPH_WHITE_CIRCLE, @@ -49,6 +51,10 @@ typedef enum SpecialGlyph { SPECIAL_GLYPH_WARNING_SIGN, SPECIAL_GLYPH_COMPUTER_DISK, SPECIAL_GLYPH_WORLD, + SPECIAL_GLYPH_RED_CIRCLE, + SPECIAL_GLYPH_YELLOW_CIRCLE, + SPECIAL_GLYPH_BLUE_CIRCLE, + SPECIAL_GLYPH_GREEN_CIRCLE, _SPECIAL_GLYPH_MAX, _SPECIAL_GLYPH_INVALID = -EINVAL, } SpecialGlyph; diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c index 5fac467..251ee4f 100644 --- a/src/basic/hash-funcs.c +++ b/src/basic/hash-funcs.c @@ -33,7 +33,7 @@ void path_hash_func(const char *q, struct siphash *state) { /* if path is absolute, add one "/" to the hash. */ if (path_is_absolute(q)) - siphash24_compress("/", 1, state); + siphash24_compress_byte('/', state); for (;;) { const char *e; @@ -67,7 +67,7 @@ DEFINE_HASH_OPS_FULL(path_hash_ops_free_free, void, free); void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); + siphash24_compress_typesafe(p, state); } int trivial_compare_func(const void *a, const void *b) { @@ -93,7 +93,7 @@ const struct hash_ops trivial_hash_ops_free_free = { }; void uint64_hash_func(const uint64_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); + siphash24_compress_typesafe(*p, state); } int uint64_compare_func(const uint64_t *a, const uint64_t *b) { @@ -104,7 +104,7 @@ DEFINE_HASH_OPS(uint64_hash_ops, uint64_t, uint64_hash_func, uint64_compare_func #if SIZEOF_DEV_T != 8 void devt_hash_func(const dev_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); + siphash24_compress_typesafe(*p, state); } #endif diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c index 894760c..a9fd762 100644 --- a/src/basic/hashmap.c +++ b/src/basic/hashmap.c @@ -2120,24 +2120,27 @@ static int hashmap_entry_compare( return compare((*a)->key, (*b)->key); } -int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { - _cleanup_free_ struct hashmap_base_entry **entries = NULL; +static int _hashmap_dump_entries_sorted( + HashmapBase *h, + void ***ret, + size_t *ret_n) { + _cleanup_free_ void **entries = NULL; Iterator iter; unsigned idx; size_t n = 0; assert(ret); + assert(ret_n); if (_hashmap_size(h) == 0) { *ret = NULL; - if (ret_n) - *ret_n = 0; + *ret_n = 0; return 0; } /* We append one more element than needed so that the resulting array can be used as a strv. We * don't count this entry in the returned size. */ - entries = new(struct hashmap_base_entry*, _hashmap_size(h) + 1); + entries = new(void*, _hashmap_size(h) + 1); if (!entries) return -ENOMEM; @@ -2147,13 +2150,47 @@ int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { assert(n == _hashmap_size(h)); entries[n] = NULL; - typesafe_qsort_r(entries, n, hashmap_entry_compare, h->hash_ops->compare); + typesafe_qsort_r((struct hashmap_base_entry**) entries, n, + hashmap_entry_compare, h->hash_ops->compare); + + *ret = TAKE_PTR(entries); + *ret_n = n; + return 0; +} + +int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { + _cleanup_free_ void **entries = NULL; + size_t n; + int r; + + r = _hashmap_dump_entries_sorted(h, &entries, &n); + if (r < 0) + return r; + + /* Reuse the array. */ + FOREACH_ARRAY(e, entries, n) + *e = (void*) (*(struct hashmap_base_entry**) e)->key; + + *ret = TAKE_PTR(entries); + if (ret_n) + *ret_n = n; + return 0; +} + +int _hashmap_dump_sorted(HashmapBase *h, void ***ret, size_t *ret_n) { + _cleanup_free_ void **entries = NULL; + size_t n; + int r; + + r = _hashmap_dump_entries_sorted(h, &entries, &n); + if (r < 0) + return r; /* Reuse the array. */ FOREACH_ARRAY(e, entries, n) - *e = entry_value(h, *e); + *e = entry_value(h, *(struct hashmap_base_entry**) e); - *ret = (void**) TAKE_PTR(entries); + *ret = TAKE_PTR(entries); if (ret_n) *ret_n = n; return 0; diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h index d0ebdf5..49d9d11 100644 --- a/src/basic/hashmap.h +++ b/src/basic/hashmap.h @@ -409,6 +409,14 @@ static inline int set_dump_sorted(Set *h, void ***ret, size_t *ret_n) { return _hashmap_dump_sorted(HASHMAP_BASE(h), ret, ret_n); } +int _hashmap_dump_keys_sorted(HashmapBase *h, void ***ret, size_t *ret_n); +static inline int hashmap_dump_keys_sorted(Hashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n); +} +static inline int ordered_hashmap_dump_keys_sorted(OrderedHashmap *h, void ***ret, size_t *ret_n) { + return _hashmap_dump_keys_sorted(HASHMAP_BASE(h), ret, ret_n); +} + /* * Hashmaps are iterated in unpredictable order. * OrderedHashmaps are an exception to this. They are iterated in the order diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index ea683eb..4cb67d9 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -114,7 +114,7 @@ int unhexmem_full( const char *p, size_t l, bool secure, - void **ret, + void **ret_data, size_t *ret_len) { _cleanup_free_ uint8_t *buf = NULL; @@ -155,8 +155,8 @@ int unhexmem_full( if (ret_len) *ret_len = (size_t) (z - buf); - if (ret) - *ret = TAKE_PTR(buf); + if (ret_data) + *ret_data = TAKE_PTR(buf); return 0; } @@ -766,7 +766,7 @@ int unbase64mem_full( const char *p, size_t l, bool secure, - void **ret, + void **ret_data, size_t *ret_size) { _cleanup_free_ uint8_t *buf = NULL; @@ -854,8 +854,8 @@ int unbase64mem_full( if (ret_size) *ret_size = (size_t) (z - buf); - if (ret) - *ret = TAKE_PTR(buf); + if (ret_data) + *ret_data = TAKE_PTR(buf); return 0; } diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h index 319b21a..0a10af3 100644 --- a/src/basic/hexdecoct.h +++ b/src/basic/hexdecoct.h @@ -18,9 +18,9 @@ char hexchar(int x) _const_; int unhexchar(char c) _const_; char *hexmem(const void *p, size_t l); -int unhexmem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); -static inline int unhexmem(const char *p, size_t l, void **mem, size_t *len) { - return unhexmem_full(p, l, false, mem, len); +int unhexmem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); +static inline int unhexmem(const char *p, void **ret_data, size_t *ret_size) { + return unhexmem_full(p, SIZE_MAX, false, ret_data, ret_size); } char base32hexchar(int x) _const_; @@ -45,9 +45,9 @@ ssize_t base64_append( size_t l, size_t margin, size_t width); -int unbase64mem_full(const char *p, size_t l, bool secure, void **mem, size_t *len); -static inline int unbase64mem(const char *p, size_t l, void **mem, size_t *len) { - return unbase64mem_full(p, l, false, mem, len); +int unbase64mem_full(const char *p, size_t l, bool secure, void **ret_data, size_t *ret_size); +static inline int unbase64mem(const char *p, void **ret_data, size_t *ret_size) { + return unbase64mem_full(p, SIZE_MAX, false, ret_data, ret_size); } void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c index ee4ea67..c077f0a 100644 --- a/src/basic/in-addr-util.c +++ b/src/basic/in-addr-util.c @@ -91,14 +91,26 @@ bool in6_addr_is_link_local_all_nodes(const struct in6_addr *a) { be32toh(a->s6_addr32[3]) == UINT32_C(0x00000001); } +bool in4_addr_is_multicast(const struct in_addr *a) { + assert(a); + + return IN_MULTICAST(be32toh(a->s_addr)); +} + +bool in6_addr_is_multicast(const struct in6_addr *a) { + assert(a); + + return IN6_IS_ADDR_MULTICAST(a); +} + int in_addr_is_multicast(int family, const union in_addr_union *u) { assert(u); if (family == AF_INET) - return IN_MULTICAST(be32toh(u->in.s_addr)); + return in4_addr_is_multicast(&u->in); if (family == AF_INET6) - return IN6_IS_ADDR_MULTICAST(&u->in6); + return in6_addr_is_multicast(&u->in6); return -EAFNOSUPPORT; } @@ -182,58 +194,69 @@ int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_ return -EAFNOSUPPORT; } -int in_addr_prefix_intersect( - int family, - const union in_addr_union *a, +bool in4_addr_prefix_intersect( + const struct in_addr *a, unsigned aprefixlen, - const union in_addr_union *b, + const struct in_addr *b, unsigned bprefixlen) { - unsigned m; - assert(a); assert(b); - /* Checks whether there are any addresses that are in both networks */ + unsigned m = MIN3(aprefixlen, bprefixlen, (unsigned) (sizeof(struct in_addr) * 8)); + if (m == 0) + return true; /* Let's return earlier, to avoid shift by 32. */ - m = MIN(aprefixlen, bprefixlen); + uint32_t x = be32toh(a->s_addr ^ b->s_addr); + uint32_t n = 0xFFFFFFFFUL << (32 - m); + return (x & n) == 0; +} - if (family == AF_INET) { - uint32_t x, nm; +bool in6_addr_prefix_intersect( + const struct in6_addr *a, + unsigned aprefixlen, + const struct in6_addr *b, + unsigned bprefixlen) { - x = be32toh(a->in.s_addr ^ b->in.s_addr); - nm = m == 0 ? 0 : 0xFFFFFFFFUL << (32 - m); + assert(a); + assert(b); - return (x & nm) == 0; - } + unsigned m = MIN3(aprefixlen, bprefixlen, (unsigned) (sizeof(struct in6_addr) * 8)); + if (m == 0) + return true; - if (family == AF_INET6) { - unsigned i; + for (size_t i = 0; i < sizeof(struct in6_addr); i++) { + uint8_t x = a->s6_addr[i] ^ b->s6_addr[i]; + uint8_t n = m < 8 ? (0xFF << (8 - m)) : 0xFF; + if ((x & n) != 0) + return false; - if (m > 128) - m = 128; + if (m <= 8) + break; - for (i = 0; i < 16; i++) { - uint8_t x, nm; + m -= 8; + } - x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; + return true; +} - if (m < 8) - nm = 0xFF << (8 - m); - else - nm = 0xFF; +int in_addr_prefix_intersect( + int family, + const union in_addr_union *a, + unsigned aprefixlen, + const union in_addr_union *b, + unsigned bprefixlen) { - if ((x & nm) != 0) - return 0; + assert(a); + assert(b); - if (m > 8) - m -= 8; - else - m = 0; - } + /* Checks whether there are any addresses that are in both networks. */ - return 1; - } + if (family == AF_INET) + return in4_addr_prefix_intersect(&a->in, aprefixlen, &b->in, bprefixlen); + + if (family == AF_INET6) + return in6_addr_prefix_intersect(&a->in6, aprefixlen, &b->in6, bprefixlen); return -EAFNOSUPPORT; } @@ -922,12 +945,19 @@ int in_addr_prefix_from_string_auto_internal( } +void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state) { + assert(u); + assert(state); + + siphash24_compress(u->bytes, FAMILY_ADDRESS_SIZE(family), state); +} + void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state) { assert(a); assert(state); - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); + siphash24_compress_typesafe(a->family, state); + in_addr_hash_func(&a->address, a->family, state); } int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y) { @@ -960,7 +990,7 @@ void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state) { assert(addr); assert(state); - siphash24_compress(addr, sizeof(*addr), state); + siphash24_compress_typesafe(*addr, state); } int in6_addr_compare_func(const struct in6_addr *a, const struct in6_addr *b) { diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h index 12720ca..9cd0aca 100644 --- a/src/basic/in-addr-util.h +++ b/src/basic/in-addr-util.h @@ -40,6 +40,8 @@ static inline bool in_addr_data_is_set(const struct in_addr_data *a) { return in_addr_data_is_null(a); } +bool in4_addr_is_multicast(const struct in_addr *a); +bool in6_addr_is_multicast(const struct in6_addr *a); int in_addr_is_multicast(int family, const union in_addr_union *u); bool in4_addr_is_link_local(const struct in_addr *a); @@ -59,7 +61,22 @@ bool in6_addr_is_ipv4_mapped_address(const struct in6_addr *a); bool in4_addr_equal(const struct in_addr *a, const struct in_addr *b); bool in6_addr_equal(const struct in6_addr *a, const struct in6_addr *b); int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); -int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); +bool in4_addr_prefix_intersect( + const struct in_addr *a, + unsigned aprefixlen, + const struct in_addr *b, + unsigned bprefixlen); +bool in6_addr_prefix_intersect( + const struct in6_addr *a, + unsigned aprefixlen, + const struct in6_addr *b, + unsigned bprefixlen); +int in_addr_prefix_intersect( + int family, + const union in_addr_union *a, + unsigned aprefixlen, + const union in_addr_union *b, + unsigned bprefixlen); int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen); int in_addr_prefix_nth(int family, union in_addr_union *u, unsigned prefixlen, uint64_t nth); int in_addr_random_prefix(int family, union in_addr_union *u, unsigned prefixlen_fixed_part, unsigned prefixlen); @@ -185,6 +202,7 @@ static inline size_t FAMILY_ADDRESS_SIZE(int family) { * See also oss-fuzz#11344. */ #define IN_ADDR_NULL ((union in_addr_union) { .in6 = {} }) +void in_addr_hash_func(const union in_addr_union *u, int family, struct siphash *state); void in_addr_data_hash_func(const struct in_addr_data *a, struct siphash *state); int in_addr_data_compare_func(const struct in_addr_data *x, const struct in_addr_data *y); void in6_addr_hash_func(const struct in6_addr *addr, struct siphash *state); diff --git a/src/basic/initrd-util.c b/src/basic/initrd-util.c index 03ccfbe..d3aa933 100644 --- a/src/basic/initrd-util.c +++ b/src/basic/initrd-util.c @@ -21,7 +21,7 @@ bool in_initrd(void) { * This can be overridden by setting SYSTEMD_IN_INITRD=0|1. */ - r = getenv_bool_secure("SYSTEMD_IN_INITRD"); + r = secure_getenv_bool("SYSTEMD_IN_INITRD"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_IN_INITRD, ignoring: %m"); diff --git a/src/basic/iovec-util.c b/src/basic/iovec-util.c index 991889a..6456945 100644 --- a/src/basic/iovec-util.c +++ b/src/basic/iovec-util.c @@ -62,8 +62,10 @@ char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const ch return x; } -void iovec_array_free(struct iovec *iovec, size_t n) { - FOREACH_ARRAY(i, iovec, n) +void iovec_array_free(struct iovec *iovec, size_t n_iovec) { + assert(iovec || n_iovec == 0); + + FOREACH_ARRAY(i, iovec, n_iovec) free(i->iov_base); free(iovec); diff --git a/src/basic/iovec-util.h b/src/basic/iovec-util.h index 39feabd..8cfa571 100644 --- a/src/basic/iovec-util.h +++ b/src/basic/iovec-util.h @@ -8,16 +8,38 @@ #include "alloc-util.h" #include "macro.h" +/* An iovec pointing to a single NUL byte */ +#define IOVEC_NUL_BYTE (const struct iovec) { \ + .iov_base = (void*) (const uint8_t[1]) { 0 }, \ + .iov_len = 1, \ + } + size_t iovec_total_size(const struct iovec *iovec, size_t n); bool iovec_increment(struct iovec *iovec, size_t n, size_t k); -#define IOVEC_MAKE(base, len) (struct iovec) { .iov_base = (base), .iov_len = (len) } -#define IOVEC_MAKE_STRING(string) \ - ({ \ - const char *_s = (string); \ - IOVEC_MAKE((char*) _s, strlen(_s)); \ - }) +/* This accepts both const and non-const pointers */ +#define IOVEC_MAKE(base, len) \ + (struct iovec) { \ + .iov_base = (void*) (base), \ + .iov_len = (len), \ + } + +static inline struct iovec* iovec_make_string(struct iovec *iovec, const char *s) { + assert(iovec); + /* We don't use strlen_ptr() here, because we don't want to include string-util.h for now */ + *iovec = IOVEC_MAKE(s, s ? strlen(s) : 0); + return iovec; +} + +#define IOVEC_MAKE_STRING(s) \ + *iovec_make_string(&(struct iovec) {}, s) + +#define CONST_IOVEC_MAKE_STRING(s) \ + (const struct iovec) { \ + .iov_base = (char*) s, \ + .iov_len = STRLEN(s), \ + } static inline void iovec_done(struct iovec *iovec) { /* A _cleanup_() helper that frees the iov_base in the iovec */ @@ -35,10 +57,43 @@ static inline void iovec_done_erase(struct iovec *iovec) { } static inline bool iovec_is_set(const struct iovec *iovec) { + /* Checks if the iovec points to a non-empty chunk of memory */ return iovec && iovec->iov_len > 0 && iovec->iov_base; } +static inline bool iovec_is_valid(const struct iovec *iovec) { + /* Checks if the iovec is either NULL, empty or points to a valid bit of memory */ + return !iovec || (iovec->iov_base || iovec->iov_len == 0); +} + char* set_iovec_string_field(struct iovec *iovec, size_t *n_iovec, const char *field, const char *value); char* set_iovec_string_field_free(struct iovec *iovec, size_t *n_iovec, const char *field, char *value); -void iovec_array_free(struct iovec *iovec, size_t n); +void iovec_array_free(struct iovec *iovec, size_t n_iovec); + +static inline int iovec_memcmp(const struct iovec *a, const struct iovec *b) { + + if (a == b) + return 0; + + return memcmp_nn(a ? a->iov_base : NULL, + a ? a->iov_len : 0, + b ? b->iov_base : NULL, + b ? b->iov_len : 0); +} + +static inline struct iovec *iovec_memdup(const struct iovec *source, struct iovec *ret) { + assert(ret); + + if (!iovec_is_set(source)) + *ret = (struct iovec) {}; + else { + void *p = memdup(source->iov_base, source->iov_len); + if (!p) + return NULL; + + *ret = IOVEC_MAKE(p, source->iov_len); + } + + return ret; +} diff --git a/src/basic/keyring-util.c b/src/basic/keyring-util.c new file mode 100644 index 0000000..c32bd50 --- /dev/null +++ b/src/basic/keyring-util.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "keyring-util.h" +#include "memory-util.h" +#include "missing_syscall.h" + +int keyring_read(key_serial_t serial, void **ret, size_t *ret_size) { + size_t bufsize = 100; + + for (;;) { + _cleanup_(erase_and_freep) uint8_t *buf = NULL; + long n; + + buf = new(uint8_t, bufsize + 1); + if (!buf) + return -ENOMEM; + + n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) buf, (unsigned long) bufsize, 0); + if (n < 0) + return -errno; + + if ((size_t) n <= bufsize) { + buf[n] = 0; /* NUL terminate, just in case */ + + if (ret) + *ret = TAKE_PTR(buf); + if (ret_size) + *ret_size = n; + + return 0; + } + + bufsize = (size_t) n; + } +} + +int keyring_describe(key_serial_t serial, char **ret) { + _cleanup_free_ char *tuple = NULL; + size_t sz = 64; + int c = -1; /* Workaround for maybe-uninitialized false positive due to missing_syscall indirection */ + + assert(ret); + + for (;;) { + tuple = new(char, sz); + if (!tuple) + return log_oom_debug(); + + c = keyctl(KEYCTL_DESCRIBE, serial, (unsigned long) tuple, c, 0); + if (c < 0) + return log_debug_errno(errno, "Failed to describe key id %d: %m", serial); + + if ((size_t) c <= sz) + break; + + sz = c; + free(tuple); + } + + /* The kernel returns a final NUL in the string, verify that. */ + assert(tuple[c-1] == 0); + + *ret = TAKE_PTR(tuple); + + return 0; +} diff --git a/src/basic/keyring-util.h b/src/basic/keyring-util.h new file mode 100644 index 0000000..6e6e685 --- /dev/null +++ b/src/basic/keyring-util.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "missing_keyctl.h" + +/* Like TAKE_PTR() but for key_serial_t, resetting them to -1 */ +#define TAKE_KEY_SERIAL(key_serial) TAKE_GENERIC(key_serial, key_serial_t, -1) + +int keyring_read(key_serial_t serial, void **ret, size_t *ret_size); +int keyring_describe(key_serial_t serial, char **ret); diff --git a/src/basic/label.c b/src/basic/label.c index f134e77..8b084a7 100644 --- a/src/basic/label.c +++ b/src/basic/label.c @@ -28,3 +28,7 @@ int label_ops_post(int dir_fd, const char *path) { return label_ops->post(dir_fd, path); } + +void label_ops_reset(void) { + label_ops = NULL; +} diff --git a/src/basic/label.h b/src/basic/label.h index 9644e43..a070bf2 100644 --- a/src/basic/label.h +++ b/src/basic/label.h @@ -12,3 +12,4 @@ int label_ops_set(const LabelOps *label_ops); int label_ops_pre(int dir_fd, const char *path, mode_t mode); int label_ops_post(int dir_fd, const char *path); +void label_ops_reset(void); diff --git a/src/basic/linux/btrfs.h b/src/basic/linux/btrfs.h index 74ed908..73a295e 100644 --- a/src/basic/linux/btrfs.h +++ b/src/basic/linux/btrfs.h @@ -94,6 +94,7 @@ struct btrfs_qgroup_limit { * struct btrfs_qgroup_inherit.flags */ #define BTRFS_QGROUP_INHERIT_SET_LIMITS (1ULL << 0) +#define BTRFS_QGROUP_INHERIT_FLAGS_SUPP (BTRFS_QGROUP_INHERIT_SET_LIMITS) struct btrfs_qgroup_inherit { __u64 flags; @@ -189,6 +190,7 @@ struct btrfs_scrub_progress { }; #define BTRFS_SCRUB_READONLY 1 +#define BTRFS_SCRUB_SUPPORTED_FLAGS (BTRFS_SCRUB_READONLY) struct btrfs_ioctl_scrub_args { __u64 devid; /* in */ __u64 start; /* in */ @@ -247,7 +249,17 @@ struct btrfs_ioctl_dev_info_args { __u8 uuid[BTRFS_UUID_SIZE]; /* in/out */ __u64 bytes_used; /* out */ __u64 total_bytes; /* out */ - __u64 unused[379]; /* pad to 4k */ + /* + * Optional, out. + * + * Showing the fsid of the device, allowing user space to check if this + * device is a seeding one. + * + * Introduced in v6.3, thus user space still needs to check if kernel + * changed this value. Older kernel will not touch the values here. + */ + __u8 fsid[BTRFS_UUID_SIZE]; + __u64 unused[377]; /* pad to 4k */ __u8 path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ }; @@ -324,6 +336,8 @@ struct btrfs_ioctl_fs_info_args { #define BTRFS_FEATURE_INCOMPAT_RAID1C34 (1ULL << 11) #define BTRFS_FEATURE_INCOMPAT_ZONED (1ULL << 12) #define BTRFS_FEATURE_INCOMPAT_EXTENT_TREE_V2 (1ULL << 13) +#define BTRFS_FEATURE_INCOMPAT_RAID_STRIPE_TREE (1ULL << 14) +#define BTRFS_FEATURE_INCOMPAT_SIMPLE_QUOTA (1ULL << 16) struct btrfs_ioctl_feature_flags { __u64 compat_flags; @@ -603,6 +617,9 @@ struct btrfs_ioctl_clone_range_args { */ #define BTRFS_DEFRAG_RANGE_COMPRESS 1 #define BTRFS_DEFRAG_RANGE_START_IO 2 +#define BTRFS_DEFRAG_RANGE_FLAGS_SUPP (BTRFS_DEFRAG_RANGE_COMPRESS | \ + BTRFS_DEFRAG_RANGE_START_IO) + struct btrfs_ioctl_defrag_range_args { /* start of the defrag operation */ __u64 start; @@ -744,6 +761,7 @@ struct btrfs_ioctl_get_dev_stats { #define BTRFS_QUOTA_CTL_ENABLE 1 #define BTRFS_QUOTA_CTL_DISABLE 2 #define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3 +#define BTRFS_QUOTA_CTL_ENABLE_SIMPLE_QUOTA 4 struct btrfs_ioctl_quota_ctl_args { __u64 cmd; __u64 status; diff --git a/src/basic/linux/btrfs_tree.h b/src/basic/linux/btrfs_tree.h index ab38d0f..d24e8e1 100644 --- a/src/basic/linux/btrfs_tree.h +++ b/src/basic/linux/btrfs_tree.h @@ -73,6 +73,9 @@ /* Holds the block group items for extent tree v2. */ #define BTRFS_BLOCK_GROUP_TREE_OBJECTID 11ULL +/* Tracks RAID stripes in block groups. */ +#define BTRFS_RAID_STRIPE_TREE_OBJECTID 12ULL + /* device stats in the device tree */ #define BTRFS_DEV_STATS_OBJECTID 0ULL @@ -216,11 +219,31 @@ */ #define BTRFS_METADATA_ITEM_KEY 169 +/* + * Special inline ref key which stores the id of the subvolume which originally + * created the extent. This subvolume owns the extent permanently from the + * perspective of simple quotas. Needed to know which subvolume to free quota + * usage from when the extent is deleted. + * + * Stored as an inline ref rather to avoid wasting space on a separate item on + * top of the existing extent item. However, unlike the other inline refs, + * there is one one owner ref per extent rather than one per extent. + * + * Because of this, it goes at the front of the list of inline refs, and thus + * must have a lower type value than any other inline ref type (to satisfy the + * disk format rule that inline refs have non-decreasing type). + */ +#define BTRFS_EXTENT_OWNER_REF_KEY 172 + #define BTRFS_TREE_BLOCK_REF_KEY 176 #define BTRFS_EXTENT_DATA_REF_KEY 178 -#define BTRFS_EXTENT_REF_V0_KEY 180 +/* + * Obsolete key. Defintion removed in 6.6, value may be reused in the future. + * + * #define BTRFS_EXTENT_REF_V0_KEY 180 + */ #define BTRFS_SHARED_BLOCK_REF_KEY 182 @@ -257,6 +280,8 @@ #define BTRFS_DEV_ITEM_KEY 216 #define BTRFS_CHUNK_ITEM_KEY 228 +#define BTRFS_RAID_STRIPE_KEY 230 + /* * Records the overall state of the qgroups. * There's only one instance of this key present, @@ -715,6 +740,30 @@ struct btrfs_free_space_header { __le64 num_bitmaps; } __attribute__ ((__packed__)); +struct btrfs_raid_stride { + /* The id of device this raid extent lives on. */ + __le64 devid; + /* The physical location on disk. */ + __le64 physical; +} __attribute__ ((__packed__)); + +/* The stripe_extent::encoding, 1:1 mapping of enum btrfs_raid_types. */ +#define BTRFS_STRIPE_RAID0 1 +#define BTRFS_STRIPE_RAID1 2 +#define BTRFS_STRIPE_DUP 3 +#define BTRFS_STRIPE_RAID10 4 +#define BTRFS_STRIPE_RAID5 5 +#define BTRFS_STRIPE_RAID6 6 +#define BTRFS_STRIPE_RAID1C3 7 +#define BTRFS_STRIPE_RAID1C4 8 + +struct btrfs_stripe_extent { + __u8 encoding; + __u8 reserved[7]; + /* An array of raid strides this stripe is composed of. */ + struct btrfs_raid_stride strides[]; +} __attribute__ ((__packed__)); + #define BTRFS_HEADER_FLAG_WRITTEN (1ULL << 0) #define BTRFS_HEADER_FLAG_RELOC (1ULL << 1) @@ -783,6 +832,10 @@ struct btrfs_shared_data_ref { __le32 count; } __attribute__ ((__packed__)); +struct btrfs_extent_owner_ref { + __le64 root_id; +} __attribute__ ((__packed__)); + struct btrfs_extent_inline_ref { __u8 type; __le64 offset; @@ -1200,9 +1253,17 @@ static inline __u16 btrfs_qgroup_level(__u64 qgroupid) */ #define BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT (1ULL << 2) +/* + * Whether or not this filesystem is using simple quotas. Not exactly the + * incompat bit, because we support using simple quotas, disabling it, then + * going back to full qgroup quotas. + */ +#define BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE (1ULL << 3) + #define BTRFS_QGROUP_STATUS_FLAGS_MASK (BTRFS_QGROUP_STATUS_FLAG_ON | \ BTRFS_QGROUP_STATUS_FLAG_RESCAN | \ - BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT) + BTRFS_QGROUP_STATUS_FLAG_INCONSISTENT | \ + BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE) #define BTRFS_QGROUP_STATUS_VERSION 1 @@ -1224,6 +1285,15 @@ struct btrfs_qgroup_status_item { * of the scan. It contains a logical address */ __le64 rescan; + + /* + * The generation when quotas were last enabled. Used by simple quotas to + * avoid decrementing when freeing an extent that was written before + * enable. + * + * Set only if flags contain BTRFS_QGROUP_STATUS_FLAG_SIMPLE_MODE. + */ + __le64 enable_gen; } __attribute__ ((__packed__)); struct btrfs_qgroup_info_item { diff --git a/src/basic/linux/fou.h b/src/basic/linux/fou.h index 87c2c9f..b5cd3e7 100644 --- a/src/basic/linux/fou.h +++ b/src/basic/linux/fou.h @@ -1,32 +1,37 @@ -/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ -/* fou.h - FOU Interface */ +/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/fou.yaml */ +/* YNL-GEN uapi header */ #ifndef _UAPI_LINUX_FOU_H #define _UAPI_LINUX_FOU_H -/* NETLINK_GENERIC related info - */ #define FOU_GENL_NAME "fou" -#define FOU_GENL_VERSION 0x1 +#define FOU_GENL_VERSION 1 enum { - FOU_ATTR_UNSPEC, - FOU_ATTR_PORT, /* u16 */ - FOU_ATTR_AF, /* u8 */ - FOU_ATTR_IPPROTO, /* u8 */ - FOU_ATTR_TYPE, /* u8 */ - FOU_ATTR_REMCSUM_NOPARTIAL, /* flag */ - FOU_ATTR_LOCAL_V4, /* u32 */ - FOU_ATTR_LOCAL_V6, /* in6_addr */ - FOU_ATTR_PEER_V4, /* u32 */ - FOU_ATTR_PEER_V6, /* in6_addr */ - FOU_ATTR_PEER_PORT, /* u16 */ - FOU_ATTR_IFINDEX, /* s32 */ - - __FOU_ATTR_MAX, + FOU_ENCAP_UNSPEC, + FOU_ENCAP_DIRECT, + FOU_ENCAP_GUE, }; -#define FOU_ATTR_MAX (__FOU_ATTR_MAX - 1) +enum { + FOU_ATTR_UNSPEC, + FOU_ATTR_PORT, + FOU_ATTR_AF, + FOU_ATTR_IPPROTO, + FOU_ATTR_TYPE, + FOU_ATTR_REMCSUM_NOPARTIAL, + FOU_ATTR_LOCAL_V4, + FOU_ATTR_LOCAL_V6, + FOU_ATTR_PEER_V4, + FOU_ATTR_PEER_V6, + FOU_ATTR_PEER_PORT, + FOU_ATTR_IFINDEX, + + __FOU_ATTR_MAX +}; +#define FOU_ATTR_MAX (__FOU_ATTR_MAX - 1) enum { FOU_CMD_UNSPEC, @@ -34,15 +39,8 @@ enum { FOU_CMD_DEL, FOU_CMD_GET, - __FOU_CMD_MAX, + __FOU_CMD_MAX }; - -enum { - FOU_ENCAP_UNSPEC, - FOU_ENCAP_DIRECT, - FOU_ENCAP_GUE, -}; - -#define FOU_CMD_MAX (__FOU_CMD_MAX - 1) +#define FOU_CMD_MAX (__FOU_CMD_MAX - 1) #endif /* _UAPI_LINUX_FOU_H */ diff --git a/src/basic/linux/if_bridge.h b/src/basic/linux/if_bridge.h index d9de241..a5b743a 100644 --- a/src/basic/linux/if_bridge.h +++ b/src/basic/linux/if_bridge.h @@ -523,6 +523,9 @@ enum { BRIDGE_VLANDB_ENTRY_TUNNEL_INFO, BRIDGE_VLANDB_ENTRY_STATS, BRIDGE_VLANDB_ENTRY_MCAST_ROUTER, + BRIDGE_VLANDB_ENTRY_MCAST_N_GROUPS, + BRIDGE_VLANDB_ENTRY_MCAST_MAX_GROUPS, + BRIDGE_VLANDB_ENTRY_NEIGH_SUPPRESS, __BRIDGE_VLANDB_ENTRY_MAX, }; #define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1) @@ -631,6 +634,11 @@ enum { MDBA_MDB_EATTR_GROUP_MODE, MDBA_MDB_EATTR_SOURCE, MDBA_MDB_EATTR_RTPROT, + MDBA_MDB_EATTR_DST, + MDBA_MDB_EATTR_DST_PORT, + MDBA_MDB_EATTR_VNI, + MDBA_MDB_EATTR_IFINDEX, + MDBA_MDB_EATTR_SRC_VNI, __MDBA_MDB_EATTR_MAX }; #define MDBA_MDB_EATTR_MAX (__MDBA_MDB_EATTR_MAX - 1) @@ -715,6 +723,24 @@ enum { }; #define MDBA_SET_ENTRY_MAX (__MDBA_SET_ENTRY_MAX - 1) +/* [MDBA_GET_ENTRY] = { + * struct br_mdb_entry + * [MDBA_GET_ENTRY_ATTRS] = { + * [MDBE_ATTR_SOURCE] + * struct in_addr / struct in6_addr + * [MDBE_ATTR_SRC_VNI] + * u32 + * } + * } + */ +enum { + MDBA_GET_ENTRY_UNSPEC, + MDBA_GET_ENTRY, + MDBA_GET_ENTRY_ATTRS, + __MDBA_GET_ENTRY_MAX, +}; +#define MDBA_GET_ENTRY_MAX (__MDBA_GET_ENTRY_MAX - 1) + /* [MDBA_SET_ENTRY_ATTRS] = { * [MDBE_ATTR_xxx] * ... @@ -726,6 +752,12 @@ enum { MDBE_ATTR_SRC_LIST, MDBE_ATTR_GROUP_MODE, MDBE_ATTR_RTPROT, + MDBE_ATTR_DST, + MDBE_ATTR_DST_PORT, + MDBE_ATTR_VNI, + MDBE_ATTR_IFINDEX, + MDBE_ATTR_SRC_VNI, + MDBE_ATTR_STATE_MASK, __MDBE_ATTR_MAX, }; #define MDBE_ATTR_MAX (__MDBE_ATTR_MAX - 1) diff --git a/src/basic/linux/if_link.h b/src/basic/linux/if_link.h index 1021a7e..ffa637b 100644 --- a/src/basic/linux/if_link.h +++ b/src/basic/linux/if_link.h @@ -374,6 +374,9 @@ enum { IFLA_DEVLINK_PORT, + IFLA_GSO_IPV4_MAX_SIZE, + IFLA_GRO_IPV4_MAX_SIZE, + IFLA_DPLL_PIN, __IFLA_MAX }; @@ -458,6 +461,286 @@ enum in6_addr_gen_mode { /* Bridge section */ +/** + * DOC: Bridge enum definition + * + * Please *note* that the timer values in the following section are expected + * in clock_t format, which is seconds multiplied by USER_HZ (generally + * defined as 100). + * + * @IFLA_BR_FORWARD_DELAY + * The bridge forwarding delay is the time spent in LISTENING state + * (before moving to LEARNING) and in LEARNING state (before moving + * to FORWARDING). Only relevant if STP is enabled. + * + * The valid values are between (2 * USER_HZ) and (30 * USER_HZ). + * The default value is (15 * USER_HZ). + * + * @IFLA_BR_HELLO_TIME + * The time between hello packets sent by the bridge, when it is a root + * bridge or a designated bridge. Only relevant if STP is enabled. + * + * The valid values are between (1 * USER_HZ) and (10 * USER_HZ). + * The default value is (2 * USER_HZ). + * + * @IFLA_BR_MAX_AGE + * The hello packet timeout is the time until another bridge in the + * spanning tree is assumed to be dead, after reception of its last hello + * message. Only relevant if STP is enabled. + * + * The valid values are between (6 * USER_HZ) and (40 * USER_HZ). + * The default value is (20 * USER_HZ). + * + * @IFLA_BR_AGEING_TIME + * Configure the bridge's FDB entries aging time. It is the time a MAC + * address will be kept in the FDB after a packet has been received from + * that address. After this time has passed, entries are cleaned up. + * Allow values outside the 802.1 standard specification for special cases: + * + * * 0 - entry never ages (all permanent) + * * 1 - entry disappears (no persistence) + * + * The default value is (300 * USER_HZ). + * + * @IFLA_BR_STP_STATE + * Turn spanning tree protocol on (*IFLA_BR_STP_STATE* > 0) or off + * (*IFLA_BR_STP_STATE* == 0) for this bridge. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_PRIORITY + * Set this bridge's spanning tree priority, used during STP root bridge + * election. + * + * The valid values are between 0 and 65535. + * + * @IFLA_BR_VLAN_FILTERING + * Turn VLAN filtering on (*IFLA_BR_VLAN_FILTERING* > 0) or off + * (*IFLA_BR_VLAN_FILTERING* == 0). When disabled, the bridge will not + * consider the VLAN tag when handling packets. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_VLAN_PROTOCOL + * Set the protocol used for VLAN filtering. + * + * The valid values are 0x8100(802.1Q) or 0x88A8(802.1AD). The default value + * is 0x8100(802.1Q). + * + * @IFLA_BR_GROUP_FWD_MASK + * The group forwarding mask. This is the bitmask that is applied to + * decide whether to forward incoming frames destined to link-local + * addresses (of the form 01:80:C2:00:00:0X). + * + * The default value is 0, which means the bridge does not forward any + * link-local frames coming on this port. + * + * @IFLA_BR_ROOT_ID + * The bridge root id, read only. + * + * @IFLA_BR_BRIDGE_ID + * The bridge id, read only. + * + * @IFLA_BR_ROOT_PORT + * The bridge root port, read only. + * + * @IFLA_BR_ROOT_PATH_COST + * The bridge root path cost, read only. + * + * @IFLA_BR_TOPOLOGY_CHANGE + * The bridge topology change, read only. + * + * @IFLA_BR_TOPOLOGY_CHANGE_DETECTED + * The bridge topology change detected, read only. + * + * @IFLA_BR_HELLO_TIMER + * The bridge hello timer, read only. + * + * @IFLA_BR_TCN_TIMER + * The bridge tcn timer, read only. + * + * @IFLA_BR_TOPOLOGY_CHANGE_TIMER + * The bridge topology change timer, read only. + * + * @IFLA_BR_GC_TIMER + * The bridge gc timer, read only. + * + * @IFLA_BR_GROUP_ADDR + * Set the MAC address of the multicast group this bridge uses for STP. + * The address must be a link-local address in standard Ethernet MAC address + * format. It is an address of the form 01:80:C2:00:00:0X, with X in [0, 4..f]. + * + * The default value is 0. + * + * @IFLA_BR_FDB_FLUSH + * Flush bridge's fdb dynamic entries. + * + * @IFLA_BR_MCAST_ROUTER + * Set bridge's multicast router if IGMP snooping is enabled. + * The valid values are: + * + * * 0 - disabled. + * * 1 - automatic (queried). + * * 2 - permanently enabled. + * + * The default value is 1. + * + * @IFLA_BR_MCAST_SNOOPING + * Turn multicast snooping on (*IFLA_BR_MCAST_SNOOPING* > 0) or off + * (*IFLA_BR_MCAST_SNOOPING* == 0). + * + * The default value is 1. + * + * @IFLA_BR_MCAST_QUERY_USE_IFADDR + * If enabled use the bridge's own IP address as source address for IGMP + * queries (*IFLA_BR_MCAST_QUERY_USE_IFADDR* > 0) or the default of 0.0.0.0 + * (*IFLA_BR_MCAST_QUERY_USE_IFADDR* == 0). + * + * The default value is 0 (disabled). + * + * @IFLA_BR_MCAST_QUERIER + * Enable (*IFLA_BR_MULTICAST_QUERIER* > 0) or disable + * (*IFLA_BR_MULTICAST_QUERIER* == 0) IGMP querier, ie sending of multicast + * queries by the bridge. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_MCAST_HASH_ELASTICITY + * Set multicast database hash elasticity, It is the maximum chain length in + * the multicast hash table. This attribute is *deprecated* and the value + * is always 16. + * + * @IFLA_BR_MCAST_HASH_MAX + * Set maximum size of the multicast hash table + * + * The default value is 4096, the value must be a power of 2. + * + * @IFLA_BR_MCAST_LAST_MEMBER_CNT + * The Last Member Query Count is the number of Group-Specific Queries + * sent before the router assumes there are no local members. The Last + * Member Query Count is also the number of Group-and-Source-Specific + * Queries sent before the router assumes there are no listeners for a + * particular source. + * + * The default value is 2. + * + * @IFLA_BR_MCAST_STARTUP_QUERY_CNT + * The Startup Query Count is the number of Queries sent out on startup, + * separated by the Startup Query Interval. + * + * The default value is 2. + * + * @IFLA_BR_MCAST_LAST_MEMBER_INTVL + * The Last Member Query Interval is the Max Response Time inserted into + * Group-Specific Queries sent in response to Leave Group messages, and + * is also the amount of time between Group-Specific Query messages. + * + * The default value is (1 * USER_HZ). + * + * @IFLA_BR_MCAST_MEMBERSHIP_INTVL + * The interval after which the bridge will leave a group, if no membership + * reports for this group are received. + * + * The default value is (260 * USER_HZ). + * + * @IFLA_BR_MCAST_QUERIER_INTVL + * The interval between queries sent by other routers. if no queries are + * seen after this delay has passed, the bridge will start to send its own + * queries (as if *IFLA_BR_MCAST_QUERIER_INTVL* was enabled). + * + * The default value is (255 * USER_HZ). + * + * @IFLA_BR_MCAST_QUERY_INTVL + * The Query Interval is the interval between General Queries sent by + * the Querier. + * + * The default value is (125 * USER_HZ). The minimum value is (1 * USER_HZ). + * + * @IFLA_BR_MCAST_QUERY_RESPONSE_INTVL + * The Max Response Time used to calculate the Max Resp Code inserted + * into the periodic General Queries. + * + * The default value is (10 * USER_HZ). + * + * @IFLA_BR_MCAST_STARTUP_QUERY_INTVL + * The interval between queries in the startup phase. + * + * The default value is (125 * USER_HZ) / 4. The minimum value is (1 * USER_HZ). + * + * @IFLA_BR_NF_CALL_IPTABLES + * Enable (*NF_CALL_IPTABLES* > 0) or disable (*NF_CALL_IPTABLES* == 0) + * iptables hooks on the bridge. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_NF_CALL_IP6TABLES + * Enable (*NF_CALL_IP6TABLES* > 0) or disable (*NF_CALL_IP6TABLES* == 0) + * ip6tables hooks on the bridge. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_NF_CALL_ARPTABLES + * Enable (*NF_CALL_ARPTABLES* > 0) or disable (*NF_CALL_ARPTABLES* == 0) + * arptables hooks on the bridge. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_VLAN_DEFAULT_PVID + * VLAN ID applied to untagged and priority-tagged incoming packets. + * + * The default value is 1. Setting to the special value 0 makes all ports of + * this bridge not have a PVID by default, which means that they will + * not accept VLAN-untagged traffic. + * + * @IFLA_BR_PAD + * Bridge attribute padding type for netlink message. + * + * @IFLA_BR_VLAN_STATS_ENABLED + * Enable (*IFLA_BR_VLAN_STATS_ENABLED* == 1) or disable + * (*IFLA_BR_VLAN_STATS_ENABLED* == 0) per-VLAN stats accounting. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_MCAST_STATS_ENABLED + * Enable (*IFLA_BR_MCAST_STATS_ENABLED* > 0) or disable + * (*IFLA_BR_MCAST_STATS_ENABLED* == 0) multicast (IGMP/MLD) stats + * accounting. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_MCAST_IGMP_VERSION + * Set the IGMP version. + * + * The valid values are 2 and 3. The default value is 2. + * + * @IFLA_BR_MCAST_MLD_VERSION + * Set the MLD version. + * + * The valid values are 1 and 2. The default value is 1. + * + * @IFLA_BR_VLAN_STATS_PER_PORT + * Enable (*IFLA_BR_VLAN_STATS_PER_PORT* == 1) or disable + * (*IFLA_BR_VLAN_STATS_PER_PORT* == 0) per-VLAN per-port stats accounting. + * Can be changed only when there are no port VLANs configured. + * + * The default value is 0 (disabled). + * + * @IFLA_BR_MULTI_BOOLOPT + * The multi_boolopt is used to control new boolean options to avoid adding + * new netlink attributes. You can look at ``enum br_boolopt_id`` for those + * options. + * + * @IFLA_BR_MCAST_QUERIER_STATE + * Bridge mcast querier states, read only. + * + * @IFLA_BR_FDB_N_LEARNED + * The number of dynamically learned FDB entries for the current bridge, + * read only. + * + * @IFLA_BR_FDB_MAX_LEARNED + * Set the number of max dynamically learned FDB entries for the current + * bridge. + */ enum { IFLA_BR_UNSPEC, IFLA_BR_FORWARD_DELAY, @@ -507,6 +790,8 @@ enum { IFLA_BR_VLAN_STATS_PER_PORT, IFLA_BR_MULTI_BOOLOPT, IFLA_BR_MCAST_QUERIER_STATE, + IFLA_BR_FDB_N_LEARNED, + IFLA_BR_FDB_MAX_LEARNED, __IFLA_BR_MAX, }; @@ -517,11 +802,252 @@ struct ifla_bridge_id { __u8 addr[6]; /* ETH_ALEN */ }; +/** + * DOC: Bridge mode enum definition + * + * @BRIDGE_MODE_HAIRPIN + * Controls whether traffic may be sent back out of the port on which it + * was received. This option is also called reflective relay mode, and is + * used to support basic VEPA (Virtual Ethernet Port Aggregator) + * capabilities. By default, this flag is turned off and the bridge will + * not forward traffic back out of the receiving port. + */ enum { BRIDGE_MODE_UNSPEC, BRIDGE_MODE_HAIRPIN, }; +/** + * DOC: Bridge port enum definition + * + * @IFLA_BRPORT_STATE + * The operation state of the port. Here are the valid values. + * + * * 0 - port is in STP *DISABLED* state. Make this port completely + * inactive for STP. This is also called BPDU filter and could be used + * to disable STP on an untrusted port, like a leaf virtual device. + * The traffic forwarding is also stopped on this port. + * * 1 - port is in STP *LISTENING* state. Only valid if STP is enabled + * on the bridge. In this state the port listens for STP BPDUs and + * drops all other traffic frames. + * * 2 - port is in STP *LEARNING* state. Only valid if STP is enabled on + * the bridge. In this state the port will accept traffic only for the + * purpose of updating MAC address tables. + * * 3 - port is in STP *FORWARDING* state. Port is fully active. + * * 4 - port is in STP *BLOCKING* state. Only valid if STP is enabled on + * the bridge. This state is used during the STP election process. + * In this state, port will only process STP BPDUs. + * + * @IFLA_BRPORT_PRIORITY + * The STP port priority. The valid values are between 0 and 255. + * + * @IFLA_BRPORT_COST + * The STP path cost of the port. The valid values are between 1 and 65535. + * + * @IFLA_BRPORT_MODE + * Set the bridge port mode. See *BRIDGE_MODE_HAIRPIN* for more details. + * + * @IFLA_BRPORT_GUARD + * Controls whether STP BPDUs will be processed by the bridge port. By + * default, the flag is turned off to allow BPDU processing. Turning this + * flag on will disable the bridge port if a STP BPDU packet is received. + * + * If the bridge has Spanning Tree enabled, hostile devices on the network + * may send BPDU on a port and cause network failure. Setting *guard on* + * will detect and stop this by disabling the port. The port will be + * restarted if the link is brought down, or removed and reattached. + * + * @IFLA_BRPORT_PROTECT + * Controls whether a given port is allowed to become a root port or not. + * Only used when STP is enabled on the bridge. By default the flag is off. + * + * This feature is also called root port guard. If BPDU is received from a + * leaf (edge) port, it should not be elected as root port. This could + * be used if using STP on a bridge and the downstream bridges are not fully + * trusted; this prevents a hostile guest from rerouting traffic. + * + * @IFLA_BRPORT_FAST_LEAVE + * This flag allows the bridge to immediately stop multicast traffic + * forwarding on a port that receives an IGMP Leave message. It is only used + * when IGMP snooping is enabled on the bridge. By default the flag is off. + * + * @IFLA_BRPORT_LEARNING + * Controls whether a given port will learn *source* MAC addresses from + * received traffic or not. Also controls whether dynamic FDB entries + * (which can also be added by software) will be refreshed by incoming + * traffic. By default this flag is on. + * + * @IFLA_BRPORT_UNICAST_FLOOD + * Controls whether unicast traffic for which there is no FDB entry will + * be flooded towards this port. By default this flag is on. + * + * @IFLA_BRPORT_PROXYARP + * Enable proxy ARP on this port. + * + * @IFLA_BRPORT_LEARNING_SYNC + * Controls whether a given port will sync MAC addresses learned on device + * port to bridge FDB. + * + * @IFLA_BRPORT_PROXYARP_WIFI + * Enable proxy ARP on this port which meets extended requirements by + * IEEE 802.11 and Hotspot 2.0 specifications. + * + * @IFLA_BRPORT_ROOT_ID + * + * @IFLA_BRPORT_BRIDGE_ID + * + * @IFLA_BRPORT_DESIGNATED_PORT + * + * @IFLA_BRPORT_DESIGNATED_COST + * + * @IFLA_BRPORT_ID + * + * @IFLA_BRPORT_NO + * + * @IFLA_BRPORT_TOPOLOGY_CHANGE_ACK + * + * @IFLA_BRPORT_CONFIG_PENDING + * + * @IFLA_BRPORT_MESSAGE_AGE_TIMER + * + * @IFLA_BRPORT_FORWARD_DELAY_TIMER + * + * @IFLA_BRPORT_HOLD_TIMER + * + * @IFLA_BRPORT_FLUSH + * Flush bridge ports' fdb dynamic entries. + * + * @IFLA_BRPORT_MULTICAST_ROUTER + * Configure the port's multicast router presence. A port with + * a multicast router will receive all multicast traffic. + * The valid values are: + * + * * 0 disable multicast routers on this port + * * 1 let the system detect the presence of routers (default) + * * 2 permanently enable multicast traffic forwarding on this port + * * 3 enable multicast routers temporarily on this port, not depending + * on incoming queries. + * + * @IFLA_BRPORT_PAD + * + * @IFLA_BRPORT_MCAST_FLOOD + * Controls whether a given port will flood multicast traffic for which + * there is no MDB entry. By default this flag is on. + * + * @IFLA_BRPORT_MCAST_TO_UCAST + * Controls whether a given port will replicate packets using unicast + * instead of multicast. By default this flag is off. + * + * This is done by copying the packet per host and changing the multicast + * destination MAC to a unicast one accordingly. + * + * *mcast_to_unicast* works on top of the multicast snooping feature of the + * bridge. Which means unicast copies are only delivered to hosts which + * are interested in unicast and signaled this via IGMP/MLD reports previously. + * + * This feature is intended for interface types which have a more reliable + * and/or efficient way to deliver unicast packets than broadcast ones + * (e.g. WiFi). + * + * However, it should only be enabled on interfaces where no IGMPv2/MLDv1 + * report suppression takes place. IGMP/MLD report suppression issue is + * usually overcome by the network daemon (supplicant) enabling AP isolation + * and by that separating all STAs. + * + * Delivery of STA-to-STA IP multicast is made possible again by enabling + * and utilizing the bridge hairpin mode, which considers the incoming port + * as a potential outgoing port, too (see *BRIDGE_MODE_HAIRPIN* option). + * Hairpin mode is performed after multicast snooping, therefore leading + * to only deliver reports to STAs running a multicast router. + * + * @IFLA_BRPORT_VLAN_TUNNEL + * Controls whether vlan to tunnel mapping is enabled on the port. + * By default this flag is off. + * + * @IFLA_BRPORT_BCAST_FLOOD + * Controls flooding of broadcast traffic on the given port. By default + * this flag is on. + * + * @IFLA_BRPORT_GROUP_FWD_MASK + * Set the group forward mask. This is a bitmask that is applied to + * decide whether to forward incoming frames destined to link-local + * addresses. The addresses of the form are 01:80:C2:00:00:0X (defaults + * to 0, which means the bridge does not forward any link-local frames + * coming on this port). + * + * @IFLA_BRPORT_NEIGH_SUPPRESS + * Controls whether neighbor discovery (arp and nd) proxy and suppression + * is enabled on the port. By default this flag is off. + * + * @IFLA_BRPORT_ISOLATED + * Controls whether a given port will be isolated, which means it will be + * able to communicate with non-isolated ports only. By default this + * flag is off. + * + * @IFLA_BRPORT_BACKUP_PORT + * Set a backup port. If the port loses carrier all traffic will be + * redirected to the configured backup port. Set the value to 0 to disable + * it. + * + * @IFLA_BRPORT_MRP_RING_OPEN + * + * @IFLA_BRPORT_MRP_IN_OPEN + * + * @IFLA_BRPORT_MCAST_EHT_HOSTS_LIMIT + * The number of per-port EHT hosts limit. The default value is 512. + * Setting to 0 is not allowed. + * + * @IFLA_BRPORT_MCAST_EHT_HOSTS_CNT + * The current number of tracked hosts, read only. + * + * @IFLA_BRPORT_LOCKED + * Controls whether a port will be locked, meaning that hosts behind the + * port will not be able to communicate through the port unless an FDB + * entry with the unit's MAC address is in the FDB. The common use case is + * that hosts are allowed access through authentication with the IEEE 802.1X + * protocol or based on whitelists. By default this flag is off. + * + * Please note that secure 802.1X deployments should always use the + * *BR_BOOLOPT_NO_LL_LEARN* flag, to not permit the bridge to populate its + * FDB based on link-local (EAPOL) traffic received on the port. + * + * @IFLA_BRPORT_MAB + * Controls whether a port will use MAC Authentication Bypass (MAB), a + * technique through which select MAC addresses may be allowed on a locked + * port, without using 802.1X authentication. Packets with an unknown source + * MAC address generates a "locked" FDB entry on the incoming bridge port. + * The common use case is for user space to react to these bridge FDB + * notifications and optionally replace the locked FDB entry with a normal + * one, allowing traffic to pass for whitelisted MAC addresses. + * + * Setting this flag also requires *IFLA_BRPORT_LOCKED* and + * *IFLA_BRPORT_LEARNING*. *IFLA_BRPORT_LOCKED* ensures that unauthorized + * data packets are dropped, and *IFLA_BRPORT_LEARNING* allows the dynamic + * FDB entries installed by user space (as replacements for the locked FDB + * entries) to be refreshed and/or aged out. + * + * @IFLA_BRPORT_MCAST_N_GROUPS + * + * @IFLA_BRPORT_MCAST_MAX_GROUPS + * Sets the maximum number of MDB entries that can be registered for a + * given port. Attempts to register more MDB entries at the port than this + * limit allows will be rejected, whether they are done through netlink + * (e.g. the bridge tool), or IGMP or MLD membership reports. Setting a + * limit of 0 disables the limit. The default value is 0. + * + * @IFLA_BRPORT_NEIGH_VLAN_SUPPRESS + * Controls whether neighbor discovery (arp and nd) proxy and suppression is + * enabled for a given port. By default this flag is off. + * + * Note that this option only takes effect when *IFLA_BRPORT_NEIGH_SUPPRESS* + * is enabled for a given port. + * + * @IFLA_BRPORT_BACKUP_NHID + * The FDB nexthop object ID to attach to packets being redirected to a + * backup port that has VLAN tunnel mapping enabled (via the + * *IFLA_BRPORT_VLAN_TUNNEL* option). Setting a value of 0 (default) has + * the effect of not attaching any ID. + */ enum { IFLA_BRPORT_UNSPEC, IFLA_BRPORT_STATE, /* Spanning tree state */ @@ -564,6 +1090,10 @@ enum { IFLA_BRPORT_MCAST_EHT_HOSTS_CNT, IFLA_BRPORT_LOCKED, IFLA_BRPORT_MAB, + IFLA_BRPORT_MCAST_N_GROUPS, + IFLA_BRPORT_MCAST_MAX_GROUPS, + IFLA_BRPORT_NEIGH_VLAN_SUPPRESS, + IFLA_BRPORT_BACKUP_NHID, __IFLA_BRPORT_MAX }; #define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) @@ -630,6 +1160,7 @@ enum { IFLA_MACVLAN_MACADDR_COUNT, IFLA_MACVLAN_BC_QUEUE_LEN, IFLA_MACVLAN_BC_QUEUE_LEN_USED, + IFLA_MACVLAN_BC_CUTOFF, __IFLA_MACVLAN_MAX, }; @@ -748,6 +1279,30 @@ struct tunnel_msg { __u32 ifindex; }; +/* netkit section */ +enum netkit_action { + NETKIT_NEXT = -1, + NETKIT_PASS = 0, + NETKIT_DROP = 2, + NETKIT_REDIRECT = 7, +}; + +enum netkit_mode { + NETKIT_L2, + NETKIT_L3, +}; + +enum { + IFLA_NETKIT_UNSPEC, + IFLA_NETKIT_PEER_INFO, + IFLA_NETKIT_PRIMARY, + IFLA_NETKIT_POLICY, + IFLA_NETKIT_PEER_POLICY, + IFLA_NETKIT_MODE, + __IFLA_NETKIT_MAX, +}; +#define IFLA_NETKIT_MAX (__IFLA_NETKIT_MAX - 1) + /* VXLAN section */ /* include statistics in the dump */ @@ -821,6 +1376,8 @@ enum { IFLA_VXLAN_TTL_INHERIT, IFLA_VXLAN_DF, IFLA_VXLAN_VNIFILTER, /* only applicable with COLLECT_METADATA mode */ + IFLA_VXLAN_LOCALBYPASS, + IFLA_VXLAN_LABEL_POLICY, /* IPv6 flow label policy; ifla_vxlan_label_policy */ __IFLA_VXLAN_MAX }; #define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) @@ -838,6 +1395,13 @@ enum ifla_vxlan_df { VXLAN_DF_MAX = __VXLAN_DF_END - 1, }; +enum ifla_vxlan_label_policy { + VXLAN_LABEL_FIXED = 0, + VXLAN_LABEL_INHERIT = 1, + __VXLAN_LABEL_END, + VXLAN_LABEL_MAX = __VXLAN_LABEL_END - 1, +}; + /* GENEVE section */ enum { IFLA_GENEVE_UNSPEC, @@ -941,6 +1505,7 @@ enum { IFLA_BOND_AD_LACP_ACTIVE, IFLA_BOND_MISSED_MAX, IFLA_BOND_NS_IP6_TARGET, + IFLA_BOND_COUPLED_CONTROL, __IFLA_BOND_MAX, }; @@ -1383,7 +1948,9 @@ enum { enum { IFLA_DSA_UNSPEC, - IFLA_DSA_MASTER, + IFLA_DSA_CONDUIT, + /* Deprecated, use IFLA_DSA_CONDUIT instead */ + IFLA_DSA_MASTER = IFLA_DSA_CONDUIT, __IFLA_DSA_MAX, }; diff --git a/src/basic/linux/in.h b/src/basic/linux/in.h index 07a4cb1..e682ab6 100644 --- a/src/basic/linux/in.h +++ b/src/basic/linux/in.h @@ -162,6 +162,8 @@ struct in_addr { #define MCAST_MSFILTER 48 #define IP_MULTICAST_ALL 49 #define IP_UNICAST_IF 50 +#define IP_LOCAL_PORT_RANGE 51 +#define IP_PROTOCOL 52 #define MCAST_EXCLUDE 0 #define MCAST_INCLUDE 1 diff --git a/src/basic/linux/in6.h b/src/basic/linux/in6.h index c4c53a9..ff8d21f 100644 --- a/src/basic/linux/in6.h +++ b/src/basic/linux/in6.h @@ -145,7 +145,7 @@ struct in6_flowlabel_req { #define IPV6_TLV_PADN 1 #define IPV6_TLV_ROUTERALERT 5 #define IPV6_TLV_CALIPSO 7 /* RFC 5570 */ -#define IPV6_TLV_IOAM 49 /* TEMPORARY IANA allocation for IOAM */ +#define IPV6_TLV_IOAM 49 /* RFC 9486 */ #define IPV6_TLV_JUMBO 194 #define IPV6_TLV_HAO 201 /* home address option */ diff --git a/src/basic/linux/magic.h b/src/basic/linux/magic.h new file mode 100644 index 0000000..1b40a96 --- /dev/null +++ b/src/basic/linux/magic.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_MAGIC_H__ +#define __LINUX_MAGIC_H__ + +#define ADFS_SUPER_MAGIC 0xadf5 +#define AFFS_SUPER_MAGIC 0xadff +#define AFS_SUPER_MAGIC 0x5346414F +#define AUTOFS_SUPER_MAGIC 0x0187 +#define CEPH_SUPER_MAGIC 0x00c36400 +#define CODA_SUPER_MAGIC 0x73757245 +#define CRAMFS_MAGIC 0x28cd3d45 /* some random number */ +#define CRAMFS_MAGIC_WEND 0x453dcd28 /* magic number with the wrong endianess */ +#define DEBUGFS_MAGIC 0x64626720 +#define SECURITYFS_MAGIC 0x73636673 +#define SELINUX_MAGIC 0xf97cff8c +#define SMACK_MAGIC 0x43415d53 /* "SMAC" */ +#define RAMFS_MAGIC 0x858458f6 /* some random number */ +#define TMPFS_MAGIC 0x01021994 +#define HUGETLBFS_MAGIC 0x958458f6 /* some random number */ +#define SQUASHFS_MAGIC 0x73717368 +#define ECRYPTFS_SUPER_MAGIC 0xf15f +#define EFS_SUPER_MAGIC 0x414A53 +#define EROFS_SUPER_MAGIC_V1 0xE0F5E1E2 +#define EXT2_SUPER_MAGIC 0xEF53 +#define EXT3_SUPER_MAGIC 0xEF53 +#define XENFS_SUPER_MAGIC 0xabba1974 +#define EXT4_SUPER_MAGIC 0xEF53 +#define BTRFS_SUPER_MAGIC 0x9123683E +#define NILFS_SUPER_MAGIC 0x3434 +#define F2FS_SUPER_MAGIC 0xF2F52010 +#define HPFS_SUPER_MAGIC 0xf995e849 +#define ISOFS_SUPER_MAGIC 0x9660 +#define JFFS2_SUPER_MAGIC 0x72b6 +#define XFS_SUPER_MAGIC 0x58465342 /* "XFSB" */ +#define PSTOREFS_MAGIC 0x6165676C +#define EFIVARFS_MAGIC 0xde5e81e4 +#define HOSTFS_SUPER_MAGIC 0x00c0ffee +#define OVERLAYFS_SUPER_MAGIC 0x794c7630 +#define FUSE_SUPER_MAGIC 0x65735546 + +#define MINIX_SUPER_MAGIC 0x137F /* minix v1 fs, 14 char names */ +#define MINIX_SUPER_MAGIC2 0x138F /* minix v1 fs, 30 char names */ +#define MINIX2_SUPER_MAGIC 0x2468 /* minix v2 fs, 14 char names */ +#define MINIX2_SUPER_MAGIC2 0x2478 /* minix v2 fs, 30 char names */ +#define MINIX3_SUPER_MAGIC 0x4d5a /* minix v3 fs, 60 char names */ + +#define MSDOS_SUPER_MAGIC 0x4d44 /* MD */ +#define EXFAT_SUPER_MAGIC 0x2011BAB0 +#define NCP_SUPER_MAGIC 0x564c /* Guess, what 0x564c is :-) */ +#define NFS_SUPER_MAGIC 0x6969 +#define OCFS2_SUPER_MAGIC 0x7461636f +#define OPENPROM_SUPER_MAGIC 0x9fa1 +#define QNX4_SUPER_MAGIC 0x002f /* qnx4 fs detection */ +#define QNX6_SUPER_MAGIC 0x68191122 /* qnx6 fs detection */ +#define AFS_FS_MAGIC 0x6B414653 + + +#define REISERFS_SUPER_MAGIC 0x52654973 /* used by gcc */ + /* used by file system utilities that + look at the superblock, etc. */ +#define REISERFS_SUPER_MAGIC_STRING "ReIsErFs" +#define REISER2FS_SUPER_MAGIC_STRING "ReIsEr2Fs" +#define REISER2FS_JR_SUPER_MAGIC_STRING "ReIsEr3Fs" + +#define SMB_SUPER_MAGIC 0x517B +#define CIFS_SUPER_MAGIC 0xFF534D42 /* the first four bytes of SMB PDUs */ +#define SMB2_SUPER_MAGIC 0xFE534D42 + +#define CGROUP_SUPER_MAGIC 0x27e0eb +#define CGROUP2_SUPER_MAGIC 0x63677270 + +#define RDTGROUP_SUPER_MAGIC 0x7655821 + +#define STACK_END_MAGIC 0x57AC6E9D + +#define TRACEFS_MAGIC 0x74726163 + +#define V9FS_MAGIC 0x01021997 + +#define BDEVFS_MAGIC 0x62646576 +#define DAXFS_MAGIC 0x64646178 +#define BINFMTFS_MAGIC 0x42494e4d +#define DEVPTS_SUPER_MAGIC 0x1cd1 +#define BINDERFS_SUPER_MAGIC 0x6c6f6f70 +#define FUTEXFS_SUPER_MAGIC 0xBAD1DEA +#define PIPEFS_MAGIC 0x50495045 +#define PROC_SUPER_MAGIC 0x9fa0 +#define SOCKFS_MAGIC 0x534F434B +#define SYSFS_MAGIC 0x62656572 +#define USBDEVICE_SUPER_MAGIC 0x9fa2 +#define MTD_INODE_FS_MAGIC 0x11307854 +#define ANON_INODE_FS_MAGIC 0x09041934 +#define BTRFS_TEST_MAGIC 0x73727279 +#define NSFS_MAGIC 0x6e736673 +#define BPF_FS_MAGIC 0xcafe4a11 +#define AAFS_MAGIC 0x5a3c69f0 +#define ZONEFS_MAGIC 0x5a4f4653 + +/* Since UDF 2.01 is ISO 13346 based... */ +#define UDF_SUPER_MAGIC 0x15013346 +#define DMA_BUF_MAGIC 0x444d4142 /* "DMAB" */ +#define DEVMEM_MAGIC 0x454d444d /* "DMEM" */ +#define SECRETMEM_MAGIC 0x5345434d /* "SECM" */ +#define PID_FS_MAGIC 0x50494446 /* "PIDF" */ + +#endif /* __LINUX_MAGIC_H__ */ diff --git a/src/basic/linux/netfilter.h b/src/basic/linux/netfilter.h new file mode 100644 index 0000000..30c045b --- /dev/null +++ b/src/basic/linux/netfilter.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __LINUX_NETFILTER_H +#define __LINUX_NETFILTER_H + +#include + +#include +#include + +/* Responses from hook functions. */ +#define NF_DROP 0 +#define NF_ACCEPT 1 +#define NF_STOLEN 2 +#define NF_QUEUE 3 +#define NF_REPEAT 4 +#define NF_STOP 5 /* Deprecated, for userspace nf_queue compatibility. */ +#define NF_MAX_VERDICT NF_STOP + +/* we overload the higher bits for encoding auxiliary data such as the queue + * number or errno values. Not nice, but better than additional function + * arguments. */ +#define NF_VERDICT_MASK 0x000000ff + +/* extra verdict flags have mask 0x0000ff00 */ +#define NF_VERDICT_FLAG_QUEUE_BYPASS 0x00008000 + +/* queue number (NF_QUEUE) or errno (NF_DROP) */ +#define NF_VERDICT_QMASK 0xffff0000 +#define NF_VERDICT_QBITS 16 + +#define NF_QUEUE_NR(x) ((((x) << 16) & NF_VERDICT_QMASK) | NF_QUEUE) + +#define NF_DROP_ERR(x) (((-x) << 16) | NF_DROP) + +/* only for userspace compatibility */ + +/* NF_VERDICT_BITS should be 8 now, but userspace might break if this changes */ +#define NF_VERDICT_BITS 16 + +enum nf_inet_hooks { + NF_INET_PRE_ROUTING, + NF_INET_LOCAL_IN, + NF_INET_FORWARD, + NF_INET_LOCAL_OUT, + NF_INET_POST_ROUTING, + NF_INET_NUMHOOKS, + NF_INET_INGRESS = NF_INET_NUMHOOKS, +}; + +enum nf_dev_hooks { + NF_NETDEV_INGRESS, + NF_NETDEV_EGRESS, + NF_NETDEV_NUMHOOKS +}; + +enum { + NFPROTO_UNSPEC = 0, + NFPROTO_INET = 1, + NFPROTO_IPV4 = 2, + NFPROTO_ARP = 3, + NFPROTO_NETDEV = 5, + NFPROTO_BRIDGE = 7, + NFPROTO_IPV6 = 10, + NFPROTO_DECNET = 12, + NFPROTO_NUMPROTO, +}; + +union nf_inet_addr { + __u32 all[4]; + __be32 ip; + __be32 ip6[4]; + struct in_addr in; + struct in6_addr in6; +}; + +#endif /* __LINUX_NETFILTER_H */ diff --git a/src/basic/linux/netfilter/nf_tables.h b/src/basic/linux/netfilter/nf_tables.h index cfa844d..aa4094c 100644 --- a/src/basic/linux/netfilter/nf_tables.h +++ b/src/basic/linux/netfilter/nf_tables.h @@ -98,6 +98,14 @@ enum nft_verdicts { * @NFT_MSG_GETFLOWTABLE: get flow table (enum nft_flowtable_attributes) * @NFT_MSG_DELFLOWTABLE: delete flow table (enum nft_flowtable_attributes) * @NFT_MSG_GETRULE_RESET: get rules and reset stateful expressions (enum nft_obj_attributes) + * @NFT_MSG_DESTROYTABLE: destroy a table (enum nft_table_attributes) + * @NFT_MSG_DESTROYCHAIN: destroy a chain (enum nft_chain_attributes) + * @NFT_MSG_DESTROYRULE: destroy a rule (enum nft_rule_attributes) + * @NFT_MSG_DESTROYSET: destroy a set (enum nft_set_attributes) + * @NFT_MSG_DESTROYSETELEM: destroy a set element (enum nft_set_elem_attributes) + * @NFT_MSG_DESTROYOBJ: destroy a stateful object (enum nft_object_attributes) + * @NFT_MSG_DESTROYFLOWTABLE: destroy flow table (enum nft_flowtable_attributes) + * @NFT_MSG_GETSETELEM_RESET: get set elements and reset attached stateful expressions (enum nft_set_elem_attributes) */ enum nf_tables_msg_types { NFT_MSG_NEWTABLE, @@ -126,6 +134,14 @@ enum nf_tables_msg_types { NFT_MSG_GETFLOWTABLE, NFT_MSG_DELFLOWTABLE, NFT_MSG_GETRULE_RESET, + NFT_MSG_DESTROYTABLE, + NFT_MSG_DESTROYCHAIN, + NFT_MSG_DESTROYRULE, + NFT_MSG_DESTROYSET, + NFT_MSG_DESTROYSETELEM, + NFT_MSG_DESTROYOBJ, + NFT_MSG_DESTROYFLOWTABLE, + NFT_MSG_GETSETELEM_RESET, NFT_MSG_MAX, }; @@ -163,13 +179,17 @@ enum nft_hook_attributes { * enum nft_table_flags - nf_tables table flags * * @NFT_TABLE_F_DORMANT: this table is not active + * @NFT_TABLE_F_OWNER: this table is owned by a process + * @NFT_TABLE_F_PERSIST: this table shall outlive its owner */ enum nft_table_flags { NFT_TABLE_F_DORMANT = 0x1, NFT_TABLE_F_OWNER = 0x2, + NFT_TABLE_F_PERSIST = 0x4, }; #define NFT_TABLE_F_MASK (NFT_TABLE_F_DORMANT | \ - NFT_TABLE_F_OWNER) + NFT_TABLE_F_OWNER | \ + NFT_TABLE_F_PERSIST) /** * enum nft_table_attributes - nf_tables table netlink attributes @@ -247,6 +267,7 @@ enum nft_chain_attributes { * @NFTA_RULE_USERDATA: user data (NLA_BINARY, NFT_USERDATA_MAXLEN) * @NFTA_RULE_ID: uniquely identifies a rule in a transaction (NLA_U32) * @NFTA_RULE_POSITION_ID: transaction unique identifier of the previous rule (NLA_U32) + * @NFTA_RULE_CHAIN_ID: add the rule to chain by ID, alternative to @NFTA_RULE_CHAIN (NLA_U32) */ enum nft_rule_attributes { NFTA_RULE_UNSPEC, @@ -268,9 +289,11 @@ enum nft_rule_attributes { /** * enum nft_rule_compat_flags - nf_tables rule compat flags * + * @NFT_RULE_COMPAT_F_UNUSED: unused * @NFT_RULE_COMPAT_F_INV: invert the check result */ enum nft_rule_compat_flags { + NFT_RULE_COMPAT_F_UNUSED = (1 << 0), NFT_RULE_COMPAT_F_INV = (1 << 1), NFT_RULE_COMPAT_F_MASK = NFT_RULE_COMPAT_F_INV, }; @@ -671,7 +694,7 @@ enum nft_range_ops { * enum nft_range_attributes - nf_tables range expression netlink attributes * * @NFTA_RANGE_SREG: source register of data to compare (NLA_U32: nft_registers) - * @NFTA_RANGE_OP: cmp operation (NLA_U32: nft_cmp_ops) + * @NFTA_RANGE_OP: cmp operation (NLA_U32: nft_range_ops) * @NFTA_RANGE_FROM_DATA: data range from (NLA_NESTED: nft_data_attributes) * @NFTA_RANGE_TO_DATA: data range to (NLA_NESTED: nft_data_attributes) */ @@ -845,12 +868,14 @@ enum nft_exthdr_flags { * @NFT_EXTHDR_OP_TCP: match against tcp options * @NFT_EXTHDR_OP_IPV4: match against ipv4 options * @NFT_EXTHDR_OP_SCTP: match against sctp chunks + * @NFT_EXTHDR_OP_DCCP: match against dccp otions */ enum nft_exthdr_op { NFT_EXTHDR_OP_IPV6, NFT_EXTHDR_OP_TCPOPT, NFT_EXTHDR_OP_IPV4, NFT_EXTHDR_OP_SCTP, + NFT_EXTHDR_OP_DCCP, __NFT_EXTHDR_OP_MAX }; #define NFT_EXTHDR_OP_MAX (__NFT_EXTHDR_OP_MAX - 1) @@ -864,7 +889,7 @@ enum nft_exthdr_op { * @NFTA_EXTHDR_LEN: extension header length (NLA_U32) * @NFTA_EXTHDR_FLAGS: extension header flags (NLA_U32) * @NFTA_EXTHDR_OP: option match type (NLA_U32) - * @NFTA_EXTHDR_SREG: option match type (NLA_U32) + * @NFTA_EXTHDR_SREG: source register (NLA_U32: nft_registers) */ enum nft_exthdr_attributes { NFTA_EXTHDR_UNSPEC, @@ -917,6 +942,7 @@ enum nft_exthdr_attributes { * @NFT_META_TIME_HOUR: hour of day (in seconds) * @NFT_META_SDIF: slave device interface index * @NFT_META_SDIFNAME: slave device interface name + * @NFT_META_BRI_BROUTE: packet br_netfilter_broute bit */ enum nft_meta_keys { NFT_META_LEN, @@ -955,6 +981,7 @@ enum nft_meta_keys { NFT_META_TIME_HOUR, NFT_META_SDIF, NFT_META_SDIFNAME, + NFT_META_BRI_BROUTE, __NFT_META_IIFTYPE, }; @@ -1246,10 +1273,10 @@ enum nft_last_attributes { /** * enum nft_log_attributes - nf_tables log expression netlink attributes * - * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U32) + * @NFTA_LOG_GROUP: netlink group to send messages to (NLA_U16) * @NFTA_LOG_PREFIX: prefix to prepend to log messages (NLA_STRING) * @NFTA_LOG_SNAPLEN: length of payload to include in netlink message (NLA_U32) - * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U32) + * @NFTA_LOG_QTHRESHOLD: queue threshold (NLA_U16) * @NFTA_LOG_LEVEL: log level (NLA_U32) * @NFTA_LOG_FLAGS: logging flags (NLA_U32) */ diff --git a/src/basic/linux/netlink.h b/src/basic/linux/netlink.h index e2ae82e..f87aaf2 100644 --- a/src/basic/linux/netlink.h +++ b/src/basic/linux/netlink.h @@ -298,6 +298,8 @@ struct nla_bitfield32 { * entry has attributes again, the policy for those inner ones * and the corresponding maxtype may be specified. * @NL_ATTR_TYPE_BITFIELD32: &struct nla_bitfield32 attribute + * @NL_ATTR_TYPE_SINT: 32-bit or 64-bit signed attribute, aligned to 4B + * @NL_ATTR_TYPE_UINT: 32-bit or 64-bit unsigned attribute, aligned to 4B */ enum netlink_attribute_type { NL_ATTR_TYPE_INVALID, @@ -322,6 +324,9 @@ enum netlink_attribute_type { NL_ATTR_TYPE_NESTED_ARRAY, NL_ATTR_TYPE_BITFIELD32, + + NL_ATTR_TYPE_SINT, + NL_ATTR_TYPE_UINT, }; /** diff --git a/src/basic/linux/nexthop.h b/src/basic/linux/nexthop.h index d8ffa8c..dd8787f 100644 --- a/src/basic/linux/nexthop.h +++ b/src/basic/linux/nexthop.h @@ -30,6 +30,9 @@ enum { #define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1) +#define NHA_OP_FLAG_DUMP_STATS BIT(0) +#define NHA_OP_FLAG_DUMP_HW_STATS BIT(1) + enum { NHA_UNSPEC, NHA_ID, /* u32; id for nexthop. id == 0 means auto-assign */ @@ -60,6 +63,18 @@ enum { /* nested; nexthop bucket attributes */ NHA_RES_BUCKET, + /* u32; operation-specific flags */ + NHA_OP_FLAGS, + + /* nested; nexthop group stats */ + NHA_GROUP_STATS, + + /* u32; nexthop hardware stats enable */ + NHA_HW_STATS_ENABLE, + + /* u32; read-only; whether any driver collects HW stats */ + NHA_HW_STATS_USED, + __NHA_MAX, }; @@ -101,4 +116,34 @@ enum { #define NHA_RES_BUCKET_MAX (__NHA_RES_BUCKET_MAX - 1) +enum { + NHA_GROUP_STATS_UNSPEC, + + /* nested; nexthop group entry stats */ + NHA_GROUP_STATS_ENTRY, + + __NHA_GROUP_STATS_MAX, +}; + +#define NHA_GROUP_STATS_MAX (__NHA_GROUP_STATS_MAX - 1) + +enum { + NHA_GROUP_STATS_ENTRY_UNSPEC, + + /* u32; nexthop id of the nexthop group entry */ + NHA_GROUP_STATS_ENTRY_ID, + + /* uint; number of packets forwarded via the nexthop group entry */ + NHA_GROUP_STATS_ENTRY_PACKETS, + + /* uint; number of packets forwarded via the nexthop group entry in + * hardware + */ + NHA_GROUP_STATS_ENTRY_PACKETS_HW, + + __NHA_GROUP_STATS_ENTRY_MAX, +}; + +#define NHA_GROUP_STATS_ENTRY_MAX (__NHA_GROUP_STATS_ENTRY_MAX - 1) + #endif diff --git a/src/basic/linux/nl80211.h b/src/basic/linux/nl80211.h index c14a91b..f23ecbd 100644 --- a/src/basic/linux/nl80211.h +++ b/src/basic/linux/nl80211.h @@ -11,7 +11,7 @@ * Copyright 2008 Jouni Malinen * Copyright 2008 Colin McCabe * Copyright 2015-2017 Intel Deutschland GmbH - * Copyright (C) 2018-2022 Intel Corporation + * Copyright (C) 2018-2024 Intel Corporation * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -72,7 +72,7 @@ * For drivers supporting TDLS with external setup (WIPHY_FLAG_SUPPORTS_TDLS * and WIPHY_FLAG_TDLS_EXTERNAL_SETUP), the station lifetime is as follows: * - a setup station entry is added, not yet authorized, without any rate - * or capability information, this just exists to avoid race conditions + * or capability information; this just exists to avoid race conditions * - when the TDLS setup is done, a single NL80211_CMD_SET_STATION is valid * to add rate and capability information to the station and at the same * time mark it authorized. @@ -87,7 +87,7 @@ * DOC: Frame transmission/registration support * * Frame transmission and registration support exists to allow userspace - * management entities such as wpa_supplicant react to management frames + * management entities such as wpa_supplicant to react to management frames * that are not being handled by the kernel. This includes, for example, * certain classes of action frames that cannot be handled in the kernel * for various reasons. @@ -113,7 +113,7 @@ * * Frame transmission allows userspace to send for example the required * responses to action frames. It is subject to some sanity checking, - * but many frames can be transmitted. When a frame was transmitted, its + * but many frames can be transmitted. When a frame is transmitted, its * status is indicated to the sending socket. * * For more technical details, see the corresponding command descriptions @@ -123,7 +123,7 @@ /** * DOC: Virtual interface / concurrency capabilities * - * Some devices are able to operate with virtual MACs, they can have + * Some devices are able to operate with virtual MACs; they can have * more than one virtual interface. The capability handling for this * is a bit complex though, as there may be a number of restrictions * on the types of concurrency that are supported. @@ -135,7 +135,7 @@ * Once concurrency is desired, more attributes must be observed: * To start with, since some interface types are purely managed in * software, like the AP-VLAN type in mac80211 for example, there's - * an additional list of these, they can be added at any time and + * an additional list of these; they can be added at any time and * are only restricted by some semantic restrictions (e.g. AP-VLAN * cannot be added without a corresponding AP interface). This list * is exported in the %NL80211_ATTR_SOFTWARE_IFTYPES attribute. @@ -164,17 +164,17 @@ * Packet coalesce feature helps to reduce number of received interrupts * to host by buffering these packets in firmware/hardware for some * predefined time. Received interrupt will be generated when one of the - * following events occur. + * following events occurs. * a) Expiration of hardware timer whose expiration time is set to maximum * coalescing delay of matching coalesce rule. - * b) Coalescing buffer in hardware reaches it's limit. + * b) Coalescing buffer in hardware reaches its limit. * c) Packet doesn't match any of the configured coalesce rules. * * User needs to configure following parameters for creating a coalesce * rule. * a) Maximum coalescing delay * b) List of packet patterns which needs to be matched - * c) Condition for coalescence. pattern 'match' or 'no match' + * c) Condition for coalescence: pattern 'match' or 'no match' * Multiple such rules can be created. */ @@ -213,7 +213,7 @@ /** * DOC: FILS shared key authentication offload * - * FILS shared key authentication offload can be advertized by drivers by + * FILS shared key authentication offload can be advertised by drivers by * setting @NL80211_EXT_FEATURE_FILS_SK_OFFLOAD flag. The drivers that support * FILS shared key authentication offload should be able to construct the * authentication and association frames for FILS shared key authentication and @@ -239,7 +239,7 @@ * The PMKSA can be maintained in userspace persistently so that it can be used * later after reboots or wifi turn off/on also. * - * %NL80211_ATTR_FILS_CACHE_ID is the cache identifier advertized by a FILS + * %NL80211_ATTR_FILS_CACHE_ID is the cache identifier advertised by a FILS * capable AP supporting PMK caching. It specifies the scope within which the * PMKSAs are cached in an ESS. %NL80211_CMD_SET_PMKSA and * %NL80211_CMD_DEL_PMKSA are enhanced to allow support for PMKSA caching based @@ -290,12 +290,12 @@ * If the configuration needs to be applied for specific peer then the MAC * address of the peer needs to be passed in %NL80211_ATTR_MAC, otherwise the * configuration will be applied for all the connected peers in the vif except - * any peers that have peer specific configuration for the TID by default; if - * the %NL80211_TID_CONFIG_ATTR_OVERRIDE flag is set, peer specific values + * any peers that have peer-specific configuration for the TID by default; if + * the %NL80211_TID_CONFIG_ATTR_OVERRIDE flag is set, peer-specific values * will be overwritten. * - * All this configuration is valid only for STA's current connection - * i.e. the configuration will be reset to default when the STA connects back + * All this configuration is valid only for STA's current connection, + * i.e., the configuration will be reset to default when the STA connects back * after disconnection/roaming, and this configuration will be cleared when * the interface goes down. */ @@ -326,7 +326,7 @@ /** * DOC: Multi-Link Operation * - * In Multi-Link Operation, a connection between to MLDs utilizes multiple + * In Multi-Link Operation, a connection between two MLDs utilizes multiple * links. To use this in nl80211, various commands and responses now need * to or will include the new %NL80211_ATTR_MLO_LINKS attribute. * Additionally, various commands that need to operate on a specific link @@ -334,6 +334,15 @@ * use %NL80211_CMD_START_AP or similar functions. */ +/** + * DOC: OWE DH IE handling offload + * + * By setting @NL80211_EXT_FEATURE_OWE_OFFLOAD flag, drivers can indicate + * kernel/application space to avoid DH IE handling. When this flag is + * advertised, the driver/device will take care of DH IE inclusion and + * processing of peer DH IE to generate PMK. + */ + /** * enum nl80211_commands - supported nl80211 commands * @@ -424,11 +433,13 @@ * interface identified by %NL80211_ATTR_IFINDEX. * @NL80211_CMD_DEL_STATION: Remove a station identified by %NL80211_ATTR_MAC * or, if no MAC address given, all stations, on the interface identified - * by %NL80211_ATTR_IFINDEX. %NL80211_ATTR_MGMT_SUBTYPE and + * by %NL80211_ATTR_IFINDEX. For MLD station, MLD address is used in + * %NL80211_ATTR_MAC. %NL80211_ATTR_MGMT_SUBTYPE and * %NL80211_ATTR_REASON_CODE can optionally be used to specify which type * of disconnection indication should be sent to the station * (Deauthentication or Disassociation frame and reason code for that - * frame). + * frame). %NL80211_ATTR_MLO_LINK_ID can be used optionally to remove + * stations connected and using at least that link as one of its links. * * @NL80211_CMD_GET_MPATH: Get mesh path attributes for mesh path to * destination %NL80211_ATTR_MAC on the interface identified by @@ -511,7 +522,7 @@ * %NL80211_ATTR_SCHED_SCAN_PLANS. If %NL80211_ATTR_SCHED_SCAN_PLANS is * not specified and only %NL80211_ATTR_SCHED_SCAN_INTERVAL is specified, * scheduled scan will run in an infinite loop with the specified interval. - * These attributes are mutually exculsive, + * These attributes are mutually exclusive, * i.e. NL80211_ATTR_SCHED_SCAN_INTERVAL must not be passed if * NL80211_ATTR_SCHED_SCAN_PLANS is defined. * If for some reason scheduled scan is aborted by the driver, all scan @@ -542,7 +553,7 @@ * %NL80211_CMD_STOP_SCHED_SCAN command is received or when the interface * is brought down while a scheduled scan was running. * - * @NL80211_CMD_GET_SURVEY: get survey resuls, e.g. channel occupation + * @NL80211_CMD_GET_SURVEY: get survey results, e.g. channel occupation * or noise level * @NL80211_CMD_NEW_SURVEY_RESULTS: survey data notification (as a reply to * NL80211_CMD_GET_SURVEY and on the "scan" multicast group) @@ -553,12 +564,13 @@ * using %NL80211_ATTR_SSID, %NL80211_ATTR_FILS_CACHE_ID, * %NL80211_ATTR_PMKID, and %NL80211_ATTR_PMK in case of FILS * authentication where %NL80211_ATTR_FILS_CACHE_ID is the identifier - * advertized by a FILS capable AP identifying the scope of PMKSA in an + * advertised by a FILS capable AP identifying the scope of PMKSA in an * ESS. * @NL80211_CMD_DEL_PMKSA: Delete a PMKSA cache entry, using %NL80211_ATTR_MAC * (for the BSSID) and %NL80211_ATTR_PMKID or using %NL80211_ATTR_SSID, * %NL80211_ATTR_FILS_CACHE_ID, and %NL80211_ATTR_PMKID in case of FILS - * authentication. + * authentication. Additionally in case of SAE offload and OWE offloads + * PMKSA entry can be deleted using %NL80211_ATTR_SSID. * @NL80211_CMD_FLUSH_PMKSA: Flush all PMKSA cache entries. * * @NL80211_CMD_REG_CHANGE: indicates to userspace the regulatory domain @@ -597,7 +609,7 @@ * BSSID in case of station mode). %NL80211_ATTR_SSID is used to specify * the SSID (mainly for association, but is included in authentication * request, too, to help BSS selection. %NL80211_ATTR_WIPHY_FREQ + - * %NL80211_ATTR_WIPHY_FREQ_OFFSET is used to specify the frequence of the + * %NL80211_ATTR_WIPHY_FREQ_OFFSET is used to specify the frequency of the * channel in MHz. %NL80211_ATTR_AUTH_TYPE is used to specify the * authentication type. %NL80211_ATTR_IE is used to define IEs * (VendorSpecificInfo, but also including RSN IE and FT IEs) to be added @@ -806,7 +818,7 @@ * reached. * @NL80211_CMD_SET_CHANNEL: Set the channel (using %NL80211_ATTR_WIPHY_FREQ * and the attributes determining channel width) the given interface - * (identifed by %NL80211_ATTR_IFINDEX) shall operate on. + * (identified by %NL80211_ATTR_IFINDEX) shall operate on. * In case multiple channels are supported by the device, the mechanism * with which it switches channels is implementation-defined. * When a monitor interface is given, it can only switch channel while @@ -878,7 +890,7 @@ * inform userspace of the new replay counter. * * @NL80211_CMD_PMKSA_CANDIDATE: This is used as an event to inform userspace - * of PMKSA caching dandidates. + * of PMKSA caching candidates. * * @NL80211_CMD_TDLS_OPER: Perform a high-level TDLS command (e.g. link setup). * In addition, this can be used as an event to request userspace to take @@ -914,7 +926,7 @@ * * @NL80211_CMD_PROBE_CLIENT: Probe an associated station on an AP interface * by sending a null data frame to it and reporting when the frame is - * acknowleged. This is used to allow timing out inactive clients. Uses + * acknowledged. This is used to allow timing out inactive clients. Uses * %NL80211_ATTR_IFINDEX and %NL80211_ATTR_MAC. The command returns a * direct reply with an %NL80211_ATTR_COOKIE that is later used to match * up the event with the request. The event includes the same data and @@ -1125,11 +1137,15 @@ * @NL80211_CMD_DEL_PMK: For offloaded 4-Way handshake, delete the previously * configured PMK for the authenticator address identified by * %NL80211_ATTR_MAC. - * @NL80211_CMD_PORT_AUTHORIZED: An event that indicates an 802.1X FT roam was - * completed successfully. Drivers that support 4 way handshake offload - * should send this event after indicating 802.1X FT assocation with - * %NL80211_CMD_ROAM. If the 4 way handshake failed %NL80211_CMD_DISCONNECT - * should be indicated instead. + * @NL80211_CMD_PORT_AUTHORIZED: An event that indicates port is authorized and + * open for regular data traffic. For STA/P2P-client, this event is sent + * with AP MAC address and for AP/P2P-GO, the event carries the STA/P2P- + * client MAC address. + * Drivers that support 4 way handshake offload should send this event for + * STA/P2P-client after successful 4-way HS or after 802.1X FT following + * NL80211_CMD_CONNECT or NL80211_CMD_ROAM. Drivers using AP/P2P-GO 4-way + * handshake offload should send this event on successful completion of + * 4-way handshake with the peer (STA/P2P-client). * @NL80211_CMD_CONTROL_PORT_FRAME: Control Port (e.g. PAE) frame TX request * and RX notification. This command is used both as a request to transmit * a control port frame and as a notification that a control port frame @@ -1166,6 +1182,23 @@ * %NL80211_ATTR_STATUS_CODE attribute in %NL80211_CMD_EXTERNAL_AUTH * command interface. * + * Host driver sends MLD address of the AP with %NL80211_ATTR_MLD_ADDR in + * %NL80211_CMD_EXTERNAL_AUTH event to indicate user space to enable MLO + * during the authentication offload in STA mode while connecting to MLD + * APs. Host driver should check %NL80211_ATTR_MLO_SUPPORT flag capability + * in %NL80211_CMD_CONNECT to know whether the user space supports enabling + * MLO during the authentication offload or not. + * User space should enable MLO during the authentication only when it + * receives the AP MLD address in authentication offload request. User + * space shouldn't enable MLO when the authentication offload request + * doesn't indicate the AP MLD address even if the AP is MLO capable. + * User space should use %NL80211_ATTR_MLD_ADDR as peer's MLD address and + * interface address identified by %NL80211_ATTR_IFINDEX as self MLD + * address. User space and host driver to use MLD addresses in RA, TA and + * BSSID fields of the frames between them, and host driver translates the + * MLD addresses to/from link addresses based on the link chosen for the + * authentication. + * * Host driver reports this status on an authentication failure to the * user space through the connect result as the user space would have * initiated the connection through the connect request. @@ -1281,6 +1314,26 @@ * @NL80211_CMD_MODIFY_LINK_STA: Modify a link of an MLD station * @NL80211_CMD_REMOVE_LINK_STA: Remove a link of an MLD station * + * @NL80211_CMD_SET_HW_TIMESTAMP: Enable/disable HW timestamping of Timing + * measurement and Fine timing measurement frames. If %NL80211_ATTR_MAC + * is included, enable/disable HW timestamping only for frames to/from the + * specified MAC address. Otherwise enable/disable HW timestamping for + * all TM/FTM frames (including ones that were enabled with specific MAC + * address). If %NL80211_ATTR_HW_TIMESTAMP_ENABLED is not included, disable + * HW timestamping. + * The number of peers that HW timestamping can be enabled for concurrently + * is indicated by %NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS. + * + * @NL80211_CMD_LINKS_REMOVED: Notify userspace about the removal of STA MLD + * setup links due to AP MLD removing the corresponding affiliated APs with + * Multi-Link reconfiguration. %NL80211_ATTR_MLO_LINKS is used to provide + * information about the removed STA MLD setup links. + * + * @NL80211_CMD_SET_TID_TO_LINK_MAPPING: Set the TID to Link Mapping for a + * non-AP MLD station. The %NL80211_ATTR_MLO_TTLM_DLINK and + * %NL80211_ATTR_MLO_TTLM_ULINK attributes are used to specify the + * TID to Link mapping for downlink/uplink traffic. + * * @NL80211_CMD_MAX: highest used command number * @__NL80211_CMD_AFTER_LAST: internal use */ @@ -1532,6 +1585,12 @@ enum nl80211_commands { NL80211_CMD_MODIFY_LINK_STA, NL80211_CMD_REMOVE_LINK_STA, + NL80211_CMD_SET_HW_TIMESTAMP, + + NL80211_CMD_LINKS_REMOVED, + + NL80211_CMD_SET_TID_TO_LINK_MAPPING, + /* add new commands above here */ /* used to define NL80211_CMD_MAX below */ @@ -1789,7 +1848,7 @@ enum nl80211_commands { * using %CMD_CONTROL_PORT_FRAME. If control port routing over NL80211 is * to be used then userspace must also use the %NL80211_ATTR_SOCKET_OWNER * flag. When used with %NL80211_ATTR_CONTROL_PORT_NO_PREAUTH, pre-auth - * frames are not forwared over the control port. + * frames are not forwarded over the control port. * * @NL80211_ATTR_TESTDATA: Testmode data blob, passed through to the driver. * We recommend using nested, driver-specific attributes within this. @@ -1926,10 +1985,10 @@ enum nl80211_commands { * bit. Depending on which antennas are selected in the bitmap, 802.11n * drivers can derive which chainmasks to use (if all antennas belonging to * a particular chain are disabled this chain should be disabled) and if - * a chain has diversity antennas wether diversity should be used or not. + * a chain has diversity antennas whether diversity should be used or not. * HT capabilities (STBC, TX Beamforming, Antenna selection) can be * derived from the available chains after applying the antenna mask. - * Non-802.11n drivers can derive wether to use diversity or not. + * Non-802.11n drivers can derive whether to use diversity or not. * Drivers may reject configurations or RX/TX mask combinations they cannot * support by returning -EINVAL. * @@ -2499,7 +2558,7 @@ enum nl80211_commands { * from successful FILS authentication and is used with * %NL80211_CMD_CONNECT. * - * @NL80211_ATTR_FILS_CACHE_ID: A 2-octet identifier advertized by a FILS AP + * @NL80211_ATTR_FILS_CACHE_ID: A 2-octet identifier advertised by a FILS AP * identifying the scope of PMKSAs. This is used with * @NL80211_CMD_SET_PMKSA and @NL80211_CMD_DEL_PMKSA. * @@ -2653,11 +2712,13 @@ enum nl80211_commands { * * @NL80211_ATTR_FILS_DISCOVERY: Optional parameter to configure FILS * discovery. It is a nested attribute, see - * &enum nl80211_fils_discovery_attributes. + * &enum nl80211_fils_discovery_attributes. Userspace should pass an empty + * nested attribute to disable this feature and delete the templates. * * @NL80211_ATTR_UNSOL_BCAST_PROBE_RESP: Optional parameter to configure * unsolicited broadcast probe response. It is a nested attribute, see - * &enum nl80211_unsol_bcast_probe_resp_attributes. + * &enum nl80211_unsol_bcast_probe_resp_attributes. Userspace should pass an empty + * nested attribute to disable this feature and delete the templates. * * @NL80211_ATTR_S1G_CAPABILITY: S1G Capability information element (from * association request when used with NL80211_CMD_NEW_STATION) @@ -2751,6 +2812,50 @@ enum nl80211_commands { * the incoming frame RX timestamp. * @NL80211_ATTR_TD_BITMAP: Transition Disable bitmap, for subsequent * (re)associations. + * + * @NL80211_ATTR_PUNCT_BITMAP: (u32) Preamble puncturing bitmap, lowest + * bit corresponds to the lowest 20 MHz channel. Each bit set to 1 + * indicates that the sub-channel is punctured. Higher 16 bits are + * reserved. + * + * @NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS: Maximum number of peers that HW + * timestamping can be enabled for concurrently (u16), a wiphy attribute. + * A value of 0xffff indicates setting for all peers (i.e. not specifying + * an address with %NL80211_CMD_SET_HW_TIMESTAMP) is supported. + * @NL80211_ATTR_HW_TIMESTAMP_ENABLED: Indicates whether HW timestamping should + * be enabled or not (flag attribute). + * + * @NL80211_ATTR_EMA_RNR_ELEMS: Optional nested attribute for + * reduced neighbor report (RNR) elements. This attribute can be used + * only when NL80211_MBSSID_CONFIG_ATTR_EMA is enabled. + * Userspace is responsible for splitting the RNR into multiple + * elements such that each element excludes the non-transmitting + * profiles already included in the MBSSID element + * (%NL80211_ATTR_MBSSID_ELEMS) at the same index. Each EMA beacon + * will be generated by adding MBSSID and RNR elements at the same + * index. If the userspace includes more RNR elements than number of + * MBSSID elements then these will be added in every EMA beacon. + * + * @NL80211_ATTR_MLO_LINK_DISABLED: Flag attribute indicating that the link is + * disabled. + * + * @NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA: Include BSS usage data, i.e. + * include BSSes that can only be used in restricted scenarios and/or + * cannot be used at all. + * + * @NL80211_ATTR_MLO_TTLM_DLINK: Binary attribute specifying the downlink TID to + * link mapping. The length is 8 * sizeof(u16). For each TID the link + * mapping is as defined in section 9.4.2.314 (TID-To-Link Mapping element) + * in Draft P802.11be_D4.0. + * @NL80211_ATTR_MLO_TTLM_ULINK: Binary attribute specifying the uplink TID to + * link mapping. The length is 8 * sizeof(u16). For each TID the link + * mapping is as defined in section 9.4.2.314 (TID-To-Link Mapping element) + * in Draft P802.11be_D4.0. + * + * @NL80211_ATTR_ASSOC_SPP_AMSDU: flag attribute used with + * %NL80211_CMD_ASSOCIATE indicating the SPP A-MSDUs + * are used on this connection + * * @NUM_NL80211_ATTR: total number of nl80211_attrs available * @NL80211_ATTR_MAX: highest attribute number currently defined * @__NL80211_ATTR_AFTER_LAST: internal use @@ -3280,6 +3385,22 @@ enum nl80211_attrs { NL80211_ATTR_RX_HW_TIMESTAMP, NL80211_ATTR_TD_BITMAP, + NL80211_ATTR_PUNCT_BITMAP, + + NL80211_ATTR_MAX_HW_TIMESTAMP_PEERS, + NL80211_ATTR_HW_TIMESTAMP_ENABLED, + + NL80211_ATTR_EMA_RNR_ELEMS, + + NL80211_ATTR_MLO_LINK_DISABLED, + + NL80211_ATTR_BSS_DUMP_INCLUDE_USE_DATA, + + NL80211_ATTR_MLO_TTLM_DLINK, + NL80211_ATTR_MLO_TTLM_ULINK, + + NL80211_ATTR_ASSOC_SPP_AMSDU, + /* add attributes here, update the policy in nl80211.c */ __NL80211_ATTR_AFTER_LAST, @@ -3420,6 +3541,7 @@ enum nl80211_iftype { * @NL80211_STA_FLAG_ASSOCIATED: station is associated; used with drivers * that support %NL80211_FEATURE_FULL_AP_CLIENT_STATE to transition a * previously added station into associated state + * @NL80211_STA_FLAG_SPP_AMSDU: station supports SPP A-MSDUs * @NL80211_STA_FLAG_MAX: highest station flag number currently defined * @__NL80211_STA_FLAG_AFTER_LAST: internal use */ @@ -3432,6 +3554,7 @@ enum nl80211_sta_flags { NL80211_STA_FLAG_AUTHENTICATED, NL80211_STA_FLAG_TDLS_PEER, NL80211_STA_FLAG_ASSOCIATED, + NL80211_STA_FLAG_SPP_AMSDU, /* keep last */ __NL80211_STA_FLAG_AFTER_LAST, @@ -3606,6 +3729,13 @@ enum nl80211_eht_ru_alloc { * (u8, see &enum nl80211_eht_gi) * @NL80211_RATE_INFO_EHT_RU_ALLOC: EHT RU allocation, if not present then * non-OFDMA was used (u8, see &enum nl80211_eht_ru_alloc) + * @NL80211_RATE_INFO_S1G_MCS: S1G MCS index (u8, 0-10) + * @NL80211_RATE_INFO_S1G_NSS: S1G NSS value (u8, 1-4) + * @NL80211_RATE_INFO_1_MHZ_WIDTH: 1 MHz S1G rate + * @NL80211_RATE_INFO_2_MHZ_WIDTH: 2 MHz S1G rate + * @NL80211_RATE_INFO_4_MHZ_WIDTH: 4 MHz S1G rate + * @NL80211_RATE_INFO_8_MHZ_WIDTH: 8 MHz S1G rate + * @NL80211_RATE_INFO_16_MHZ_WIDTH: 16 MHz S1G rate * @__NL80211_RATE_INFO_AFTER_LAST: internal use */ enum nl80211_rate_info { @@ -3632,6 +3762,13 @@ enum nl80211_rate_info { NL80211_RATE_INFO_EHT_NSS, NL80211_RATE_INFO_EHT_GI, NL80211_RATE_INFO_EHT_RU_ALLOC, + NL80211_RATE_INFO_S1G_MCS, + NL80211_RATE_INFO_S1G_NSS, + NL80211_RATE_INFO_1_MHZ_WIDTH, + NL80211_RATE_INFO_2_MHZ_WIDTH, + NL80211_RATE_INFO_4_MHZ_WIDTH, + NL80211_RATE_INFO_8_MHZ_WIDTH, + NL80211_RATE_INFO_16_MHZ_WIDTH, /* keep last */ __NL80211_RATE_INFO_AFTER_LAST, @@ -4000,6 +4137,10 @@ enum nl80211_band_iftype_attr { * @NL80211_BAND_ATTR_EDMG_BW_CONFIG: Channel BW Configuration subfield encodes * the allowed channel bandwidth configurations. * Defined by IEEE P802.11ay/D4.0 section 9.4.2.251, Table 13. + * @NL80211_BAND_ATTR_S1G_MCS_NSS_SET: S1G capabilities, supported S1G-MCS and NSS + * set subfield, as in the S1G information IE, 5 bytes + * @NL80211_BAND_ATTR_S1G_CAPA: S1G capabilities information subfield as in the + * S1G information IE, 10 bytes * @NL80211_BAND_ATTR_MAX: highest band attribute currently defined * @__NL80211_BAND_ATTR_AFTER_LAST: internal use */ @@ -4020,6 +4161,9 @@ enum nl80211_band_attr { NL80211_BAND_ATTR_EDMG_CHANNELS, NL80211_BAND_ATTR_EDMG_BW_CONFIG, + NL80211_BAND_ATTR_S1G_MCS_NSS_SET, + NL80211_BAND_ATTR_S1G_CAPA, + /* keep last */ __NL80211_BAND_ATTR_AFTER_LAST, NL80211_BAND_ATTR_MAX = __NL80211_BAND_ATTR_AFTER_LAST - 1 @@ -4065,7 +4209,7 @@ enum nl80211_wmm_rule { * (100 * dBm). * @NL80211_FREQUENCY_ATTR_DFS_STATE: current state for DFS * (enum nl80211_dfs_state) - * @NL80211_FREQUENCY_ATTR_DFS_TIME: time in miliseconds for how long + * @NL80211_FREQUENCY_ATTR_DFS_TIME: time in milliseconds for how long * this channel is in this DFS state. * @NL80211_FREQUENCY_ATTR_NO_HT40_MINUS: HT40- isn't possible with this * channel as the control channel @@ -4119,6 +4263,19 @@ enum nl80211_wmm_rule { * as the primary or any of the secondary channels isn't possible * @NL80211_FREQUENCY_ATTR_NO_EHT: EHT operation is not allowed on this channel * in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_PSD: Power spectral density (in dBm) that + * is allowed on this channel in current regulatory domain. + * @NL80211_FREQUENCY_ATTR_DFS_CONCURRENT: Operation on this channel is + * allowed for peer-to-peer or adhoc communication under the control + * of a DFS master which operates on the same channel (FCC-594280 D01 + * Section B.3). Should be used together with %NL80211_RRF_DFS only. + * @NL80211_FREQUENCY_ATTR_NO_6GHZ_VLP_CLIENT: Client connection to VLP AP + * not allowed using this channel + * @NL80211_FREQUENCY_ATTR_NO_6GHZ_AFC_CLIENT: Client connection to AFC AP + * not allowed using this channel + * @NL80211_FREQUENCY_ATTR_CAN_MONITOR: This channel can be used in monitor + * mode despite other (regulatory) restrictions, even if the channel is + * otherwise completely disabled. * @NL80211_FREQUENCY_ATTR_MAX: highest frequency attribute number * currently defined * @__NL80211_FREQUENCY_ATTR_AFTER_LAST: internal use @@ -4157,6 +4314,11 @@ enum nl80211_frequency_attr { NL80211_FREQUENCY_ATTR_16MHZ, NL80211_FREQUENCY_ATTR_NO_320MHZ, NL80211_FREQUENCY_ATTR_NO_EHT, + NL80211_FREQUENCY_ATTR_PSD, + NL80211_FREQUENCY_ATTR_DFS_CONCURRENT, + NL80211_FREQUENCY_ATTR_NO_6GHZ_VLP_CLIENT, + NL80211_FREQUENCY_ATTR_NO_6GHZ_AFC_CLIENT, + NL80211_FREQUENCY_ATTR_CAN_MONITOR, /* keep last */ __NL80211_FREQUENCY_ATTR_AFTER_LAST, @@ -4169,6 +4331,10 @@ enum nl80211_frequency_attr { #define NL80211_FREQUENCY_ATTR_NO_IR NL80211_FREQUENCY_ATTR_NO_IR #define NL80211_FREQUENCY_ATTR_GO_CONCURRENT \ NL80211_FREQUENCY_ATTR_IR_CONCURRENT +#define NL80211_FREQUENCY_ATTR_NO_UHB_VLP_CLIENT \ + NL80211_FREQUENCY_ATTR_NO_6GHZ_VLP_CLIENT +#define NL80211_FREQUENCY_ATTR_NO_UHB_AFC_CLIENT \ + NL80211_FREQUENCY_ATTR_NO_6GHZ_AFC_CLIENT /** * enum nl80211_bitrate_attr - bitrate attributes @@ -4257,6 +4423,8 @@ enum nl80211_reg_type { * a given frequency range. The value is in mBm (100 * dBm). * @NL80211_ATTR_DFS_CAC_TIME: DFS CAC time in milliseconds. * If not present or 0 default CAC time will be used. + * @NL80211_ATTR_POWER_RULE_PSD: power spectral density (in dBm). + * This could be negative. * @NL80211_REG_RULE_ATTR_MAX: highest regulatory rule attribute number * currently defined * @__NL80211_REG_RULE_ATTR_AFTER_LAST: internal use @@ -4274,6 +4442,8 @@ enum nl80211_reg_rule_attr { NL80211_ATTR_DFS_CAC_TIME, + NL80211_ATTR_POWER_RULE_PSD, + /* keep last */ __NL80211_REG_RULE_ATTR_AFTER_LAST, NL80211_REG_RULE_ATTR_MAX = __NL80211_REG_RULE_ATTR_AFTER_LAST - 1 @@ -4302,14 +4472,7 @@ enum nl80211_reg_rule_attr { * value as specified by &struct nl80211_bss_select_rssi_adjust. * @NL80211_SCHED_SCAN_MATCH_ATTR_BSSID: BSSID to be used for matching * (this cannot be used together with SSID). - * @NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI: Nested attribute that carries the - * band specific minimum rssi thresholds for the bands defined in - * enum nl80211_band. The minimum rssi threshold value(s32) specific to a - * band shall be encapsulated in attribute with type value equals to one - * of the NL80211_BAND_* defined in enum nl80211_band. For example, the - * minimum rssi threshold value for 2.4GHZ band shall be encapsulated - * within an attribute of type NL80211_BAND_2GHZ. And one or more of such - * attributes will be nested within this attribute. + * @NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI: Obsolete * @NL80211_SCHED_SCAN_MATCH_ATTR_MAX: highest scheduled scan filter * attribute number currently defined * @__NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST: internal use @@ -4322,7 +4485,7 @@ enum nl80211_sched_scan_match_attr { NL80211_SCHED_SCAN_MATCH_ATTR_RELATIVE_RSSI, NL80211_SCHED_SCAN_MATCH_ATTR_RSSI_ADJUST, NL80211_SCHED_SCAN_MATCH_ATTR_BSSID, - NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI, + NL80211_SCHED_SCAN_MATCH_PER_BAND_RSSI, /* obsolete */ /* keep last */ __NL80211_SCHED_SCAN_MATCH_ATTR_AFTER_LAST, @@ -4356,6 +4519,14 @@ enum nl80211_sched_scan_match_attr { * @NL80211_RRF_NO_160MHZ: 160MHz operation not allowed * @NL80211_RRF_NO_HE: HE operation not allowed * @NL80211_RRF_NO_320MHZ: 320MHz operation not allowed + * @NL80211_RRF_NO_EHT: EHT operation not allowed + * @NL80211_RRF_PSD: Ruleset has power spectral density value + * @NL80211_RRF_DFS_CONCURRENT: Operation on this channel is allowed for + peer-to-peer or adhoc communication under the control of a DFS master + which operates on the same channel (FCC-594280 D01 Section B.3). + Should be used together with %NL80211_RRF_DFS only. + * @NL80211_RRF_NO_6GHZ_VLP_CLIENT: Client connection to VLP AP not allowed + * @NL80211_RRF_NO_6GHZ_AFC_CLIENT: Client connection to AFC AP not allowed */ enum nl80211_reg_rule_flags { NL80211_RRF_NO_OFDM = 1<<0, @@ -4375,6 +4546,11 @@ enum nl80211_reg_rule_flags { NL80211_RRF_NO_160MHZ = 1<<16, NL80211_RRF_NO_HE = 1<<17, NL80211_RRF_NO_320MHZ = 1<<18, + NL80211_RRF_NO_EHT = 1<<19, + NL80211_RRF_PSD = 1<<20, + NL80211_RRF_DFS_CONCURRENT = 1<<21, + NL80211_RRF_NO_6GHZ_VLP_CLIENT = 1<<22, + NL80211_RRF_NO_6GHZ_AFC_CLIENT = 1<<23, }; #define NL80211_RRF_PASSIVE_SCAN NL80211_RRF_NO_IR @@ -4383,6 +4559,8 @@ enum nl80211_reg_rule_flags { #define NL80211_RRF_NO_HT40 (NL80211_RRF_NO_HT40MINUS |\ NL80211_RRF_NO_HT40PLUS) #define NL80211_RRF_GO_CONCURRENT NL80211_RRF_IR_CONCURRENT +#define NL80211_RRF_NO_UHB_VLP_CLIENT NL80211_RRF_NO_6GHZ_VLP_CLIENT +#define NL80211_RRF_NO_UHB_AFC_CLIENT NL80211_RRF_NO_6GHZ_AFC_CLIENT /* For backport compatibility with older userspace */ #define NL80211_RRF_NO_IR_ALL (NL80211_RRF_NO_IR | __NL80211_RRF_NO_IBSS) @@ -4911,6 +5089,36 @@ enum nl80211_bss_scan_width { NL80211_BSS_CHAN_WIDTH_2, }; +/** + * enum nl80211_bss_use_for - bitmap indicating possible BSS use + * @NL80211_BSS_USE_FOR_NORMAL: Use this BSS for normal "connection", + * including IBSS/MBSS depending on the type. + * @NL80211_BSS_USE_FOR_MLD_LINK: This BSS can be used as a link in an + * MLO connection. Note that for an MLO connection, all links including + * the assoc link must have this flag set, and the assoc link must + * additionally have %NL80211_BSS_USE_FOR_NORMAL set. + */ +enum nl80211_bss_use_for { + NL80211_BSS_USE_FOR_NORMAL = 1 << 0, + NL80211_BSS_USE_FOR_MLD_LINK = 1 << 1, +}; + +/** + * enum nl80211_bss_cannot_use_reasons - reason(s) connection to a + * BSS isn't possible + * @NL80211_BSS_CANNOT_USE_NSTR_NONPRIMARY: NSTR nonprimary links aren't + * supported by the device, and this BSS entry represents one. + * @NL80211_BSS_CANNOT_USE_6GHZ_PWR_MISMATCH: STA is not supporting + * the AP power type (SP, VLP, AP) that the AP uses. + */ +enum nl80211_bss_cannot_use_reasons { + NL80211_BSS_CANNOT_USE_NSTR_NONPRIMARY = 1 << 0, + NL80211_BSS_CANNOT_USE_6GHZ_PWR_MISMATCH = 1 << 1, +}; + +#define NL80211_BSS_CANNOT_USE_UHB_PWR_MISMATCH \ + NL80211_BSS_CANNOT_USE_6GHZ_PWR_MISMATCH + /** * enum nl80211_bss - netlink attributes for a BSS * @@ -4942,7 +5150,7 @@ enum nl80211_bss_scan_width { * elements from a Beacon frame (bin); not present if no Beacon frame has * yet been received * @NL80211_BSS_CHAN_WIDTH: channel width of the control channel - * (u32, enum nl80211_bss_scan_width) + * (u32, enum nl80211_bss_scan_width) - No longer used! * @NL80211_BSS_BEACON_TSF: TSF of the last received beacon (u64) * (not present if no beacon frame has been received yet) * @NL80211_BSS_PRESP_DATA: the data in @NL80211_BSS_INFORMATION_ELEMENTS and @@ -4963,6 +5171,14 @@ enum nl80211_bss_scan_width { * @NL80211_BSS_FREQUENCY_OFFSET: frequency offset in KHz * @NL80211_BSS_MLO_LINK_ID: MLO link ID of the BSS (u8). * @NL80211_BSS_MLD_ADDR: MLD address of this BSS if connected to it. + * @NL80211_BSS_USE_FOR: u32 bitmap attribute indicating what the BSS can be + * used for, see &enum nl80211_bss_use_for. + * @NL80211_BSS_CANNOT_USE_REASONS: Indicates the reason that this BSS cannot + * be used for all or some of the possible uses by the device reporting it, + * even though its presence was detected. + * This is a u64 attribute containing a bitmap of values from + * &enum nl80211_cannot_use_reasons, note that the attribute may be missing + * if no reasons are specified. * @__NL80211_BSS_AFTER_LAST: internal * @NL80211_BSS_MAX: highest BSS attribute */ @@ -4990,6 +5206,8 @@ enum nl80211_bss { NL80211_BSS_FREQUENCY_OFFSET, NL80211_BSS_MLO_LINK_ID, NL80211_BSS_MLD_ADDR, + NL80211_BSS_USE_FOR, + NL80211_BSS_CANNOT_USE_REASONS, /* keep last */ __NL80211_BSS_AFTER_LAST, @@ -5338,7 +5556,7 @@ enum nl80211_tx_rate_setting { * (%NL80211_TID_CONFIG_ATTR_TIDS, %NL80211_TID_CONFIG_ATTR_OVERRIDE). * @NL80211_TID_CONFIG_ATTR_PEER_SUPP: same as the previous per-vif one, but * per peer instead. - * @NL80211_TID_CONFIG_ATTR_OVERRIDE: flag attribue, if set indicates + * @NL80211_TID_CONFIG_ATTR_OVERRIDE: flag attribute, if set indicates * that the new configuration overrides all previous peer * configurations, otherwise previous peer specific configurations * should be left untouched. @@ -5539,6 +5757,8 @@ struct nl80211_pattern_support { * %NL80211_ATTR_SCAN_FREQUENCIES contains more than one * frequency, it means that the match occurred in more than one * channel. + * @NL80211_WOWLAN_TRIG_UNPROTECTED_DEAUTH_DISASSOC: For wakeup reporting only. + * Wake up happened due to unprotected deauth or disassoc frame in MFP. * @NUM_NL80211_WOWLAN_TRIG: number of wake on wireless triggers * @MAX_NL80211_WOWLAN_TRIG: highest wowlan trigger attribute number * @@ -5566,6 +5786,7 @@ enum nl80211_wowlan_triggers { NL80211_WOWLAN_TRIG_WAKEUP_TCP_NOMORETOKENS, NL80211_WOWLAN_TRIG_NET_DETECT, NL80211_WOWLAN_TRIG_NET_DETECT_RESULTS, + NL80211_WOWLAN_TRIG_UNPROTECTED_DEAUTH_DISASSOC, /* keep last */ NUM_NL80211_WOWLAN_TRIG, @@ -5721,7 +5942,7 @@ enum nl80211_attr_coalesce_rule { /** * enum nl80211_coalesce_condition - coalesce rule conditions - * @NL80211_COALESCE_CONDITION_MATCH: coalaesce Rx packets when patterns + * @NL80211_COALESCE_CONDITION_MATCH: coalesce Rx packets when patterns * in a rule are matched. * @NL80211_COALESCE_CONDITION_NO_MATCH: coalesce Rx packets when patterns * in a rule are not matched. @@ -5820,7 +6041,7 @@ enum nl80211_if_combination_attrs { * enum nl80211_plink_state - state of a mesh peer link finite state machine * * @NL80211_PLINK_LISTEN: initial state, considered the implicit - * state of non existent mesh peer links + * state of non-existent mesh peer links * @NL80211_PLINK_OPN_SNT: mesh plink open frame has been sent to * this mesh peer * @NL80211_PLINK_OPN_RCVD: mesh plink open frame has been received @@ -5869,6 +6090,7 @@ enum plink_actions { #define NL80211_KEK_LEN 16 #define NL80211_KCK_EXT_LEN 24 #define NL80211_KEK_EXT_LEN 32 +#define NL80211_KCK_EXT_LEN_32 32 #define NL80211_REPLAY_CTR_LEN 8 /** @@ -6112,7 +6334,7 @@ enum nl80211_feature_flags { * request to use RRM (see %NL80211_ATTR_USE_RRM) with * %NL80211_CMD_ASSOCIATE and %NL80211_CMD_CONNECT requests, which will set * the ASSOC_REQ_USE_RRM flag in the association request even if - * NL80211_FEATURE_QUIET is not advertized. + * NL80211_FEATURE_QUIET is not advertised. * @NL80211_EXT_FEATURE_MU_MIMO_AIR_SNIFFER: This device supports MU-MIMO air * sniffer which means that it can be configured to hear packets from * certain groups which can be configured by the @@ -6124,13 +6346,15 @@ enum nl80211_feature_flags { * the BSS that the interface that requested the scan is connected to * (if available). * @NL80211_EXT_FEATURE_BSS_PARENT_TSF: Per BSS, this driver reports the - * time the last beacon/probe was received. The time is the TSF of the - * BSS that the interface that requested the scan is connected to - * (if available). + * time the last beacon/probe was received. For a non-MLO connection, the + * time is the TSF of the BSS that the interface that requested the scan is + * connected to (if available). For an MLO connection, the time is the TSF + * of the BSS corresponding with link ID specified in the scan request (if + * specified). * @NL80211_EXT_FEATURE_SET_SCAN_DWELL: This driver supports configuration of * channel dwell time. * @NL80211_EXT_FEATURE_BEACON_RATE_LEGACY: Driver supports beacon rate - * configuration (AP/mesh), supporting a legacy (non HT/VHT) rate. + * configuration (AP/mesh), supporting a legacy (non-HT/VHT) rate. * @NL80211_EXT_FEATURE_BEACON_RATE_HT: Driver supports beacon rate * configuration (AP/mesh) with HT rates. * @NL80211_EXT_FEATURE_BEACON_RATE_VHT: Driver supports beacon rate @@ -6204,8 +6428,7 @@ enum nl80211_feature_flags { * @NL80211_EXT_FEATURE_AP_PMKSA_CACHING: Driver/device supports PMKSA caching * (set/del PMKSA operations) in AP mode. * - * @NL80211_EXT_FEATURE_SCHED_SCAN_BAND_SPECIFIC_RSSI_THOLD: Driver supports - * filtering of sched scan results using band specific RSSI thresholds. + * @NL80211_EXT_FEATURE_SCHED_SCAN_BAND_SPECIFIC_RSSI_THOLD: Obsolete * * @NL80211_EXT_FEATURE_STA_TX_PWR: This driver supports controlling tx power * to a station. @@ -6294,6 +6517,31 @@ enum nl80211_feature_flags { * might apply, e.g. no scans in progress, no offchannel operations * in progress, and no active connections. * + * @NL80211_EXT_FEATURE_PUNCT: Driver supports preamble puncturing in AP mode. + * + * @NL80211_EXT_FEATURE_SECURE_NAN: Device supports NAN Pairing which enables + * authentication, data encryption and message integrity. + * + * @NL80211_EXT_FEATURE_AUTH_AND_DEAUTH_RANDOM_TA: Device supports randomized TA + * in authentication and deauthentication frames sent to unassociated peer + * using @NL80211_CMD_FRAME. + * + * @NL80211_EXT_FEATURE_OWE_OFFLOAD: Driver/Device wants to do OWE DH IE + * handling in station mode. + * + * @NL80211_EXT_FEATURE_OWE_OFFLOAD_AP: Driver/Device wants to do OWE DH IE + * handling in AP mode. + * + * @NL80211_EXT_FEATURE_DFS_CONCURRENT: The device supports peer-to-peer or + * ad hoc operation on DFS channels under the control of a concurrent + * DFS master on the same channel as described in FCC-594280 D01 + * (Section B.3). This, for example, allows P2P GO and P2P clients to + * operate on DFS channels as long as there's a concurrent BSS connection. + * + * @NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT: The driver has support for SPP + * (signaling and payload protected) A-MSDUs and this shall be advertised + * in the RSNXE. + * * @NUM_NL80211_EXT_FEATURES: number of extended features. * @MAX_NL80211_EXT_FEATURES: highest extended feature index. */ @@ -6335,7 +6583,7 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER, NL80211_EXT_FEATURE_AIRTIME_FAIRNESS, NL80211_EXT_FEATURE_AP_PMKSA_CACHING, - NL80211_EXT_FEATURE_SCHED_SCAN_BAND_SPECIFIC_RSSI_THOLD, + NL80211_EXT_FEATURE_SCHED_SCAN_BAND_SPECIFIC_RSSI_THOLD, /* obsolete */ NL80211_EXT_FEATURE_EXT_KEY_ID, NL80211_EXT_FEATURE_STA_TX_PWR, NL80211_EXT_FEATURE_SAE_OFFLOAD, @@ -6362,6 +6610,13 @@ enum nl80211_ext_feature_index { NL80211_EXT_FEATURE_FILS_CRYPTO_OFFLOAD, NL80211_EXT_FEATURE_RADAR_BACKGROUND, NL80211_EXT_FEATURE_POWERED_ADDR_CHANGE, + NL80211_EXT_FEATURE_PUNCT, + NL80211_EXT_FEATURE_SECURE_NAN, + NL80211_EXT_FEATURE_AUTH_AND_DEAUTH_RANDOM_TA, + NL80211_EXT_FEATURE_OWE_OFFLOAD, + NL80211_EXT_FEATURE_OWE_OFFLOAD_AP, + NL80211_EXT_FEATURE_DFS_CONCURRENT, + NL80211_EXT_FEATURE_SPP_AMSDU_SUPPORT, /* add new features before the definition below */ NUM_NL80211_EXT_FEATURES, @@ -6446,7 +6701,7 @@ enum nl80211_timeout_reason { * request parameters IE in the probe request * @NL80211_SCAN_FLAG_ACCEPT_BCAST_PROBE_RESP: accept broadcast probe responses * @NL80211_SCAN_FLAG_OCE_PROBE_REQ_HIGH_TX_RATE: send probe request frames at - * rate of at least 5.5M. In case non OCE AP is discovered in the channel, + * rate of at least 5.5M. In case non-OCE AP is discovered in the channel, * only the first probe req in the channel will be sent in high rate. * @NL80211_SCAN_FLAG_OCE_PROBE_REQ_DEFERRAL_SUPPRESSION: allow probe request * tx deferral (dot11FILSProbeDelay shall be set to 15ms) @@ -6476,8 +6731,16 @@ enum nl80211_timeout_reason { * @NL80211_SCAN_FLAG_FREQ_KHZ: report scan results with * %NL80211_ATTR_SCAN_FREQ_KHZ. This also means * %NL80211_ATTR_SCAN_FREQUENCIES will not be included. - * @NL80211_SCAN_FLAG_COLOCATED_6GHZ: scan for colocated APs reported by - * 2.4/5 GHz APs + * @NL80211_SCAN_FLAG_COLOCATED_6GHZ: scan for collocated APs reported by + * 2.4/5 GHz APs. When the flag is set, the scan logic will use the + * information from the RNR element found in beacons/probe responses + * received on the 2.4/5 GHz channels to actively scan only the 6GHz + * channels on which APs are expected to be found. Note that when not set, + * the scan logic would scan all 6GHz channels, but since transmission of + * probe requests on non-PSC channels is limited, it is highly likely that + * these channels would passively be scanned. Also note that when the flag + * is set, in addition to the colocated APs, PSC channels would also be + * scanned if the user space has asked for it. */ enum nl80211_scan_flags { NL80211_SCAN_FLAG_LOW_PRIORITY = 1<<0, @@ -6806,7 +7069,7 @@ enum nl80211_nan_func_term_reason { * The instance ID for the follow up Service Discovery Frame. This is u8. * @NL80211_NAN_FUNC_FOLLOW_UP_REQ_ID: relevant if the function's type * is follow up. This is a u8. - * The requestor instance ID for the follow up Service Discovery Frame. + * The requester instance ID for the follow up Service Discovery Frame. * @NL80211_NAN_FUNC_FOLLOW_UP_DEST: the MAC address of the recipient of the * follow up Service Discovery Frame. This is a binary attribute. * @NL80211_NAN_FUNC_CLOSE_RANGE: is this function limited for devices in a @@ -7196,7 +7459,7 @@ enum nl80211_peer_measurement_attrs { * @NL80211_PMSR_FTM_CAPA_ATTR_TRIGGER_BASED: flag attribute indicating if * trigger based ranging measurement is supported * @NL80211_PMSR_FTM_CAPA_ATTR_NON_TRIGGER_BASED: flag attribute indicating - * if non trigger based ranging measurement is supported + * if non-trigger-based ranging measurement is supported * * @NUM_NL80211_PMSR_FTM_CAPA_ATTR: internal * @NL80211_PMSR_FTM_CAPA_ATTR_MAX: highest attribute number @@ -7250,7 +7513,7 @@ enum nl80211_peer_measurement_ftm_capa { * if neither %NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED nor * %NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED is set, EDCA based * ranging will be used. - * @NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED: request non trigger based + * @NL80211_PMSR_FTM_REQ_ATTR_NON_TRIGGER_BASED: request non-trigger-based * ranging measurement (flag) * This attribute and %NL80211_PMSR_FTM_REQ_ATTR_TRIGGER_BASED are * mutually exclusive. @@ -7328,7 +7591,7 @@ enum nl80211_peer_measurement_ftm_failure_reasons { * @NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_ATTEMPTS: number of FTM Request frames * transmitted (u32, optional) * @NL80211_PMSR_FTM_RESP_ATTR_NUM_FTMR_SUCCESSES: number of FTM Request frames - * that were acknowleged (u32, optional) + * that were acknowledged (u32, optional) * @NL80211_PMSR_FTM_RESP_ATTR_BUSY_RETRY_TIME: retry time received from the * busy peer (u32, seconds) * @NL80211_PMSR_FTM_RESP_ATTR_NUM_BURSTS_EXP: actual number of bursts exponent @@ -7489,7 +7752,7 @@ enum nl80211_iftype_akm_attributes { * @NL80211_FILS_DISCOVERY_ATTR_INT_MIN: Minimum packet interval (u32, TU). * Allowed range: 0..10000 (TU = Time Unit) * @NL80211_FILS_DISCOVERY_ATTR_INT_MAX: Maximum packet interval (u32, TU). - * Allowed range: 0..10000 (TU = Time Unit) + * Allowed range: 0..10000 (TU = Time Unit). If set to 0, the feature is disabled. * @NL80211_FILS_DISCOVERY_ATTR_TMPL: Template data for FILS discovery action * frame including the headers. * @@ -7522,7 +7785,8 @@ enum nl80211_fils_discovery_attributes { * * @NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_INT: Maximum packet interval (u32, TU). * Allowed range: 0..20 (TU = Time Unit). IEEE P802.11ax/D6.0 - * 26.17.2.3.2 (AP behavior for fast passive scanning). + * 26.17.2.3.2 (AP behavior for fast passive scanning). If set to 0, the feature is + * disabled. * @NL80211_UNSOL_BCAST_PROBE_RESP_ATTR_TMPL: Unsolicited broadcast probe response * frame template (binary). * diff --git a/src/basic/linux/pkt_sched.h b/src/basic/linux/pkt_sched.h index 000eec1..a3cd0c2 100644 --- a/src/basic/linux/pkt_sched.h +++ b/src/basic/linux/pkt_sched.h @@ -477,115 +477,6 @@ enum { #define TCA_HFSC_MAX (__TCA_HFSC_MAX - 1) - -/* CBQ section */ - -#define TC_CBQ_MAXPRIO 8 -#define TC_CBQ_MAXLEVEL 8 -#define TC_CBQ_DEF_EWMA 5 - -struct tc_cbq_lssopt { - unsigned char change; - unsigned char flags; -#define TCF_CBQ_LSS_BOUNDED 1 -#define TCF_CBQ_LSS_ISOLATED 2 - unsigned char ewma_log; - unsigned char level; -#define TCF_CBQ_LSS_FLAGS 1 -#define TCF_CBQ_LSS_EWMA 2 -#define TCF_CBQ_LSS_MAXIDLE 4 -#define TCF_CBQ_LSS_MINIDLE 8 -#define TCF_CBQ_LSS_OFFTIME 0x10 -#define TCF_CBQ_LSS_AVPKT 0x20 - __u32 maxidle; - __u32 minidle; - __u32 offtime; - __u32 avpkt; -}; - -struct tc_cbq_wrropt { - unsigned char flags; - unsigned char priority; - unsigned char cpriority; - unsigned char __reserved; - __u32 allot; - __u32 weight; -}; - -struct tc_cbq_ovl { - unsigned char strategy; -#define TC_CBQ_OVL_CLASSIC 0 -#define TC_CBQ_OVL_DELAY 1 -#define TC_CBQ_OVL_LOWPRIO 2 -#define TC_CBQ_OVL_DROP 3 -#define TC_CBQ_OVL_RCLASSIC 4 - unsigned char priority2; - __u16 pad; - __u32 penalty; -}; - -struct tc_cbq_police { - unsigned char police; - unsigned char __res1; - unsigned short __res2; -}; - -struct tc_cbq_fopt { - __u32 split; - __u32 defmap; - __u32 defchange; -}; - -struct tc_cbq_xstats { - __u32 borrows; - __u32 overactions; - __s32 avgidle; - __s32 undertime; -}; - -enum { - TCA_CBQ_UNSPEC, - TCA_CBQ_LSSOPT, - TCA_CBQ_WRROPT, - TCA_CBQ_FOPT, - TCA_CBQ_OVL_STRATEGY, - TCA_CBQ_RATE, - TCA_CBQ_RTAB, - TCA_CBQ_POLICE, - __TCA_CBQ_MAX, -}; - -#define TCA_CBQ_MAX (__TCA_CBQ_MAX - 1) - -/* dsmark section */ - -enum { - TCA_DSMARK_UNSPEC, - TCA_DSMARK_INDICES, - TCA_DSMARK_DEFAULT_INDEX, - TCA_DSMARK_SET_TC_INDEX, - TCA_DSMARK_MASK, - TCA_DSMARK_VALUE, - __TCA_DSMARK_MAX, -}; - -#define TCA_DSMARK_MAX (__TCA_DSMARK_MAX - 1) - -/* ATM section */ - -enum { - TCA_ATM_UNSPEC, - TCA_ATM_FD, /* file/socket descriptor */ - TCA_ATM_PTR, /* pointer to descriptor - later */ - TCA_ATM_HDR, /* LL header */ - TCA_ATM_EXCESS, /* excess traffic class (0 for CLP) */ - TCA_ATM_ADDR, /* PVC address (for output only) */ - TCA_ATM_STATE, /* VC state (ATM_VS_*; for output only) */ - __TCA_ATM_MAX, -}; - -#define TCA_ATM_MAX (__TCA_ATM_MAX - 1) - /* Network emulator */ enum { @@ -603,6 +494,7 @@ enum { TCA_NETEM_JITTER64, TCA_NETEM_SLOT, TCA_NETEM_SLOT_DIST, + TCA_NETEM_PRNG_SEED, __TCA_NETEM_MAX, }; @@ -719,6 +611,11 @@ enum { #define __TC_MQPRIO_SHAPER_MAX (__TC_MQPRIO_SHAPER_MAX - 1) +enum { + TC_FP_EXPRESS = 1, + TC_FP_PREEMPTIBLE = 2, +}; + struct tc_mqprio_qopt { __u8 num_tc; __u8 prio_tc_map[TC_QOPT_BITMASK + 1]; @@ -732,12 +629,23 @@ struct tc_mqprio_qopt { #define TC_MQPRIO_F_MIN_RATE 0x4 #define TC_MQPRIO_F_MAX_RATE 0x8 +enum { + TCA_MQPRIO_TC_ENTRY_UNSPEC, + TCA_MQPRIO_TC_ENTRY_INDEX, /* u32 */ + TCA_MQPRIO_TC_ENTRY_FP, /* u32 */ + + /* add new constants above here */ + __TCA_MQPRIO_TC_ENTRY_CNT, + TCA_MQPRIO_TC_ENTRY_MAX = (__TCA_MQPRIO_TC_ENTRY_CNT - 1) +}; + enum { TCA_MQPRIO_UNSPEC, TCA_MQPRIO_MODE, TCA_MQPRIO_SHAPER, TCA_MQPRIO_MIN_RATE64, TCA_MQPRIO_MAX_RATE64, + TCA_MQPRIO_TC_ENTRY, __TCA_MQPRIO_MAX, }; @@ -924,15 +832,22 @@ enum { TCA_FQ_HORIZON_DROP, /* drop packets beyond horizon, or cap their EDT */ + TCA_FQ_PRIOMAP, /* prio2band */ + + TCA_FQ_WEIGHTS, /* Weights for each band */ + __TCA_FQ_MAX }; #define TCA_FQ_MAX (__TCA_FQ_MAX - 1) +#define FQ_BANDS 3 +#define FQ_MIN_WEIGHT 16384 + struct tc_fq_qd_stats { __u64 gc_flows; - __u64 highprio_packets; - __u64 tcp_retrans; + __u64 highprio_packets; /* obsolete */ + __u64 tcp_retrans; /* obsolete */ __u64 throttled; __u64 flows_plimit; __u64 pkts_too_long; @@ -945,6 +860,10 @@ struct tc_fq_qd_stats { __u64 ce_mark; /* packets above ce_threshold */ __u64 horizon_drops; __u64 horizon_caps; + __u64 fastpath_packets; + __u64 band_drops[FQ_BANDS]; + __u32 band_pkt_count[FQ_BANDS]; + __u32 pad; }; /* Heavy-Hitter Filter */ @@ -1236,12 +1155,23 @@ enum { TCA_TAPRIO_TC_ENTRY_UNSPEC, TCA_TAPRIO_TC_ENTRY_INDEX, /* u32 */ TCA_TAPRIO_TC_ENTRY_MAX_SDU, /* u32 */ + TCA_TAPRIO_TC_ENTRY_FP, /* u32 */ /* add new constants above here */ __TCA_TAPRIO_TC_ENTRY_CNT, TCA_TAPRIO_TC_ENTRY_MAX = (__TCA_TAPRIO_TC_ENTRY_CNT - 1) }; +enum { + TCA_TAPRIO_OFFLOAD_STATS_PAD = 1, /* u64 */ + TCA_TAPRIO_OFFLOAD_STATS_WINDOW_DROPS, /* u64 */ + TCA_TAPRIO_OFFLOAD_STATS_TX_OVERRUNS, /* u64 */ + + /* add new constants above here */ + __TCA_TAPRIO_OFFLOAD_STATS_CNT, + TCA_TAPRIO_OFFLOAD_STATS_MAX = (__TCA_TAPRIO_OFFLOAD_STATS_CNT - 1) +}; + enum { TCA_TAPRIO_ATTR_UNSPEC, TCA_TAPRIO_ATTR_PRIOMAP, /* struct tc_mqprio_qopt */ diff --git a/src/basic/linux/rtnetlink.h b/src/basic/linux/rtnetlink.h index eb2747d..3b687d2 100644 --- a/src/basic/linux/rtnetlink.h +++ b/src/basic/linux/rtnetlink.h @@ -502,13 +502,17 @@ enum { #define RTAX_MAX (__RTAX_MAX - 1) -#define RTAX_FEATURE_ECN (1 << 0) -#define RTAX_FEATURE_SACK (1 << 1) -#define RTAX_FEATURE_TIMESTAMP (1 << 2) -#define RTAX_FEATURE_ALLFRAG (1 << 3) - -#define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | RTAX_FEATURE_SACK | \ - RTAX_FEATURE_TIMESTAMP | RTAX_FEATURE_ALLFRAG) +#define RTAX_FEATURE_ECN (1 << 0) +#define RTAX_FEATURE_SACK (1 << 1) /* unused */ +#define RTAX_FEATURE_TIMESTAMP (1 << 2) /* unused */ +#define RTAX_FEATURE_ALLFRAG (1 << 3) /* unused */ +#define RTAX_FEATURE_TCP_USEC_TS (1 << 4) + +#define RTAX_FEATURE_MASK (RTAX_FEATURE_ECN | \ + RTAX_FEATURE_SACK | \ + RTAX_FEATURE_TIMESTAMP | \ + RTAX_FEATURE_ALLFRAG | \ + RTAX_FEATURE_TCP_USEC_TS) struct rta_session { __u8 proto; @@ -635,6 +639,7 @@ enum { TCA_INGRESS_BLOCK, TCA_EGRESS_BLOCK, TCA_DUMP_FLAGS, + TCA_EXT_WARN_MSG, __TCA_MAX }; @@ -788,6 +793,7 @@ enum { TCA_ROOT_FLAGS, TCA_ROOT_COUNT, TCA_ROOT_TIME_DELTA, /* in msecs */ + TCA_ROOT_EXT_WARN_MSG, __TCA_ROOT_MAX, #define TCA_ROOT_MAX (__TCA_ROOT_MAX - 1) }; diff --git a/src/basic/linux/stddef.h b/src/basic/linux/stddef.h index 1a73963..b888a83 100644 --- a/src/basic/linux/stddef.h +++ b/src/basic/linux/stddef.h @@ -26,8 +26,13 @@ union { \ struct { MEMBERS } ATTRS; \ struct TAG { MEMBERS } ATTRS NAME; \ - } + } ATTRS +#ifdef __cplusplus +/* sizeof(struct{}) is 1 in C++, not 0, can't use C version of the macro. */ +#define __DECLARE_FLEX_ARRAY(T, member) \ + T member[0] +#else /** * __DECLARE_FLEX_ARRAY() - Declare a flexible array usable in a union * @@ -44,3 +49,9 @@ TYPE NAME[]; \ } #endif + +#ifndef __counted_by +#define __counted_by(m) +#endif + +#endif /* _UAPI_LINUX_STDDEF_H */ diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c index d3fef01..2356527 100644 --- a/src/basic/locale-util.c +++ b/src/basic/locale-util.c @@ -221,7 +221,7 @@ int get_locales(char ***ret) { locales = set_free(locales); r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES"); - if (r == -ENXIO || r == 0) { + if (IN_SET(r, -ENXIO, 0)) { char **a, **b; /* Filter out non-UTF-8 locales, because it's 2019, by default */ @@ -260,7 +260,10 @@ bool locale_is_valid(const char *name) { if (!filename_is_valid(name)) return false; - if (!string_is_safe(name)) + /* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with + * dashes, and variant seems to be alphabetic. + * See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */ + if (!in_charset(name, ALPHANUMERICAL "_.-@")) return false; return true; @@ -292,7 +295,7 @@ bool is_locale_utf8(void) { if (cached_answer >= 0) goto out; - r = getenv_bool_secure("SYSTEMD_UTF8"); + r = secure_getenv_bool("SYSTEMD_UTF8"); if (r >= 0) { cached_answer = r; goto out; diff --git a/src/basic/lock-util.c b/src/basic/lock-util.c index 7bffe85..aef395d 100644 --- a/src/basic/lock-util.c +++ b/src/basic/lock-util.c @@ -139,7 +139,14 @@ static int fcntl_lock(int fd, int operation, bool ofd) { .l_len = 0, })); - if (r == -EACCES) /* Treat EACCESS/EAGAIN the same as per man page. */ + /* If we are doing non-blocking operations, treat EACCES/EAGAIN the same as per man page. But if + * not, propagate EACCES back, as it will likely be due to an LSM denying the operation (for example + * LXC with AppArmor when running on kernel < 6.2), and in some cases we want to gracefully + * fallback (e.g.: PrivateNetwork=yes). As per documentation, it's only the non-blocking operation + * F_SETLK that might return EACCES on some platforms (although the Linux implementation doesn't + * seem to), as F_SETLKW and F_OFD_SETLKW block so this is not an issue, and F_OFD_SETLK is documented + * to only return EAGAIN if the lock is already held. */ + if ((operation & LOCK_NB) && r == -EACCES) r = -EAGAIN; return r; diff --git a/src/basic/lock-util.h b/src/basic/lock-util.h index 91b332f..8fb4757 100644 --- a/src/basic/lock-util.h +++ b/src/basic/lock-util.h @@ -17,7 +17,7 @@ static inline int make_lock_file(const char *p, int operation, LockFile *ret) { int make_lock_file_for(const char *p, int operation, LockFile *ret); void release_lock_file(LockFile *f); -#define LOCK_FILE_INIT { .dir_fd = -EBADF, .fd = -EBADF } +#define LOCK_FILE_INIT (LockFile) { .dir_fd = -EBADF, .fd = -EBADF } /* POSIX locks with the same interface as flock(). */ int posix_lock(int fd, int operation); diff --git a/src/basic/log.c b/src/basic/log.c index 7a44300..13ad19a 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -49,6 +49,12 @@ static void *log_syntax_callback_userdata = NULL; static LogTarget log_target = LOG_TARGET_CONSOLE; static int log_max_level = LOG_INFO; +static int log_target_max_level[] = { + [LOG_TARGET_CONSOLE] = INT_MAX, + [LOG_TARGET_KMSG] = INT_MAX, + [LOG_TARGET_SYSLOG] = INT_MAX, + [LOG_TARGET_JOURNAL] = INT_MAX, +}; static int log_facility = LOG_DAEMON; static bool ratelimit_kmsg = true; @@ -69,6 +75,7 @@ static bool upgrade_syslog_to_journal = false; static bool always_reopen_console = false; static bool open_when_needed = false; static bool prohibit_ipc = false; +static bool assert_return_is_critical = BUILD_MODE_DEVELOPER; /* Akin to glibc's __abort_msg; which is private and we hence cannot * use here. */ @@ -249,7 +256,7 @@ fail: return r; } -static bool stderr_is_journal(void) { +bool stderr_is_journal(void) { _cleanup_free_ char *w = NULL; const char *e; uint64_t dev, ino; @@ -389,7 +396,7 @@ void log_forget_fds(void) { } void log_set_max_level(int level) { - assert(level == LOG_NULL || (level & LOG_PRIMASK) == level); + assert(level == LOG_NULL || LOG_PRI(level) == level); log_max_level = level; @@ -414,7 +421,7 @@ static bool check_console_fd_is_tty(void) { return false; if (console_fd_is_tty < 0) - console_fd_is_tty = isatty(console_fd) > 0; + console_fd_is_tty = isatty_safe(console_fd); return console_fd_is_tty; } @@ -443,6 +450,9 @@ static int write_to_console( if (dumb < 0) dumb = getenv_terminal_is_dumb(); + if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_CONSOLE]) + return 0; + if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { xsprintf(prefix, "<%i>", level); iovec[n++] = IOVEC_MAKE_STRING(prefix); @@ -528,6 +538,9 @@ static int write_to_syslog( if (syslog_fd < 0) return 0; + if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_SYSLOG]) + return 0; + xsprintf(header_priority, "<%i>", level); t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); @@ -597,6 +610,9 @@ static int write_to_kmsg( if (kmsg_fd < 0) return 0; + if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_KMSG]) + return 0; + if (ratelimit_kmsg && !ratelimit_below(&ratelimit)) { if (ratelimit_num_dropped(&ratelimit) > 1) return 0; @@ -724,6 +740,9 @@ static int write_to_journal( if (journal_fd < 0) return 0; + if (LOG_PRI(level) > log_target_max_level[LOG_TARGET_JOURNAL]) + return 0; + iovec_len = MIN(6 + _log_context_num_fields * 2, IOVEC_MAX); iovec = newa(struct iovec, iovec_len); @@ -769,7 +788,7 @@ int log_dispatch_internal( return -ERRNO_VALUE(error); /* Patch in LOG_DAEMON facility if necessary */ - if ((level & LOG_FACMASK) == 0) + if (LOG_FAC(level) == 0) level |= log_facility; if (open_when_needed) @@ -987,9 +1006,13 @@ void log_assert_failed_return( const char *file, int line, const char *func) { + + if (assert_return_is_critical) + log_assert_failed(text, file, line, func); + PROTECT_ERRNO; log_assert(LOG_DEBUG, text, file, line, func, - "Assertion '%s' failed at %s:%u, function %s(). Ignoring."); + "Assertion '%s' failed at %s:%u, function %s(), ignoring."); } int log_oom_internal(int level, const char *file, int line, const char *func) { @@ -1054,7 +1077,7 @@ int log_struct_internal( log_target == LOG_TARGET_NULL) return -ERRNO_VALUE(error); - if ((level & LOG_FACMASK) == 0) + if (LOG_FAC(level) == 0) level |= log_facility; if (IN_SET(log_target, @@ -1157,7 +1180,7 @@ int log_struct_iovec_internal( log_target == LOG_TARGET_NULL) return -ERRNO_VALUE(error); - if ((level & LOG_FACMASK) == 0) + if (LOG_FAC(level) == 0) level |= log_facility; if (IN_SET(log_target, LOG_TARGET_AUTO, @@ -1219,11 +1242,74 @@ int log_set_target_from_string(const char *e) { int log_set_max_level_from_string(const char *e) { int r; - r = log_level_from_string(e); + for (;;) { + _cleanup_free_ char *word = NULL, *prefix = NULL; + LogTarget target; + const char *colon; + + r = extract_first_word(&e, &word, ",", 0); + if (r < 0) + return r; + if (r == 0) + break; + + colon = strchr(word, ':'); + if (!colon) { + r = log_level_from_string(word); + if (r < 0) + return r; + + log_set_max_level(r); + continue; + } + + prefix = strndup(word, colon - word); + if (!prefix) + return -ENOMEM; + + target = log_target_from_string(prefix); + if (target < 0) + return target; + + if (target >= _LOG_TARGET_SINGLE_MAX) + return -EINVAL; + + r = log_level_from_string(colon + 1); + if (r < 0) + return r; + + log_target_max_level[target] = r; + } + + return 0; +} + +int log_max_levels_to_string(int level, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(ret); + + r = log_level_to_string_alloc(level, &s); if (r < 0) return r; - log_set_max_level(r); + for (LogTarget target = 0; target < _LOG_TARGET_SINGLE_MAX; target++) { + _cleanup_free_ char *l = NULL; + + if (log_target_max_level[target] == INT_MAX) + continue; + + r = log_level_to_string_alloc(log_target_max_level[target], &l); + if (r < 0) + return r; + + r = strextendf_with_separator(&s, ",", "%s:%s", log_target_to_string(target), l); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(s); return 0; } @@ -1238,6 +1324,14 @@ static int log_set_ratelimit_kmsg_from_string(const char *e) { return 0; } +void log_set_assert_return_is_critical(bool b) { + assert_return_is_critical = b; +} + +bool log_get_assert_return_is_critical(void) { + return assert_return_is_critical; +} + static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { /* @@ -1258,7 +1352,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; if (log_set_target_from_string(value) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", value); + log_warning("Failed to parse log target '%s', ignoring.", value); } else if (proc_cmdline_key_streq(key, "systemd.log_level")) { @@ -1266,32 +1360,32 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; if (log_set_max_level_from_string(value) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", value); + log_warning("Failed to parse log level setting '%s', ignoring.", value); } else if (proc_cmdline_key_streq(key, "systemd.log_color")) { if (log_show_color_from_string(value ?: "1") < 0) - log_warning("Failed to parse log color setting '%s'. Ignoring.", value); + log_warning("Failed to parse log color setting '%s', ignoring.", value); } else if (proc_cmdline_key_streq(key, "systemd.log_location")) { if (log_show_location_from_string(value ?: "1") < 0) - log_warning("Failed to parse log location setting '%s'. Ignoring.", value); + log_warning("Failed to parse log location setting '%s', ignoring.", value); } else if (proc_cmdline_key_streq(key, "systemd.log_tid")) { if (log_show_tid_from_string(value ?: "1") < 0) - log_warning("Failed to parse log tid setting '%s'. Ignoring.", value); + log_warning("Failed to parse log tid setting '%s', ignoring.", value); } else if (proc_cmdline_key_streq(key, "systemd.log_time")) { if (log_show_time_from_string(value ?: "1") < 0) - log_warning("Failed to parse log time setting '%s'. Ignoring.", value); + 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); + log_warning("Failed to parse log ratelimit kmsg boolean '%s', ignoring.", value); } return 0; @@ -1311,31 +1405,31 @@ void log_parse_environment_variables(void) { e = getenv("SYSTEMD_LOG_TARGET"); if (e && log_set_target_from_string(e) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", e); + log_warning("Failed to parse log target '%s', ignoring.", e); e = getenv("SYSTEMD_LOG_LEVEL"); if (e && log_set_max_level_from_string(e) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", e); + log_warning("Failed to parse log level '%s', ignoring.", e); e = getenv("SYSTEMD_LOG_COLOR"); if (e && log_show_color_from_string(e) < 0) - log_warning("Failed to parse log color '%s'. Ignoring.", e); + log_warning("Failed to parse log color '%s', ignoring.", e); e = getenv("SYSTEMD_LOG_LOCATION"); if (e && log_show_location_from_string(e) < 0) - log_warning("Failed to parse log location '%s'. Ignoring.", e); + log_warning("Failed to parse log location '%s', ignoring.", e); e = getenv("SYSTEMD_LOG_TIME"); if (e && log_show_time_from_string(e) < 0) - log_warning("Failed to parse log time '%s'. Ignoring.", e); + log_warning("Failed to parse log time '%s', ignoring.", e); e = getenv("SYSTEMD_LOG_TID"); if (e && log_show_tid_from_string(e) < 0) - log_warning("Failed to parse log tid '%s'. Ignoring.", e); + 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); + log_warning("Failed to parse log ratelimit kmsg boolean '%s', ignoring.", e); } void log_parse_environment(void) { @@ -1667,7 +1761,7 @@ bool log_context_enabled(void) { if (saved_log_context_enabled >= 0) return saved_log_context_enabled; - r = getenv_bool_secure("SYSTEMD_ENABLE_LOG_CONTEXT"); + r = secure_getenv_bool("SYSTEMD_ENABLE_LOG_CONTEXT"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_ENABLE_LOG_CONTEXT, ignoring: %m"); diff --git a/src/basic/log.h b/src/basic/log.h index 9008d47..726f035 100644 --- a/src/basic/log.h +++ b/src/basic/log.h @@ -18,20 +18,21 @@ struct signalfd_siginfo; typedef enum LogTarget{ LOG_TARGET_CONSOLE, - LOG_TARGET_CONSOLE_PREFIXED, LOG_TARGET_KMSG, LOG_TARGET_JOURNAL, - LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_SYSLOG, + LOG_TARGET_CONSOLE_PREFIXED, + LOG_TARGET_JOURNAL_OR_KMSG, LOG_TARGET_SYSLOG_OR_KMSG, LOG_TARGET_AUTO, /* console if stderr is not journal, JOURNAL_OR_KMSG otherwise */ LOG_TARGET_NULL, - _LOG_TARGET_MAX, + _LOG_TARGET_SINGLE_MAX = LOG_TARGET_SYSLOG + 1, + _LOG_TARGET_MAX = LOG_TARGET_NULL + 1, _LOG_TARGET_INVALID = -EINVAL, } LogTarget; /* This log level disables logging completely. It can only be passed to log_set_max_level() and cannot be - * used a regular log level. */ + * used as a regular log level. */ #define LOG_NULL (LOG_EMERG - 1) /* Note to readers: << and >> have lower precedence (are evaluated earlier) than & and | */ @@ -59,6 +60,7 @@ void log_settle_target(void); void log_set_max_level(int level); int log_set_max_level_from_string(const char *e); int log_get_max_level(void) _pure_; +int log_max_levels_to_string(int level, char **ret); void log_set_facility(int facility); @@ -83,6 +85,7 @@ int log_show_tid_from_string(const char *e); assert_cc(STRLEN(__FILE__) > STRLEN(RELATIVE_SOURCE_PATH) + 1); #define PROJECT_FILE (&__FILE__[STRLEN(RELATIVE_SOURCE_PATH) + 1]) +bool stderr_is_journal(void); int log_open(void); void log_close(void); void log_forget_fds(void); @@ -331,6 +334,9 @@ void log_set_open_when_needed(bool b); * stderr, the console or kmsg */ void log_set_prohibit_ipc(bool b); +void log_set_assert_return_is_critical(bool b); +bool log_get_assert_return_is_critical(void) _pure_; + int log_dup_console(void); int log_syntax_internal( @@ -380,7 +386,7 @@ typedef struct LogRateLimit { RateLimit ratelimit; } LogRateLimit; -#define log_ratelimit_internal(_level, _error, _ratelimit, _format, _file, _line, _func, ...) \ +#define log_ratelimit_internal(_level, _error, _ratelimit, _file, _line, _func, _format, ...) \ ({ \ int _log_ratelimit_error = (_error); \ int _log_ratelimit_level = (_level); \ @@ -404,7 +410,7 @@ typedef struct LogRateLimit { ({ \ int _level = (level), _e = (error); \ _e = (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_ratelimit_internal(_level, _e, _ratelimit, format, PROJECT_FILE, __LINE__, __func__, ##__VA_ARGS__) \ + ? log_ratelimit_internal(_level, _e, _ratelimit, PROJECT_FILE, __LINE__, __func__, format, ##__VA_ARGS__) \ : -ERRNO_VALUE(_e); \ _e < 0 ? _e : -ESTRPIPE; \ }) diff --git a/src/basic/macro.h b/src/basic/macro.h index d63aa81..19d5039 100644 --- a/src/basic/macro.h +++ b/src/basic/macro.h @@ -266,12 +266,6 @@ static inline int __coverity_check_and_return__(int condition) { /* Pointers range from NULL to POINTER_MAX */ #define POINTER_MAX ((void*) UINTPTR_MAX) -/* Iterates through a specified list of pointers. Accepts NULL pointers, but uses POINTER_MAX as internal marker for EOL. */ -#define FOREACH_POINTER(p, x, ...) \ - for (typeof(p) *_l = (typeof(p)[]) { ({ p = x; }), ##__VA_ARGS__, POINTER_MAX }; \ - p != (typeof(p)) POINTER_MAX; \ - p = *(++_l)) - #define _FOREACH_ARRAY(i, array, num, m, end) \ for (typeof(array[0]) *i = (array), *end = ({ \ typeof(num) m = (num); \ @@ -281,6 +275,9 @@ static inline int __coverity_check_and_return__(int condition) { #define FOREACH_ARRAY(i, array, num) \ _FOREACH_ARRAY(i, array, num, UNIQ_T(m, UNIQ), UNIQ_T(end, UNIQ)) +#define FOREACH_ELEMENT(i, array) \ + FOREACH_ARRAY(i, array, ELEMENTSOF(array)) + #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ scope type *name##_ref(type *p) { \ if (!p) \ @@ -380,13 +377,26 @@ assert_cc(sizeof(dummy_t) == 0); _q && _q > (base) ? &_q[-1] : NULL; \ }) -/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly +/* Iterate through each argument passed. All must be the same type as 'entry' or must be implicitly * 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), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__) -#define _VA_ARGS_FOREACH(entry, _entries_, _current_, _va_sentinel_, ...) \ +#define FOREACH_ARGUMENT(entry, ...) \ + _FOREACH_ARGUMENT(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), UNIQ_T(_va_sentinel_, UNIQ), ##__VA_ARGS__) +#define _FOREACH_ARGUMENT(entry, _entries_, _current_, _va_sentinel_, ...) \ for (typeof(entry) _va_sentinel_[1] = {}, _entries_[] = { __VA_ARGS__ __VA_OPT__(,) _va_sentinel_[0] }, *_current_ = _entries_; \ ((long)(_current_ - _entries_) < (long)(ELEMENTSOF(_entries_) - 1)) && ({ entry = *_current_; true; }); \ _current_++) +#define DECIMAL_STR_FMT(x) _Generic((x), \ + char: "%c", \ + bool: "%d", \ + unsigned char: "%d", \ + short: "%hd", \ + unsigned short: "%hu", \ + int: "%d", \ + unsigned: "%u", \ + long: "%ld", \ + unsigned long: "%lu", \ + long long: "%lld", \ + unsigned long long: "%llu") + #include "log.h" diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c index fcedae2..ed6024f 100644 --- a/src/basic/memory-util.c +++ b/src/basic/memory-util.c @@ -39,3 +39,19 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { /* Now we know first 16 bytes match, memcmp() with self. */ return memcmp(data, p + 16, length) == 0; } + +void *memdup_reverse(const void *mem, size_t size) { + assert(mem); + assert(size != 0); + + void *p = malloc(size); + if (!p) + return NULL; + + uint8_t *p_dst = p; + const uint8_t *p_src = mem; + for (size_t i = 0, k = size; i < size; i++, k--) + p_dst[i] = p_src[k-1]; + + return p; +} diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h index 1179513..294aed6 100644 --- a/src/basic/memory-util.h +++ b/src/basic/memory-util.h @@ -107,3 +107,6 @@ static inline void erase_and_freep(void *p) { static inline void erase_char(char *p) { explicit_bzero_safe(p, sizeof(char)); } + +/* Makes a copy of the buffer with reversed order of bytes */ +void *memdup_reverse(const void *mem, size_t size); diff --git a/src/basic/meson.build b/src/basic/meson.build index 111253e..9a21457 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -10,16 +10,19 @@ basic_sources = files( 'audit-util.c', 'btrfs.c', 'build.c', + 'build-path.c', 'bus-label.c', 'cap-list.c', 'capability-util.c', 'cgroup-util.c', 'chase.c', 'chattr-util.c', + 'compress.c', 'conf-files.c', 'confidential-virt.c', 'devnum-util.c', 'dirent-util.c', + 'dlfcn-util.c', 'efivars.c', 'env-file.c', 'env-util.c', @@ -32,6 +35,7 @@ basic_sources = files( 'filesystems.c', 'format-util.c', 'fs-util.c', + 'gcrypt-util.c', 'glob-util.c', 'glyph-util.c', 'gunicode.c', @@ -53,6 +57,7 @@ basic_sources = files( 'lock-util.c', 'log.c', 'login-util.c', + 'keyring-util.c', 'memfd-util.c', 'memory-util.c', 'mempool.c', @@ -79,6 +84,7 @@ basic_sources = files( 'replace-var.c', 'rlimit-util.c', 'runtime-scope.c', + 'sha256.c', 'sigbus.c', 'signal-util.c', 'siphash24.c', @@ -96,7 +102,7 @@ basic_sources = files( 'terminal-util.c', 'time-util.c', 'tmpfile-util.c', - 'uid-alloc-range.c', + 'uid-classification.c', 'uid-range.c', 'unit-def.c', 'unit-file.c', @@ -229,8 +235,10 @@ run_target( ############################################################ -filesystem_includes = ['linux/magic.h', - 'linux/gfs2_ondisk.h'] +filesystem_includes = files( + 'linux/magic.h', + 'missing_magic.h', +) check_filesystems = find_program('check-filesystems.sh') r = run_command([check_filesystems, cpp, files('filesystems-gperf.gperf')] + filesystem_includes, check: false) @@ -272,45 +280,14 @@ libbasic = static_library( fundamental_sources, include_directories : basic_includes, dependencies : [libcap, + libdl, + libgcrypt_cflags, + liblz4_cflags, libm, librt, + libxz_cflags, + libzstd_cflags, threads, userspace], c_args : ['-fvisibility=default'], build_by_default : false) - -############################################################ - -basic_gcrypt_sources = files( - 'gcrypt-util.c', -) - -# A convenience library that is separate from libbasic to avoid -# unnecessary linking to libgcrypt. -libbasic_gcrypt = static_library( - 'basic-gcrypt', - basic_gcrypt_sources, - include_directories : basic_includes, - dependencies : [libgcrypt, - userspace], - c_args : ['-fvisibility=default'], - build_by_default : false) - -############################################################ - -basic_compress_sources = files( - 'compress.c', -) - -# A convenience library that is separate from libbasic to avoid unnecessary -# linking to the compression libraries. -libbasic_compress = static_library( - 'basic-compress', - basic_compress_sources, - include_directories : basic_includes, - dependencies : [liblz4, - libxz, - libzstd, - userspace], - c_args : ['-fvisibility=default'], - build_by_default : false) diff --git a/src/basic/missing_audit.h b/src/basic/missing_audit.h index 62e3c29..3f72acf 100644 --- a/src/basic/missing_audit.h +++ b/src/basic/missing_audit.h @@ -4,21 +4,31 @@ #include #if HAVE_AUDIT -#include +# include #endif #ifndef AUDIT_SERVICE_START -#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +# define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#else +assert_cc(AUDIT_SERVICE_START == 1130); #endif #ifndef AUDIT_SERVICE_STOP -#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +# define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#else +assert_cc(AUDIT_SERVICE_STOP == 1131); #endif #ifndef MAX_AUDIT_MESSAGE_LENGTH -#define MAX_AUDIT_MESSAGE_LENGTH 8970 +# define MAX_AUDIT_MESSAGE_LENGTH 8970 +#else +assert_cc(MAX_AUDIT_MESSAGE_LENGTH == 8970); #endif +/* Note: we check for AUDIT_NLGRP_MAX because it's a define, but we actually + * need AUDIT_NLGRP_READLOG which is an enum. */ #ifndef AUDIT_NLGRP_MAX -#define AUDIT_NLGRP_READLOG 1 +# define AUDIT_NLGRP_READLOG 1 +#else +assert_cc(AUDIT_NLGRP_READLOG == 1); #endif diff --git a/src/basic/missing_capability.h b/src/basic/missing_capability.h index 5adda55..c1c63a6 100644 --- a/src/basic/missing_capability.h +++ b/src/basic/missing_capability.h @@ -6,21 +6,29 @@ /* 3a101b8de0d39403b2c7e5c23fd0b005668acf48 (3.16) */ #ifndef CAP_AUDIT_READ # define CAP_AUDIT_READ 37 +#else +assert_cc(CAP_AUDIT_READ == 37); #endif /* 980737282232b752bb14dab96d77665c15889c36 (5.8) */ #ifndef CAP_PERFMON # define CAP_PERFMON 38 +#else +assert_cc(CAP_PERFMON == 38); #endif /* a17b53c4a4b55ec322c132b6670743612229ee9c (5.8) */ #ifndef CAP_BPF # define CAP_BPF 39 +#else +assert_cc(CAP_BPF == 39); #endif /* 124ea650d3072b005457faed69909221c2905a1f (5.9) */ #ifndef CAP_CHECKPOINT_RESTORE # define CAP_CHECKPOINT_RESTORE 40 +#else +assert_cc(CAP_CHECKPOINT_RESTORE == 40); #endif #define SYSTEMD_CAP_LAST_CAP CAP_CHECKPOINT_RESTORE @@ -34,6 +42,7 @@ # undef CAP_LAST_CAP # endif #endif + #ifndef CAP_LAST_CAP # define CAP_LAST_CAP SYSTEMD_CAP_LAST_CAP #endif diff --git a/src/basic/missing_drm.h b/src/basic/missing_drm.h index 0dec591..e4ca56f 100644 --- a/src/basic/missing_drm.h +++ b/src/basic/missing_drm.h @@ -2,9 +2,9 @@ #pragma once #ifndef DRM_IOCTL_SET_MASTER -#define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) +# define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) #endif #ifndef DRM_IOCTL_DROP_MASTER -#define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) +# define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) #endif diff --git a/src/basic/missing_fs.h b/src/basic/missing_fs.h index 9b03bba..d97b190 100644 --- a/src/basic/missing_fs.h +++ b/src/basic/missing_fs.h @@ -3,6 +3,8 @@ #include +#include "macro.h" + /* linux/fs.h */ #ifndef RENAME_NOREPLACE /* 0a7c3937a1f23f8cb5fc77ae01661e9968a51d0c (3.15) */ #define RENAME_NOREPLACE (1 << 0) @@ -28,43 +30,63 @@ struct file_clone_range { /* linux/fs.h or sys/mount.h */ #ifndef MS_MOVE -#define MS_MOVE 8192 +# define MS_MOVE 8192 +#else +assert_cc(MS_MOVE == 8192); #endif #ifndef MS_REC -#define MS_REC 16384 +# define MS_REC 16384 +#else +assert_cc(MS_REC == 16384); #endif #ifndef MS_PRIVATE -#define MS_PRIVATE (1<<18) +# define MS_PRIVATE (1<<18) +#else +assert_cc(MS_PRIVATE == (1<<18)); #endif #ifndef MS_SLAVE -#define MS_SLAVE (1<<19) +# define MS_SLAVE (1<<19) +#else +assert_cc(MS_SLAVE == (1<<19)); #endif #ifndef MS_SHARED -#define MS_SHARED (1<<20) +# define MS_SHARED (1<<20) +#else +assert_cc(MS_SHARED == (1<<20)); #endif #ifndef MS_RELATIME -#define MS_RELATIME (1<<21) +# define MS_RELATIME (1<<21) +#else +assert_cc(MS_RELATIME == (1<<21)); #endif #ifndef MS_KERNMOUNT -#define MS_KERNMOUNT (1<<22) +# define MS_KERNMOUNT (1<<22) +#else +assert_cc(MS_KERNMOUNT == (1<<22)); #endif #ifndef MS_I_VERSION -#define MS_I_VERSION (1<<23) +# define MS_I_VERSION (1<<23) +#else +assert_cc(MS_I_VERSION == (1<<23)); #endif #ifndef MS_STRICTATIME -#define MS_STRICTATIME (1<<24) +# define MS_STRICTATIME (1<<24) +#else +assert_cc(MS_STRICTATIME == (1 << 24)); #endif #ifndef MS_LAZYTIME -#define MS_LAZYTIME (1<<25) +# define MS_LAZYTIME (1<<25) +#else +assert_cc(MS_LAZYTIME == (1<<25)); #endif /* Not exposed yet. Defined at fs/ext4/ext4.h */ @@ -78,10 +100,19 @@ struct file_clone_range { #endif #ifndef FS_PROJINHERIT_FL -#define FS_PROJINHERIT_FL 0x20000000 +# define FS_PROJINHERIT_FL 0x20000000 +#else +assert_cc(FS_PROJINHERIT_FL == 0x20000000); #endif /* linux/fscrypt.h */ #ifndef FS_KEY_DESCRIPTOR_SIZE -#define FS_KEY_DESCRIPTOR_SIZE 8 +# define FS_KEY_DESCRIPTOR_SIZE 8 +#else +assert_cc(FS_KEY_DESCRIPTOR_SIZE == 8); +#endif + +/* linux/exportfs.h */ +#ifndef FILEID_KERNFS +#define FILEID_KERNFS 0xfe #endif diff --git a/src/basic/missing_input.h b/src/basic/missing_input.h index 6cf16ff..ee61bf9 100644 --- a/src/basic/missing_input.h +++ b/src/basic/missing_input.h @@ -4,9 +4,11 @@ #include #include +#include "macro.h" + /* linux@c7dc65737c9a607d3e6f8478659876074ad129b8 (3.12) */ #ifndef EVIOCREVOKE -#define EVIOCREVOKE _IOW('E', 0x91, int) +# define EVIOCREVOKE _IOW('E', 0x91, int) #endif /* linux@06a16293f71927f756dcf37558a79c0b05a91641 (4.4) */ @@ -17,29 +19,40 @@ struct input_mask { __u64 codes_ptr; }; -#define EVIOCGMASK _IOR('E', 0x92, struct input_mask) -#define EVIOCSMASK _IOW('E', 0x93, struct input_mask) +# define EVIOCGMASK _IOR('E', 0x92, struct input_mask) +# define EVIOCSMASK _IOW('E', 0x93, struct input_mask) #endif /* linux@7611392fe8ff95ecae528b01a815ae3d72ca6b95 (3.17) */ #ifndef INPUT_PROP_POINTING_STICK -#define INPUT_PROP_POINTING_STICK 0x05 +# define INPUT_PROP_POINTING_STICK 0x05 +#else +assert_cc(INPUT_PROP_POINTING_STICK == 0x05); #endif /* linux@500d4160abe9a2e88b12e319c13ae3ebd1e18108 (4.0) */ #ifndef INPUT_PROP_ACCELEROMETER -#define INPUT_PROP_ACCELEROMETER 0x06 +# define INPUT_PROP_ACCELEROMETER 0x06 +#else +assert_cc(INPUT_PROP_ACCELEROMETER == 0x06); #endif /* linux@d09bbfd2a8408a995419dff0d2ba906013cf4cc9 (3.11) */ #ifndef BTN_DPAD_UP -#define BTN_DPAD_UP 0x220 -#define BTN_DPAD_DOWN 0x221 -#define BTN_DPAD_LEFT 0x222 -#define BTN_DPAD_RIGHT 0x223 +# define BTN_DPAD_UP 0x220 +# define BTN_DPAD_DOWN 0x221 +# define BTN_DPAD_LEFT 0x222 +# define BTN_DPAD_RIGHT 0x223 +#else +assert_cc(BTN_DPAD_UP == 0x220); +assert_cc(BTN_DPAD_DOWN == 0x221); +assert_cc(BTN_DPAD_LEFT == 0x222); +assert_cc(BTN_DPAD_RIGHT == 0x223); #endif /* linux@358f24704f2f016af7d504b357cdf32606091d07 (3.13) */ #ifndef KEY_ALS_TOGGLE -#define KEY_ALS_TOGGLE 0x230 +# fine KEY_ALS_TOGGLE 0x230 +#else +assert_cc(KEY_ALS_TOGGLE == 0x230); #endif diff --git a/src/basic/missing_ioprio.h b/src/basic/missing_ioprio.h index 9cbd172..13ce792 100644 --- a/src/basic/missing_ioprio.h +++ b/src/basic/missing_ioprio.h @@ -1,49 +1,81 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include +#if HAVE_LINUX_IOPRIO_H +# include +#endif + +#include "macro.h" /* Match values uses by the kernel internally, as no public header seems to exist. */ #ifndef IOPRIO_N_CLASSES # define IOPRIO_N_CLASSES 8 +#else +assert_cc(IOPRIO_N_CLASSES == 8); #endif #ifndef IOPRIO_BE_NR # define IOPRIO_BE_NR 8 +#else +assert_cc(IOPRIO_BE_NR == 8); #endif #ifndef IOPRIO_CLASS_NONE # define IOPRIO_CLASS_NONE 0 +#else +assert_cc(IOPRIO_CLASS_NONE == 0); #endif #ifndef IOPRIO_CLASS_RT # define IOPRIO_CLASS_RT 1 +#else +assert_cc(IOPRIO_CLASS_RT == 1); #endif #ifndef IOPRIO_CLASS_BE # define IOPRIO_CLASS_BE 2 +#else +assert_cc(IOPRIO_CLASS_BE == 2); #endif #ifndef IOPRIO_CLASS_IDLE # define IOPRIO_CLASS_IDLE 3 +#else +assert_cc(IOPRIO_CLASS_IDLE == 3); #endif #ifndef IOPRIO_WHO_PROCESS # define IOPRIO_WHO_PROCESS 1 +#else +assert_cc(IOPRIO_WHO_PROCESS == 1); #endif + #ifndef IOPRIO_WHO_PGRP # define IOPRIO_WHO_PGRP 2 +#else +assert_cc(IOPRIO_WHO_PGRP == 2); #endif + #ifndef IOPRIO_WHO_USER # define IOPRIO_WHO_USER 3 +#else +assert_cc(IOPRIO_WHO_USER == 3); #endif #ifndef IOPRIO_BITS # define IOPRIO_BITS 16 +#else +assert_cc(IOPRIO_BITS == 16); #endif + #ifndef IOPRIO_N_CLASSES # define IOPRIO_N_CLASSES 8 +#else +assert_cc(IOPRIO_N_CLASSES == 8); #endif + #ifndef IOPRIO_CLASS_SHIFT # define IOPRIO_CLASS_SHIFT 13 +#else +assert_cc(IOPRIO_CLASS_SHIFT == 13); #endif static inline int ioprio_prio_class(int value) { diff --git a/src/basic/missing_keyctl.h b/src/basic/missing_keyctl.h index 081003a..78795fa 100644 --- a/src/basic/missing_keyctl.h +++ b/src/basic/missing_keyctl.h @@ -4,40 +4,60 @@ #include #include +#include "macro.h" + #ifndef KEYCTL_JOIN_SESSION_KEYRING -#define KEYCTL_JOIN_SESSION_KEYRING 1 +# define KEYCTL_JOIN_SESSION_KEYRING 1 +#else +assert_cc(KEYCTL_JOIN_SESSION_KEYRING == 1); #endif #ifndef KEYCTL_CHOWN -#define KEYCTL_CHOWN 4 +# define KEYCTL_CHOWN 4 +#else +assert_cc(KEYCTL_CHOWN == 4); #endif #ifndef KEYCTL_SETPERM -#define KEYCTL_SETPERM 5 +# define KEYCTL_SETPERM 5 +#else +assert_cc(KEYCTL_SETPERM == 5); #endif #ifndef KEYCTL_DESCRIBE -#define KEYCTL_DESCRIBE 6 +# define KEYCTL_DESCRIBE 6 +#else +assert_cc(KEYCTL_DESCRIBE == 6); #endif #ifndef KEYCTL_LINK -#define KEYCTL_LINK 8 +# define KEYCTL_LINK 8 +#else +assert_cc(KEYCTL_LINK == 8); #endif #ifndef KEYCTL_READ -#define KEYCTL_READ 11 +# define KEYCTL_READ 11 +#else +assert_cc(KEYCTL_READ == 11); #endif #ifndef KEYCTL_SET_TIMEOUT -#define KEYCTL_SET_TIMEOUT 15 +# define KEYCTL_SET_TIMEOUT 15 +#else +assert_cc(KEYCTL_SET_TIMEOUT == 15); #endif #ifndef KEY_SPEC_USER_KEYRING -#define KEY_SPEC_USER_KEYRING -4 +# define KEY_SPEC_USER_KEYRING -4 +#else +assert_cc(KEY_SPEC_USER_KEYRING == -4); #endif #ifndef KEY_SPEC_SESSION_KEYRING -#define KEY_SPEC_SESSION_KEYRING -3 +# define KEY_SPEC_SESSION_KEYRING -3 +#else +assert_cc(KEY_SPEC_SESSION_KEYRING == -3); #endif /* From linux/key.h */ @@ -45,35 +65,37 @@ typedef int32_t key_serial_t; -#define KEY_POS_VIEW 0x01000000 -#define KEY_POS_READ 0x02000000 -#define KEY_POS_WRITE 0x04000000 -#define KEY_POS_SEARCH 0x08000000 -#define KEY_POS_LINK 0x10000000 -#define KEY_POS_SETATTR 0x20000000 -#define KEY_POS_ALL 0x3f000000 - -#define KEY_USR_VIEW 0x00010000 -#define KEY_USR_READ 0x00020000 -#define KEY_USR_WRITE 0x00040000 -#define KEY_USR_SEARCH 0x00080000 -#define KEY_USR_LINK 0x00100000 -#define KEY_USR_SETATTR 0x00200000 -#define KEY_USR_ALL 0x003f0000 - -#define KEY_GRP_VIEW 0x00000100 -#define KEY_GRP_READ 0x00000200 -#define KEY_GRP_WRITE 0x00000400 -#define KEY_GRP_SEARCH 0x00000800 -#define KEY_GRP_LINK 0x00001000 -#define KEY_GRP_SETATTR 0x00002000 -#define KEY_GRP_ALL 0x00003f00 - -#define KEY_OTH_VIEW 0x00000001 -#define KEY_OTH_READ 0x00000002 -#define KEY_OTH_WRITE 0x00000004 -#define KEY_OTH_SEARCH 0x00000008 -#define KEY_OTH_LINK 0x00000010 -#define KEY_OTH_SETATTR 0x00000020 -#define KEY_OTH_ALL 0x0000003f +# define KEY_POS_VIEW 0x01000000 +# define KEY_POS_READ 0x02000000 +# define KEY_POS_WRITE 0x04000000 +# define KEY_POS_SEARCH 0x08000000 +# define KEY_POS_LINK 0x10000000 +# define KEY_POS_SETATTR 0x20000000 +# define KEY_POS_ALL 0x3f000000 + +# define KEY_USR_VIEW 0x00010000 +# define KEY_USR_READ 0x00020000 +# define KEY_USR_WRITE 0x00040000 +# define KEY_USR_SEARCH 0x00080000 +# define KEY_USR_LINK 0x00100000 +# define KEY_USR_SETATTR 0x00200000 +# define KEY_USR_ALL 0x003f0000 + +# define KEY_GRP_VIEW 0x00000100 +# define KEY_GRP_READ 0x00000200 +# define KEY_GRP_WRITE 0x00000400 +# define KEY_GRP_SEARCH 0x00000800 +# define KEY_GRP_LINK 0x00001000 +# define KEY_GRP_SETATTR 0x00002000 +# define KEY_GRP_ALL 0x00003f00 + +# define KEY_OTH_VIEW 0x00000001 +# define KEY_OTH_READ 0x00000002 +# define KEY_OTH_WRITE 0x00000004 +# define KEY_OTH_SEARCH 0x00000008 +# define KEY_OTH_LINK 0x00000010 +# define KEY_OTH_SETATTR 0x00000020 +# define KEY_OTH_ALL 0x0000003f +#else +assert_cc(KEY_OTH_ALL == 0x0000003f); #endif diff --git a/src/basic/missing_loop.h b/src/basic/missing_loop.h index 7141544..b88501d 100644 --- a/src/basic/missing_loop.h +++ b/src/basic/missing_loop.h @@ -3,6 +3,8 @@ #include +#include "macro.h" + #ifndef LOOP_CONFIGURE struct loop_config { __u32 fd; @@ -11,14 +13,19 @@ struct loop_config { __u64 __reserved[8]; }; -#define LOOP_CONFIGURE 0x4C0A +# define LOOP_CONFIGURE 0x4C0A +#else +assert_cc(LOOP_CONFIGURE == 0x4C0A); #endif #ifndef LO_FLAGS_DIRECT_IO -#define LO_FLAGS_DIRECT_IO 16 -#define LOOP_SET_DIRECT_IO 0x4C08 +# define LO_FLAGS_DIRECT_IO 16 +# define LOOP_SET_DIRECT_IO 0x4C08 +#else +assert_cc(LO_FLAGS_DIRECT_IO == 16); +assert_cc(LOOP_SET_DIRECT_IO == 0x4C08); #endif #ifndef LOOP_SET_STATUS_SETTABLE_FLAGS -#define LOOP_SET_STATUS_SETTABLE_FLAGS (LO_FLAGS_AUTOCLEAR | LO_FLAGS_PARTSCAN | LO_FLAGS_DIRECT_IO) +# define LOOP_SET_STATUS_SETTABLE_FLAGS (LO_FLAGS_AUTOCLEAR | LO_FLAGS_PARTSCAN) #endif diff --git a/src/basic/missing_magic.h b/src/basic/missing_magic.h index 82d71c8..4e930ac 100644 --- a/src/basic/missing_magic.h +++ b/src/basic/missing_magic.h @@ -3,197 +3,107 @@ #include -/* 62aa81d7c4c24b90fdb61da70ac0dbbc414f9939 (4.13) */ -#ifndef OCFS2_SUPER_MAGIC -#define OCFS2_SUPER_MAGIC 0x7461636f -#endif - -/* 67e9c74b8a873408c27ac9a8e4c1d1c8d72c93ff (4.5) */ -#ifndef CGROUP2_SUPER_MAGIC -#define CGROUP2_SUPER_MAGIC 0x63677270 -#endif - -/* 4282d60689d4f21b40692029080440cc58e8a17d (4.1) */ -#ifndef TRACEFS_MAGIC -#define TRACEFS_MAGIC 0x74726163 -#endif - -/* e149ed2b805fefdccf7ccdfc19eca22fdd4514ac (3.19) */ -#ifndef NSFS_MAGIC -#define NSFS_MAGIC 0x6e736673 -#endif - -/* b2197755b2633e164a439682fb05a9b5ea48f706 (4.4) */ -#ifndef BPF_FS_MAGIC -#define BPF_FS_MAGIC 0xcafe4a11 -#endif - /* Not exposed yet (4.20). Defined at ipc/mqueue.c */ #ifndef MQUEUE_MAGIC -#define MQUEUE_MAGIC 0x19800202 -#endif - -/* Not exposed yet (as of Linux 5.4). Defined in fs/xfs/libxfs/xfs_format.h */ -#ifndef XFS_SB_MAGIC -#define XFS_SB_MAGIC 0x58465342 -#endif - -/* dea2903719283c156b53741126228c4a1b40440f (5.17) */ -#ifndef CIFS_SUPER_MAGIC -#define CIFS_SUPER_MAGIC 0xFF534D42 -#endif - -/* dea2903719283c156b53741126228c4a1b40440f (5.17) */ -#ifndef SMB2_SUPER_MAGIC -#define SMB2_SUPER_MAGIC 0xFE534D42 -#endif - -/* 257f871993474e2bde6c497b54022c362cf398e1 (4.5) */ -#ifndef OVERLAYFS_SUPER_MAGIC -#define OVERLAYFS_SUPER_MAGIC 0x794c7630 -#endif - -/* 2a28900be20640fcd1e548b1e3bad79e8221fcf9 (4.7) */ -#ifndef UDF_SUPER_MAGIC -#define UDF_SUPER_MAGIC 0x15013346 +# define MQUEUE_MAGIC 0x19800202 +#else +assert_cc(MQUEUE_MAGIC == 0x19800202); #endif -/* b1123ea6d3b3da25af5c8a9d843bd07ab63213f4 (4.8) */ +/* b1123ea6d3b3da25af5c8a9d843bd07ab63213f4 (4.8), dropped by 68f2736a858324c3ec852f6c2cddd9d1c777357d (v6.0) */ #ifndef BALLOON_KVM_MAGIC -#define BALLOON_KVM_MAGIC 0x13661366 +# define BALLOON_KVM_MAGIC 0x13661366 +#else +assert_cc(BALLOON_KVM_MAGIC == 0x13661366); #endif -/* 48b4800a1c6af2cdda344ea4e2c843dcc1f6afc9 (4.8) */ +/* 48b4800a1c6af2cdda344ea4e2c843dcc1f6afc9 (4.8), dropped by 68f2736a858324c3ec852f6c2cddd9d1c777357d (v6.0) */ #ifndef ZSMALLOC_MAGIC -#define ZSMALLOC_MAGIC 0x58295829 -#endif - -/* 3bc52c45bac26bf7ed1dc8d287ad1aeaed1250b6 (4.9) */ -#ifndef DAXFS_MAGIC -#define DAXFS_MAGIC 0x64646178 -#endif - -/* 5ff193fbde20df5d80fec367cea3e7856c057320 (4.10) */ -#ifndef RDTGROUP_SUPER_MAGIC -#define RDTGROUP_SUPER_MAGIC 0x7655821 -#endif - -/* a481f4d917835cad86701fc0d1e620c74bb5cd5f (4.13) */ -#ifndef AAFS_MAGIC -#define AAFS_MAGIC 0x5a3c69f0 +# define ZSMALLOC_MAGIC 0x58295829 +#else +assert_cc(ZSMALLOC_MAGIC == 0x58295829); #endif -/* f044c8847bb61eff5e1e95b6f6bb950e7f4a73a4 (4.15) */ -#ifndef AFS_FS_MAGIC -#define AFS_FS_MAGIC 0x6b414653 -#endif - -/* dddde68b8f06dd83486124b8d245e7bfb15c185d (4.20) */ -#ifndef XFS_SUPER_MAGIC -#define XFS_SUPER_MAGIC 0x58465342 -#endif - -/* 3ad20fe393b31025bebfc2d76964561f65df48aa (5.0) */ -#ifndef BINDERFS_SUPER_MAGIC -#define BINDERFS_SUPER_MAGIC 0x6c6f6f70 -#endif - -/* ed63bb1d1f8469586006a9ca63c42344401aa2ab (5.3) */ -#ifndef DMA_BUF_MAGIC -#define DMA_BUF_MAGIC 0x444d4142 -#endif - -/* ea8157ab2ae5e914dd427e5cfab533b6da3819cd (5.3) */ +/* ea8157ab2ae5e914dd427e5cfab533b6da3819cd (5.3), dropped by 68f2736a858324c3ec852f6c2cddd9d1c777357d (v6.0) */ #ifndef Z3FOLD_MAGIC -#define Z3FOLD_MAGIC 0x33 -#endif - -/* 47e4937a4a7ca4184fd282791dfee76c6799966a (5.4) */ -#ifndef EROFS_SUPER_MAGIC_V1 -#define EROFS_SUPER_MAGIC_V1 0xe0f5e1e2 +# define Z3FOLD_MAGIC 0x33 +#else +assert_cc(Z3FOLD_MAGIC == 0x33); #endif -/* fe030c9b85e6783bc52fe86449c0a4b8aa16c753 (5.5) */ +/* fe030c9b85e6783bc52fe86449c0a4b8aa16c753 (5.5), dropped by 68f2736a858324c3ec852f6c2cddd9d1c777357d (v6.0) */ #ifndef PPC_CMM_MAGIC -#define PPC_CMM_MAGIC 0xc7571590 -#endif - -/* 8dcc1a9d90c10fa4143e5c17821082e5e60e46a1 (5.6) */ -#ifndef ZONEFS_MAGIC -#define ZONEFS_MAGIC 0x5a4f4653 -#endif - -/* 3234ac664a870e6ea69ae3a57d824cd7edbeacc5 (5.8) */ -#ifndef DEVMEM_MAGIC -#define DEVMEM_MAGIC 0x454d444d -#endif - -/* cb12fd8e0dabb9a1c8aef55a6a41e2c255fcdf4b (6.8) */ -#ifndef PID_FS_MAGIC -#define PID_FS_MAGIC 0x50494446 +# define PPC_CMM_MAGIC 0xc7571590 +#else +assert_cc(PPC_CMM_MAGIC == 0xc7571590); #endif /* Not in mainline but included in Ubuntu */ #ifndef SHIFTFS_MAGIC -#define SHIFTFS_MAGIC 0x6a656a62 -#endif - -/* 1507f51255c9ff07d75909a84e7c0d7f3c4b2f49 (5.14) */ -#ifndef SECRETMEM_MAGIC -#define SECRETMEM_MAGIC 0x5345434d -#endif - -/* Not exposed yet. Defined at fs/fuse/inode.c */ -#ifndef FUSE_SUPER_MAGIC -#define FUSE_SUPER_MAGIC 0x65735546 +# define SHIFTFS_MAGIC 0x6a656a62 +#else +assert_cc(SHIFTFS_MAGIC == 0x6a656a62); #endif /* Not exposed yet. Defined at fs/fuse/control.c */ #ifndef FUSE_CTL_SUPER_MAGIC -#define FUSE_CTL_SUPER_MAGIC 0x65735543 -#endif - -/* Not exposed yet. Defined at fs/ceph/super.h */ -#ifndef CEPH_SUPER_MAGIC -#define CEPH_SUPER_MAGIC 0x00c36400 +# define FUSE_CTL_SUPER_MAGIC 0x65735543 +#else +assert_cc(FUSE_CTL_SUPER_MAGIC == 0x65735543); #endif /* Not exposed yet. Defined at fs/orangefs/orangefs-kernel.h */ #ifndef ORANGEFS_DEVREQ_MAGIC -#define ORANGEFS_DEVREQ_MAGIC 0x20030529 +# define ORANGEFS_DEVREQ_MAGIC 0x20030529 +#else +assert_cc(ORANGEFS_DEVREQ_MAGIC == 0x20030529); #endif /* linux/gfs2_ondisk.h */ #ifndef GFS2_MAGIC -#define GFS2_MAGIC 0x01161970 +# define GFS2_MAGIC 0x01161970 +#else +assert_cc(GFS2_MAGIC == 0x01161970); #endif /* Not exposed yet. Defined at fs/configfs/mount.c */ #ifndef CONFIGFS_MAGIC -#define CONFIGFS_MAGIC 0x62656570 +# define CONFIGFS_MAGIC 0x62656570 +#else +assert_cc(CONFIGFS_MAGIC == 0x62656570); #endif /* Not exposed yet. Defined at fs/vboxsf/super.c */ #ifndef VBOXSF_SUPER_MAGIC -#define VBOXSF_SUPER_MAGIC 0x786f4256 -#endif - -/* Not exposed yet. Defined at fs/exfat/exfat_fs.h */ -#ifndef EXFAT_SUPER_MAGIC -#define EXFAT_SUPER_MAGIC 0x2011BAB0UL +# define VBOXSF_SUPER_MAGIC 0x786f4256 +#else +assert_cc(VBOXSF_SUPER_MAGIC == 0x786f4256); #endif /* Not exposed yet, internally actually called RPCAUTH_GSSMAGIC. Defined in net/sunrpc/rpc_pipe.c */ #ifndef RPC_PIPEFS_SUPER_MAGIC -#define RPC_PIPEFS_SUPER_MAGIC 0x67596969 +# define RPC_PIPEFS_SUPER_MAGIC 0x67596969 +#else +assert_cc(RPC_PIPEFS_SUPER_MAGIC == 0x67596969); #endif /* Not exposed yet, defined at fs/ntfs/ntfs.h */ #ifndef NTFS_SB_MAGIC -#define NTFS_SB_MAGIC 0x5346544e +# define NTFS_SB_MAGIC 0x5346544e +#else +assert_cc(NTFS_SB_MAGIC == 0x5346544e); #endif /* Not exposed yet, encoded literally in fs/ntfs3/super.c. */ #ifndef NTFS3_SUPER_MAGIC -#define NTFS3_SUPER_MAGIC 0x7366746e +# define NTFS3_SUPER_MAGIC 0x7366746e +#else +assert_cc(NTFS3_SUPER_MAGIC == 0x7366746e); +#endif + +/* Added in Linux commit e2f48c48090dea172c0c571101041de64634dae5. Remove when next sync'd */ +#ifndef BCACHEFS_SUPER_MAGIC +# define BCACHEFS_SUPER_MAGIC 0xca451a4e +#else +assert_cc(BCACHEFS_SUPER_MAGIC == 0xca451a4e) #endif diff --git a/src/basic/missing_mman.h b/src/basic/missing_mman.h index f48c436..d6a8b4b 100644 --- a/src/basic/missing_mman.h +++ b/src/basic/missing_mman.h @@ -3,18 +3,28 @@ #include +#include "macro.h" + #ifndef MFD_ALLOW_SEALING -#define MFD_ALLOW_SEALING 0x0002U +# define MFD_ALLOW_SEALING 0x0002U +#else +assert_cc(MFD_ALLOW_SEALING == 0x0002U); #endif #ifndef MFD_CLOEXEC -#define MFD_CLOEXEC 0x0001U +# define MFD_CLOEXEC 0x0001U +#else +assert_cc(MFD_CLOEXEC == 0x0001U); #endif #ifndef MFD_NOEXEC_SEAL -#define MFD_NOEXEC_SEAL 0x0008U +# define MFD_NOEXEC_SEAL 0x0008U +#else +assert_cc(MFD_NOEXEC_SEAL == 0x0008U); #endif #ifndef MFD_EXEC -#define MFD_EXEC 0x0010U +# define MFD_EXEC 0x0010U +#else +assert_cc(MFD_EXEC == 0x0010U); #endif diff --git a/src/basic/missing_mount.h b/src/basic/missing_mount.h index 69b0bcf..d6e16e5 100644 --- a/src/basic/missing_mount.h +++ b/src/basic/missing_mount.h @@ -3,7 +3,11 @@ #include +#include "macro.h" + /* dab741e0e02bd3c4f5e2e97be74b39df2523fc6e (5.10) */ #ifndef MS_NOSYMFOLLOW -#define MS_NOSYMFOLLOW 256 +# define MS_NOSYMFOLLOW 256 +#else +assert_cc(MS_NOSYMFOLLOW == 256); #endif diff --git a/src/basic/missing_prctl.h b/src/basic/missing_prctl.h index 7d9e395..2c9f9f6 100644 --- a/src/basic/missing_prctl.h +++ b/src/basic/missing_prctl.h @@ -3,6 +3,8 @@ #include +#include "macro.h" + /* 58319057b7847667f0c9585b9de0e8932b0fdb08 (4.3) */ #ifndef PR_CAP_AMBIENT #define PR_CAP_AMBIENT 47 @@ -15,12 +17,19 @@ /* b507808ebce23561d4ff8c2aa1fb949fe402bc61 (6.3) */ #ifndef PR_SET_MDWE -#define PR_SET_MDWE 65 +# define PR_SET_MDWE 65 +#else +assert_cc(PR_SET_MDWE == 65); #endif + #ifndef PR_MDWE_REFUSE_EXEC_GAIN -#define PR_MDWE_REFUSE_EXEC_GAIN 1 +# define PR_MDWE_REFUSE_EXEC_GAIN 1 +#else +assert_cc(PR_MDWE_REFUSE_EXEC_GAIN == 1); #endif #ifndef PR_SET_MEMORY_MERGE -#define PR_SET_MEMORY_MERGE 67 +# define PR_SET_MEMORY_MERGE 67 +#else +assert_cc(PR_SET_MEMORY_MERGE == 67); #endif diff --git a/src/basic/missing_random.h b/src/basic/missing_random.h index 443b913..0f8a5be 100644 --- a/src/basic/missing_random.h +++ b/src/basic/missing_random.h @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "macro.h" + #if USE_SYS_RANDOM_H # include #else @@ -8,13 +10,19 @@ #endif #ifndef GRND_NONBLOCK -#define GRND_NONBLOCK 0x0001 +# define GRND_NONBLOCK 0x0001 +#else +assert_cc(GRND_NONBLOCK == 0x0001); #endif #ifndef GRND_RANDOM -#define GRND_RANDOM 0x0002 +# define GRND_RANDOM 0x0002 +#else +assert_cc(GRND_RANDOM == 0x0002); #endif #ifndef GRND_INSECURE -#define GRND_INSECURE 0x0004 +# define GRND_INSECURE 0x0004 +#else +assert_cc(GRND_INSECURE == 0x0004); #endif diff --git a/src/basic/missing_resource.h b/src/basic/missing_resource.h index 6e76765..1807673 100644 --- a/src/basic/missing_resource.h +++ b/src/basic/missing_resource.h @@ -3,8 +3,12 @@ #include +#include "macro.h" + #ifndef RLIMIT_RTTIME -#define RLIMIT_RTTIME 15 +# define RLIMIT_RTTIME 15 +#else +assert_cc(RLIMIT_RTTIME == 15); #endif /* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ diff --git a/src/basic/missing_sched.h b/src/basic/missing_sched.h index bcd5b77..b8109d3 100644 --- a/src/basic/missing_sched.h +++ b/src/basic/missing_sched.h @@ -3,24 +3,35 @@ #include +#include "macro.h" + #ifndef CLONE_NEWCGROUP -#define CLONE_NEWCGROUP 0x02000000 +# define CLONE_NEWCGROUP 0x02000000 +#else +assert_cc(CLONE_NEWCGROUP == 0x02000000); #endif /* 769071ac9f20b6a447410c7eaa55d1a5233ef40c (5.8) */ #ifndef CLONE_NEWTIME -#define CLONE_NEWTIME 0x00000080 +# define CLONE_NEWTIME 0x00000080 +#else +assert_cc(CLONE_NEWTIME == 0x00000080); #endif /* Not exposed yet. Defined at include/linux/sched.h */ #ifndef PF_KTHREAD -#define PF_KTHREAD 0x00200000 +# define PF_KTHREAD 0x00200000 +#else +assert_cc(PF_KTHREAD == 0x00200000); #endif -/* The maximum thread/process name length including trailing NUL byte. This mimics the kernel definition of the same - * name, which we need in userspace at various places but is not defined in userspace currently, neither under this - * name nor any other. */ -/* Not exposed yet. Defined at include/linux/sched.h */ +/* The maximum thread/process name length including trailing NUL byte. This mimics the kernel definition of + * the same name, which we need in userspace at various places but is not defined in userspace currently, + * neither under this name nor any other. + * + * Not exposed yet. Defined at include/linux/sched.h */ #ifndef TASK_COMM_LEN -#define TASK_COMM_LEN 16 +# define TASK_COMM_LEN 16 +#else +assert_cc(TASK_COMM_LEN == 16); #endif diff --git a/src/basic/missing_socket.h b/src/basic/missing_socket.h index 30ac297..47cc762 100644 --- a/src/basic/missing_socket.h +++ b/src/basic/missing_socket.h @@ -6,7 +6,6 @@ #if HAVE_LINUX_VM_SOCKETS_H #include #else -#define VMADDR_CID_ANY -1U struct sockaddr_vm { unsigned short svm_family; unsigned short svm_reserved1; @@ -20,6 +19,26 @@ struct sockaddr_vm { }; #endif /* !HAVE_LINUX_VM_SOCKETS_H */ +#ifndef VMADDR_CID_ANY +#define VMADDR_CID_ANY -1U +#endif + +#ifndef VMADDR_CID_HYPERVISOR +#define VMADDR_CID_HYPERVISOR 0U +#endif + +#ifndef VMADDR_CID_LOCAL +#define VMADDR_CID_LOCAL 1U +#endif + +#ifndef VMADDR_CID_HOST +#define VMADDR_CID_HOST 2U +#endif + +#ifndef VMADDR_PORT_ANY +#define VMADDR_PORT_ANY -1U +#endif + #ifndef AF_VSOCK #define AF_VSOCK 40 #endif @@ -32,6 +51,10 @@ struct sockaddr_vm { #define SO_PEERGROUPS 59 #endif +#ifndef SO_PEERPIDFD +#define SO_PEERPIDFD 77 +#endif + #ifndef SO_BINDTOIFINDEX #define SO_BINDTOIFINDEX 62 #endif diff --git a/src/basic/missing_timerfd.h b/src/basic/missing_timerfd.h index dba3043..a01a4ec 100644 --- a/src/basic/missing_timerfd.h +++ b/src/basic/missing_timerfd.h @@ -3,6 +3,10 @@ #include +#include "macro.h" + #ifndef TFD_TIMER_CANCEL_ON_SET -#define TFD_TIMER_CANCEL_ON_SET (1 << 1) +# define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#else +assert_cc(TFD_TIMER_CANCEL_ON_SET == (1 << 1)); #endif diff --git a/src/basic/missing_type.h b/src/basic/missing_type.h index f623309..1d17705 100644 --- a/src/basic/missing_type.h +++ b/src/basic/missing_type.h @@ -4,9 +4,9 @@ #include #if !HAVE_CHAR32_T -#define char32_t uint32_t +# define char32_t uint32_t #endif #if !HAVE_CHAR16_T -#define char16_t uint16_t +# define char16_t uint16_t #endif diff --git a/src/basic/missing_wait.h b/src/basic/missing_wait.h new file mode 100644 index 0000000..3965b5b --- /dev/null +++ b/src/basic/missing_wait.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +#ifndef P_PIDFD +# define P_PIDFD 3 +#else +assert_cc(P_PIDFD == 3); +#endif diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index c770e5e..f87de0a 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -70,17 +70,11 @@ int mkdirat_safe_internal( path, st.st_mode & 0777, mode); if ((uid != UID_INVALID && st.st_uid != uid) || - (gid != GID_INVALID && st.st_gid != gid)) { - char u[DECIMAL_STR_MAX(uid_t)] = "-", g[DECIMAL_STR_MAX(gid_t)] = "-"; - - if (uid != UID_INVALID) - xsprintf(u, UID_FMT, uid); - if (gid != UID_INVALID) - xsprintf(g, GID_FMT, gid); + (gid != GID_INVALID && st.st_gid != gid)) return log_full_errno(flags & MKDIR_WARN_MODE ? LOG_WARNING : LOG_DEBUG, SYNTHETIC_ERRNO(EEXIST), "Directory \"%s\" already exists, but is owned by "UID_FMT":"GID_FMT" (%s:%s was requested), refusing.", - path, st.st_uid, st.st_gid, u, g); - } + path, st.st_uid, st.st_gid, uid != UID_INVALID ? FORMAT_UID(uid) : "-", + gid != UID_INVALID ? FORMAT_GID(gid) : "-"); return 0; } @@ -118,7 +112,7 @@ int mkdirat_parents_internal(int dir_fd, const char *path, mode_t mode, uid_t ui /* drop the last component */ path = strndupa_safe(path, e - path); - r = is_dir_full(dir_fd, path, true); + r = is_dir_at(dir_fd, path, /* follow = */ true); if (r > 0) return 0; if (r == 0) @@ -210,11 +204,13 @@ int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, g return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper); } -int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes) { +int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes) { _cleanup_free_ char *pp = NULL, *bn = NULL; _cleanup_close_ int dfd = -EBADF; int r; + assert(p); + r = path_extract_directory(p, &pp); if (r == -EDESTADDRREQ) { /* only fname is passed, no prefix to operate on */ @@ -228,11 +224,11 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m return r; else { /* Extracting the parent dir worked, hence we aren't top-level? Recurse up first. */ - r = mkdir_p_root(root, pp, uid, gid, m, subvolumes); + r = mkdir_p_root_full(root, pp, uid, gid, m, ts, subvolumes); if (r < 0) return r; - dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_DIRECTORY, NULL); + dfd = chase_and_open(pp, root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_DIRECTORY, NULL); if (dfd < 0) return dfd; } @@ -247,23 +243,31 @@ int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m r = btrfs_subvol_make_fallback(dfd, bn, m); else r = RET_NERRNO(mkdirat(dfd, bn, m)); - if (r < 0) { - if (r == -EEXIST) - return 0; - + if (r == -EEXIST) + return 0; + if (r < 0) return r; - } - if (uid_is_valid(uid) || gid_is_valid(gid)) { - _cleanup_close_ int nfd = -EBADF; + if (ts == USEC_INFINITY && !uid_is_valid(uid) && !gid_is_valid(gid)) + return 1; + + _cleanup_close_ int nfd = openat(dfd, bn, O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); + if (nfd < 0) + return -errno; + + if (ts != USEC_INFINITY) { + struct timespec tspec; + timespec_store(&tspec, ts); - nfd = openat(dfd, bn, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW); - if (nfd < 0) + if (futimens(dfd, (const struct timespec[2]) { TIMESPEC_OMIT, tspec }) < 0) return -errno; - if (fchown(nfd, uid, gid) < 0) + if (futimens(nfd, (const struct timespec[2]) { tspec, tspec }) < 0) return -errno; } + if ((uid_is_valid(uid) || gid_is_valid(gid)) && fchown(nfd, uid, gid) < 0) + return -errno; + return 1; } diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h index e538748..471f45b 100644 --- a/src/basic/mkdir.h +++ b/src/basic/mkdir.h @@ -4,6 +4,8 @@ #include #include +#include "time-util.h" + typedef enum MkdirFlags { MKDIR_FOLLOW_SYMLINK = 1 << 0, MKDIR_IGNORE_EXISTING = 1 << 1, /* Quietly accept a preexisting directory (or file) */ @@ -23,7 +25,10 @@ static inline int mkdir_parents(const char *path, mode_t mode) { int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); int mkdir_p(const char *path, mode_t mode); int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); -int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, char **subvolumes); +int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes); +static inline int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) { + return mkdir_p_root_full(root, p, uid, gid, m, USEC_INFINITY, NULL); +} /* The following are used to implement the mkdir_xyz_label() calls, don't use otherwise. */ typedef int (*mkdirat_func_t)(int dir_fd, const char *pathname, mode_t mode); diff --git a/src/basic/mountpoint-util.c b/src/basic/mountpoint-util.c index bf67f7e..66fa35b 100644 --- a/src/basic/mountpoint-util.c +++ b/src/basic/mountpoint-util.c @@ -329,34 +329,33 @@ fallback_fstat: } /* flags can be AT_SYMLINK_FOLLOW or 0 */ -int path_is_mount_point(const char *t, const char *root, int flags) { +int path_is_mount_point_full(const char *path, const char *root, int flags) { _cleanup_free_ char *canonical = NULL; _cleanup_close_ int fd = -EBADF; int r; - assert(t); + assert(path); assert((flags & ~AT_SYMLINK_FOLLOW) == 0); - if (path_equal(t, "/")) + if (path_equal(path, "/")) return 1; - /* we need to resolve symlinks manually, we can't just rely on - * fd_is_mount_point() to do that for us; if we have a structure like - * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we + /* we need to resolve symlinks manually, we can't just rely on fd_is_mount_point() to do that for us; + * if we have a structure like /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(t, root, CHASE_TRAIL_SLASH, &canonical, NULL); + if (FLAGS_SET(flags, AT_SYMLINK_FOLLOW)) { + r = chase(path, root, CHASE_TRAIL_SLASH, &canonical, NULL); if (r < 0) return r; - t = canonical; + path = canonical; } - fd = open_parent(t, O_PATH|O_CLOEXEC, 0); + fd = open_parent(path, O_PATH|O_CLOEXEC, 0); if (fd < 0) return fd; - return fd_is_mount_point(fd, last_path_component(t), flags); + return fd_is_mount_point(fd, last_path_component(path), flags); } int path_get_mnt_id_at_fallback(int dir_fd, const char *path, int *ret) { @@ -446,14 +445,15 @@ bool fstype_needs_quota(const char *fstype) { } bool fstype_is_api_vfs(const char *fstype) { - const FilesystemSet *fs; + assert(fstype); - FOREACH_POINTER(fs, - filesystem_sets + FILESYSTEM_SET_BASIC_API, - filesystem_sets + FILESYSTEM_SET_AUXILIARY_API, - filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API, - filesystem_sets + FILESYSTEM_SET_TEMPORARY) - if (nulstr_contains(fs->value, fstype)) + const FilesystemSet *fs; + FOREACH_ARGUMENT(fs, + filesystem_sets + FILESYSTEM_SET_BASIC_API, + filesystem_sets + FILESYSTEM_SET_AUXILIARY_API, + filesystem_sets + FILESYSTEM_SET_PRIVILEGED_API, + filesystem_sets + FILESYSTEM_SET_TEMPORARY) + if (nulstr_contains(fs->value, fstype)) return true; /* Filesystems not present in the internal database */ @@ -495,16 +495,34 @@ bool fstype_can_discard(const char *fstype) { return mount_option_supported(fstype, "discard", NULL) > 0; } -bool fstype_can_norecovery(const char *fstype) { +const char* fstype_norecovery_option(const char *fstype) { + int r; + assert(fstype); /* Use a curated list as first check, to avoid calling fsopen() which might load kmods, which might * not be allowed in our MAC context. */ - if (STR_IN_SET(fstype, "ext3", "ext4", "xfs", "btrfs")) - return true; + if (STR_IN_SET(fstype, "ext3", "ext4", "xfs")) + return "norecovery"; + + /* btrfs dropped support for the "norecovery" option in 6.8 + * (https://github.com/torvalds/linux/commit/a1912f712188291f9d7d434fba155461f1ebef66) and replaced + * it with rescue=nologreplay, so we check for the new name first and fall back to checking for the + * old name if the new name doesn't work. */ + if (streq(fstype, "btrfs")) { + r = mount_option_supported(fstype, "rescue=nologreplay", NULL); + if (r == -EAGAIN) { + log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming old kernel with 'norecovery': %m"); + return "norecovery"; + } + if (r < 0) + log_debug_errno(r, "Failed to check for btrfs 'rescue=nologreplay' option, assuming it is not supported: %m"); + if (r > 0) + return "rescue=nologreplay"; + } /* On new kernels we can just ask the kernel */ - return mount_option_supported(fstype, "norecovery", NULL) > 0; + return mount_option_supported(fstype, "norecovery", NULL) > 0 ? "norecovery" : NULL; } bool fstype_can_umask(const char *fstype) { @@ -784,3 +802,10 @@ int mount_option_supported(const char *fstype, const char *key, const char *valu return true; /* works! */ } + +bool path_below_api_vfs(const char *p) { + assert(p); + + /* API VFS are either directly mounted on any of these three paths, or below it. */ + return PATH_STARTSWITH_SET(p, "/dev", "/sys", "/proc"); +} diff --git a/src/basic/mountpoint-util.h b/src/basic/mountpoint-util.h index 499403a..d7c6251 100644 --- a/src/basic/mountpoint-util.h +++ b/src/basic/mountpoint-util.h @@ -3,6 +3,7 @@ #include #include +#include #include /* The limit used for /dev itself. 4MB should be enough since device nodes and symlinks don't @@ -44,7 +45,10 @@ static inline int path_get_mnt_id(const char *path, int *ret) { } int fd_is_mount_point(int fd, const char *filename, int flags); -int path_is_mount_point(const char *path, const char *root, int flags); +int path_is_mount_point_full(const char *path, const char *root, int flags); +static inline int path_is_mount_point(const char *path) { + return path_is_mount_point_full(path, NULL, 0); +} bool fstype_is_network(const char *fstype); bool fstype_needs_quota(const char *fstype); @@ -53,9 +57,10 @@ bool fstype_is_blockdev_backed(const char *fstype); bool fstype_is_ro(const char *fsype); bool fstype_can_discard(const char *fstype); bool fstype_can_uid_gid(const char *fstype); -bool fstype_can_norecovery(const char *fstype); bool fstype_can_umask(const char *fstype); +const char* fstype_norecovery_option(const char *fstype); + int dev_is_devtmpfs(void); int mount_fd(const char *source, int target_fd, const char *filesystemtype, unsigned long mountflags, const void *data); @@ -69,3 +74,5 @@ bool mount_new_api_supported(void); unsigned long ms_nosymfollow_supported(void); int mount_option_supported(const char *fstype, const char *key, const char *value); + +bool path_below_api_vfs(const char *p); diff --git a/src/basic/namespace-util.c b/src/basic/namespace-util.c index 2101f61..5b4e43f 100644 --- a/src/basic/namespace-util.c +++ b/src/basic/namespace-util.c @@ -11,6 +11,7 @@ #include "missing_magic.h" #include "missing_sched.h" #include "namespace-util.h" +#include "parse-util.h" #include "process-util.h" #include "stat-util.h" #include "stdio-util.h" @@ -33,71 +34,86 @@ const struct namespace_info namespace_info[] = { #define pid_namespace_path(pid, type) procfs_file_alloca(pid, namespace_info[type].proc_path) -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { - _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, netnsfd = -EBADF, usernsfd = -EBADF; - int rfd = -EBADF; +static NamespaceType clone_flag_to_namespace_type(unsigned long clone_flag) { + for (NamespaceType t = 0; t < _NAMESPACE_TYPE_MAX; t++) + if (((namespace_info[t].clone_flag ^ clone_flag) & (CLONE_NEWCGROUP|CLONE_NEWIPC|CLONE_NEWNET|CLONE_NEWNS|CLONE_NEWPID|CLONE_NEWUSER|CLONE_NEWUTS|CLONE_NEWTIME)) == 0) + return t; + + return _NAMESPACE_TYPE_INVALID; +} + +int namespace_open( + pid_t pid, + int *ret_pidns_fd, + int *ret_mntns_fd, + int *ret_netns_fd, + int *ret_userns_fd, + int *ret_root_fd) { + + _cleanup_close_ int pidns_fd = -EBADF, mntns_fd = -EBADF, netns_fd = -EBADF, + userns_fd = -EBADF, root_fd = -EBADF; assert(pid >= 0); - if (mntns_fd) { - const char *mntns; + if (ret_pidns_fd) { + const char *pidns; - mntns = pid_namespace_path(pid, NAMESPACE_MOUNT); - mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntnsfd < 0) + pidns = pid_namespace_path(pid, NAMESPACE_PID); + pidns_fd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (pidns_fd < 0) return -errno; } - if (pidns_fd) { - const char *pidns; + if (ret_mntns_fd) { + const char *mntns; - pidns = pid_namespace_path(pid, NAMESPACE_PID); - pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (pidnsfd < 0) + mntns = pid_namespace_path(pid, NAMESPACE_MOUNT); + mntns_fd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntns_fd < 0) return -errno; } - if (netns_fd) { + if (ret_netns_fd) { const char *netns; netns = pid_namespace_path(pid, NAMESPACE_NET); - netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (netnsfd < 0) + netns_fd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (netns_fd < 0) return -errno; } - if (userns_fd) { + if (ret_userns_fd) { const char *userns; userns = pid_namespace_path(pid, NAMESPACE_USER); - usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (usernsfd < 0 && errno != ENOENT) + userns_fd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (userns_fd < 0 && errno != ENOENT) return -errno; } - if (root_fd) { + if (ret_root_fd) { const char *root; root = procfs_file_alloca(pid, "root"); - rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (rfd < 0) + root_fd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (root_fd < 0) return -errno; } - if (pidns_fd) - *pidns_fd = TAKE_FD(pidnsfd); + if (ret_pidns_fd) + *ret_pidns_fd = TAKE_FD(pidns_fd); - if (mntns_fd) - *mntns_fd = TAKE_FD(mntnsfd); + if (ret_mntns_fd) + *ret_mntns_fd = TAKE_FD(mntns_fd); - if (netns_fd) - *netns_fd = TAKE_FD(netnsfd); + if (ret_netns_fd) + *ret_netns_fd = TAKE_FD(netns_fd); - if (userns_fd) - *userns_fd = TAKE_FD(usernsfd); + if (ret_userns_fd) + *ret_userns_fd = TAKE_FD(userns_fd); - if (root_fd) - *root_fd = TAKE_FD(rfd); + if (ret_root_fd) + *ret_root_fd = TAKE_FD(root_fd); return 0; } @@ -206,6 +222,88 @@ int detach_mount_namespace(void) { return 0; } +int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid) { + int r; + + /* Tried detach_mount_namespace() first. If that doesn't work due to permissions, opens up an + * unprivileged user namespace with a mapping of the originating UID/GID to the specified target + * UID/GID. Then, tries detach_mount_namespace() again. + * + * Or in other words: tries much harder to get a mount namespace, making use of unprivileged user + * namespaces if need be. + * + * Note that after this function completed: + * + * → if we had privs, afterwards uids/gids on files and processes are as before + * + * → if we had no privs, our own id and all our files will show up owned by target_uid/target_gid, + * and everything else owned by nobody. + * + * Yes, that's quite a difference. */ + + if (!uid_is_valid(target_uid)) + return -EINVAL; + if (!gid_is_valid(target_gid)) + return -EINVAL; + + r = detach_mount_namespace(); + if (r != -EPERM) + return r; + + if (unshare(CLONE_NEWUSER) < 0) + return log_debug_errno(errno, "Failed to acquire user namespace: %m"); + + r = write_string_filef("/proc/self/uid_map", 0, + UID_FMT " " UID_FMT " 1\n", target_uid, getuid()); + if (r < 0) + return log_debug_errno(r, "Failed to write uid map: %m"); + + r = write_string_file("/proc/self/setgroups", "deny", 0); + if (r < 0) + return log_debug_errno(r, "Failed to write setgroups file: %m"); + + r = write_string_filef("/proc/self/gid_map", 0, + GID_FMT " " GID_FMT " 1\n", target_gid, getgid()); + if (r < 0) + return log_debug_errno(r, "Failed to write gid map: %m"); + + return detach_mount_namespace(); +} + +int detach_mount_namespace_userns(int userns_fd) { + int r; + + assert(userns_fd >= 0); + + if (setns(userns_fd, CLONE_NEWUSER) < 0) + return log_debug_errno(errno, "Failed to join user namespace: %m"); + + r = reset_uid_gid(); + if (r < 0) + return log_debug_errno(r, "Failed to become root in user namespace: %m"); + + return detach_mount_namespace(); +} + +int userns_acquire_empty(void) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + _cleanup_close_ int userns_fd = -EBADF; + int r; + + r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); + if (r < 0) + return r; + if (r == 0) + /* Child. We do nothing here, just freeze until somebody kills us. */ + freeze(); + + r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to open userns fd: %m"); + + return TAKE_FD(userns_fd); +} + int userns_acquire(const char *uid_map, const char *gid_map) { char path[STRLEN("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; _cleanup_(sigkill_waitp) pid_t pid = 0; @@ -221,7 +319,7 @@ int userns_acquire(const char *uid_map, const char *gid_map) { r = safe_fork("(sd-mkuserns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_USERNS, &pid); if (r < 0) - return r; + return log_debug_errno(r, "Failed to fork process (sd-mkuserns): %m"); if (r == 0) /* Child. We do nothing here, just freeze until somebody kills us. */ freeze(); @@ -229,19 +327,50 @@ int userns_acquire(const char *uid_map, const char *gid_map) { xsprintf(path, "/proc/" PID_FMT "/uid_map", pid); r = write_string_file(path, uid_map, WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) - return log_error_errno(r, "Failed to write UID map: %m"); + return log_debug_errno(r, "Failed to write UID map: %m"); xsprintf(path, "/proc/" PID_FMT "/gid_map", pid); r = write_string_file(path, gid_map, WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) - return log_error_errno(r, "Failed to write GID map: %m"); - - r = namespace_open(pid, NULL, NULL, NULL, &userns_fd, NULL); + return log_debug_errno(r, "Failed to write GID map: %m"); + + r = namespace_open(pid, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + /* ret_netns_fd = */ NULL, + &userns_fd, + /* ret_root_fd = */ NULL); if (r < 0) - return log_error_errno(r, "Failed to open userns fd: %m"); + return log_debug_errno(r, "Failed to open userns fd: %m"); return TAKE_FD(userns_fd); +} +int netns_acquire(void) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + _cleanup_close_ int netns_fd = -EBADF; + int r; + + /* Forks off a process in a new network namespace, acquires a network namespace fd, and then kills + * the process again. This way we have a netns fd that is not bound to any process. */ + + r = safe_fork("(sd-mknetns)", FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_NEW_NETNS, &pid); + if (r < 0) + return log_debug_errno(r, "Failed to fork process (sd-mknetns): %m"); + if (r == 0) + /* Child. We do nothing here, just freeze until somebody kills us. */ + freeze(); + + r = namespace_open(pid, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + &netns_fd, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); + if (r < 0) + return log_debug_errno(r, "Failed to open netns fd: %m"); + + return TAKE_FD(netns_fd); } int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) { @@ -267,3 +396,88 @@ int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type) { return stat_inode_same(&ns_st1, &ns_st2); } + +int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range) { + _cleanup_free_ char *buffer = NULL; + const char *range, *shift; + int r; + uid_t uid_shift, uid_range = 65536; + + assert(s); + + range = strchr(s, ':'); + if (range) { + buffer = strndup(s, range - s); + if (!buffer) + return log_oom(); + shift = buffer; + + range++; + r = safe_atou32(range, &uid_range); + if (r < 0) + return log_error_errno(r, "Failed to parse UID range \"%s\": %m", range); + } else + shift = s; + + r = parse_uid(shift, &uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to parse UID \"%s\": %m", s); + + if (!userns_shift_range_valid(uid_shift, uid_range)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID range cannot be empty or go beyond " UID_FMT ".", UID_INVALID); + + if (ret_uid_shift) + *ret_uid_shift = uid_shift; + + if (ret_uid_range) + *ret_uid_range = uid_range; + + return 0; +} + +int namespace_open_by_type(NamespaceType type) { + const char *p; + int fd; + + assert(type >= 0); + assert(type < _NAMESPACE_TYPE_MAX); + + p = pid_namespace_path(0, type); + + fd = RET_NERRNO(open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC)); + if (fd == -ENOENT && proc_mounted() == 0) + return -ENOSYS; + + return fd; +} + +int is_our_namespace(int fd, NamespaceType request_type) { + int clone_flag; + + assert(fd >= 0); + + clone_flag = ioctl(fd, NS_GET_NSTYPE); + if (clone_flag < 0) + return -errno; + + NamespaceType found_type = clone_flag_to_namespace_type(clone_flag); + if (found_type < 0) + return -EBADF; /* Uh? Unknown namespace type? */ + + if (request_type >= 0 && request_type != found_type) /* It's a namespace, but not of the right type? */ + return -EUCLEAN; + + struct stat st_fd, st_ours; + if (fstat(fd, &st_fd) < 0) + return -errno; + + const char *p = pid_namespace_path(0, found_type); + if (stat(p, &st_ours) < 0) { + if (errno == ENOENT) + return proc_mounted() == 0 ? -ENOSYS : -ENOENT; + + return -errno; + } + + return stat_inode_same(&st_ours, &st_fd); +} diff --git a/src/basic/namespace-util.h b/src/basic/namespace-util.h index be5b228..545952a 100644 --- a/src/basic/namespace-util.h +++ b/src/basic/namespace-util.h @@ -22,12 +22,20 @@ extern const struct namespace_info { unsigned int clone_flag; } namespace_info[_NAMESPACE_TYPE_MAX + 1]; -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd); +int namespace_open( + pid_t pid, + int *ret_pidns_fd, + int *ret_mntns_fd, + int *ret_netns_fd, + int *ret_userns_fd, + int *ret_root_fd); int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd); int fd_is_ns(int fd, unsigned long nsflag); int detach_mount_namespace(void); +int detach_mount_namespace_harder(uid_t target_uid, gid_t target_gid); +int detach_mount_namespace_userns(int userns_fd); static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { /* Checks that the specified userns range makes sense, i.e. contains at least one UID, and the end @@ -44,5 +52,15 @@ static inline bool userns_shift_range_valid(uid_t shift, uid_t range) { return true; } +int userns_acquire_empty(void); int userns_acquire(const char *uid_map, const char *gid_map); + +int netns_acquire(void); + int in_same_namespace(pid_t pid1, pid_t pid2, NamespaceType type); + +int parse_userns_uid_range(const char *s, uid_t *ret_uid_shift, uid_t *ret_uid_range); + +int namespace_open_by_type(NamespaceType type); + +int is_our_namespace(int fd, NamespaceType type); diff --git a/src/basic/nulstr-util.c b/src/basic/nulstr-util.c index 06fa219..7097a2c 100644 --- a/src/basic/nulstr-util.c +++ b/src/basic/nulstr-util.c @@ -4,7 +4,21 @@ #include "string-util.h" #include "strv.h" +const char* nulstr_get(const char *nulstr, const char *needle) { + if (!nulstr) + return NULL; + + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return i; + + return NULL; +} + char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) { + _cleanup_strv_free_ char **v = NULL; + size_t c = 0, i = 0; + /* 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 @@ -13,9 +27,6 @@ char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) * Note that contrary to a normal nulstr which cannot contain empty strings, because the input data * is terminated by any two consequent NUL bytes, this parser accepts empty strings in s. */ - _cleanup_strv_free_ char **v = NULL; - size_t c = 0, i = 0; - assert(s || l <= 0); if (drop_trailing_nuls) @@ -36,7 +47,7 @@ char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) if (!v) return NULL; - for (const char *p = s; p < s + l; ) { + for (const char *p = s; p < s + l;) { const char *e; e = memchr(p, 0, s + l - p); @@ -44,7 +55,6 @@ char** strv_parse_nulstr_full(const char *s, size_t l, bool drop_trailing_nuls) v[i] = memdup_suffix0(p, e ? e - p : s + l - p); if (!v[i]) return NULL; - i++; if (!e) @@ -74,6 +84,9 @@ char** strv_split_nulstr(const char *s) { } int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { + _cleanup_free_ char *m = NULL; + size_t n = 0; + /* Builds a nulstr and returns it together with the size. An extra NUL byte will be appended (⚠️ but * not included in the size! ⚠️). This is done so that the nulstr can be used both in * strv_parse_nulstr() and in NULSTR_FOREACH()/strv_split_nulstr() contexts, i.e. with and without a @@ -84,21 +97,18 @@ int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { * NUL bytes (which it will, if not empty). To ensure that this assumption *always* holds, we'll * return a buffer with two NUL bytes in that case, but return a size of zero. */ - _cleanup_free_ char *m = NULL; - size_t n = 0; - assert(ret); STRV_FOREACH(i, l) { size_t z; - z = strlen(*i); + z = strlen(*i) + 1; - if (!GREEDY_REALLOC(m, n + z + 2)) + if (!GREEDY_REALLOC(m, n + z + 1)) /* One extra NUL at the end as marker */ return -ENOMEM; - memcpy(m + n, *i, z + 1); - n += z + 1; + memcpy(m + n, *i, z); + n += z; } if (!m) { @@ -109,7 +119,7 @@ int strv_make_nulstr(char * const *l, char **ret, size_t *ret_size) { n = 0; } else - /* Make sure there is a second extra NUL at the end of resulting nulstr (not counted in return size) */ + /* Extra NUL is not counted in size returned */ m[n] = '\0'; *ret = TAKE_PTR(m); @@ -132,14 +142,3 @@ int set_make_nulstr(Set *s, char **ret, size_t *ret_size) { return strv_make_nulstr(strv, ret, ret_size); } - -const char* nulstr_get(const char *nulstr, const char *needle) { - if (!nulstr) - return NULL; - - NULSTR_FOREACH(i, nulstr) - if (streq(i, needle)) - return i; - - return NULL; -} diff --git a/src/basic/nulstr-util.h b/src/basic/nulstr-util.h index d7bc5fd..d6f2f58 100644 --- a/src/basic/nulstr-util.h +++ b/src/basic/nulstr-util.h @@ -15,7 +15,6 @@ for (typeof(*(l)) *(i) = (l), *(j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) const char* nulstr_get(const char *nulstr, const char *needle); - static inline bool nulstr_contains(const char *nulstr, const char *needle) { return nulstr_get(nulstr, needle); } @@ -25,9 +24,6 @@ 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); - static inline int strv_from_nulstr(char ***ret, const char *nulstr) { char **t; @@ -40,3 +36,6 @@ static inline int strv_from_nulstr(char ***ret, const char *nulstr) { *ret = t; return 0; } + +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/ordered-set.c b/src/basic/ordered-set.c index b4c2588..65cf3a0 100644 --- a/src/basic/ordered-set.c +++ b/src/basic/ordered-set.c @@ -91,13 +91,16 @@ void ordered_set_print(FILE *f, const char *field, OrderedSet *s) { bool space = false; char *p; + assert(f); + assert(field); + if (ordered_set_isempty(s)) return; fputs(field, f); ORDERED_SET_FOREACH(p, s) - fputs_with_space(f, p, NULL, &space); + fputs_with_separator(f, p, NULL, &space); fputc('\n', f); } diff --git a/src/basic/os-util.c b/src/basic/os-util.c index 985d89b..0d26d18 100644 --- a/src/basic/os-util.c +++ b/src/basic/os-util.c @@ -72,16 +72,11 @@ int path_extract_image_name(const char *path, char **ret) { r = path_extract_filename(path, &fn); if (r < 0) return r; - if (r != O_DIRECTORY) { - /* Chop off any image suffixes we recognize (unless we already know this must refer to some dir */ - FOREACH_STRING(suffix, ".sysext.raw", ".confext.raw", ".raw") { - char *m = endswith(fn, suffix); - if (m) { - *m = 0; - break; - } - } + /* Chop off any image suffixes we recognize (unless we already know this must refer to some dir) */ + char *m = ENDSWITH_SET(fn, ".sysext.raw", ".confext.raw", ".raw"); + if (m) + *m = 0; } /* Truncate the version/counting suffixes */ diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 0430e33..35d13eb 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -633,7 +633,7 @@ int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { s = *p; /* accept any number of digits, strtoull is limited to 19 */ - for (size_t i = 0; i < digits; i++,s++) { + for (size_t i = 0; i < digits; i++, s++) { if (!ascii_isdigit(*s)) { if (i == 0) return -EINVAL; @@ -691,7 +691,7 @@ int parse_ip_port(const char *s, uint16_t *ret) { return 0; } -int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) { +int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero) { unsigned l, h; int r; @@ -699,7 +699,10 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) { if (r < 0) return r; - if (l <= 0 || l > 65535 || h <= 0 || h > 65535) + if (l > 65535 || h > 65535) + return -EINVAL; + + if (!allow_zero && (l == 0 || h == 0)) return -EINVAL; if (h < l) diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 1845f0a..c12988e 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -139,7 +139,7 @@ int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); int parse_nice(const char *p, int *ret); int parse_ip_port(const char *s, uint16_t *ret); -int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high); +int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high, bool allow_zero); int parse_ip_prefix_length(const char *s, int *ret); diff --git a/src/basic/path-lookup.c b/src/basic/path-lookup.c index 4e3d59f..540256b 100644 --- a/src/basic/path-lookup.c +++ b/src/basic/path-lookup.c @@ -91,6 +91,37 @@ int xdg_user_data_dir(char **ret, const char *suffix) { return 1; } +int runtime_directory(char **ret, RuntimeScope scope, const char *suffix) { + int r; + + assert(ret); + assert(suffix); + assert(IN_SET(scope, RUNTIME_SCOPE_SYSTEM, RUNTIME_SCOPE_USER, RUNTIME_SCOPE_GLOBAL)); + + /* Accept $RUNTIME_DIRECTORY as authoritative + * If its missing apply the suffix to /run or $XDG_RUNTIME_DIR + * if we are in a user runtime scope. + * + * Return value indicates whether the suffix was applied or not */ + + const char *e = secure_getenv("RUNTIME_DIRECTORY"); + if (e) + return strdup_to(ret, e); + + if (scope == RUNTIME_SCOPE_USER) { + r = xdg_user_runtime_dir(ret, suffix); + if (r < 0) + return r; + } else { + char *d = path_join("/run", suffix); + if (!d) + return -ENOMEM; + *ret = d; + } + + return true; +} + static const char* const user_data_unit_paths[] = { "/usr/local/lib/systemd/user", "/usr/local/share/systemd/user", @@ -167,22 +198,16 @@ static char** user_dirs( return NULL; /* Now merge everything we found. */ - if (strv_extend(&res, persistent_control) < 0) + if (strv_extend_many( + &res, + persistent_control, + runtime_control, + transient, + generator_early, + persistent_config) < 0) return NULL; - if (strv_extend(&res, runtime_control) < 0) - return NULL; - - if (strv_extend(&res, transient) < 0) - return NULL; - - if (strv_extend(&res, generator_early) < 0) - return NULL; - - if (strv_extend(&res, persistent_config) < 0) - return NULL; - - if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) + if (strv_extend_strv_concat(&res, (const char* const*) config_dirs, "/systemd/user") < 0) return NULL; /* global config has lower priority than the user config of the same type */ @@ -192,19 +217,15 @@ static char** user_dirs( if (strv_extend_strv(&res, (char**) user_config_unit_paths, false) < 0) return NULL; - if (strv_extend(&res, runtime_config) < 0) - return NULL; - - if (strv_extend(&res, global_runtime_config) < 0) - return NULL; - - if (strv_extend(&res, generator) < 0) - return NULL; - - if (strv_extend(&res, data_home) < 0) + if (strv_extend_many( + &res, + runtime_config, + global_runtime_config, + generator, + data_home) < 0) return NULL; - if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) + if (strv_extend_strv_concat(&res, (const char* const*) data_dirs, "/systemd/user") < 0) return NULL; if (strv_extend_strv(&res, (char**) user_data_unit_paths, false) < 0) @@ -748,9 +769,8 @@ int lookup_paths_init_or_warn(LookupPaths *lp, RuntimeScope scope, LookupPathsFl return r; } -void lookup_paths_free(LookupPaths *lp) { - if (!lp) - return; +void lookup_paths_done(LookupPaths *lp) { + assert(lp); lp->search_path = strv_free(lp->search_path); diff --git a/src/basic/path-lookup.h b/src/basic/path-lookup.h index 1601787..0db2c5a 100644 --- a/src/basic/path-lookup.h +++ b/src/basic/path-lookup.h @@ -59,12 +59,13 @@ int xdg_user_dirs(char ***ret_config_dirs, char ***ret_data_dirs); int xdg_user_runtime_dir(char **ret, const char *suffix); int xdg_user_config_dir(char **ret, const char *suffix); int xdg_user_data_dir(char **ret, const char *suffix); +int runtime_directory(char **ret, RuntimeScope scope, const char *suffix); bool path_is_user_data_dir(const char *path); bool path_is_user_config_dir(const char *path); void lookup_paths_log(LookupPaths *p); -void lookup_paths_free(LookupPaths *p); +void lookup_paths_done(LookupPaths *p); char **generator_binary_paths(RuntimeScope scope); char **env_generator_binary_paths(RuntimeScope scope); diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 6810bf6..068fb42 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -217,8 +217,10 @@ int path_make_relative_parent(const char *from_child, const char *to, char **ret return path_make_relative(from, to, ret); } -char* path_startswith_strv(const char *p, char **set) { - STRV_FOREACH(s, set) { +char* path_startswith_strv(const char *p, char * const *strv) { + assert(p); + + STRV_FOREACH(s, strv) { char *t; t = path_startswith(p, *s); @@ -525,6 +527,18 @@ int path_compare_filename(const char *a, const char *b) { return strcmp(fa, fb); } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags) { + /* Returns true if paths are of the same entry, false if not, <0 on error. */ + + if (path_equal(a, b)) + return 1; + + if (!a || !b) + return 0; + + return inode_same(a, b, flags); +} + char* path_extend_internal(char **x, ...) { size_t sz, old_sz; char *q, *nx; @@ -684,7 +698,7 @@ int find_executable_full( * binary. */ p = getenv("PATH"); if (!p) - p = DEFAULT_PATH; + p = default_PATH(); if (exec_search_path) { STRV_FOREACH(element, exec_search_path) { @@ -1094,7 +1108,6 @@ int path_extract_filename(const char *path, char **ret) { } int path_extract_directory(const char *path, char **ret) { - _cleanup_free_ char *a = NULL; const char *c, *next = NULL; int r; @@ -1118,14 +1131,10 @@ int path_extract_directory(const char *path, char **ret) { if (*path != '/') /* filename only */ return -EDESTADDRREQ; - a = strdup("/"); - if (!a) - return -ENOMEM; - *ret = TAKE_PTR(a); - return 0; + return strdup_to(ret, "/"); } - a = strndup(path, next - path); + _cleanup_free_ char *a = strndup(path, next - path); if (!a) return -ENOMEM; @@ -1336,6 +1345,20 @@ bool dot_or_dot_dot(const char *path) { return path[2] == 0; } +bool path_implies_directory(const char *path) { + + /* Sometimes, if we look at a path we already know it must refer to a directory, because it is + * suffixed with a slash, or its last component is "." or ".." */ + + if (!path) + return false; + + if (dot_or_dot_dot(path)) + return true; + + return ENDSWITH_SET(path, "/", "/.", "/.."); +} + bool empty_or_root(const char *path) { /* For operations relative to some root directory, returns true if the specified root directory is @@ -1347,7 +1370,9 @@ bool empty_or_root(const char *path) { return path_equal(path, "/"); } -bool path_strv_contains(char **l, const char *path) { +bool path_strv_contains(char * const *l, const char *path) { + assert(path); + STRV_FOREACH(i, l) if (path_equal(*i, path)) return true; @@ -1355,7 +1380,9 @@ bool path_strv_contains(char **l, const char *path) { return false; } -bool prefixed_path_strv_contains(char **l, const char *path) { +bool prefixed_path_strv_contains(char * const *l, const char *path) { + assert(path); + STRV_FOREACH(i, l) { const char *j = *i; @@ -1363,6 +1390,7 @@ bool prefixed_path_strv_contains(char **l, const char *path) { j++; if (*j == '+') j++; + if (path_equal(j, path)) return true; } @@ -1432,3 +1460,31 @@ int path_glob_can_match(const char *pattern, const char *prefix, char **ret) { *ret = NULL; return false; } + +const char* default_PATH(void) { +#if HAVE_SPLIT_BIN + static int split = -1; + int r; + + /* Check whether /usr/sbin is not a symlink and return the appropriate $PATH. + * On error fall back to the safe value with both directories as configured… */ + + if (split < 0) + STRV_FOREACH_PAIR(bin, sbin, STRV_MAKE("/usr/bin", "/usr/sbin", + "/usr/local/bin", "/usr/local/sbin")) { + r = inode_same(*bin, *sbin, AT_NO_AUTOMOUNT); + if (r > 0 || r == -ENOENT) + continue; + if (r < 0) + log_debug_errno(r, "Failed to compare \"%s\" and \"%s\", using compat $PATH: %m", + *bin, *sbin); + split = true; + break; + } + if (split < 0) + split = false; + if (split) + return DEFAULT_PATH_WITH_SBIN; +#endif + return DEFAULT_PATH_WITHOUT_SBIN; +} diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 6d943e9..792b8ff 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -11,27 +11,26 @@ #include "strv.h" #include "time-util.h" -#define PATH_SPLIT_SBIN_BIN(x) x "sbin:" x "bin" -#define PATH_SPLIT_SBIN_BIN_NULSTR(x) x "sbin\0" x "bin\0" +#define PATH_SPLIT_BIN(x) x "sbin:" x "bin" +#define PATH_SPLIT_BIN_NULSTR(x) x "sbin\0" x "bin\0" -#define PATH_NORMAL_SBIN_BIN(x) x "bin" -#define PATH_NORMAL_SBIN_BIN_NULSTR(x) x "bin\0" +#define PATH_MERGED_BIN(x) x "bin" +#define PATH_MERGED_BIN_NULSTR(x) x "bin\0" -#if HAVE_SPLIT_BIN -# define PATH_SBIN_BIN(x) PATH_SPLIT_SBIN_BIN(x) -# define PATH_SBIN_BIN_NULSTR(x) PATH_SPLIT_SBIN_BIN_NULSTR(x) -#else -# define PATH_SBIN_BIN(x) PATH_NORMAL_SBIN_BIN(x) -# define PATH_SBIN_BIN_NULSTR(x) PATH_NORMAL_SBIN_BIN_NULSTR(x) -#endif +#define DEFAULT_PATH_WITH_SBIN PATH_SPLIT_BIN("/usr/local/") ":" PATH_SPLIT_BIN("/usr/") +#define DEFAULT_PATH_WITHOUT_SBIN PATH_MERGED_BIN("/usr/local/") ":" PATH_MERGED_BIN("/usr/") + +#define DEFAULT_PATH_COMPAT PATH_SPLIT_BIN("/usr/local/") ":" PATH_SPLIT_BIN("/usr/") ":" PATH_SPLIT_BIN("/") -#define DEFAULT_PATH PATH_SBIN_BIN("/usr/local/") ":" PATH_SBIN_BIN("/usr/") -#define DEFAULT_PATH_NULSTR PATH_SBIN_BIN_NULSTR("/usr/local/") PATH_SBIN_BIN_NULSTR("/usr/") -#define DEFAULT_PATH_COMPAT PATH_SPLIT_SBIN_BIN("/usr/local/") ":" PATH_SPLIT_SBIN_BIN("/usr/") ":" PATH_SPLIT_SBIN_BIN("/") +const char* default_PATH(void); -#ifndef DEFAULT_USER_PATH -# define DEFAULT_USER_PATH DEFAULT_PATH +static inline const char* default_user_PATH(void) { +#ifdef DEFAULT_USER_PATH + return DEFAULT_USER_PATH; +#else + return default_PATH(); #endif +} static inline bool is_path(const char *p) { if (!p) /* A NULL pointer is definitely not a path */ @@ -68,14 +67,19 @@ static inline bool path_equal_filename(const char *a, const char *b) { return path_compare_filename(a, b) == 0; } +int path_equal_or_inode_same_full(const char *a, const char *b, int flags); static inline bool path_equal_or_inode_same(const char *a, const char *b, int flags) { - return path_equal(a, b) || inode_same(a, b, flags) > 0; + return path_equal_or_inode_same_full(a, b, flags) > 0; } char* path_extend_internal(char **x, ...); #define path_extend(x, ...) path_extend_internal(x, __VA_ARGS__, POINTER_MAX) #define path_join(...) path_extend_internal(NULL, __VA_ARGS__, POINTER_MAX) +static inline char* skip_leading_slash(const char *p) { + return skip_leading_chars(p, "/"); +} + typedef enum PathSimplifyFlags { PATH_SIMPLIFY_KEEP_TRAILING_SLASH = 1 << 0, } PathSimplifyFlags; @@ -101,14 +105,10 @@ static inline int path_simplify_alloc(const char *path, char **ret) { return 0; } -static inline bool path_equal_ptr(const char *a, const char *b) { - return !!a == !!b && (!a || path_equal(a, b)); -} - /* Note: the search terminates on the first NULL item. */ #define PATH_IN_SET(p, ...) path_strv_contains(STRV_MAKE(__VA_ARGS__), p) -char* path_startswith_strv(const char *p, char **set); +char* path_startswith_strv(const char *p, char * const *strv); #define PATH_STARTSWITH_SET(p, ...) path_startswith_strv(p, STRV_MAKE(__VA_ARGS__)) int path_strv_make_absolute_cwd(char **l); @@ -156,7 +156,7 @@ int fsck_exists_for_fstype(const char *fstype); char *_p, *_n; \ size_t _l; \ while (_path[0] == '/' && _path[1] == '/') \ - _path ++; \ + _path++; \ if (isempty(_root)) \ _ret = _path; \ else { \ @@ -201,6 +201,8 @@ bool valid_device_allow_pattern(const char *path); bool dot_or_dot_dot(const char *path); +bool path_implies_directory(const char *path); + static inline const char *skip_dev_prefix(const char *p) { const char *e; @@ -216,7 +218,7 @@ static inline const char* empty_to_root(const char *path) { return isempty(path) ? "/" : path; } -bool path_strv_contains(char **l, const char *path); -bool prefixed_path_strv_contains(char **l, const char *path); +bool path_strv_contains(char * const *l, const char *path); +bool prefixed_path_strv_contains(char * const *l, const char *path); int path_glob_can_match(const char *pattern, const char *prefix, char **ret); diff --git a/src/basic/pidref.c b/src/basic/pidref.c index 69b5cad..69a0102 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -1,12 +1,44 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#if HAVE_PIDFD_OPEN +#include +#endif + #include "errno-util.h" #include "fd-util.h" #include "missing_syscall.h" +#include "missing_wait.h" #include "parse-util.h" #include "pidref.h" #include "process-util.h" #include "signal-util.h" +#include "stat-util.h" + +bool pidref_equal(const PidRef *a, const PidRef *b) { + int r; + + if (pidref_is_set(a)) { + if (!pidref_is_set(b)) + return false; + + if (a->pid != b->pid) + return false; + + if (a->fd < 0 || b->fd < 0) + return true; + + /* pidfds live in their own pidfs and each process comes with a unique inode number since + * kernel 6.8. We can safely do this on older kernels too though, as previously anonymous + * inode was used and inode number was the same for all pidfds. */ + r = fd_inode_same(a->fd, b->fd); + if (r < 0) + log_debug_errno(r, "Failed to check whether pidfds for pid " PID_FMT " are equal, assuming yes: %m", + a->pid); + return r != 0; + } + + return !pidref_is_set(b); +} int pidref_set_pid(PidRef *pidref, pid_t pid) { int fd; @@ -22,7 +54,7 @@ int pidref_set_pid(PidRef *pidref, pid_t pid) { if (fd < 0) { /* Graceful fallback in case the kernel doesn't support pidfds or is out of fds */ if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno) && !ERRNO_IS_RESOURCE(errno)) - return -errno; + return log_debug_errno(errno, "Failed to open pidfd for pid " PID_FMT ": %m", pid); fd = -EBADF; } @@ -106,6 +138,38 @@ int pidref_set_pidfd_consume(PidRef *pidref, int fd) { return r; } +int pidref_set_parent(PidRef *ret) { + _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; + pid_t ppid; + int r; + + assert(ret); + + /* Acquires a pidref to our parent process. Deals with the fact that parent processes might exit, and + * we get reparented to other processes, with our old parent's PID already being recycled. */ + + ppid = getppid(); + for (;;) { + r = pidref_set_pid(&parent, ppid); + if (r < 0) + return r; + + if (parent.fd < 0) /* If pidfds are not available, then we are done */ + break; + + pid_t now_ppid = getppid(); + if (now_ppid == ppid) /* If our ppid is still the same, then we are done */ + break; + + /* Otherwise let's try again with the new ppid */ + ppid = now_ppid; + pidref_done(&parent); + } + + *ret = TAKE_PIDREF(parent); + return 0; +} + void pidref_done(PidRef *pidref) { assert(pidref); @@ -123,11 +187,11 @@ PidRef *pidref_free(PidRef *pidref) { return mfree(pidref); } -int pidref_dup(const PidRef *pidref, PidRef **ret) { +int pidref_copy(const PidRef *pidref, PidRef *dest) { _cleanup_close_ int dup_fd = -EBADF; pid_t dup_pid = 0; - assert(ret); + assert(dest); /* Allocates a new PidRef on the heap, making it a copy of the specified pidref. This does not try to * acquire a pidfd if we don't have one yet! @@ -150,21 +214,34 @@ int pidref_dup(const PidRef *pidref, PidRef **ret) { dup_pid = pidref->pid; } - PidRef *dup_pidref = new(PidRef, 1); - if (!dup_pidref) - return -ENOMEM; - - *dup_pidref = (PidRef) { + *dest = (PidRef) { .fd = TAKE_FD(dup_fd), .pid = dup_pid, }; + return 0; +} + +int pidref_dup(const PidRef *pidref, PidRef **ret) { + _cleanup_(pidref_freep) PidRef *dup_pidref = NULL; + int r; + + assert(ret); + + dup_pidref = newdup(PidRef, &PIDREF_NULL, 1); + if (!dup_pidref) + return -ENOMEM; + + r = pidref_copy(pidref, dup_pidref); + if (r < 0) + return r; + *ret = TAKE_PTR(dup_pidref); return 0; } int pidref_new_from_pid(pid_t pid, PidRef **ret) { - _cleanup_(pidref_freep) PidRef *n = 0; + _cleanup_(pidref_freep) PidRef *n = NULL; int r; assert(ret); @@ -270,8 +347,46 @@ bool pidref_is_self(const PidRef *pidref) { return pidref->pid == getpid_cached(); } +int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) { + int r; + + if (!pidref_is_set(pidref)) + return -ESRCH; + + if (pidref->pid == 1 || pidref->pid == getpid_cached()) + return -ECHILD; + + siginfo_t si = {}; + + if (pidref->fd >= 0) { + r = RET_NERRNO(waitid(P_PIDFD, pidref->fd, &si, options)); + if (r >= 0) { + if (ret) + *ret = si; + return r; + } + if (r != -EINVAL) /* P_PIDFD was added in kernel 5.4 only */ + return r; + } + + r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, options)); + if (r >= 0 && ret) + *ret = si; + return r; +} + +int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) { + int r; + + for (;;) { + r = pidref_wait(pidref, ret, WEXITED); + if (r != -EINTR) + return r; + } +} + static void pidref_hash_func(const PidRef *pidref, struct siphash *state) { - siphash24_compress(&pidref->pid, sizeof(pidref->pid), state); + siphash24_compress_typesafe(pidref->pid, state); } static int pidref_compare_func(const PidRef *a, const PidRef *b) { diff --git a/src/basic/pidref.h b/src/basic/pidref.h index dada069..9920ebb 100644 --- a/src/basic/pidref.h +++ b/src/basic/pidref.h @@ -19,17 +19,7 @@ static inline bool pidref_is_set(const PidRef *pidref) { return pidref && pidref->pid > 0; } -static inline bool pidref_equal(const PidRef *a, const PidRef *b) { - - if (pidref_is_set(a)) { - if (!pidref_is_set(b)) - return false; - - return a->pid == b->pid; - } - - return !pidref_is_set(b); -} +bool pidref_equal(const PidRef *a, const PidRef *b); /* This turns a pid_t into a PidRef structure, and acquires a pidfd for it, if possible. (As opposed to * PIDREF_MAKE_FROM_PID() above, which does not acquire a pidfd.) */ @@ -38,7 +28,7 @@ int pidref_set_pidstr(PidRef *pidref, const char *pid); int pidref_set_pidfd(PidRef *pidref, int fd); int pidref_set_pidfd_take(PidRef *pidref, int fd); /* takes ownership of the passed pidfd on success*/ int pidref_set_pidfd_consume(PidRef *pidref, int fd); /* takes ownership of the passed pidfd in both success and failure */ - +int pidref_set_parent(PidRef *ret); static inline int pidref_set_self(PidRef *pidref) { return pidref_set_pid(pidref, 0); } @@ -49,13 +39,26 @@ void pidref_done(PidRef *pidref); PidRef *pidref_free(PidRef *pidref); DEFINE_TRIVIAL_CLEANUP_FUNC(PidRef*, pidref_free); +int pidref_copy(const PidRef *pidref, PidRef *dest); int pidref_dup(const PidRef *pidref, PidRef **ret); int pidref_new_from_pid(pid_t pid, PidRef **ret); int pidref_kill(const PidRef *pidref, int sig); int pidref_kill_and_sigcont(const PidRef *pidref, int sig); -int pidref_sigqueue(const PidRef *pidfref, int sig, int value); +int pidref_sigqueue(const PidRef *pidref, int sig, int value); + +int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options); +int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret); + +static inline void pidref_done_sigkill_wait(PidRef *pidref) { + if (!pidref_is_set(pidref)) + return; + + (void) pidref_kill(pidref, SIGKILL); + (void) pidref_wait_for_terminate(pidref, NULL); + pidref_done(pidref); +} int pidref_verify(const PidRef *pidref); diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 522d8de..ce1ba3a 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -116,16 +116,8 @@ int proc_cmdline(char **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) { - char *m; - - m = strdup(e); - if (!m) - return -ENOMEM; - - *ret = m; - return 0; - } + if (e) + return strdup_to(ret, e); if (detect_container() > 0) return pid_get_cmdline(1, SIZE_MAX, 0, ret); diff --git a/src/basic/process-util.c b/src/basic/process-util.c index 4492e7d..c9d968d 100644 --- a/src/basic/process-util.c +++ b/src/basic/process-util.c @@ -25,6 +25,7 @@ #include "alloc-util.h" #include "architecture.h" #include "argv-util.h" +#include "cgroup-util.h" #include "dirent-util.h" #include "env-file.h" #include "env-util.h" @@ -510,7 +511,7 @@ static int get_process_link_contents(pid_t pid, const char *proc_file, char **re p = procfs_file_alloca(pid, proc_file); r = readlink_malloc(p, ret); - return r == -ENOENT ? -ESRCH : r; + return (r == -ENOENT && proc_mounted() > 0) ? -ESRCH : r; } int get_process_exe(pid_t pid, char **ret) { @@ -730,6 +731,82 @@ int get_process_ppid(pid_t pid, pid_t *ret) { return 0; } +int pid_get_start_time(pid_t pid, uint64_t *ret) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* Let's skip the pid and comm fields. The latter is enclosed in () but does not escape any () in its + * value, so let's skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + unsigned long llu; + + if (sscanf(p, " " + "%*c " /* state */ + "%*u " /* ppid */ + "%*u " /* pgrp */ + "%*u " /* session */ + "%*u " /* tty_nr */ + "%*u " /* tpgid */ + "%*u " /* flags */ + "%*u " /* minflt */ + "%*u " /* cminflt */ + "%*u " /* majflt */ + "%*u " /* cmajflt */ + "%*u " /* utime */ + "%*u " /* stime */ + "%*u " /* cutime */ + "%*u " /* cstime */ + "%*i " /* priority */ + "%*i " /* nice */ + "%*u " /* num_threads */ + "%*u " /* itrealvalue */ + "%lu ", /* starttime */ + &llu) != 1) + return -EIO; + + if (ret) + *ret = llu; + + return 0; +} + +int pidref_get_start_time(const PidRef *pid, uint64_t *ret) { + uint64_t t; + int r; + + if (!pidref_is_set(pid)) + return -ESRCH; + + r = pid_get_start_time(pid->pid, ret ? &t : NULL); + if (r < 0) + return r; + + r = pidref_verify(pid); + if (r < 0) + return r; + + if (ret) + *ret = t; + + return 0; +} + int get_process_umask(pid_t pid, mode_t *ret) { _cleanup_free_ char *m = NULL; const char *p; @@ -946,31 +1023,16 @@ int kill_and_sigcont(pid_t pid, int sig) { int getenv_for_pid(pid_t pid, const char *field, char **ret) { _cleanup_fclose_ FILE *f = NULL; - char *value = NULL; const char *path; - size_t l, sum = 0; + size_t sum = 0; int r; assert(pid >= 0); assert(field); assert(ret); - if (pid == 0 || pid == getpid_cached()) { - const char *e; - - e = getenv(field); - if (!e) { - *ret = NULL; - return 0; - } - - value = strdup(e); - if (!value) - return -ENOMEM; - - *ret = value; - return 1; - } + if (pid == 0 || pid == getpid_cached()) + return strdup_to_full(ret, getenv(field)); if (!pid_is_valid(pid)) return -EINVAL; @@ -983,9 +1045,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { if (r < 0) return r; - l = strlen(field); for (;;) { _cleanup_free_ char *line = NULL; + const char *match; if (sum > ENVIRONMENT_BLOCK_MAX) /* Give up searching eventually */ return -ENOBUFS; @@ -998,14 +1060,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) { sum += r; - if (strneq(line, field, l) && line[l] == '=') { - value = strdup(line + l + 1); - if (!value) - return -ENOMEM; - - *ret = value; - return 1; - } + match = startswith(line, field); + if (match && *match == '=') + return strdup_to_full(ret, match + 1); } *ret = NULL; @@ -1112,8 +1169,10 @@ int pidref_is_alive(const PidRef *pidref) { return -ESRCH; result = pid_is_alive(pidref->pid); - if (result < 0) + if (result < 0) { + assert(result != -ESRCH); return result; + } r = pidref_verify(pidref); if (r == -ESRCH) @@ -1224,7 +1283,7 @@ int opinionated_personality(unsigned long *ret) { if (current < 0) return current; - if (((unsigned long) current & 0xffff) == PER_LINUX32) + if (((unsigned long) current & OPINIONATED_PERSONALITY_MASK) == PER_LINUX32) *ret = PER_LINUX32; else *ret = PER_LINUX; @@ -1389,7 +1448,7 @@ static int fork_flags_to_signal(ForkFlags flags) { int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid) { @@ -1462,10 +1521,11 @@ int safe_fork_full( } } - if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS)) != 0) + if ((flags & (FORK_NEW_MOUNTNS|FORK_NEW_USERNS|FORK_NEW_NETNS)) != 0) pid = raw_clone(SIGCHLD| (FLAGS_SET(flags, FORK_NEW_MOUNTNS) ? CLONE_NEWNS : 0) | - (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0)); + (FLAGS_SET(flags, FORK_NEW_USERNS) ? CLONE_NEWUSER : 0) | + (FLAGS_SET(flags, FORK_NEW_NETNS) ? CLONE_NEWNET : 0)); else pid = fork(); if (pid < 0) @@ -1589,6 +1649,9 @@ int safe_fork_full( log_full_errno(prio, r, "Failed to rearrange stdio fds: %m"); _exit(EXIT_FAILURE); } + + /* Turn off O_NONBLOCK on the fdio fds, in case it was left on */ + stdio_disable_nonblock(); } else { r = make_null_stdio(); if (r < 0) { @@ -1614,6 +1677,19 @@ int safe_fork_full( } } + if (flags & FORK_PACK_FDS) { + /* FORK_CLOSE_ALL_FDS ensures that except_fds are the only FDs >= 3 that are + * open, this is including the log. This is required by pack_fds, which will + * get stuck in an infinite loop of any FDs other than except_fds are open. */ + assert(FLAGS_SET(flags, FORK_CLOSE_ALL_FDS)); + + r = pack_fds(except_fds, n_except_fds); + if (r < 0) { + log_full_errno(prio, r, "Failed to pack file descriptors: %m"); + _exit(EXIT_FAILURE); + } + } + if (flags & FORK_CLOEXEC_OFF) { r = fd_cloexec_many(except_fds, n_except_fds, false); if (r < 0) { @@ -1650,10 +1726,34 @@ int safe_fork_full( return 0; } +int pidref_safe_fork_full( + const char *name, + const int stdio_fds[3], + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + PidRef *ret_pid) { + + pid_t pid; + int r, q; + + assert(!FLAGS_SET(flags, FORK_WAIT)); + + r = safe_fork_full(name, stdio_fds, except_fds, n_except_fds, flags, &pid); + if (r < 0) + return r; + + q = pidref_set_pid(ret_pid, pid); + if (q < 0) /* Let's not fail for this, no matter what, the process exists after all, and that's key */ + *ret_pid = PIDREF_MAKE_FROM_PID(pid); + + return r; +} + int namespace_fork( const char *outer_name, const char *inner_name, - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, @@ -1927,47 +2027,115 @@ int make_reaper_process(bool b) { return 0; } -int posix_spawn_wrapper(const char *path, char *const *argv, char *const *envp, pid_t *ret_pid) { +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(posix_spawnattr_t*, posix_spawnattr_destroy, NULL); + +int posix_spawn_wrapper( + const char *path, + char * const *argv, + char * const *envp, + const char *cgroup, + PidRef *ret_pidref) { + + short flags = POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF; posix_spawnattr_t attr; sigset_t mask; - pid_t pid; int r; /* Forks and invokes 'path' with 'argv' and 'envp' using CLONE_VM and CLONE_VFORK, which means the * caller will be blocked until the child either exits or exec's. The memory of the child will be * fully shared with the memory of the parent, so that there are no copy-on-write or memory.max - * issues. */ + * issues. + * + * Also, move the newly-created process into 'cgroup' through POSIX_SPAWN_SETCGROUP (clone3()) + * if available. Note that CLONE_INTO_CGROUP is only supported on cgroup v2. + * returns 1: We're already in the right cgroup + * 0: 'cgroup' not specified or POSIX_SPAWN_SETCGROUP is not supported. The caller + * needs to call 'cg_attach' on their own */ assert(path); assert(argv); - assert(ret_pid); + assert(ret_pidref); assert_se(sigfillset(&mask) >= 0); r = posix_spawnattr_init(&attr); if (r != 0) return -r; /* These functions return a positive errno on failure */ - /* Set all signals to SIG_DFL */ - r = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK|POSIX_SPAWN_SETSIGDEF); + + /* Initialization needs to succeed before we can set up a destructor. */ + _unused_ _cleanup_(posix_spawnattr_destroyp) posix_spawnattr_t *attr_destructor = &attr; + +#if HAVE_PIDFD_SPAWN + _cleanup_close_ int cgroup_fd = -EBADF; + + if (cgroup) { + _cleanup_free_ char *resolved_cgroup = NULL; + + r = cg_get_path_and_check( + SYSTEMD_CGROUP_CONTROLLER, + cgroup, + /* suffix= */ NULL, + &resolved_cgroup); + if (r < 0) + return r; + + cgroup_fd = open(resolved_cgroup, O_PATH|O_DIRECTORY|O_CLOEXEC); + if (cgroup_fd < 0) + return -errno; + + r = posix_spawnattr_setcgroup_np(&attr, cgroup_fd); + if (r != 0) + return -r; + + flags |= POSIX_SPAWN_SETCGROUP; + } +#endif + + r = posix_spawnattr_setflags(&attr, flags); if (r != 0) - goto fail; + return -r; r = posix_spawnattr_setsigmask(&attr, &mask); if (r != 0) - goto fail; + return -r; - r = posix_spawn(&pid, path, NULL, &attr, argv, envp); +#if HAVE_PIDFD_SPAWN + _cleanup_close_ int pidfd = -EBADF; + + r = pidfd_spawn(&pidfd, path, NULL, &attr, argv, envp); + if (r == 0) { + r = pidref_set_pidfd_consume(ret_pidref, TAKE_FD(pidfd)); + if (r < 0) + return r; + + return FLAGS_SET(flags, POSIX_SPAWN_SETCGROUP); + } + if (ERRNO_IS_NOT_SUPPORTED(r)) { + /* clone3() could also return EOPNOTSUPP if the target cgroup is in threaded mode. */ + if (cgroup && cg_is_threaded(cgroup) > 0) + return -EUCLEAN; + + /* clone3() not available? */ + } else if (!ERRNO_IS_PRIVILEGE(r)) + return -r; + + /* Compiled on a newer host, or seccomp&friends blocking clone3()? Fallback, but need to change the + * flags to remove the cgroup one, which is what redirects to clone3() */ + flags &= ~POSIX_SPAWN_SETCGROUP; + r = posix_spawnattr_setflags(&attr, flags); if (r != 0) - goto fail; + return -r; +#endif - *ret_pid = pid; + pid_t pid; + r = posix_spawn(&pid, path, NULL, &attr, argv, envp); + if (r != 0) + return -r; - posix_spawnattr_destroy(&attr); - return 0; + r = pidref_set_pid(ret_pidref, pid); + if (r < 0) + return r; -fail: - assert(r > 0); - posix_spawnattr_destroy(&attr); - return -r; + return 0; /* We did not use CLONE_INTO_CGROUP so return 0, the caller will have to move the child */ } int proc_dir_open(DIR **ret) { diff --git a/src/basic/process-util.h b/src/basic/process-util.h index af6cba1..8308402 100644 --- a/src/basic/process-util.h +++ b/src/basic/process-util.h @@ -54,6 +54,8 @@ int get_process_cwd(pid_t pid, char **ret); int get_process_root(pid_t pid, char **ret); int get_process_environ(pid_t pid, char **ret); int get_process_ppid(pid_t pid, pid_t *ret); +int pid_get_start_time(pid_t pid, uint64_t *ret); +int pidref_get_start_time(const PidRef* pid, uint64_t *ret); int get_process_umask(pid_t pid, mode_t *ret); int container_get_leader(const char *machine, pid_t *pid); @@ -99,12 +101,17 @@ bool is_main_thread(void); bool oom_score_adjust_is_valid(int oa); #ifndef PERSONALITY_INVALID -/* personality(7) documents that 0xffffffffUL is used for querying the +/* personality(2) documents that 0xFFFFFFFFUL is used for querying the * current personality, hence let's use that here as error * indicator. */ -#define PERSONALITY_INVALID 0xffffffffLU +#define PERSONALITY_INVALID 0xFFFFFFFFUL #endif +/* The personality() syscall returns a 32-bit value where the top three bytes are reserved for flags that + * emulate historical or architectural quirks, and only the least significant byte reflects the actual + * personality we're interested in. */ +#define OPINIONATED_PERSONALITY_MASK 0xFFUL + unsigned long personality_from_string(const char *p); const char *personality_to_string(unsigned long); @@ -152,11 +159,11 @@ int must_be_root(void); pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata); -/* 💣 Note that FORK_NEW_USERNS + FORK_NEW_MOUNTNS should not be called in threaded programs, because they - * cause us to use raw_clone() which does not synchronize the glibc malloc() locks, and thus will cause - * deadlocks if the parent uses threads and the child does memory allocations. Hence: if the parent is - * threaded these flags may not be used. These flags cannot be used if the parent uses threads or the child - * uses malloc(). 💣 */ +/* 💣 Note that FORK_NEW_USERNS, FORK_NEW_MOUNTNS, or FORK_NEW_NETNS should not be called in threaded + * programs, because they cause us to use raw_clone() which does not synchronize the glibc malloc() locks, + * and thus will cause deadlocks if the parent uses threads and the child does memory allocations. Hence: if + * the parent is threaded these flags may not be used. These flags cannot be used if the parent uses threads + * or the child uses malloc(). 💣 */ typedef enum ForkFlags { FORK_RESET_SIGNALS = 1 << 0, /* Reset all signal handlers and signal mask */ FORK_CLOSE_ALL_FDS = 1 << 1, /* Close all open file descriptors in the child, except for 0,1,2 */ @@ -177,12 +184,14 @@ typedef enum ForkFlags { FORK_CLOEXEC_OFF = 1 << 16, /* In the child: turn off O_CLOEXEC on all fds in except_fds[] */ FORK_KEEP_NOTIFY_SOCKET = 1 << 17, /* Unless this specified, $NOTIFY_SOCKET will be unset. */ FORK_DETACH = 1 << 18, /* Double fork if needed to ensure PID1/subreaper is parent */ + FORK_NEW_NETNS = 1 << 19, /* Run child in its own network namespace 💣 DO NOT USE IN THREADED PROGRAMS! 💣 */ + FORK_PACK_FDS = 1 << 20, /* Rearrange the passed FDs to be FD 3,4,5,etc. Updates the array in place (combine with FORK_CLOSE_ALL_FDS!) */ } ForkFlags; int safe_fork_full( const char *name, const int stdio_fds[3], - const int except_fds[], + int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid); @@ -191,7 +200,30 @@ static inline int safe_fork(const char *name, ForkFlags flags, pid_t *ret_pid) { return safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); } -int namespace_fork(const char *outer_name, const char *inner_name, const int except_fds[], size_t n_except_fds, ForkFlags flags, int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd, pid_t *ret_pid); +int pidref_safe_fork_full( + const char *name, + const int stdio_fds[3], + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + PidRef *ret_pid); + +static inline int pidref_safe_fork(const char *name, ForkFlags flags, PidRef *ret_pid) { + return pidref_safe_fork_full(name, NULL, NULL, 0, flags, ret_pid); +} + +int namespace_fork( + const char *outer_name, + const char *inner_name, + int except_fds[], + size_t n_except_fds, + ForkFlags flags, + int pidns_fd, + int mntns_fd, + int netns_fd, + int userns_fd, + int root_fd, + pid_t *ret_pid); int set_oom_score_adjust(int value); int get_oom_score_adjust(int *ret); @@ -223,7 +255,12 @@ int get_process_threads(pid_t pid); int is_reaper_process(void); int make_reaper_process(bool b); -int posix_spawn_wrapper(const char *path, char *const *argv, char *const *envp, pid_t *ret_pid); +int posix_spawn_wrapper( + const char *path, + char * const *argv, + char * const *envp, + const char *cgroup, + PidRef *ret_pidref); int proc_dir_open(DIR **ret); int proc_dir_read(DIR *d, pid_t *ret); diff --git a/src/basic/recurse-dir.c b/src/basic/recurse-dir.c index 5e98b7a..7767331 100644 --- a/src/basic/recurse-dir.c +++ b/src/basic/recurse-dir.c @@ -4,6 +4,7 @@ #include "dirent-util.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "missing_syscall.h" #include "mountpoint-util.h" #include "recurse-dir.h" @@ -132,6 +133,18 @@ int readdir_all(int dir_fd, return 0; } +int readdir_all_at(int fd, const char *path, RecurseDirFlags flags, DirectoryEntries **ret) { + _cleanup_close_ int dir_fd = -EBADF; + + assert(fd >= 0 || fd == AT_FDCWD); + + dir_fd = xopenat(fd, path, O_DIRECTORY|O_CLOEXEC); + if (dir_fd < 0) + return dir_fd; + + return readdir_all(dir_fd, flags, ret); +} + int recurse_dir( int dir_fd, const char *path, diff --git a/src/basic/recurse-dir.h b/src/basic/recurse-dir.h index 9f6a7ad..aaeae95 100644 --- a/src/basic/recurse-dir.h +++ b/src/basic/recurse-dir.h @@ -76,6 +76,7 @@ typedef struct DirectoryEntries { } DirectoryEntries; int readdir_all(int dir_fd, RecurseDirFlags flags, DirectoryEntries **ret); +int readdir_all_at(int fd, const char *path, RecurseDirFlags flags, DirectoryEntries **ret); int recurse_dir(int dir_fd, const char *path, unsigned statx_mask, unsigned n_depth_max, RecurseDirFlags flags, recurse_dir_func_t func, void *userdata); int recurse_dir_at(int atfd, const char *path, unsigned statx_mask, unsigned n_depth_max, RecurseDirFlags flags, recurse_dir_func_t func, void *userdata); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c index c1f0b2b..a9f7b87 100644 --- a/src/basic/rlimit-util.c +++ b/src/basic/rlimit-util.c @@ -6,11 +6,14 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "fileio.h" #include "format-util.h" #include "macro.h" #include "missing_resource.h" +#include "process-util.h" #include "rlimit-util.h" #include "string-table.h" +#include "strv.h" #include "time-util.h" int setrlimit_closest(int resource, const struct rlimit *rlim) { @@ -426,3 +429,116 @@ int rlimit_nofile_safe(void) { return 1; } + +int pid_getrlimit(pid_t pid, int resource, struct rlimit *ret) { + + static const char * const prefix_table[_RLIMIT_MAX] = { + [RLIMIT_CPU] = "Max cpu time", + [RLIMIT_FSIZE] = "Max file size", + [RLIMIT_DATA] = "Max data size", + [RLIMIT_STACK] = "Max stack size", + [RLIMIT_CORE] = "Max core file size", + [RLIMIT_RSS] = "Max resident set", + [RLIMIT_NPROC] = "Max processes", + [RLIMIT_NOFILE] = "Max open files", + [RLIMIT_MEMLOCK] = "Max locked memory", + [RLIMIT_AS] = "Max address space", + [RLIMIT_LOCKS] = "Max file locks", + [RLIMIT_SIGPENDING] = "Max pending signals", + [RLIMIT_MSGQUEUE] = "Max msgqueue size", + [RLIMIT_NICE] = "Max nice priority", + [RLIMIT_RTPRIO] = "Max realtime priority", + [RLIMIT_RTTIME] = "Max realtime timeout", + }; + + int r; + + assert(resource >= 0); + assert(resource < _RLIMIT_MAX); + assert(pid >= 0); + assert(ret); + + if (pid == 0 || pid == getpid_cached()) + return RET_NERRNO(getrlimit(resource, ret)); + + r = RET_NERRNO(prlimit(pid, resource, /* new_limit= */ NULL, ret)); + if (!ERRNO_IS_NEG_PRIVILEGE(r)) + return r; + + /* We don't have access? Then try to go via /proc/$PID/limits. Weirdly that's world readable in + * contrast to querying the data via prlimit() */ + + const char *p = procfs_file_alloca(pid, "limits"); + _cleanup_free_ char *limits = NULL; + + r = read_full_virtual_file(p, &limits, NULL); + if (r < 0) + return -EPERM; /* propagate original permission error if we can't access the limits file */ + + _cleanup_strv_free_ char **l = NULL; + l = strv_split(limits, "\n"); + if (!l) + return -ENOMEM; + + STRV_FOREACH(i, strv_skip(l, 1)) { + _cleanup_free_ char *soft = NULL, *hard = NULL; + uint64_t sv, hv; + const char *e; + + e = startswith(*i, prefix_table[resource]); + if (!e) + continue; + + if (*e != ' ') + continue; + + e += strspn(e, WHITESPACE); + + size_t n; + n = strcspn(e, WHITESPACE); + if (n == 0) + continue; + + soft = strndup(e, n); + if (!soft) + return -ENOMEM; + + e += n; + if (*e != ' ') + continue; + + e += strspn(e, WHITESPACE); + n = strcspn(e, WHITESPACE); + if (n == 0) + continue; + + hard = strndup(e, n); + if (!hard) + return -ENOMEM; + + if (streq(soft, "unlimited")) + sv = RLIM_INFINITY; + else { + r = safe_atou64(soft, &sv); + if (r < 0) + return r; + } + + if (streq(hard, "unlimited")) + hv = RLIM_INFINITY; + else { + r = safe_atou64(hard, &hv); + if (r < 0) + return r; + } + + *ret = (struct rlimit) { + .rlim_cur = sv, + .rlim_max = hv, + }; + + return 0; + } + + return -ENOTRECOVERABLE; +} diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h index 202c3fd..afc1a1f 100644 --- a/src/basic/rlimit-util.h +++ b/src/basic/rlimit-util.h @@ -25,3 +25,5 @@ void rlimit_free_all(struct rlimit **rl); int rlimit_nofile_bump(int limit); int rlimit_nofile_safe(void); + +int pid_getrlimit(pid_t pid, int resource, struct rlimit *ret); diff --git a/src/basic/sha256.c b/src/basic/sha256.c new file mode 100644 index 0000000..f011695 --- /dev/null +++ b/src/basic/sha256.c @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "hexdecoct.h" +#include "macro.h" +#include "sha256.h" + +int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]) { + struct sha256_ctx ctx; + uint64_t total_size = 0; + + sha256_init_ctx(&ctx); + + for (;;) { + uint8_t buffer[64 * 1024]; + ssize_t n; + + n = read(fd, buffer, sizeof(buffer)); + if (n < 0) + return -errno; + if (n == 0) + break; + + if (!INC_SAFE(&total_size, n) || total_size > max_size) + return -EFBIG; + + sha256_process_bytes(buffer, n, &ctx); + } + + sha256_finish_ctx(&ctx, ret); + return 0; +} + +int parse_sha256(const char *s, uint8_t ret[static SHA256_DIGEST_SIZE]) { + _cleanup_free_ uint8_t *data = NULL; + size_t size = 0; + int r; + + if (!sha256_is_valid(s)) + return -EINVAL; + + r = unhexmem_full(s, SHA256_DIGEST_SIZE * 2, false, (void**) &data, &size); + if (r < 0) + return r; + assert(size == SHA256_DIGEST_SIZE); + + memcpy(ret, data, size); + return 0; +} diff --git a/src/basic/sha256.h b/src/basic/sha256.h new file mode 100644 index 0000000..95bac1b --- /dev/null +++ b/src/basic/sha256.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include + +#include "sha256-fundamental.h" +#include "string-util.h" + +int sha256_fd(int fd, uint64_t max_size, uint8_t ret[static SHA256_DIGEST_SIZE]); + +int parse_sha256(const char *s, uint8_t res[static SHA256_DIGEST_SIZE]); + +static inline bool sha256_is_valid(const char *s) { + return s && in_charset(s, HEXDIGITS) && (strlen(s) == SHA256_DIGEST_SIZE * 2); +} diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c index 5d948462..27d094b 100644 --- a/src/basic/signal-util.c +++ b/src/basic/signal-util.c @@ -18,7 +18,7 @@ int reset_all_signal_handlers(void) { .sa_handler = SIG_DFL, .sa_flags = SA_RESTART, }; - int r = 0; + int ret = 0, r; for (int sig = 1; sig < _NSIG; sig++) { @@ -26,14 +26,14 @@ int reset_all_signal_handlers(void) { if (IN_SET(sig, SIGKILL, SIGSTOP)) continue; - /* On Linux the first two RT signals are reserved by - * glibc, and sigaction() will return EINVAL for them. */ - if (sigaction(sig, &sa, NULL) < 0) - if (errno != EINVAL && r >= 0) - r = -errno; + /* On Linux the first two RT signals are reserved by glibc, and sigaction() will return + * EINVAL for them. */ + r = RET_NERRNO(sigaction(sig, &sa, NULL)); + if (r != -EINVAL) + RET_GATHER(ret, r); } - return r; + return ret; } int reset_signal_mask(void) { @@ -57,10 +57,7 @@ int sigaction_many_internal(const struct sigaction *sa, ...) { if (sig == 0) continue; - if (sigaction(sig, sa, NULL) < 0) { - if (r >= 0) - r = -errno; - } + RET_GATHER(r, RET_NERRNO(sigaction(sig, sa, NULL))); } va_end(ap); @@ -87,7 +84,7 @@ static int sigset_add_many_ap(sigset_t *ss, va_list ap) { return r; } -int sigset_add_many(sigset_t *ss, ...) { +int sigset_add_many_internal(sigset_t *ss, ...) { va_list ap; int r; @@ -98,7 +95,7 @@ int sigset_add_many(sigset_t *ss, ...) { return r; } -int sigprocmask_many(int how, sigset_t *old, ...) { +int sigprocmask_many_internal(int how, sigset_t *old, ...) { va_list ap; sigset_t ss; int r; @@ -113,46 +110,43 @@ int sigprocmask_many(int how, sigset_t *old, ...) { if (r < 0) return r; - if (sigprocmask(how, &ss, old) < 0) - return -errno; - - return 0; + return RET_NERRNO(sigprocmask(how, &ss, old)); } static const char *const static_signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", #ifdef SIGSTKFLT [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ #endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP(static_signal, int); @@ -274,7 +268,7 @@ int pop_pending_signal_internal(int sig, ...) { if (r < 0) return r; - r = sigtimedwait(&ss, NULL, &(struct timespec) { 0, 0 }); + r = sigtimedwait(&ss, NULL, &(const struct timespec) {}); if (r < 0) { if (errno == EAGAIN) return 0; diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h index ad2ba84..8826fbe 100644 --- a/src/basic/signal-util.h +++ b/src/basic/signal-util.h @@ -31,8 +31,11 @@ int sigaction_many_internal(const struct sigaction *sa, ...); #define sigaction_many(sa, ...) \ sigaction_many_internal(sa, __VA_ARGS__, -1) -int sigset_add_many(sigset_t *ss, ...); -int sigprocmask_many(int how, sigset_t *old, ...); +int sigset_add_many_internal(sigset_t *ss, ...); +#define sigset_add_many(...) sigset_add_many_internal(__VA_ARGS__, -1) + +int sigprocmask_many_internal(int how, sigset_t *old, ...); +#define sigprocmask_many(...) sigprocmask_many_internal(__VA_ARGS__, -1) const char *signal_to_string(int i) _const_; int signal_from_string(const char *s) _pure_; @@ -46,7 +49,7 @@ static inline void block_signals_reset(sigset_t *ss) { #define BLOCK_SIGNALS(...) \ _cleanup_(block_signals_reset) _unused_ sigset_t _saved_sigset = ({ \ sigset_t _t; \ - assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__, -1) >= 0); \ + assert_se(sigprocmask_many(SIG_BLOCK, &_t, __VA_ARGS__) >= 0); \ _t; \ }) diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h index 0b3e845..2ef4a04 100644 --- a/src/basic/siphash24.h +++ b/src/basic/siphash24.h @@ -22,15 +22,16 @@ struct siphash { void siphash24_init(struct siphash *state, const uint8_t k[static 16]); void siphash24_compress(const void *in, size_t inlen, struct siphash *state); #define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) +#define siphash24_compress_typesafe(in, state) \ + siphash24_compress(&(in), sizeof(typeof(in)), (state)) static inline void siphash24_compress_boolean(bool in, struct siphash *state) { - uint8_t i = in; - - siphash24_compress(&i, sizeof i, state); + siphash24_compress_byte(in, state); } static inline void siphash24_compress_usec_t(usec_t in, struct siphash *state) { - siphash24_compress(&in, sizeof in, state); + uint64_t u = htole64(in); + siphash24_compress_typesafe(u, state); } static inline void siphash24_compress_safe(const void *in, size_t inlen, struct siphash *state) { diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index beb64d8..6e304e8 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include -#include #include #include #include @@ -453,6 +454,7 @@ int sockaddr_pretty( assert(sa); assert(salen >= sizeof(sa->sa.sa_family)); + assert(ret); switch (sa->sa.sa_family) { @@ -547,7 +549,7 @@ int sockaddr_pretty( } else { if (path[path_len - 1] == '\0') /* We expect a terminating NUL and don't print it */ - path_len --; + path_len--; p = cescape_length(path, path_len); } @@ -628,29 +630,27 @@ int getsockname_pretty(int fd, char **ret) { return sockaddr_pretty(&sa.sa, salen, false, true, ret); } -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { +int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **ret) { + char host[NI_MAXHOST]; int r; - char host[NI_MAXHOST], *ret; - assert(_ret); + assert(sa); + assert(salen >= sizeof(sa_family_t)); + assert(ret); - r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, IDN_FLAGS); + r = getnameinfo(sa, salen, host, sizeof(host), /* service= */ NULL, /* service_len= */ 0, IDN_FLAGS); if (r != 0) { - int saved_errno = errno; - - r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); - if (r < 0) - return r; + if (r == EAI_MEMORY) + return log_oom_debug(); + if (r == EAI_SYSTEM) + log_debug_errno(errno, "getnameinfo() failed, ignoring: %m"); + else + log_debug("getnameinfo() failed, ignoring: %s", gai_strerror(r)); - log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); - } else { - ret = strdup(host); - if (!ret) - return -ENOMEM; + return sockaddr_pretty(sa, salen, /* translate_ipv6= */ true, /* include_port= */ true, ret); } - *_ret = ret; - return 0; + return strdup_to(ret, host); } static const char* const netlink_family_table[] = { @@ -872,13 +872,11 @@ bool address_label_valid(const char *p) { int getpeercred(int fd, struct ucred *ucred) { socklen_t n = sizeof(struct ucred); struct ucred u; - int r; assert(fd >= 0); assert(ucred); - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n) < 0) return -errno; if (n != sizeof(struct ucred)) @@ -907,8 +905,10 @@ int getpeersec(int fd, char **ret) { if (!s) return -ENOMEM; - if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) + if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n) >= 0) { + s[n] = 0; break; + } if (errno != ERANGE) return -errno; @@ -925,12 +925,16 @@ int getpeersec(int fd, char **ret) { } int getpeergroups(int fd, gid_t **ret) { - socklen_t n = sizeof(gid_t) * 64; + socklen_t n = sizeof(gid_t) * 64U; _cleanup_free_ gid_t *d = NULL; assert(fd >= 0); assert(ret); + long ngroups_max = sysconf(_SC_NGROUPS_MAX); + if (ngroups_max > 0) + n = MAX(n, sizeof(gid_t) * (socklen_t) ngroups_max); + for (;;) { d = malloc(n); if (!d) @@ -948,7 +952,7 @@ int getpeergroups(int fd, gid_t **ret) { assert_se(n % sizeof(gid_t) == 0); n /= sizeof(gid_t); - if ((socklen_t) (int) n != n) + if (n > INT_MAX) return -E2BIG; *ret = TAKE_PTR(d); @@ -956,6 +960,21 @@ int getpeergroups(int fd, gid_t **ret) { return (int) n; } +int getpeerpidfd(int fd) { + socklen_t n = sizeof(int); + int pidfd = -EBADF; + + assert(fd >= 0); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERPIDFD, &pidfd, &n) < 0) + return -errno; + + if (n != sizeof(int)) + return -EIO; + + return pidfd; +} + ssize_t send_many_fds_iov_sa( int transport_fd, int *fds_array, size_t n_fds_array, @@ -1093,14 +1112,10 @@ ssize_t receive_many_fds_iov( if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n); - if (!fds_array) { + if (!GREEDY_REALLOC_APPEND(fds_array, n_fds_array, CMSG_TYPED_DATA(cmsg, int), n)) { cmsg_close_all(&mh); return -ENOMEM; } - - memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n); - n_fds_array += n; } if (n_fds_array == 0) { @@ -1641,6 +1656,50 @@ int socket_address_parse_unix(SocketAddress *ret_address, const char *s) { return 0; } +int vsock_parse_port(const char *s, unsigned *ret) { + int r; + + assert(ret); + + if (!s) + return -EINVAL; + + unsigned u; + r = safe_atou(s, &u); + if (r < 0) + return r; + + /* Port 0 is apparently valid and not special in AF_VSOCK (unlike on IP). But VMADDR_PORT_ANY + * (UINT32_MAX) is. Hence refuse that. */ + + if (u == VMADDR_PORT_ANY) + return -EINVAL; + + *ret = u; + return 0; +} + +int vsock_parse_cid(const char *s, unsigned *ret) { + assert(ret); + + if (!s) + return -EINVAL; + + /* Parsed an AF_VSOCK "CID". This is a 32bit entity, and the usual type is "unsigned". We recognize + * the three special CIDs as strings, and otherwise parse the numeric CIDs. */ + + if (streq(s, "hypervisor")) + *ret = VMADDR_CID_HYPERVISOR; + else if (streq(s, "local")) + *ret = VMADDR_CID_LOCAL; + else if (streq(s, "host")) + *ret = VMADDR_CID_HOST; + else + return safe_atou(s, ret); + + return 0; +} + int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { /* AF_VSOCK socket in vsock:cid:port notation */ _cleanup_free_ char *n = NULL; @@ -1666,7 +1725,7 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (!e) return -EINVAL; - r = safe_atou(e+1, &port); + r = vsock_parse_port(e+1, &port); if (r < 0) return r; @@ -1677,15 +1736,15 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { if (isempty(n)) cid = VMADDR_CID_ANY; else { - r = safe_atou(n, &cid); + r = vsock_parse_cid(n, &cid); if (r < 0) return r; } *ret_address = (SocketAddress) { .sockaddr.vm = { - .svm_cid = cid, .svm_family = AF_VSOCK, + .svm_cid = cid, .svm_port = port, }, .type = type, @@ -1694,3 +1753,18 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s) { return 0; } + +int vsock_get_local_cid(unsigned *ret) { + _cleanup_close_ int vsock_fd = -EBADF; + + assert(ret); + + vsock_fd = open("/dev/vsock", O_RDONLY|O_CLOEXEC); + if (vsock_fd < 0) + return log_debug_errno(errno, "Failed to open /dev/vsock: %m"); + + if (ioctl(vsock_fd, IOCTL_VM_SOCKETS_GET_LOCAL_CID, ret) < 0) + return log_debug_errno(errno, "Failed to query local AF_VSOCK CID: %m"); + + return 0; +} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h index 9a11df8..c784125 100644 --- a/src/basic/socket-util.h +++ b/src/basic/socket-util.h @@ -113,7 +113,7 @@ int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ int getpeername_pretty(int fd, bool include_port, char **ret); int getsockname_pretty(int fd, char **ret); -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); +int socknameinfo_pretty(const struct sockaddr *sa, socklen_t salen, char **_ret); const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; @@ -152,6 +152,7 @@ bool address_label_valid(const char *p); int getpeercred(int fd, struct ucred *ucred); int getpeersec(int fd, char **ret); int getpeergroups(int fd, gid_t **ret); +int getpeerpidfd(int fd); ssize_t send_many_fds_iov_sa( int transport_fd, @@ -373,6 +374,14 @@ int socket_get_mtu(int fd, int af, size_t *ret); int connect_unix_path(int fd, int dir_fd, const char *path); +static inline bool VSOCK_CID_IS_REGULAR(unsigned cid) { + /* 0, 1, 2, UINT32_MAX are special, refuse those */ + return cid > 2 && cid < UINT32_MAX; +} + +int vsock_parse_port(const char *s, unsigned *ret); +int vsock_parse_cid(const char *s, unsigned *ret); + /* Parses AF_UNIX and AF_VSOCK addresses. AF_INET[6] require some netlink calls, so it cannot be in * src/basic/ and is done from 'socket_local_address from src/shared/. Return -EPROTO in case of * protocol mismatch. */ @@ -385,3 +394,5 @@ int socket_address_parse_vsock(SocketAddress *ret_address, const char *s); * /proc/sys/net/core/somaxconn anyway, thus by setting this to unbounded we just make that sysctl file * authoritative. */ #define SOMAXCONN_DELUXE INT_MAX + +int vsock_get_local_cid(unsigned *ret); diff --git a/src/basic/special.h b/src/basic/special.h index a625e75..166737a 100644 --- a/src/basic/special.h +++ b/src/basic/special.h @@ -47,6 +47,7 @@ #define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ #define SPECIAL_TIME_SET_TARGET "time-set.target" #define SPECIAL_BASIC_TARGET "basic.target" +#define SPECIAL_TPM2_TARGET "tpm2.target" /* LSB compatibility */ #define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ @@ -83,8 +84,10 @@ #define SPECIAL_FSCK_SERVICE "systemd-fsck@.service" #define SPECIAL_FSCK_ROOT_SERVICE "systemd-fsck-root.service" #define SPECIAL_FSCK_USR_SERVICE "systemd-fsck-usr.service" -#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service" -#define SPECIAL_QUOTAON_SERVICE "quotaon.service" +#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck@.service" +#define SPECIAL_QUOTACHECK_ROOT_SERVICE "systemd-quotacheck-root.service" +#define SPECIAL_QUOTAON_SERVICE "quotaon@.service" +#define SPECIAL_QUOTAON_ROOT_SERVICE "quotaon-root.service" #define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service" #define SPECIAL_VOLATILE_ROOT_SERVICE "systemd-volatile-root.service" #define SPECIAL_UDEVD_SERVICE "systemd-udevd.service" diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 581370d..a833aa2 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -25,43 +25,130 @@ #include "stat-util.h" #include "string-util.h" -int is_symlink(const char *path) { - struct stat info; +static int verify_stat_at( + int fd, + const char *path, + bool follow, + int (*verify_func)(const struct stat *st), + bool verify) { - assert(path); + struct stat st; + int r; + + assert(fd >= 0 || fd == AT_FDCWD); + assert(!isempty(path) || !follow); + assert(verify_func); - if (lstat(path, &info) < 0) + if (fstatat(fd, strempty(path), &st, + (isempty(path) ? AT_EMPTY_PATH : 0) | (follow ? 0 : AT_SYMLINK_NOFOLLOW)) < 0) return -errno; - return !!S_ISLNK(info.st_mode); + r = verify_func(&st); + return verify ? r : r >= 0; } -int is_dir_full(int atfd, const char* path, bool follow) { - struct stat st; - int r; +int stat_verify_regular(const struct stat *st) { + assert(st); - assert(atfd >= 0 || atfd == AT_FDCWD); - assert(atfd >= 0 || path); + /* Checks whether the specified stat() structure refers to a regular file. If not returns an + * appropriate error code. */ - if (path) - r = fstatat(atfd, path, &st, follow ? 0 : AT_SYMLINK_NOFOLLOW); - else - r = fstat(atfd, &st); - if (r < 0) - return -errno; + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISREG(st->st_mode)) + return -EBADFD; - return !!S_ISDIR(st.st_mode); + return 0; } -int is_device_node(const char *path) { - struct stat info; +int verify_regular_at(int fd, const char *path, bool follow) { + return verify_stat_at(fd, path, follow, stat_verify_regular, true); +} - assert(path); +int fd_verify_regular(int fd) { + assert(fd >= 0); + return verify_regular_at(fd, NULL, false); +} - if (lstat(path, &info) < 0) - return -errno; +int stat_verify_directory(const struct stat *st) { + assert(st); + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (!S_ISDIR(st->st_mode)) + return -ENOTDIR; + + return 0; +} + +int fd_verify_directory(int fd) { + assert(fd >= 0); + return verify_stat_at(fd, NULL, false, stat_verify_directory, true); +} - return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); +int is_dir_at(int fd, const char *path, bool follow) { + return verify_stat_at(fd, path, follow, stat_verify_directory, false); +} + +int is_dir(const char *path, bool follow) { + assert(!isempty(path)); + return is_dir_at(AT_FDCWD, path, follow); +} + +int stat_verify_symlink(const struct stat *st) { + assert(st); + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (!S_ISLNK(st->st_mode)) + return -ENOLINK; + + return 0; +} + +int is_symlink(const char *path) { + assert(!isempty(path)); + return verify_stat_at(AT_FDCWD, path, false, stat_verify_symlink, false); +} + +int stat_verify_linked(const struct stat *st) { + assert(st); + + if (st->st_nlink <= 0) + return -EIDRM; /* recognizable error. */ + + return 0; +} + +int fd_verify_linked(int fd) { + assert(fd >= 0); + return verify_stat_at(fd, NULL, false, stat_verify_linked, true); +} + +int stat_verify_device_node(const struct stat *st) { + assert(st); + + if (S_ISLNK(st->st_mode)) + return -ELOOP; + + if (S_ISDIR(st->st_mode)) + return -EISDIR; + + if (!S_ISBLK(st->st_mode) && !S_ISCHR(st->st_mode)) + return -ENOTTY; + + return 0; +} + +int is_device_node(const char *path) { + assert(!isempty(path)); + return verify_stat_at(AT_FDCWD, path, false, stat_verify_device_node, false); } int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup) { @@ -142,7 +229,7 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { * When looking under root_dir, we can't expect /dev/ to be mounted, * so let's see if the path is a (possibly dangling) symlink to /dev/null. */ - if (path_equal_ptr(path_startswith(fn, root ?: "/"), "dev/null")) + if (path_equal(path_startswith(fn, root ?: "/"), "dev/null")) return true; r = chase_and_stat(fn, root, CHASE_PREFIX_ROOT, NULL, &st); @@ -152,7 +239,7 @@ int null_or_empty_path_with_root(const char *fn, const char *root) { return null_or_empty(&st); } -static int fd_is_read_only_fs(int fd) { +int fd_is_read_only_fs(int fd) { struct statvfs st; assert(fd >= 0); @@ -187,14 +274,12 @@ int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int fl struct stat a, b; assert(fda >= 0 || fda == AT_FDCWD); - assert(filea); assert(fdb >= 0 || fdb == AT_FDCWD); - assert(fileb); - if (fstatat(fda, filea, &a, flags) < 0) + if (fstatat(fda, strempty(filea), &a, flags) < 0) return log_debug_errno(errno, "Cannot stat %s: %m", filea); - if (fstatat(fdb, fileb, &b, flags) < 0) + if (fstatat(fdb, strempty(fileb), &b, flags) < 0) return log_debug_errno(errno, "Cannot stat %s: %m", fileb); return stat_inode_same(&a, &b); @@ -262,90 +347,6 @@ int path_is_network_fs(const char *path) { return is_network_fs(&s); } -int stat_verify_linked(const struct stat *st) { - assert(st); - - if (st->st_nlink <= 0) - return -EIDRM; /* recognizable error. */ - - return 0; -} - -int fd_verify_linked(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return stat_verify_linked(&st); -} - -int stat_verify_regular(const struct stat *st) { - assert(st); - - /* Checks whether the specified stat() structure refers to a regular file. If not returns an - * appropriate error code. */ - - if (S_ISDIR(st->st_mode)) - return -EISDIR; - - if (S_ISLNK(st->st_mode)) - return -ELOOP; - - if (!S_ISREG(st->st_mode)) - return -EBADFD; - - return 0; -} - -int fd_verify_regular(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - 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); - - if (S_ISLNK(st->st_mode)) - return -ELOOP; - - if (!S_ISDIR(st->st_mode)) - return -ENOTDIR; - - return 0; -} - -int fd_verify_directory(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return stat_verify_directory(&st); -} - int proc_mounted(void) { int r; @@ -363,8 +364,7 @@ bool stat_inode_same(const struct stat *a, const struct stat *b) { /* Returns if the specified stat structure references the same (though possibly modified) inode. Does * a thorough check, comparing inode nr, backing device and if the inode is still of the same type. */ - return a && b && - (a->st_mode & S_IFMT) != 0 && /* We use the check for .st_mode if the structure was ever initialized */ + return stat_is_set(a) && stat_is_set(b) && ((a->st_mode ^ b->st_mode) & S_IFMT) == 0 && /* same inode type */ a->st_dev == b->st_dev && a->st_ino == b->st_ino; @@ -392,9 +392,8 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { /* Same as stat_inode_same() but for struct statx */ - return a && b && + return statx_is_set(a) && statx_is_set(b) && FLAGS_SET(a->stx_mask, STATX_TYPE|STATX_INO) && FLAGS_SET(b->stx_mask, STATX_TYPE|STATX_INO) && - (a->stx_mode & S_IFMT) != 0 && ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 && a->stx_dev_major == b->stx_dev_major && a->stx_dev_minor == b->stx_dev_minor && @@ -402,7 +401,7 @@ bool statx_inode_same(const struct statx *a, const struct statx *b) { } bool statx_mount_same(const struct new_statx *a, const struct new_statx *b) { - if (!a || !b) + if (!new_statx_is_set(a) || !new_statx_is_set(b)) return false; /* if we have the mount ID, that's all we need */ @@ -498,8 +497,8 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *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); + siphash24_compress_typesafe(q->st_dev, state); + siphash24_compress_typesafe(q->st_ino, state); } int inode_compare_func(const struct stat *a, const struct stat *b) { @@ -536,5 +535,29 @@ const char* inode_type_to_string(mode_t m) { return "sock"; } + /* Note anonymous inodes in the kernel will have a zero type. Hence fstat() of an eventfd() will + * return an .st_mode where we'll return NULL here! */ return NULL; } + +mode_t inode_type_from_string(const char *s) { + if (!s) + return MODE_INVALID; + + if (streq(s, "reg")) + return S_IFREG; + if (streq(s, "dir")) + return S_IFDIR; + if (streq(s, "lnk")) + return S_IFLNK; + if (streq(s, "chr")) + return S_IFCHR; + if (streq(s, "blk")) + return S_IFBLK; + if (streq(s, "fifo")) + return S_IFIFO; + if (streq(s, "sock")) + return S_IFSOCK; + + return MODE_INVALID; +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 3501406..7556f8f 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -9,18 +9,28 @@ #include #include +#include "fs-util.h" #include "macro.h" #include "missing_stat.h" #include "siphash24.h" +#include "time-util.h" +int stat_verify_regular(const struct stat *st); +int verify_regular_at(int fd, const char *path, bool follow); +int fd_verify_regular(int fd); + +int stat_verify_directory(const struct stat *st); +int fd_verify_directory(int fd); +int is_dir_at(int fd, const char *path, bool follow); +int is_dir(const char *path, bool follow); + +int stat_verify_symlink(const struct stat *st); int is_symlink(const char *path); -int is_dir_full(int atfd, const char *fname, bool follow); -static inline int is_dir(const char *path, bool follow) { - return is_dir_full(AT_FDCWD, path, follow); -} -static inline int is_dir_fd(int fd) { - return is_dir_full(fd, NULL, false); -} + +int stat_verify_linked(const struct stat *st); +int fd_verify_linked(int fd); + +int stat_verify_device_node(const struct stat *st); int is_device_node(const char *path); int dir_is_empty_at(int dir_fd, const char *path, bool ignore_hidden_or_backup); @@ -35,13 +45,16 @@ static inline int null_or_empty_path(const char *fn) { return null_or_empty_path_with_root(fn, NULL); } +int fd_is_read_only_fs(int fd); int path_is_read_only_fs(const char *path); int inode_same_at(int fda, const char *filea, int fdb, const char *fileb, int flags); - static inline int inode_same(const char *filea, const char *fileb, int flags) { return inode_same_at(AT_FDCWD, filea, AT_FDCWD, fileb, flags); } +static inline int fd_inode_same(int fda, int fdb) { + return inode_same_at(fda, NULL, fdb, NULL, AT_EMPTY_PATH); +} /* The .f_type field of struct statfs is really weird defined on * different archs. Let's give its type a name. */ @@ -71,16 +84,6 @@ int path_is_network_fs(const char *path); */ #define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) -int stat_verify_linked(const struct stat *st); -int fd_verify_linked(int fd); - -int stat_verify_regular(const struct stat *st); -int fd_verify_regular(int fd); -int verify_regular_at(int dir_fd, const char *path, bool follow); - -int stat_verify_directory(const struct stat *st); -int fd_verify_directory(int fd); - int proc_mounted(void); bool stat_inode_same(const struct stat *a, const struct stat *b); @@ -112,8 +115,31 @@ int xstatfsat(int dir_fd, const char *path, struct statfs *ret); } var #endif +static inline usec_t statx_timestamp_load(const struct statx_timestamp *ts) { + return timespec_load(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); +} +static inline nsec_t statx_timestamp_load_nsec(const struct statx_timestamp *ts) { + return timespec_load_nsec(&(const struct timespec) { .tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec }); +} + 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); +mode_t inode_type_from_string(const char *s); + +/* Macros that check whether the stat/statx structures have been initialized already. For "struct stat" we + * use a check for .st_dev being non-zero, since the kernel unconditionally fills that in, mapping the file + * to its originating superblock, regardless if the fs is block based or virtual (we also check for .st_mode + * being MODE_INVALID, since we use that as an invalid marker for separate mode_t fields). For "struct statx" + * we use the .stx_mask field, which must be non-zero if any of the fields have already been initialized. */ +static inline bool stat_is_set(const struct stat *st) { + return st && st->st_dev != 0 && st->st_mode != MODE_INVALID; +} +static inline bool statx_is_set(const struct statx *sx) { + return sx && sx->stx_mask != 0; +} +static inline bool new_statx_is_set(const struct new_statx *sx) { + return sx && sx->stx_mask != 0; +} diff --git a/src/basic/stdio-util.h b/src/basic/stdio-util.h index 4e93ac9..0a2239d 100644 --- a/src/basic/stdio-util.h +++ b/src/basic/stdio-util.h @@ -9,14 +9,12 @@ #include "macro.h" _printf_(3, 4) -static inline char *snprintf_ok(char *buf, size_t len, const char *format, ...) { +static inline char* snprintf_ok(char *buf, size_t len, const char *format, ...) { va_list ap; int r; va_start(ap, format); - DISABLE_WARNING_FORMAT_NONLITERAL; r = vsnprintf(buf, len, format, ap); - REENABLE_WARNING; va_end(ap); return r >= 0 && (size_t) r < len ? buf : NULL; diff --git a/src/basic/string-table.h b/src/basic/string-table.h index 3be70df..d1d90df 100644 --- a/src/basic/string-table.h +++ b/src/basic/string-table.h @@ -47,10 +47,8 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k s = strdup(name##_table[i]); \ if (!s) \ return -ENOMEM; \ - } else { \ - if (asprintf(&s, "%i", i) < 0) \ - return -ENOMEM; \ - } \ + } else if (asprintf(&s, "%i", i) < 0) \ + return -ENOMEM; \ *str = s; \ return 0; \ } diff --git a/src/basic/string-util.c b/src/basic/string-util.c index 7329bfa..d0d33a4 100644 --- a/src/basic/string-util.c +++ b/src/basic/string-util.c @@ -11,6 +11,7 @@ #include "extract-word.h" #include "fd-util.h" #include "fileio.h" +#include "glyph-util.h" #include "gunicode.h" #include "locale-util.h" #include "macro.h" @@ -282,16 +283,9 @@ bool string_has_cc(const char *p, const char *ok) { } static int write_ellipsis(char *buf, bool unicode) { - if (unicode || is_locale_utf8()) { - buf[0] = 0xe2; /* tri-dot ellipsis: … */ - buf[1] = 0x80; - buf[2] = 0xa6; - } else { - buf[0] = '.'; - buf[1] = '.'; - buf[2] = '.'; - } - + const char *s = special_glyph_full(SPECIAL_GLYPH_ELLIPSIS, unicode); + assert(strlen(s) == 3); + memcpy(buf, s, 3); return 3; } @@ -398,8 +392,7 @@ static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_le x = ((new_length - need_space) * percent + 50) / 100; assert(x <= new_length - need_space); - memcpy(t, s, x); - write_ellipsis(t + x, false); + write_ellipsis(mempcpy(t, s, x), /* unicode = */ false); suffix_len = new_length - x - need_space; memcpy(t + x + 3, s + old_length - suffix_len, suffix_len); *(t + x + 3 + suffix_len) = '\0'; @@ -520,13 +513,8 @@ char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigne if (!e) return NULL; - /* - printf("old_length=%zu new_length=%zu x=%zu len=%zu len2=%zu k=%zu\n", - old_length, new_length, x, len, len2, k); - */ - memcpy_safe(e, s, len); - write_ellipsis(e + len, true); + write_ellipsis(e + len, /* unicode = */ true); char *dst = e + len + 3; @@ -562,7 +550,9 @@ char *cellescape(char *buf, size_t len, const char *s) { size_t i = 0, last_char_width[4] = {}, k = 0; + assert(buf); assert(len > 0); /* at least a terminating NUL */ + assert(s); for (;;) { char four[4]; @@ -603,7 +593,7 @@ char *cellescape(char *buf, size_t len, const char *s) { } if (i + 4 <= len) /* yay, enough space */ - i += write_ellipsis(buf + i, false); + i += write_ellipsis(buf + i, /* unicode = */ false); else if (i + 3 <= len) { /* only space for ".." */ buf[i++] = '.'; buf[i++] = '.'; @@ -612,7 +602,7 @@ char *cellescape(char *buf, size_t len, const char *s) { else assert(i + 1 <= len); - done: +done: buf[i] = '\0'; return buf; } @@ -620,6 +610,9 @@ char *cellescape(char *buf, size_t len, const char *s) { char* strshorten(char *s, size_t l) { assert(s); + if (l >= SIZE_MAX-1) /* Would not change anything */ + return s; + if (strnlen(s, l+1) > l) s[l] = 0; @@ -993,7 +986,7 @@ int strextendf_with_separator(char **x, const char *separator, const char *forma return 0; oom: - /* truncate the bytes added after the first vsnprintf() attempt again */ + /* truncate the bytes added after memcpy_safe() again */ (*x)[m] = 0; return -ENOMEM; } @@ -1123,6 +1116,24 @@ int free_and_strndup(char **p, const char *s, size_t l) { return 1; } +int strdup_to_full(char **ret, const char *src) { + if (!src) { + if (ret) + *ret = NULL; + + return 0; + } else { + if (ret) { + char *t = strdup(src); + if (!t) + return -ENOMEM; + *ret = t; + } + + return 1; + } +}; + bool string_is_safe(const char *p) { if (!p) return false; @@ -1232,54 +1243,31 @@ int string_extract_line(const char *s, size_t i, char **ret) { return -ENOMEM; *ret = m; - return !isempty(q + 1); /* more coming? */ - } else { - if (p == s) - *ret = NULL; /* Just use the input string */ - else { - char *m; - - m = strdup(p); - if (!m) - return -ENOMEM; - - *ret = m; - } - - return 0; /* The end */ - } + return !isempty(q + 1); /* More coming? */ + } else + /* Tell the caller to use the input string if equal */ + return strdup_to(ret, p != s ? p : NULL); } - if (!q) { - char *m; - + if (!q) /* No more lines, return empty line */ - - m = strdup(""); - if (!m) - return -ENOMEM; - - *ret = m; - return 0; /* The end */ - } + return strdup_to(ret, ""); p = q + 1; c++; } } -int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word) { - /* In the default mode with no separators specified, we split on whitespace and - * don't coalesce separators. */ +int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word) { + /* In the default mode with no separators specified, we split on whitespace and coalesce separators. */ const ExtractFlags flags = separators ? EXTRACT_DONT_COALESCE_SEPARATORS : 0; - const char *found = NULL; + int r; - for (const char *p = string;;) { + for (;;) { _cleanup_free_ char *w = NULL; - int r; - r = extract_first_word(&p, &w, separators, flags); + r = extract_first_word(&string, &w, separators, flags); if (r < 0) return r; if (r == 0) @@ -1420,18 +1408,6 @@ char *find_line_startswith(const char *haystack, const char *needle) { 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; -} - bool version_is_valid(const char *s) { if (isempty(s)) return false; @@ -1519,3 +1495,22 @@ ssize_t strlevenshtein(const char *x, const char *y) { return t1[yl]; } + +char *strrstr(const char *haystack, const char *needle) { + /* Like strstr() but returns the last rather than the first occurrence of "needle" in "haystack". */ + + if (!haystack || !needle) + return NULL; + + /* Special case: for the empty string we return the very last possible occurrence, i.e. *after* the + * last char, not before. */ + if (*needle == 0) + return strchr(haystack, 0); + + for (const char *p = strstr(haystack, needle), *q; p; p = q) { + q = strstr(p + 1, needle); + if (!q) + return (char *) p; + } + return NULL; +} diff --git a/src/basic/string-util.h b/src/basic/string-util.h index b6d8be3..ff5efbc 100644 --- a/src/basic/string-util.h +++ b/src/basic/string-util.h @@ -133,7 +133,7 @@ static inline char *truncate_nl(char *s) { return truncate_nl_full(s, NULL); } -static inline char *skip_leading_chars(const char *s, const char *bad) { +static inline char* skip_leading_chars(const char *s, const char *bad) { if (!s) return NULL; @@ -224,6 +224,12 @@ static inline int free_and_strdup_warn(char **p, const char *s) { } int free_and_strndup(char **p, const char *s, size_t l); +int strdup_to_full(char **ret, const char *src); +static inline int strdup_to(char **ret, const char *src) { + int r = strdup_to_full(ASSERT_PTR(ret), src); + return r < 0 ? r : 0; /* Suppress return value of 1. */ +} + bool string_is_safe(const char *p) _pure_; DISABLE_WARNING_STRINGOP_TRUNCATION; @@ -265,7 +271,7 @@ char* string_erase(char *x); int string_truncate_lines(const char *s, size_t n_lines, char **ret); int string_extract_line(const char *s, size_t i, char **ret); -int string_contains_word_strv(const char *string, const char *separators, char **words, const char **ret_word); +int string_contains_word_strv(const char *string, const char *separators, char * const *words, const char **ret_word); static inline int string_contains_word(const char *string, const char *separators, const char *word) { return string_contains_word_strv(string, separators, STRV_MAKE(word), NULL); } @@ -291,34 +297,10 @@ 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__)) - bool version_is_valid(const char *s); bool version_is_valid_versionspec(const char *s); ssize_t strlevenshtein(const char *x, const char *y); -static inline int strdup_or_null(const char *s, char **ret) { - char *c; - - assert(ret); - - /* This is a lot like strdup(), but is happy with NULL strings, and does not treat that as error, but - * copies the NULL value. */ - - if (!s) { - *ret = NULL; - return 0; - } - - c = strdup(s); - if (!c) - return -ENOMEM; - - *ret = c; - return 1; -} +char *strrstr(const char *haystack, const char *needle); diff --git a/src/basic/strv.c b/src/basic/strv.c index 1065e1b..d081821 100644 --- a/src/basic/strv.c +++ b/src/basic/strv.c @@ -242,21 +242,19 @@ rollback: return -ENOMEM; } -int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix) { +int strv_extend_strv_biconcat(char ***a, const char *prefix, const char* const *b, const char *suffix) { int r; STRV_FOREACH(s, b) { char *v; - v = strjoin(*s, suffix); + v = strjoin(strempty(prefix), *s, suffix); if (!v) return -ENOMEM; - r = strv_push(a, v); - if (r < 0) { - free(v); + r = strv_consume(a, v); + if (r < 0) return r; - } } return 0; @@ -358,7 +356,7 @@ int strv_split_colon_pairs(char ***t, const char *s) { const char *p = tuple; r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, - &first, &second, NULL); + &first, &second); if (r < 0) return r; if (r == 0) @@ -505,29 +503,31 @@ int strv_insert(char ***l, size_t position, char *value) { char **c; size_t n, m; + assert(l); + if (!value) return 0; n = strv_length(*l); position = MIN(position, n); - /* increase and check for overflow */ - m = n + 2; - if (m < n) + /* check for overflow and increase*/ + if (n > SIZE_MAX - 2) return -ENOMEM; + m = n + 2; - c = new(char*, m); + c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m), sizeof(char*)); if (!c) return -ENOMEM; - for (size_t i = 0; i < position; i++) - c[i] = (*l)[i]; + if (n > position) + memmove(c + position + 1, c + position, (n - position) * sizeof(char*)); + c[position] = value; - for (size_t i = position; i < n; i++) - c[i+1] = (*l)[i]; - c[n+1] = NULL; + c[n + 1] = NULL; - return free_and_replace(*l, c); + *l = c; + return 0; } int strv_consume_with_size(char ***l, size_t *n, char *value) { @@ -588,39 +588,63 @@ int strv_extend_with_size(char ***l, size_t *n, const char *value) { return strv_consume_with_size(l, n, v); } -int strv_extend_front(char ***l, const char *value) { +int strv_extend_many_internal(char ***l, const char *value, ...) { + va_list ap; size_t n, m; - char *v, **c; + int r; assert(l); - /* Like strv_extend(), but prepends rather than appends the new entry */ + m = n = strv_length(*l); - if (!value) - return 0; + r = 0; + va_start(ap, value); + for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) { + if (!s) + continue; - n = strv_length(*l); + if (m > SIZE_MAX-1) { /* overflow */ + r = -ENOMEM; + break; + } + m++; + } + va_end(ap); - /* Increase and overflow check. */ - m = n + 2; - if (m < n) + if (r < 0) + return r; + if (m > SIZE_MAX-1) return -ENOMEM; - v = strdup(value); - if (!v) + char **c = reallocarray(*l, GREEDY_ALLOC_ROUND_UP(m+1), sizeof(char*)); + if (!c) return -ENOMEM; + *l = c; - c = reallocarray(*l, m, sizeof(char*)); - if (!c) { - free(v); - return -ENOMEM; + r = 0; + size_t i = n; + va_start(ap, value); + for (const char *s = value; s != POINTER_MAX; s = va_arg(ap, const char*)) { + if (!s) + continue; + + c[i] = strdup(s); + if (!c[i]) { + r = -ENOMEM; + break; + } + i++; } + va_end(ap); - memmove(c+1, c, n * sizeof(char*)); - c[0] = v; - c[n+1] = NULL; + if (r < 0) { + /* rollback on error */ + for (size_t j = n; j < i; j++) + c[j] = mfree(c[j]); + return r; + } - *l = c; + c[i] = NULL; return 0; } @@ -722,6 +746,26 @@ int strv_extendf(char ***l, const char *format, ...) { return strv_consume(l, x); } +char* startswith_strv(const char *s, char * const *l) { + STRV_FOREACH(i, l) { + char *found = startswith(s, *i); + if (found) + return found; + } + + return NULL; +} + +char* endswith_strv(const char *s, char * const *l) { + STRV_FOREACH(i, l) { + char *found = endswith(s, *i); + if (found) + return found; + } + + return NULL; +} + char** strv_reverse(char **l) { size_t n; @@ -848,13 +892,15 @@ int fputstrv(FILE *f, char * const *l, const char *separator, bool *space) { bool b = false; int r; + assert(f); + /* Like fputs(), but for strv, and with a less stupid argument order */ if (!space) space = &b; STRV_FOREACH(s, l) { - r = fputs_with_space(f, *s, separator, space); + r = fputs_with_separator(f, *s, separator, space); if (r < 0) return r; } diff --git a/src/basic/strv.h b/src/basic/strv.h index 03089d5..169737d 100644 --- a/src/basic/strv.h +++ b/src/basic/strv.h @@ -43,7 +43,10 @@ int strv_copy_unless_empty(char * const *l, char ***ret); size_t strv_length(char * const *l) _pure_; int strv_extend_strv(char ***a, char * const *b, bool filter_duplicates); -int strv_extend_strv_concat(char ***a, char * const *b, const char *suffix); +int strv_extend_strv_biconcat(char ***a, const char *prefix, const char* const *b, const char *suffix); +static inline int strv_extend_strv_concat(char ***a, const char* const *b, const char *suffix) { + return strv_extend_strv_biconcat(a, NULL, b, suffix); +} int strv_prepend(char ***l, const char *value); /* _with_size() are lower-level functions where the size can be provided externally, @@ -55,8 +58,10 @@ static inline int strv_extend(char ***l, const char *value) { return strv_extend_with_size(l, NULL, value); } +int strv_extend_many_internal(char ***l, const char *value, ...); +#define strv_extend_many(l, ...) strv_extend_many_internal(l, __VA_ARGS__, POINTER_MAX) + int strv_extendf(char ***l, const char *format, ...) _printf_(2,3); -int strv_extend_front(char ***l, const char *value); int strv_push_with_size(char ***l, size_t *n, char *value); static inline int strv_push(char ***l, char *value) { @@ -161,6 +166,16 @@ static inline void strv_print(char * const *l) { strv_print_full(l, NULL); } +char* startswith_strv(const char *s, char * const *l); + +#define STARTSWITH_SET(p, ...) \ + startswith_strv(p, STRV_MAKE(__VA_ARGS__)) + +char* endswith_strv(const char *s, char * const *l); + +#define ENDSWITH_SET(p, ...) \ + endswith_strv(p, STRV_MAKE(__VA_ARGS__)) + #define strv_from_stdarg_alloca(first) \ ({ \ char **_l; \ @@ -204,18 +219,6 @@ static inline void strv_print(char * const *l) { _x && strv_contains_case(STRV_MAKE(__VA_ARGS__), _x); \ }) -#define ENDSWITH_SET(p, ...) \ - ({ \ - const char *_p = (p); \ - char *_found = NULL; \ - STRV_FOREACH(_i, STRV_MAKE(__VA_ARGS__)) { \ - _found = endswith(_p, *_i); \ - if (_found) \ - break; \ - } \ - _found; \ - }) - #define _FOREACH_STRING(uniq, x, y, ...) \ for (const char *x, * const*UNIQ_T(l, uniq) = STRV_MAKE_CONST(({ x = y; }), ##__VA_ARGS__); \ x; \ diff --git a/src/basic/syscall-list.txt b/src/basic/syscall-list.txt index 1c335bb..4a7b7fb 100644 --- a/src/basic/syscall-list.txt +++ b/src/basic/syscall-list.txt @@ -188,12 +188,16 @@ lgetxattr link linkat listen +listmount listxattr llistxattr lookup_dcookie lremovexattr lseek lsetxattr +lsm_get_self_attr +lsm_list_modules +lsm_set_self_attr lstat lstat64 madvise @@ -229,6 +233,7 @@ mq_timedsend mq_timedsend_time64 mq_unlink mremap +mseal msgctl msgget msgrcv @@ -449,6 +454,7 @@ stat stat64 statfs statfs64 +statmount statx stime subpage_prot diff --git a/src/basic/syscalls-alpha.txt b/src/basic/syscalls-alpha.txt index d3ed3a4..da50c04 100644 --- a/src/basic/syscalls-alpha.txt +++ b/src/basic/syscalls-alpha.txt @@ -38,7 +38,7 @@ clock_nanosleep_time64 clock_settime 419 clock_settime64 clone 312 -clone3 +clone3 545 close 6 close_range 546 connect 98 @@ -188,12 +188,16 @@ lgetxattr 386 link 9 linkat 458 listen 106 +listmount 568 listxattr 388 llistxattr 389 lookup_dcookie 406 lremovexattr 392 lseek 19 lsetxattr 383 +lsm_get_self_attr 569 +lsm_list_modules 571 +lsm_set_self_attr 570 lstat 68 lstat64 426 madvise 75 @@ -229,6 +233,7 @@ mq_timedsend 434 mq_timedsend_time64 mq_unlink 433 mremap 341 +mseal 572 msgctl 200 msgget 201 msgrcv 202 @@ -449,6 +454,7 @@ stat 67 stat64 425 statfs 328 statfs64 528 +statmount 567 statx 522 stime subpage_prot diff --git a/src/basic/syscalls-arc.txt b/src/basic/syscalls-arc.txt index 951ef56..cdb8a53 100644 --- a/src/basic/syscalls-arc.txt +++ b/src/basic/syscalls-arc.txt @@ -188,12 +188,16 @@ lgetxattr 9 link linkat 37 listen 201 +listmount 458 listxattr 11 llistxattr 12 lookup_dcookie 18 lremovexattr 15 lseek lsetxattr 6 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat lstat64 madvise 233 @@ -229,6 +233,7 @@ mq_timedsend 182 mq_timedsend_time64 418 mq_unlink 181 mremap 216 +mseal 462 msgctl 187 msgget 186 msgrcv 188 @@ -449,6 +454,7 @@ stat stat64 statfs statfs64 43 +statmount 457 statx 291 stime subpage_prot diff --git a/src/basic/syscalls-arm.txt b/src/basic/syscalls-arm.txt index 1c0e66f..743dd87 100644 --- a/src/basic/syscalls-arm.txt +++ b/src/basic/syscalls-arm.txt @@ -188,12 +188,16 @@ lgetxattr 230 link 9 linkat 330 listen 284 +listmount 458 listxattr 232 llistxattr 233 lookup_dcookie 249 lremovexattr 236 lseek 19 lsetxattr 227 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 196 madvise 220 @@ -229,6 +233,7 @@ mq_timedsend 276 mq_timedsend_time64 418 mq_unlink 275 mremap 163 +mseal 462 msgctl 304 msgget 303 msgrcv 302 @@ -449,6 +454,7 @@ stat 106 stat64 195 statfs 99 statfs64 266 +statmount 457 statx 397 stime subpage_prot diff --git a/src/basic/syscalls-arm64.txt b/src/basic/syscalls-arm64.txt index b8602a1..e2fc548 100644 --- a/src/basic/syscalls-arm64.txt +++ b/src/basic/syscalls-arm64.txt @@ -188,12 +188,16 @@ lgetxattr 9 link linkat 37 listen 201 +listmount 458 listxattr 11 llistxattr 12 lookup_dcookie 18 lremovexattr 15 lseek 62 lsetxattr 6 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat lstat64 madvise 233 @@ -229,6 +233,7 @@ mq_timedsend 182 mq_timedsend_time64 mq_unlink 181 mremap 216 +mseal 462 msgctl 187 msgget 186 msgrcv 188 @@ -449,6 +454,7 @@ stat stat64 statfs 43 statfs64 +statmount 457 statx 291 stime subpage_prot diff --git a/src/basic/syscalls-i386.txt b/src/basic/syscalls-i386.txt index 6d0c57f..3c571e8 100644 --- a/src/basic/syscalls-i386.txt +++ b/src/basic/syscalls-i386.txt @@ -188,12 +188,16 @@ lgetxattr 230 link 9 linkat 303 listen 363 +listmount 458 listxattr 232 llistxattr 233 lookup_dcookie 253 lremovexattr 236 lseek 19 lsetxattr 227 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 196 madvise 219 @@ -229,6 +233,7 @@ mq_timedsend 279 mq_timedsend_time64 418 mq_unlink 278 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 195 statfs 99 statfs64 268 +statmount 457 statx 383 stime 25 subpage_prot diff --git a/src/basic/syscalls-loongarch64.txt b/src/basic/syscalls-loongarch64.txt index 34a45cb..ba69d80 100644 --- a/src/basic/syscalls-loongarch64.txt +++ b/src/basic/syscalls-loongarch64.txt @@ -188,12 +188,16 @@ lgetxattr 9 link linkat 37 listen 201 +listmount 458 listxattr 11 llistxattr 12 lookup_dcookie 18 lremovexattr 15 lseek 62 lsetxattr 6 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat lstat64 madvise 233 @@ -229,6 +233,7 @@ mq_timedsend 182 mq_timedsend_time64 mq_unlink 181 mremap 216 +mseal 462 msgctl 187 msgget 186 msgrcv 188 @@ -449,6 +454,7 @@ stat stat64 statfs 43 statfs64 +statmount 457 statx 291 stime subpage_prot diff --git a/src/basic/syscalls-m68k.txt b/src/basic/syscalls-m68k.txt index 712f272..032e354 100644 --- a/src/basic/syscalls-m68k.txt +++ b/src/basic/syscalls-m68k.txt @@ -188,12 +188,16 @@ lgetxattr 227 link 9 linkat 296 listen 360 +listmount 458 listxattr 229 llistxattr 230 lookup_dcookie 248 lremovexattr 233 lseek 19 lsetxattr 224 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 196 madvise 238 @@ -229,6 +233,7 @@ mq_timedsend 273 mq_timedsend_time64 418 mq_unlink 272 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 195 statfs 99 statfs64 263 +statmount 457 statx 379 stime 25 subpage_prot diff --git a/src/basic/syscalls-mips64.txt b/src/basic/syscalls-mips64.txt index 2d0984e..b470f8d 100644 --- a/src/basic/syscalls-mips64.txt +++ b/src/basic/syscalls-mips64.txt @@ -188,12 +188,16 @@ lgetxattr 5184 link 5084 linkat 5255 listen 5049 +listmount 5458 listxattr 5186 llistxattr 5187 lookup_dcookie 5206 lremovexattr 5190 lseek 5008 lsetxattr 5181 +lsm_get_self_attr 5459 +lsm_list_modules 5461 +lsm_set_self_attr 5460 lstat 5006 lstat64 madvise 5027 @@ -229,6 +233,7 @@ mq_timedsend 5232 mq_timedsend_time64 mq_unlink 5231 mremap 5024 +mseal 5462 msgctl 5069 msgget 5066 msgrcv 5068 @@ -449,6 +454,7 @@ stat 5004 stat64 statfs 5134 statfs64 +statmount 5457 statx 5326 stime subpage_prot diff --git a/src/basic/syscalls-mips64n32.txt b/src/basic/syscalls-mips64n32.txt index 4475867..30ddfca 100644 --- a/src/basic/syscalls-mips64n32.txt +++ b/src/basic/syscalls-mips64n32.txt @@ -188,12 +188,16 @@ lgetxattr 6184 link 6084 linkat 6259 listen 6049 +listmount 6458 listxattr 6186 llistxattr 6187 lookup_dcookie 6206 lremovexattr 6190 lseek 6008 lsetxattr 6181 +lsm_get_self_attr 6459 +lsm_list_modules 6461 +lsm_set_self_attr 6460 lstat 6006 lstat64 madvise 6027 @@ -229,6 +233,7 @@ mq_timedsend 6236 mq_timedsend_time64 6418 mq_unlink 6235 mremap 6024 +mseal 6462 msgctl 6069 msgget 6066 msgrcv 6068 @@ -449,6 +454,7 @@ stat 6004 stat64 statfs 6134 statfs64 6217 +statmount 6457 statx 6330 stime subpage_prot diff --git a/src/basic/syscalls-mipso32.txt b/src/basic/syscalls-mipso32.txt index 0254cb3..26cb3c1 100644 --- a/src/basic/syscalls-mipso32.txt +++ b/src/basic/syscalls-mipso32.txt @@ -188,12 +188,16 @@ lgetxattr 4228 link 4009 linkat 4296 listen 4174 +listmount 4458 listxattr 4230 llistxattr 4231 lookup_dcookie 4247 lremovexattr 4234 lseek 4019 lsetxattr 4225 +lsm_get_self_attr 4459 +lsm_list_modules 4461 +lsm_set_self_attr 4460 lstat 4107 lstat64 4214 madvise 4218 @@ -229,6 +233,7 @@ mq_timedsend 4273 mq_timedsend_time64 4418 mq_unlink 4272 mremap 4167 +mseal 4462 msgctl 4402 msgget 4399 msgrcv 4401 @@ -449,6 +454,7 @@ stat 4106 stat64 4213 statfs 4099 statfs64 4255 +statmount 4457 statx 4366 stime 4025 subpage_prot diff --git a/src/basic/syscalls-parisc.txt b/src/basic/syscalls-parisc.txt index 2bb1de5..99c7712 100644 --- a/src/basic/syscalls-parisc.txt +++ b/src/basic/syscalls-parisc.txt @@ -188,12 +188,16 @@ lgetxattr 242 link 9 linkat 283 listen 32 +listmount 458 listxattr 244 llistxattr 245 lookup_dcookie 223 lremovexattr 248 lseek 19 lsetxattr 239 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 84 lstat64 198 madvise 119 @@ -229,6 +233,7 @@ mq_timedsend 231 mq_timedsend_time64 418 mq_unlink 230 mremap 163 +mseal 462 msgctl 191 msgget 190 msgrcv 189 @@ -449,6 +454,7 @@ stat 18 stat64 101 statfs 99 statfs64 298 +statmount 457 statx 349 stime 25 subpage_prot diff --git a/src/basic/syscalls-powerpc.txt b/src/basic/syscalls-powerpc.txt index a8c1b1b..a1c6452 100644 --- a/src/basic/syscalls-powerpc.txt +++ b/src/basic/syscalls-powerpc.txt @@ -188,12 +188,16 @@ lgetxattr 213 link 9 linkat 294 listen 329 +listmount 458 listxattr 215 llistxattr 216 lookup_dcookie 235 lremovexattr 219 lseek 19 lsetxattr 210 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 196 madvise 205 @@ -229,6 +233,7 @@ mq_timedsend 264 mq_timedsend_time64 418 mq_unlink 263 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 195 statfs 99 statfs64 252 +statmount 457 statx 383 stime 25 subpage_prot 310 diff --git a/src/basic/syscalls-powerpc64.txt b/src/basic/syscalls-powerpc64.txt index 824cc61..992c61d 100644 --- a/src/basic/syscalls-powerpc64.txt +++ b/src/basic/syscalls-powerpc64.txt @@ -188,12 +188,16 @@ lgetxattr 213 link 9 linkat 294 listen 329 +listmount 458 listxattr 215 llistxattr 216 lookup_dcookie 235 lremovexattr 219 lseek 19 lsetxattr 210 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 madvise 205 @@ -229,6 +233,7 @@ mq_timedsend 264 mq_timedsend_time64 mq_unlink 263 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 statfs 99 statfs64 252 +statmount 457 statx 383 stime 25 subpage_prot 310 diff --git a/src/basic/syscalls-riscv32.txt b/src/basic/syscalls-riscv32.txt index 5011956..3af7cdb 100644 --- a/src/basic/syscalls-riscv32.txt +++ b/src/basic/syscalls-riscv32.txt @@ -188,12 +188,16 @@ lgetxattr 9 link linkat 37 listen 201 +listmount 458 listxattr 11 llistxattr 12 lookup_dcookie 18 lremovexattr 15 lseek lsetxattr 6 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat lstat64 madvise 233 @@ -229,6 +233,7 @@ mq_timedsend mq_timedsend_time64 418 mq_unlink 181 mremap 216 +mseal 462 msgctl 187 msgget 186 msgrcv 188 @@ -449,6 +454,7 @@ stat stat64 statfs statfs64 43 +statmount 457 statx 291 stime subpage_prot diff --git a/src/basic/syscalls-riscv64.txt b/src/basic/syscalls-riscv64.txt index ba00b90..7a1882a 100644 --- a/src/basic/syscalls-riscv64.txt +++ b/src/basic/syscalls-riscv64.txt @@ -188,12 +188,16 @@ lgetxattr 9 link linkat 37 listen 201 +listmount 458 listxattr 11 llistxattr 12 lookup_dcookie 18 lremovexattr 15 lseek 62 lsetxattr 6 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat lstat64 madvise 233 @@ -229,6 +233,7 @@ mq_timedsend 182 mq_timedsend_time64 mq_unlink 181 mremap 216 +mseal 462 msgctl 187 msgget 186 msgrcv 188 @@ -449,6 +454,7 @@ stat stat64 statfs 43 statfs64 +statmount 457 statx 291 stime subpage_prot diff --git a/src/basic/syscalls-s390.txt b/src/basic/syscalls-s390.txt index c81f795..855d5c4 100644 --- a/src/basic/syscalls-s390.txt +++ b/src/basic/syscalls-s390.txt @@ -188,12 +188,16 @@ lgetxattr 228 link 9 linkat 296 listen 363 +listmount 458 listxattr 230 llistxattr 231 lookup_dcookie 110 lremovexattr 234 lseek 19 lsetxattr 225 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 196 madvise 219 @@ -229,6 +233,7 @@ mq_timedsend 273 mq_timedsend_time64 418 mq_unlink 272 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 195 statfs 99 statfs64 265 +statmount 457 statx 379 stime 25 subpage_prot diff --git a/src/basic/syscalls-s390x.txt b/src/basic/syscalls-s390x.txt index c999fd6..05dcbe9 100644 --- a/src/basic/syscalls-s390x.txt +++ b/src/basic/syscalls-s390x.txt @@ -188,12 +188,16 @@ lgetxattr 228 link 9 linkat 296 listen 363 +listmount 458 listxattr 230 llistxattr 231 lookup_dcookie 110 lremovexattr 234 lseek 19 lsetxattr 225 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 107 lstat64 madvise 219 @@ -229,6 +233,7 @@ mq_timedsend 273 mq_timedsend_time64 mq_unlink 272 mremap 163 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 106 stat64 statfs 99 statfs64 265 +statmount 457 statx 379 stime subpage_prot diff --git a/src/basic/syscalls-sparc.txt b/src/basic/syscalls-sparc.txt index e631d30..f4ff31e 100644 --- a/src/basic/syscalls-sparc.txt +++ b/src/basic/syscalls-sparc.txt @@ -188,12 +188,16 @@ lgetxattr 173 link 9 linkat 292 listen 354 +listmount 458 listxattr 178 llistxattr 179 lookup_dcookie 208 lremovexattr 182 lseek 19 lsetxattr 170 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 40 lstat64 132 madvise 75 @@ -229,6 +233,7 @@ mq_timedsend 275 mq_timedsend_time64 418 mq_unlink 274 mremap 250 +mseal 462 msgctl 402 msgget 399 msgrcv 401 @@ -449,6 +454,7 @@ stat 38 stat64 139 statfs 157 statfs64 234 +statmount 457 statx 360 stime 233 subpage_prot diff --git a/src/basic/syscalls-x86_64.txt b/src/basic/syscalls-x86_64.txt index 52d6176..f314ed0 100644 --- a/src/basic/syscalls-x86_64.txt +++ b/src/basic/syscalls-x86_64.txt @@ -188,12 +188,16 @@ lgetxattr 192 link 86 linkat 265 listen 50 +listmount 458 listxattr 194 llistxattr 195 lookup_dcookie 212 lremovexattr 198 lseek 8 lsetxattr 189 +lsm_get_self_attr 459 +lsm_list_modules 461 +lsm_set_self_attr 460 lstat 6 lstat64 madvise 28 @@ -229,6 +233,7 @@ mq_timedsend 242 mq_timedsend_time64 mq_unlink 241 mremap 25 +mseal 462 msgctl 71 msgget 68 msgrcv 70 @@ -449,6 +454,7 @@ stat 4 stat64 statfs 137 statfs64 +statmount 457 statx 332 stime subpage_prot diff --git a/src/basic/sysctl-util.c b/src/basic/sysctl-util.c index b66a662..9a1933f 100644 --- a/src/basic/sysctl-util.c +++ b/src/basic/sysctl-util.c @@ -96,6 +96,26 @@ int sysctl_write_ip_property(int af, const char *ifname, const char *property, c return sysctl_write(p, value); } +int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value) { + const char *p; + + assert(property); + assert(value); + assert(ifname); + + if (!IN_SET(af, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (ifname) { + if (!ifname_valid_full(ifname, IFNAME_VALID_SPECIAL)) + return -EINVAL; + p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/", ifname, "/", property); + } else + p = strjoina("net/", af_to_ipv4_ipv6(af), "/neigh/default/", property); + + return sysctl_write(p, value); +} + int sysctl_read(const char *property, char **ret) { char *p; int r; diff --git a/src/basic/sysctl-util.h b/src/basic/sysctl-util.h index 3236419..7192e8c 100644 --- a/src/basic/sysctl-util.h +++ b/src/basic/sysctl-util.h @@ -19,6 +19,13 @@ static inline int sysctl_write_ip_property_boolean(int af, const char *ifname, c return sysctl_write_ip_property(af, ifname, property, one_zero(value)); } +int sysctl_write_ip_neighbor_property(int af, const char *ifname, const char *property, const char *value); +static inline int sysctl_write_ip_neighbor_property_uint32(int af, const char *ifname, const char *property, uint32_t value) { + char buf[DECIMAL_STR_MAX(uint32_t)]; + xsprintf(buf, "%u", value); + return sysctl_write_ip_neighbor_property(af, ifname, property, buf); +} + #define DEFINE_SYSCTL_WRITE_IP_PROPERTY(name, type, format) \ static inline int sysctl_write_ip_property_##name(int af, const char *ifname, const char *property, type value) { \ char buf[DECIMAL_STR_MAX(type)]; \ diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index 530ef9a..dda5920 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -27,6 +27,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "hexdecoct.h" #include "inotify-util.h" #include "io-util.h" #include "log.h" @@ -53,6 +54,18 @@ static volatile int cached_on_dev_null = -1; static volatile int cached_color_mode = _COLOR_INVALID; static volatile int cached_underline_enabled = -1; +bool isatty_safe(int fd) { + assert(fd >= 0); + + if (isatty(fd)) + return true; + + /* Be resilient if we're working on stdio, since they're set up by parent process. */ + assert(errno != EBADF || IN_SET(fd, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO)); + + return false; +} + int chvt(int vt) { _cleanup_close_ int fd = -EBADF; @@ -239,7 +252,7 @@ int reset_terminal_fd(int fd, bool switch_to_text) { assert(fd >= 0); - if (isatty(fd) < 1) + if (!isatty_safe(fd)) return log_debug_errno(errno, "Asked to reset a terminal that actually isn't a terminal: %m"); /* We leave locked terminal attributes untouched, so that Plymouth may set whatever it wants to set, @@ -293,6 +306,8 @@ int reset_terminal_fd(int fd, bool switch_to_text) { termios.c_cc[VMIN] = 1; r = RET_NERRNO(tcsetattr(fd, TCSANOW, &termios)); + if (r < 0) + log_debug_errno(r, "Failed to set terminal parameters: %m"); finish: /* Just in case, flush all crap out */ @@ -346,7 +361,7 @@ int open_terminal(const char *name, int mode) { c++; } - if (isatty(fd) < 1) + if (!isatty_safe(fd)) return negative_errno(); return TAKE_FD(fd); @@ -684,18 +699,10 @@ int vtnr_from_tty(const char *tty) { tty = active; } - if (tty == active) - *ret = TAKE_PTR(active); - else { - char *tmp; - - tmp = strdup(tty); - if (!tmp) - return -ENOMEM; - - *ret = tmp; - } + if (tty != active) + return strdup_to(ret, tty); + *ret = TAKE_PTR(active); return 0; } @@ -975,7 +982,7 @@ bool on_tty(void) { } int getttyname_malloc(int fd, char **ret) { - char path[PATH_MAX], *c; /* PATH_MAX is counted *with* the trailing NUL byte */ + char path[PATH_MAX]; /* PATH_MAX is counted *with* the trailing NUL byte */ int r; assert(fd >= 0); @@ -988,12 +995,7 @@ int getttyname_malloc(int fd, char **ret) { if (r > 0) return -r; - c = strdup(skip_dev_prefix(path)); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; + return strdup_to(ret, skip_dev_prefix(path)); } int getttyname_harder(int fd, char **ret) { @@ -1098,13 +1100,9 @@ int get_ctty(pid_t pid, dev_t *ret_devnr, char **ret) { return -EINVAL; if (ret) { - _cleanup_free_ char *b = NULL; - - b = strdup(w); - if (!b) - return -ENOMEM; - - *ret = TAKE_PTR(b); + r = strdup_to(ret, w); + if (r < 0) + return r; } if (ret_devnr) @@ -1198,7 +1196,7 @@ int openpt_allocate_in_namespace(pid_t pid, int flags, char **ret_slave) { assert(pid > 0); - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); if (r < 0) return r; @@ -1249,7 +1247,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { pid_t child; int r; - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); if (r < 0) return r; @@ -1446,38 +1444,33 @@ int vt_reset_keyboard(int fd) { } int vt_restore(int fd) { + static const struct vt_mode mode = { .mode = VT_AUTO, }; - int r, q = 0; - if (isatty(fd) < 1) + int r, ret = 0; + + assert(fd >= 0); + + if (!isatty_safe(fd)) return log_debug_errno(errno, "Asked to restore the VT for an fd that does not refer to a terminal: %m"); if (ioctl(fd, KDSETMODE, KD_TEXT) < 0) - q = log_debug_errno(errno, "Failed to set VT in text mode, ignoring: %m"); + RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT to text mode, ignoring: %m")); r = vt_reset_keyboard(fd); - if (r < 0) { - log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m"); - if (q >= 0) - q = r; - } + if (r < 0) + RET_GATHER(ret, log_debug_errno(r, "Failed to reset keyboard mode, ignoring: %m")); - if (ioctl(fd, VT_SETMODE, &mode) < 0) { - log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m"); - if (q >= 0) - q = -errno; - } + if (ioctl(fd, VT_SETMODE, &mode) < 0) + RET_GATHER(ret, log_debug_errno(errno, "Failed to set VT_AUTO mode, ignoring: %m")); r = fchmod_and_chown(fd, TTY_MODE, 0, GID_INVALID); - if (r < 0) { - log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m"); - if (q >= 0) - q = r; - } + if (r < 0) + RET_GATHER(ret, log_debug_errno(r, "Failed to chmod()/chown() VT, ignoring: %m")); - return q; + return ret; } int vt_release(int fd, bool restore) { @@ -1487,7 +1480,7 @@ int vt_release(int fd, bool restore) { * sent by the kernel and optionally reset the VT in text and auto * VT-switching modes. */ - if (isatty(fd) < 1) + if (!isatty_safe(fd)) return log_debug_errno(errno, "Asked to release the VT for an fd that does not refer to a terminal: %m"); if (ioctl(fd, VT_RELDISP, 1) < 0) @@ -1551,3 +1544,264 @@ int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column) return 0; } + +int terminal_reset_ansi_seq(int fd) { + int r, k; + + assert(fd >= 0); + + if (getenv_terminal_is_dumb()) + return 0; + + r = fd_nonblock(fd, true); + if (r < 0) + return log_debug_errno(r, "Failed to set terminal to non-blocking mode: %m"); + + k = loop_write_full(fd, + "\033c" /* reset to initial state */ + "\033[!p" /* soft terminal reset */ + "\033]104\007" /* reset colors */ + "\033[?7h", /* enable line-wrapping */ + SIZE_MAX, + 50 * USEC_PER_MSEC); + if (k < 0) + log_debug_errno(k, "Failed to write to terminal: %m"); + + if (r > 0) { + r = fd_nonblock(fd, false); + if (r < 0) + log_debug_errno(r, "Failed to set terminal back to blocking mode: %m"); + } + + return k < 0 ? k : r; +} + +void termios_disable_echo(struct termios *termios) { + assert(termios); + + termios->c_lflag &= ~(ICANON|ECHO); + termios->c_cc[VMIN] = 1; + termios->c_cc[VTIME] = 0; +} + +typedef enum BackgroundColorState { + BACKGROUND_TEXT, + BACKGROUND_ESCAPE, + BACKGROUND_BRACKET, + BACKGROUND_FIRST_ONE, + BACKGROUND_SECOND_ONE, + BACKGROUND_SEMICOLON, + BACKGROUND_R, + BACKGROUND_G, + BACKGROUND_B, + BACKGROUND_RED, + BACKGROUND_GREEN, + BACKGROUND_BLUE, + BACKGROUND_STRING_TERMINATOR, +} BackgroundColorState; + +typedef struct BackgroundColorContext { + BackgroundColorState state; + uint32_t red, green, blue; + unsigned red_bits, green_bits, blue_bits; +} BackgroundColorContext; + +static int scan_background_color_response( + BackgroundColorContext *context, + const char *buf, + size_t size) { + + assert(context); + assert(buf || size == 0); + + for (size_t i = 0; i < size; i++) { + char c = buf[i]; + + switch (context->state) { + + case BACKGROUND_TEXT: + context->state = c == '\x1B' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT; + break; + + case BACKGROUND_ESCAPE: + context->state = c == ']' ? BACKGROUND_BRACKET : BACKGROUND_TEXT; + break; + + case BACKGROUND_BRACKET: + context->state = c == '1' ? BACKGROUND_FIRST_ONE : BACKGROUND_TEXT; + break; + + case BACKGROUND_FIRST_ONE: + context->state = c == '1' ? BACKGROUND_SECOND_ONE : BACKGROUND_TEXT; + break; + + case BACKGROUND_SECOND_ONE: + context->state = c == ';' ? BACKGROUND_SEMICOLON : BACKGROUND_TEXT; + break; + + case BACKGROUND_SEMICOLON: + context->state = c == 'r' ? BACKGROUND_R : BACKGROUND_TEXT; + break; + + case BACKGROUND_R: + context->state = c == 'g' ? BACKGROUND_G : BACKGROUND_TEXT; + break; + + case BACKGROUND_G: + context->state = c == 'b' ? BACKGROUND_B : BACKGROUND_TEXT; + break; + + case BACKGROUND_B: + context->state = c == ':' ? BACKGROUND_RED : BACKGROUND_TEXT; + break; + + case BACKGROUND_RED: + if (c == '/') + context->state = context->red_bits > 0 ? BACKGROUND_GREEN : BACKGROUND_TEXT; + else { + int d = unhexchar(c); + if (d < 0 || context->red_bits >= sizeof(context->red)*8) + context->state = BACKGROUND_TEXT; + else { + context->red = (context->red << 4) | d; + context->red_bits += 4; + } + } + break; + + case BACKGROUND_GREEN: + if (c == '/') + context->state = context->green_bits > 0 ? BACKGROUND_BLUE : BACKGROUND_TEXT; + else { + int d = unhexchar(c); + if (d < 0 || context->green_bits >= sizeof(context->green)*8) + context->state = BACKGROUND_TEXT; + else { + context->green = (context->green << 4) | d; + context->green_bits += 4; + } + } + break; + + case BACKGROUND_BLUE: + if (c == '\x07') { + if (context->blue_bits > 0) + return 1; /* success! */ + + context->state = BACKGROUND_TEXT; + } else if (c == '\x1b') + context->state = context->blue_bits > 0 ? BACKGROUND_STRING_TERMINATOR : BACKGROUND_TEXT; + else { + int d = unhexchar(c); + if (d < 0 || context->blue_bits >= sizeof(context->blue)*8) + context->state = BACKGROUND_TEXT; + else { + context->blue = (context->blue << 4) | d; + context->blue_bits += 4; + } + } + break; + + case BACKGROUND_STRING_TERMINATOR: + if (c == '\\') + return 1; /* success! */ + + context->state = c == ']' ? BACKGROUND_ESCAPE : BACKGROUND_TEXT; + break; + + } + + /* Reset any colors we might have picked up */ + if (IN_SET(context->state, BACKGROUND_TEXT, BACKGROUND_ESCAPE)) { + /* reset color */ + context->red = context->green = context->blue = 0; + context->red_bits = context->green_bits = context->blue_bits = 0; + } + } + + return 0; /* all good, but not enough data yet */ +} + +int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue) { + int r; + + assert(ret_red); + assert(ret_green); + assert(ret_blue); + + if (!colors_enabled()) + return -EOPNOTSUPP; + + if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO)) + return -EOPNOTSUPP; + + if (streq_ptr(getenv("TERM"), "linux")) { + /* Linux console is black */ + *ret_red = *ret_green = *ret_blue = 0.0; + return 0; + } + + struct termios old_termios; + if (tcgetattr(STDIN_FILENO, &old_termios) < 0) + return -errno; + + struct termios new_termios = old_termios; + termios_disable_echo(&new_termios); + + if (tcsetattr(STDOUT_FILENO, TCSADRAIN, &new_termios) < 0) + return -errno; + + r = loop_write(STDOUT_FILENO, "\x1B]11;?\x07", SIZE_MAX); + if (r < 0) + goto finish; + + usec_t end = usec_add(now(CLOCK_MONOTONIC), 100 * USEC_PER_MSEC); + char buf[256]; + size_t buf_full = 0; + BackgroundColorContext context = {}; + + for (;;) { + usec_t n = now(CLOCK_MONOTONIC); + + if (n >= end) { + r = -EOPNOTSUPP; + goto finish; + } + + r = fd_wait_for_event(STDIN_FILENO, POLLIN, usec_sub_unsigned(end, n)); + if (r < 0) + goto finish; + if (r == 0) { + r = -EOPNOTSUPP; + goto finish; + } + + ssize_t l; + l = read(STDIN_FILENO, buf, sizeof(buf) - buf_full); + if (l < 0) { + r = -errno; + goto finish; + } + + buf_full += l; + assert(buf_full <= sizeof(buf)); + + r = scan_background_color_response(&context, buf, buf_full); + if (r < 0) + goto finish; + if (r > 0) { + assert(context.red_bits > 0); + *ret_red = (double) context.red / ((UINT64_C(1) << context.red_bits) - 1); + assert(context.green_bits > 0); + *ret_green = (double) context.green / ((UINT64_C(1) << context.green_bits) - 1); + assert(context.blue_bits > 0); + *ret_blue = (double) context.blue / ((UINT64_C(1) << context.blue_bits) - 1); + r = 0; + goto finish; + } + } + +finish: + (void) tcsetattr(STDOUT_FILENO, TCSADRAIN, &old_termios); + return r; +} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h index b1d7aee..ecfe574 100644 --- a/src/basic/terminal-util.h +++ b/src/basic/terminal-util.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "macro.h" #include "time-util.h" @@ -59,6 +60,8 @@ /* Other ANSI codes */ #define ANSI_UNDERLINE "\x1B[0;4m" +#define ANSI_ADD_UNDERLINE "\x1B[4m" +#define ANSI_ADD_UNDERLINE_GREY ANSI_ADD_UNDERLINE "\x1B[58;5;245m" #define ANSI_HIGHLIGHT "\x1B[0;1;39m" #define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" @@ -77,15 +80,25 @@ /* Erase characters until the end of the line */ #define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" +/* Erase characters until end of screen */ +#define ANSI_ERASE_TO_END_OF_SCREEN "\x1B[J" + /* Move cursor up one line */ #define ANSI_REVERSE_LINEFEED "\x1BM" /* Set cursor to top left corner and clear screen */ #define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" +/* Push/pop a window title off the stack of window titles */ +#define ANSI_WINDOW_TITLE_PUSH "\x1b[22;2t" +#define ANSI_WINDOW_TITLE_POP "\x1b[23;2t" + +bool isatty_safe(int fd); + int reset_terminal_fd(int fd, bool switch_to_text); int reset_terminal(const char *name); int set_terminal_cursor_position(int fd, unsigned int row, unsigned int column); +int terminal_reset_ansi_seq(int fd); int open_terminal(const char *name, int mode); @@ -167,7 +180,6 @@ bool underline_enabled(void); bool dev_console_colors_enabled(void); static inline bool colors_enabled(void) { - /* Returns true if colors are considered supported on our stdout. */ return get_color_mode() != COLOR_OFF; } @@ -190,6 +202,15 @@ static inline const char *ansi_underline(void) { return underline_enabled() ? ANSI_UNDERLINE : ""; } +static inline const char *ansi_add_underline(void) { + return underline_enabled() ? ANSI_ADD_UNDERLINE : ""; +} + +static inline const char *ansi_add_underline_grey(void) { + return underline_enabled() ? + (colors_enabled() ? ANSI_ADD_UNDERLINE_GREY : ANSI_ADD_UNDERLINE) : ""; +} + #define DEFINE_ANSI_FUNC_UNDERLINE(name, NAME) \ static inline const char *ansi_##name(void) { \ return underline_enabled() ? ANSI_##NAME##_UNDERLINE : \ @@ -276,3 +297,7 @@ static inline const char* ansi_highlight_green_red(bool b) { /* This assumes there is a 'tty' group */ #define TTY_MODE 0620 + +void termios_disable_echo(struct termios *termios); + +int get_default_background_color(double *ret_red, double *ret_green, double *ret_blue); diff --git a/src/basic/time-util.c b/src/basic/time-util.c index f9014dc..b94f37c 100644 --- a/src/basic/time-util.c +++ b/src/basic/time-util.c @@ -83,7 +83,7 @@ triple_timestamp* triple_timestamp_now(triple_timestamp *ts) { return ts; } -static usec_t map_clock_usec_internal(usec_t from, usec_t from_base, usec_t to_base) { +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base) { /* Maps the time 'from' between two clocks, based on a common reference point where the first clock * is at 'from_base' and the second clock at 'to_base'. Basically calculates: @@ -121,7 +121,7 @@ usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock) { if (from == USEC_INFINITY) return from; - return map_clock_usec_internal(from, now(from_clock), now(to_clock)); + return map_clock_usec_raw(from, now(from_clock), now(to_clock)); } dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { @@ -150,8 +150,8 @@ triple_timestamp* triple_timestamp_from_realtime(triple_timestamp *ts, usec_t u) nowr = now(CLOCK_REALTIME); ts->realtime = u; - ts->monotonic = map_clock_usec_internal(u, nowr, now(CLOCK_MONOTONIC)); - ts->boottime = map_clock_usec_internal(u, nowr, now(CLOCK_BOOTTIME)); + ts->monotonic = map_clock_usec_raw(u, nowr, now(CLOCK_MONOTONIC)); + ts->boottime = map_clock_usec_raw(u, nowr, now(CLOCK_BOOTTIME)); return ts; } @@ -169,8 +169,8 @@ triple_timestamp* triple_timestamp_from_boottime(triple_timestamp *ts, usec_t u) nowb = now(CLOCK_BOOTTIME); ts->boottime = u; - ts->monotonic = map_clock_usec_internal(u, nowb, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowb, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowb, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowb, now(CLOCK_REALTIME)); return ts; } @@ -199,8 +199,8 @@ dual_timestamp* dual_timestamp_from_boottime(dual_timestamp *ts, usec_t u) { } nowm = now(CLOCK_BOOTTIME); - ts->monotonic = map_clock_usec_internal(u, nowm, now(CLOCK_MONOTONIC)); - ts->realtime = map_clock_usec_internal(u, nowm, now(CLOCK_REALTIME)); + ts->monotonic = map_clock_usec_raw(u, nowm, now(CLOCK_MONOTONIC)); + ts->realtime = map_clock_usec_raw(u, nowm, now(CLOCK_REALTIME)); return ts; } @@ -1429,7 +1429,7 @@ static int get_timezones_from_zone1970_tab(char ***ret) { /* Line format is: * 'country codes' 'coordinates' 'timezone' 'comments' */ - r = extract_many_words(&p, NULL, 0, &cc, &co, &tz, NULL); + r = extract_many_words(&p, NULL, 0, &cc, &co, &tz); if (r < 0) continue; @@ -1474,7 +1474,7 @@ static int get_timezones_from_tzdata_zi(char ***ret) { * Link line format is: * 'Link' 'target' 'alias' * See 'man zic' for more detail. */ - r = extract_many_words(&p, NULL, 0, &type, &f1, &f2, NULL); + r = extract_many_words(&p, NULL, 0, &type, &f1, &f2); if (r < 0) continue; @@ -1517,7 +1517,7 @@ int get_timezones(char ***ret) { /* Always include UTC */ r = strv_extend(&zones, "UTC"); if (r < 0) - return -ENOMEM; + return r; strv_sort(zones); strv_uniq(zones); @@ -1573,7 +1573,7 @@ int verify_timezone(const char *name, int log_level) { r = fd_verify_regular(fd); if (r < 0) - return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); + return log_full_errno(log_level, r, "Timezone file '%s' is not a regular file: %m", t); r = loop_read_exact(fd, buf, 4, false); if (r < 0) @@ -1606,38 +1606,24 @@ bool clock_supported(clockid_t clock) { int get_timezone(char **ret) { _cleanup_free_ char *t = NULL; - const char *e; - char *z; int r; assert(ret); r = readlink_malloc("/etc/localtime", &t); - if (r == -ENOENT) { + if (r == -ENOENT) /* If the symlink does not exist, assume "UTC", like glibc does */ - z = strdup("UTC"); - if (!z) - return -ENOMEM; - - *ret = z; - return 0; - } + return strdup_to(ret, "UTC"); if (r < 0) - return r; /* returns EINVAL if not a symlink */ + return r; /* Return EINVAL if not a symlink */ - e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/"); + const char *e = PATH_STARTSWITH_SET(t, "/usr/share/zoneinfo/", "../usr/share/zoneinfo/"); if (!e) return -EINVAL; - if (!timezone_is_valid(e, LOG_DEBUG)) return -EINVAL; - z = strdup(e); - if (!z) - return -ENOMEM; - - *ret = z; - return 0; + return strdup_to(ret, e); } time_t mktime_or_timegm(struct tm *tm, bool utc) { diff --git a/src/basic/time-util.h b/src/basic/time-util.h index ed4c1aa..f273770 100644 --- a/src/basic/time-util.h +++ b/src/basic/time-util.h @@ -71,12 +71,16 @@ typedef enum TimestampStyle { #define TIME_T_MAX (time_t)((UINTMAX_C(1) << ((sizeof(time_t) << 3) - 1)) - 1) -#define DUAL_TIMESTAMP_NULL ((struct dual_timestamp) {}) -#define TRIPLE_TIMESTAMP_NULL ((struct triple_timestamp) {}) +#define DUAL_TIMESTAMP_NULL ((dual_timestamp) {}) +#define DUAL_TIMESTAMP_INFINITY ((dual_timestamp) { USEC_INFINITY, USEC_INFINITY }) +#define TRIPLE_TIMESTAMP_NULL ((triple_timestamp) {}) + +#define TIMESPEC_OMIT ((const struct timespec) { .tv_nsec = UTIME_OMIT }) usec_t now(clockid_t clock); nsec_t now_nsec(clockid_t clock); +usec_t map_clock_usec_raw(usec_t from, usec_t from_base, usec_t to_base); usec_t map_clock_usec(usec_t from, clockid_t from_clock, clockid_t to_clock); dual_timestamp* dual_timestamp_now(dual_timestamp *ts); @@ -219,6 +223,9 @@ static inline int usleep_safe(usec_t usec) { * ⚠️ Note we are not using plain nanosleep() here, since that operates on CLOCK_REALTIME, not * CLOCK_MONOTONIC! */ + if (usec == 0) + return 0; + // FIXME: use RET_NERRNO() macro here. Currently, this header cannot include errno-util.h. return clock_nanosleep(CLOCK_MONOTONIC, 0, TIMESPEC_STORE(usec), NULL) < 0 ? -errno : 0; } diff --git a/src/basic/tmpfile-util.c b/src/basic/tmpfile-util.c index e77ca94..3a3f7dc 100644 --- a/src/basic/tmpfile-util.c +++ b/src/basic/tmpfile-util.c @@ -330,28 +330,7 @@ int fopen_tmpfile_linkable(const char *target, int flags, char **ret_path, FILE return 0; } -static int link_fd(int fd, int newdirfd, const char *newpath) { - int r; - - assert(fd >= 0); - assert(newdirfd >= 0 || newdirfd == AT_FDCWD); - assert(newpath); - - /* Try symlinking via /proc/fd/ first. */ - r = RET_NERRNO(linkat(AT_FDCWD, FORMAT_PROC_FD_PATH(fd), newdirfd, newpath, AT_SYMLINK_FOLLOW)); - if (r != -ENOENT) - return r; - - /* Fall back to symlinking via AT_EMPTY_PATH as fallback (this requires CAP_DAC_READ_SEARCH and a - * more recent kernel, but does not require /proc/ mounted) */ - if (proc_mounted() != 0) - return r; - - return RET_NERRNO(linkat(fd, "", newdirfd, newpath, AT_EMPTY_PATH)); -} - int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, LinkTmpfileFlags flags) { - _cleanup_free_ char *tmp = NULL; int r; assert(fd >= 0); @@ -370,33 +349,14 @@ int link_tmpfile_at(int fd, int dir_fd, const char *path, const char *target, Li r = RET_NERRNO(renameat(dir_fd, path, dir_fd, target)); else r = rename_noreplace(dir_fd, path, dir_fd, target); - if (r < 0) - return r; } else { - - r = link_fd(fd, dir_fd, target); - if (r != -EEXIST || !FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) - return r; - - /* So the target already exists and we were asked to replace it. That sucks a bit, since the kernel's - * linkat() logic does not allow that. We work-around this by linking the file to a random name - * first, and then renaming that to the final name. This reintroduces the race O_TMPFILE kinda is - * trying to fix, but at least the vulnerability window (i.e. where the file is linked into the file - * system under a temporary name) is very short. */ - - r = tempfn_random(target, NULL, &tmp); - if (r < 0) - return r; - - if (link_fd(fd, dir_fd, tmp) < 0) - return -EEXIST; /* propagate original error */ - - r = RET_NERRNO(renameat(dir_fd, tmp, dir_fd, target)); - if (r < 0) { - (void) unlinkat(dir_fd, tmp, 0); - return r; - } + if (FLAGS_SET(flags, LINK_TMPFILE_REPLACE)) + r = linkat_replace(fd, /* oldpath= */ NULL, dir_fd, target); + else + r = link_fd(fd, dir_fd, target); } + if (r < 0) + return r; if (FLAGS_SET(flags, LINK_TMPFILE_SYNC)) { r = fsync_full(fd); diff --git a/src/basic/uid-alloc-range.c b/src/basic/uid-alloc-range.c deleted file mode 100644 index 669cb6d..0000000 --- a/src/basic/uid-alloc-range.c +++ /dev/null @@ -1,131 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "chase.h" -#include "fd-util.h" -#include "fileio.h" -#include "missing_threads.h" -#include "string-util.h" -#include "uid-alloc-range.h" -#include "user-util.h" - -static const UGIDAllocationRange default_ugid_allocation_range = { - .system_alloc_uid_min = SYSTEM_ALLOC_UID_MIN, - .system_uid_max = SYSTEM_UID_MAX, - .system_alloc_gid_min = SYSTEM_ALLOC_GID_MIN, - .system_gid_max = SYSTEM_GID_MAX, -}; - -#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES -static int parse_alloc_uid(const char *path, const char *name, const char *t, uid_t *ret_uid) { - uid_t uid; - int r; - - r = parse_uid(t, &uid); - if (r < 0) - return log_debug_errno(r, "%s: failed to parse %s %s, ignoring: %m", path, name, t); - if (uid == 0) - uid = 1; - - *ret_uid = uid; - return 0; -} -#endif - -int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root) { -#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES - _cleanup_fclose_ FILE *f = NULL; - UGIDAllocationRange defs; - int r; - - if (!path) - path = "/etc/login.defs"; - - r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f); - if (r == -ENOENT) - goto defaults; - if (r < 0) - return log_debug_errno(r, "Failed to open %s: %m", path); - - defs = default_ugid_allocation_range; - - for (;;) { - _cleanup_free_ char *line = NULL; - char *t; - - r = read_line(f, LINE_MAX, &line); - if (r < 0) - return log_debug_errno(r, "Failed to read %s: %m", path); - if (r == 0) - break; - - if ((t = first_word(line, "SYS_UID_MIN"))) - (void) parse_alloc_uid(path, "SYS_UID_MIN", t, &defs.system_alloc_uid_min); - else if ((t = first_word(line, "SYS_UID_MAX"))) - (void) parse_alloc_uid(path, "SYS_UID_MAX", t, &defs.system_uid_max); - else if ((t = first_word(line, "SYS_GID_MIN"))) - (void) parse_alloc_uid(path, "SYS_GID_MIN", t, &defs.system_alloc_gid_min); - else if ((t = first_word(line, "SYS_GID_MAX"))) - (void) parse_alloc_uid(path, "SYS_GID_MAX", t, &defs.system_gid_max); - } - - if (defs.system_alloc_uid_min > defs.system_uid_max) { - log_debug("%s: SYS_UID_MIN > SYS_UID_MAX, resetting.", path); - defs.system_alloc_uid_min = MIN(defs.system_uid_max - 1, (uid_t) SYSTEM_ALLOC_UID_MIN); - /* Look at sys_uid_max to make sure sys_uid_min..sys_uid_max remains a valid range. */ - } - if (defs.system_alloc_gid_min > defs.system_gid_max) { - log_debug("%s: SYS_GID_MIN > SYS_GID_MAX, resetting.", path); - defs.system_alloc_gid_min = MIN(defs.system_gid_max - 1, (gid_t) SYSTEM_ALLOC_GID_MIN); - /* Look at sys_gid_max to make sure sys_gid_min..sys_gid_max remains a valid range. */ - } - - *ret_defs = defs; - return 1; -defaults: -#endif - *ret_defs = default_ugid_allocation_range; - return 0; -} - -const UGIDAllocationRange *acquire_ugid_allocation_range(void) { -#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES - static thread_local UGIDAllocationRange defs; - static thread_local int initialized = 0; /* == 0 → not initialized yet - * < 0 → failure - * > 0 → success */ - - /* This function will ignore failure to read the file, so it should only be called from places where - * we don't crucially depend on the answer. In other words, it's appropriate for journald, but - * probably not for sysusers. */ - - if (initialized == 0) - initialized = read_login_defs(&defs, NULL, NULL) < 0 ? -1 : 1; - if (initialized < 0) - return &default_ugid_allocation_range; - - return &defs; - -#endif - return &default_ugid_allocation_range; -} - -bool uid_is_system(uid_t uid) { - const UGIDAllocationRange *defs; - assert_se(defs = acquire_ugid_allocation_range()); - - return uid <= defs->system_uid_max; -} - -bool gid_is_system(gid_t gid) { - const UGIDAllocationRange *defs; - assert_se(defs = acquire_ugid_allocation_range()); - - return gid <= defs->system_gid_max; -} - -bool uid_for_system_journal(uid_t uid) { - - /* Returns true if the specified UID shall get its data stored in the system journal. */ - - return uid_is_system(uid) || uid_is_dynamic(uid) || uid == UID_NOBODY || uid_is_container(uid); -} diff --git a/src/basic/uid-alloc-range.h b/src/basic/uid-alloc-range.h deleted file mode 100644 index 5badde1..0000000 --- a/src/basic/uid-alloc-range.h +++ /dev/null @@ -1,36 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include -#include - -bool uid_is_system(uid_t uid); -bool gid_is_system(gid_t gid); - -static inline bool uid_is_dynamic(uid_t uid) { - return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX; -} - -static inline bool gid_is_dynamic(gid_t gid) { - return uid_is_dynamic((uid_t) gid); -} - -static inline bool uid_is_container(uid_t uid) { - return CONTAINER_UID_BASE_MIN <= uid && uid <= CONTAINER_UID_BASE_MAX; -} - -static inline bool gid_is_container(gid_t gid) { - return uid_is_container((uid_t) gid); -} - -typedef struct UGIDAllocationRange { - uid_t system_alloc_uid_min; - uid_t system_uid_max; - gid_t system_alloc_gid_min; - gid_t system_gid_max; -} UGIDAllocationRange; - -int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root); -const UGIDAllocationRange *acquire_ugid_allocation_range(void); - -bool uid_for_system_journal(uid_t uid); diff --git a/src/basic/uid-classification.c b/src/basic/uid-classification.c new file mode 100644 index 0000000..e2d2ceb --- /dev/null +++ b/src/basic/uid-classification.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "chase.h" +#include "fd-util.h" +#include "fileio.h" +#include "missing_threads.h" +#include "string-util.h" +#include "uid-classification.h" +#include "user-util.h" + +static const UGIDAllocationRange default_ugid_allocation_range = { + .system_alloc_uid_min = SYSTEM_ALLOC_UID_MIN, + .system_uid_max = SYSTEM_UID_MAX, + .system_alloc_gid_min = SYSTEM_ALLOC_GID_MIN, + .system_gid_max = SYSTEM_GID_MAX, +}; + +#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES +static int parse_alloc_uid(const char *path, const char *name, const char *t, uid_t *ret_uid) { + uid_t uid; + int r; + + r = parse_uid(t, &uid); + if (r < 0) + return log_debug_errno(r, "%s: failed to parse %s %s, ignoring: %m", path, name, t); + if (uid == 0) + uid = 1; + + *ret_uid = uid; + return 0; +} +#endif + +int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root) { +#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES + _cleanup_fclose_ FILE *f = NULL; + UGIDAllocationRange defs; + int r; + + if (!path) + path = "/etc/login.defs"; + + r = chase_and_fopen_unlocked(path, root, CHASE_PREFIX_ROOT, "re", NULL, &f); + if (r == -ENOENT) + goto defaults; + if (r < 0) + return log_debug_errno(r, "Failed to open %s: %m", path); + + defs = default_ugid_allocation_range; + + for (;;) { + _cleanup_free_ char *line = NULL; + char *t; + + r = read_line(f, LINE_MAX, &line); + if (r < 0) + return log_debug_errno(r, "Failed to read %s: %m", path); + if (r == 0) + break; + + if ((t = first_word(line, "SYS_UID_MIN"))) + (void) parse_alloc_uid(path, "SYS_UID_MIN", t, &defs.system_alloc_uid_min); + else if ((t = first_word(line, "SYS_UID_MAX"))) + (void) parse_alloc_uid(path, "SYS_UID_MAX", t, &defs.system_uid_max); + else if ((t = first_word(line, "SYS_GID_MIN"))) + (void) parse_alloc_uid(path, "SYS_GID_MIN", t, &defs.system_alloc_gid_min); + else if ((t = first_word(line, "SYS_GID_MAX"))) + (void) parse_alloc_uid(path, "SYS_GID_MAX", t, &defs.system_gid_max); + } + + if (defs.system_alloc_uid_min > defs.system_uid_max) { + log_debug("%s: SYS_UID_MIN > SYS_UID_MAX, resetting.", path); + defs.system_alloc_uid_min = MIN(defs.system_uid_max - 1, (uid_t) SYSTEM_ALLOC_UID_MIN); + /* Look at sys_uid_max to make sure sys_uid_min..sys_uid_max remains a valid range. */ + } + if (defs.system_alloc_gid_min > defs.system_gid_max) { + log_debug("%s: SYS_GID_MIN > SYS_GID_MAX, resetting.", path); + defs.system_alloc_gid_min = MIN(defs.system_gid_max - 1, (gid_t) SYSTEM_ALLOC_GID_MIN); + /* Look at sys_gid_max to make sure sys_gid_min..sys_gid_max remains a valid range. */ + } + + *ret_defs = defs; + return 1; +defaults: +#endif + *ret_defs = default_ugid_allocation_range; + return 0; +} + +const UGIDAllocationRange *acquire_ugid_allocation_range(void) { +#if ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES + static thread_local UGIDAllocationRange defs; + static thread_local int initialized = 0; /* == 0 → not initialized yet + * < 0 → failure + * > 0 → success */ + + /* This function will ignore failure to read the file, so it should only be called from places where + * we don't crucially depend on the answer. In other words, it's appropriate for journald, but + * probably not for sysusers. */ + + if (initialized == 0) + initialized = read_login_defs(&defs, NULL, NULL) < 0 ? -1 : 1; + if (initialized < 0) + return &default_ugid_allocation_range; + + return &defs; + +#endif + return &default_ugid_allocation_range; +} + +bool uid_is_system(uid_t uid) { + const UGIDAllocationRange *defs; + assert_se(defs = acquire_ugid_allocation_range()); + + return uid <= defs->system_uid_max; +} + +bool gid_is_system(gid_t gid) { + const UGIDAllocationRange *defs; + assert_se(defs = acquire_ugid_allocation_range()); + + return gid <= defs->system_gid_max; +} + +bool uid_for_system_journal(uid_t uid) { + + /* Returns true if the specified UID shall get its data stored in the system journal. */ + + return uid_is_system(uid) || uid_is_dynamic(uid) || uid == UID_NOBODY || uid_is_container(uid); +} diff --git a/src/basic/uid-classification.h b/src/basic/uid-classification.h new file mode 100644 index 0000000..5badde1 --- /dev/null +++ b/src/basic/uid-classification.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +bool uid_is_system(uid_t uid); +bool gid_is_system(gid_t gid); + +static inline bool uid_is_dynamic(uid_t uid) { + return DYNAMIC_UID_MIN <= uid && uid <= DYNAMIC_UID_MAX; +} + +static inline bool gid_is_dynamic(gid_t gid) { + return uid_is_dynamic((uid_t) gid); +} + +static inline bool uid_is_container(uid_t uid) { + return CONTAINER_UID_BASE_MIN <= uid && uid <= CONTAINER_UID_BASE_MAX; +} + +static inline bool gid_is_container(gid_t gid) { + return uid_is_container((uid_t) gid); +} + +typedef struct UGIDAllocationRange { + uid_t system_alloc_uid_min; + uid_t system_uid_max; + gid_t system_alloc_gid_min; + gid_t system_gid_max; +} UGIDAllocationRange; + +int read_login_defs(UGIDAllocationRange *ret_defs, const char *path, const char *root); +const UGIDAllocationRange *acquire_ugid_allocation_range(void); + +bool uid_for_system_journal(uid_t uid); diff --git a/src/basic/uid-range.c b/src/basic/uid-range.c index 8463599..a765881 100644 --- a/src/basic/uid-range.c +++ b/src/basic/uid-range.c @@ -10,12 +10,13 @@ #include "format-util.h" #include "macro.h" #include "path-util.h" +#include "process-util.h" #include "sort-util.h" #include "stat-util.h" #include "uid-range.h" #include "user-util.h" -UidRange *uid_range_free(UidRange *range) { +UIDRange *uid_range_free(UIDRange *range) { if (!range) return NULL; @@ -23,14 +24,14 @@ UidRange *uid_range_free(UidRange *range) { return mfree(range); } -static bool uid_range_entry_intersect(const UidRangeEntry *a, const UidRangeEntry *b) { +static bool uid_range_entry_intersect(const UIDRangeEntry *a, const UIDRangeEntry *b) { assert(a); assert(b); return a->start <= b->start + b->nr && a->start + a->nr >= b->start; } -static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry *b) { +static int uid_range_entry_compare(const UIDRangeEntry *a, const UIDRangeEntry *b) { int r; assert(a); @@ -43,7 +44,7 @@ static int uid_range_entry_compare(const UidRangeEntry *a, const UidRangeEntry * return CMP(a->nr, b->nr); } -static void uid_range_coalesce(UidRange *range) { +static void uid_range_coalesce(UIDRange *range) { assert(range); if (range->n_entries <= 0) @@ -52,10 +53,10 @@ static void uid_range_coalesce(UidRange *range) { typesafe_qsort(range->entries, range->n_entries, uid_range_entry_compare); for (size_t i = 0; i < range->n_entries; i++) { - UidRangeEntry *x = range->entries + i; + UIDRangeEntry *x = range->entries + i; for (size_t j = i + 1; j < range->n_entries; j++) { - UidRangeEntry *y = range->entries + j; + UIDRangeEntry *y = range->entries + j; uid_t begin, end; if (!uid_range_entry_intersect(x, y)) @@ -68,7 +69,7 @@ static void uid_range_coalesce(UidRange *range) { x->nr = end - begin; if (range->n_entries > j + 1) - memmove(y, y + 1, sizeof(UidRangeEntry) * (range->n_entries - j - 1)); + memmove(y, y + 1, sizeof(UIDRangeEntry) * (range->n_entries - j - 1)); range->n_entries--; j--; @@ -76,9 +77,9 @@ static void uid_range_coalesce(UidRange *range) { } } -int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce) { - _cleanup_(uid_range_freep) UidRange *range_new = NULL; - UidRange *p; +int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce) { + _cleanup_(uid_range_freep) UIDRange *range_new = NULL; + UIDRange *p; assert(range); @@ -91,7 +92,7 @@ int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesc if (*range) p = *range; else { - range_new = new0(UidRange, 1); + range_new = new0(UIDRange, 1); if (!range_new) return -ENOMEM; @@ -101,7 +102,7 @@ int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesc if (!GREEDY_REALLOC(p->entries, p->n_entries + 1)) return -ENOMEM; - p->entries[p->n_entries++] = (UidRangeEntry) { + p->entries[p->n_entries++] = (UIDRangeEntry) { .start = start, .nr = nr, }; @@ -115,7 +116,7 @@ int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesc return 0; } -int uid_range_add_str(UidRange **range, const char *s) { +int uid_range_add_str(UIDRange **range, const char *s) { uid_t start, end; int r; @@ -129,7 +130,7 @@ int uid_range_add_str(UidRange **range, const char *s) { return uid_range_add_internal(range, start, end - start + 1, /* coalesce = */ true); } -int uid_range_next_lower(const UidRange *range, uid_t *uid) { +int uid_range_next_lower(const UIDRange *range, uid_t *uid) { uid_t closest = UID_INVALID, candidate; assert(range); @@ -162,7 +163,7 @@ int uid_range_next_lower(const UidRange *range, uid_t *uid) { return 1; } -bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) { +bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr) { if (nr == 0) /* empty range? always covered... */ return true; @@ -172,16 +173,40 @@ bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr) { if (!range) return false; - for (size_t i = 0; i < range->n_entries; i++) - if (start >= range->entries[i].start && - start + nr <= range->entries[i].start + range->entries[i].nr) + FOREACH_ARRAY(i, range->entries, range->n_entries) + if (start >= i->start && + start + nr <= i->start + i->nr) return true; return false; } -int uid_range_load_userns(UidRange **ret, const char *path) { - _cleanup_(uid_range_freep) UidRange *range = NULL; +int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range) { + uid_t uid_base, uid_shift, uid_range; + int r; + + assert(f); + assert(ret_base); + assert(ret_shift); + assert(ret_range); + + errno = 0; + r = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); + if (r == EOF) + return errno_or_else(ENOMSG); + assert(r >= 0); + if (r != 3) + return -EBADMSG; + + *ret_base = uid_base; + *ret_shift = uid_shift; + *ret_range = uid_range; + + return 0; +} + +int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret) { + _cleanup_(uid_range_freep) UIDRange *range = NULL; _cleanup_fclose_ FILE *f = NULL; int r; @@ -191,10 +216,12 @@ int uid_range_load_userns(UidRange **ret, const char *path) { * * To simplify things this will modify the passed array in case of later failure. */ + assert(mode >= 0); + assert(mode < _UID_RANGE_USERNS_MODE_MAX); assert(ret); if (!path) - path = "/proc/self/uid_map"; + path = IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "/proc/self/uid_map" : "/proc/self/gid_map"; f = fopen(path, "re"); if (!f) { @@ -206,26 +233,24 @@ int uid_range_load_userns(UidRange **ret, const char *path) { return r; } - range = new0(UidRange, 1); + range = new0(UIDRange, 1); if (!range) return -ENOMEM; for (;;) { uid_t uid_base, uid_shift, uid_range; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); - if (k == EOF) { - if (ferror(f)) - return errno_or_else(EIO); + r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); + if (r == -ENOMSG) break; - } - if (k != 3) - return -EBADMSG; + if (r < 0) + return r; - r = uid_range_add_internal(&range, uid_base, uid_range, /* coalesce = */ false); + r = uid_range_add_internal( + &range, + IN_SET(mode, UID_RANGE_USERNS_INSIDE, GID_RANGE_USERNS_INSIDE) ? uid_base : uid_shift, + uid_range, + /* coalesce = */ false); if (r < 0) return r; } @@ -235,3 +260,103 @@ int uid_range_load_userns(UidRange **ret, const char *path) { *ret = TAKE_PTR(range); return 0; } + +int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret) { + _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + ssize_t n; + char x; + int r; + + assert(userns_fd >= 0); + assert(mode >= 0); + assert(mode < _UID_RANGE_USERNS_MODE_MAX); + assert(ret); + + if (pipe2(pfd, O_CLOEXEC) < 0) + return -errno; + + r = safe_fork_full( + "(sd-mkuserns)", + /* stdio_fds= */ NULL, + (int[]) { pfd[1], userns_fd }, 2, + FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL, + &pid); + if (r < 0) + return r; + if (r == 0) { + /* Child. */ + + if (setns(userns_fd, CLONE_NEWUSER) < 0) { + log_debug_errno(errno, "Failed to join userns: %m"); + _exit(EXIT_FAILURE); + } + + userns_fd = safe_close(userns_fd); + + n = write(pfd[1], &(const char) { 'x' }, 1); + if (n < 0) { + log_debug_errno(errno, "Failed to write to fifo: %m"); + _exit(EXIT_FAILURE); + } + assert(n == 1); + + freeze(); + } + + pfd[1] = safe_close(pfd[1]); + + n = read(pfd[0], &x, 1); + if (n < 0) + return -errno; + if (n == 0) + return -EPROTO; + assert(n == 1); + assert(x == 'x'); + + const char *p = procfs_file_alloca( + pid, + IN_SET(mode, UID_RANGE_USERNS_INSIDE, UID_RANGE_USERNS_OUTSIDE) ? "uid_map" : "gid_map"); + + return uid_range_load_userns(p, mode, ret); +} + +bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr) { + + if (!range) + return false; + + /* Avoid overflow */ + if (start > UINT32_MAX - nr) + nr = UINT32_MAX - start; + + if (nr == 0) + return false; + + FOREACH_ARRAY(entry, range->entries, range->n_entries) + if (start < entry->start + entry->nr && + start + nr >= entry->start) + return true; + + return false; +} + +bool uid_range_equal(const UIDRange *a, const UIDRange *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + if (a->n_entries != b->n_entries) + return false; + + for (size_t i = 0; i < a->n_entries; i++) { + if (a->entries[i].start != b->entries[i].start) + return false; + if (a->entries[i].nr != b->entries[i].nr) + return false; + } + + return true; +} diff --git a/src/basic/uid-range.h b/src/basic/uid-range.h index 461a511..1f687b2 100644 --- a/src/basic/uid-range.h +++ b/src/basic/uid-range.h @@ -6,29 +6,73 @@ #include "macro.h" -typedef struct UidRangeEntry { +typedef struct UIDRangeEntry { uid_t start, nr; -} UidRangeEntry; +} UIDRangeEntry; -typedef struct UidRange { - UidRangeEntry *entries; +typedef struct UIDRange { + UIDRangeEntry *entries; size_t n_entries; -} UidRange; +} UIDRange; -UidRange *uid_range_free(UidRange *range); -DEFINE_TRIVIAL_CLEANUP_FUNC(UidRange*, uid_range_free); +UIDRange *uid_range_free(UIDRange *range); +DEFINE_TRIVIAL_CLEANUP_FUNC(UIDRange*, uid_range_free); -int uid_range_add_internal(UidRange **range, uid_t start, uid_t nr, bool coalesce); -static inline int uid_range_add(UidRange **range, uid_t start, uid_t nr) { +int uid_range_add_internal(UIDRange **range, uid_t start, uid_t nr, bool coalesce); +static inline int uid_range_add(UIDRange **range, uid_t start, uid_t nr) { return uid_range_add_internal(range, start, nr, true); } -int uid_range_add_str(UidRange **range, const char *s); +int uid_range_add_str(UIDRange **range, const char *s); -int uid_range_next_lower(const UidRange *range, uid_t *uid); +int uid_range_next_lower(const UIDRange *range, uid_t *uid); -bool uid_range_covers(const UidRange *range, uid_t start, uid_t nr); -static inline bool uid_range_contains(const UidRange *range, uid_t uid) { +bool uid_range_covers(const UIDRange *range, uid_t start, uid_t nr); +static inline bool uid_range_contains(const UIDRange *range, uid_t uid) { return uid_range_covers(range, uid, 1); } -int uid_range_load_userns(UidRange **ret, const char *path); +int uid_map_read_one(FILE *f, uid_t *ret_base, uid_t *ret_shift, uid_t *ret_range); + +static inline size_t uid_range_entries(const UIDRange *range) { + return range ? range->n_entries : 0; +} + +static inline unsigned uid_range_size(const UIDRange *range) { + if (!range) + return 0; + + unsigned n = 0; + + FOREACH_ARRAY(e, range->entries, range->n_entries) + n += e->nr; + + return n; +} + +static inline bool uid_range_is_empty(const UIDRange *range) { + + if (!range) + return true; + + FOREACH_ARRAY(e, range->entries, range->n_entries) + if (e->nr > 0) + return false; + + return true; +} + +bool uid_range_equal(const UIDRange *a, const UIDRange *b); + +typedef enum UIDRangeUsernsMode { + UID_RANGE_USERNS_INSIDE, + UID_RANGE_USERNS_OUTSIDE, + GID_RANGE_USERNS_INSIDE, + GID_RANGE_USERNS_OUTSIDE, + _UID_RANGE_USERNS_MODE_MAX, + _UID_RANGE_USERNS_MODE_INVALID = -EINVAL, +} UIDRangeUsernsMode; + +int uid_range_load_userns(const char *path, UIDRangeUsernsMode mode, UIDRange **ret); +int uid_range_load_userns_by_fd(int userns_fd, UIDRangeUsernsMode mode, UIDRange **ret); + +bool uid_range_overlaps(const UIDRange *range, uid_t start, uid_t nr); diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c index 908c0cd..d03363b 100644 --- a/src/basic/unit-def.c +++ b/src/basic/unit-def.c @@ -99,7 +99,7 @@ static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { [UNIT_BAD_SETTING] = "bad-setting", [UNIT_ERROR] = "error", [UNIT_MERGED] = "merged", - [UNIT_MASKED] = "masked" + [UNIT_MASKED] = "masked", }; DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); @@ -117,14 +117,33 @@ static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); static const char* const freezer_state_table[_FREEZER_STATE_MAX] = { - [FREEZER_RUNNING] = "running", - [FREEZER_FREEZING] = "freezing", - [FREEZER_FROZEN] = "frozen", - [FREEZER_THAWING] = "thawing", + [FREEZER_RUNNING] = "running", + [FREEZER_FREEZING] = "freezing", + [FREEZER_FREEZING_BY_PARENT] = "freezing-by-parent", + [FREEZER_FROZEN] = "frozen", + [FREEZER_FROZEN_BY_PARENT] = "frozen-by-parent", + [FREEZER_THAWING] = "thawing", }; DEFINE_STRING_TABLE_LOOKUP(freezer_state, FreezerState); +/* Maps in-progress freezer states to the corresponding finished state */ +static const FreezerState freezer_state_finish_table[_FREEZER_STATE_MAX] = { + [FREEZER_FREEZING] = FREEZER_FROZEN, + [FREEZER_FREEZING_BY_PARENT] = FREEZER_FROZEN_BY_PARENT, + [FREEZER_THAWING] = FREEZER_RUNNING, + + /* Finished states trivially map to themselves */ + [FREEZER_RUNNING] = FREEZER_RUNNING, + [FREEZER_FROZEN] = FREEZER_FROZEN, + [FREEZER_FROZEN_BY_PARENT] = FREEZER_FROZEN_BY_PARENT, +}; + +FreezerState freezer_state_finish(FreezerState state) { + assert(state >= 0 && state < _FREEZER_STATE_MAX); + return freezer_state_finish_table[state]; +} + static const char* const unit_marker_table[_UNIT_MARKER_MAX] = { [UNIT_MARKER_NEEDS_RELOAD] = "needs-reload", [UNIT_MARKER_NEEDS_RESTART] = "needs-restart", @@ -136,7 +155,7 @@ static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { [AUTOMOUNT_DEAD] = "dead", [AUTOMOUNT_WAITING] = "waiting", [AUTOMOUNT_RUNNING] = "running", - [AUTOMOUNT_FAILED] = "failed" + [AUTOMOUNT_FAILED] = "failed", }; DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); @@ -170,7 +189,7 @@ static const char* const path_state_table[_PATH_STATE_MAX] = { [PATH_DEAD] = "dead", [PATH_WAITING] = "waiting", [PATH_RUNNING] = "running", - [PATH_FAILED] = "failed" + [PATH_FAILED] = "failed", }; DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); @@ -219,7 +238,7 @@ DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); static const char* const slice_state_table[_SLICE_STATE_MAX] = { [SLICE_DEAD] = "dead", - [SLICE_ACTIVE] = "active" + [SLICE_ACTIVE] = "active", }; DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); @@ -259,7 +278,7 @@ DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); static const char* const target_state_table[_TARGET_STATE_MAX] = { [TARGET_DEAD] = "dead", - [TARGET_ACTIVE] = "active" + [TARGET_ACTIVE] = "active", }; DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); @@ -269,7 +288,7 @@ static const char* const timer_state_table[_TIMER_STATE_MAX] = { [TIMER_WAITING] = "waiting", [TIMER_RUNNING] = "running", [TIMER_ELAPSED] = "elapsed", - [TIMER_FAILED] = "failed" + [TIMER_FAILED] = "failed", }; DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); @@ -314,7 +333,7 @@ static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { [NOTIFY_NONE] = "none", [NOTIFY_MAIN] = "main", [NOTIFY_EXEC] = "exec", - [NOTIFY_ALL] = "all" + [NOTIFY_ALL] = "all", }; DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h index 6627da5..8e73e28 100644 --- a/src/basic/unit-def.h +++ b/src/basic/unit-def.h @@ -53,8 +53,10 @@ typedef enum UnitActiveState { typedef enum FreezerState { FREEZER_RUNNING, - FREEZER_FREEZING, + FREEZER_FREEZING, /* freezing due to user request */ FREEZER_FROZEN, + FREEZER_FREEZING_BY_PARENT, /* freezing as a result of parent slice freezing */ + FREEZER_FROZEN_BY_PARENT, FREEZER_THAWING, _FREEZER_STATE_MAX, _FREEZER_STATE_INVALID = -EINVAL, @@ -297,6 +299,7 @@ UnitActiveState unit_active_state_from_string(const char *s) _pure_; const char *freezer_state_to_string(FreezerState i) _const_; FreezerState freezer_state_from_string(const char *s) _pure_; +FreezerState freezer_state_finish(FreezerState i) _const_; const char *unit_marker_to_string(UnitMarker m) _const_; UnitMarker unit_marker_from_string(const char *s) _pure_; diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c index 8bf28ba..4e2f77c 100644 --- a/src/basic/unit-name.c +++ b/src/basic/unit-name.c @@ -454,34 +454,45 @@ int unit_name_path_unescape(const char *f, char **ret) { return 0; } -int unit_name_replace_instance(const char *f, const char *i, char **ret) { +int unit_name_replace_instance_full( + const char *original, + const char *instance, + bool accept_glob, + char **ret) { + _cleanup_free_ char *s = NULL; - const char *p, *e; - size_t a, b; + const char *prefix, *suffix; + size_t pl; - assert(f); - assert(i); + assert(original); + assert(instance); assert(ret); - if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + if (!unit_name_is_valid(original, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) return -EINVAL; - if (!unit_instance_is_valid(i)) + if (!unit_instance_is_valid(instance) && !(accept_glob && in_charset(instance, VALID_CHARS_GLOB))) return -EINVAL; - assert_se(p = strchr(f, '@')); - assert_se(e = strrchr(f, '.')); + prefix = ASSERT_PTR(strchr(original, '@')); + suffix = ASSERT_PTR(strrchr(original, '.')); + assert(prefix < suffix); - a = p - f; - b = strlen(i); + pl = prefix - original + 1; /* include '@' */ - s = new(char, a + 1 + b + strlen(e) + 1); + s = new(char, pl + strlen(instance) + strlen(suffix) + 1); if (!s) return -ENOMEM; - strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); +#if HAS_FEATURE_MEMORY_SANITIZER + /* MSan doesn't like stpncpy... See also https://github.com/google/sanitizers/issues/926 */ + memzero(s, pl + strlen(instance) + strlen(suffix) + 1); +#endif - /* Make sure the resulting name still is valid, i.e. didn't grow too large */ - if (!unit_name_is_valid(s, UNIT_NAME_INSTANCE)) + strcpy(stpcpy(stpncpy(s, original, pl), instance), suffix); + + /* Make sure the resulting name still is valid, i.e. didn't grow too large. Globs will be expanded + * by clients when used, so the check is pointless. */ + if (!accept_glob && !unit_name_is_valid(s, UNIT_NAME_INSTANCE)) return -EINVAL; *ret = TAKE_PTR(s); @@ -782,19 +793,10 @@ int unit_name_mangle_with_suffix( return 1; good: - s = strdup(name); - if (!s) - return -ENOMEM; - - *ret = TAKE_PTR(s); - return 0; + return strdup_to(ret, name); } int slice_build_parent_slice(const char *slice, char **ret) { - _cleanup_free_ char *s = NULL; - char *dash; - int r; - assert(slice); assert(ret); @@ -806,18 +808,16 @@ int slice_build_parent_slice(const char *slice, char **ret) { return 0; } - s = strdup(slice); + _cleanup_free_ char *s = strdup(slice); if (!s) return -ENOMEM; - dash = strrchr(s, '-'); - if (dash) - strcpy(dash, ".slice"); - else { - r = free_and_strdup(&s, SPECIAL_ROOT_SLICE); - if (r < 0) - return r; - } + char *dash = strrchr(s, '-'); + if (!dash) + return strdup_to_full(ret, SPECIAL_ROOT_SLICE); + + /* We know that s ended with .slice before truncation, so we have enough space. */ + strcpy(dash, ".slice"); *ret = TAKE_PTR(s); return 1; diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h index eaa701e..fa7295e 100644 --- a/src/basic/unit-name.h +++ b/src/basic/unit-name.h @@ -33,14 +33,21 @@ UnitType unit_name_to_type(const char *n) _pure_; int unit_name_change_suffix(const char *n, const char *suffix, char **ret); int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); -int unit_name_build_from_type(const char *prefix, const char *instance, UnitType, char **ret); +int unit_name_build_from_type(const char *prefix, const char *instance, UnitType type, char **ret); char *unit_name_escape(const char *f); int unit_name_unescape(const char *f, char **ret); int unit_name_path_escape(const char *f, char **ret); int unit_name_path_unescape(const char *f, char **ret); -int unit_name_replace_instance(const char *f, const char *i, char **ret); +int unit_name_replace_instance_full( + const char *original, + const char *instance, + bool accept_glob, + char **ret); +static inline int unit_name_replace_instance(const char *original, const char *instance, char **ret) { + return unit_name_replace_instance_full(original, instance, false, ret); +} int unit_name_template(const char *f, char **ret); diff --git a/src/basic/user-util.c b/src/basic/user-util.c index 9e6926b..6bdf5bf 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -184,27 +184,28 @@ const char* default_root_shell(const char *root) { static int synthesize_user_creds( const char **username, - uid_t *uid, gid_t *gid, - const char **home, - const char **shell, + uid_t *ret_uid, gid_t *ret_gid, + const char **ret_home, + const char **ret_shell, UserCredsFlags flags) { + assert(username); + assert(*username); + /* We enforce some special rules for uid=0 and uid=65534: in order to avoid NSS lookups for root we hardcode * their user record data. */ if (STR_IN_SET(*username, "root", "0")) { *username = "root"; - if (uid) - *uid = 0; - if (gid) - *gid = 0; - - if (home) - *home = "/root"; - - if (shell) - *shell = default_root_shell(NULL); + if (ret_uid) + *ret_uid = 0; + if (ret_gid) + *ret_gid = 0; + if (ret_home) + *ret_home = "/root"; + if (ret_shell) + *ret_shell = default_root_shell(NULL); return 0; } @@ -213,16 +214,14 @@ static int synthesize_user_creds( synthesize_nobody()) { *username = NOBODY_USER_NAME; - if (uid) - *uid = UID_NOBODY; - if (gid) - *gid = GID_NOBODY; - - if (home) - *home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/"; - - if (shell) - *shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN; + if (ret_uid) + *ret_uid = UID_NOBODY; + if (ret_gid) + *ret_gid = GID_NOBODY; + if (ret_home) + *ret_home = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : "/"; + if (ret_shell) + *ret_shell = FLAGS_SET(flags, USER_CREDS_CLEAN) ? NULL : NOLOGIN; return 0; } @@ -232,11 +231,12 @@ static int synthesize_user_creds( int get_user_creds( const char **username, - uid_t *uid, gid_t *gid, - const char **home, - const char **shell, + uid_t *ret_uid, gid_t *ret_gid, + const char **ret_home, + const char **ret_shell, UserCredsFlags flags) { + bool patch_username = false; uid_t u = UID_INVALID; struct passwd *p; int r; @@ -245,7 +245,7 @@ int get_user_creds( assert(*username); if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS) || - (!home && !shell)) { + (!ret_home && !ret_shell)) { /* So here's the deal: normally, we'll try to synthesize all records we can synthesize, and override * the user database with that. However, if the user specifies USER_CREDS_PREFER_NSS then the @@ -256,7 +256,7 @@ int get_user_creds( * of the relevant users, but changing the UID/GID mappings for them is something we explicitly don't * support. */ - r = synthesize_user_creds(username, uid, gid, home, shell, flags); + r = synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags); if (r >= 0) return 0; if (r != -ENOMEDIUM) /* not a username we can synthesize */ @@ -271,15 +271,15 @@ int get_user_creds( * instead of the first occurrence in the database. However if the uid was configured by a numeric uid, * then let's pick the real username from /etc/passwd. */ if (p) - *username = p->pw_name; - else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !gid && !home && !shell) { + patch_username = true; + else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING) && !ret_gid && !ret_home && !ret_shell) { /* If the specified user is a numeric UID and it isn't in the user database, and the caller * passed USER_CREDS_ALLOW_MISSING and was only interested in the UID, then just return that * and don't complain. */ - if (uid) - *uid = u; + if (ret_uid) + *ret_uid = u; return 0; } @@ -293,65 +293,57 @@ int get_user_creds( r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; /* If the user requested that we only synthesize as fallback, do so now */ - if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { - if (synthesize_user_creds(username, uid, gid, home, shell, flags) >= 0) + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) + if (synthesize_user_creds(username, ret_uid, ret_gid, ret_home, ret_shell, flags) >= 0) return 0; - } return r; } - if (uid) { - if (!uid_is_valid(p->pw_uid)) - return -EBADMSG; + if (ret_uid && !uid_is_valid(p->pw_uid)) + return -EBADMSG; - *uid = p->pw_uid; - } + if (ret_gid && !gid_is_valid(p->pw_gid)) + return -EBADMSG; - if (gid) { - if (!gid_is_valid(p->pw_gid)) - return -EBADMSG; + if (ret_uid) + *ret_uid = p->pw_uid; - *gid = p->pw_gid; - } + if (ret_gid) + *ret_gid = p->pw_gid; - if (home) { - if (FLAGS_SET(flags, USER_CREDS_CLEAN) && - (empty_or_root(p->pw_dir) || - !path_is_valid(p->pw_dir) || - !path_is_absolute(p->pw_dir))) - *home = NULL; /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ - else - *home = p->pw_dir; - } + if (ret_home) + /* Note: we don't insist on normalized paths, since there are setups that have /./ in the path */ + *ret_home = (FLAGS_SET(flags, USER_CREDS_CLEAN) && + (empty_or_root(p->pw_dir) || + !path_is_valid(p->pw_dir) || + !path_is_absolute(p->pw_dir))) ? NULL : p->pw_dir; - if (shell) { - if (FLAGS_SET(flags, USER_CREDS_CLEAN) && - (isempty(p->pw_shell) || - !path_is_valid(p->pw_shell) || - !path_is_absolute(p->pw_shell) || - is_nologin_shell(p->pw_shell))) - *shell = NULL; - else - *shell = p->pw_shell; - } + if (ret_shell) + *ret_shell = (FLAGS_SET(flags, USER_CREDS_CLEAN) && + (isempty(p->pw_shell) || + !path_is_valid(p->pw_shell) || + !path_is_absolute(p->pw_shell) || + is_nologin_shell(p->pw_shell))) ? NULL : p->pw_shell; + + if (patch_username) + *username = p->pw_name; return 0; } -int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) { - struct group *g; - gid_t id; +static int synthesize_group_creds( + const char **groupname, + gid_t *ret_gid) { assert(groupname); - - /* We enforce some special rules for gid=0: in order to avoid NSS lookups for root we hardcode its data. */ + assert(*groupname); if (STR_IN_SET(*groupname, "root", "0")) { *groupname = "root"; - if (gid) - *gid = 0; + if (ret_gid) + *ret_gid = 0; return 0; } @@ -360,21 +352,41 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) { synthesize_nobody()) { *groupname = NOBODY_GROUP_NAME; - if (gid) - *gid = GID_NOBODY; + if (ret_gid) + *ret_gid = GID_NOBODY; return 0; } + return -ENOMEDIUM; +} + +int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags) { + bool patch_groupname = false; + struct group *g; + gid_t id; + int r; + + assert(groupname); + assert(*groupname); + + if (!FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { + r = synthesize_group_creds(groupname, ret_gid); + if (r >= 0) + return 0; + if (r != -ENOMEDIUM) /* not a groupname we can synthesize */ + return r; + } + if (parse_gid(*groupname, &id) >= 0) { errno = 0; g = getgrgid(id); if (g) - *groupname = g->gr_name; + patch_groupname = true; else if (FLAGS_SET(flags, USER_CREDS_ALLOW_MISSING)) { - if (gid) - *gid = id; + if (ret_gid) + *ret_gid = id; return 0; } @@ -383,18 +395,28 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) { g = getgrnam(*groupname); } - if (!g) + if (!g) { /* getgrnam() may fail with ENOENT if /etc/group is missing. * For us that is equivalent to the name not being defined. */ - return IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; + + if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) + if (synthesize_group_creds(groupname, ret_gid) >= 0) + return 0; - if (gid) { + return r; + } + + if (ret_gid) { if (!gid_is_valid(g->gr_gid)) return -EBADMSG; - *gid = g->gr_gid; + *ret_gid = g->gr_gid; } + if (patch_groupname) + *groupname = g->gr_name; + return 0; } @@ -409,31 +431,11 @@ char* uid_to_name(uid_t uid) { return strdup(NOBODY_USER_NAME); if (uid_is_valid(uid)) { - long bufsize; - - bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; + _cleanup_free_ struct passwd *pw = NULL; - for (;;) { - struct passwd pwbuf, *pw = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); - if (r == 0 && pw) - return strdup(pw->pw_name); - if (r != ERANGE) - break; - - if (bufsize > LONG_MAX/2) /* overflow check */ - return NULL; - - bufsize *= 2; - } + r = getpwuid_malloc(uid, &pw); + if (r >= 0) + return strdup(pw->pw_name); } if (asprintf(&ret, UID_FMT, uid) < 0) @@ -452,31 +454,11 @@ char* gid_to_name(gid_t gid) { return strdup(NOBODY_GROUP_NAME); if (gid_is_valid(gid)) { - long bufsize; - - bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; - - for (;;) { - struct group grbuf, *gr = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); - if (r == 0 && gr) - return strdup(gr->gr_name); - if (r != ERANGE) - break; - - if (bufsize > LONG_MAX/2) /* overflow check */ - return NULL; + _cleanup_free_ struct group *gr = NULL; - bufsize *= 2; - } + r = getgrgid_malloc(gid, &gr); + if (r >= 0) + return strdup(gr->gr_name); } if (asprintf(&ret, GID_FMT, gid) < 0) @@ -587,9 +569,10 @@ int getgroups_alloc(gid_t** gids) { } int get_home_dir(char **ret) { - struct passwd *p; + _cleanup_free_ struct passwd *p = NULL; const char *e; uid_t u; + int r; assert(ret); @@ -604,19 +587,17 @@ int get_home_dir(char **ret) { e = "/root"; goto found; } - if (u == UID_NOBODY && synthesize_nobody()) { e = "/"; goto found; } /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno_or_else(ESRCH); - e = p->pw_dir; + r = getpwuid_malloc(u, &p); + if (r < 0) + return r; + e = p->pw_dir; if (!path_is_valid(e) || !path_is_absolute(e)) return -EINVAL; @@ -625,9 +606,10 @@ int get_home_dir(char **ret) { } int get_shell(char **ret) { - struct passwd *p; + _cleanup_free_ struct passwd *p = NULL; const char *e; uid_t u; + int r; assert(ret); @@ -648,12 +630,11 @@ int get_shell(char **ret) { } /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno_or_else(ESRCH); - e = p->pw_shell; + r = getpwuid_malloc(u, &p); + if (r < 0) + return r; + e = p->pw_shell; if (!path_is_valid(e) || !path_is_absolute(e)) return -EINVAL; @@ -661,17 +642,26 @@ int get_shell(char **ret) { return path_simplify_alloc(e, ret); } -int reset_uid_gid(void) { +int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids) { int r; - r = maybe_setgroups(0, NULL); + assert(supplementary_gids || n_supplementary_gids == 0); + + /* Sets all UIDs and all GIDs to the specified ones. Drops all auxiliary GIDs */ + + r = maybe_setgroups(n_supplementary_gids, supplementary_gids); if (r < 0) return r; - if (setresgid(0, 0, 0) < 0) - return -errno; + if (gid_is_valid(gid)) + if (setresgid(gid, gid, gid) < 0) + return -errno; + + if (uid_is_valid(uid)) + if (setresuid(uid, uid, uid) < 0) + return -errno; - return RET_NERRNO(setresuid(0, 0, 0)); + return 0; } int take_etc_passwd_lock(const char *root) { @@ -807,11 +797,11 @@ bool valid_user_group_name(const char *u, ValidUserFlags flags) { sz = sysconf(_SC_LOGIN_NAME_MAX); assert_se(sz > 0); - if (l > (size_t) sz) + if (l > (size_t) sz) /* glibc: 256 */ return false; - if (l > NAME_MAX) /* must fit in a filename */ + if (l > NAME_MAX) /* must fit in a filename: 255 */ return false; - if (l > UT_NAMESIZE - 1) + if (l > UT_NAMESIZE - 1) /* must fit in utmp: 31 */ return false; } @@ -987,8 +977,8 @@ int fgetpwent_sane(FILE *stream, struct passwd **pw) { errno = 0; struct passwd *p = fgetpwent(stream); - if (!p && errno != ENOENT) - return errno_or_else(EIO); + if (!p && !IN_SET(errno, 0, ENOENT)) + return -errno; *pw = p; return !!p; @@ -1000,8 +990,8 @@ int fgetspent_sane(FILE *stream, struct spwd **sp) { errno = 0; struct spwd *s = fgetspent(stream); - if (!s && errno != ENOENT) - return errno_or_else(EIO); + if (!s && !IN_SET(errno, 0, ENOENT)) + return -errno; *sp = s; return !!s; @@ -1013,8 +1003,8 @@ int fgetgrent_sane(FILE *stream, struct group **gr) { errno = 0; struct group *g = fgetgrent(stream); - if (!g && errno != ENOENT) - return errno_or_else(EIO); + if (!g && !IN_SET(errno, 0, ENOENT)) + return -errno; *gr = g; return !!g; @@ -1027,8 +1017,8 @@ int fgetsgent_sane(FILE *stream, struct sgrp **sg) { errno = 0; struct sgrp *s = fgetsgent(stream); - if (!s && errno != ENOENT) - return errno_or_else(EIO); + if (!s && !IN_SET(errno, 0, ENOENT)) + return -errno; *sg = s; return !!s; @@ -1058,3 +1048,180 @@ const char* get_home_root(void) { return "/home"; } + +static size_t getpw_buffer_size(void) { + long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + return bufsize <= 0 ? 4096U : (size_t) bufsize; +} + +static bool errno_is_user_doesnt_exist(int error) { + /* See getpwnam(3) and getgrnam(3): those codes and others can be returned if the user or group are + * not found. */ + return IN_SET(abs(error), ENOENT, ESRCH, EBADF, EPERM); +} + +int getpwnam_malloc(const char *name, struct passwd **ret) { + size_t bufsize = getpw_buffer_size(); + int r; + + /* A wrapper around getpwnam_r() that allocates the necessary buffer on the heap. The caller must + * free() the returned structures! */ + + if (isempty(name)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize); + if (!buf) + return -ENOMEM; + + struct passwd *pw = NULL; + r = getpwnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw); + if (r == 0) { + if (pw) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + /* getpwnam() may fail with ENOENT if /etc/passwd is missing. For us that is equivalent to + * the name not being defined. */ + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd))) + return -ENOMEM; + bufsize *= 2; + } +} + +int getpwuid_malloc(uid_t uid, struct passwd **ret) { + size_t bufsize = getpw_buffer_size(); + int r; + + if (!uid_is_valid(uid)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct passwd)) + bufsize); + if (!buf) + return -ENOMEM; + + struct passwd *pw = NULL; + r = getpwuid_r(uid, buf, (char*) buf + ALIGN(sizeof(struct passwd)), (size_t) bufsize, &pw); + if (r == 0) { + if (pw) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct passwd))) + return -ENOMEM; + bufsize *= 2; + } +} + +static size_t getgr_buffer_size(void) { + long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + return bufsize <= 0 ? 4096U : (size_t) bufsize; +} + +int getgrnam_malloc(const char *name, struct group **ret) { + size_t bufsize = getgr_buffer_size(); + int r; + + if (isempty(name)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct group)) + bufsize); + if (!buf) + return -ENOMEM; + + struct group *gr = NULL; + r = getgrnam_r(name, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr); + if (r == 0) { + if (gr) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group))) + return -ENOMEM; + bufsize *= 2; + } +} + +int getgrgid_malloc(gid_t gid, struct group **ret) { + size_t bufsize = getgr_buffer_size(); + int r; + + if (!gid_is_valid(gid)) + return -EINVAL; + + for (;;) { + _cleanup_free_ void *buf = NULL; + + buf = malloc(ALIGN(sizeof(struct group)) + bufsize); + if (!buf) + return -ENOMEM; + + struct group *gr = NULL; + r = getgrgid_r(gid, buf, (char*) buf + ALIGN(sizeof(struct group)), (size_t) bufsize, &gr); + if (r == 0) { + if (gr) { + if (ret) + *ret = TAKE_PTR(buf); + return 0; + } + + return -ESRCH; + } + + assert(r > 0); + + if (errno_is_user_doesnt_exist(r)) + return -ESRCH; + if (r != ERANGE) + return -r; + + if (bufsize > SIZE_MAX/2 - ALIGN(sizeof(struct group))) + return -ENOMEM; + bufsize *= 2; + } +} diff --git a/src/basic/user-util.h b/src/basic/user-util.h index f394f62..9d07ef3 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -42,8 +42,8 @@ typedef enum UserCredsFlags { USER_CREDS_CLEAN = 1 << 2, /* try to clean up shell and home fields with invalid data */ } UserCredsFlags; -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell, UserCredsFlags flags); -int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags); +int get_user_creds(const char **username, uid_t *ret_uid, gid_t *ret_gid, const char **ret_home, const char **ret_shell, UserCredsFlags flags); +int get_group_creds(const char **groupname, gid_t *ret_gid, UserCredsFlags flags); char* uid_to_name(uid_t uid); char* gid_to_name(gid_t gid); @@ -57,7 +57,10 @@ int getgroups_alloc(gid_t** gids); int get_home_dir(char **ret); int get_shell(char **ret); -int reset_uid_gid(void); +int fully_set_uid_gid(uid_t uid, gid_t gid, const gid_t supplementary_gids[], size_t n_supplementary_gids); +static inline int reset_uid_gid(void) { + return fully_set_uid_gid(0, 0, NULL, 0); +} int take_etc_passwd_lock(const char *root); @@ -155,3 +158,9 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325. */ #define PASSWORD_UNPROVISIONED "!unprovisioned" + +int getpwuid_malloc(uid_t uid, struct passwd **ret); +int getpwnam_malloc(const char *name, struct passwd **ret); + +int getgrnam_malloc(const char *name, struct group **ret); +int getgrgid_malloc(gid_t gid, struct group **ret); diff --git a/src/basic/utf8.c b/src/basic/utf8.c index 36e1e0f..36f0dc9 100644 --- a/src/basic/utf8.c +++ b/src/basic/utf8.c @@ -1,26 +1,10 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* SPDX-License-Identifier: LGPL-2.0-or-later */ -/* Parts of this file are based on the GLIB utf8 validation functions. The - * original license text follows. */ - -/* gutf8.c - Operations on UTF-8 strings. +/* Parts of this file are based on the GLIB utf8 validation functions. The original copyright follows. * + * gutf8.c - Operations on UTF-8 strings. * Copyright (C) 1999 Tom Tromey * Copyright (C) 2000 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library 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 - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include diff --git a/src/basic/virt.c b/src/basic/virt.c index 88357a9..0970350 100644 --- a/src/basic/virt.c +++ b/src/basic/virt.c @@ -21,6 +21,7 @@ #include "stat-util.h" #include "string-table.h" #include "string-util.h" +#include "uid-range.h" #include "virt.h" enum { @@ -446,7 +447,7 @@ static Virtualization detect_vm_zvm(void) { /* Returns a short identifier for the various VM implementations */ Virtualization detect_vm(void) { static thread_local Virtualization cached_found = _VIRTUALIZATION_INVALID; - bool other = false; + bool other = false, hyperv = false; int xen_dom0 = 0; Virtualization v, dmi; @@ -455,12 +456,12 @@ Virtualization detect_vm(void) { /* We have to use the correct order here: * - * → First, try to detect Oracle Virtualbox, Amazon EC2 Nitro, Parallels, and Google Compute Engine, even if they use KVM, - * as well as Xen even if it cloaks as Microsoft Hyper-V. Attempt to detect uml at this stage also - * since it runs as a user-process nested inside other VMs. Also check for Xen now, because Xen PV - * mode does not override CPUID when nested inside another hypervisor. + * → First, try to detect Oracle Virtualbox, Amazon EC2 Nitro, Parallels, and Google Compute Engine, + * even if they use KVM, as well as Xen, even if it cloaks as Microsoft Hyper-V. Attempt to detect + * UML at this stage too, since it runs as a user-process nested inside other VMs. Also check for + * Xen now, because Xen PV mode does not override CPUID when nested inside another hypervisor. * - * → Second, try to detect from CPUID, this will report KVM for whatever software is used even if + * → Second, try to detect from CPUID. This will report KVM for whatever software is used even if * info in DMI is overwritten. * * → Third, try to detect from DMI. */ @@ -503,7 +504,12 @@ Virtualization detect_vm(void) { v = detect_vm_cpuid(); if (v < 0) return v; - if (v == VIRTUALIZATION_VM_OTHER) + if (v == VIRTUALIZATION_MICROSOFT) + /* QEMU sets the CPUID string to hyperv's, in case it provides hyperv enlightenments. Let's + * hence not return Microsoft here but just use the other mechanisms first to make a better + * decision. */ + hyperv = true; + else if (v == VIRTUALIZATION_VM_OTHER) other = true; else if (v != VIRTUALIZATION_NONE) goto finish; @@ -544,8 +550,15 @@ Virtualization detect_vm(void) { return v; finish: - if (v == VIRTUALIZATION_NONE && other) - v = VIRTUALIZATION_VM_OTHER; + /* None of the checks above gave us a clear answer, hence let's now use fallback logic: if hyperv + * enlightenments are available but the VMM wasn't recognized as anything yet, it's probably + * Microsoft. */ + if (v == VIRTUALIZATION_NONE) { + if (hyperv) + v = VIRTUALIZATION_MICROSOFT; + else if (other) + v = VIRTUALIZATION_VM_OTHER; + } cached_found = v; log_debug("Found VM virtualization %s", virtualization_to_string(v)); @@ -818,7 +831,7 @@ Virtualization detect_virtualization(void) { static int userns_has_mapping(const char *name) { _cleanup_fclose_ FILE *f = NULL; - uid_t a, b, c; + uid_t base, shift, range; int r; f = fopen(name, "re"); @@ -827,26 +840,22 @@ static int userns_has_mapping(const char *name) { return errno == ENOENT ? false : -errno; } - errno = 0; - r = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &a, &b, &c); - if (r == EOF) { - if (ferror(f)) - return log_debug_errno(errno_or_else(EIO), "Failed to read %s: %m", name); - - log_debug("%s is empty, we're in an uninitialized user namespace", name); + r = uid_map_read_one(f, &base, &shift, &range); + if (r == -ENOMSG) { + log_debug("%s is empty, we're in an uninitialized user namespace.", name); return true; } - if (r != 3) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse %s: %m", name); + if (r < 0) + return log_debug_errno(r, "Failed to read %s: %m", name); - if (a == 0 && b == 0 && c == UINT32_MAX) { + if (base == 0 && shift == 0 && range == UINT32_MAX) { /* The kernel calls mappings_overlap() and does not allow overlaps */ log_debug("%s has a full 1:1 mapping", name); return false; } /* Anything else implies that we are in a user namespace */ - log_debug("Mapping found in %s, we're in a user namespace", name); + log_debug("Mapping found in %s, we're in a user namespace.", name); return true; } diff --git a/src/battery-check/battery-check.c b/src/battery-check/battery-check.c index 03628c8..1563147 100644 --- a/src/battery-check/battery-check.c +++ b/src/battery-check/battery-check.c @@ -123,9 +123,9 @@ static int run(int argc, char *argv[]) { log_setup(); - r = proc_cmdline_get_bool("systemd.battery-check", PROC_CMDLINE_STRIP_RD_PREFIX|PROC_CMDLINE_TRUE_WHEN_MISSING, &arg_doit); + r = proc_cmdline_get_bool("systemd.battery_check", PROC_CMDLINE_STRIP_RD_PREFIX|PROC_CMDLINE_TRUE_WHEN_MISSING, &arg_doit); if (r < 0) - log_warning_errno(r, "Failed to parse systemd.battery-check= kernel command line option, ignoring: %m"); + log_warning_errno(r, "Failed to parse systemd.battery_check= kernel command line option, ignoring: %m"); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/boot/bless-boot-generator.c b/src/boot/bless-boot-generator.c index 38b2c3a..cf645e2 100644 --- a/src/boot/bless-boot-generator.c +++ b/src/boot/bless-boot-generator.c @@ -7,7 +7,6 @@ #include "generator.h" #include "initrd-util.h" #include "log.h" -#include "mkdir.h" #include "special.h" #include "string-util.h" #include "virt.h" @@ -17,6 +16,7 @@ * boot as "good" if we manage to boot up far enough. */ static int run(const char *dest, const char *dest_early, const char *dest_late) { + assert(dest_early); if (in_initrd()) { log_debug("Skipping generator, running in the initrd."); @@ -34,7 +34,6 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) } if (access(EFIVAR_PATH(EFI_LOADER_VARIABLE(LoaderBootCountPath)), F_OK) < 0) { - if (errno == ENOENT) { log_debug_errno(errno, "Skipping generator, not booted with boot counting in effect."); return 0; @@ -45,12 +44,7 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) /* We pull this in from basic.target so that it ends up in all "regular" boot ups, but not in * rescue.target or even emergency.target. */ - const char *p = strjoina(dest_early, "/" SPECIAL_BASIC_TARGET ".wants/systemd-bless-boot.service"); - (void) mkdir_parents(p, 0755); - if (symlink(SYSTEM_DATA_UNIT_DIR "/systemd-bless-boot.service", p) < 0) - return log_error_errno(errno, "Failed to create symlink '%s': %m", p); - - return 0; + return generator_add_symlink(dest_early, SPECIAL_BASIC_TARGET, "wants", "systemd-bless-boot.service"); } DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index 0c0b4f2..f86d102 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -316,13 +316,6 @@ static int make_bad(const char *prefix, uint64_t done, const char *suffix, char return 0; } -static const char *skip_slash(const char *path) { - assert(path); - assert(path[0] == '/'); - - return path + 1; -} - static int verb_status(int argc, char *argv[], void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; uint64_t left, done; @@ -370,14 +363,14 @@ static int verb_status(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); } - if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(path), F_OK, 0) >= 0) { puts("indeterminate"); return 0; } if (errno != ENOENT) return log_error_errno(errno, "Failed to check if '%s' exists: %m", path); - if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(good), F_OK, 0) >= 0) { puts("good"); return 0; } @@ -385,7 +378,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (errno != ENOENT) return log_error_errno(errno, "Failed to check if '%s' exists: %m", good); - if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) { + if (faccessat(fd, skip_leading_slash(bad), F_OK, 0) >= 0) { puts("bad"); return 0; } @@ -395,7 +388,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { /* We didn't find any of the three? If so, let's try the next directory, before we give up. */ } - return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state."); } static int verb_set(int argc, char *argv[], void *userdata) { @@ -445,17 +438,17 @@ static int verb_set(int argc, char *argv[], void *userdata) { if (fd < 0) return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); - r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target)); + r = rename_noreplace(fd, skip_leading_slash(source1), fd, skip_leading_slash(target)); if (r == -EEXIST) goto exists; if (r == -ENOENT) { - r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target)); + r = rename_noreplace(fd, skip_leading_slash(source2), fd, skip_leading_slash(target)); if (r == -EEXIST) goto exists; if (r == -ENOENT) { - if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ + if (faccessat(fd, skip_leading_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ goto exists; if (errno != ENOENT) @@ -474,9 +467,9 @@ static int verb_set(int argc, char *argv[], void *userdata) { log_debug("Successfully renamed '%s' to '%s'.", source1, target); /* First, fsync() the directory these files are located in */ - r = fsync_parent_at(fd, skip_slash(target)); + r = fsync_parent_at(fd, skip_leading_slash(target)); if (r < 0) - log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m"); + log_debug_errno(r, "Failed to synchronize image directory, ignoring: %m"); /* Secondly, syncfs() the whole file system these files are located in */ if (syncfs(fd) < 0) @@ -486,8 +479,7 @@ static int verb_set(int argc, char *argv[], void *userdata) { return 0; } - log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target); - return 1; + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s'.", target); exists: log_debug("Operation already executed before, not doing anything."); @@ -506,8 +498,7 @@ static int run(int argc, char *argv[]) { int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/boot/boot-check-no-failures.c b/src/boot/boot-check-no-failures.c index 4ff91cb..56c63b7 100644 --- a/src/boot/boot-check-no-failures.c +++ b/src/boot/boot-check-no-failures.c @@ -79,8 +79,7 @@ static int run(int argc, char *argv[]) { uint32_t n; int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/boot/bootctl-install.c b/src/boot/bootctl-install.c index bacbbb2..dc46d30 100644 --- a/src/boot/bootctl-install.c +++ b/src/boot/bootctl-install.c @@ -14,6 +14,7 @@ #include "fs-util.h" #include "glyph-util.h" #include "id128-util.h" +#include "kernel-config.h" #include "os-util.h" #include "path-util.h" #include "rm-rf.h" @@ -81,22 +82,22 @@ static int load_etc_machine_info(void) { return 0; } -static int load_etc_kernel_install_conf(void) { - _cleanup_free_ char *layout = NULL, *p = NULL; +static int load_kernel_install_layout(void) { + _cleanup_free_ char *layout = NULL; int r; - p = path_join(arg_root, etc_kernel(), "install.conf"); - if (!p) - return log_oom(); - - r = parse_env_file(NULL, p, "layout", &layout); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to parse %s: %m", p); + r = load_kernel_install_conf(arg_root, + getenv("KERNEL_INSTALL_CONF_ROOT"), + /* ret_machine_id= */ NULL, + /* ret_boot_root= */ NULL, + &layout, + /* ret_initrd_generator= */ NULL, + /* ret_uki_generator= */ NULL); + if (r <= 0) + return r; if (!isempty(layout)) { - log_debug("layout=%s is specified in %s.", layout, p); + log_debug("layout=%s is specified in config.", layout); free_and_replace(arg_install_layout, layout); } @@ -120,7 +121,7 @@ static int settle_make_entry_directory(void) { if (r < 0) return r; - r = load_etc_kernel_install_conf(); + r = load_kernel_install_layout(); if (r < 0) return r; @@ -318,6 +319,46 @@ static int create_subdirs(const char *root, const char * const *subdirs) { return 0; } +static int update_efi_boot_binaries(const char *esp_path, const char *source_path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *p = NULL; + int r, ret = 0; + + r = chase_and_opendir("/EFI/BOOT", esp_path, CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, &p, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open directory \"%s/EFI/BOOT\": %m", esp_path); + + FOREACH_DIRENT(de, d, break) { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *v = NULL; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + 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); + + r = get_file_version(fd, &v); + if (r == -ESRCH) + continue; /* No version information */ + if (r < 0) + return r; + if (startswith(v, "systemd-boot ")) { + _cleanup_free_ char *dest_path = NULL; + + dest_path = path_join(p, de->d_name); + if (!dest_path) + return log_oom(); + + RET_GATHER(ret, copy_file_with_version_check(source_path, dest_path, /* force = */ false)); + } + } + + return ret; +} static int copy_one_file(const char *esp_path, const char *name, bool force) { char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; @@ -371,9 +412,12 @@ static int copy_one_file(const char *esp_path, const char *name, bool force) { if (r < 0) return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path); - r = copy_file_with_version_check(source_path, default_dest_path, force); - if (r < 0 && ret == 0) - ret = r; + RET_GATHER(ret, copy_file_with_version_check(source_path, default_dest_path, force)); + + /* If we were installed under any other name in /EFI/BOOT, make sure we update those binaries + * as well. */ + if (!force) + RET_GATHER(ret, update_efi_boot_binaries(esp_path, source_path)); } return ret; @@ -514,7 +558,7 @@ static int install_entry_token(void) { 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"); + p = path_join(arg_root, getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/", "entry-token"); if (!p) return log_oom(); @@ -845,9 +889,6 @@ static int remove_boot_efi(const char *esp_path) { if (!endswith_no_case(de->d_name, ".efi")) continue; - if (!startswith_no_case(de->d_name, "boot")) - continue; - 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); @@ -978,7 +1019,7 @@ static int remove_loader_variables(void) { EFI_LOADER_VARIABLE(LoaderEntryDefault), EFI_LOADER_VARIABLE(LoaderEntryLastBooted), EFI_LOADER_VARIABLE(LoaderEntryOneShot), - EFI_LOADER_VARIABLE(LoaderSystemToken)){ + EFI_LOADER_VARIABLE(LoaderSystemToken)) { int q; diff --git a/src/boot/bootctl-reboot-to-firmware.c b/src/boot/bootctl-reboot-to-firmware.c index 91f2597..cdb04f8 100644 --- a/src/boot/bootctl-reboot-to-firmware.c +++ b/src/boot/bootctl-reboot-to-firmware.c @@ -2,6 +2,7 @@ #include "bootctl-reboot-to-firmware.h" #include "efi-api.h" +#include "errno-util.h" #include "parse-util.h" int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { @@ -17,7 +18,7 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { puts("supported"); return 1; /* recognizable error #1 */ } - if (r == -EOPNOTSUPP) { + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { puts("not supported"); return 2; /* recognizable error #2 */ } @@ -36,3 +37,39 @@ int verb_reboot_to_firmware(int argc, char *argv[], void *userdata) { return 0; } } + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "state", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + bool b; + int r; + + r = varlink_dispatch(link, parameters, dispatch_table, &b); + if (r != 0) + return r; + + r = efi_set_reboot_to_firmware(b); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = efi_get_reboot_to_firmware(); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return varlink_error(link, "io.systemd.BootControl.RebootToFirmwareNotSupported", NULL); + if (r < 0) + return r; + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("state", r))); +} diff --git a/src/boot/bootctl-reboot-to-firmware.h b/src/boot/bootctl-reboot-to-firmware.h index 0ca4b2c..fb8a248 100644 --- a/src/boot/bootctl-reboot-to-firmware.h +++ b/src/boot/bootctl-reboot-to-firmware.h @@ -1,3 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_reboot_to_firmware(int argc, char *argv[], void *userdata); + +int vl_method_set_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); +int vl_method_get_reboot_to_firmware(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl-set-efivar.c b/src/boot/bootctl-set-efivar.c index cb2ed0d..10867b2 100644 --- a/src/boot/bootctl-set-efivar.c +++ b/src/boot/bootctl-set-efivar.c @@ -38,7 +38,7 @@ static int parse_timeout(const char *arg1, char16_t **ret_timeout, size_t *ret_t (void) efi_loader_get_features(&loader_features); if (!(loader_features & EFI_LOADER_FEATURE_MENU_DISABLE)) { if (!arg_graceful) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled': %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Loader does not support 'menu-disabled'."); log_warning("Loader does not support 'menu-disabled', setting anyway."); } diff --git a/src/boot/bootctl-status.c b/src/boot/bootctl-status.c index d171512..224a1bf 100644 --- a/src/boot/bootctl-status.c +++ b/src/boot/bootctl-status.c @@ -92,6 +92,7 @@ static int status_entries( r = show_boot_entry( boot_config_default_entry(config), + &config->global_addons, /* show_as_default= */ false, /* show_as_selected= */ false, /* show_discovered= */ false); @@ -187,7 +188,6 @@ static int status_variables(void) { static int enumerate_binaries( const char *esp_path, const char *path, - const char *prefix, char **previous, bool *is_first) { @@ -213,9 +213,6 @@ static int enumerate_binaries( if (!endswith_no_case(de->d_name, ".efi")) continue; - if (prefix && !startswith_no_case(de->d_name, prefix)) - continue; - filename = path_join(p, de->d_name); if (!filename) return log_oom(); @@ -272,11 +269,11 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf(" (/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR ")", SD_ID128_FORMAT_VAL(partition)); printf("\n"); - r = enumerate_binaries(esp_path, "EFI/systemd", NULL, &last, &is_first); + r = enumerate_binaries(esp_path, "EFI/systemd", &last, &is_first); if (r < 0) goto fail; - k = enumerate_binaries(esp_path, "EFI/BOOT", "boot", &last, &is_first); + k = enumerate_binaries(esp_path, "EFI/BOOT", &last, &is_first); if (k < 0) { r = k; goto fail; @@ -321,7 +318,13 @@ int verb_status(int argc, char *argv[], void *userdata) { dev_t esp_devid = 0, xbootldr_devid = 0; int r, k; - r = acquire_esp(/* unprivileged_mode= */ -1, /* graceful= */ false, NULL, NULL, NULL, &esp_uuid, &esp_devid); + r = acquire_esp(/* unprivileged_mode= */ -1, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + &esp_uuid, + &esp_devid); if (arg_print_esp_path) { if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only * error the find_esp_and_warn() won't log on its own) */ @@ -333,7 +336,10 @@ int verb_status(int argc, char *argv[], void *userdata) { return 0; } - r = acquire_xbootldr(/* unprivileged_mode= */ -1, &xbootldr_uuid, &xbootldr_devid); + r = acquire_xbootldr( + /* unprivileged_mode= */ -1, + &xbootldr_uuid, + &xbootldr_devid); if (arg_print_dollar_boot_path) { if (r == -EACCES) return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); @@ -342,7 +348,7 @@ int verb_status(int argc, char *argv[], void *userdata) { const char *path = arg_dollar_boot_path(); if (!path) - return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Failed to determine XBOOTLDR location."); puts(path); return 0; @@ -377,14 +383,15 @@ int verb_status(int argc, char *argv[], void *userdata) { uint64_t flag; const char *name; } stub_flags[] = { - { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, - { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, - { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, - { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, - { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, - { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" }, - { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, - { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, + { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION, "Stub sets ESP information" }, + { EFI_STUB_FEATURE_PICK_UP_CREDENTIALS, "Picks up credentials from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_SYSEXTS, "Picks up system extension images from boot partition" }, + { EFI_STUB_FEATURE_PICK_UP_CONFEXTS, "Picks up configuration extension images from boot partition" }, + { EFI_STUB_FEATURE_THREE_PCRS, "Measures kernel+command line+sysexts" }, + { EFI_STUB_FEATURE_RANDOM_SEED, "Support for passing random seed to OS" }, + { EFI_STUB_FEATURE_CMDLINE_ADDONS, "Pick up .cmdline from addons" }, + { EFI_STUB_FEATURE_CMDLINE_SMBIOS, "Pick up .cmdline from SMBIOS Type 11" }, + { EFI_STUB_FEATURE_DEVICETREE_ADDONS, "Pick up .dtb from addons" }, }; _cleanup_free_ char *fw_type = NULL, *fw_info = NULL, *loader = NULL, *loader_path = NULL, *stub = NULL; sd_id128_t loader_part_uuid = SD_ID128_NULL; @@ -827,3 +834,58 @@ int verb_list(int argc, char *argv[], void *userdata) { int verb_unlink(int argc, char *argv[], void *userdata) { return verb_list(argc, argv, userdata); } + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + dev_t esp_devid = 0, xbootldr_devid = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = acquire_esp(/* unprivileged_mode= */ false, + /* graceful= */ false, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid=*/ NULL, + &esp_devid); + if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ + return log_error_errno(r, "Failed to determine ESP location: %m"); + if (r < 0) + return r; + + r = acquire_xbootldr( + /* unprivileged_mode= */ false, + /* ret_uuid= */ NULL, + &xbootldr_devid); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + r = boot_config_load_and_select(&config, arg_esp_path, esp_devid, arg_xbootldr_path, xbootldr_devid); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *previous = NULL; + for (size_t i = 0; i < config.n_entries; i++) { + if (previous) { + r = varlink_notifyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("entry", previous))); + if (r < 0) + return r; + + previous = json_variant_unref(previous); + } + + r = boot_entry_to_json(&config, i, &previous); + if (r < 0) + return r; + } + + return varlink_replyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(previous, "entry", JSON_BUILD_VARIANT(previous)))); +} diff --git a/src/boot/bootctl-status.h b/src/boot/bootctl-status.h index f7998a3..6fd4365 100644 --- a/src/boot/bootctl-status.h +++ b/src/boot/bootctl-status.h @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "varlink.h" + int verb_status(int argc, char *argv[], void *userdata); int verb_list(int argc, char *argv[], void *userdata); int verb_unlink(int argc, char *argv[], void *userdata); + +int vl_method_list_boot_entries(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/boot/bootctl-util.c b/src/boot/bootctl-util.c index 3cab875..448e868 100644 --- a/src/boot/bootctl-util.c +++ b/src/boot/bootctl-util.c @@ -119,7 +119,7 @@ int settle_entry_token(void) { r = boot_entry_token_ensure( arg_root, - etc_kernel(), + getenv("KERNEL_INSTALL_CONF_ROOT"), arg_machine_id, /* machine_id_is_random = */ false, &arg_entry_token_type, diff --git a/src/boot/bootctl-util.h b/src/boot/bootctl-util.h index 147455e..6f2c163 100644 --- a/src/boot/bootctl-util.h +++ b/src/boot/bootctl-util.h @@ -8,7 +8,3 @@ const char *get_efi_arch(void); int get_file_version(int fd, char **ret); int settle_entry_token(void); - -static inline const char* etc_kernel(void) { - return getenv("KERNEL_INSTALL_CONF_ROOT") ?: "/etc/kernel/"; -} diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 4614ca1..b883159 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -22,6 +22,8 @@ #include "parse-argument.h" #include "pretty-print.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.BootControl.h" #include "verbs.h" #include "virt.h" @@ -53,6 +55,7 @@ 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; +bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); @@ -324,7 +327,7 @@ static int parse_argv(int argc, char *argv[]) { break; case 'R': - arg_print_root_device ++; + arg_print_root_device++; break; case ARG_NO_VARIABLES: @@ -418,6 +421,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_dry_run && argv[optind] && !STR_IN_SET(argv[optind], "unlink", "cleanup")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--dry is only supported with --unlink or --cleanup"); + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -462,6 +473,34 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_BootControl); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.BootControl.ListBootEntries", vl_method_list_boot_entries, + "io.systemd.BootControl.SetRebootToFirmware", vl_method_set_reboot_to_firmware, + "io.systemd.BootControl.GetRebootToFirmware", vl_method_get_reboot_to_firmware); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + if (arg_print_root_device > 0) { _cleanup_free_ char *path = NULL; dev_t devno; @@ -498,7 +537,8 @@ static int run(int argc, char *argv[]) { arg_image, arg_image_policy, DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK, + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/boot/bootctl.h b/src/boot/bootctl.h index e395b33..25cb516 100644 --- a/src/boot/bootctl.h +++ b/src/boot/bootctl.h @@ -36,6 +36,7 @@ extern InstallSource arg_install_source; extern char *arg_efi_boot_option_description; extern bool arg_dry_run; extern ImagePolicy *arg_image_policy; +extern bool arg_varlink; static inline const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ diff --git a/src/boot/efi/UEFI_SECURITY.md b/src/boot/efi/UEFI_SECURITY.md index 9f750d8..55e66db 100644 --- a/src/boot/efi/UEFI_SECURITY.md +++ b/src/boot/efi/UEFI_SECURITY.md @@ -9,28 +9,28 @@ stub's role is that of the fundamental entrypoint to kernel execution from UEFI modern Linux boot protocol. `systemd-stub` on the other hand loads various resources, including the kernel image, via the EFI LoadImage/StartImage protocol (although it does support the legacy Linux boot protocol, as a fallback for older kernels on x86). The purpose of `systemd-stub` is to provide additional features and -functionality for either or both `systemd-boot` and `systemd` (userspace). +functionality for `systemd-boot` and `systemd` (in userspace). ## Fundamental Security Design Goals -The fundamental security design goals for these components are separation of security policy logic from the -rest of the functionality, achieved by offloading security-critical tasks to the firmware or earlier stages -of the boot process (e.g.: `Shim`). +The fundamental security design goal for these components is the separation of security policy logic from the +rest of the functionality. This is achieved by offloading security-critical tasks to the firmware or earlier stages +of the boot process (in particular `Shim`). -When SecureBoot is enabled, these components are designed to avoid executing, loading or using +When SecureBoot is enabled, these components are designed to avoid loading, executing, or using unauthenticated payloads that could compromise the boot process, with special care taken for anything that could affect the system before `ExitBootServices()` has been called. For example, when additional resources are loaded, if running with SecureBoot enabled, they will be validated before use. The only exceptions are -the bootloader's own textual configuration files, and parsing metadata out of images for displaying purposes +the bootloader's own textual configuration files, and metadata parsed out of kernel images for display purposes only. There are no build time or runtime configuration options that can be set to weaken the security model of these components when SecureBoot is enabled. The role of `systemd-boot` is to discover next stage components in the ESP (and XBOOTLDR if present), via -filesystem enumeration or explicit configuration files, and present a menu to the user, to choose the next -step. This auto discovery mechanism is described in details in the [BLS (Boot Loader +filesystem enumeration or explicit configuration files, and present a menu to the user to choose the next +step. This auto discovery mechanism is described in detail in the [BLS (Boot Loader Specification)](https://uapi-group.org/specifications/specs/boot_loader_specification/). The role of `systemd-stub` is to load and measure in the TPM the post-bootloader stages, such as the kernel, -initrd and kernel command line, and implement optional features such as augmenting the initrd with +initrd, and kernel command line, and implement optional features such as augmenting the initrd with additional content such as configuration or optional services. [Unified Kernel Images](https://uapi-group.org/specifications/specs/unified_kernel_image/) embed `systemd-stub`, a kernel and other optional components as sections in a PE signed binary, that can thus be executed in UEFI @@ -42,19 +42,19 @@ the image, given that the payload kernel was already authenticated and verified SecureBoot authentication is re-enabled immediately after the kernel image has been loaded. Various EFI variables, under the vendor UUID `4a67b082-0a4c-41cf-b6c7-440b29bb8c4f`, are set and read by -these components, to pass metadata and configuration between different stages of the boot process, as +these components. This is used to pass metadata and configuration between different stages of the boot process, as defined in the [Boot Loader Interface](https://systemd.io/BOOT_LOADER_INTERFACE/). ## Dependencies -Neither of these components implements cryptographic primitives, cryptographic checks or drivers. File +Neither of these components implements cryptographic primitives, cryptographic checks, or drivers. File access to the ESP is implemented solely via the appropriate UEFI file protocols. Verification of next stage -payloads is implementend solely via the appropriate UEFI image load protocols, which means authenticode +payloads is implementend solely via the appropriate UEFI image load protocols, which means `authenticode` signature checks are again done by the firmware or `Shim`. As a consequence, no external security-critical -libraries (such as OpenSSL or gnu-efi) are used, linked or embedded. +libraries (such as OpenSSL or gnu-efi) are linked, embedded, or used. ## Additional Resources BLS Type #1 entries allow the user to load two types of additional resources that can affect the system -before `ExitBootServices()` has been called, kernel command line arguments and Devicetree blobs, that are +before `ExitBootServices()` has been called — kernel command line arguments and Devicetree blobs — that are not validated before use, as they do not carry signatures. For this reason, when SecureBoot is enabled, loading these resources is automatically disabled. There is no override for this security mechanism, neither at build time nor at runtime. Note that initrds are also not verified in BLS Type #1 configurations, for @@ -62,8 +62,9 @@ compatibility with how SecureBoot has been traditionally handled on Linux-based only load them after `ExitBootServices()` has been called. Another mechanism is supported by `systemd-boot` and `systemd-stub` to add additional payloads to the boot -process: `addons`. Addons are PE signed binaries that can carry kernel command line arguments or Devicetree -blobs (more might be added in the future). In contrast to the user-specified additions in the Type #1 case +process: "addons". Addons are PE signed binaries that can carry kernel command line arguments or Devicetree +blobs (more payload types might be added in the future). +In contrast to the user-specified additions in the Type #1 case described above, these addons are loaded through the UEFI image loading protocol, and thus are subject to signature validation, and will be rejected if not signed or if the signature is invalid, following the standard SecureBoot model. They are also measured in the TPM. @@ -72,22 +73,22 @@ standard SecureBoot model. They are also measured in the TPM. firmware's capabilities. These are again PE signed binaries and will be verified using the appropriate UEFI protocol. -A random seed will be loaded and passed to the kernel for early-boot entropy pool filling if found in the -ESP. It is mixed with various other sources of entropy available in the UEFI environment, such as the RNG +A random seed will be loaded and passed to the kernel for early-boot entropy if found in the ESP. +It is mixed with various other sources of entropy available in the UEFI environment, such as the RNG protocol, the boot counter and the clock. Moreover, the seed is updated before the kernel is invoked, as well as after the kernel is invoked (from userspace), with a new seed derived from the Linux kernel entropy pool. When operating as a virtual machine payload, the loaded payloads can be customized via `SMBIOS Type 11 -Strings`, if the hypervisor specifies them. This is automatically disabled if running inside a confidential -computing VM. +Strings`. Those settings are specified by the hypervisor and trusted. +They are automatically disabled if running inside a confidential computing VM. ## Certificates Enrollment -When SecureBoot is supported but in `setup` mode, `systemd-boot` can enroll user certificates if a set of -`PK`, `KEK` and `db` certificates is found in the ESP, after which SecureBoot is enabled and a firmware -reset is performed. When running on bare metal, the certificate(s) will be shown to the user on the console, -and manual confirmation will be asked before proceeding. When running as a virtual machine payload, -enrollment is fully automated, without user interaction, unless disabled via a configuration file in the +When SecureBoot is supported, but in `setup` mode, `systemd-boot` can enroll user certificates if a set of +`PK`, `KEK` and `db` certificates is found in the ESP. Afterwards, SecureBoot is enabled and a firmware +reset is performed. When running on bare metal, the certificates will be shown to the user on the console, +and manual confirmation is required before proceeding. When running as a virtual machine payload, +enrollment is fully automated without user interaction, unless disabled via a configuration file in the ESP. The configuration file can also be used to disable enrollment completely. ## Compiler Hardening @@ -111,10 +112,10 @@ allow customizations of the metadata included in the section, that can be used b The `systemd` project will participate in the coordinated `SBAT` disclosure and metadata revision process as deemed necessary, in coordination with the Shim Review group. -The upstream project name used to be unified (`systemd`) for both components, but since version v255 has +The upstream project name used to be unified (`systemd`) for both components, but since version 255 has been split into separate `systemd-boot` and `systemd-stub` project names, so that each component can be -revisioned independently. Most of the code tend to be shared between these two components, but there is no -complete overlap, so it is possible for a vulnerability to affect only one component but not the other. +revisioned independently. Most of the code tends to be shared between these two components, but the +overlap is not complete, so a future vulnerability may affect only one of the components. ## Known Vulnerabilities There is currently one known (and fixed) security vulnerability affecting `systemd-boot` on arm64 and diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index a3d5607..79de121 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -741,20 +741,25 @@ static bool menu_run( lines = xnew(char16_t *, config->n_entries + 1); for (size_t i = 0; i < config->n_entries; i++) { - size_t j, padding; + size_t width = line_width - MIN(strlen16(config->entries[i]->title_show), line_width); + size_t padding = width / 2; + bool odd = width % 2; - lines[i] = xnew(char16_t, line_width + 1); - padding = (line_width - MIN(strlen16(config->entries[i]->title_show), line_width)) / 2; + /* Make sure there is space for => */ + padding = MAX((size_t) 2, padding); - for (j = 0; j < padding; j++) - lines[i][j] = ' '; + size_t print_width = MIN( + strlen16(config->entries[i]->title_show), + line_width - padding * 2); - for (size_t k = 0; config->entries[i]->title_show[k] != '\0' && j < line_width; j++, k++) - lines[i][j] = config->entries[i]->title_show[k]; + assert((padding + 1) <= INT_MAX); + assert(print_width <= INT_MAX); - for (; j < line_width; j++) - lines[i][j] = ' '; - lines[i][line_width] = '\0'; + lines[i] = xasprintf( + "%*ls%.*ls%*ls", + (int) padding, u"", + (int) print_width, config->entries[i]->title_show, + odd ? (int) (padding + 1) : (int) padding, u""); } lines[config->n_entries] = NULL; @@ -2288,7 +2293,7 @@ static EFI_STATUS initrd_prepare( continue; size_t new_size, read_size = info->FileSize; - if (__builtin_add_overflow(size, read_size, &new_size)) + if (!ADD_SAFE(&new_size, size, read_size)) return EFI_OUT_OF_RESOURCES; initrd = xrealloc(initrd, size, new_size); @@ -2369,7 +2374,16 @@ static EFI_STATUS image_start( /* If we had to append an initrd= entry to the command line, we have to pass it, and measure it. * Otherwise, only pass/measure it if it is not implicit anyway (i.e. embedded into the UKI or * so). */ - char16_t *options = options_initrd ?: entry->options_implied ? NULL : entry->options; + _cleanup_free_ char16_t *options = xstrdup16(options_initrd ?: entry->options_implied ? NULL : entry->options); + + if (entry->type == LOADER_LINUX && !is_confidential_vm()) { + const char *extra = smbios_find_oem_string("io.systemd.boot.kernel-cmdline-extra"); + if (extra) { + _cleanup_free_ char16_t *tmp = TAKE_PTR(options), *extra16 = xstr8_to_16(extra); + options = xasprintf("%ls %ls", tmp, extra16); + } + } + if (options) { loaded_image->LoadOptions = options; loaded_image->LoadOptionsSize = strsize16(options); @@ -2466,7 +2480,7 @@ static EFI_STATUS secure_boot_discover_keys(Config *config, EFI_FILE *root_dir) EFI_STATUS err; _cleanup_(file_closep) EFI_FILE *keys_basedir = NULL; - if (secure_boot_mode() != SECURE_BOOT_SETUP) + if (!IN_SET(secure_boot_mode(), SECURE_BOOT_SETUP, SECURE_BOOT_AUDIT)) return EFI_SUCCESS; /* the lack of a 'keys' directory is not fatal and is silently ignored */ diff --git a/src/boot/efi/cpio.c b/src/boot/efi/cpio.c index c4f803c..bd1118a 100644 --- a/src/boot/efi/cpio.c +++ b/src/boot/efi/cpio.c @@ -305,6 +305,7 @@ EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, + const char16_t *exclude_suffix, const char *target_dir_prefix, uint32_t dir_mode, uint32_t access_mode, @@ -367,6 +368,8 @@ EFI_STATUS pack_cpio( continue; if (match_suffix && !endswith_no_case(dirent->FileName, match_suffix)) continue; + if (exclude_suffix && endswith_no_case(dirent->FileName, exclude_suffix)) + continue; if (!is_ascii(dirent->FileName)) continue; if (strlen16(dirent->FileName) > 255) /* Max filename size on Linux */ diff --git a/src/boot/efi/cpio.h b/src/boot/efi/cpio.h index 26851e3..9d14fa1 100644 --- a/src/boot/efi/cpio.h +++ b/src/boot/efi/cpio.h @@ -8,6 +8,7 @@ EFI_STATUS pack_cpio( EFI_LOADED_IMAGE_PROTOCOL *loaded_image, const char16_t *dropin_dir, const char16_t *match_suffix, + const char16_t *exclude_suffix, const char *target_dir_prefix, uint32_t dir_mode, uint32_t access_mode, diff --git a/src/boot/efi/efi-string.c b/src/boot/efi/efi-string.c index 4144c0d..3bdb802 100644 --- a/src/boot/efi/efi-string.c +++ b/src/boot/efi/efi-string.c @@ -372,9 +372,9 @@ bool efi_fnmatch(const char16_t *pattern, const char16_t *haystack) { \ uint64_t u = 0; \ while (*s >= '0' && *s <= '9') { \ - if (__builtin_mul_overflow(u, 10, &u)) \ + if (!MUL_ASSIGN_SAFE(&u, 10)) \ return false; \ - if (__builtin_add_overflow(u, *s - '0', &u)) \ + if (!INC_SAFE(&u, *s - '0')) \ return false; \ s++; \ } \ @@ -593,13 +593,13 @@ typedef struct { static void grow_buf(FormatContext *ctx, size_t need) { assert(ctx); - assert_se(!__builtin_add_overflow(ctx->n, need, &need)); + assert_se(INC_SAFE(&need, ctx->n)); if (need < ctx->n_buf) return; /* Greedily allocate if we can. */ - if (__builtin_mul_overflow(need, 2, &ctx->n_buf)) + if (!MUL_SAFE(&ctx->n_buf, need, 2)) ctx->n_buf = need; /* We cannot use realloc here as ctx->buf may be ctx->stack_buf, which we cannot free. */ diff --git a/src/boot/efi/efi.h b/src/boot/efi/efi.h index fbc5d10..e8217c1 100644 --- a/src/boot/efi/efi.h +++ b/src/boot/efi/efi.h @@ -140,6 +140,8 @@ typedef struct { GUID_DEF(0x8be4df61, 0x93ca, 0x11d2, 0xaa, 0x0d, 0x00, 0xe0, 0x98, 0x03, 0x2b, 0x8c) #define EFI_IMAGE_SECURITY_DATABASE_GUID \ GUID_DEF(0xd719b2cb, 0x3d3a, 0x4596, 0xa3, 0xbc, 0xda, 0xd0, 0x0e, 0x67, 0x65, 0x6f) +#define EFI_CUSTOM_MODE_ENABLE_GUID \ + GUID_DEF(0xc076ec0c, 0x7028, 0x4399, 0xa0, 0x72, 0x71, 0xee, 0x5c, 0x44, 0x8b, 0x9f) #define EVT_TIMER 0x80000000U #define EVT_RUNTIME 0x40000000U diff --git a/src/boot/efi/measure.c b/src/boot/efi/measure.c index 01c97c8..08a2ecd 100644 --- a/src/boot/efi/measure.c +++ b/src/boot/efi/measure.c @@ -5,43 +5,11 @@ #include "macro-fundamental.h" #include "measure.h" #include "memory-util-fundamental.h" +#include "proto/cc-measurement.h" #include "proto/tcg.h" #include "tpm2-pcr.h" #include "util.h" -static EFI_STATUS tpm1_measure_to_pcr_and_event_log( - const EFI_TCG_PROTOCOL *tcg, - uint32_t pcrindex, - EFI_PHYSICAL_ADDRESS buffer, - size_t buffer_size, - const char16_t *description) { - - _cleanup_free_ TCG_PCR_EVENT *tcg_event = NULL; - EFI_PHYSICAL_ADDRESS event_log_last; - uint32_t event_number = 1; - size_t desc_len; - - assert(tcg); - assert(description); - - desc_len = strsize16(description); - tcg_event = xmalloc(offsetof(TCG_PCR_EVENT, Event) + desc_len); - *tcg_event = (TCG_PCR_EVENT) { - .EventSize = desc_len, - .PCRIndex = pcrindex, - .EventType = EV_IPL, - }; - memcpy(tcg_event->Event, description, desc_len); - - return tcg->HashLogExtendEvent( - (EFI_TCG_PROTOCOL *) tcg, - buffer, buffer_size, - TCG_ALG_SHA, - tcg_event, - &event_number, - &event_log_last); -} - static EFI_STATUS tpm2_measure_to_pcr_and_tagged_event_log( EFI_TCG2_PROTOCOL *tcg, uint32_t pcrindex, @@ -123,35 +91,67 @@ static EFI_STATUS tpm2_measure_to_pcr_and_event_log( tcg_event); } -static EFI_TCG_PROTOCOL *tcg1_interface_check(void) { - EFI_PHYSICAL_ADDRESS event_log_location, event_log_last_entry; - EFI_TCG_BOOT_SERVICE_CAPABILITY capability = { +static EFI_STATUS cc_measure_to_mr_and_event_log( + EFI_CC_MEASUREMENT_PROTOCOL *cc, + uint32_t pcrindex, + EFI_PHYSICAL_ADDRESS buffer, + uint64_t buffer_size, + const char16_t *description) { + + _cleanup_free_ EFI_CC_EVENT *event = NULL; + uint32_t mr; + EFI_STATUS err; + size_t desc_len; + + assert(cc); + assert(description); + + /* MapPcrToMrIndex service provides callers information on + * how the TPM PCR registers are mapped to the CC measurement + * registers (MR) in the vendor implementation. */ + err = cc->MapPcrToMrIndex(cc, pcrindex, &mr); + if (err != EFI_SUCCESS) + return EFI_NOT_FOUND; + + desc_len = strsize16(description); + event = xmalloc(offsetof(EFI_CC_EVENT, Event) + desc_len); + *event = (EFI_CC_EVENT) { + .Size = offsetof(EFI_CC_EVENT, Event) + desc_len, + .Header.HeaderSize = sizeof(EFI_CC_EVENT_HEADER), + .Header.HeaderVersion = EFI_CC_EVENT_HEADER_VERSION, + .Header.MrIndex = mr, + .Header.EventType = EV_IPL, + }; + + memcpy(event->Event, description, desc_len); + + return cc->HashLogExtendEvent( + cc, + 0, + buffer, + buffer_size, + event); +} + +static EFI_CC_MEASUREMENT_PROTOCOL *cc_interface_check(void) { + EFI_CC_BOOT_SERVICE_CAPABILITY capability = { .Size = sizeof(capability), }; EFI_STATUS err; - uint32_t features; - EFI_TCG_PROTOCOL *tcg; + EFI_CC_MEASUREMENT_PROTOCOL *cc; - err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_TCG_PROTOCOL), NULL, (void **) &tcg); + err = BS->LocateProtocol(MAKE_GUID_PTR(EFI_CC_MEASUREMENT_PROTOCOL), NULL, (void **) &cc); if (err != EFI_SUCCESS) return NULL; - err = tcg->StatusCheck( - tcg, - &capability, - &features, - &event_log_location, - &event_log_last_entry); + err = cc->GetCapability(cc, &capability); if (err != EFI_SUCCESS) return NULL; - if (capability.TPMDeactivatedFlag) + if (!(capability.SupportedEventLogs & EFI_CC_EVENT_LOG_FORMAT_TCG_2)) return NULL; - if (!capability.TPMPresentFlag) - return NULL; - - return tcg; + return cc; } static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { @@ -184,12 +184,42 @@ static EFI_TCG2_PROTOCOL *tcg2_interface_check(void) { } bool tpm_present(void) { - return tcg2_interface_check() || tcg1_interface_check(); + return tcg2_interface_check(); } -EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { +static EFI_STATUS tcg2_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_TCG2_PROTOCOL *tpm2; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + tpm2 = tcg2_interface_check(); + if (tpm2) + err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); + + *ret_measured = tpm2 && (err == EFI_SUCCESS); + + return err; +} + +static EFI_STATUS cc_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { + EFI_CC_MEASUREMENT_PROTOCOL *cc; + EFI_STATUS err = EFI_SUCCESS; + + assert(ret_measured); + + cc = cc_interface_check(); + if (cc) + err = cc_measure_to_mr_and_event_log(cc, pcrindex, buffer, buffer_size, description); + + *ret_measured = cc && (err == EFI_SUCCESS); + + return err; +} + +EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t buffer_size, const char16_t *description, bool *ret_measured) { EFI_STATUS err; + bool tpm_ret_measured, cc_ret_measured; assert(description || pcrindex == UINT32_MAX); @@ -203,27 +233,15 @@ EFI_STATUS tpm_log_event(uint32_t pcrindex, EFI_PHYSICAL_ADDRESS buffer, size_t return EFI_SUCCESS; } - tpm2 = tcg2_interface_check(); - if (tpm2) - err = tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description); - else { - EFI_TCG_PROTOCOL *tpm1; - - tpm1 = tcg1_interface_check(); - if (tpm1) - err = tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description); - else { - /* No active TPM found, so don't return an error */ - - if (ret_measured) - *ret_measured = false; + /* Measure into both CC and TPM if both are available to avoid a problem like CVE-2021-42299 */ + err = cc_log_event(pcrindex, buffer, buffer_size, description, &cc_ret_measured); + if (err != EFI_SUCCESS) + return err; - return EFI_SUCCESS; - } - } + err = tcg2_log_event(pcrindex, buffer, buffer_size, description, &tpm_ret_measured); if (err == EFI_SUCCESS && ret_measured) - *ret_measured = true; + *ret_measured = tpm_ret_measured || cc_ret_measured; return err; } diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 43727ef..7a60b0e 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -28,7 +28,7 @@ efi_fuzz_template = fuzz_template + efitest_base executables += [ efi_test_template + { 'sources' : files('test-bcd.c'), - 'dependencies' : libzstd, + 'dependencies' : libzstd_cflags, 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_ZSTD'], }, efi_test_template + { @@ -67,7 +67,8 @@ if meson.is_cross_build() and get_option('sbat-distro') == 'auto' warning('Auto detection of SBAT information not supported when cross-building, disabling SBAT.') elif get_option('sbat-distro') != '' efi_conf.set_quoted('SBAT_PROJECT', meson.project_name()) - efi_conf.set_quoted('PROJECT_VERSION', meson.project_version()) + efi_conf.set_quoted('PROJECT_VERSION', meson.project_version().split('~')[0]) + efi_conf.set_quoted('VERSION_TAG', version_tag) efi_conf.set('PROJECT_URL', conf.get('PROJECT_URL')) if get_option('sbat-distro-generation') < 1 error('SBAT Distro Generation must be a positive integer') @@ -209,11 +210,13 @@ if cc.get_id() == 'clang' endif efi_arch_c_args = { - 'aarch64' : ['-mgeneral-regs-only'], - 'arm' : ['-mgeneral-regs-only'], + 'aarch64' : ['-mgeneral-regs-only'], + 'arm' : ['-mgeneral-regs-only'], + # Until -mgeneral-regs-only is supported in LoongArch, use the following option instead: + 'loongarch64' : ['-mno-lsx', '-mno-lasx'], # Pass -m64/32 explicitly to make building on x32 work. - 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], - 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], + 'x86_64' : ['-m64', '-march=x86-64', '-mno-red-zone', '-mgeneral-regs-only'], + 'x86' : ['-m32', '-march=i686', '-mgeneral-regs-only', '-malign-double'], } efi_arch_c_ld_args = { # libgcc is not compiled with -fshort-wchar, but it does not use it anyways, @@ -385,7 +388,7 @@ foreach efi_elf_binary : efi_elf_binaries install_tag : 'systemd-boot', command : [ elf2efi_py, - '--version-major=' + meson.project_version(), + '--version-major=' + meson.project_version().split('~')[0], '--version-minor=0', '--efi-major=1', '--efi-minor=1', diff --git a/src/boot/efi/part-discovery.c b/src/boot/efi/part-discovery.c index f5b1573..e66e2da 100644 --- a/src/boot/efi/part-discovery.c +++ b/src/boot/efi/part-discovery.c @@ -232,7 +232,7 @@ static EFI_STATUS find_device(const EFI_GUID *type, EFI_HANDLE *device, EFI_DEVI } /* Patch in the data we found */ - *ret_device_path = device_path_replace_node(partition_path, part_node, (EFI_DEVICE_PATH *) &hd); + *ret_device_path = device_path_replace_node(partition_path, part_node, &hd.Header); return EFI_SUCCESS; } diff --git a/src/boot/efi/proto/cc-measurement.h b/src/boot/efi/proto/cc-measurement.h new file mode 100644 index 0000000..9335ecf --- /dev/null +++ b/src/boot/efi/proto/cc-measurement.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "efi.h" + +/* The UEFI specification defines the interface between the confidential virtual guest OS and + * virtual firmware as EFI_CC_MEASUREMENT_PROTOCOL. The measurements are captured in the CC eventlog + * that follows the TCG2 format. TPM PCR registers are mapped to vendor specific measurement registers + * and the mapping can be queried using MapPcrToMrIndex service as part of the protocol. + * + * The "Confidential Computing" section in the UEFI specification covers the details. */ + +#define EFI_CC_MEASUREMENT_PROTOCOL_GUID \ + GUID_DEF(0x96751a3d, 0x72f4, 0x41a6, 0xa7, 0x94, 0xed, 0x5d, 0x0e, 0x67, 0xae, 0x6b) + +#define EFI_CC_EVENT_HEADER_VERSION 1 +#define EFI_CC_EVENT_LOG_FORMAT_TCG_2 0x00000002 + +typedef struct { + uint8_t Type; + uint8_t SubType; +} EFI_CC_TYPE; + +typedef struct { + uint8_t Major; + uint8_t Minor; +} EFI_CC_VERSION; + +typedef struct { + uint8_t Size; + EFI_CC_VERSION StructureVersion; + EFI_CC_VERSION ProtocolVersion; + uint32_t HashAlgorithmBitmap; + uint32_t SupportedEventLogs; + EFI_CC_TYPE CcType; +} EFI_CC_BOOT_SERVICE_CAPABILITY; + +typedef struct { + uint32_t HeaderSize; + uint16_t HeaderVersion; + uint32_t MrIndex; + uint32_t EventType; +} _packed_ EFI_CC_EVENT_HEADER; + +typedef struct { + uint32_t Size; + EFI_CC_EVENT_HEADER Header; + uint8_t Event[]; +} _packed_ EFI_CC_EVENT; + +typedef struct EFI_CC_MEASUREMENT_PROTOCOL EFI_CC_MEASUREMENT_PROTOCOL; +struct EFI_CC_MEASUREMENT_PROTOCOL { + EFI_STATUS (EFIAPI *GetCapability)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + EFI_CC_BOOT_SERVICE_CAPABILITY *ProtocolCapability); + void *GetEventLog; + EFI_STATUS (EFIAPI *HashLogExtendEvent)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + uint64_t Flags, + EFI_PHYSICAL_ADDRESS DataToHash, + uint64_t DataToHashLen, + EFI_CC_EVENT *EfiCcEvent); + EFI_STATUS (EFIAPI *MapPcrToMrIndex)( + EFI_CC_MEASUREMENT_PROTOCOL *This, + uint32_t PcrIndex, + uint32_t *MrIndex); +}; diff --git a/src/boot/efi/proto/tcg.h b/src/boot/efi/proto/tcg.h index b4b8296..e243bf8 100644 --- a/src/boot/efi/proto/tcg.h +++ b/src/boot/efi/proto/tcg.h @@ -3,12 +3,9 @@ #include "efi.h" -#define EFI_TCG_PROTOCOL_GUID \ - GUID_DEF(0xf541796d, 0xa62e, 0x4954, 0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd) #define EFI_TCG2_PROTOCOL_GUID \ GUID_DEF(0x607f766c, 0x7455, 0x42be, 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f) -#define TCG_ALG_SHA 0x4 #define EFI_TCG2_EVENT_HEADER_VERSION 1 #define EV_IPL 13 #define EV_EVENT_TAG UINT32_C(6) @@ -48,16 +45,6 @@ typedef struct { uint32_t ActivePcrBanks; } EFI_TCG2_BOOT_SERVICE_CAPABILITY; -typedef struct { - uint32_t PCRIndex; - uint32_t EventType; - struct { - uint8_t Digest[20]; - } Digest; - uint32_t EventSize; - uint8_t Event[]; -} _packed_ TCG_PCR_EVENT; - typedef struct { uint32_t HeaderSize; uint16_t HeaderVersion; @@ -77,27 +64,6 @@ typedef struct { uint8_t Event[]; } _packed_ EFI_TCG2_TAGGED_EVENT; -typedef struct EFI_TCG_PROTOCOL EFI_TCG_PROTOCOL; -struct EFI_TCG_PROTOCOL { - EFI_STATUS (EFIAPI *StatusCheck)( - EFI_TCG_PROTOCOL *This, - EFI_TCG_BOOT_SERVICE_CAPABILITY *ProtocolCapability, - uint32_t *TCGFeatureFlags, - EFI_PHYSICAL_ADDRESS *EventLogLocation, - EFI_PHYSICAL_ADDRESS *EventLogLastEntry); - void *HashAll; - void *LogEvent; - void *PassThroughToTpm; - EFI_STATUS (EFIAPI *HashLogExtendEvent)( - EFI_TCG_PROTOCOL *This, - EFI_PHYSICAL_ADDRESS HashData, - uint64_t HashDataLen, - uint32_t AlgorithmId, - TCG_PCR_EVENT *TCGLogData, - uint32_t *EventNumber, - EFI_PHYSICAL_ADDRESS *EventLogLastEntry); -}; - typedef struct EFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL; struct EFI_TCG2_PROTOCOL { EFI_STATUS (EFIAPI *GetCapability)( diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c index 8147e54..03f3ebd 100644 --- a/src/boot/efi/random-seed.c +++ b/src/boot/efi/random-seed.c @@ -4,7 +4,7 @@ #include "proto/rng.h" #include "random-seed.h" #include "secure-boot.h" -#include "sha256.h" +#include "sha256-fundamental.h" #include "util.h" #define RANDOM_MAX_SIZE_MIN (32U) diff --git a/src/boot/efi/secure-boot.c b/src/boot/efi/secure-boot.c index f76d2f4..2400324 100644 --- a/src/boot/efi/secure-boot.c +++ b/src/boot/efi/secure-boot.c @@ -32,10 +32,46 @@ SecureBootMode secure_boot_mode(void) { return decode_secure_boot_mode(secure, audit, deployed, setup); } +/* + * Custom mode allows the secure boot certificate databases db, dbx, KEK, and PK to be changed without the variable + * updates being signed. When enrolling certificates to an unconfigured system (no PK present yet) writing + * db, dbx and KEK updates without signature works fine even in standard mode. Writing PK updates without + * signature requires custom mode in any case. + * + * Enabling custom mode works only if a user is physically present. Note that OVMF has a dummy + * implementation for the user presence check (there is no useful way to implement a presence check for a + * virtual machine). + * + * FYI: Your firmware setup utility might offers the option to enroll certificates from *.crt files + * (DER-encoded x509 certificates) on the ESP; that uses custom mode too. Your firmware setup might also + * offer the option to switch the system into custom mode for the next boot. + */ +static bool custom_mode_enabled(void) { + bool enabled = false; + + (void) efivar_get_boolean_u8(MAKE_GUID_PTR(EFI_CUSTOM_MODE_ENABLE), + u"CustomMode", &enabled); + return enabled; +} + +static EFI_STATUS set_custom_mode(bool enable) { + static char16_t name[] = u"CustomMode"; + static uint32_t attr = + EFI_VARIABLE_NON_VOLATILE | + EFI_VARIABLE_BOOTSERVICE_ACCESS; + uint8_t mode = enable + ? 1 /* CUSTOM_SECURE_BOOT_MODE */ + : 0; /* STANDARD_SECURE_BOOT_MODE */ + + return RT->SetVariable(name, MAKE_GUID_PTR(EFI_CUSTOM_MODE_ENABLE), + attr, sizeof(mode), &mode); +} + EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool force) { assert(root_dir); assert(path); + bool need_custom_mode = false; EFI_STATUS err; clear_screen(COLOR_NORMAL); @@ -88,21 +124,47 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool const char16_t *name; const char16_t *filename; const EFI_GUID vendor; + bool required; char *buffer; size_t size; } sb_vars[] = { - { u"db", u"db.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, NULL, 0 }, - { u"KEK", u"KEK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 }, - { u"PK", u"PK.auth", EFI_GLOBAL_VARIABLE, NULL, 0 }, + { u"db", u"db.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, true }, + { u"dbx", u"dbx.auth", EFI_IMAGE_SECURITY_DATABASE_GUID, false }, + { u"KEK", u"KEK.auth", EFI_GLOBAL_VARIABLE, true }, + { u"PK", u"PK.auth", EFI_GLOBAL_VARIABLE, true }, }; /* Make sure all keys files exist before we start enrolling them by loading them from the disk first. */ for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) { err = file_read(dir, sb_vars[i].filename, 0, 0, &sb_vars[i].buffer, &sb_vars[i].size); - if (err != EFI_SUCCESS) { + if (err != EFI_SUCCESS && sb_vars[i].required) { log_error_status(err, "Failed reading file %ls\\%ls: %m", path, sb_vars[i].filename); goto out_deallocate; } + if (streq16(sb_vars[i].name, u"PK") && sb_vars[i].size > 20) { + assert(sb_vars[i].buffer); + /* + * The buffer should be EFI_TIME (16 bytes), followed by + * EFI_VARIABLE_AUTHENTICATION_2 header. First header field is the size. If the + * size covers only the header itself (8 bytes) plus the signature type guid (16 + * bytes), leaving no space for an actual signature, we can conclude that no + * signature is present. + */ + uint32_t *sigsize = (uint32_t*)(sb_vars[i].buffer + 16); + if (*sigsize <= 24) { + printf("PK is not signed (need custom mode).\n"); + need_custom_mode = true; + } + } + } + + if (need_custom_mode && !custom_mode_enabled()) { + err = set_custom_mode(/* enable */ true); + if (err != EFI_SUCCESS) { + log_error_status(err, "Failed to enable custom mode: %m"); + goto out_deallocate; + } + printf("Custom mode enabled.\n"); } for (size_t i = 0; i < ELEMENTSOF(sb_vars); i++) { @@ -112,6 +174,9 @@ EFI_STATUS secure_boot_enroll_at(EFI_FILE *root_dir, const char16_t *path, bool EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; + if (!sb_vars[i].buffer) + continue; + err = efivar_set_raw(&sb_vars[i].vendor, sb_vars[i].name, sb_vars[i].buffer, sb_vars[i].size, sb_vars_opts); if (err != EFI_SUCCESS) { log_error_status(err, "Failed to write %ls secure boot variable: %m", sb_vars[i].name); diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 0d9df7e..9aa605b 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -26,28 +26,27 @@ DECLARE_NOALLOC_SECTION(".sdmagic", "#### LoaderInfo: systemd-stub " GIT_VERSION DECLARE_SBAT(SBAT_STUB_SECTION_TEXT); -static EFI_STATUS combine_initrd( - EFI_PHYSICAL_ADDRESS initrd_base, size_t initrd_size, - const void * const extra_initrds[], const size_t extra_initrd_sizes[], size_t n_extra_initrds, +/* Combine initrds by concatenation in memory */ +static EFI_STATUS combine_initrds( + const void * const initrds[], const size_t initrd_sizes[], size_t n_initrds, Pages *ret_initr_pages, size_t *ret_initrd_size) { - size_t n; + size_t n = 0; assert(ret_initr_pages); assert(ret_initrd_size); - /* Combines four initrds into one, by simple concatenation in memory */ - - n = ALIGN4(initrd_size); /* main initrd might not be padded yet */ - - for (size_t i = 0; i < n_extra_initrds; i++) { - if (!extra_initrds[i]) + for (size_t i = 0; i < n_initrds; i++) { + if (!initrds[i]) continue; - if (n > SIZE_MAX - extra_initrd_sizes[i]) + /* some initrds (the ones from UKI sections) need padding, + * pad all to be safe */ + size_t initrd_size = ALIGN4(initrd_sizes[i]); + if (n > SIZE_MAX - initrd_size) return EFI_OUT_OF_RESOURCES; - n += extra_initrd_sizes[i]; + n += initrd_size; } _cleanup_pages_ Pages pages = xmalloc_pages( @@ -56,27 +55,21 @@ static EFI_STATUS combine_initrd( EFI_SIZE_TO_PAGES(n), UINT32_MAX /* Below 4G boundary. */); uint8_t *p = PHYSICAL_ADDRESS_TO_POINTER(pages.addr); - if (initrd_base != 0) { + for (size_t i = 0; i < n_initrds; i++) { + if (!initrds[i]) + continue; + size_t pad; - /* Order matters, the real initrd must come first, since it might include microcode updates - * which the kernel only looks for in the first cpio archive */ - p = mempcpy(p, PHYSICAL_ADDRESS_TO_POINTER(initrd_base), initrd_size); + p = mempcpy(p, initrds[i], initrd_sizes[i]); - pad = ALIGN4(initrd_size) - initrd_size; + pad = ALIGN4(initrd_sizes[i]) - initrd_sizes[i]; if (pad > 0) { memzero(p, pad); p += pad; } } - for (size_t i = 0; i < n_extra_initrds; i++) { - if (!extra_initrds[i]) - continue; - - p = mempcpy(p, extra_initrds[i], extra_initrd_sizes[i]); - } - assert(PHYSICAL_ADDRESS_TO_POINTER(pages.addr + n) == p); *ret_initr_pages = pages; @@ -91,6 +84,7 @@ static void export_variables(EFI_LOADED_IMAGE_PROTOCOL *loaded_image) { EFI_STUB_FEATURE_REPORT_BOOT_PARTITION | /* We set LoaderDevicePartUUID */ EFI_STUB_FEATURE_PICK_UP_CREDENTIALS | /* We pick up credentials from the boot partition */ EFI_STUB_FEATURE_PICK_UP_SYSEXTS | /* We pick up system extensions from the boot partition */ + EFI_STUB_FEATURE_PICK_UP_CONFEXTS | /* We pick up configuration extensions from the boot partition */ EFI_STUB_FEATURE_THREE_PCRS | /* We can measure kernel image, parameters and sysext */ EFI_STUB_FEATURE_RANDOM_SEED | /* We pass a random seed to the kernel */ EFI_STUB_FEATURE_CMDLINE_ADDONS | /* We pick up .cmdline addons */ @@ -497,20 +491,20 @@ static EFI_STATUS load_addons( } static EFI_STATUS run(EFI_HANDLE image) { - _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; - size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; + _cleanup_free_ void *credential_initrd = NULL, *global_credential_initrd = NULL, *sysext_initrd = NULL, *confext_initrd = NULL, *pcrsig_initrd = NULL, *pcrpkey_initrd = NULL; + size_t credential_initrd_size = 0, global_credential_initrd_size = 0, sysext_initrd_size = 0, confext_initrd_size = 0, pcrsig_initrd_size = 0, pcrpkey_initrd_size = 0; void **dt_bases_addons_global = NULL, **dt_bases_addons_uki = NULL; char16_t **dt_filenames_addons_global = NULL, **dt_filenames_addons_uki = NULL; _cleanup_free_ size_t *dt_sizes_addons_global = NULL, *dt_sizes_addons_uki = NULL; - size_t linux_size, initrd_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; - EFI_PHYSICAL_ADDRESS linux_base, initrd_base, dt_base; + size_t linux_size, initrd_size, ucode_size, dt_size, n_dts_addons_global = 0, n_dts_addons_uki = 0; + EFI_PHYSICAL_ADDRESS linux_base, initrd_base, ucode_base, dt_base; _cleanup_(devicetree_cleanup) struct devicetree_state dt_state = {}; EFI_LOADED_IMAGE_PROTOCOL *loaded_image; size_t addrs[_UNIFIED_SECTION_MAX] = {}, szs[_UNIFIED_SECTION_MAX] = {}; _cleanup_free_ char16_t *cmdline = NULL, *cmdline_addons_global = NULL, *cmdline_addons_uki = NULL; int sections_measured = -1, parameters_measured = -1; _cleanup_free_ char *uname = NULL; - bool sysext_measured = false, m; + bool sysext_measured = false, confext_measured = false, m; uint64_t loader_features = 0; EFI_STATUS err; @@ -660,8 +654,9 @@ static EFI_STATUS run(EFI_HANDLE image) { export_variables(loaded_image); if (pack_cpio(loaded_image, - NULL, + /* dropin_dir= */ NULL, u".cred", + /* exclude_suffix= */ NULL, ".extra/credentials", /* dir_mode= */ 0500, /* access_mode= */ 0400, @@ -675,6 +670,7 @@ static EFI_STATUS run(EFI_HANDLE image) { if (pack_cpio(loaded_image, u"\\loader\\credentials", u".cred", + /* exclude_suffix= */ NULL, ".extra/global_credentials", /* dir_mode= */ 0500, /* access_mode= */ 0400, @@ -686,8 +682,9 @@ static EFI_STATUS run(EFI_HANDLE image) { parameters_measured = parameters_measured < 0 ? m : (parameters_measured && m); if (pack_cpio(loaded_image, - NULL, - u".raw", + /* dropin_dir= */ NULL, + u".raw", /* ideally we'd pick up only *.sysext.raw here, but for compat we pick up *.raw instead … */ + u".confext.raw", /* … but then exclude *.confext.raw again */ ".extra/sysext", /* dir_mode= */ 0555, /* access_mode= */ 0444, @@ -698,6 +695,20 @@ static EFI_STATUS run(EFI_HANDLE image) { &m) == EFI_SUCCESS) sysext_measured = m; + if (pack_cpio(loaded_image, + /* dropin_dir= */ NULL, + u".confext.raw", + /* exclude_suffix= */ NULL, + ".extra/confext", + /* dir_mode= */ 0555, + /* access_mode= */ 0444, + /* tpm_pcr= */ TPM2_PCR_KERNEL_CONFIG, + u"Configuration extension initrd", + &confext_initrd, + &confext_initrd_size, + &m) == EFI_SUCCESS) + confext_measured = m; + dt_size = szs[UNIFIED_SECTION_DTB]; dt_base = dt_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_DTB] : 0; @@ -728,6 +739,8 @@ static EFI_STATUS run(EFI_HANDLE image) { (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrKernelParameters", TPM2_PCR_KERNEL_CONFIG, 0); if (sysext_measured) (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDSysExts", TPM2_PCR_SYSEXTS, 0); + if (confext_measured) + (void) efivar_set_uint_string(MAKE_GUID_PTR(LOADER), u"StubPcrInitRDConfExts", TPM2_PCR_KERNEL_CONFIG, 0); /* If the PCR signature was embedded in the PE image, then let's wrap it in a cpio and also pass it * to the kernel, so that it can be read from /.extra/tpm2-pcr-signature.json. Note that this section @@ -772,26 +785,36 @@ static EFI_STATUS run(EFI_HANDLE image) { initrd_size = szs[UNIFIED_SECTION_INITRD]; initrd_base = initrd_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_INITRD] : 0; + ucode_size = szs[UNIFIED_SECTION_UCODE]; + ucode_base = ucode_size != 0 ? POINTER_TO_PHYSICAL_ADDRESS(loaded_image->ImageBase) + addrs[UNIFIED_SECTION_UCODE] : 0; + _cleanup_pages_ Pages initrd_pages = {}; - if (credential_initrd || global_credential_initrd || sysext_initrd || pcrsig_initrd || pcrpkey_initrd) { - /* If we have generated initrds dynamically, let's combine them with the built-in initrd. */ - err = combine_initrd( - initrd_base, initrd_size, + if (ucode_base || credential_initrd || global_credential_initrd || sysext_initrd || confext_initrd || pcrsig_initrd || pcrpkey_initrd) { + /* If we have generated initrds dynamically or there is a microcode initrd, combine them with the built-in initrd. */ + err = combine_initrds( (const void*const[]) { + /* Microcode must always be first as kernel only scans uncompressed cpios + * and later initrds might be compressed. */ + PHYSICAL_ADDRESS_TO_POINTER(ucode_base), + PHYSICAL_ADDRESS_TO_POINTER(initrd_base), credential_initrd, global_credential_initrd, sysext_initrd, + confext_initrd, pcrsig_initrd, pcrpkey_initrd, }, (const size_t[]) { + ucode_size, + initrd_size, credential_initrd_size, global_credential_initrd_size, sysext_initrd_size, + confext_initrd_size, pcrsig_initrd_size, pcrpkey_initrd_size, }, - 5, + 8, &initrd_pages, &initrd_size); if (err != EFI_SUCCESS) return err; @@ -802,6 +825,7 @@ static EFI_STATUS run(EFI_HANDLE image) { credential_initrd = mfree(credential_initrd); global_credential_initrd = mfree(global_credential_initrd); sysext_initrd = mfree(sysext_initrd); + confext_initrd = mfree(confext_initrd); pcrsig_initrd = mfree(pcrsig_initrd); pcrpkey_initrd = mfree(pcrpkey_initrd); } diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index e56ccfd..b5c8c63 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -303,7 +303,7 @@ EFI_STATUS chunked_read(EFI_FILE *file, size_t *size, void *buf) { * Some broken firmwares cannot handle large file reads and will instead return * an error. As a workaround, read such files in small chunks. * Note that we cannot just try reading the whole file first on such firmware as - * that will permanently break the handle even if it is re-opened. + * that will permanently break the handle even if it is reopened. * * https://github.com/systemd/systemd/issues/25911 */ diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 0306e32..ceac07c 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -33,7 +33,7 @@ void *xmalloc(size_t size); _malloc_ _alloc_(1, 2) _returns_nonnull_ _warn_unused_result_ static inline void *xmalloc_multiply(size_t n, size_t size) { - assert_se(!__builtin_mul_overflow(size, n, &size)); + assert_se(MUL_ASSIGN_SAFE(&size, n)); return xmalloc(size); } @@ -84,7 +84,7 @@ static inline Pages xmalloc_pages( EFI_STATUS efivar_set(const EFI_GUID *vendor, const char16_t *name, const char16_t *value, uint32_t flags); EFI_STATUS efivar_set_raw(const EFI_GUID *vendor, const char16_t *name, const void *buf, size_t size, uint32_t flags); EFI_STATUS efivar_set_uint_string(const EFI_GUID *vendor, const char16_t *name, size_t i, uint32_t flags); -EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *NAME, uint32_t value, uint32_t flags); +EFI_STATUS efivar_set_uint32_le(const EFI_GUID *vendor, const char16_t *name, uint32_t value, uint32_t flags); EFI_STATUS efivar_set_uint64_le(const EFI_GUID *vendor, const char16_t *name, uint64_t value, uint32_t flags); void efivar_set_time_usec(const EFI_GUID *vendor, const char16_t *name, uint64_t usec); diff --git a/src/boot/efi/vmm.h b/src/boot/efi/vmm.h index df48af3..1d1037b 100644 --- a/src/boot/efi/vmm.h +++ b/src/boot/efi/vmm.h @@ -4,7 +4,7 @@ #include "efi.h" bool is_direct_boot(EFI_HANDLE device); -EFI_STATUS vmm_open(EFI_HANDLE *ret_qemu_dev, EFI_FILE **ret_qemu_dir); +EFI_STATUS vmm_open(EFI_HANDLE *ret_vmm_dev, EFI_FILE **ret_vmm_dir); bool in_hypervisor(void); diff --git a/src/boot/measure.c b/src/boot/measure.c index 5c5071e..a6d27a7 100644 --- a/src/boot/measure.c +++ b/src/boot/measure.c @@ -30,7 +30,10 @@ static char *arg_sections[_UNIFIED_SECTION_MAX] = {}; static char **arg_banks = NULL; static char *arg_tpm2_device = NULL; static char *arg_private_key = NULL; +static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; +static char *arg_private_key_source = NULL; static char *arg_public_key = NULL; +static char *arg_certificate = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO|JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_current = false; @@ -40,7 +43,9 @@ static char *arg_append = NULL; STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_public_key, freep); +STATIC_DESTRUCTOR_REGISTER(arg_certificate, freep); STATIC_DESTRUCTOR_REGISTER(arg_phase, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_append, freep); @@ -74,7 +79,11 @@ static int help(int argc, char *argv[], void *userdata) { " --bank=DIGEST Select TPM bank (SHA1, SHA256, SHA384, SHA512)\n" " --tpm2-device=PATH Use specified TPM2 device\n" " --private-key=KEY Private key (PEM) to sign with\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use KEY for --private-key=. Allows\n" + " an OpenSSL engine/provider to be used for signing\n" " --public-key=KEY Public key (PEM) to validate against\n" + " --certificate=PATH PEM certificate to use when signing with a URI\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" " --append=PATH Load specified JSON signature, and append new signature to it\n" @@ -83,6 +92,7 @@ static int help(int argc, char *argv[], void *userdata) { " --osrel=PATH Path to os-release file %7$s .osrel\n" " --cmdline=PATH Path to file with kernel command line %7$s .cmdline\n" " --initrd=PATH Path to initrd image file %7$s .initrd\n" + " --ucode=PATH Path to microcode image file %7$s .ucode\n" " --splash=PATH Path to splash bitmap file %7$s .splash\n" " --dtb=PATH Path to Devicetree file %7$s .dtb\n" " --uname=PATH Path to 'uname -r' file %7$s .uname\n" @@ -124,6 +134,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_OSREL, ARG_CMDLINE, ARG_INITRD, + ARG_UCODE, ARG_SPLASH, ARG_DTB, ARG_UNAME, @@ -133,7 +144,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_PCRPKEY = _ARG_SECTION_LAST, ARG_BANK, ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, ARG_PUBLIC_KEY, + ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_JSON, ARG_PHASE, @@ -141,26 +154,29 @@ static int parse_argv(int argc, char *argv[]) { }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "version", no_argument, NULL, ARG_VERSION }, - { "linux", required_argument, NULL, ARG_LINUX }, - { "osrel", required_argument, NULL, ARG_OSREL }, - { "cmdline", required_argument, NULL, ARG_CMDLINE }, - { "initrd", required_argument, NULL, ARG_INITRD }, - { "splash", required_argument, NULL, ARG_SPLASH }, - { "dtb", required_argument, NULL, ARG_DTB }, - { "uname", required_argument, NULL, ARG_UNAME }, - { "sbat", required_argument, NULL, ARG_SBAT }, - { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, - { "current", no_argument, NULL, 'c' }, - { "bank", required_argument, NULL, ARG_BANK }, - { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, - { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, - { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, - { "json", required_argument, NULL, ARG_JSON }, - { "phase", required_argument, NULL, ARG_PHASE }, - { "append", required_argument, NULL, ARG_APPEND }, + { "help", no_argument, NULL, 'h' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "version", no_argument, NULL, ARG_VERSION }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "osrel", required_argument, NULL, ARG_OSREL }, + { "cmdline", required_argument, NULL, ARG_CMDLINE }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "ucode", required_argument, NULL, ARG_UCODE }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "uname", required_argument, NULL, ARG_UNAME }, + { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, + { "public-key", required_argument, NULL, ARG_PUBLIC_KEY }, + { "certificate", required_argument, NULL, ARG_CERTIFICATE }, + { "json", required_argument, NULL, ARG_JSON }, + { "phase", required_argument, NULL, ARG_PHASE }, + { "append", required_argument, NULL, ARG_APPEND }, {} }; @@ -213,7 +229,17 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: - r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_private_key); + r = free_and_strdup_warn(&arg_private_key, optarg); + if (r < 0) + return r; + + break; + + case ARG_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + optarg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; @@ -226,6 +252,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CERTIFICATE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_certificate); + if (r < 0) + return r; + + break; + case ARG_TPM2_DEVICE: { _cleanup_free_ char *device = NULL; @@ -281,6 +314,12 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_public_key && arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Both --public-key= and --certificate= specified, refusing."); + + if (arg_private_key_source && !arg_certificate) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "When using --private-key-source=, --certificate= must be specified."); + if (strv_isempty(arg_banks)) { /* If no banks are specifically selected, pick all known banks */ arg_banks = strv_new("SHA1", "SHA256", "SHA384", "SHA512"); @@ -300,12 +339,11 @@ static int parse_argv(int argc, char *argv[]) { if (strv_isempty(arg_phase)) { /* If no phases are specifically selected, pick everything from the beginning of the initrd * to the beginning of shutdown. */ - if (strv_extend_strv(&arg_phase, - STRV_MAKE("enter-initrd", - "enter-initrd:leave-initrd", - "enter-initrd:leave-initrd:sysinit", - "enter-initrd:leave-initrd:sysinit:ready"), - /* filter_duplicates= */ false) < 0) + if (strv_extend_many(&arg_phase, + "enter-initrd", + "enter-initrd:leave-initrd", + "enter-initrd:leave-initrd:sysinit", + "enter-initrd:leave-initrd:sysinit:ready") < 0) return log_oom(); } else { strv_sort(arg_phase); @@ -419,7 +457,7 @@ static int measure_kernel(PcrState *pcr_states, size_t n) { if (r < 0) return log_error_errno(r, "Failed to read '%s': %m", p); - r = unhexmem(strstrip(s), SIZE_MAX, &v, &sz); + r = unhexmem(strstrip(s), &v, &sz); if (r < 0) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); @@ -732,7 +770,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; - _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_(X509_freep) X509 *certificate = NULL; size_t n; int r; @@ -760,13 +798,57 @@ static int verb_sign(int argc, char *argv[], void *userdata) { /* When signing we only support JSON output */ arg_json_format_flags &= ~JSON_FORMAT_OFF; - privkeyf = fopen(arg_private_key, "re"); - if (!privkeyf) - return log_error_errno(errno, "Failed to open private key file '%s': %m", arg_private_key); + /* This must be done before openssl_load_key_from_token() otherwise it will get stuck */ + if (arg_certificate) { + _cleanup_(BIO_freep) BIO *cb = NULL; + _cleanup_free_ char *crt = NULL; - privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); - if (!privkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", arg_private_key); + r = read_full_file_full( + AT_FDCWD, arg_certificate, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + /* bind_name= */ NULL, + &crt, &n); + if (r < 0) + return log_error_errno(r, "Failed to read certificate file '%s': %m", arg_certificate); + + cb = BIO_new_mem_buf(crt, n); + if (!cb) + return log_oom(); + + certificate = PEM_read_bio_X509(cb, NULL, NULL, NULL); + if (!certificate) + return log_error_errno( + SYNTHETIC_ERRNO(EBADMSG), + "Failed to parse X.509 certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + if (arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + _cleanup_fclose_ FILE *privkeyf = NULL; + _cleanup_free_ char *resolved_pkey = NULL; + + r = parse_path_argument(arg_private_key, /* suppress_root= */ false, &resolved_pkey); + if (r < 0) + return log_error_errno(r, "Failed to parse private key path %s: %m", arg_private_key); + + privkeyf = fopen(resolved_pkey, "re"); + if (!privkeyf) + return log_error_errno(errno, "Failed to open private key file '%s': %m", resolved_pkey); + + privkey = PEM_read_PrivateKey(privkeyf, NULL, NULL, NULL); + if (!privkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse private key '%s'.", resolved_pkey); + } else if (arg_private_key_source && + IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { + r = openssl_load_key_from_token( + arg_private_key_source_type, arg_private_key_source, arg_private_key, &privkey); + if (r < 0) + return log_error_errno( + r, + "Failed to load key '%s' from OpenSSL key source %s: %m", + arg_private_key, + arg_private_key_source); + } if (arg_public_key) { _cleanup_fclose_ FILE *pubkeyf = NULL; @@ -778,6 +860,13 @@ static int verb_sign(int argc, char *argv[], void *userdata) { pubkey = PEM_read_PUBKEY(pubkeyf, NULL, NULL, NULL); if (!pubkey) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key '%s'.", arg_public_key); + } else if (certificate) { + pubkey = X509_get_pubkey(certificate); + if (!pubkey) + return log_error_errno( + SYNTHETIC_ERRNO(EIO), + "Failed to extract public key from certificate %s.", + arg_certificate); } else { _cleanup_(memstream_done) MemStream m = {}; FILE *tf; @@ -995,7 +1084,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to read '%s': %m", p); - r = unhexmem(strstrip(s), SIZE_MAX, &h, &l); + r = unhexmem(strstrip(s), &h, &l); if (r < 0) return log_error_errno(r, "Failed to decode PCR value '%s': %m", s); @@ -1071,9 +1160,7 @@ static int measure_main(int argc, char *argv[]) { static int run(int argc, char *argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/busctl/busctl.c b/src/busctl/busctl.c index 01cb896..8db9076 100644 --- a/src/busctl/busctl.c +++ b/src/busctl/busctl.c @@ -13,6 +13,7 @@ #include "bus-type.h" #include "bus-util.h" #include "busctl-introspect.h" +#include "capsule-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" @@ -72,6 +73,7 @@ static int json_transform_message(sd_bus_message *m, JsonVariant **ret); static int acquire_bus(bool set_monitor, sd_bus **ret) { _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int pin_fd = -EBADF; int r; r = sd_bus_new(&bus); @@ -138,10 +140,13 @@ static int acquire_bus(bool set_monitor, sd_bus **ret) { r = bus_set_address_machine(bus, arg_runtime_scope, arg_host); break; + case BUS_TRANSPORT_CAPSULE: + r = bus_set_address_capsule_bus(bus, arg_host, &pin_fd); + break; + default: assert_not_reached(); } - if (r < 0) return bus_log_address_error(r, arg_transport); @@ -1366,10 +1371,10 @@ static int verb_monitor(int argc, char **argv, void *userdata) { static int verb_capture(int argc, char **argv, void *userdata) { _cleanup_free_ char *osname = NULL; static const char info[] = - "busctl (systemd) " STRINGIFY(PROJECT_VERSION) " (Git " GIT_VERSION ")"; + "busctl (systemd) " PROJECT_VERSION_FULL " (Git " GIT_VERSION ")"; int r; - if (isatty(fileno(stdout)) > 0) + if (isatty(STDOUT_FILENO)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Refusing to write message data to console, please redirect output to a file."); @@ -2385,6 +2390,7 @@ static int parse_argv(int argc, char *argv[]) { { "match", required_argument, NULL, ARG_MATCH }, { "host", required_argument, NULL, 'H' }, { "machine", required_argument, NULL, 'M' }, + { "capsule", required_argument, NULL, 'C' }, { "size", required_argument, NULL, ARG_SIZE }, { "list", no_argument, NULL, ARG_LIST }, { "quiet", no_argument, NULL, 'q' }, @@ -2406,7 +2412,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:qjl", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hH:M:C:J:qjl", options, NULL)) >= 0) switch (c) { @@ -2490,6 +2496,17 @@ static int parse_argv(int argc, char *argv[]) { arg_host = optarg; break; + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; + break; + case 'q': arg_quiet = true; break; diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c index ca51455..08eae59 100644 --- a/src/cgtop/cgtop.c +++ b/src/cgtop/cgtop.c @@ -207,7 +207,7 @@ static int process( return r; g->n_tasks = 0; - while (cg_read_pid(f, &pid) > 0) { + while (cg_read_pid(f, &pid, CGROUP_DONT_SKIP_UNMAPPED) > 0) { if (arg_count == COUNT_USERSPACE_PROCESSES && pid_is_kernel_thread(pid) > 0) continue; diff --git a/src/core/automount.c b/src/core/automount.c index 14bf7e6..6cb9d52 100644 --- a/src/core/automount.c +++ b/src/core/automount.c @@ -38,10 +38,10 @@ #include "unit.h" static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = UNIT_INACTIVE, + [AUTOMOUNT_DEAD] = UNIT_INACTIVE, [AUTOMOUNT_WAITING] = UNIT_ACTIVE, [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, - [AUTOMOUNT_FAILED] = UNIT_FAILED + [AUTOMOUNT_FAILED] = UNIT_FAILED, }; static int open_dev_autofs(Manager *m); @@ -51,10 +51,8 @@ static void automount_stop_expire(Automount *a); static int automount_send_ready(Automount *a, Set *tokens, int status); static void automount_init(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); - assert(a); - assert(u); assert(u->load_state == UNIT_STUB); a->pipe_fd = -EBADF; @@ -88,9 +86,7 @@ static void unmount_autofs(Automount *a) { } static void automount_done(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); unmount_autofs(a); @@ -126,7 +122,7 @@ static int automount_add_mount_dependencies(Automount *a) { if (r < 0) return r; - return unit_require_mounts_for(UNIT(a), parent, UNIT_DEPENDENCY_IMPLICIT); + return unit_add_mounts_for(UNIT(a), parent, UNIT_DEPENDENCY_IMPLICIT, UNIT_MOUNT_REQUIRES); } static int automount_add_default_dependencies(Automount *a) { @@ -227,10 +223,9 @@ static int automount_add_extras(Automount *a) { } static int automount_load(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(u); assert(u->load_state == UNIT_STUB); /* Load a .automount file */ @@ -250,6 +245,7 @@ static int automount_load(Unit *u) { static void automount_set_state(Automount *a, AutomountState state) { AutomountState old_state; + assert(a); if (a->state != state) @@ -271,10 +267,9 @@ static void automount_set_state(Automount *a, AutomountState state) { } static int automount_coldplug(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(a); assert(a->state == AUTOMOUNT_DEAD); if (a->deserialized_state == a->state) @@ -310,9 +305,7 @@ static int automount_coldplug(Unit *u) { } static void automount_dump(Unit *u, FILE *f, const char *prefix) { - Automount *a = AUTOMOUNT(u); - - assert(a); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); fprintf(f, "%sAutomount State: %s\n" @@ -478,30 +471,22 @@ static int automount_send_ready(Automount *a, Set *tokens, int status) { r = 0; /* Autofs thankfully does not hand out 0 as a token */ - while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { - int k; - + while ((token = PTR_TO_UINT(set_steal_first(tokens)))) /* Autofs fun fact: * - * if you pass a positive status code here, kernels - * prior to 4.12 will freeze! Yay! */ - - k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, - ioctl_fd, - token, - status); - if (k < 0) - r = k; - } + * if you pass a positive status code here, kernels prior to 4.12 will freeze! Yay! */ + RET_GATHER(r, autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, + ioctl_fd, + token, + status)); return r; } static void automount_trigger_notify(Unit *u, Unit *other) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(a); assert(other); /* Filter out invocations with bogus state */ @@ -697,11 +682,10 @@ static int asynchronous_expire(int dev_autofs_fd, int ioctl_fd) { } static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); _cleanup_close_ int ioctl_fd = -EBADF; - Automount *a = AUTOMOUNT(userdata); int r; - assert(a); assert(source == a->expire_event_source); ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); @@ -815,13 +799,12 @@ fail: } static int automount_start(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(a); assert(IN_SET(a->state, AUTOMOUNT_DEAD, AUTOMOUNT_FAILED)); - if (path_is_mount_point(a->where, NULL, 0) > 0) + if (path_is_mount_point(a->where) > 0) return log_unit_error_errno(u, SYNTHETIC_ERRNO(EEXIST), "Path %s is already a mount point, refusing start.", a->where); r = unit_test_trigger_loaded(u); @@ -838,9 +821,8 @@ static int automount_start(Unit *u) { } static int automount_stop(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); - assert(a); assert(IN_SET(a->state, AUTOMOUNT_WAITING, AUTOMOUNT_RUNNING)); automount_enter_dead(a, AUTOMOUNT_SUCCESS); @@ -848,11 +830,10 @@ static int automount_stop(Unit *u) { } static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); void *p; int r; - assert(a); assert(f); assert(fds); @@ -873,10 +854,9 @@ static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { } static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(a); assert(fds); if (streq(key, "state")) { @@ -958,13 +938,12 @@ static bool automount_may_gc(Unit *u) { } static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { + Automount *a = ASSERT_PTR(AUTOMOUNT(userdata)); _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; union autofs_v5_packet_union packet; - Automount *a = AUTOMOUNT(userdata); Unit *trigger; int r; - assert(a); assert(fd == a->pipe_fd); if (events & (EPOLLHUP|EPOLLERR)) { @@ -1048,9 +1027,7 @@ static void automount_shutdown(Manager *m) { } static void automount_reset_failed(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); if (a->state == AUTOMOUNT_FAILED) automount_set_state(a, AUTOMOUNT_DEAD); @@ -1068,11 +1045,9 @@ static bool automount_supported(void) { } static int automount_can_start(Unit *u) { - Automount *a = AUTOMOUNT(u); + Automount *a = ASSERT_PTR(AUTOMOUNT(u)); int r; - assert(a); - r = unit_test_start_limit(u); if (r < 0) { automount_enter_dead(a, AUTOMOUNT_FAILURE_START_LIMIT_HIT); diff --git a/src/core/bpf-devices.c b/src/core/bpf-devices.c index 06d2146..8484dbc 100644 --- a/src/core/bpf-devices.c +++ b/src/core/bpf-devices.c @@ -24,15 +24,15 @@ assert_cc((unsigned) BPF_DEVCG_ACC_WRITE == (unsigned) CGROUP_DEVICE_WRITE); static int bpf_prog_allow_list_device( BPFProgram *prog, char type, - int major, - int minor, + unsigned major, + unsigned minor, CGroupDevicePermissions p) { int r; assert(prog); - log_trace("%s: %c %d:%d %s", __func__, type, major, minor, cgroup_device_permissions_to_string(p)); + log_trace("%s: %c %u:%u %s", __func__, type, major, minor, cgroup_device_permissions_to_string(p)); if (p <= 0 || p >= _CGROUP_DEVICE_PERMISSIONS_MAX) return -EINVAL; @@ -56,22 +56,22 @@ static int bpf_prog_allow_list_device( else r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); if (r < 0) - log_error_errno(r, "Extending device control BPF program failed: %m"); + return log_error_errno(r, "Extending device control BPF program failed: %m"); - return r; + return 1; /* return 1 → we did something */ } static int bpf_prog_allow_list_major( BPFProgram *prog, char type, - int major, + unsigned major, CGroupDevicePermissions p) { int r; assert(prog); - log_trace("%s: %c %d:* %s", __func__, type, major, cgroup_device_permissions_to_string(p)); + log_trace("%s: %c %u:* %s", __func__, type, major, cgroup_device_permissions_to_string(p)); if (p <= 0 || p >= _CGROUP_DEVICE_PERMISSIONS_MAX) return -EINVAL; @@ -94,9 +94,9 @@ static int bpf_prog_allow_list_major( else r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); if (r < 0) - log_error_errno(r, "Extending device control BPF program failed: %m"); + return log_error_errno(r, "Extending device control BPF program failed: %m"); - return r; + return 1; /* return 1 → we did something */ } static int bpf_prog_allow_list_class( @@ -130,9 +130,9 @@ static int bpf_prog_allow_list_class( else r = bpf_program_add_instructions(prog, insn, ELEMENTSOF(insn)); if (r < 0) - log_error_errno(r, "Extending device control BPF program failed: %m"); + return log_error_errno(r, "Extending device control BPF program failed: %m"); - return r; + return 1; /* return 1 → we did something */ } int bpf_devices_cgroup_init( @@ -165,8 +165,10 @@ int bpf_devices_cgroup_init( assert(ret); - if (policy == CGROUP_DEVICE_POLICY_AUTO && !allow_list) + if (policy == CGROUP_DEVICE_POLICY_AUTO && !allow_list) { + *ret = NULL; return 0; + } r = bpf_program_new(BPF_PROG_TYPE_CGROUP_DEVICE, "sd_devices", &prog); if (r < 0) @@ -179,8 +181,7 @@ int bpf_devices_cgroup_init( } *ret = TAKE_PTR(prog); - - return 0; + return 1; } int bpf_devices_apply_policy( @@ -307,8 +308,8 @@ static int allow_list_device_pattern( BPFProgram *prog, const char *path, char type, - const unsigned *maj, - const unsigned *min, + unsigned major, + unsigned minor, CGroupDevicePermissions p) { assert(IN_SET(type, 'b', 'c')); @@ -317,10 +318,10 @@ static int allow_list_device_pattern( if (!prog) return 0; - if (maj && min) - return bpf_prog_allow_list_device(prog, type, *maj, *min, p); - else if (maj) - return bpf_prog_allow_list_major(prog, type, *maj, p); + if (major != UINT_MAX && minor != UINT_MAX) + return bpf_prog_allow_list_device(prog, type, major, minor, p); + else if (major != UINT_MAX) + return bpf_prog_allow_list_major(prog, type, major, p); else return bpf_prog_allow_list_class(prog, type, p); @@ -328,10 +329,10 @@ static int allow_list_device_pattern( char buf[2+DECIMAL_STR_MAX(unsigned)*2+2+4]; int r; - if (maj && min) - xsprintf(buf, "%c %u:%u %s", type, *maj, *min, cgroup_device_permissions_to_string(p)); - else if (maj) - xsprintf(buf, "%c %u:* %s", type, *maj, cgroup_device_permissions_to_string(p)); + if (major != UINT_MAX && minor != UINT_MAX) + xsprintf(buf, "%c %u:%u %s", type, major, minor, cgroup_device_permissions_to_string(p)); + else if (major != UINT_MAX) + xsprintf(buf, "%c %u:* %s", type, major, cgroup_device_permissions_to_string(p)); else xsprintf(buf, "%c *:* %s", type, cgroup_device_permissions_to_string(p)); @@ -371,8 +372,14 @@ int bpf_devices_allow_list_device( return log_warning_errno(r, "Couldn't parse major/minor from device path '%s': %m", node); struct stat st; - if (stat(node, &st) < 0) + if (stat(node, &st) < 0) { + if (errno == ENOENT) { + log_debug_errno(errno, "Device '%s' does not exist, skipping.", node); + return 0; /* returning 0 means → skipped */ + } + return log_warning_errno(errno, "Couldn't stat device %s: %m", node); + } if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "%s is not a device.", node); @@ -381,8 +388,7 @@ int bpf_devices_allow_list_device( rdev = (dev_t) st.st_rdev; } - unsigned maj = major(rdev), min = minor(rdev); - return allow_list_device_pattern(prog, path, S_ISCHR(mode) ? 'c' : 'b', &maj, &min, p); + return allow_list_device_pattern(prog, path, S_ISCHR(mode) ? 'c' : 'b', major(rdev), minor(rdev), p); } int bpf_devices_allow_list_major( @@ -392,7 +398,7 @@ int bpf_devices_allow_list_major( char type, CGroupDevicePermissions permissions) { - unsigned maj; + unsigned major; int r; assert(path); @@ -401,12 +407,12 @@ int bpf_devices_allow_list_major( if (streq(name, "*")) /* If the name is a wildcard, then apply this list to all devices of this type */ - return allow_list_device_pattern(prog, path, type, NULL, NULL, permissions); + return allow_list_device_pattern(prog, path, type, /* major= */ UINT_MAX, /* minor= */ UINT_MAX, permissions); - if (safe_atou(name, &maj) >= 0 && DEVICE_MAJOR_VALID(maj)) + if (safe_atou(name, &major) >= 0 && DEVICE_MAJOR_VALID(major)) /* The name is numeric and suitable as major. In that case, let's take its major, and create * the entry directly. */ - return allow_list_device_pattern(prog, path, type, &maj, NULL, permissions); + return allow_list_device_pattern(prog, path, type, major, /* minor= */ UINT_MAX, permissions); _cleanup_fclose_ FILE *f = NULL; bool good = false, any = false; @@ -450,10 +456,10 @@ int bpf_devices_allow_list_major( continue; *w = 0; - r = safe_atou(p, &maj); + r = safe_atou(p, &major); if (r < 0) continue; - if (maj <= 0) + if (major <= 0) continue; w++; @@ -462,15 +468,15 @@ int bpf_devices_allow_list_major( if (fnmatch(name, w, 0) != 0) continue; - any = true; - (void) allow_list_device_pattern(prog, path, type, &maj, NULL, permissions); + if (allow_list_device_pattern(prog, path, type, major, /* minor= */ UINT_MAX, permissions) > 0) + any = true; } if (!any) return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Device allow list pattern \"%s\" did not match anything.", name); - return 0; + return any; } int bpf_devices_allow_list_static( @@ -492,13 +498,13 @@ int bpf_devices_allow_list_static( NULSTR_FOREACH_PAIR(node, acc, auto_devices) { k = bpf_devices_allow_list_device(prog, path, node, cgroup_device_permissions_from_string(acc)); - if (r >= 0 && k < 0) + if ((r >= 0 && k < 0) || (r >= 0 && k > 0)) r = k; } /* PTS (/dev/pts) devices may not be duplicated, but accessed */ k = bpf_devices_allow_list_major(prog, path, "pts", 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); - if (r >= 0 && k < 0) + if ((r >= 0 && k < 0) || (r >= 0 && k > 0)) r = k; return r; diff --git a/src/core/bpf-firewall.c b/src/core/bpf-firewall.c index 66773e1..185ed7d 100644 --- a/src/core/bpf-firewall.c +++ b/src/core/bpf-firewall.c @@ -1,12 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include #include #include #include -#include #include #include #include @@ -196,19 +197,26 @@ static int bpf_firewall_compile_bpf( _cleanup_(bpf_program_freep) BPFProgram *p = NULL; int accounting_map_fd, r; bool access_enabled; + CGroupRuntime *crt; assert(u); assert(ret); + crt = unit_get_cgroup_runtime(u); + if (!crt) { + *ret = NULL; + return 0; + } + accounting_map_fd = is_ingress ? - u->ip_accounting_ingress_map_fd : - u->ip_accounting_egress_map_fd; + crt->ip_accounting_ingress_map_fd : + crt->ip_accounting_egress_map_fd; access_enabled = - u->ipv4_allow_map_fd >= 0 || - u->ipv6_allow_map_fd >= 0 || - u->ipv4_deny_map_fd >= 0 || - u->ipv6_deny_map_fd >= 0 || + crt->ipv4_allow_map_fd >= 0 || + crt->ipv6_allow_map_fd >= 0 || + crt->ipv4_deny_map_fd >= 0 || + crt->ipv6_deny_map_fd >= 0 || ip_allow_any || ip_deny_any; @@ -234,26 +242,26 @@ static int bpf_firewall_compile_bpf( * - Otherwise, access will be granted */ - if (u->ipv4_deny_map_fd >= 0) { - r = add_lookup_instructions(p, u->ipv4_deny_map_fd, ETH_P_IP, is_ingress, ACCESS_DENIED); + if (crt->ipv4_deny_map_fd >= 0) { + r = add_lookup_instructions(p, crt->ipv4_deny_map_fd, ETH_P_IP, is_ingress, ACCESS_DENIED); if (r < 0) return r; } - if (u->ipv6_deny_map_fd >= 0) { - r = add_lookup_instructions(p, u->ipv6_deny_map_fd, ETH_P_IPV6, is_ingress, ACCESS_DENIED); + if (crt->ipv6_deny_map_fd >= 0) { + r = add_lookup_instructions(p, crt->ipv6_deny_map_fd, ETH_P_IPV6, is_ingress, ACCESS_DENIED); if (r < 0) return r; } - if (u->ipv4_allow_map_fd >= 0) { - r = add_lookup_instructions(p, u->ipv4_allow_map_fd, ETH_P_IP, is_ingress, ACCESS_ALLOWED); + if (crt->ipv4_allow_map_fd >= 0) { + r = add_lookup_instructions(p, crt->ipv4_allow_map_fd, ETH_P_IP, is_ingress, ACCESS_ALLOWED); if (r < 0) return r; } - if (u->ipv6_allow_map_fd >= 0) { - r = add_lookup_instructions(p, u->ipv6_allow_map_fd, ETH_P_IPV6, is_ingress, ACCESS_ALLOWED); + if (crt->ipv6_allow_map_fd >= 0) { + r = add_lookup_instructions(p, crt->ipv6_allow_map_fd, ETH_P_IPV6, is_ingress, ACCESS_ALLOWED); if (r < 0) return r; } @@ -495,37 +503,36 @@ static int bpf_firewall_prepare_access_maps( return 0; } -static int bpf_firewall_prepare_accounting_maps(Unit *u, bool enabled, int *fd_ingress, int *fd_egress) { +static int bpf_firewall_prepare_accounting_maps(Unit *u, bool enabled, CGroupRuntime *crt) { int r; assert(u); - assert(fd_ingress); - assert(fd_egress); + assert(crt); if (enabled) { - if (*fd_ingress < 0) { + if (crt->ip_accounting_ingress_map_fd < 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; - *fd_ingress = r; + crt->ip_accounting_ingress_map_fd = r; } - if (*fd_egress < 0) { + if (crt->ip_accounting_egress_map_fd < 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; - *fd_egress = r; + crt->ip_accounting_egress_map_fd = r; } } else { - *fd_ingress = safe_close(*fd_ingress); - *fd_egress = safe_close(*fd_egress); + crt->ip_accounting_ingress_map_fd = safe_close(crt->ip_accounting_ingress_map_fd); + crt->ip_accounting_egress_map_fd = safe_close(crt->ip_accounting_egress_map_fd); - zero(u->ip_accounting_extra); + zero(crt->ip_accounting_extra); } return 0; @@ -535,6 +542,7 @@ int bpf_firewall_compile(Unit *u) { const char *ingress_name = NULL, *egress_name = NULL; bool ip_allow_any = false, ip_deny_any = false; CGroupContext *cc; + CGroupRuntime *crt; int r, supported; assert(u); @@ -543,6 +551,10 @@ int bpf_firewall_compile(Unit *u) { if (!cc) return -EINVAL; + crt = unit_setup_cgroup_runtime(u); + if (!crt) + return -ENOMEM; + supported = bpf_firewall_supported(); if (supported < 0) return supported; @@ -569,14 +581,14 @@ int bpf_firewall_compile(Unit *u) { * but we reuse the accounting maps. That way the firewall in effect always maps to the actual * configuration, but we don't flush out the accounting unnecessarily */ - u->ip_bpf_ingress = bpf_program_free(u->ip_bpf_ingress); - u->ip_bpf_egress = bpf_program_free(u->ip_bpf_egress); + crt->ip_bpf_ingress = bpf_program_free(crt->ip_bpf_ingress); + crt->ip_bpf_egress = bpf_program_free(crt->ip_bpf_egress); - u->ipv4_allow_map_fd = safe_close(u->ipv4_allow_map_fd); - u->ipv4_deny_map_fd = safe_close(u->ipv4_deny_map_fd); + crt->ipv4_allow_map_fd = safe_close(crt->ipv4_allow_map_fd); + crt->ipv4_deny_map_fd = safe_close(crt->ipv4_deny_map_fd); - u->ipv6_allow_map_fd = safe_close(u->ipv6_allow_map_fd); - u->ipv6_deny_map_fd = safe_close(u->ipv6_deny_map_fd); + crt->ipv6_allow_map_fd = safe_close(crt->ipv6_allow_map_fd); + crt->ipv6_deny_map_fd = safe_close(crt->ipv6_deny_map_fd); if (u->type != UNIT_SLICE) { /* In inner nodes we only do accounting, we do not actually bother with access control. However, leaf @@ -585,24 +597,24 @@ int bpf_firewall_compile(Unit *u) { * means that all configure IP access rules *will* take effect on processes, even though we never * compile them for inner nodes. */ - r = bpf_firewall_prepare_access_maps(u, ACCESS_ALLOWED, &u->ipv4_allow_map_fd, &u->ipv6_allow_map_fd, &ip_allow_any); + r = bpf_firewall_prepare_access_maps(u, ACCESS_ALLOWED, &crt->ipv4_allow_map_fd, &crt->ipv6_allow_map_fd, &ip_allow_any); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Preparation of BPF allow maps failed: %m"); - r = bpf_firewall_prepare_access_maps(u, ACCESS_DENIED, &u->ipv4_deny_map_fd, &u->ipv6_deny_map_fd, &ip_deny_any); + r = bpf_firewall_prepare_access_maps(u, ACCESS_DENIED, &crt->ipv4_deny_map_fd, &crt->ipv6_deny_map_fd, &ip_deny_any); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Preparation of BPF deny maps failed: %m"); } - r = bpf_firewall_prepare_accounting_maps(u, cc->ip_accounting, &u->ip_accounting_ingress_map_fd, &u->ip_accounting_egress_map_fd); + r = bpf_firewall_prepare_accounting_maps(u, cc->ip_accounting, crt); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Preparation of BPF accounting maps failed: %m"); - r = bpf_firewall_compile_bpf(u, ingress_name, true, &u->ip_bpf_ingress, ip_allow_any, ip_deny_any); + r = bpf_firewall_compile_bpf(u, ingress_name, true, &crt->ip_bpf_ingress, ip_allow_any, ip_deny_any); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Compilation of ingress BPF program failed: %m"); - r = bpf_firewall_compile_bpf(u, egress_name, false, &u->ip_bpf_egress, ip_allow_any, ip_deny_any); + r = bpf_firewall_compile_bpf(u, egress_name, false, &crt->ip_bpf_egress, ip_allow_any, ip_deny_any); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Compilation of egress BPF program failed: %m"); @@ -634,6 +646,7 @@ static int load_bpf_progs_from_fs_to_set(Unit *u, char **filter_paths, Set **set int bpf_firewall_load_custom(Unit *u) { CGroupContext *cc; + CGroupRuntime *crt; int r, supported; assert(u); @@ -641,6 +654,9 @@ int bpf_firewall_load_custom(Unit *u) { cc = unit_get_cgroup_context(u); if (!cc) return 0; + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; if (!(cc->ip_filters_ingress || cc->ip_filters_egress)) return 0; @@ -653,10 +669,10 @@ int bpf_firewall_load_custom(Unit *u) { return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-firewall: BPF_F_ALLOW_MULTI not supported, cannot attach custom BPF programs."); - r = load_bpf_progs_from_fs_to_set(u, cc->ip_filters_ingress, &u->ip_bpf_custom_ingress); + r = load_bpf_progs_from_fs_to_set(u, cc->ip_filters_ingress, &crt->ip_bpf_custom_ingress); if (r < 0) return r; - r = load_bpf_progs_from_fs_to_set(u, cc->ip_filters_egress, &u->ip_bpf_custom_egress); + r = load_bpf_progs_from_fs_to_set(u, cc->ip_filters_egress, &crt->ip_bpf_custom_egress); if (r < 0) return r; @@ -686,6 +702,7 @@ int bpf_firewall_install(Unit *u) { _cleanup_(bpf_program_freep) BPFProgram *ip_bpf_ingress_uninstall = NULL, *ip_bpf_egress_uninstall = NULL; _cleanup_free_ char *path = NULL; CGroupContext *cc; + CGroupRuntime *crt; int r, supported; uint32_t flags; @@ -694,9 +711,12 @@ int bpf_firewall_install(Unit *u) { cc = unit_get_cgroup_context(u); if (!cc) return -EINVAL; - if (!u->cgroup_path) + crt = unit_get_cgroup_runtime(u); + if (!crt) + return -EINVAL; + if (!crt->cgroup_path) return -EINVAL; - if (!u->cgroup_realized) + if (!crt->cgroup_realized) return -EINVAL; supported = bpf_firewall_supported(); @@ -709,11 +729,11 @@ int bpf_firewall_install(Unit *u) { return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-firewall: BPF_F_ALLOW_MULTI not supported, not doing BPF firewall on slice units."); if (supported != BPF_FIREWALL_SUPPORTED_WITH_MULTI && - (!set_isempty(u->ip_bpf_custom_ingress) || !set_isempty(u->ip_bpf_custom_egress))) + (!set_isempty(crt->ip_bpf_custom_ingress) || !set_isempty(crt->ip_bpf_custom_egress))) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-firewall: BPF_F_ALLOW_MULTI not supported, cannot attach custom BPF programs."); - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &path); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &path); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Failed to determine cgroup path: %m"); @@ -724,44 +744,44 @@ int bpf_firewall_install(Unit *u) { * after attaching the new programs, so that there's no time window where neither program is * attached. (There will be a program where both are attached, but that's OK, since this is a * security feature where we rather want to lock down too much than too little */ - ip_bpf_egress_uninstall = TAKE_PTR(u->ip_bpf_egress_installed); - ip_bpf_ingress_uninstall = TAKE_PTR(u->ip_bpf_ingress_installed); + ip_bpf_egress_uninstall = TAKE_PTR(crt->ip_bpf_egress_installed); + ip_bpf_ingress_uninstall = TAKE_PTR(crt->ip_bpf_ingress_installed); } else { /* If we don't have BPF_F_ALLOW_MULTI then unref the old BPF programs (which will implicitly * detach them) right before attaching the new program, to minimize the time window when we * don't account for IP traffic. */ - u->ip_bpf_egress_installed = bpf_program_free(u->ip_bpf_egress_installed); - u->ip_bpf_ingress_installed = bpf_program_free(u->ip_bpf_ingress_installed); + crt->ip_bpf_egress_installed = bpf_program_free(crt->ip_bpf_egress_installed); + crt->ip_bpf_ingress_installed = bpf_program_free(crt->ip_bpf_ingress_installed); } - if (u->ip_bpf_egress) { - r = bpf_program_cgroup_attach(u->ip_bpf_egress, BPF_CGROUP_INET_EGRESS, path, flags); + if (crt->ip_bpf_egress) { + r = bpf_program_cgroup_attach(crt->ip_bpf_egress, BPF_CGROUP_INET_EGRESS, path, flags); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Attaching egress BPF program to cgroup %s failed: %m", path); /* Remember that this BPF program is installed now. */ - u->ip_bpf_egress_installed = TAKE_PTR(u->ip_bpf_egress); + crt->ip_bpf_egress_installed = TAKE_PTR(crt->ip_bpf_egress); } - if (u->ip_bpf_ingress) { - r = bpf_program_cgroup_attach(u->ip_bpf_ingress, BPF_CGROUP_INET_INGRESS, path, flags); + if (crt->ip_bpf_ingress) { + r = bpf_program_cgroup_attach(crt->ip_bpf_ingress, BPF_CGROUP_INET_INGRESS, path, flags); if (r < 0) return log_unit_error_errno(u, r, "bpf-firewall: Attaching ingress BPF program to cgroup %s failed: %m", path); - u->ip_bpf_ingress_installed = TAKE_PTR(u->ip_bpf_ingress); + crt->ip_bpf_ingress_installed = TAKE_PTR(crt->ip_bpf_ingress); } /* And now, definitely get rid of the old programs, and detach them */ ip_bpf_egress_uninstall = bpf_program_free(ip_bpf_egress_uninstall); ip_bpf_ingress_uninstall = bpf_program_free(ip_bpf_ingress_uninstall); - r = attach_custom_bpf_progs(u, path, BPF_CGROUP_INET_EGRESS, &u->ip_bpf_custom_egress, &u->ip_bpf_custom_egress_installed); + r = attach_custom_bpf_progs(u, path, BPF_CGROUP_INET_EGRESS, &crt->ip_bpf_custom_egress, &crt->ip_bpf_custom_egress_installed); if (r < 0) return r; - r = attach_custom_bpf_progs(u, path, BPF_CGROUP_INET_INGRESS, &u->ip_bpf_custom_ingress, &u->ip_bpf_custom_ingress_installed); + r = attach_custom_bpf_progs(u, path, BPF_CGROUP_INET_INGRESS, &crt->ip_bpf_custom_ingress, &crt->ip_bpf_custom_ingress_installed); if (r < 0) return r; @@ -954,21 +974,25 @@ void emit_bpf_firewall_warning(Unit *u) { void bpf_firewall_close(Unit *u) { assert(u); - u->ip_accounting_ingress_map_fd = safe_close(u->ip_accounting_ingress_map_fd); - u->ip_accounting_egress_map_fd = safe_close(u->ip_accounting_egress_map_fd); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + + crt->ip_accounting_ingress_map_fd = safe_close(crt->ip_accounting_ingress_map_fd); + crt->ip_accounting_egress_map_fd = safe_close(crt->ip_accounting_egress_map_fd); - u->ipv4_allow_map_fd = safe_close(u->ipv4_allow_map_fd); - u->ipv6_allow_map_fd = safe_close(u->ipv6_allow_map_fd); - u->ipv4_deny_map_fd = safe_close(u->ipv4_deny_map_fd); - u->ipv6_deny_map_fd = safe_close(u->ipv6_deny_map_fd); + crt->ipv4_allow_map_fd = safe_close(crt->ipv4_allow_map_fd); + crt->ipv6_allow_map_fd = safe_close(crt->ipv6_allow_map_fd); + crt->ipv4_deny_map_fd = safe_close(crt->ipv4_deny_map_fd); + crt->ipv6_deny_map_fd = safe_close(crt->ipv6_deny_map_fd); - u->ip_bpf_ingress = bpf_program_free(u->ip_bpf_ingress); - u->ip_bpf_ingress_installed = bpf_program_free(u->ip_bpf_ingress_installed); - u->ip_bpf_egress = bpf_program_free(u->ip_bpf_egress); - u->ip_bpf_egress_installed = bpf_program_free(u->ip_bpf_egress_installed); + crt->ip_bpf_ingress = bpf_program_free(crt->ip_bpf_ingress); + crt->ip_bpf_ingress_installed = bpf_program_free(crt->ip_bpf_ingress_installed); + crt->ip_bpf_egress = bpf_program_free(crt->ip_bpf_egress); + crt->ip_bpf_egress_installed = bpf_program_free(crt->ip_bpf_egress_installed); - u->ip_bpf_custom_ingress = set_free(u->ip_bpf_custom_ingress); - u->ip_bpf_custom_egress = set_free(u->ip_bpf_custom_egress); - u->ip_bpf_custom_ingress_installed = set_free(u->ip_bpf_custom_ingress_installed); - u->ip_bpf_custom_egress_installed = set_free(u->ip_bpf_custom_egress_installed); + crt->ip_bpf_custom_ingress = set_free(crt->ip_bpf_custom_ingress); + crt->ip_bpf_custom_egress = set_free(crt->ip_bpf_custom_egress); + crt->ip_bpf_custom_ingress_installed = set_free(crt->ip_bpf_custom_ingress_installed); + crt->ip_bpf_custom_egress_installed = set_free(crt->ip_bpf_custom_egress_installed); } diff --git a/src/core/bpf-foreign.c b/src/core/bpf-foreign.c index cff2f61..851cc42 100644 --- a/src/core/bpf-foreign.c +++ b/src/core/bpf-foreign.c @@ -45,8 +45,8 @@ static int bpf_foreign_key_compare_func(const BPFForeignKey *a, const BPFForeign } static void bpf_foreign_key_hash_func(const BPFForeignKey *p, struct siphash *h) { - siphash24_compress(&p->prog_id, sizeof(p->prog_id), h); - siphash24_compress(&p->attach_type, sizeof(p->attach_type), h); + siphash24_compress_typesafe(p->prog_id, h); + siphash24_compress_typesafe(p->attach_type, h); } DEFINE_PRIVATE_HASH_OPS_FULL(bpf_foreign_by_key_hash_ops, @@ -81,6 +81,7 @@ static int bpf_foreign_prepare( Unit *u, enum bpf_attach_type attach_type, const char *bpffs_path) { + _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; _cleanup_free_ BPFForeignKey *key = NULL; uint32_t prog_id; @@ -101,6 +102,11 @@ static int bpf_foreign_prepare( return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), "bpf-foreign: Path in BPF filesystem is expected."); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Failed to get control group runtime object."); + r = bpf_program_new_from_bpffs_path(bpffs_path, &prog); if (r < 0) return log_unit_error_errno(u, r, "bpf-foreign: Failed to create foreign BPF program: %m"); @@ -114,7 +120,7 @@ static int bpf_foreign_prepare( return log_unit_error_errno(u, r, "bpf-foreign: Failed to create foreign BPF program key from path '%s': %m", bpffs_path); - r = hashmap_ensure_put(&u->bpf_foreign_by_key, &bpf_foreign_by_key_hash_ops, key, prog); + r = hashmap_ensure_put(&crt->bpf_foreign_by_key, &bpf_foreign_by_key_hash_ops, key, prog); if (r == -EEXIST) { log_unit_warning_errno(u, r, "bpf-foreign: Foreign BPF program already exists, ignoring: %m"); return 0; @@ -131,6 +137,7 @@ static int bpf_foreign_prepare( int bpf_foreign_install(Unit *u) { _cleanup_free_ char *cgroup_path = NULL; CGroupContext *cc; + CGroupRuntime *crt; int r, ret = 0; assert(u); @@ -139,7 +146,11 @@ int bpf_foreign_install(Unit *u) { if (!cc) return 0; - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path); + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &cgroup_path); if (r < 0) return log_unit_error_errno(u, r, "bpf-foreign: Failed to get cgroup path: %m"); @@ -149,6 +160,6 @@ int bpf_foreign_install(Unit *u) { ret = r; } - r = attach_programs(u, cgroup_path, u->bpf_foreign_by_key, BPF_F_ALLOW_MULTI); + r = attach_programs(u, cgroup_path, crt->bpf_foreign_by_key, BPF_F_ALLOW_MULTI); return ret < 0 ? ret : r; } diff --git a/src/core/bpf-lsm.c b/src/core/bpf-lsm.c deleted file mode 100644 index 216fc34..0000000 --- a/src/core/bpf-lsm.c +++ /dev/null @@ -1,320 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bpf-lsm.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "filesystems.h" -#include "log.h" -#include "lsm-util.h" -#include "manager.h" -#include "mkdir.h" -#include "nulstr-util.h" -#include "stat-util.h" -#include "strv.h" - -#if BPF_FRAMEWORK -/* libbpf, clang and llc compile time dependencies are satisfied */ -#include "bpf-dlopen.h" -#include "bpf-link.h" -#include "bpf-util.h" -#include "bpf/restrict_fs/restrict-fs-skel.h" - -#define CGROUP_HASH_SIZE_MAX 2048 - -static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) { - /* restrict_fs_bpf__destroy handles object == NULL case */ - (void) restrict_fs_bpf__destroy(obj); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free); - -static bool bpf_can_link_lsm_program(struct bpf_program *prog) { - _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; - - assert(prog); - - link = sym_bpf_program__attach_lsm(prog); - - /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory - * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence - * BPF_LSM_MAC attach type) is not supported. */ - return sym_libbpf_get_error(link) == 0; -} - -static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) { - _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; - _cleanup_close_ int inner_map_fd = -EBADF; - int r; - - assert(ret_obj); - - obj = restrict_fs_bpf__open(); - if (!obj) - return log_error_errno(errno, "bpf-lsm: Failed to open BPF object: %m"); - - /* TODO Maybe choose a number based on runtime information? */ - r = sym_bpf_map__set_max_entries(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX); - assert(r <= 0); - if (r < 0) - return log_error_errno(r, "bpf-lsm: Failed to resize BPF map '%s': %m", - sym_bpf_map__name(obj->maps.cgroup_hash)); - - /* Dummy map to satisfy the verifier */ - inner_map_fd = compat_bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(uint32_t), sizeof(uint32_t), 128U, NULL); - if (inner_map_fd < 0) - return log_error_errno(errno, "bpf-lsm: Failed to create BPF map: %m"); - - r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd); - assert(r <= 0); - if (r < 0) - return log_error_errno(r, "bpf-lsm: Failed to set inner map fd: %m"); - - r = restrict_fs_bpf__load(obj); - assert(r <= 0); - if (r < 0) - return log_error_errno(r, "bpf-lsm: Failed to load BPF object: %m"); - - *ret_obj = TAKE_PTR(obj); - - return 0; -} - -bool lsm_bpf_supported(bool initialize) { - _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; - static int supported = -1; - int r; - - if (supported >= 0) - return supported; - if (!initialize) - return false; - - if (!cgroup_bpf_supported()) - return (supported = false); - - 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"); - return (supported = false); - } - - r = prepare_restrict_fs_bpf(&obj); - if (r < 0) - return (supported = false); - - if (!bpf_can_link_lsm_program(obj->progs.restrict_filesystems)) { - log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "bpf-lsm: Failed to link program; assuming BPF LSM is not available"); - return (supported = false); - } - - return (supported = true); -} - -int lsm_bpf_setup(Manager *m) { - _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; - _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; - int r; - - assert(m); - - r = prepare_restrict_fs_bpf(&obj); - if (r < 0) - return r; - - link = sym_bpf_program__attach_lsm(obj->progs.restrict_filesystems); - r = sym_libbpf_get_error(link); - if (r != 0) - return log_error_errno(r, "bpf-lsm: Failed to link '%s' LSM BPF program: %m", - sym_bpf_program__name(obj->progs.restrict_filesystems)); - - log_info("bpf-lsm: LSM BPF program attached"); - - obj->links.restrict_filesystems = TAKE_PTR(link); - m->restrict_fs = TAKE_PTR(obj); - - return 0; -} - -int lsm_bpf_restrict_filesystems(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, bool allow_list) { - uint32_t dummy_value = 1, zero = 0; - const char *fs; - const statfs_f_type_t *magic; - int r; - - assert(filesystems); - assert(outer_map_fd >= 0); - - int inner_map_fd = compat_bpf_map_create( - BPF_MAP_TYPE_HASH, - NULL, - sizeof(uint32_t), - sizeof(uint32_t), - 128U, /* Should be enough for all filesystem types */ - NULL); - if (inner_map_fd < 0) - return log_error_errno(errno, "bpf-lsm: Failed to create inner BPF map: %m"); - - if (sym_bpf_map_update_elem(outer_map_fd, &cgroup_id, &inner_map_fd, BPF_ANY) != 0) - return log_error_errno(errno, "bpf-lsm: Error populating BPF map: %m"); - - uint32_t allow = allow_list; - - /* Use key 0 to store whether this is an allow list or a deny list */ - if (sym_bpf_map_update_elem(inner_map_fd, &zero, &allow, BPF_ANY) != 0) - return log_error_errno(errno, "bpf-lsm: Error initializing map: %m"); - - SET_FOREACH(fs, filesystems) { - r = fs_type_from_string(fs, &magic); - if (r < 0) { - log_warning("bpf-lsm: Invalid filesystem name '%s', ignoring.", fs); - continue; - } - - log_debug("bpf-lsm: Restricting filesystem access to '%s'", fs); - - for (int i = 0; i < FILESYSTEM_MAGIC_MAX; i++) { - if (magic[i] == 0) - break; - - if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) { - r = log_error_errno(errno, "bpf-lsm: Failed to update BPF map: %m"); - - if (sym_bpf_map_delete_elem(outer_map_fd, &cgroup_id) != 0) - log_debug_errno(errno, "bpf-lsm: Failed to delete cgroup entry from BPF map: %m"); - - return r; - } - } - } - - return 0; -} - -int lsm_bpf_cleanup(const Unit *u) { - assert(u); - assert(u->manager); - - /* If we never successfully detected support, there is nothing to clean up. */ - if (!lsm_bpf_supported(/* initialize = */ false)) - return 0; - - if (!u->manager->restrict_fs) - return 0; - - if (u->cgroup_id == 0) - return 0; - - int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); - if (fd < 0) - return log_unit_error_errno(u, errno, "bpf-lsm: Failed to get BPF map fd: %m"); - - if (sym_bpf_map_delete_elem(fd, &u->cgroup_id) != 0 && errno != ENOENT) - return log_unit_debug_errno(u, errno, "bpf-lsm: Failed to delete cgroup entry from LSM BPF map: %m"); - - return 0; -} - -int lsm_bpf_map_restrict_fs_fd(Unit *unit) { - assert(unit); - assert(unit->manager); - - if (!unit->manager->restrict_fs) - return -ENOMEDIUM; - - return sym_bpf_map__fd(unit->manager->restrict_fs->maps.cgroup_hash); -} - -void lsm_bpf_destroy(struct restrict_fs_bpf *prog) { - restrict_fs_bpf__destroy(prog); -} -#else /* ! BPF_FRAMEWORK */ -bool lsm_bpf_supported(bool initialize) { - return false; -} - -int lsm_bpf_setup(Manager *m) { - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to set up LSM BPF: %m"); -} - -int lsm_bpf_restrict_filesystems(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, const bool allow_list) { - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm: Failed to restrict filesystems using LSM BPF: %m"); -} - -int lsm_bpf_cleanup(const Unit *u) { - return 0; -} - -int lsm_bpf_map_restrict_fs_fd(Unit *unit) { - return -ENOMEDIUM; -} - -void lsm_bpf_destroy(struct restrict_fs_bpf *prog) { - return; -} -#endif - -int lsm_bpf_parse_filesystem( - const char *name, - Set **filesystems, - FilesystemParseFlags flags, - const char *unit, - const char *filename, - unsigned line) { - int r; - - assert(name); - assert(filesystems); - - if (name[0] == '@') { - const FilesystemSet *set; - - set = filesystem_set_find(name); - if (!set) { - log_syntax(unit, flags & FILESYSTEM_PARSE_LOG ? LOG_WARNING : LOG_DEBUG, filename, line, 0, - "bpf-lsm: Unknown filesystem group, ignoring: %s", name); - return 0; - } - - NULSTR_FOREACH(i, set->value) { - /* Call ourselves again, for the group to parse. Note that we downgrade logging here - * (i.e. take away the FILESYSTEM_PARSE_LOG flag) since any issues in the group table - * are our own problem, not a problem in user configuration data and we shouldn't - * pretend otherwise by complaining about them. */ - r = lsm_bpf_parse_filesystem(i, filesystems, flags &~ FILESYSTEM_PARSE_LOG, unit, filename, line); - if (r < 0) - return r; - } - } else { - /* If we previously wanted to forbid access to a filesystem and now - * we want to allow it, then remove it from the list. */ - if (!(flags & FILESYSTEM_PARSE_INVERT) == !!(flags & FILESYSTEM_PARSE_ALLOW_LIST)) { - r = set_put_strdup(filesystems, name); - if (r == -ENOMEM) - return flags & FILESYSTEM_PARSE_LOG ? log_oom() : -ENOMEM; - if (r < 0 && r != -EEXIST) /* When already in set, ignore */ - return r; - } else - free(set_remove(*filesystems, name)); - } - - return 0; -} diff --git a/src/core/bpf-lsm.h b/src/core/bpf-lsm.h deleted file mode 100644 index a6eda19..0000000 --- a/src/core/bpf-lsm.h +++ /dev/null @@ -1,28 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "hashmap.h" - -typedef enum FilesystemParseFlags { - FILESYSTEM_PARSE_INVERT = 1 << 0, - FILESYSTEM_PARSE_ALLOW_LIST = 1 << 1, - FILESYSTEM_PARSE_LOG = 1 << 2, -} FilesystemParseFlags; - -typedef struct Unit Unit; -typedef struct Manager Manager; - -typedef struct restrict_fs_bpf restrict_fs_bpf; - -bool lsm_bpf_supported(bool initialize); -int lsm_bpf_setup(Manager *m); -int lsm_bpf_restrict_filesystems(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, bool allow_list); -int lsm_bpf_cleanup(const Unit *u); -int lsm_bpf_map_restrict_fs_fd(Unit *u); -void lsm_bpf_destroy(struct restrict_fs_bpf *prog); -int lsm_bpf_parse_filesystem(const char *name, - Set **filesystems, - FilesystemParseFlags flags, - const char *unit, - const char *filename, - unsigned line); diff --git a/src/core/bpf-restrict-fs.c b/src/core/bpf-restrict-fs.c new file mode 100644 index 0000000..d36bfb5 --- /dev/null +++ b/src/core/bpf-restrict-fs.c @@ -0,0 +1,324 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bpf-restrict-fs.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "filesystems.h" +#include "log.h" +#include "lsm-util.h" +#include "manager.h" +#include "mkdir.h" +#include "nulstr-util.h" +#include "stat-util.h" +#include "strv.h" + +#if BPF_FRAMEWORK +/* libbpf, clang and llc compile time dependencies are satisfied */ +#include "bpf-dlopen.h" +#include "bpf-link.h" +#include "bpf-util.h" +#include "bpf/restrict_fs/restrict-fs-skel.h" + +#define CGROUP_HASH_SIZE_MAX 2048 + +static struct restrict_fs_bpf *restrict_fs_bpf_free(struct restrict_fs_bpf *obj) { + /* restrict_fs_bpf__destroy handles object == NULL case */ + (void) restrict_fs_bpf__destroy(obj); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_fs_bpf *, restrict_fs_bpf_free); + +static bool bpf_can_link_lsm_program(struct bpf_program *prog) { + _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; + + assert(prog); + + link = sym_bpf_program__attach_lsm(prog); + + /* If bpf_program__attach_lsm fails the resulting value stores libbpf error code instead of memory + * pointer. That is the case when the helper is called on architectures where BPF trampoline (hence + * BPF_LSM_MAC attach type) is not supported. */ + return bpf_get_error_translated(link) == 0; +} + +static int prepare_restrict_fs_bpf(struct restrict_fs_bpf **ret_obj) { + _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; + _cleanup_close_ int inner_map_fd = -EBADF; + int r; + + assert(ret_obj); + + obj = restrict_fs_bpf__open(); + if (!obj) + return log_error_errno(errno, "bpf-restrict-fs: Failed to open BPF object: %m"); + + /* TODO Maybe choose a number based on runtime information? */ + r = sym_bpf_map__set_max_entries(obj->maps.cgroup_hash, CGROUP_HASH_SIZE_MAX); + assert(r <= 0); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fs: Failed to resize BPF map '%s': %m", + sym_bpf_map__name(obj->maps.cgroup_hash)); + + /* Dummy map to satisfy the verifier */ + inner_map_fd = compat_bpf_map_create(BPF_MAP_TYPE_HASH, NULL, sizeof(uint32_t), sizeof(uint32_t), 128U, NULL); + if (inner_map_fd < 0) + return log_error_errno(errno, "bpf-restrict-fs: Failed to create BPF map: %m"); + + r = sym_bpf_map__set_inner_map_fd(obj->maps.cgroup_hash, inner_map_fd); + assert(r <= 0); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fs: Failed to set inner map fd: %m"); + + r = restrict_fs_bpf__load(obj); + assert(r <= 0); + if (r < 0) + return log_error_errno(r, "bpf-restrict-fs: Failed to load BPF object: %m"); + + *ret_obj = TAKE_PTR(obj); + + return 0; +} + +bool bpf_restrict_fs_supported(bool initialize) { + _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; + static int supported = -1; + int r; + + if (supported >= 0) + return supported; + if (!initialize) + return false; + + if (!cgroup_bpf_supported()) + return (supported = false); + + r = lsm_supported("bpf"); + if (r < 0) { + log_warning_errno(r, "bpf-restrict-fs: Can't determine whether the BPF LSM module is used: %m"); + return (supported = false); + } + if (r == 0) { + log_info("bpf-restrict-fs: BPF LSM hook not enabled in the kernel, BPF LSM not supported."); + return (supported = false); + } + + r = prepare_restrict_fs_bpf(&obj); + if (r < 0) + return (supported = false); + + if (!bpf_can_link_lsm_program(obj->progs.restrict_filesystems)) { + log_warning("bpf-restrict-fs: Failed to link program; assuming BPF LSM is not available."); + return (supported = false); + } + + return (supported = true); +} + +int bpf_restrict_fs_setup(Manager *m) { + _cleanup_(restrict_fs_bpf_freep) struct restrict_fs_bpf *obj = NULL; + _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; + int r; + + assert(m); + + r = prepare_restrict_fs_bpf(&obj); + if (r < 0) + return r; + + link = sym_bpf_program__attach_lsm(obj->progs.restrict_filesystems); + r = bpf_get_error_translated(link); + if (r != 0) + return log_error_errno(r, "bpf-restrict-fs: Failed to link '%s' LSM BPF program: %m", + sym_bpf_program__name(obj->progs.restrict_filesystems)); + + log_info("bpf-restrict-fs: LSM BPF program attached"); + + obj->links.restrict_filesystems = TAKE_PTR(link); + m->restrict_fs = TAKE_PTR(obj); + + return 0; +} + +int bpf_restrict_fs_update(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, bool allow_list) { + uint32_t dummy_value = 1, zero = 0; + const char *fs; + const statfs_f_type_t *magic; + int r; + + assert(filesystems); + assert(outer_map_fd >= 0); + + int inner_map_fd = compat_bpf_map_create( + BPF_MAP_TYPE_HASH, + NULL, + sizeof(uint32_t), + sizeof(uint32_t), + 128U, /* Should be enough for all filesystem types */ + NULL); + if (inner_map_fd < 0) + return log_error_errno(errno, "bpf-restrict-fs: Failed to create inner BPF map: %m"); + + if (sym_bpf_map_update_elem(outer_map_fd, &cgroup_id, &inner_map_fd, BPF_ANY) != 0) + return log_error_errno(errno, "bpf-restrict-fs: Error populating BPF map: %m"); + + uint32_t allow = allow_list; + + /* Use key 0 to store whether this is an allow list or a deny list */ + if (sym_bpf_map_update_elem(inner_map_fd, &zero, &allow, BPF_ANY) != 0) + return log_error_errno(errno, "bpf-restrict-fs: Error initializing map: %m"); + + SET_FOREACH(fs, filesystems) { + r = fs_type_from_string(fs, &magic); + if (r < 0) { + log_warning("bpf-restrict-fs: Invalid filesystem name '%s', ignoring.", fs); + continue; + } + + log_debug("bpf-restrict-fs: Restricting filesystem access to '%s'", fs); + + for (int i = 0; i < FILESYSTEM_MAGIC_MAX; i++) { + if (magic[i] == 0) + break; + + if (sym_bpf_map_update_elem(inner_map_fd, &magic[i], &dummy_value, BPF_ANY) != 0) { + r = log_error_errno(errno, "bpf-restrict-fs: Failed to update BPF map: %m"); + + if (sym_bpf_map_delete_elem(outer_map_fd, &cgroup_id) != 0) + log_debug_errno(errno, "bpf-restrict-fs: Failed to delete cgroup entry from BPF map: %m"); + + return r; + } + } + } + + return 0; +} + +int bpf_restrict_fs_cleanup(Unit *u) { + CGroupRuntime *crt; + + assert(u); + assert(u->manager); + + /* If we never successfully detected support, there is nothing to clean up. */ + if (!bpf_restrict_fs_supported(/* initialize = */ false)) + return 0; + + if (!u->manager->restrict_fs) + return 0; + + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + if (crt->cgroup_id == 0) + return 0; + + int fd = sym_bpf_map__fd(u->manager->restrict_fs->maps.cgroup_hash); + if (fd < 0) + return log_unit_error_errno(u, errno, "bpf-restrict-fs: Failed to get BPF map fd: %m"); + + if (sym_bpf_map_delete_elem(fd, &crt->cgroup_id) != 0 && errno != ENOENT) + return log_unit_debug_errno(u, errno, "bpf-restrict-fs: Failed to delete cgroup entry from LSM BPF map: %m"); + + return 0; +} + +int bpf_restrict_fs_map_fd(Unit *unit) { + assert(unit); + assert(unit->manager); + + if (!unit->manager->restrict_fs) + return -ENOMEDIUM; + + return sym_bpf_map__fd(unit->manager->restrict_fs->maps.cgroup_hash); +} + +void bpf_restrict_fs_destroy(struct restrict_fs_bpf *prog) { + restrict_fs_bpf__destroy(prog); +} +#else /* ! BPF_FRAMEWORK */ +bool bpf_restrict_fs_supported(bool initialize) { + return false; +} + +int bpf_restrict_fs_setup(Manager *m) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-restrict-fs: BPF framework is not supported."); +} + +int bpf_restrict_fs_update(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, const bool allow_list) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-restrict-fs: BPF framework is not supported."); +} + +int bpf_restrict_fs_cleanup(Unit *u) { + return 0; +} + +int bpf_restrict_fs_map_fd(Unit *unit) { + return -ENOMEDIUM; +} + +void bpf_restrict_fs_destroy(struct restrict_fs_bpf *prog) { + return; +} +#endif + +int bpf_restrict_fs_parse_filesystem( + const char *name, + Set **filesystems, + FilesystemParseFlags flags, + const char *unit, + const char *filename, + unsigned line) { + int r; + + assert(name); + assert(filesystems); + + if (name[0] == '@') { + const FilesystemSet *set; + + set = filesystem_set_find(name); + if (!set) { + log_syntax(unit, flags & FILESYSTEM_PARSE_LOG ? LOG_WARNING : LOG_DEBUG, filename, line, 0, + "bpf-restrict-fs: Unknown filesystem group, ignoring: %s", name); + return 0; + } + + NULSTR_FOREACH(i, set->value) { + /* Call ourselves again, for the group to parse. Note that we downgrade logging here + * (i.e. take away the FILESYSTEM_PARSE_LOG flag) since any issues in the group table + * are our own problem, not a problem in user configuration data and we shouldn't + * pretend otherwise by complaining about them. */ + r = bpf_restrict_fs_parse_filesystem(i, filesystems, flags &~ FILESYSTEM_PARSE_LOG, unit, filename, line); + if (r < 0) + return r; + } + } else { + /* If we previously wanted to forbid access to a filesystem and now + * we want to allow it, then remove it from the list. */ + if (!(flags & FILESYSTEM_PARSE_INVERT) == !!(flags & FILESYSTEM_PARSE_ALLOW_LIST)) { + r = set_put_strdup(filesystems, name); + if (r == -ENOMEM) + return flags & FILESYSTEM_PARSE_LOG ? log_oom() : -ENOMEM; + if (r < 0 && r != -EEXIST) /* When already in set, ignore */ + return r; + } else + free(set_remove(*filesystems, name)); + } + + return 0; +} diff --git a/src/core/bpf-restrict-fs.h b/src/core/bpf-restrict-fs.h new file mode 100644 index 0000000..8da12de --- /dev/null +++ b/src/core/bpf-restrict-fs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "hashmap.h" + +typedef enum FilesystemParseFlags { + FILESYSTEM_PARSE_INVERT = 1 << 0, + FILESYSTEM_PARSE_ALLOW_LIST = 1 << 1, + FILESYSTEM_PARSE_LOG = 1 << 2, +} FilesystemParseFlags; + +typedef struct Unit Unit; +typedef struct Manager Manager; + +typedef struct restrict_fs_bpf restrict_fs_bpf; + +bool bpf_restrict_fs_supported(bool initialize); +int bpf_restrict_fs_setup(Manager *m); +int bpf_restrict_fs_update(const Set *filesystems, uint64_t cgroup_id, int outer_map_fd, bool allow_list); +int bpf_restrict_fs_cleanup(Unit *u); +int bpf_restrict_fs_map_fd(Unit *u); +void bpf_restrict_fs_destroy(struct restrict_fs_bpf *prog); +int bpf_restrict_fs_parse_filesystem(const char *name, Set **filesystems, FilesystemParseFlags flags, const char *unit, const char *filename, unsigned line); diff --git a/src/core/bpf-restrict-ifaces.c b/src/core/bpf-restrict-ifaces.c new file mode 100644 index 0000000..64d8d1a --- /dev/null +++ b/src/core/bpf-restrict-ifaces.c @@ -0,0 +1,223 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "bpf-restrict-ifaces.h" +#include "netlink-util.h" + +#if BPF_FRAMEWORK +/* libbpf, clang and llc compile time dependencies are satisfied */ + +#include "bpf-dlopen.h" +#include "bpf-link.h" +#include "bpf-util.h" +#include "bpf/restrict_ifaces/restrict-ifaces-skel.h" + +static struct restrict_ifaces_bpf *restrict_ifaces_bpf_free(struct restrict_ifaces_bpf *obj) { + restrict_ifaces_bpf__destroy(obj); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_ifaces_bpf *, restrict_ifaces_bpf_free); + +static int prepare_restrict_ifaces_bpf( + Unit* u, + bool is_allow_list, + const Set *restrict_network_interfaces, + struct restrict_ifaces_bpf **ret_object) { + + _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char *iface; + int r, map_fd; + + assert(ret_object); + + obj = restrict_ifaces_bpf__open(); + if (!obj) + return log_unit_full_errno(u, u ? LOG_ERR : LOG_DEBUG, errno, "restrict-interfaces: Failed to open BPF object: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.sd_restrictif, MAX(set_size(restrict_network_interfaces), 1u)); + if (r != 0) + return log_unit_full_errno(u, u ? LOG_ERR : LOG_WARNING, r, + "restrict-interfaces: Failed to resize BPF map '%s': %m", + sym_bpf_map__name(obj->maps.sd_restrictif)); + + obj->rodata->is_allow_list = is_allow_list; + + r = restrict_ifaces_bpf__load(obj); + if (r != 0) + return log_unit_full_errno(u, u ? LOG_ERR : LOG_DEBUG, r, "restrict-interfaces: Failed to load BPF object: %m"); + + map_fd = sym_bpf_map__fd(obj->maps.sd_restrictif); + + SET_FOREACH(iface, restrict_network_interfaces) { + uint8_t dummy = 0; + int ifindex; + + ifindex = rtnl_resolve_interface(&rtnl, iface); + if (ifindex < 0) { + log_unit_warning_errno(u, ifindex, + "restrict-interfaces: Couldn't find index of network interface '%s', ignoring: %m", + iface); + continue; + } + + if (sym_bpf_map_update_elem(map_fd, &ifindex, &dummy, BPF_ANY)) + return log_unit_full_errno(u, u ? LOG_ERR : LOG_WARNING, errno, + "restrict-interfaces: Failed to update BPF map '%s' fd: %m", + sym_bpf_map__name(obj->maps.sd_restrictif)); + } + + *ret_object = TAKE_PTR(obj); + return 0; +} + +int bpf_restrict_ifaces_supported(void) { + _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; + static int supported = -1; + int r; + + if (supported >= 0) + return supported; + + if (!cgroup_bpf_supported()) + return (supported = false); + + if (!compat_libbpf_probe_bpf_prog_type(BPF_PROG_TYPE_CGROUP_SKB, /*opts=*/NULL)) { + log_debug("restrict-interfaces: BPF program type cgroup_skb is not supported"); + return (supported = false); + } + + r = prepare_restrict_ifaces_bpf(NULL, true, NULL, &obj); + if (r < 0) { + log_debug_errno(r, "restrict-interfaces: Failed to load BPF object: %m"); + return (supported = false); + } + + return (supported = bpf_can_link_program(obj->progs.sd_restrictif_i)); +} + +static int restrict_ifaces_install_impl(Unit *u) { + _cleanup_(bpf_link_freep) struct bpf_link *egress_link = NULL, *ingress_link = NULL; + _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; + _cleanup_free_ char *cgroup_path = NULL; + _cleanup_close_ int cgroup_fd = -EBADF; + CGroupContext *cc; + CGroupRuntime *crt; + int r; + + cc = unit_get_cgroup_context(u); + if (!cc) + return 0; + + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &cgroup_path); + if (r < 0) + return log_unit_error_errno(u, r, "restrict-interfaces: Failed to get cgroup path: %m"); + + if (!cc->restrict_network_interfaces) + return 0; + + r = prepare_restrict_ifaces_bpf(u, + cc->restrict_network_interfaces_is_allow_list, + cc->restrict_network_interfaces, + &obj); + if (r < 0) + return r; + + cgroup_fd = open(cgroup_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0); + if (cgroup_fd < 0) + return -errno; + + ingress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_i, cgroup_fd); + r = bpf_get_error_translated(ingress_link); + if (r != 0) + return log_unit_error_errno(u, r, "restrict-interfaces: Failed to create ingress cgroup link: %m"); + + egress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_e, cgroup_fd); + r = bpf_get_error_translated(egress_link); + if (r != 0) + return log_unit_error_errno(u, r, "restrict-interfaces: Failed to create egress cgroup link: %m"); + + crt->restrict_ifaces_ingress_bpf_link = TAKE_PTR(ingress_link); + crt->restrict_ifaces_egress_bpf_link = TAKE_PTR(egress_link); + + return 0; +} + +int bpf_restrict_ifaces_install(Unit *u) { + CGroupRuntime *crt; + int r; + + assert(u); + + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = restrict_ifaces_install_impl(u); + fdset_close(crt->initial_restrict_ifaces_link_fds); + return r; +} + +int bpf_restrict_ifaces_serialize(Unit *u, FILE *f, FDSet *fds) { + CGroupRuntime *crt; + int r; + + assert(u); + + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", crt->restrict_ifaces_ingress_bpf_link); + if (r < 0) + return r; + + return bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", crt->restrict_ifaces_egress_bpf_link); +} + +int bpf_restrict_ifaces_add_initial_link_fd(Unit *u, int fd) { + int r; + + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return -EINVAL; + + if (!crt->initial_restrict_ifaces_link_fds) { + crt->initial_restrict_ifaces_link_fds = fdset_new(); + if (!crt->initial_restrict_ifaces_link_fds) + return log_oom(); + } + + r = fdset_put(crt->initial_restrict_ifaces_link_fds, fd); + if (r < 0) + return log_unit_error_errno(u, r, + "restrict-interfaces: Failed to put restrict-ifaces-bpf-fd %d to restored fdset: %m", fd); + + return 0; +} + +#else /* ! BPF_FRAMEWORK */ +int bpf_restrict_ifaces_supported(void) { + return 0; +} + +int bpf_restrict_ifaces_install(Unit *u) { + return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), + "restrict-interfaces: Failed to install; BPF programs built from source code are not supported: %m"); +} + +int bpf_restrict_ifaces_serialize(Unit *u, FILE *f, FDSet *fds) { + return 0; +} + +int bpf_restrict_ifaces_add_initial_link_fd(Unit *u, int fd) { + return 0; +} +#endif diff --git a/src/core/bpf-restrict-ifaces.h b/src/core/bpf-restrict-ifaces.h new file mode 100644 index 0000000..28f7427 --- /dev/null +++ b/src/core/bpf-restrict-ifaces.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "fdset.h" +#include "unit.h" + +typedef struct Unit Unit; + +int bpf_restrict_ifaces_supported(void); +int bpf_restrict_ifaces_install(Unit *u); + +int bpf_restrict_ifaces_serialize(Unit *u, FILE *f, FDSet *fds); + +/* Add BPF link fd created before daemon-reload or daemon-reexec. + * FDs will be closed at the end of restrict_network_interfaces_install. */ +int bpf_restrict_ifaces_add_initial_link_fd(Unit *u, int fd); diff --git a/src/core/bpf-socket-bind.c b/src/core/bpf-socket-bind.c index 88ab487..2a1a027 100644 --- a/src/core/bpf-socket-bind.c +++ b/src/core/bpf-socket-bind.c @@ -148,13 +148,18 @@ int bpf_socket_bind_add_initial_link_fd(Unit *u, int fd) { assert(u); - if (!u->initial_socket_bind_link_fds) { - u->initial_socket_bind_link_fds = fdset_new(); - if (!u->initial_socket_bind_link_fds) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EINVAL), + "Failed to get control group runtime object."); + + if (!crt->initial_socket_bind_link_fds) { + crt->initial_socket_bind_link_fds = fdset_new(); + if (!crt->initial_socket_bind_link_fds) return log_oom(); } - r = fdset_put(u->initial_socket_bind_link_fds, fd); + r = fdset_put(crt->initial_socket_bind_link_fds, fd); if (r < 0) return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to put BPF fd %d to initial fdset", fd); @@ -167,6 +172,7 @@ static int socket_bind_install_impl(Unit *u) { _cleanup_free_ char *cgroup_path = NULL; _cleanup_close_ int cgroup_fd = -EBADF; CGroupContext *cc; + CGroupRuntime *crt; int r; assert(u); @@ -175,7 +181,11 @@ static int socket_bind_install_impl(Unit *u) { if (!cc) return 0; - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path); + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &cgroup_path); if (r < 0) return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to get cgroup path: %m"); @@ -191,46 +201,53 @@ static int socket_bind_install_impl(Unit *u) { return log_unit_error_errno(u, errno, "bpf-socket-bind: Failed to open cgroup %s for reading: %m", cgroup_path); ipv4 = sym_bpf_program__attach_cgroup(obj->progs.sd_bind4, cgroup_fd); - r = sym_libbpf_get_error(ipv4); + r = bpf_get_error_translated(ipv4); if (r != 0) return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to link '%s' cgroup-bpf program: %m", sym_bpf_program__name(obj->progs.sd_bind4)); ipv6 = sym_bpf_program__attach_cgroup(obj->progs.sd_bind6, cgroup_fd); - r = sym_libbpf_get_error(ipv6); + r = bpf_get_error_translated(ipv6); if (r != 0) return log_unit_error_errno(u, r, "bpf-socket-bind: Failed to link '%s' cgroup-bpf program: %m", sym_bpf_program__name(obj->progs.sd_bind6)); - u->ipv4_socket_bind_link = TAKE_PTR(ipv4); - u->ipv6_socket_bind_link = TAKE_PTR(ipv6); + crt->ipv4_socket_bind_link = TAKE_PTR(ipv4); + crt->ipv6_socket_bind_link = TAKE_PTR(ipv6); return 0; } int bpf_socket_bind_install(Unit *u) { + CGroupRuntime *crt; int r; assert(u); - r = socket_bind_install_impl(u); - if (r == -ENOMEM) - return r; + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; - fdset_close(u->initial_socket_bind_link_fds); + r = socket_bind_install_impl(u); + fdset_close(crt->initial_socket_bind_link_fds); return r; } -int bpf_serialize_socket_bind(Unit *u, FILE *f, FDSet *fds) { +int bpf_socket_bind_serialize(Unit *u, FILE *f, FDSet *fds) { + CGroupRuntime *crt; int r; assert(u); - r = bpf_serialize_link(f, fds, "ipv4-socket-bind-bpf-link", u->ipv4_socket_bind_link); + crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + r = bpf_serialize_link(f, fds, "ipv4-socket-bind-bpf-link", crt->ipv4_socket_bind_link); if (r < 0) return r; - return bpf_serialize_link(f, fds, "ipv6-socket-bind-bpf-link", u->ipv6_socket_bind_link); + return bpf_serialize_link(f, fds, "ipv6-socket-bind-bpf-link", crt->ipv6_socket_bind_link); } #else /* ! BPF_FRAMEWORK */ @@ -247,7 +264,7 @@ int bpf_socket_bind_install(Unit *u) { "bpf-socket-bind: Failed to install; BPF framework is not supported"); } -int bpf_serialize_socket_bind(Unit *u, FILE *f, FDSet *fds) { +int bpf_socket_bind_serialize(Unit *u, FILE *f, FDSet *fds) { return 0; } #endif diff --git a/src/core/bpf-socket-bind.h b/src/core/bpf-socket-bind.h index 7d426df..28b25f6 100644 --- a/src/core/bpf-socket-bind.h +++ b/src/core/bpf-socket-bind.h @@ -12,4 +12,4 @@ int bpf_socket_bind_add_initial_link_fd(Unit *u, int fd); int bpf_socket_bind_install(Unit *u); -int bpf_serialize_socket_bind(Unit *u, FILE *f, FDSet *fds); +int bpf_socket_bind_serialize(Unit *u, FILE *f, FDSet *fds); diff --git a/src/core/bpf-util.c b/src/core/bpf-util.c index 6fe229e..b337ba9 100644 --- a/src/core/bpf-util.c +++ b/src/core/bpf-util.c @@ -20,8 +20,7 @@ bool cgroup_bpf_supported(void) { } if (r == 0) { - log_info_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Not running with unified cgroup hierarchy, disabling cgroup BPF features."); + log_info("Not running with unified cgroup hierarchy, disabling cgroup BPF features."); return (supported = false); } diff --git a/src/core/cgroup.c b/src/core/cgroup.c index 61ac4df..34fd2a2 100644 --- a/src/core/cgroup.c +++ b/src/core/cgroup.c @@ -10,6 +10,7 @@ #include "bpf-devices.h" #include "bpf-firewall.h" #include "bpf-foreign.h" +#include "bpf-restrict-ifaces.h" #include "bpf-socket-bind.h" #include "btrfs-util.h" #include "bus-error.h" @@ -32,7 +33,8 @@ #include "percent-util.h" #include "process-util.h" #include "procfs-util.h" -#include "restrict-ifaces.h" +#include "set.h" +#include "serialize.h" #include "special.h" #include "stdio-util.h" #include "string-table.h" @@ -115,10 +117,16 @@ bool unit_has_host_root_cgroup(Unit *u) { static int set_attribute_and_warn(Unit *u, const char *controller, const char *attribute, const char *value) { int r; - r = cg_set_attribute(controller, u->cgroup_path, attribute, value); + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -EOWNERDEAD; + + r = cg_set_attribute(controller, crt->cgroup_path, attribute, value); if (r < 0) log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to set '%s' attribute on '%s' to '%.*s': %m", - strna(attribute), empty_to_root(u->cgroup_path), (int) strcspn(value, NEWLINE), value); + strna(attribute), empty_to_root(crt->cgroup_path), (int) strcspn(value, NEWLINE), value); return r; } @@ -172,6 +180,8 @@ void cgroup_context_init(CGroupContext *c) { .memory_limit = CGROUP_LIMIT_MAX, + .memory_zswap_writeback = true, + .io_weight = CGROUP_WEIGHT_INVALID, .startup_io_weight = CGROUP_WEIGHT_INVALID, @@ -189,6 +199,319 @@ void cgroup_context_init(CGroupContext *c) { }; } +int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w) { + _cleanup_free_ CGroupIODeviceWeight *n = NULL; + + assert(c); + assert(w); + + n = new(CGroupIODeviceWeight, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupIODeviceWeight) { + .path = strdup(w->path), + .weight = w->weight, + }; + if (!n->path) + return -ENOMEM; + + LIST_PREPEND(device_weights, c->io_device_weights, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l) { + _cleanup_free_ CGroupIODeviceLimit *n = NULL; + + assert(c); + assert(l); + + n = new0(CGroupIODeviceLimit, 1); + if (!n) + return -ENOMEM; + + n->path = strdup(l->path); + if (!n->path) + return -ENOMEM; + + for (CGroupIOLimitType type = 0; type < _CGROUP_IO_LIMIT_TYPE_MAX; type++) + n->limits[type] = l->limits[type]; + + LIST_PREPEND(device_limits, c->io_device_limits, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l) { + _cleanup_free_ CGroupIODeviceLatency *n = NULL; + + assert(c); + assert(l); + + n = new(CGroupIODeviceLatency, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupIODeviceLatency) { + .path = strdup(l->path), + .target_usec = l->target_usec, + }; + if (!n->path) + return -ENOMEM; + + LIST_PREPEND(device_latencies, c->io_device_latencies, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w) { + _cleanup_free_ CGroupBlockIODeviceWeight *n = NULL; + + assert(c); + assert(w); + + n = new(CGroupBlockIODeviceWeight, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupBlockIODeviceWeight) { + .path = strdup(w->path), + .weight = w->weight, + }; + if (!n->path) + return -ENOMEM; + + LIST_PREPEND(device_weights, c->blockio_device_weights, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b) { + _cleanup_free_ CGroupBlockIODeviceBandwidth *n = NULL; + + assert(c); + assert(b); + + n = new(CGroupBlockIODeviceBandwidth, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupBlockIODeviceBandwidth) { + .rbps = b->rbps, + .wbps = b->wbps, + }; + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a) { + _cleanup_free_ CGroupDeviceAllow *n = NULL; + + assert(c); + assert(a); + + n = new(CGroupDeviceAllow, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupDeviceAllow) { + .path = strdup(a->path), + .permissions = a->permissions, + }; + if (!n->path) + return -ENOMEM; + + LIST_PREPEND(device_allow, c->device_allow, TAKE_PTR(n)); + return 0; +} + +static int cgroup_context_add_socket_bind_item_dup(CGroupContext *c, const CGroupSocketBindItem *i, CGroupSocketBindItem *h) { + _cleanup_free_ CGroupSocketBindItem *n = NULL; + + assert(c); + assert(i); + + n = new(CGroupSocketBindItem, 1); + if (!n) + return -ENOMEM; + + *n = (CGroupSocketBindItem) { + .address_family = i->address_family, + .ip_protocol = i->ip_protocol, + .nr_ports = i->nr_ports, + .port_min = i->port_min, + }; + + LIST_PREPEND(socket_bind_items, h, TAKE_PTR(n)); + return 0; +} + +int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i) { + return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_allow); +} + +int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i) { + return cgroup_context_add_socket_bind_item_dup(c, i, c->socket_bind_deny); +} + +int cgroup_context_copy(CGroupContext *dst, const CGroupContext *src) { + struct in_addr_prefix *i; + char *iface; + int r; + + assert(src); + assert(dst); + + dst->cpu_accounting = src->cpu_accounting; + dst->io_accounting = src->io_accounting; + dst->blockio_accounting = src->blockio_accounting; + dst->memory_accounting = src->memory_accounting; + dst->tasks_accounting = src->tasks_accounting; + dst->ip_accounting = src->ip_accounting; + + dst->memory_oom_group = src->memory_oom_group; + + dst->cpu_weight = src->cpu_weight; + dst->startup_cpu_weight = src->startup_cpu_weight; + dst->cpu_quota_per_sec_usec = src->cpu_quota_per_sec_usec; + dst->cpu_quota_period_usec = src->cpu_quota_period_usec; + + dst->cpuset_cpus = src->cpuset_cpus; + dst->startup_cpuset_cpus = src->startup_cpuset_cpus; + dst->cpuset_mems = src->cpuset_mems; + dst->startup_cpuset_mems = src->startup_cpuset_mems; + + dst->io_weight = src->io_weight; + dst->startup_io_weight = src->startup_io_weight; + + LIST_FOREACH_BACKWARDS(device_weights, w, LIST_FIND_TAIL(device_weights, src->io_device_weights)) { + r = cgroup_context_add_io_device_weight_dup(dst, w); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_limits, l, LIST_FIND_TAIL(device_limits, src->io_device_limits)) { + r = cgroup_context_add_io_device_limit_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_latencies, l, LIST_FIND_TAIL(device_latencies, src->io_device_latencies)) { + r = cgroup_context_add_io_device_latency_dup(dst, l); + if (r < 0) + return r; + } + + dst->default_memory_min = src->default_memory_min; + dst->default_memory_low = src->default_memory_low; + dst->default_startup_memory_low = src->default_startup_memory_low; + dst->memory_min = src->memory_min; + dst->memory_low = src->memory_low; + dst->startup_memory_low = src->startup_memory_low; + dst->memory_high = src->memory_high; + dst->startup_memory_high = src->startup_memory_high; + dst->memory_max = src->memory_max; + dst->startup_memory_max = src->startup_memory_max; + dst->memory_swap_max = src->memory_swap_max; + dst->startup_memory_swap_max = src->startup_memory_swap_max; + dst->memory_zswap_max = src->memory_zswap_max; + dst->startup_memory_zswap_max = src->startup_memory_zswap_max; + + dst->default_memory_min_set = src->default_memory_min_set; + dst->default_memory_low_set = src->default_memory_low_set; + dst->default_startup_memory_low_set = src->default_startup_memory_low_set; + dst->memory_min_set = src->memory_min_set; + dst->memory_low_set = src->memory_low_set; + dst->startup_memory_low_set = src->startup_memory_low_set; + dst->startup_memory_high_set = src->startup_memory_high_set; + dst->startup_memory_max_set = src->startup_memory_max_set; + dst->startup_memory_swap_max_set = src->startup_memory_swap_max_set; + dst->startup_memory_zswap_max_set = src->startup_memory_zswap_max_set; + dst->memory_zswap_writeback = src->memory_zswap_writeback; + + SET_FOREACH(i, src->ip_address_allow) { + r = in_addr_prefix_add(&dst->ip_address_allow, i); + if (r < 0) + return r; + } + + SET_FOREACH(i, src->ip_address_deny) { + r = in_addr_prefix_add(&dst->ip_address_deny, i); + if (r < 0) + return r; + } + + dst->ip_address_allow_reduced = src->ip_address_allow_reduced; + dst->ip_address_deny_reduced = src->ip_address_deny_reduced; + + if (!strv_isempty(src->ip_filters_ingress)) { + dst->ip_filters_ingress = strv_copy(src->ip_filters_ingress); + if (!dst->ip_filters_ingress) + return -ENOMEM; + } + + if (!strv_isempty(src->ip_filters_egress)) { + dst->ip_filters_egress = strv_copy(src->ip_filters_egress); + if (!dst->ip_filters_egress) + return -ENOMEM; + } + + LIST_FOREACH_BACKWARDS(programs, l, LIST_FIND_TAIL(programs, src->bpf_foreign_programs)) { + r = cgroup_context_add_bpf_foreign_program_dup(dst, l); + if (r < 0) + return r; + } + + SET_FOREACH(iface, src->restrict_network_interfaces) { + r = set_put_strdup(&dst->restrict_network_interfaces, iface); + if (r < 0) + return r; + } + dst->restrict_network_interfaces_is_allow_list = src->restrict_network_interfaces_is_allow_list; + + dst->cpu_shares = src->cpu_shares; + dst->startup_cpu_shares = src->startup_cpu_shares; + + dst->blockio_weight = src->blockio_weight; + dst->startup_blockio_weight = src->startup_blockio_weight; + + LIST_FOREACH_BACKWARDS(device_weights, l, LIST_FIND_TAIL(device_weights, src->blockio_device_weights)) { + r = cgroup_context_add_block_io_device_weight_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(device_bandwidths, l, LIST_FIND_TAIL(device_bandwidths, src->blockio_device_bandwidths)) { + r = cgroup_context_add_block_io_device_bandwidth_dup(dst, l); + if (r < 0) + return r; + } + + dst->memory_limit = src->memory_limit; + + dst->device_policy = src->device_policy; + LIST_FOREACH_BACKWARDS(device_allow, l, LIST_FIND_TAIL(device_allow, src->device_allow)) { + r = cgroup_context_add_device_allow_dup(dst, l); + if (r < 0) + return r; + } + + LIST_FOREACH_BACKWARDS(socket_bind_items, l, LIST_FIND_TAIL(socket_bind_items, src->socket_bind_allow)) { + r = cgroup_context_add_socket_bind_item_allow_dup(dst, l); + if (r < 0) + return r; + + } + + LIST_FOREACH_BACKWARDS(socket_bind_items, l, LIST_FIND_TAIL(socket_bind_items, src->socket_bind_deny)) { + r = cgroup_context_add_socket_bind_item_deny_dup(dst, l); + if (r < 0) + return r; + } + + dst->tasks_max = src->tasks_max; + + return 0; +} + void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { assert(c); assert(a); @@ -306,10 +629,11 @@ void cgroup_context_done(CGroupContext *c) { static int unit_get_kernel_memory_limit(Unit *u, const char *file, uint64_t *ret) { assert(u); - if (!u->cgroup_realized) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -EOWNERDEAD; - return cg_get_attribute_as_uint64("memory", u->cgroup_path, file, ret); + return cg_get_attribute_as_uint64("memory", crt->cgroup_path, file, ret); } static int unit_compare_memory_limit(Unit *u, const char *property_name, uint64_t *ret_unit_value, uint64_t *ret_kernel_value) { @@ -425,11 +749,12 @@ static int unit_compare_memory_limit(Unit *u, const char *property_name, uint64_ #define FORMAT_CGROUP_DIFF_MAX 128 -static char *format_cgroup_memory_limit_comparison(char *buf, size_t l, Unit *u, const char *property_name) { +static char *format_cgroup_memory_limit_comparison(Unit *u, const char *property_name, char *buf, size_t l) { uint64_t kval, sval; int r; assert(u); + assert(property_name); assert(buf); assert(l > 0); @@ -499,18 +824,9 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { _cleanup_free_ char *disable_controllers_str = NULL, *delegate_controllers_str = NULL, *cpuset_cpus = NULL, *cpuset_mems = NULL, *startup_cpuset_cpus = NULL, *startup_cpuset_mems = NULL; CGroupContext *c; struct in_addr_prefix *iaai; - - char cda[FORMAT_CGROUP_DIFF_MAX]; - char cdb[FORMAT_CGROUP_DIFF_MAX]; - char cdc[FORMAT_CGROUP_DIFF_MAX]; - char cdd[FORMAT_CGROUP_DIFF_MAX]; - char cde[FORMAT_CGROUP_DIFF_MAX]; - char cdf[FORMAT_CGROUP_DIFF_MAX]; - char cdg[FORMAT_CGROUP_DIFF_MAX]; - char cdh[FORMAT_CGROUP_DIFF_MAX]; - char cdi[FORMAT_CGROUP_DIFF_MAX]; - char cdj[FORMAT_CGROUP_DIFF_MAX]; - char cdk[FORMAT_CGROUP_DIFF_MAX]; + char cda[FORMAT_CGROUP_DIFF_MAX], cdb[FORMAT_CGROUP_DIFF_MAX], cdc[FORMAT_CGROUP_DIFF_MAX], cdd[FORMAT_CGROUP_DIFF_MAX], + cde[FORMAT_CGROUP_DIFF_MAX], cdf[FORMAT_CGROUP_DIFF_MAX], cdg[FORMAT_CGROUP_DIFF_MAX], cdh[FORMAT_CGROUP_DIFF_MAX], + cdi[FORMAT_CGROUP_DIFF_MAX], cdj[FORMAT_CGROUP_DIFF_MAX], cdk[FORMAT_CGROUP_DIFF_MAX]; assert(u); assert(f); @@ -564,6 +880,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { "%sStartupMemorySwapMax: %" PRIu64 "%s\n" "%sMemoryZSwapMax: %" PRIu64 "%s\n" "%sStartupMemoryZSwapMax: %" PRIu64 "%s\n" + "%sMemoryZSwapWriteback: %s\n" "%sMemoryLimit: %" PRIu64 "\n" "%sTasksMax: %" PRIu64 "\n" "%sDevicePolicy: %s\n" @@ -597,17 +914,18 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) { prefix, c->startup_blockio_weight, prefix, c->default_memory_min, prefix, c->default_memory_low, - prefix, c->memory_min, format_cgroup_memory_limit_comparison(cda, sizeof(cda), u, "MemoryMin"), - prefix, c->memory_low, format_cgroup_memory_limit_comparison(cdb, sizeof(cdb), u, "MemoryLow"), - prefix, c->startup_memory_low, format_cgroup_memory_limit_comparison(cdc, sizeof(cdc), u, "StartupMemoryLow"), - prefix, c->memory_high, format_cgroup_memory_limit_comparison(cdd, sizeof(cdd), u, "MemoryHigh"), - prefix, c->startup_memory_high, format_cgroup_memory_limit_comparison(cde, sizeof(cde), u, "StartupMemoryHigh"), - prefix, c->memory_max, format_cgroup_memory_limit_comparison(cdf, sizeof(cdf), u, "MemoryMax"), - prefix, c->startup_memory_max, format_cgroup_memory_limit_comparison(cdg, sizeof(cdg), u, "StartupMemoryMax"), - prefix, c->memory_swap_max, format_cgroup_memory_limit_comparison(cdh, sizeof(cdh), u, "MemorySwapMax"), - prefix, c->startup_memory_swap_max, format_cgroup_memory_limit_comparison(cdi, sizeof(cdi), u, "StartupMemorySwapMax"), - prefix, c->memory_zswap_max, format_cgroup_memory_limit_comparison(cdj, sizeof(cdj), u, "MemoryZSwapMax"), - prefix, c->startup_memory_zswap_max, format_cgroup_memory_limit_comparison(cdk, sizeof(cdk), u, "StartupMemoryZSwapMax"), + prefix, c->memory_min, format_cgroup_memory_limit_comparison(u, "MemoryMin", cda, sizeof(cda)), + prefix, c->memory_low, format_cgroup_memory_limit_comparison(u, "MemoryLow", cdb, sizeof(cdb)), + prefix, c->startup_memory_low, format_cgroup_memory_limit_comparison(u, "StartupMemoryLow", cdc, sizeof(cdc)), + prefix, c->memory_high, format_cgroup_memory_limit_comparison(u, "MemoryHigh", cdd, sizeof(cdd)), + prefix, c->startup_memory_high, format_cgroup_memory_limit_comparison(u, "StartupMemoryHigh", cde, sizeof(cde)), + prefix, c->memory_max, format_cgroup_memory_limit_comparison(u, "MemoryMax", cdf, sizeof(cdf)), + prefix, c->startup_memory_max, format_cgroup_memory_limit_comparison(u, "StartupMemoryMax", cdg, sizeof(cdg)), + prefix, c->memory_swap_max, format_cgroup_memory_limit_comparison(u, "MemorySwapMax", cdh, sizeof(cdh)), + prefix, c->startup_memory_swap_max, format_cgroup_memory_limit_comparison(u, "StartupMemorySwapMax", cdi, sizeof(cdi)), + prefix, c->memory_zswap_max, format_cgroup_memory_limit_comparison(u, "MemoryZSwapMax", cdj, sizeof(cdj)), + prefix, c->startup_memory_zswap_max, format_cgroup_memory_limit_comparison(u, "StartupMemoryZSwapMax", cdk, sizeof(cdk)), + prefix, yes_no(c->memory_zswap_writeback), prefix, c->memory_limit, prefix, cgroup_tasks_max_resolve(&c->tasks_max), prefix, cgroup_device_policy_to_string(c->device_policy), @@ -811,7 +1129,7 @@ int cgroup_context_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_typ assert(bpffs_path); if (!path_is_normalized(bpffs_path) || !path_is_absolute(bpffs_path)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not normalized: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not normalized."); d = strdup(bpffs_path); if (!d) @@ -867,12 +1185,13 @@ static void unit_set_xattr_graceful(Unit *u, const char *name, const void *data, assert(u); assert(name); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return; - r = cg_set_xattr(u->cgroup_path, name, data, size, 0); + r = cg_set_xattr(crt->cgroup_path, name, data, size, 0); if (r < 0) - log_unit_debug_errno(u, r, "Failed to set '%s' xattr on control group %s, ignoring: %m", name, empty_to_root(u->cgroup_path)); + log_unit_debug_errno(u, r, "Failed to set '%s' xattr on control group %s, ignoring: %m", name, empty_to_root(crt->cgroup_path)); } static void unit_remove_xattr_graceful(Unit *u, const char *name) { @@ -881,12 +1200,13 @@ static void unit_remove_xattr_graceful(Unit *u, const char *name) { assert(u); assert(name); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return; - r = cg_remove_xattr(u->cgroup_path, name); + r = cg_remove_xattr(crt->cgroup_path, name); if (r < 0 && !ERRNO_IS_XATTR_ABSENT(r)) - log_unit_debug_errno(u, r, "Failed to remove '%s' xattr flag on control group %s, ignoring: %m", name, empty_to_root(u->cgroup_path)); + log_unit_debug_errno(u, r, "Failed to remove '%s' xattr flag on control group %s, ignoring: %m", name, empty_to_root(crt->cgroup_path)); } static void cgroup_oomd_xattr_apply(Unit *u) { @@ -1013,9 +1333,13 @@ static void cgroup_survive_xattr_apply(Unit *u) { assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + if (u->survive_final_kill_signal) { r = cg_set_xattr( - u->cgroup_path, + crt->cgroup_path, "user.survive_final_kill_signal", "1", 1, @@ -1023,7 +1347,7 @@ static void cgroup_survive_xattr_apply(Unit *u) { /* user xattr support was added in kernel v5.7 */ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) r = cg_set_xattr( - u->cgroup_path, + crt->cgroup_path, "trusted.survive_final_kill_signal", "1", 1, @@ -1033,7 +1357,7 @@ static void cgroup_survive_xattr_apply(Unit *u) { r, "Failed to set 'survive_final_kill_signal' xattr on control " "group %s, ignoring: %m", - empty_to_root(u->cgroup_path)); + empty_to_root(crt->cgroup_path)); } else { unit_remove_xattr_graceful(u, "user.survive_final_kill_signal"); unit_remove_xattr_graceful(u, "trusted.survive_final_kill_signal"); @@ -1170,6 +1494,12 @@ usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, static usec_t cgroup_cpu_adjust_period_and_log(Unit *u, usec_t period, usec_t quota) { usec_t new_period; + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return USEC_INFINITY; + if (quota == USEC_INFINITY) /* Always use default period for infinity quota. */ return CGROUP_CPU_QUOTA_DEFAULT_PERIOD_USEC; @@ -1182,10 +1512,10 @@ static usec_t cgroup_cpu_adjust_period_and_log(Unit *u, usec_t period, usec_t qu new_period = cgroup_cpu_adjust_period(period, quota, USEC_PER_MSEC, USEC_PER_SEC); if (new_period != period) { - log_unit_full(u, u->warned_clamping_cpu_quota_period ? LOG_DEBUG : LOG_WARNING, + log_unit_full(u, crt->warned_clamping_cpu_quota_period ? LOG_DEBUG : LOG_WARNING, "Clamping CPU interval for cpu.max: period is now %s", FORMAT_TIMESPAN(new_period, 1)); - u->warned_clamping_cpu_quota_period = true; + crt->warned_clamping_cpu_quota_period = true; } return new_period; @@ -1205,17 +1535,25 @@ static void cgroup_apply_unified_cpu_idle(Unit *u, uint64_t weight) { bool is_idle; const char *idle_val; + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + is_idle = weight == CGROUP_WEIGHT_IDLE; idle_val = one_zero(is_idle); - r = cg_set_attribute("cpu", u->cgroup_path, "cpu.idle", idle_val); + r = cg_set_attribute("cpu", crt->cgroup_path, "cpu.idle", idle_val); if (r < 0 && (r != -ENOENT || is_idle)) log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to set '%s' attribute on '%s' to '%s': %m", - "cpu.idle", empty_to_root(u->cgroup_path), idle_val); + "cpu.idle", empty_to_root(crt->cgroup_path), idle_val); } static void cgroup_apply_unified_cpu_quota(Unit *u, usec_t quota, usec_t period) { char buf[(DECIMAL_STR_MAX(usec_t) + 1) * 2 + 1]; + assert(u); + period = cgroup_cpu_adjust_period_and_log(u, period, quota); if (quota != USEC_INFINITY) xsprintf(buf, USEC_FMT " " USEC_FMT "\n", @@ -1331,6 +1669,12 @@ static int set_bfq_weight(Unit *u, const char *controller, dev_t dev, uint64_t i uint64_t bfq_weight; int r; + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -EOWNERDEAD; + /* FIXME: drop this function when distro kernels properly support BFQ through "io.weight" * See also: https://github.com/systemd/systemd/pull/13335 and * https://github.com/torvalds/linux/commit/65752aef0a407e1ef17ec78a7fc31ba4e0b360f9. */ @@ -1343,7 +1687,7 @@ static int set_bfq_weight(Unit *u, const char *controller, dev_t dev, uint64_t i else xsprintf(buf, "%" PRIu64 "\n", bfq_weight); - r = cg_set_attribute(controller, u->cgroup_path, p, buf); + r = cg_set_attribute(controller, crt->cgroup_path, p, buf); /* FIXME: drop this when kernels prior * 795fe54c2a82 ("bfq: Add per-device weight") v5.4 @@ -1367,13 +1711,19 @@ static void cgroup_apply_io_device_weight(Unit *u, const char *dev_path, uint64_ dev_t dev; int r, r1, r2; + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + if (lookup_block_device(dev_path, &dev) < 0) return; r1 = set_bfq_weight(u, "io", dev, io_weight); xsprintf(buf, DEVNUM_FORMAT_STR " %" PRIu64 "\n", DEVNUM_FORMAT_VAL(dev), io_weight); - r2 = cg_set_attribute("io", u->cgroup_path, "io.weight", buf); + r2 = cg_set_attribute("io", crt->cgroup_path, "io.weight", buf); /* Look at the configured device, when both fail, prefer io.weight errno. */ r = r2 == -EOPNOTSUPP ? r1 : r2; @@ -1381,7 +1731,7 @@ static void cgroup_apply_io_device_weight(Unit *u, const char *dev_path, uint64_ if (r < 0) log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to set 'io[.bfq].weight' attribute on '%s' to '%.*s': %m", - empty_to_root(u->cgroup_path), (int) strcspn(buf, NEWLINE), buf); + empty_to_root(crt->cgroup_path), (int) strcspn(buf, NEWLINE), buf); } static void cgroup_apply_blkio_device_weight(Unit *u, const char *dev_path, uint64_t blkio_weight) { @@ -1498,7 +1848,8 @@ void unit_modify_nft_set(Unit *u, bool add) { if (cg_all_unified() <= 0) return; - if (u->cgroup_id == 0) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || crt->cgroup_id == 0) return; if (!u->manager->fw_ctx) { @@ -1515,15 +1866,15 @@ void unit_modify_nft_set(Unit *u, bool add) { if (nft_set->source != NFT_SET_SOURCE_CGROUP) continue; - uint64_t element = u->cgroup_id; + uint64_t element = crt->cgroup_id; r = nft_set_element_modify_any(u->manager->fw_ctx, add, nft_set->nfproto, nft_set->table, nft_set->set, &element, sizeof(element)); if (r < 0) log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, cgroup %" PRIu64 ", ignoring: %m", - add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, u->cgroup_id); + add? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, crt->cgroup_id); else log_debug("%s NFT set: family %s, table %s, set %s, cgroup %" PRIu64, - add? "Added" : "Deleted", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, u->cgroup_id); + add? "Added" : "Deleted", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, crt->cgroup_id); } } @@ -1536,18 +1887,20 @@ static void cgroup_apply_socket_bind(Unit *u) { static void cgroup_apply_restrict_network_interfaces(Unit *u) { assert(u); - (void) restrict_network_interfaces_install(u); + (void) bpf_restrict_ifaces_install(u); } static int cgroup_apply_devices(Unit *u) { _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; - const char *path; CGroupContext *c; CGroupDevicePolicy policy; int r; assert_se(c = unit_get_cgroup_context(u)); - assert_se(path = u->cgroup_path); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -EOWNERDEAD; policy = c->device_policy; @@ -1561,9 +1914,9 @@ static int cgroup_apply_devices(Unit *u) { * EINVAL here. */ if (c->device_allow || policy != CGROUP_DEVICE_POLICY_AUTO) - r = cg_set_attribute("devices", path, "devices.deny", "a"); + r = cg_set_attribute("devices", crt->cgroup_path, "devices.deny", "a"); else - r = cg_set_attribute("devices", path, "devices.allow", "a"); + r = cg_set_attribute("devices", crt->cgroup_path, "devices.allow", "a"); if (r < 0) log_unit_full_errno(u, IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r, "Failed to reset devices.allow/devices.deny: %m"); @@ -1571,10 +1924,14 @@ static int cgroup_apply_devices(Unit *u) { bool allow_list_static = policy == CGROUP_DEVICE_POLICY_CLOSED || (policy == CGROUP_DEVICE_POLICY_AUTO && c->device_allow); - if (allow_list_static) - (void) bpf_devices_allow_list_static(prog, path); - bool any = allow_list_static; + bool any = false; + if (allow_list_static) { + r = bpf_devices_allow_list_static(prog, crt->cgroup_path); + if (r > 0) + any = true; + } + LIST_FOREACH(device_allow, a, c->device_allow) { const char *val; @@ -1582,22 +1939,22 @@ static int cgroup_apply_devices(Unit *u) { continue; if (path_startswith(a->path, "/dev/")) - r = bpf_devices_allow_list_device(prog, path, a->path, a->permissions); + r = bpf_devices_allow_list_device(prog, crt->cgroup_path, a->path, a->permissions); else if ((val = startswith(a->path, "block-"))) - r = bpf_devices_allow_list_major(prog, path, val, 'b', a->permissions); + r = bpf_devices_allow_list_major(prog, crt->cgroup_path, val, 'b', a->permissions); else if ((val = startswith(a->path, "char-"))) - r = bpf_devices_allow_list_major(prog, path, val, 'c', a->permissions); + r = bpf_devices_allow_list_major(prog, crt->cgroup_path, val, 'c', a->permissions); else { log_unit_debug(u, "Ignoring device '%s' while writing cgroup attribute.", a->path); continue; } - if (r >= 0) + if (r > 0) any = true; } if (prog && !any) { - log_unit_warning_errno(u, SYNTHETIC_ERRNO(ENODEV), "No devices matched by device filter."); + log_unit_warning(u, "No devices matched by device filter."); /* The kernel verifier would reject a program we would build with the normal intro and outro but no allow-listing rules (outro would contain an unreachable instruction for successful @@ -1605,7 +1962,7 @@ static int cgroup_apply_devices(Unit *u) { policy = CGROUP_DEVICE_POLICY_STRICT; } - r = bpf_devices_apply_policy(&prog, policy, any, path, &u->bpf_device_control_installed); + r = bpf_devices_apply_policy(&prog, policy, any, crt->cgroup_path, &crt->bpf_device_control_installed); if (r < 0) { static bool warned = false; @@ -1652,9 +2009,9 @@ static void cgroup_context_apply( CGroupMask apply_mask, ManagerState state) { + bool is_host_root, is_local_root; const char *path; CGroupContext *c; - bool is_host_root, is_local_root; int r; assert(u); @@ -1669,7 +2026,12 @@ static void cgroup_context_apply( is_host_root = unit_has_host_root_cgroup(u); assert_se(c = unit_get_cgroup_context(u)); - assert_se(path = u->cgroup_path); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + + path = crt->cgroup_path; if (is_local_root) /* Make sure we don't try to display messages with an empty path. */ path = "/"; @@ -1879,6 +2241,7 @@ static void cgroup_context_apply( cgroup_apply_unified_memory_limit(u, "memory.zswap.max", zswap_max); (void) set_attribute_and_warn(u, "memory", "memory.oom.group", one_zero(c->memory_oom_group)); + (void) set_attribute_and_warn(u, "memory", "memory.zswap.writeback", one_zero(c->memory_zswap_writeback)); } else { char buf[DECIMAL_STR_MAX(uint64_t) + 1]; @@ -2137,20 +2500,24 @@ CGroupMask unit_get_members_mask(Unit *u) { /* Returns the mask of controllers all of the unit's children require, merged */ - if (u->cgroup_members_mask_valid) - return u->cgroup_members_mask; /* Use cached value if possible */ - - u->cgroup_members_mask = 0; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt && crt->cgroup_members_mask_valid) + return crt->cgroup_members_mask; /* Use cached value if possible */ + CGroupMask m = 0; if (u->type == UNIT_SLICE) { Unit *member; UNIT_FOREACH_DEPENDENCY(member, u, UNIT_ATOM_SLICE_OF) - u->cgroup_members_mask |= unit_get_subtree_mask(member); /* note that this calls ourselves again, for the children */ + m |= unit_get_subtree_mask(member); /* note that this calls ourselves again, for the children */ + } + + if (crt) { + crt->cgroup_members_mask = m; + crt->cgroup_members_mask_valid = true; } - u->cgroup_members_mask_valid = true; - return u->cgroup_members_mask; + return m; } CGroupMask unit_get_siblings_mask(Unit *u) { @@ -2236,8 +2603,12 @@ void unit_invalidate_cgroup_members_masks(Unit *u) { assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + /* Recurse invalidate the member masks cache all the way up the tree */ - u->cgroup_members_mask_valid = false; + crt->cgroup_members_mask_valid = false; slice = UNIT_GET_SLICE(u); if (slice) @@ -2249,11 +2620,12 @@ const char *unit_get_realized_cgroup_path(Unit *u, CGroupMask mask) { /* Returns the realized cgroup path of the specified unit where all specified controllers are available. */ while (u) { - - if (u->cgroup_path && - u->cgroup_realized && - FLAGS_SET(u->cgroup_realized_mask, mask)) - return u->cgroup_path; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt && + crt->cgroup_path && + crt->cgroup_realized && + FLAGS_SET(crt->cgroup_realized_mask, mask)) + return crt->cgroup_path; u = UNIT_GET_SLICE(u); } @@ -2303,27 +2675,34 @@ int unit_default_cgroup_path(const Unit *u, char **ret) { int unit_set_cgroup_path(Unit *u, const char *path) { _cleanup_free_ char *p = NULL; + CGroupRuntime *crt; int r; assert(u); - if (streq_ptr(u->cgroup_path, path)) + crt = unit_get_cgroup_runtime(u); + + if (crt && streq_ptr(crt->cgroup_path, path)) return 0; + unit_release_cgroup(u); + + crt = unit_setup_cgroup_runtime(u); + if (!crt) + return -ENOMEM; + if (path) { p = strdup(path); if (!p) return -ENOMEM; - } - if (p) { r = hashmap_put(u->manager->cgroup_unit, p, u); if (r < 0) return r; } - unit_release_cgroup(u); - u->cgroup_path = TAKE_PTR(p); + assert(!crt->cgroup_path); + crt->cgroup_path = TAKE_PTR(p); return 1; } @@ -2337,10 +2716,11 @@ int unit_watch_cgroup(Unit *u) { /* Watches the "cgroups.events" attribute of this unit's cgroup for "empty" events, but only if * cgroupv2 is available. */ - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; - if (u->cgroup_control_inotify_wd >= 0) + if (crt->cgroup_control_inotify_wd >= 0) return 0; /* Only applies to the unified hierarchy */ @@ -2358,30 +2738,29 @@ int unit_watch_cgroup(Unit *u) { if (r < 0) return log_oom(); - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", &events); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, "cgroup.events", &events); if (r < 0) return log_oom(); - u->cgroup_control_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); - if (u->cgroup_control_inotify_wd < 0) { + crt->cgroup_control_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); + if (crt->cgroup_control_inotify_wd < 0) { if (errno == ENOENT) /* If the directory is already gone we don't need to track it, so this * is not an error */ return 0; - return log_unit_error_errno(u, errno, "Failed to add control inotify watch descriptor for control group %s: %m", empty_to_root(u->cgroup_path)); + return log_unit_error_errno(u, errno, "Failed to add control inotify watch descriptor for control group %s: %m", empty_to_root(crt->cgroup_path)); } - r = hashmap_put(u->manager->cgroup_control_inotify_wd_unit, INT_TO_PTR(u->cgroup_control_inotify_wd), u); + r = hashmap_put(u->manager->cgroup_control_inotify_wd_unit, INT_TO_PTR(crt->cgroup_control_inotify_wd), u); if (r < 0) - return log_unit_error_errno(u, r, "Failed to add control inotify watch descriptor for control group %s to hash map: %m", empty_to_root(u->cgroup_path)); + return log_unit_error_errno(u, r, "Failed to add control inotify watch descriptor for control group %s to hash map: %m", empty_to_root(crt->cgroup_path)); return 0; } int unit_watch_cgroup_memory(Unit *u) { _cleanup_free_ char *events = NULL; - CGroupContext *c; int r; assert(u); @@ -2389,10 +2768,11 @@ int unit_watch_cgroup_memory(Unit *u) { /* Watches the "memory.events" attribute of this unit's cgroup for "oom_kill" events, but only if * cgroupv2 is available. */ - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; - c = unit_get_cgroup_context(u); + CGroupContext *c = unit_get_cgroup_context(u); if (!c) return 0; @@ -2407,7 +2787,7 @@ int unit_watch_cgroup_memory(Unit *u) { if (u->type == UNIT_SLICE) return 0; - if (u->cgroup_memory_inotify_wd >= 0) + if (crt->cgroup_memory_inotify_wd >= 0) return 0; /* Only applies to the unified hierarchy */ @@ -2421,23 +2801,23 @@ int unit_watch_cgroup_memory(Unit *u) { if (r < 0) return log_oom(); - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "memory.events", &events); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, "memory.events", &events); if (r < 0) return log_oom(); - u->cgroup_memory_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); - if (u->cgroup_memory_inotify_wd < 0) { + crt->cgroup_memory_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, events, IN_MODIFY); + if (crt->cgroup_memory_inotify_wd < 0) { if (errno == ENOENT) /* If the directory is already gone we don't need to track it, so this * is not an error */ return 0; - return log_unit_error_errno(u, errno, "Failed to add memory inotify watch descriptor for control group %s: %m", empty_to_root(u->cgroup_path)); + return log_unit_error_errno(u, errno, "Failed to add memory inotify watch descriptor for control group %s: %m", empty_to_root(crt->cgroup_path)); } - r = hashmap_put(u->manager->cgroup_memory_inotify_wd_unit, INT_TO_PTR(u->cgroup_memory_inotify_wd), u); + r = hashmap_put(u->manager->cgroup_memory_inotify_wd_unit, INT_TO_PTR(crt->cgroup_memory_inotify_wd), u); if (r < 0) - return log_unit_error_errno(u, r, "Failed to add memory inotify watch descriptor for control group %s to hash map: %m", empty_to_root(u->cgroup_path)); + return log_unit_error_errno(u, r, "Failed to add memory inotify watch descriptor for control group %s to hash map: %m", empty_to_root(crt->cgroup_path)); return 0; } @@ -2448,12 +2828,15 @@ int unit_pick_cgroup_path(Unit *u) { assert(u); - if (u->cgroup_path) - return 0; - if (!UNIT_HAS_CGROUP_CONTEXT(u)) return -EINVAL; + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + return -ENOMEM; + if (crt->cgroup_path) + return 0; + r = unit_default_cgroup_path(u, &path); if (r < 0) return log_unit_error_errno(u, r, "Failed to generate default cgroup path: %m"); @@ -2483,30 +2866,35 @@ static int unit_update_cgroup( if (!UNIT_HAS_CGROUP_CONTEXT(u)) return 0; + if (u->freezer_state != FREEZER_RUNNING) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EBUSY), "Cannot realize cgroup for frozen unit."); + /* Figure out our cgroup path */ r = unit_pick_cgroup_path(u); if (r < 0) return r; + CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + /* First, create our own group */ - r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, u->cgroup_path); + r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, crt->cgroup_path); if (r < 0) - return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", empty_to_root(u->cgroup_path)); + return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", empty_to_root(crt->cgroup_path)); created = r; if (cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0) { uint64_t cgroup_id = 0; - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_full_path); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, NULL, &cgroup_full_path); if (r == 0) { r = cg_path_get_cgroupid(cgroup_full_path, &cgroup_id); if (r < 0) log_unit_full_errno(u, ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, "Failed to get cgroup ID of cgroup %s, ignoring: %m", cgroup_full_path); } else - log_unit_warning_errno(u, r, "Failed to get full cgroup path on cgroup %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_warning_errno(u, r, "Failed to get full cgroup path on cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path)); - u->cgroup_id = cgroup_id; + crt->cgroup_id = cgroup_id; } /* Start watching it */ @@ -2515,23 +2903,23 @@ static int unit_update_cgroup( /* For v2 we preserve enabled controllers in delegated units, adjust others, * for v1 we figure out which controller hierarchies need migration. */ - if (created || !u->cgroup_realized || !unit_cgroup_delegate(u)) { + if (created || !crt->cgroup_realized || !unit_cgroup_delegate(u)) { CGroupMask result_mask = 0; /* Enable all controllers we need */ - r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path, &result_mask); + r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, crt->cgroup_path, &result_mask); if (r < 0) - log_unit_warning_errno(u, r, "Failed to enable/disable controllers on cgroup %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_warning_errno(u, r, "Failed to enable/disable controllers on cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path)); /* Remember what's actually enabled now */ - u->cgroup_enabled_mask = result_mask; + crt->cgroup_enabled_mask = result_mask; - migrate_mask = u->cgroup_realized_mask ^ target_mask; + migrate_mask = crt->cgroup_realized_mask ^ target_mask; } /* Keep track that this is now realized */ - u->cgroup_realized = true; - u->cgroup_realized_mask = target_mask; + crt->cgroup_realized = true; + crt->cgroup_realized_mask = target_mask; /* Migrate processes in controller hierarchies both downwards (enabling) and upwards (disabling). * @@ -2541,14 +2929,14 @@ static int unit_update_cgroup( * delegated units. */ if (cg_all_unified() == 0) { - r = cg_migrate_v1_controllers(u->manager->cgroup_supported, migrate_mask, u->cgroup_path, migrate_callback, u); + r = cg_migrate_v1_controllers(u->manager->cgroup_supported, migrate_mask, crt->cgroup_path, migrate_callback, u); if (r < 0) - log_unit_warning_errno(u, r, "Failed to migrate controller cgroups from %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_warning_errno(u, r, "Failed to migrate controller cgroups from %s, ignoring: %m", empty_to_root(crt->cgroup_path)); is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); - r = cg_trim_v1_controllers(u->manager->cgroup_supported, ~target_mask, u->cgroup_path, !is_root_slice); + r = cg_trim_v1_controllers(u->manager->cgroup_supported, ~target_mask, crt->cgroup_path, !is_root_slice); if (r < 0) - log_unit_warning_errno(u, r, "Failed to delete controller cgroups %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_warning_errno(u, r, "Failed to delete controller cgroups %s, ignoring: %m", empty_to_root(crt->cgroup_path)); } /* Set attributes */ @@ -2578,11 +2966,12 @@ static int unit_attach_pid_to_cgroup_via_bus(Unit *u, pid_t pid, const char *suf if (!u->manager->system_bus) return -EIO; - if (!u->cgroup_path) - return -EINVAL; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -EOWNERDEAD; /* Determine this unit's cgroup path relative to our cgroup root */ - pp = path_startswith(u->cgroup_path, u->manager->cgroup_root); + pp = path_startswith(crt->cgroup_path, u->manager->cgroup_root); if (!pp) return -EINVAL; @@ -2626,10 +3015,12 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { if (r < 0) return r; + CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + if (isempty(suffix_path)) - p = u->cgroup_path; + p = crt->cgroup_path; else { - joined = path_join(u->cgroup_path, suffix_path); + joined = path_join(crt->cgroup_path, suffix_path); if (!joined) return -ENOMEM; @@ -2701,7 +3092,7 @@ int unit_attach_pids_to_cgroup(Unit *u, Set *pids, const char *suffix_path) { continue; /* If this controller is delegated and realized, honour the caller's request for the cgroup suffix. */ - if (delegated_mask & u->cgroup_realized_mask & bit) { + if (delegated_mask & crt->cgroup_realized_mask & bit) { r = cg_attach(cgroup_controller_to_string(c), p, pid->pid); if (r >= 0) continue; /* Success! */ @@ -2734,6 +3125,10 @@ static bool unit_has_mask_realized( assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return false; + /* Returns true if this unit is fully realized. We check four things: * * 1. Whether the cgroup was created at all @@ -2749,10 +3144,10 @@ static bool unit_has_mask_realized( * enabled through cgroup.subtree_control, and since the BPF pseudo-controllers don't show up there, they * simply don't matter. */ - return u->cgroup_realized && - ((u->cgroup_realized_mask ^ target_mask) & CGROUP_MASK_V1) == 0 && - ((u->cgroup_enabled_mask ^ enable_mask) & CGROUP_MASK_V2) == 0 && - u->cgroup_invalidated_mask == 0; + return crt->cgroup_realized && + ((crt->cgroup_realized_mask ^ target_mask) & CGROUP_MASK_V1) == 0 && + ((crt->cgroup_enabled_mask ^ enable_mask) & CGROUP_MASK_V2) == 0 && + crt->cgroup_invalidated_mask == 0; } static bool unit_has_mask_disables_realized( @@ -2762,14 +3157,18 @@ static bool unit_has_mask_disables_realized( assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return true; + /* Returns true if all controllers which should be disabled are indeed disabled. * * Unlike unit_has_mask_realized, we don't care what was enabled, only that anything we want to remove is * already removed. */ - return !u->cgroup_realized || - (FLAGS_SET(u->cgroup_realized_mask, target_mask & CGROUP_MASK_V1) && - FLAGS_SET(u->cgroup_enabled_mask, enable_mask & CGROUP_MASK_V2)); + return !crt->cgroup_realized || + (FLAGS_SET(crt->cgroup_realized_mask, target_mask & CGROUP_MASK_V1) && + FLAGS_SET(crt->cgroup_enabled_mask, enable_mask & CGROUP_MASK_V2)); } static bool unit_has_mask_enables_realized( @@ -2779,14 +3178,18 @@ static bool unit_has_mask_enables_realized( assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return false; + /* Returns true if all controllers which should be enabled are indeed enabled. * * Unlike unit_has_mask_realized, we don't care about the controllers that are not present, only that anything * we want to add is already added. */ - return u->cgroup_realized && - ((u->cgroup_realized_mask | target_mask) & CGROUP_MASK_V1) == (u->cgroup_realized_mask & CGROUP_MASK_V1) && - ((u->cgroup_enabled_mask | enable_mask) & CGROUP_MASK_V2) == (u->cgroup_enabled_mask & CGROUP_MASK_V2); + return crt->cgroup_realized && + ((crt->cgroup_realized_mask | target_mask) & CGROUP_MASK_V1) == (crt->cgroup_realized_mask & CGROUP_MASK_V1) && + ((crt->cgroup_enabled_mask | enable_mask) & CGROUP_MASK_V2) == (crt->cgroup_enabled_mask & CGROUP_MASK_V2); } void unit_add_to_cgroup_realize_queue(Unit *u) { @@ -2835,8 +3238,10 @@ static int unit_realize_cgroup_now_enable(Unit *u, ManagerState state) { if (unit_has_mask_enables_realized(u, target_mask, enable_mask)) return 0; - new_target_mask = u->cgroup_realized_mask | target_mask; - new_enable_mask = u->cgroup_enabled_mask | enable_mask; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + + new_target_mask = (crt ? crt->cgroup_realized_mask : 0) | target_mask; + new_enable_mask = (crt ? crt->cgroup_enabled_mask : 0) | enable_mask; return unit_update_cgroup(u, new_target_mask, new_enable_mask, state); } @@ -2855,9 +3260,13 @@ static int unit_realize_cgroup_now_disable(Unit *u, ManagerState state) { CGroupMask target_mask, enable_mask, new_target_mask, new_enable_mask; int r; + CGroupRuntime *rt = unit_get_cgroup_runtime(m); + if (!rt) + continue; + /* The cgroup for this unit might not actually be fully realised yet, in which case it isn't * holding any controllers open anyway. */ - if (!m->cgroup_realized) + if (!rt->cgroup_realized) continue; /* We must disable those below us first in order to release the controller. */ @@ -2871,8 +3280,8 @@ static int unit_realize_cgroup_now_disable(Unit *u, ManagerState state) { if (unit_has_mask_disables_realized(m, target_mask, enable_mask)) continue; - new_target_mask = m->cgroup_realized_mask & target_mask; - new_enable_mask = m->cgroup_enabled_mask & enable_mask; + new_target_mask = rt->cgroup_realized_mask & target_mask; + new_enable_mask = rt->cgroup_enabled_mask & enable_mask; r = unit_update_cgroup(m, new_target_mask, new_enable_mask, state); if (r < 0) @@ -2959,8 +3368,10 @@ static int unit_realize_cgroup_now(Unit *u, ManagerState state) { if (r < 0) return r; + CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + /* Now, reset the invalidation mask */ - u->cgroup_invalidated_mask = 0; + crt->cgroup_invalidated_mask = 0; return 0; } @@ -3011,11 +3422,13 @@ void unit_add_family_to_cgroup_realize_queue(Unit *u) { * masks. */ do { - Unit *m; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); /* Children of u likely changed when we're called */ - u->cgroup_members_mask_valid = false; + if (crt) + crt->cgroup_members_mask_valid = false; + Unit *m; UNIT_FOREACH_DEPENDENCY(m, u, UNIT_ATOM_SLICE_OF) { /* No point in doing cgroup application for units without active processes. */ @@ -3024,7 +3437,8 @@ void unit_add_family_to_cgroup_realize_queue(Unit *u) { /* We only enqueue siblings if they were realized once at least, in the main * hierarchy. */ - if (!m->cgroup_realized) + crt = unit_get_cgroup_runtime(m); + if (!crt || !crt->cgroup_realized) continue; /* If the unit doesn't need any new controllers and has current ones @@ -3075,26 +3489,50 @@ void unit_release_cgroup(Unit *u) { /* Forgets all cgroup details for this cgroup — but does *not* destroy the cgroup. This is hence OK to call * when we close down everything for reexecution, where we really want to leave the cgroup in place. */ - if (u->cgroup_path) { - (void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); - u->cgroup_path = mfree(u->cgroup_path); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + + if (crt->cgroup_path) { + (void) hashmap_remove(u->manager->cgroup_unit, crt->cgroup_path); + crt->cgroup_path = mfree(crt->cgroup_path); } - if (u->cgroup_control_inotify_wd >= 0) { - if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_control_inotify_wd) < 0) - log_unit_debug_errno(u, errno, "Failed to remove cgroup control inotify watch %i for %s, ignoring: %m", u->cgroup_control_inotify_wd, u->id); + if (crt->cgroup_control_inotify_wd >= 0) { + if (inotify_rm_watch(u->manager->cgroup_inotify_fd, crt->cgroup_control_inotify_wd) < 0) + log_unit_debug_errno(u, errno, "Failed to remove cgroup control inotify watch %i for %s, ignoring: %m", crt->cgroup_control_inotify_wd, u->id); - (void) hashmap_remove(u->manager->cgroup_control_inotify_wd_unit, INT_TO_PTR(u->cgroup_control_inotify_wd)); - u->cgroup_control_inotify_wd = -1; + (void) hashmap_remove(u->manager->cgroup_control_inotify_wd_unit, INT_TO_PTR(crt->cgroup_control_inotify_wd)); + crt->cgroup_control_inotify_wd = -1; } - if (u->cgroup_memory_inotify_wd >= 0) { - if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_memory_inotify_wd) < 0) - log_unit_debug_errno(u, errno, "Failed to remove cgroup memory inotify watch %i for %s, ignoring: %m", u->cgroup_memory_inotify_wd, u->id); + if (crt->cgroup_memory_inotify_wd >= 0) { + if (inotify_rm_watch(u->manager->cgroup_inotify_fd, crt->cgroup_memory_inotify_wd) < 0) + log_unit_debug_errno(u, errno, "Failed to remove cgroup memory inotify watch %i for %s, ignoring: %m", crt->cgroup_memory_inotify_wd, u->id); - (void) hashmap_remove(u->manager->cgroup_memory_inotify_wd_unit, INT_TO_PTR(u->cgroup_memory_inotify_wd)); - u->cgroup_memory_inotify_wd = -1; + (void) hashmap_remove(u->manager->cgroup_memory_inotify_wd_unit, INT_TO_PTR(crt->cgroup_memory_inotify_wd)); + crt->cgroup_memory_inotify_wd = -1; } + + *(CGroupRuntime**) ((uint8_t*) u + UNIT_VTABLE(u)->cgroup_runtime_offset) = cgroup_runtime_free(crt); +} + +int unit_cgroup_is_empty(Unit *u) { + int r; + + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return -ENXIO; + if (!crt->cgroup_path) + return -EOWNERDEAD; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path); + if (r < 0) + return log_unit_debug_errno(u, r, "Failed to determine whether cgroup %s is empty, ignoring: %m", empty_to_root(crt->cgroup_path)); + + return r; } bool unit_maybe_release_cgroup(Unit *u) { @@ -3102,17 +3540,16 @@ bool unit_maybe_release_cgroup(Unit *u) { assert(u); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return true; - /* Don't release the cgroup if there are still processes under it. If we get notified later when all the - * processes exit (e.g. the processes were in D-state and exited after the unit was marked as failed) - * we need the cgroup paths to continue to be tracked by the manager so they can be looked up and cleaned - * up later. */ - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r < 0) - log_unit_debug_errno(u, r, "Error checking if the cgroup is recursively empty, ignoring: %m"); - else if (r == 1) { + /* Don't release the cgroup if there are still processes under it. If we get notified later when all + * the processes exit (e.g. the processes were in D-state and exited after the unit was marked as + * failed) we need the cgroup paths to continue to be tracked by the manager so they can be looked up + * and cleaned up later. */ + r = unit_cgroup_is_empty(u); + if (r == 1) { unit_release_cgroup(u); return true; } @@ -3127,28 +3564,32 @@ void unit_prune_cgroup(Unit *u) { assert(u); /* Removes the cgroup, if empty and possible, and stops watching it. */ - - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return; - (void) unit_get_cpu_usage(u, NULL); /* Cache the last CPU usage value before we destroy the cgroup */ + /* Cache the last CPU and memory usage values before we destroy the cgroup */ + (void) unit_get_cpu_usage(u, /* ret = */ NULL); + + for (CGroupMemoryAccountingMetric metric = 0; metric <= _CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST; metric++) + (void) unit_get_memory_accounting(u, metric, /* ret = */ NULL); #if BPF_FRAMEWORK - (void) lsm_bpf_cleanup(u); /* Remove cgroup from the global LSM BPF map */ + (void) bpf_restrict_fs_cleanup(u); /* Remove cgroup from the global LSM BPF map */ #endif unit_modify_nft_set(u, /* add = */ false); is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); - r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); + r = cg_trim_everywhere(u->manager->cgroup_supported, crt->cgroup_path, !is_root_slice); if (r < 0) /* One reason we could have failed here is, that the cgroup still contains a process. * However, if the cgroup becomes removable at a later time, it might be removed when * the containing slice is stopped. So even if we failed now, this unit shouldn't assume * that the cgroup is still realized the next time it is started. Do not return early * on error, continue cleanup. */ - log_unit_full_errno(u, r == -EBUSY ? LOG_DEBUG : LOG_WARNING, r, "Failed to destroy cgroup %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_full_errno(u, r == -EBUSY ? LOG_DEBUG : LOG_WARNING, r, "Failed to destroy cgroup %s, ignoring: %m", empty_to_root(crt->cgroup_path)); if (is_root_slice) return; @@ -3156,11 +3597,15 @@ void unit_prune_cgroup(Unit *u) { if (!unit_maybe_release_cgroup(u)) /* Returns true if the cgroup was released */ return; - u->cgroup_realized = false; - u->cgroup_realized_mask = 0; - u->cgroup_enabled_mask = 0; + crt = unit_get_cgroup_runtime(u); /* The above might have destroyed the runtime object, let's see if it's still there */ + if (!crt) + return; + + crt->cgroup_realized = false; + crt->cgroup_realized_mask = 0; + crt->cgroup_enabled_mask = 0; - u->bpf_device_control_installed = bpf_program_free(u->bpf_device_control_installed); + crt->bpf_device_control_installed = bpf_program_free(crt->bpf_device_control_installed); } int unit_search_main_pid(Unit *u, PidRef *ret) { @@ -3171,17 +3616,20 @@ int unit_search_main_pid(Unit *u, PidRef *ret) { assert(u); assert(ret); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENXIO; - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f); + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, &f); if (r < 0) return r; for (;;) { _cleanup_(pidref_done) PidRef npidref = PIDREF_NULL; - r = cg_read_pidref(f, &npidref); + /* cg_read_pidref() will return an error on unmapped PIDs. + * We can't reasonably deal with units that contain those. */ + r = cg_read_pidref(f, &npidref, CGROUP_DONT_SKIP_UNMAPPED); if (r < 0) return r; if (r == 0) @@ -3223,7 +3671,7 @@ static int unit_watch_pids_in_path(Unit *u, const char *path) { for (;;) { _cleanup_(pidref_done) PidRef pid = PIDREF_NULL; - r = cg_read_pidref(f, &pid); + r = cg_read_pidref(f, &pid, /* flags = */ 0); if (r == 0) break; if (r < 0) { @@ -3270,7 +3718,8 @@ int unit_synthesize_cgroup_empty_event(Unit *u) { * support for non-unified systems where notifications aren't reliable, and hence need to take whatever we can * get as notification source as soon as we stopped having any useful PIDs to watch for. */ - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENOENT; r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); @@ -3296,7 +3745,8 @@ int unit_watch_all_pids(Unit *u) { * get reliable cgroup empty notifications: we try to use * SIGCHLD as replacement. */ - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENOENT; r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); @@ -3305,7 +3755,7 @@ int unit_watch_all_pids(Unit *u) { if (r > 0) /* On unified we can use proper notifications */ return 0; - return unit_watch_pids_in_path(u, u->cgroup_path); + return unit_watch_pids_in_path(u, crt->cgroup_path); } static int on_cgroup_empty_event(sd_event_source *s, void *userdata) { @@ -3370,15 +3820,8 @@ void unit_add_to_cgroup_empty_queue(Unit *u) { return; /* Let's verify that the cgroup is really empty */ - if (!u->cgroup_path) - return; - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r < 0) { - log_unit_debug_errno(u, r, "Failed to determine whether cgroup %s is empty: %m", empty_to_root(u->cgroup_path)); - return; - } - if (r == 0) + r = unit_cgroup_is_empty(u); + if (r <= 0) return; LIST_PREPEND(cgroup_empty_queue, u->manager->cgroup_empty_queue, u); @@ -3406,7 +3849,10 @@ int unit_check_oomd_kill(Unit *u) { uint64_t n = 0; int r; - if (!u->cgroup_path) + assert(u); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; r = cg_all_unified(); @@ -3415,7 +3861,7 @@ int unit_check_oomd_kill(Unit *u) { else if (r == 0) return 0; - r = cg_get_xattr_malloc(u->cgroup_path, "user.oomd_ooms", &value); + r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_ooms", &value); if (r < 0 && !ERRNO_IS_XATTR_ABSENT(r)) return r; @@ -3425,15 +3871,15 @@ int unit_check_oomd_kill(Unit *u) { return r; } - increased = n > u->managed_oom_kill_last; - u->managed_oom_kill_last = n; + increased = n > crt->managed_oom_kill_last; + crt->managed_oom_kill_last = n; if (!increased) return 0; n = 0; value = mfree(value); - r = cg_get_xattr_malloc(u->cgroup_path, "user.oomd_kill", &value); + r = cg_get_xattr_malloc(crt->cgroup_path, "user.oomd_kill", &value); if (r >= 0 && !isempty(value)) (void) safe_atou64(value, &n); @@ -3460,10 +3906,16 @@ int unit_check_oom(Unit *u) { uint64_t c; int r; - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; - r = cg_get_keyed_attribute("memory", u->cgroup_path, "memory.events", STRV_MAKE("oom_kill"), &oom_kill); + r = cg_get_keyed_attribute( + "memory", + crt->cgroup_path, + "memory.events", + STRV_MAKE("oom_kill"), + &oom_kill); if (IN_SET(r, -ENOENT, -ENXIO)) /* Handle gracefully if cgroup or oom_kill attribute don't exist */ c = 0; else if (r < 0) @@ -3474,8 +3926,8 @@ int unit_check_oom(Unit *u) { return log_unit_debug_errno(u, r, "Failed to parse oom_kill field: %m"); } - increased = c > u->oom_kill_last; - u->oom_kill_last = c; + increased = c > crt->oom_kill_last; + crt->oom_kill_last = c; if (!increased) return 0; @@ -3525,7 +3977,9 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) { if (u->in_cgroup_oom_queue) return; - if (!u->cgroup_path) + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return; LIST_PREPEND(cgroup_oom_queue, u->manager->cgroup_oom_queue, u); @@ -3541,7 +3995,7 @@ static void unit_add_to_cgroup_oom_queue(Unit *u) { return; } - r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_NORMAL-8); + r = sd_event_source_set_priority(s, EVENT_PRIORITY_CGROUP_OOM); if (r < 0) { log_error_errno(r, "Failed to set priority of cgroup oom event source: %m"); return; @@ -3562,11 +4016,16 @@ static int unit_check_cgroup_events(Unit *u) { assert(u); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; - r = cg_get_keyed_attribute_graceful(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", - STRV_MAKE("populated", "frozen"), values); + r = cg_get_keyed_attribute_graceful( + SYSTEMD_CGROUP_CONTROLLER, + crt->cgroup_path, + "cgroup.events", + STRV_MAKE("populated", "frozen"), + values); if (r < 0) return r; @@ -3580,8 +4039,10 @@ static int unit_check_cgroup_events(Unit *u) { unit_add_to_cgroup_empty_queue(u); } - /* Disregard freezer state changes due to operations not initiated by us */ - if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)) { + /* Disregard freezer state changes due to operations not initiated by us. + * See: https://github.com/systemd/systemd/pull/13512/files#r416469963 and + * https://github.com/systemd/systemd/pull/13512#issuecomment-573007207 */ + if (values[1] && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING)) { if (streq(values[1], "0")) unit_thawed(u); else @@ -3670,7 +4131,7 @@ static int cg_bpf_mask_supported(CGroupMask *ret) { mask |= CGROUP_MASK_BPF_SOCKET_BIND; /* BPF-based cgroup_skb/{egress|ingress} hooks */ - r = restrict_network_interfaces_supported(); + r = bpf_restrict_ifaces_supported(); if (r < 0) return r; if (r > 0) @@ -3747,7 +4208,7 @@ int manager_setup_cgroup(Manager *m) { /* Schedule cgroup empty checks early, but after having processed service notification messages or * SIGCHLD signals, so that a cgroup running empty is always just the last safety net of * notification, and we collected the metadata the notification and SIGCHLD stuff offers first. */ - r = sd_event_source_set_priority(m->cgroup_empty_event_source, SD_EVENT_PRIORITY_NORMAL-5); + r = sd_event_source_set_priority(m->cgroup_empty_event_source, EVENT_PRIORITY_CGROUP_EMPTY); if (r < 0) return log_error_errno(r, "Failed to set priority of cgroup empty event source: %m"); @@ -3776,7 +4237,7 @@ int manager_setup_cgroup(Manager *m) { /* Process cgroup empty notifications early. Note that when this event is dispatched it'll * just add the unit to a cgroup empty queue, hence let's run earlier than that. Also see * handling of cgroup agent notifications, for the classic cgroup hierarchy support. */ - r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_NORMAL-9); + r = sd_event_source_set_priority(m->cgroup_inotify_event_source, EVENT_PRIORITY_CGROUP_INOTIFY); if (r < 0) return log_error_errno(r, "Failed to set priority of inotify event source: %m"); @@ -3885,7 +4346,7 @@ Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { } } -Unit *manager_get_unit_by_pidref_cgroup(Manager *m, PidRef *pid) { +Unit *manager_get_unit_by_pidref_cgroup(Manager *m, const PidRef *pid) { _cleanup_free_ char *cgroup = NULL; assert(m); @@ -3896,7 +4357,7 @@ Unit *manager_get_unit_by_pidref_cgroup(Manager *m, PidRef *pid) { return manager_get_unit_by_cgroup(m, cgroup); } -Unit *manager_get_unit_by_pidref_watching(Manager *m, PidRef *pid) { +Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid) { Unit *u, **array; assert(m); @@ -3915,7 +4376,7 @@ Unit *manager_get_unit_by_pidref_watching(Manager *m, PidRef *pid) { return NULL; } -Unit *manager_get_unit_by_pidref(Manager *m, PidRef *pid) { +Unit *manager_get_unit_by_pidref(Manager *m, const PidRef *pid) { Unit *u; assert(m); @@ -3994,7 +4455,8 @@ int unit_get_memory_available(Unit *u, uint64_t *ret) { if (!unit_context) return -ENODATA; - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) continue; (void) unit_get_memory_current(u, ¤t); @@ -4026,21 +4488,22 @@ int unit_get_memory_current(Unit *u, uint64_t *ret) { if (!UNIT_CGROUP_BOOL(u, memory_accounting)) return -ENODATA; - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENODATA; /* The root cgroup doesn't expose this information, let's get it from /proc instead */ if (unit_has_host_root_cgroup(u)) return procfs_memory_get_used(ret); - if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) + if ((crt->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) return -ENODATA; r = cg_all_unified(); if (r < 0) return r; - return cg_get_attribute_as_uint64("memory", u->cgroup_path, r > 0 ? "memory.current" : "memory.usage_in_bytes", ret); + return cg_get_attribute_as_uint64("memory", crt->cgroup_path, r > 0 ? "memory.current" : "memory.usage_in_bytes", ret); } int unit_get_memory_accounting(Unit *u, CGroupMemoryAccountingMetric metric, uint64_t *ret) { @@ -4063,7 +4526,10 @@ int unit_get_memory_accounting(Unit *u, CGroupMemoryAccountingMetric metric, uin if (!UNIT_CGROUP_BOOL(u, memory_accounting)) return -ENODATA; - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return -ENODATA; + if (!crt->cgroup_path) /* If the cgroup is already gone, we try to find the last cached value. */ goto finish; @@ -4071,7 +4537,7 @@ int unit_get_memory_accounting(Unit *u, CGroupMemoryAccountingMetric metric, uin if (unit_has_host_root_cgroup(u)) return -ENODATA; - if (!FLAGS_SET(u->cgroup_realized_mask, CGROUP_MASK_MEMORY)) + if (!FLAGS_SET(crt->cgroup_realized_mask, CGROUP_MASK_MEMORY)) return -ENODATA; r = cg_all_unified(); @@ -4080,14 +4546,14 @@ int unit_get_memory_accounting(Unit *u, CGroupMemoryAccountingMetric metric, uin if (r == 0) return -ENODATA; - r = cg_get_attribute_as_uint64("memory", u->cgroup_path, attributes_table[metric], &bytes); + r = cg_get_attribute_as_uint64("memory", crt->cgroup_path, attributes_table[metric], &bytes); if (r < 0 && r != -ENODATA) return r; updated = r >= 0; finish: if (metric <= _CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST) { - uint64_t *last = &u->memory_accounting_last[metric]; + uint64_t *last = &crt->memory_accounting_last[metric]; if (updated) *last = bytes; @@ -4112,17 +4578,18 @@ int unit_get_tasks_current(Unit *u, uint64_t *ret) { if (!UNIT_CGROUP_BOOL(u, tasks_accounting)) return -ENODATA; - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENODATA; /* The root cgroup doesn't expose this information, let's get it from /proc instead */ if (unit_has_host_root_cgroup(u)) return procfs_tasks_get_current(ret); - if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) + if ((crt->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) return -ENODATA; - return cg_get_attribute_as_uint64("pids", u->cgroup_path, "pids.current", ret); + return cg_get_attribute_as_uint64("pids", crt->cgroup_path, "pids.current", ret); } static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { @@ -4132,7 +4599,8 @@ static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { assert(u); assert(ret); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENODATA; /* The root cgroup doesn't expose this information, let's get it from /proc instead */ @@ -4140,7 +4608,7 @@ static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { return procfs_cpu_get_usage(ret); /* Requisite controllers for CPU accounting are not enabled */ - if ((get_cpu_accounting_mask() & ~u->cgroup_realized_mask) != 0) + if ((get_cpu_accounting_mask() & ~crt->cgroup_realized_mask) != 0) return -ENODATA; r = cg_all_unified(); @@ -4150,7 +4618,7 @@ static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { _cleanup_free_ char *val = NULL; uint64_t us; - r = cg_get_keyed_attribute("cpu", u->cgroup_path, "cpu.stat", STRV_MAKE("usage_usec"), &val); + r = cg_get_keyed_attribute("cpu", crt->cgroup_path, "cpu.stat", STRV_MAKE("usage_usec"), &val); if (IN_SET(r, -ENOENT, -ENXIO)) return -ENODATA; if (r < 0) @@ -4162,7 +4630,7 @@ static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { ns = us * NSEC_PER_USEC; } else - return cg_get_attribute_as_uint64("cpuacct", u->cgroup_path, "cpuacct.usage", ret); + return cg_get_attribute_as_uint64("cpuacct", crt->cgroup_path, "cpuacct.usage", ret); *ret = ns; return 0; @@ -4178,27 +4646,31 @@ int unit_get_cpu_usage(Unit *u, nsec_t *ret) { * started. If the cgroup has been removed already, returns the last cached value. To cache the value, simply * call this function with a NULL return value. */ + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -ENODATA; + if (!UNIT_CGROUP_BOOL(u, cpu_accounting)) return -ENODATA; r = unit_get_cpu_usage_raw(u, &ns); - if (r == -ENODATA && u->cpu_usage_last != NSEC_INFINITY) { + if (r == -ENODATA && crt->cpu_usage_last != NSEC_INFINITY) { /* If we can't get the CPU usage anymore (because the cgroup was already removed, for example), use our * cached value. */ if (ret) - *ret = u->cpu_usage_last; + *ret = crt->cpu_usage_last; return 0; } if (r < 0) return r; - if (ns > u->cpu_usage_base) - ns -= u->cpu_usage_base; + if (ns > crt->cpu_usage_base) + ns -= crt->cpu_usage_base; else ns = 0; - u->cpu_usage_last = ns; + crt->cpu_usage_last = ns; if (ret) *ret = ns; @@ -4221,9 +4693,13 @@ int unit_get_ip_accounting( if (!UNIT_CGROUP_BOOL(u, ip_accounting)) return -ENODATA; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -ENODATA; + fd = IN_SET(metric, CGROUP_IP_INGRESS_BYTES, CGROUP_IP_INGRESS_PACKETS) ? - u->ip_accounting_ingress_map_fd : - u->ip_accounting_egress_map_fd; + crt->ip_accounting_ingress_map_fd : + crt->ip_accounting_egress_map_fd; if (fd < 0) return -ENODATA; @@ -4238,11 +4714,62 @@ int unit_get_ip_accounting( * all BPF programs and maps anew, but serialize the old counters. When deserializing we store them in the * ip_accounting_extra[] field, and add them in here transparently. */ - *ret = value + u->ip_accounting_extra[metric]; + *ret = value + crt->ip_accounting_extra[metric]; return r; } +static uint64_t unit_get_effective_limit_one(Unit *u, CGroupLimitType type) { + CGroupContext *cc; + + assert(u); + assert(UNIT_HAS_CGROUP_CONTEXT(u)); + + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + switch (type) { + case CGROUP_LIMIT_MEMORY_MAX: + case CGROUP_LIMIT_MEMORY_HIGH: + return physical_memory(); + case CGROUP_LIMIT_TASKS_MAX: + return system_tasks_max(); + default: + assert_not_reached(); + } + + cc = ASSERT_PTR(unit_get_cgroup_context(u)); + switch (type) { + /* Note: on legacy/hybrid hierarchies memory_max stays CGROUP_LIMIT_MAX unless configured + * explicitly. Effective value of MemoryLimit= (cgroup v1) is not implemented. */ + case CGROUP_LIMIT_MEMORY_MAX: + return cc->memory_max; + case CGROUP_LIMIT_MEMORY_HIGH: + return cc->memory_high; + case CGROUP_LIMIT_TASKS_MAX: + return cgroup_tasks_max_resolve(&cc->tasks_max); + default: + assert_not_reached(); + } +} + +int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret) { + uint64_t infimum; + + assert(u); + assert(ret); + assert(type >= 0); + assert(type < _CGROUP_LIMIT_TYPE_MAX); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return -EINVAL; + + infimum = unit_get_effective_limit_one(u, type); + for (Unit *slice = UNIT_GET_SLICE(u); slice; slice = UNIT_GET_SLICE(slice)) + infimum = MIN(infimum, unit_get_effective_limit_one(slice, type)); + + *ret = infimum; + return 0; +} + static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_ACCOUNTING_METRIC_MAX]) { static const char *const field_names[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { [CGROUP_IO_READ_BYTES] = "rbytes=", @@ -4257,7 +4784,8 @@ static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_AC assert(u); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENODATA; if (unit_has_host_root_cgroup(u)) @@ -4266,13 +4794,13 @@ static int unit_get_io_accounting_raw(Unit *u, uint64_t ret[static _CGROUP_IO_AC r = cg_all_unified(); if (r < 0) return r; - if (r == 0) /* TODO: support cgroupv1 */ + if (r == 0) return -ENODATA; - if (!FLAGS_SET(u->cgroup_realized_mask, CGROUP_MASK_IO)) + if (!FLAGS_SET(crt->cgroup_realized_mask, CGROUP_MASK_IO)) return -ENODATA; - r = cg_get_path("io", u->cgroup_path, "io.stat", &path); + r = cg_get_path("io", crt->cgroup_path, "io.stat", &path); if (r < 0) return r; @@ -4340,26 +4868,30 @@ int unit_get_io_accounting( if (!UNIT_CGROUP_BOOL(u, io_accounting)) return -ENODATA; - if (allow_cache && u->io_accounting_last[metric] != UINT64_MAX) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -ENODATA; + + if (allow_cache && crt->io_accounting_last[metric] != UINT64_MAX) goto done; r = unit_get_io_accounting_raw(u, raw); - if (r == -ENODATA && u->io_accounting_last[metric] != UINT64_MAX) + if (r == -ENODATA && crt->io_accounting_last[metric] != UINT64_MAX) goto done; if (r < 0) return r; for (CGroupIOAccountingMetric i = 0; i < _CGROUP_IO_ACCOUNTING_METRIC_MAX; i++) { /* Saturated subtraction */ - if (raw[i] > u->io_accounting_base[i]) - u->io_accounting_last[i] = raw[i] - u->io_accounting_base[i]; + if (raw[i] > crt->io_accounting_base[i]) + crt->io_accounting_last[i] = raw[i] - crt->io_accounting_base[i]; else - u->io_accounting_last[i] = 0; + crt->io_accounting_last[i] = 0; } done: if (ret) - *ret = u->io_accounting_last[metric]; + *ret = crt->io_accounting_last[metric]; return 0; } @@ -4369,11 +4901,15 @@ int unit_reset_cpu_accounting(Unit *u) { assert(u); - u->cpu_usage_last = NSEC_INFINITY; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return 0; + + crt->cpu_usage_last = NSEC_INFINITY; - r = unit_get_cpu_usage_raw(u, &u->cpu_usage_base); + r = unit_get_cpu_usage_raw(u, &crt->cpu_usage_base); if (r < 0) { - u->cpu_usage_base = 0; + crt->cpu_usage_base = 0; return r; } @@ -4383,7 +4919,11 @@ int unit_reset_cpu_accounting(Unit *u) { void unit_reset_memory_accounting_last(Unit *u) { assert(u); - FOREACH_ARRAY(i, u->memory_accounting_last, ELEMENTSOF(u->memory_accounting_last)) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + + FOREACH_ELEMENT(i, crt->memory_accounting_last) *i = UINT64_MAX; } @@ -4392,13 +4932,17 @@ int unit_reset_ip_accounting(Unit *u) { assert(u); - if (u->ip_accounting_ingress_map_fd >= 0) - RET_GATHER(r, bpf_firewall_reset_accounting(u->ip_accounting_ingress_map_fd)); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return 0; + + if (crt->ip_accounting_ingress_map_fd >= 0) + RET_GATHER(r, bpf_firewall_reset_accounting(crt->ip_accounting_ingress_map_fd)); - if (u->ip_accounting_egress_map_fd >= 0) - RET_GATHER(r, bpf_firewall_reset_accounting(u->ip_accounting_egress_map_fd)); + if (crt->ip_accounting_egress_map_fd >= 0) + RET_GATHER(r, bpf_firewall_reset_accounting(crt->ip_accounting_egress_map_fd)); - zero(u->ip_accounting_extra); + zero(crt->ip_accounting_extra); return r; } @@ -4406,7 +4950,11 @@ int unit_reset_ip_accounting(Unit *u) { void unit_reset_io_accounting_last(Unit *u) { assert(u); - FOREACH_ARRAY(i, u->io_accounting_last, _CGROUP_IO_ACCOUNTING_METRIC_MAX) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return; + + FOREACH_ARRAY(i, crt->io_accounting_last, _CGROUP_IO_ACCOUNTING_METRIC_MAX) *i = UINT64_MAX; } @@ -4415,11 +4963,15 @@ int unit_reset_io_accounting(Unit *u) { assert(u); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return 0; + unit_reset_io_accounting_last(u); - r = unit_get_io_accounting_raw(u, u->io_accounting_base); + r = unit_get_io_accounting_raw(u, crt->io_accounting_base); if (r < 0) { - zero(u->io_accounting_base); + zero(crt->io_accounting_base); return r; } @@ -4445,6 +4997,10 @@ void unit_invalidate_cgroup(Unit *u, CGroupMask m) { if (!UNIT_HAS_CGROUP_CONTEXT(u)) return; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + if (m == 0) return; @@ -4455,10 +5011,10 @@ void unit_invalidate_cgroup(Unit *u, CGroupMask m) { if (m & (CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT)) m |= CGROUP_MASK_CPU | CGROUP_MASK_CPUACCT; - if (FLAGS_SET(u->cgroup_invalidated_mask, m)) /* NOP? */ + if (FLAGS_SET(crt->cgroup_invalidated_mask, m)) /* NOP? */ return; - u->cgroup_invalidated_mask |= m; + crt->cgroup_invalidated_mask |= m; unit_add_to_cgroup_realize_queue(u); } @@ -4468,10 +5024,14 @@ void unit_invalidate_cgroup_bpf(Unit *u) { if (!UNIT_HAS_CGROUP_CONTEXT(u)) return; - if (u->cgroup_invalidated_mask & CGROUP_MASK_BPF_FIREWALL) /* NOP? */ + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return; + + if (crt->cgroup_invalidated_mask & CGROUP_MASK_BPF_FIREWALL) /* NOP? */ return; - u->cgroup_invalidated_mask |= CGROUP_MASK_BPF_FIREWALL; + crt->cgroup_invalidated_mask |= CGROUP_MASK_BPF_FIREWALL; unit_add_to_cgroup_realize_queue(u); /* If we are a slice unit, we also need to put compile a new BPF program for all our children, as the IP access @@ -4523,66 +5083,102 @@ void manager_invalidate_startup_units(Manager *m) { unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_IO|CGROUP_MASK_BLKIO|CGROUP_MASK_CPUSET); } +static int unit_cgroup_freezer_kernel_state(Unit *u, FreezerState *ret) { + _cleanup_free_ char *val = NULL; + FreezerState s; + int r; + + assert(u); + assert(ret); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) + return -EOWNERDEAD; + + r = cg_get_keyed_attribute( + SYSTEMD_CGROUP_CONTROLLER, + crt->cgroup_path, + "cgroup.events", + STRV_MAKE("frozen"), + &val); + if (IN_SET(r, -ENOENT, -ENXIO)) + return -ENODATA; + if (r < 0) + return r; + + if (streq(val, "0")) + s = FREEZER_RUNNING; + else if (streq(val, "1")) + s = FREEZER_FROZEN; + else { + log_unit_debug(u, "Unexpected cgroup frozen state: %s", val); + s = _FREEZER_STATE_INVALID; + } + + *ret = s; + return 0; +} + int unit_cgroup_freezer_action(Unit *u, FreezerAction action) { _cleanup_free_ char *path = NULL; - FreezerState target, kernel = _FREEZER_STATE_INVALID; - int r, ret; + FreezerState target, current, next; + int r; assert(u); - assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_PARENT_FREEZE, + FREEZER_THAW, FREEZER_PARENT_THAW)); if (!cg_freezer_supported()) return 0; - /* Ignore all requests to thaw init.scope or -.slice and reject all requests to freeze them */ - if (unit_has_name(u, SPECIAL_ROOT_SLICE) || unit_has_name(u, SPECIAL_INIT_SCOPE)) - return action == FREEZER_FREEZE ? -EPERM : 0; - - if (!u->cgroup_realized) - return -EBUSY; - - if (action == FREEZER_THAW) { - Unit *slice = UNIT_GET_SLICE(u); + unit_next_freezer_state(u, action, &next, &target); - if (slice) { - r = unit_cgroup_freezer_action(slice, FREEZER_THAW); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to thaw slice %s of unit: %m", slice->id); - } + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_realized) { + /* No realized cgroup = nothing to freeze */ + u->freezer_state = freezer_state_finish(next); + return 0; } - target = action == FREEZER_FREEZE ? FREEZER_FROZEN : FREEZER_RUNNING; - - r = unit_freezer_state_kernel(u, &kernel); + r = unit_cgroup_freezer_kernel_state(u, ¤t); if (r < 0) - log_unit_debug_errno(u, r, "Failed to obtain cgroup freezer state: %m"); + return r; - if (target == kernel) { - u->freezer_state = target; - if (action == FREEZER_FREEZE) - return 0; - ret = 0; - } else - ret = 1; + if (current == target) + next = freezer_state_finish(next); + else if (IN_SET(next, FREEZER_FROZEN, FREEZER_FROZEN_BY_PARENT, FREEZER_RUNNING)) { + /* We're transitioning into a finished state, which implies that the cgroup's + * current state already matches the target and thus we'd return 0. But, reality + * shows otherwise. This indicates that our freezer_state tracking has diverged + * from the real state of the cgroup, which can happen if someone meddles with the + * cgroup from underneath us. This really shouldn't happen during normal operation, + * though. So, let's warn about it and fix up the state to be valid */ + + log_unit_warning(u, "Unit wants to transition to %s freezer state but cgroup is unexpectedly %s, fixing up.", + freezer_state_to_string(next), freezer_state_to_string(current) ?: "(invalid)"); + + if (next == FREEZER_FROZEN) + next = FREEZER_FREEZING; + else if (next == FREEZER_FROZEN_BY_PARENT) + next = FREEZER_FREEZING_BY_PARENT; + else if (next == FREEZER_RUNNING) + next = FREEZER_THAWING; + } - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.freeze", &path); + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, crt->cgroup_path, "cgroup.freeze", &path); if (r < 0) return r; - log_unit_debug(u, "%s unit.", action == FREEZER_FREEZE ? "Freezing" : "Thawing"); - - if (target != kernel) { - if (action == FREEZER_FREEZE) - u->freezer_state = FREEZER_FREEZING; - else - u->freezer_state = FREEZER_THAWING; - } + log_unit_debug(u, "Unit freezer state was %s, now %s.", + freezer_state_to_string(u->freezer_state), + freezer_state_to_string(next)); - r = write_string_file(path, one_zero(action == FREEZER_FREEZE), WRITE_STRING_FILE_DISABLE_BUFFER); + r = write_string_file(path, one_zero(target == FREEZER_FROZEN), WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) return r; - return ret; + u->freezer_state = next; + return target != current; } int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { @@ -4592,10 +5188,11 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { assert(u); assert(cpus); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENODATA; - if ((u->cgroup_realized_mask & CGROUP_MASK_CPUSET) == 0) + if ((crt->cgroup_realized_mask & CGROUP_MASK_CPUSET) == 0) return -ENODATA; r = cg_all_unified(); @@ -4604,7 +5201,7 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { if (r == 0) return -ENODATA; - r = cg_get_attribute("cpuset", u->cgroup_path, name, &v); + r = cg_get_attribute("cpuset", crt->cgroup_path, name, &v); if (r == -ENOENT) return -ENODATA; if (r < 0) @@ -4613,6 +5210,422 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name) { return parse_cpu_set_full(v, cpus, false, NULL, NULL, 0, NULL); } +CGroupRuntime *cgroup_runtime_new(void) { + _cleanup_(cgroup_runtime_freep) CGroupRuntime *crt = NULL; + + crt = new(CGroupRuntime, 1); + if (!crt) + return NULL; + + *crt = (CGroupRuntime) { + .cpu_usage_last = NSEC_INFINITY, + + .cgroup_control_inotify_wd = -1, + .cgroup_memory_inotify_wd = -1, + + .ip_accounting_ingress_map_fd = -EBADF, + .ip_accounting_egress_map_fd = -EBADF, + + .ipv4_allow_map_fd = -EBADF, + .ipv6_allow_map_fd = -EBADF, + .ipv4_deny_map_fd = -EBADF, + .ipv6_deny_map_fd = -EBADF, + + .cgroup_invalidated_mask = _CGROUP_MASK_ALL, + }; + + FOREACH_ELEMENT(i, crt->memory_accounting_last) + *i = UINT64_MAX; + FOREACH_ELEMENT(i, crt->io_accounting_base) + *i = UINT64_MAX; + FOREACH_ELEMENT(i, crt->io_accounting_last) + *i = UINT64_MAX; + FOREACH_ELEMENT(i, crt->ip_accounting_extra) + *i = UINT64_MAX; + + return TAKE_PTR(crt); +} + +CGroupRuntime *cgroup_runtime_free(CGroupRuntime *crt) { + if (!crt) + return NULL; + + fdset_free(crt->initial_socket_bind_link_fds); +#if BPF_FRAMEWORK + bpf_link_free(crt->ipv4_socket_bind_link); + bpf_link_free(crt->ipv6_socket_bind_link); +#endif + hashmap_free(crt->bpf_foreign_by_key); + + bpf_program_free(crt->bpf_device_control_installed); + +#if BPF_FRAMEWORK + bpf_link_free(crt->restrict_ifaces_ingress_bpf_link); + bpf_link_free(crt->restrict_ifaces_egress_bpf_link); +#endif + fdset_free(crt->initial_restrict_ifaces_link_fds); + + safe_close(crt->ipv4_allow_map_fd); + safe_close(crt->ipv6_allow_map_fd); + safe_close(crt->ipv4_deny_map_fd); + safe_close(crt->ipv6_deny_map_fd); + + bpf_program_free(crt->ip_bpf_ingress); + bpf_program_free(crt->ip_bpf_ingress_installed); + bpf_program_free(crt->ip_bpf_egress); + bpf_program_free(crt->ip_bpf_egress_installed); + + set_free(crt->ip_bpf_custom_ingress); + set_free(crt->ip_bpf_custom_ingress_installed); + set_free(crt->ip_bpf_custom_egress); + set_free(crt->ip_bpf_custom_egress_installed); + + free(crt->cgroup_path); + + return mfree(crt); +} + +static const char* const ip_accounting_metric_field_table[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IP_INGRESS_BYTES] = "ip-accounting-ingress-bytes", + [CGROUP_IP_INGRESS_PACKETS] = "ip-accounting-ingress-packets", + [CGROUP_IP_EGRESS_BYTES] = "ip-accounting-egress-bytes", + [CGROUP_IP_EGRESS_PACKETS] = "ip-accounting-egress-packets", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(ip_accounting_metric_field, CGroupIPAccountingMetric); + +static const char* const io_accounting_metric_field_base_table[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-base", + [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-base", + [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-base", + [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-base", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(io_accounting_metric_field_base, CGroupIOAccountingMetric); + +static const char* const io_accounting_metric_field_last_table[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-last", + [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-last", + [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-last", + [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-last", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(io_accounting_metric_field_last, CGroupIOAccountingMetric); + +static const char* const memory_accounting_metric_field_last_table[_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1] = { + [CGROUP_MEMORY_PEAK] = "memory-accounting-peak", + [CGROUP_MEMORY_SWAP_PEAK] = "memory-accounting-swap-peak", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(memory_accounting_metric_field_last, CGroupMemoryAccountingMetric); + +static int serialize_cgroup_mask(FILE *f, const char *key, CGroupMask mask) { + _cleanup_free_ char *s = NULL; + int r; + + assert(f); + assert(key); + + if (mask == 0) + return 0; + + r = cg_mask_to_string(mask, &s); + if (r < 0) + return log_error_errno(r, "Failed to format cgroup mask: %m"); + + return serialize_item(f, key, s); +} + +int cgroup_runtime_serialize(Unit *u, FILE *f, FDSet *fds) { + int r; + + assert(u); + assert(f); + assert(fds); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return 0; + + (void) serialize_item_format(f, "cpu-usage-base", "%" PRIu64, crt->cpu_usage_base); + if (crt->cpu_usage_last != NSEC_INFINITY) + (void) serialize_item_format(f, "cpu-usage-last", "%" PRIu64, crt->cpu_usage_last); + + if (crt->managed_oom_kill_last > 0) + (void) serialize_item_format(f, "managed-oom-kill-last", "%" PRIu64, crt->managed_oom_kill_last); + + if (crt->oom_kill_last > 0) + (void) serialize_item_format(f, "oom-kill-last", "%" PRIu64, crt->oom_kill_last); + + for (CGroupMemoryAccountingMetric metric = 0; metric <= _CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST; metric++) { + uint64_t v; + + r = unit_get_memory_accounting(u, metric, &v); + if (r >= 0) + (void) serialize_item_format(f, memory_accounting_metric_field_last_to_string(metric), "%" PRIu64, v); + } + + for (CGroupIPAccountingMetric m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { + uint64_t v; + + r = unit_get_ip_accounting(u, m, &v); + if (r >= 0) + (void) serialize_item_format(f, ip_accounting_metric_field_to_string(m), "%" PRIu64, v); + } + + for (CGroupIOAccountingMetric im = 0; im < _CGROUP_IO_ACCOUNTING_METRIC_MAX; im++) { + (void) serialize_item_format(f, io_accounting_metric_field_base_to_string(im), "%" PRIu64, crt->io_accounting_base[im]); + + if (crt->io_accounting_last[im] != UINT64_MAX) + (void) serialize_item_format(f, io_accounting_metric_field_last_to_string(im), "%" PRIu64, crt->io_accounting_last[im]); + } + + if (crt->cgroup_path) + (void) serialize_item(f, "cgroup", crt->cgroup_path); + if (crt->cgroup_id != 0) + (void) serialize_item_format(f, "cgroup-id", "%" PRIu64, crt->cgroup_id); + + (void) serialize_bool(f, "cgroup-realized", crt->cgroup_realized); + (void) serialize_cgroup_mask(f, "cgroup-realized-mask", crt->cgroup_realized_mask); + (void) serialize_cgroup_mask(f, "cgroup-enabled-mask", crt->cgroup_enabled_mask); + (void) serialize_cgroup_mask(f, "cgroup-invalidated-mask", crt->cgroup_invalidated_mask); + + (void) bpf_socket_bind_serialize(u, f, fds); + + (void) bpf_program_serialize_attachment(f, fds, "ip-bpf-ingress-installed", crt->ip_bpf_ingress_installed); + (void) bpf_program_serialize_attachment(f, fds, "ip-bpf-egress-installed", crt->ip_bpf_egress_installed); + (void) bpf_program_serialize_attachment(f, fds, "bpf-device-control-installed", crt->bpf_device_control_installed); + (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-ingress-installed", crt->ip_bpf_custom_ingress_installed); + (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-egress-installed", crt->ip_bpf_custom_egress_installed); + + (void) bpf_restrict_ifaces_serialize(u, f, fds); + + return 0; +} + +#define MATCH_DESERIALIZE(u, key, l, v, parse_func, target) \ + ({ \ + bool _deserialize_matched = streq(l, key); \ + if (_deserialize_matched) { \ + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); \ + if (!crt) \ + log_oom_debug(); \ + else { \ + int _deserialize_r = parse_func(v); \ + if (_deserialize_r < 0) \ + log_unit_debug_errno(u, _deserialize_r, \ + "Failed to parse \"%s=%s\", ignoring.", l, v); \ + else \ + crt->target = _deserialize_r; \ + } \ + } \ + _deserialize_matched; \ + }) + +#define MATCH_DESERIALIZE_IMMEDIATE(u, key, l, v, parse_func, target) \ + ({ \ + bool _deserialize_matched = streq(l, key); \ + if (_deserialize_matched) { \ + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); \ + if (!crt) \ + log_oom_debug(); \ + else { \ + int _deserialize_r = parse_func(v, &crt->target); \ + if (_deserialize_r < 0) \ + log_unit_debug_errno(u, _deserialize_r, \ + "Failed to parse \"%s=%s\", ignoring", l, v); \ + } \ + } \ + _deserialize_matched; \ + }) + +#define MATCH_DESERIALIZE_METRIC(u, key, l, v, parse_func, target) \ + ({ \ + bool _deserialize_matched = streq(l, key); \ + if (_deserialize_matched) { \ + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); \ + if (!crt) \ + log_oom_debug(); \ + else { \ + int _deserialize_r = parse_func(v); \ + if (_deserialize_r < 0) \ + log_unit_debug_errno(u, _deserialize_r, \ + "Failed to parse \"%s=%s\", ignoring.", l, v); \ + else \ + crt->target = _deserialize_r; \ + } \ + } \ + _deserialize_matched; \ + }) + +int cgroup_runtime_deserialize_one(Unit *u, const char *key, const char *value, FDSet *fds) { + int r; + + assert(u); + assert(value); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cpu-usage-base", key, value, safe_atou64, cpu_usage_base) || + MATCH_DESERIALIZE_IMMEDIATE(u, "cpuacct-usage-base", key, value, safe_atou64, cpu_usage_base)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cpu-usage-last", key, value, safe_atou64, cpu_usage_last)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "managed-oom-kill-last", key, value, safe_atou64, managed_oom_kill_last)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "oom-kill-last", key, value, safe_atou64, oom_kill_last)) + return 1; + + if (streq(key, "cgroup")) { + r = unit_set_cgroup_path(u, value); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", value); + + (void) unit_watch_cgroup(u); + (void) unit_watch_cgroup_memory(u); + return 1; + } + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cgroup-id", key, value, safe_atou64, cgroup_id)) + return 1; + + if (MATCH_DESERIALIZE(u, "cgroup-realized", key, value, parse_boolean, cgroup_realized)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cgroup-realized-mask", key, value, cg_mask_from_string, cgroup_realized_mask)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cgroup-enabled-mask", key, value, cg_mask_from_string, cgroup_enabled_mask)) + return 1; + + if (MATCH_DESERIALIZE_IMMEDIATE(u, "cgroup-invalidated-mask", key, value, cg_mask_from_string, cgroup_invalidated_mask)) + return 1; + + if (STR_IN_SET(key, "ipv4-socket-bind-bpf-link-fd", "ipv6-socket-bind-bpf-link-fd")) { + int fd; + + fd = deserialize_fd(fds, value); + if (fd >= 0) + (void) bpf_socket_bind_add_initial_link_fd(u, fd); + + return 1; + } + + if (STR_IN_SET(key, + "ip-bpf-ingress-installed", "ip-bpf-egress-installed", + "bpf-device-control-installed", + "ip-bpf-custom-ingress-installed", "ip-bpf-custom-egress-installed")) { + + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + log_oom_debug(); + else { + if (streq(key, "ip-bpf-ingress-installed")) + (void) bpf_program_deserialize_attachment(value, fds, &crt->ip_bpf_ingress_installed); + + if (streq(key, "ip-bpf-egress-installed")) + (void) bpf_program_deserialize_attachment(value, fds, &crt->ip_bpf_egress_installed); + + if (streq(key, "bpf-device-control-installed")) + (void) bpf_program_deserialize_attachment(value, fds, &crt->bpf_device_control_installed); + + if (streq(key, "ip-bpf-custom-ingress-installed")) + (void) bpf_program_deserialize_attachment_set(value, fds, &crt->ip_bpf_custom_ingress_installed); + + if (streq(key, "ip-bpf-custom-egress-installed")) + (void) bpf_program_deserialize_attachment_set(value, fds, &crt->ip_bpf_custom_egress_installed); + } + + return 1; + } + + if (streq(key, "restrict-ifaces-bpf-fd")) { + int fd; + + fd = deserialize_fd(fds, value); + if (fd >= 0) + (void) bpf_restrict_ifaces_add_initial_link_fd(u, fd); + return 1; + } + + CGroupMemoryAccountingMetric mm = memory_accounting_metric_field_last_from_string(key); + if (mm >= 0) { + uint64_t c; + + r = safe_atou64(value, &c); + if (r < 0) + log_unit_debug(u, "Failed to parse memory accounting last value %s, ignoring.", value); + else { + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + log_oom_debug(); + else + crt->memory_accounting_last[mm] = c; + } + + return 1; + } + + CGroupIPAccountingMetric ipm = ip_accounting_metric_field_from_string(key); + if (ipm >= 0) { + uint64_t c; + + r = safe_atou64(value, &c); + if (r < 0) + log_unit_debug(u, "Failed to parse IP accounting value %s, ignoring.", value); + else { + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + log_oom_debug(); + else + crt->ip_accounting_extra[ipm] = c; + } + + return 1; + } + + CGroupIOAccountingMetric iom = io_accounting_metric_field_base_from_string(key); + if (iom >= 0) { + uint64_t c; + + r = safe_atou64(value, &c); + if (r < 0) + log_unit_debug(u, "Failed to parse IO accounting base value %s, ignoring.", value); + else { + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + log_oom_debug(); + else + crt->io_accounting_base[iom] = c; + } + + return 1; + } + + iom = io_accounting_metric_field_last_from_string(key); + if (iom >= 0) { + uint64_t c; + + r = safe_atou64(value, &c); + if (r < 0) + log_unit_debug(u, "Failed to parse IO accounting last value %s, ignoring.", value); + else { + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + log_oom_debug(); + else + crt->io_accounting_last[iom] = c; + } + return 1; + } + + return 0; +} + static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { [CGROUP_DEVICE_POLICY_AUTO] = "auto", [CGROUP_DEVICE_POLICY_CLOSED] = "closed", @@ -4621,17 +5634,10 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); -static const char* const freezer_action_table[_FREEZER_ACTION_MAX] = { - [FREEZER_FREEZE] = "freeze", - [FREEZER_THAW] = "thaw", -}; - -DEFINE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction); - static const char* const cgroup_pressure_watch_table[_CGROUP_PRESSURE_WATCH_MAX] = { - [CGROUP_PRESSURE_WATCH_OFF] = "off", + [CGROUP_PRESSURE_WATCH_OFF] = "off", [CGROUP_PRESSURE_WATCH_AUTO] = "auto", - [CGROUP_PRESSURE_WATCH_ON] = "on", + [CGROUP_PRESSURE_WATCH_ON] = "on", [CGROUP_PRESSURE_WATCH_SKIP] = "skip", }; @@ -4663,3 +5669,11 @@ static const char* const cgroup_memory_accounting_metric_table[_CGROUP_MEMORY_AC }; DEFINE_STRING_TABLE_LOOKUP(cgroup_memory_accounting_metric, CGroupMemoryAccountingMetric); + +static const char *const cgroup_effective_limit_type_table[_CGROUP_LIMIT_TYPE_MAX] = { + [CGROUP_LIMIT_MEMORY_MAX] = "EffectiveMemoryMax", + [CGROUP_LIMIT_MEMORY_HIGH] = "EffectiveMemoryHigh", + [CGROUP_LIMIT_TASKS_MAX] = "EffectiveTasksMax", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_effective_limit_type, CGroupLimitType); diff --git a/src/core/cgroup.h b/src/core/cgroup.h index f1b674b..72fe275 100644 --- a/src/core/cgroup.h +++ b/src/core/cgroup.h @@ -3,7 +3,10 @@ #include -#include "bpf-lsm.h" +#include "sd-event.h" + +#include "bpf-program.h" +#include "bpf-restrict-fs.h" #include "cgroup-util.h" #include "cpu-set-util.h" #include "firewall-util.h" @@ -35,6 +38,7 @@ typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; typedef struct CGroupBPFForeignProgram CGroupBPFForeignProgram; typedef struct CGroupSocketBindItem CGroupSocketBindItem; +typedef struct CGroupRuntime CGroupRuntime; typedef enum CGroupDevicePolicy { /* When devices listed, will allow those, plus built-in ones, if none are listed will allow @@ -53,7 +57,9 @@ typedef enum CGroupDevicePolicy { typedef enum FreezerAction { FREEZER_FREEZE, + FREEZER_PARENT_FREEZE, FREEZER_THAW, + FREEZER_PARENT_THAW, _FREEZER_ACTION_MAX, _FREEZER_ACTION_INVALID = -EINVAL, @@ -129,6 +135,9 @@ typedef enum CGroupPressureWatch { _CGROUP_PRESSURE_WATCH_INVALID = -EINVAL, } CGroupPressureWatch; +/* The user-supplied cgroup-related configuration options. This remains mostly immutable while the service + * manager is running (except for an occasional SetProperty() configuration change), outside of reload + * cycles. When adding members make sure to update cgroup_context_copy() accordingly. */ struct CGroupContext { bool cpu_accounting; bool io_accounting; @@ -188,6 +197,8 @@ struct CGroupContext { bool startup_memory_swap_max_set:1; bool startup_memory_zswap_max_set:1; + bool memory_zswap_writeback; + Set *ip_address_allow; Set *ip_address_deny; /* These two flags indicate that redundant entries have been removed from @@ -276,6 +287,95 @@ typedef enum CGroupMemoryAccountingMetric { _CGROUP_MEMORY_ACCOUNTING_METRIC_INVALID = -EINVAL, } CGroupMemoryAccountingMetric; +/* Used for limits whose value sets have infimum */ +typedef enum CGroupLimitType { + CGROUP_LIMIT_MEMORY_MAX, + CGROUP_LIMIT_MEMORY_HIGH, + CGROUP_LIMIT_TASKS_MAX, + _CGROUP_LIMIT_TYPE_MAX, + _CGROUP_LIMIT_INVALID = -EINVAL, +} CGroupLimitType; + +/* The dynamic, regular updated information about a unit that as a realized cgroup. This is only allocated when a unit is first realized */ +typedef struct CGroupRuntime { + /* Where the cpu.stat or cpuacct.usage was at the time the unit was started */ + nsec_t cpu_usage_base; + nsec_t cpu_usage_last; /* the most recently read value */ + + /* Most recently read value of memory accounting metrics */ + uint64_t memory_accounting_last[_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1]; + + /* The current counter of OOM kills initiated by systemd-oomd */ + uint64_t managed_oom_kill_last; + + /* The current counter of the oom_kill field in the memory.events cgroup attribute */ + uint64_t oom_kill_last; + + /* Where the io.stat data was at the time the unit was started */ + uint64_t io_accounting_base[_CGROUP_IO_ACCOUNTING_METRIC_MAX]; + uint64_t io_accounting_last[_CGROUP_IO_ACCOUNTING_METRIC_MAX]; /* the most recently read value */ + + /* Counterparts in the cgroup filesystem */ + char *cgroup_path; + uint64_t cgroup_id; + CGroupMask cgroup_realized_mask; /* In which hierarchies does this unit's cgroup exist? (only relevant on cgroup v1) */ + CGroupMask cgroup_enabled_mask; /* Which controllers are enabled (or more correctly: enabled for the children) for this unit's cgroup? (only relevant on cgroup v2) */ + CGroupMask cgroup_invalidated_mask; /* A mask specifying controllers which shall be considered invalidated, and require re-realization */ + CGroupMask cgroup_members_mask; /* A cache for the controllers required by all children of this cgroup (only relevant for slice units) */ + + /* Inotify watch descriptors for watching cgroup.events and memory.events on cgroupv2 */ + int cgroup_control_inotify_wd; + int cgroup_memory_inotify_wd; + + /* Device Controller BPF program */ + BPFProgram *bpf_device_control_installed; + + /* IP BPF Firewalling/accounting */ + int ip_accounting_ingress_map_fd; + int ip_accounting_egress_map_fd; + uint64_t ip_accounting_extra[_CGROUP_IP_ACCOUNTING_METRIC_MAX]; + + int ipv4_allow_map_fd; + int ipv6_allow_map_fd; + int ipv4_deny_map_fd; + int ipv6_deny_map_fd; + BPFProgram *ip_bpf_ingress, *ip_bpf_ingress_installed; + BPFProgram *ip_bpf_egress, *ip_bpf_egress_installed; + + Set *ip_bpf_custom_ingress; + Set *ip_bpf_custom_ingress_installed; + Set *ip_bpf_custom_egress; + Set *ip_bpf_custom_egress_installed; + + /* BPF programs managed (e.g. loaded to kernel) by an entity external to systemd, + * attached to unit cgroup by provided program fd and attach type. */ + Hashmap *bpf_foreign_by_key; + + FDSet *initial_socket_bind_link_fds; +#if BPF_FRAMEWORK + /* BPF links to BPF programs attached to cgroup/bind{4|6} hooks and + * responsible for allowing or denying a unit to bind(2) to a socket + * address. */ + struct bpf_link *ipv4_socket_bind_link; + struct bpf_link *ipv6_socket_bind_link; +#endif + + FDSet *initial_restrict_ifaces_link_fds; +#if BPF_FRAMEWORK + struct bpf_link *restrict_ifaces_ingress_bpf_link; + struct bpf_link *restrict_ifaces_egress_bpf_link; +#endif + + bool cgroup_realized:1; + bool cgroup_members_mask_valid:1; + + /* Reset cgroup accounting next time we fork something off */ + bool reset_accounting:1; + + /* Whether we warned about clamping the CPU quota period */ + bool warned_clamping_cpu_quota_period:1; +} CGroupRuntime; + typedef struct Unit Unit; typedef struct Manager Manager; typedef enum ManagerState ManagerState; @@ -285,6 +385,7 @@ uint64_t cgroup_context_cpu_weight(CGroupContext *c, ManagerState state); usec_t cgroup_cpu_adjust_period(usec_t period, usec_t quota, usec_t resolution, usec_t max_period); void cgroup_context_init(CGroupContext *c); +int cgroup_context_copy(CGroupContext *dst, const CGroupContext *src); void cgroup_context_done(CGroupContext *c); void cgroup_context_dump(Unit *u, FILE* f, const char *prefix); void cgroup_context_dump_socket_bind_item(const CGroupSocketBindItem *item, FILE *f); @@ -309,6 +410,17 @@ static inline bool cgroup_context_want_memory_pressure(const CGroupContext *c) { int cgroup_context_add_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p); int cgroup_context_add_or_update_device_allow(CGroupContext *c, const char *dev, CGroupDevicePermissions p); int cgroup_context_add_bpf_foreign_program(CGroupContext *c, uint32_t attach_type, const char *path); +static inline int cgroup_context_add_bpf_foreign_program_dup(CGroupContext *c, const CGroupBPFForeignProgram *p) { + return cgroup_context_add_bpf_foreign_program(c, p->attach_type, p->bpffs_path); +} +int cgroup_context_add_io_device_limit_dup(CGroupContext *c, const CGroupIODeviceLimit *l); +int cgroup_context_add_io_device_weight_dup(CGroupContext *c, const CGroupIODeviceWeight *w); +int cgroup_context_add_io_device_latency_dup(CGroupContext *c, const CGroupIODeviceLatency *l); +int cgroup_context_add_block_io_device_weight_dup(CGroupContext *c, const CGroupBlockIODeviceWeight *w); +int cgroup_context_add_block_io_device_bandwidth_dup(CGroupContext *c, const CGroupBlockIODeviceBandwidth *b); +int cgroup_context_add_device_allow_dup(CGroupContext *c, const CGroupDeviceAllow *a); +int cgroup_context_add_socket_bind_item_allow_dup(CGroupContext *c, const CGroupSocketBindItem *i); +int cgroup_context_add_socket_bind_item_deny_dup(CGroupContext *c, const CGroupSocketBindItem *i); void unit_modify_nft_set(Unit *u, bool add); @@ -336,6 +448,7 @@ int unit_watch_cgroup(Unit *u); int unit_watch_cgroup_memory(Unit *u); void unit_add_to_cgroup_realize_queue(Unit *u); +int unit_cgroup_is_empty(Unit *u); void unit_release_cgroup(Unit *u); /* Releases the cgroup only if it is recursively empty. * Returns true if the cgroup was released, false otherwise. */ @@ -353,9 +466,9 @@ void manager_shutdown_cgroup(Manager *m, bool delete); unsigned manager_dispatch_cgroup_realize_queue(Manager *m); Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); -Unit *manager_get_unit_by_pidref_cgroup(Manager *m, PidRef *pid); -Unit *manager_get_unit_by_pidref_watching(Manager *m, PidRef *pid); -Unit* manager_get_unit_by_pidref(Manager *m, PidRef *pid); +Unit *manager_get_unit_by_pidref_cgroup(Manager *m, const PidRef *pid); +Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid); +Unit* manager_get_unit_by_pidref(Manager *m, const PidRef *pid); Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); uint64_t unit_get_ancestor_memory_min(Unit *u); @@ -374,6 +487,7 @@ int unit_get_tasks_current(Unit *u, uint64_t *ret); int unit_get_cpu_usage(Unit *u, nsec_t *ret); int unit_get_io_accounting(Unit *u, CGroupIOAccountingMetric metric, bool allow_cache, uint64_t *ret); int unit_get_ip_accounting(Unit *u, CGroupIPAccountingMetric metric, uint64_t *ret); +int unit_get_effective_limit(Unit *u, CGroupLimitType type, uint64_t *ret); int unit_reset_cpu_accounting(Unit *u); void unit_reset_memory_accounting_last(Unit *u); @@ -413,6 +527,13 @@ int unit_cgroup_freezer_action(Unit *u, FreezerAction action); const char* freezer_action_to_string(FreezerAction a) _const_; FreezerAction freezer_action_from_string(const char *s) _pure_; +CGroupRuntime *cgroup_runtime_new(void); +CGroupRuntime *cgroup_runtime_free(CGroupRuntime *crt); +DEFINE_TRIVIAL_CLEANUP_FUNC(CGroupRuntime*, cgroup_runtime_free); + +int cgroup_runtime_serialize(Unit *u, FILE *f, FDSet *fds); +int cgroup_runtime_deserialize_one(Unit *u, const char *key, const char *value, FDSet *fds); + const char* cgroup_pressure_watch_to_string(CGroupPressureWatch a) _const_; CGroupPressureWatch cgroup_pressure_watch_from_string(const char *s) _pure_; @@ -425,5 +546,8 @@ CGroupIPAccountingMetric cgroup_ip_accounting_metric_from_string(const char *s) const char* cgroup_io_accounting_metric_to_string(CGroupIOAccountingMetric m) _const_; CGroupIOAccountingMetric cgroup_io_accounting_metric_from_string(const char *s) _pure_; +const char* cgroup_effective_limit_type_to_string(CGroupLimitType m) _const_; +CGroupLimitType cgroup_effective_limit_type_from_string(const char *s) _pure_; + const char* cgroup_memory_accounting_metric_to_string(CGroupMemoryAccountingMetric m) _const_; CGroupMemoryAccountingMetric cgroup_memory_accounting_metric_from_string(const char *s) _pure_; diff --git a/src/core/core-varlink.c b/src/core/core-varlink.c index cd91381..3e6168d 100644 --- a/src/core/core-varlink.c +++ b/src/core/core-varlink.c @@ -69,6 +69,10 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, J if (!c) return -EINVAL; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt) + return -EINVAL; + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u))) /* systemd-oomd should always treat inactive units as though they didn't enable any action since they * should not have a valid cgroup */ @@ -83,19 +87,24 @@ static int build_managed_oom_json_array_element(Unit *u, const char *property, J return json_build(ret_v, JSON_BUILD_OBJECT( JSON_BUILD_PAIR("mode", JSON_BUILD_STRING(mode)), - JSON_BUILD_PAIR("path", JSON_BUILD_STRING(u->cgroup_path)), + JSON_BUILD_PAIR("path", JSON_BUILD_STRING(crt->cgroup_path)), JSON_BUILD_PAIR("property", JSON_BUILD_STRING(property)), JSON_BUILD_PAIR_CONDITION(use_limit, "limit", JSON_BUILD_UNSIGNED(c->moom_mem_pressure_limit)))); } int manager_varlink_send_managed_oom_update(Unit *u) { _cleanup_(json_variant_unrefp) JsonVariant *arr = NULL, *v = NULL; + CGroupRuntime *crt; CGroupContext *c; int r; assert(u); - if (!UNIT_VTABLE(u)->can_set_managed_oom || !u->manager || !u->cgroup_path) + if (!UNIT_VTABLE(u)->can_set_managed_oom || !u->manager) + return 0; + + crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return 0; if (MANAGER_IS_SYSTEM(u->manager)) { @@ -119,10 +128,10 @@ int manager_varlink_send_managed_oom_update(Unit *u) { if (r < 0) return r; - for (size_t i = 0; i < ELEMENTSOF(managed_oom_mode_properties); i++) { + FOREACH_ELEMENT(i, managed_oom_mode_properties) { _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - r = build_managed_oom_json_array_element(u, managed_oom_mode_properties[i], &e); + r = build_managed_oom_json_array_element(u, *i, &e); if (r < 0) return r; @@ -173,16 +182,16 @@ static int build_managed_oom_cgroups_json(Manager *m, JsonVariant **ret) { if (!c) continue; - for (size_t j = 0; j < ELEMENTSOF(managed_oom_mode_properties); j++) { + FOREACH_ELEMENT(i, managed_oom_mode_properties) { _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; /* For the initial varlink call we only care about units that enabled (i.e. mode is not * set to "auto") oomd properties. */ - if (!(streq(managed_oom_mode_properties[j], "ManagedOOMSwap") && c->moom_swap == MANAGED_OOM_KILL) && - !(streq(managed_oom_mode_properties[j], "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL)) + if (!(streq(*i, "ManagedOOMSwap") && c->moom_swap == MANAGED_OOM_KILL) && + !(streq(*i, "ManagedOOMMemoryPressure") && c->moom_mem_pressure == MANAGED_OOM_KILL)) continue; - r = build_managed_oom_json_array_element(u, managed_oom_mode_properties[j], &e); + r = build_managed_oom_json_array_element(u, *i, &e); if (r < 0) return r; @@ -359,7 +368,7 @@ static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)), JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.DynamicUser")), JSON_BUILD_PAIR("disposition", JSON_BUILD_CONST_STRING("dynamic")))))); - } +} static bool group_match_lookup_parameters(LookupParameters *p, const char *name, gid_t gid) { assert(p); @@ -491,6 +500,43 @@ static void vl_disconnect(VarlinkServer *s, Varlink *link, void *userdata) { m->managed_oom_varlink = varlink_unref(link); } +static int manager_setup_varlink_server(Manager *m, VarlinkServer **ret) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + assert(ret); + + r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_debug_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + r = varlink_server_add_interface_many( + s, + &vl_interface_io_systemd_UserDatabase, + &vl_interface_io_systemd_ManagedOOM); + if (r < 0) + return log_debug_errno(r, "Failed to add interfaces to varlink server: %m"); + + r = varlink_server_bind_method_many( + s, + "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, + "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, + "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships, + "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink methods: %m"); + + r = varlink_server_bind_disconnect(s, vl_disconnect); + if (r < 0) + return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); + + *ret = TAKE_PTR(s); + return 0; +} + static int manager_varlink_init_system(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -527,7 +573,7 @@ static int manager_varlink_init_system(Manager *m) { } } - r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + r = varlink_server_attach_event(s, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); @@ -585,7 +631,7 @@ static int manager_varlink_init_user(Manager *m) { if (r < 0) return r; - r = varlink_attach_event(link, m->event, SD_EVENT_PRIORITY_NORMAL); + r = varlink_attach_event(link, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); @@ -597,43 +643,6 @@ static int manager_varlink_init_user(Manager *m) { return 1; } -int manager_setup_varlink_server(Manager *m, VarlinkServer **ret) { - _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; - int r; - - assert(m); - assert(ret); - - r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); - if (r < 0) - return log_debug_errno(r, "Failed to allocate varlink server object: %m"); - - varlink_server_set_userdata(s, m); - - r = varlink_server_add_interface_many( - s, - &vl_interface_io_systemd_UserDatabase, - &vl_interface_io_systemd_ManagedOOM); - if (r < 0) - return log_error_errno(r, "Failed to add interfaces to varlink server: %m"); - - r = varlink_server_bind_method_many( - s, - "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, - "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, - "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships, - "io.systemd.ManagedOOM.SubscribeManagedOOMCGroups", vl_method_subscribe_managed_oom_cgroups); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink methods: %m"); - - r = varlink_server_bind_disconnect(s, vl_disconnect); - if (r < 0) - return log_debug_errno(r, "Failed to register varlink disconnect handler: %m"); - - *ret = TAKE_PTR(s); - return 0; -} - int manager_varlink_init(Manager *m) { return MANAGER_IS_SYSTEM(m) ? manager_varlink_init_system(m) : manager_varlink_init_user(m); } diff --git a/src/core/core-varlink.h b/src/core/core-varlink.h index 7f810d1..20507a4 100644 --- a/src/core/core-varlink.h +++ b/src/core/core-varlink.h @@ -6,10 +6,6 @@ int manager_varlink_init(Manager *m); void manager_varlink_done(Manager *m); -/* Creates a new VarlinkServer and binds methods. Does not set up sockets or attach events. - * Used for manager serialize/deserialize. */ -int manager_setup_varlink_server(Manager *m, VarlinkServer **ret_s); - /* The manager is expected to send an update to systemd-oomd if one of the following occurs: * - The value of ManagedOOM*= properties change * - A unit with ManagedOOM*= properties changes unit active state */ diff --git a/src/core/crash-handler.c b/src/core/crash-handler.c index f5c31b6..4a3fc01 100644 --- a/src/core/crash-handler.c +++ b/src/core/crash-handler.c @@ -27,7 +27,13 @@ _noreturn_ void freeze_or_exit_or_reboot(void) { _exit(EXIT_EXCEPTION); } - if (arg_crash_reboot) { + if (arg_crash_action == CRASH_POWEROFF) { + log_notice("Shutting down..."); + (void) reboot(RB_POWER_OFF); + log_struct_errno(LOG_EMERG, errno, + LOG_MESSAGE("Failed to power off: %m"), + "MESSAGE_ID=" SD_MESSAGE_CRASH_FAILED_STR); + } else if (arg_crash_action == CRASH_REBOOT) { log_notice("Rebooting in 10s..."); (void) sleep(10); diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c index 8a9570f..49e84b4 100644 --- a/src/core/dbus-cgroup.c +++ b/src/core/dbus-cgroup.c @@ -487,6 +487,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = { SD_BUS_PROPERTY("StartupMemorySwapMax", "t", NULL, offsetof(CGroupContext, startup_memory_swap_max), 0), SD_BUS_PROPERTY("MemoryZSwapMax", "t", NULL, offsetof(CGroupContext, memory_zswap_max), 0), SD_BUS_PROPERTY("StartupMemoryZSwapMax", "t", NULL, offsetof(CGroupContext, startup_memory_zswap_max), 0), + SD_BUS_PROPERTY("MemoryZSwapWriteback", "b", bus_property_get_bool, offsetof(CGroupContext, memory_zswap_writeback), 0), SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), @@ -1279,6 +1280,9 @@ int bus_cgroup_set_property( if (streq(name, "MemoryLimitScale")) return bus_cgroup_set_memory_scale(u, name, &c->memory_limit, message, flags, error); + if (streq(name, "MemoryZSwapWriteback")) + return bus_cgroup_set_boolean(u, name, &c->memory_zswap_writeback, CGROUP_MASK_MEMORY, message, flags, error); + if (streq(name, "TasksAccounting")) return bus_cgroup_set_boolean(u, name, &c->tasks_accounting, CGROUP_MASK_PIDS, message, flags, error); @@ -1300,17 +1304,18 @@ int bus_cgroup_set_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { c->cpu_quota_per_sec_usec = u64; - u->warned_clamping_cpu_quota_period = false; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt) + crt->warned_clamping_cpu_quota_period = false; unit_invalidate_cgroup(u, CGROUP_MASK_CPU); if (c->cpu_quota_per_sec_usec == USEC_INFINITY) unit_write_setting(u, flags, "CPUQuota", "CPUQuota="); else - /* config_parse_cpu_quota() requires an integer, so truncating division is used on - * purpose here. */ unit_write_settingf(u, flags, "CPUQuota", - "CPUQuota=%0.f%%", - (double) (c->cpu_quota_per_sec_usec / 10000)); + "CPUQuota=" USEC_FMT ".%02" PRI_USEC "%%", + c->cpu_quota_per_sec_usec / 10000, + (c->cpu_quota_per_sec_usec % 10000) / 100); } return 1; @@ -1324,7 +1329,9 @@ int bus_cgroup_set_property( if (!UNIT_WRITE_FLAGS_NOOP(flags)) { c->cpu_quota_period_usec = u64; - u->warned_clamping_cpu_quota_period = false; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt) + crt->warned_clamping_cpu_quota_period = false; unit_invalidate_cgroup(u, CGROUP_MASK_CPU); if (c->cpu_quota_period_usec == USEC_INFINITY) unit_write_setting(u, flags, "CPUQuotaPeriodSec", "CPUQuotaPeriodSec="); @@ -2188,7 +2195,7 @@ int bus_cgroup_set_property( c->restrict_network_interfaces_is_allow_list = is_allow_list; STRV_FOREACH(s, l) { - if (!ifname_valid(*s)) { + if (!ifname_valid_full(*s, IFNAME_VALID_ALTERNATIVE)) { log_full(LOG_WARNING, "Invalid interface name, ignoring: %s", *s); continue; } diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 2d05ba7..21c260b 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -67,6 +67,7 @@ static BUS_DEFINE_PROPERTY_GET(property_get_cpu_sched_policy, "i", ExecContext, static BUS_DEFINE_PROPERTY_GET(property_get_cpu_sched_priority, "i", ExecContext, exec_context_get_cpu_sched_priority); static BUS_DEFINE_PROPERTY_GET(property_get_coredump_filter, "t", ExecContext, exec_context_get_coredump_filter); static BUS_DEFINE_PROPERTY_GET(property_get_timer_slack_nsec, "t", ExecContext, exec_context_get_timer_slack_nsec); +static BUS_DEFINE_PROPERTY_GET(property_get_set_login_environment, "b", ExecContext, exec_context_get_set_login_environment); static int property_get_environment_files( sd_bus *bus, @@ -1038,7 +1039,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("User", "s", NULL, offsetof(ExecContext, user), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SetLoginEnvironment", "b", bus_property_get_tristate, offsetof(ExecContext, set_login_environment), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SetLoginEnvironment", "b", property_get_set_login_environment, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SetCredentialEncrypted", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -1305,18 +1306,24 @@ int bus_set_transient_exec_command( sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error) { - bool is_ex_prop = endswith(name, "Ex"); - unsigned n = 0; + + const char *ex_prop = endswith(ASSERT_PTR(name), "Ex"); + size_t n = 0; int r; + assert(u); + assert(exec_command); + assert(message); + assert(error); + /* Drop Ex from the written setting. E.g. ExecStart=, not ExecStartEx=. */ - const char *written_name = is_ex_prop ? strndupa(name, strlen(name) - 2) : name; + const char *written_name = ex_prop ? strndupa_safe(name, ex_prop - name) : name; - r = sd_bus_message_enter_container(message, 'a', is_ex_prop ? "(sasas)" : "(sasb)"); + r = sd_bus_message_enter_container(message, 'a', ex_prop ? "(sasas)" : "(sasb)"); if (r < 0) return r; - while ((r = sd_bus_message_enter_container(message, 'r', is_ex_prop ? "sasas" : "sasb")) > 0) { + while ((r = sd_bus_message_enter_container(message, 'r', ex_prop ? "sasas" : "sasb")) > 0) { _cleanup_strv_free_ char **argv = NULL, **ex_opts = NULL; const char *path; int b; @@ -1338,7 +1345,7 @@ int bus_set_transient_exec_command( return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "\"%s\" argv cannot be empty", name); - r = is_ex_prop ? sd_bus_message_read_strv(message, &ex_opts) : sd_bus_message_read(message, "b", &b); + r = ex_prop ? sd_bus_message_read_strv(message, &ex_opts) : sd_bus_message_read(message, "b", &b); if (r < 0) return r; @@ -1347,29 +1354,28 @@ int bus_set_transient_exec_command( return r; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - ExecCommand *c; + _cleanup_(exec_command_freep) ExecCommand *c = NULL; - c = new0(ExecCommand, 1); + c = new(ExecCommand, 1); if (!c) return -ENOMEM; - c->path = strdup(path); - if (!c->path) { - free(c); - return -ENOMEM; - } + *c = (ExecCommand) { + .argv = TAKE_PTR(argv), + }; - c->argv = TAKE_PTR(argv); + r = path_simplify_alloc(path, &c->path); + if (r < 0) + return r; - if (is_ex_prop) { + if (ex_prop) { r = exec_command_flags_from_strv(ex_opts, &c->flags); if (r < 0) return r; - } else - c->flags = b ? EXEC_COMMAND_IGNORE_FAILURE : 0; + } else if (b) + c->flags |= EXEC_COMMAND_IGNORE_FAILURE; - path_simplify(c->path); - exec_command_append_list(exec_command, c); + exec_command_append_list(exec_command, TAKE_PTR(c)); } n++; @@ -1738,6 +1744,9 @@ int bus_exec_context_set_transient_property( if (streq(name, "PrivateMounts")) return bus_set_transient_tristate(u, name, &c->private_mounts, message, flags, error); + if (streq(name, "MountAPIVFS")) + return bus_set_transient_tristate(u, name, &c->mount_apivfs, message, flags, error); + if (streq(name, "PrivateNetwork")) return bus_set_transient_bool(u, name, &c->private_network, message, flags, error); @@ -1897,7 +1906,7 @@ int bus_exec_context_set_transient_property( c->restrict_filesystems_allow_list = allow_list; STRV_FOREACH(s, l) { - r = lsm_bpf_parse_filesystem( + r = bpf_restrict_fs_parse_filesystem( *s, &c->restrict_filesystems, FILESYSTEM_PARSE_LOG| @@ -1948,7 +1957,7 @@ int bus_exec_context_set_transient_property( r = strv_extend_strv(&c->supplementary_groups, l, true); if (r < 0) - return -ENOMEM; + return r; joined = strv_join(c->supplementary_groups, " "); if (!joined) @@ -2705,51 +2714,51 @@ int bus_exec_context_set_transient_property( return 1; - } else if (streq(name, "MountAPIVFS")) { - bool b; - - r = bus_set_transient_bool(u, name, &b, message, flags, error); - if (r < 0) - return r; - - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - c->mount_apivfs = b; - c->mount_apivfs_set = true; - } - - return 1; - } else if (streq(name, "WorkingDirectory")) { + _cleanup_free_ char *simplified = NULL; + bool missing_ok = false, is_home = false; const char *s; - bool missing_ok; r = sd_bus_message_read(message, "s", &s); if (r < 0) return r; - if (s[0] == '-') { - missing_ok = true; - s++; - } else - missing_ok = false; + if (!isempty(s)) { + if (s[0] == '-') { + missing_ok = true; + s++; + } - if (!isempty(s) && !streq(s, "~") && !path_is_absolute(s)) - return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects an absolute path or '~'"); + if (streq(s, "~")) + is_home = true; + else { + if (!path_is_absolute(s)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, + "WorkingDirectory= expects an absolute path or '~'"); - if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - if (streq(s, "~")) { - c->working_directory = mfree(c->working_directory); - c->working_directory_home = true; - } else { - r = free_and_strdup(&c->working_directory, empty_to_null(s)); + r = path_simplify_alloc(s, &simplified); if (r < 0) return r; - c->working_directory_home = false; + if (!path_is_normalized(simplified)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, + "WorkingDirectory= expects a normalized path or '~'"); + + if (path_below_api_vfs(simplified)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, + "WorkingDirectory= may not be below /proc/, /sys/ or /dev/"); } + } + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + free_and_replace(c->working_directory, simplified); + c->working_directory_home = is_home; c->working_directory_missing_ok = missing_ok; - unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "WorkingDirectory=%s%s", missing_ok ? "-" : "", s); + + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, + "WorkingDirectory=%s%s", + c->working_directory_missing_ok ? "-" : "", + c->working_directory_home ? "~" : strempty(c->working_directory)); } return 1; @@ -3173,7 +3182,7 @@ int bus_exec_context_set_transient_property( r = strv_extend_strv(dirs, l, true); if (r < 0) - return -ENOMEM; + return r; unit_write_settingf(u, flags, name, "%s=%s", name, joined); } @@ -3200,7 +3209,7 @@ int bus_exec_context_set_transient_property( _cleanup_free_ char *joined = NULL; r = strv_extend_strv(&c->exec_search_path, l, true); if (r < 0) - return -ENOMEM; + return r; joined = strv_join(c->exec_search_path, ":"); if (!joined) return log_oom(); diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h index 5926bdb..4b7cb86 100644 --- a/src/core/dbus-execute.h +++ b/src/core/dbus-execute.h @@ -9,6 +9,7 @@ #define BUS_EXEC_STATUS_VTABLE(prefix, offset, flags) \ BUS_PROPERTY_DUAL_TIMESTAMP(prefix "StartTimestamp", (offset) + offsetof(ExecStatus, start_timestamp), flags), \ BUS_PROPERTY_DUAL_TIMESTAMP(prefix "ExitTimestamp", (offset) + offsetof(ExecStatus, exit_timestamp), flags), \ + BUS_PROPERTY_DUAL_TIMESTAMP(prefix "HandoffTimestamp", (offset) + offsetof(ExecStatus, handoff_timestamp), flags), \ SD_BUS_PROPERTY(prefix "PID", "u", bus_property_get_pid, (offset) + offsetof(ExecStatus, pid), flags), \ SD_BUS_PROPERTY(prefix "Code", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, code), flags), \ SD_BUS_PROPERTY(prefix "Status", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, status), flags) diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c index c88d8c2..693efbb 100644 --- a/src/core/dbus-job.c +++ b/src/core/dbus-job.c @@ -54,7 +54,7 @@ int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error if (!sd_bus_track_contains(j->bus_track, sd_bus_message_get_sender(message))) { /* And for everybody else consult polkit */ - r = bus_verify_manage_units_async(j->unit->manager, message, error); + r = bus_verify_manage_units_async(j->manager, message, error); if (r < 0) return r; if (r == 0) @@ -87,22 +87,23 @@ int bus_job_method_get_waiting_jobs(sd_bus_message *message, void *userdata, sd_ if (r < 0) return r; - for (int i = 0; i < n; i ++) { + FOREACH_ARRAY(i, list, n) { _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + Job *job = *i; - job_path = job_dbus_path(list[i]); + job_path = job_dbus_path(job); if (!job_path) return -ENOMEM; - unit_path = unit_dbus_path(list[i]->unit); + unit_path = unit_dbus_path(job->unit); if (!unit_path) return -ENOMEM; r = sd_bus_message_append(reply, "(usssoo)", - list[i]->id, - list[i]->unit->id, - job_type_to_string(list[i]->type), - job_state_to_string(list[i]->state), + job->id, + job->unit->id, + job_type_to_string(job->type), + job_state_to_string(job->state), job_path, unit_path); if (r < 0) @@ -262,7 +263,7 @@ void bus_job_send_pending_change_signal(Job *j, bool including_new) { if (!j->sent_dbus_new_signal && !including_new) return; - if (MANAGER_IS_RELOADING(j->unit->manager)) + if (MANAGER_IS_RELOADING(j->manager)) return; bus_job_send_change_signal(j); @@ -331,12 +332,12 @@ static int bus_job_allocate_bus_track(Job *j) { if (j->bus_track) return 0; - return sd_bus_track_new(j->unit->manager->api_bus, &j->bus_track, bus_job_track_handler, j); + return sd_bus_track_new(j->manager->api_bus, &j->bus_track, bus_job_track_handler, j); } int bus_job_coldplug_bus_track(Job *j) { - int r; _cleanup_strv_free_ char **deserialized_clients = NULL; + int r; assert(j); @@ -361,7 +362,7 @@ int bus_job_track_sender(Job *j, sd_bus_message *m) { assert(j); assert(m); - if (sd_bus_message_get_bus(m) != j->unit->manager->api_bus) { + if (sd_bus_message_get_bus(m) != j->manager->api_bus) { j->ref_by_private_bus = true; return 0; } diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 745f5cc..2515f54 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -11,6 +11,7 @@ #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-log-control-api.h" +#include "bus-util.h" #include "chase.h" #include "confidential-virt.h" #include "data-fd-util.h" @@ -39,6 +40,7 @@ #include "string-util.h" #include "strv.h" #include "syslog-util.h" +#include "taint.h" #include "user-util.h" #include "version.h" #include "virt.h" @@ -125,13 +127,10 @@ static int property_get_tainted( void *userdata, sd_bus_error *error) { - _cleanup_free_ char *s = NULL; - Manager *m = ASSERT_PTR(userdata); - assert(bus); assert(reply); - s = manager_taint_string(m); + _cleanup_free_ char *s = taint_string(); if (!s) return log_oom(); @@ -464,18 +463,13 @@ static int bus_get_unit_by_name(Manager *m, sd_bus_message *message, const char * its sleeve: if the name is specified empty we use the client's unit. */ if (isempty(name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = sd_bus_creds_get_pid(creds, &pid); + r = bus_query_sender_pidref(message, &pidref); if (r < 0) return r; - u = manager_get_unit_by_pid(m, pid); + u = manager_get_unit_by_pidref(m, &pidref); if (!u) return sd_bus_error_set(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); } else { @@ -542,7 +536,7 @@ static int method_get_unit(sd_bus_message *message, void *userdata, sd_bus_error static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); - pid_t pid; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; Unit *u; int r; @@ -552,27 +546,20 @@ static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bu /* Anyone can call this method */ - r = sd_bus_message_read(message, "u", &pid); + r = sd_bus_message_read(message, "u", &pidref.pid); if (r < 0) return r; - if (pid < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pid); - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); + if (pidref.pid < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pidref.pid); + if (pidref.pid == 0) { + r = bus_query_sender_pidref(message, &pidref); if (r < 0) return r; } - u = manager_get_unit_by_pid(m, pid); + u = manager_get_unit_by_pidref(m, &pidref); if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid); + return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pidref.pid); return reply_unit_path(u, message, error); } @@ -581,41 +568,27 @@ static int method_get_unit_by_invocation_id(sd_bus_message *message, void *userd _cleanup_free_ char *path = NULL; Manager *m = ASSERT_PTR(userdata); sd_id128_t id; - const void *a; Unit *u; - size_t sz; int r; assert(message); /* Anyone can call this method */ - r = sd_bus_message_read_array(message, 'y', &a, &sz); - if (r < 0) - return r; - if (sz == 0) - id = SD_ID128_NULL; - else if (sz == 16) - memcpy(&id, a, sz); - else + if (bus_message_read_id128(message, &id) < 0) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid invocation ID"); if (sd_id128_is_null(id)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = sd_bus_creds_get_pid(creds, &pid); + r = bus_query_sender_pidref(message, &pidref); if (r < 0) return r; - u = manager_get_unit_by_pid(m, pid); + u = manager_get_unit_by_pidref(m, &pidref); if (!u) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, - "Client " PID_FMT " not member of any unit.", pid); + "Client " PID_FMT " not member of any unit.", pidref.pid); } else { u = hashmap_get(m->units_by_invocation_id, &id); if (!u) @@ -797,6 +770,7 @@ static int method_generic_unit_operation( assert(message); assert(m); + assert(handler); /* Read the first argument from the command and pass the operation to the specified per-unit * method. */ @@ -860,11 +834,13 @@ static int method_clean_unit(sd_bus_message *message, void *userdata, sd_bus_err } static int method_freeze_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_generic_unit_operation(message, userdata, error, bus_unit_method_freeze, 0); + /* Only active units can be frozen, which must be properly loaded already */ + return method_generic_unit_operation(message, userdata, error, bus_unit_method_freeze, GENERIC_UNIT_VALIDATE_LOADED); } static int method_thaw_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_generic_unit_operation(message, userdata, error, bus_unit_method_thaw, 0); + /* Same as freeze above */ + return method_generic_unit_operation(message, userdata, error, bus_unit_method_thaw, GENERIC_UNIT_VALIDATE_LOADED); } static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -972,9 +948,10 @@ static int method_list_units_by_names(sd_bus_message *message, void *userdata, s } static int method_get_unit_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { - /* Don't load a unit (since it won't have any processes if it's not loaded), but don't insist on the - * unit being loaded (because even improperly loaded units might still have processes around */ - return method_generic_unit_operation(message, userdata, error, bus_unit_method_get_processes, 0); + /* Don't load a unit actively (since it won't have any processes if it's not loaded), but don't + * insist on the unit being loaded either (because even improperly loaded units might still have + * processes around). */ + return method_generic_unit_operation(message, userdata, error, bus_unit_method_get_processes, /* flags = */ 0); } static int method_attach_processes_to_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -1430,11 +1407,11 @@ static int dump_impl( * operations, and can cause PID1 to stall. So it seems similar enough in terms of security * considerations and impact, and thus use the same access check for dumps which, given the * large amount of data to fetch, can stall PID1 for quite some time. */ - r = mac_selinux_access_check(message, "reload", error); + r = mac_selinux_access_check(message, "reload", /* error = */ NULL); if (r < 0) goto ratelimited; - r = bus_verify_bypass_dump_ratelimit_async(m, message, error); + r = bus_verify_bypass_dump_ratelimit_async(m, message, /* error = */ NULL); if (r < 0) goto ratelimited; if (r == 0) @@ -1469,7 +1446,7 @@ static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *er static int reply_dump_by_fd(sd_bus_message *message, char *dump) { _cleanup_close_ int fd = -EBADF; - fd = acquire_data_fd(dump, strlen(dump), 0); + fd = acquire_data_fd(dump); if (fd < 0) return fd; @@ -1621,10 +1598,10 @@ static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error * return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ /* Write a log message noting the unit or process who requested the Reload() */ - log_caller(message, m, "Reloading"); + log_caller(message, m, "Reload"); /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ - if (!ratelimit_below(&m->reload_ratelimit)) { + if (!ratelimit_below(&m->reload_reexec_ratelimit)) { log_warning("Reloading request rejected due to rate limit."); return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, @@ -1667,7 +1644,15 @@ static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_erro return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ /* Write a log message noting the unit or process who requested the Reexecute() */ - log_caller(message, m, "Reexecuting"); + log_caller(message, m, "Reexecution"); + + /* Check the rate limit after the authorization succeeds, to avoid denial-of-service issues. */ + if (!ratelimit_below(&m->reload_reexec_ratelimit)) { + log_warning("Reexecution request rejected due to rate limit."); + return sd_bus_error_setf(error, + SD_BUS_ERROR_LIMITS_EXCEEDED, + "Reexecute() request rejected due to rate limit."); + } /* We don't send a reply back here, the client should * just wait for us disconnecting. */ @@ -2329,85 +2314,53 @@ static int send_unit_files_changed(sd_bus *bus, void *userdata) { return sd_bus_send(bus, message, NULL); } -/* Create an error reply, using the error information from changes[] - * if possible, and fall back to generating an error from error code c. - * The error message only describes the first error. - */ +static void manager_unit_files_changed(Manager *m, const InstallChange *changes, size_t n_changes) { + int r; + + assert(m); + assert(changes || n_changes == 0); + + if (!install_changes_have_modification(changes, n_changes)) + return; + + /* See comments for this variable in manager.h */ + m->unit_file_state_outdated = true; + + r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send UnitFilesChanged signal, ignoring: %m"); +} + static int install_error( sd_bus_error *error, int c, InstallChange *changes, size_t n_changes) { - CLEANUP_ARRAY(changes, n_changes, install_changes_free); + int r; - for (size_t i = 0; i < n_changes; i++) + /* Create an error reply, using the error information from changes[] if possible, and fall back to + * generating an error from error code c. The error message only describes the first error. */ - /* When making changes here, make sure to also change install_changes_dump() in install.c. */ + assert(changes || n_changes == 0); - switch (changes[i].type) { - case 0 ... _INSTALL_CHANGE_TYPE_MAX: /* not errors */ - break; + CLEANUP_ARRAY(changes, n_changes, install_changes_free); - case -EEXIST: - if (changes[i].source) - 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: - return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, - "Unit file %s is masked.", changes[i].path); - - case -EADDRNOTAVAIL: - return sd_bus_error_setf(error, BUS_ERROR_UNIT_GENERATED, - "Unit %s is transient or generated.", changes[i].path); - - case -ETXTBSY: - 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: - return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING, - "Invalid specifier in %s.", changes[i].path); - - case -EIDRM: - return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING, - "Destination unit %s is a non-template unit.", changes[i].path); - - case -EUCLEAN: - return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING, - "\"%s\" is not a valid unit name.", - changes[i].path); - - case -ELOOP: - 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) - 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: - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, - "Unit file %s does not exist.", changes[i].path); + FOREACH_ARRAY(i, changes, n_changes) { + _cleanup_free_ char *err_message = NULL; + const char *bus_error; - case -EUNATCH: - return sd_bus_error_setf(error, BUS_ERROR_BAD_UNIT_SETTING, - "Cannot resolve specifiers in %s.", changes[i].path); + if (i->type >= 0) + continue; - default: - assert(changes[i].type < 0); /* other errors */ - return sd_bus_error_set_errnof(error, changes[i].type, "File %s: %m", changes[i].path); - } + r = install_change_dump_error(i, &err_message, &bus_error); + if (r == -ENOMEM) + return r; + if (r < 0) + return sd_bus_error_set_errnof(error, r, "File %s: %m", i->path); + + return sd_bus_error_set(error, bus_error, err_message); + } return c < 0 ? c : -EINVAL; } @@ -2426,12 +2379,6 @@ static int reply_install_changes_and_free( CLEANUP_ARRAY(changes, n_changes, install_changes_free); - if (install_changes_have_modification(changes, n_changes)) { - r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send UnitFilesChanged signal: %m"); - } - r = sd_bus_message_new_method_return(message, &reply); if (r < 0) return r; @@ -2446,18 +2393,17 @@ static int reply_install_changes_and_free( if (r < 0) return r; - for (size_t i = 0; i < n_changes; i++) { - - if (changes[i].type < 0) { + FOREACH_ARRAY(i, changes, n_changes) { + if (i->type < 0) { bad = true; continue; } r = sd_bus_message_append( reply, "(sss)", - install_change_type_to_string(changes[i].type), - changes[i].path, - changes[i].source); + install_change_type_to_string(i->type), + i->path, + i->source); if (r < 0) return r; @@ -2521,7 +2467,7 @@ static int method_enable_unit_files_generic( return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = call(m->runtime_scope, flags, NULL, l, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2594,7 +2540,7 @@ static int method_preset_unit_files_with_mode(sd_bus_message *message, void *use return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = unit_file_preset(m->runtime_scope, flags, NULL, l, preset_mode, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2648,7 +2594,7 @@ static int method_disable_unit_files_generic( return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = call(m->runtime_scope, flags, NULL, l, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2691,7 +2637,7 @@ static int method_revert_unit_files(sd_bus_message *message, void *userdata, sd_ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = unit_file_revert(m->runtime_scope, NULL, l, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2722,6 +2668,7 @@ static int method_set_default_target(sd_bus_message *message, void *userdata, sd return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = unit_file_set_default(m->runtime_scope, force ? UNIT_FILE_FORCE : 0, NULL, name, &changes, &n_changes); + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2764,7 +2711,7 @@ static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ r = unit_file_preset_all(m->runtime_scope, flags, NULL, preset_mode, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2804,7 +2751,7 @@ static int method_add_dependency_unit_files(sd_bus_message *message, void *userd return -EINVAL; r = unit_file_add_dependency(m->runtime_scope, flags, NULL, l, target, dep, &changes, &n_changes); - m->unit_file_state_outdated = m->unit_file_state_outdated || n_changes > 0; /* See comments for this variable in manager.h */ + manager_unit_files_changed(m, changes, n_changes); if (r < 0) return install_error(error, r, changes, n_changes); @@ -2933,6 +2880,175 @@ static int method_dump_unit_descriptor_store(sd_bus_message *message, void *user return method_generic_unit_operation(message, userdata, error, bus_service_method_dump_file_descriptor_store, 0); } +static int aux_scope_from_message(Manager *m, sd_bus_message *message, Unit **ret_scope, sd_bus_error *error) { + _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL; + _cleanup_free_ PidRef *pidrefs = NULL; + const char *name; + Unit *from, *scope; + PidRef *main_pid; + CGroupContext *cc; + size_t n_pids = 0; + uint64_t flags; + int r; + + assert(ret_scope); + + r = bus_query_sender_pidref(message, &sender_pidref); + if (r < 0) + return r; + + from = manager_get_unit_by_pidref(m, &sender_pidref); + if (!from) + return sd_bus_error_set(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + + if (!IN_SET(from->type, UNIT_SERVICE, UNIT_SCOPE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Starting auxiliary scope is supported only for service and scope units, refusing."); + + if (!unit_name_is_valid(from->id, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Auxiliary scope can be started only for non-template service units and scope units, refusing."); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid name \"%s\" for auxiliary scope.", name); + + if (unit_name_to_type(name) != UNIT_SCOPE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Name \"%s\" of auxiliary scope doesn't have .scope suffix.", name); + + main_pid = unit_main_pid(from); + + r = sd_bus_message_enter_container(message, 'a', "h"); + if (r < 0) + return r; + + for (;;) { + _cleanup_(pidref_done) PidRef p = PIDREF_NULL; + Unit *unit; + int fd; + + r = sd_bus_message_read(message, "h", &fd); + if (r < 0) + return r; + if (r == 0) + break; + + r = pidref_set_pidfd(&p, fd); + if (r < 0) { + log_unit_warning_errno(from, r, "Failed to create process reference from PIDFD, ignoring: %m"); + continue; + } + + unit = manager_get_unit_by_pidref(m, &p); + if (!unit) { + log_unit_warning(from, "Failed to get unit from PIDFD, ignoring."); + continue; + } + + if (!streq(unit->id, from->id)) { + log_unit_warning(from, "PID " PID_FMT " is not running in the same service as the calling process, ignoring.", p.pid); + continue; + } + + if (pidref_equal(main_pid, &p)) { + log_unit_warning(from, "Main PID cannot be migrated into auxiliary scope, ignoring."); + continue; + } + + if (!GREEDY_REALLOC(pidrefs, n_pids+1)) + return -ENOMEM; + + pidrefs[n_pids++] = TAKE_PIDREF(p); + } + + if (n_pids == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "No processes can be migrated to auxiliary scope."); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); + + r = manager_load_unit(m, name, NULL, error, &scope); + if (r < 0) + return r; + + if (!unit_is_pristine(scope)) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, + "Unit %s was already loaded or has a fragment file.", name); + + r = unit_set_slice(scope, UNIT_GET_SLICE(from)); + if (r < 0) + return r; + + cc = unit_get_cgroup_context(scope); + + r = cgroup_context_copy(cc, unit_get_cgroup_context(from)); + if (r < 0) + return r; + + r = unit_make_transient(scope); + if (r < 0) + return r; + + r = bus_unit_set_properties(scope, message, UNIT_RUNTIME, true, error); + if (r < 0) + return r; + + FOREACH_ARRAY(p, pidrefs, n_pids) { + r = unit_pid_attachable(scope, p, error); + if (r < 0) + return r; + + r = unit_watch_pidref(scope, p, /* exclusive= */ false); + if (r < 0 && r != -EEXIST) + return r; + } + + /* Now load the missing bits of the unit we just created */ + unit_add_to_load_queue(scope); + manager_dispatch_load_queue(m); + + *ret_scope = TAKE_PTR(scope); + + return 1; +} + +static int method_start_aux_scope(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = ASSERT_PTR(userdata); + Unit *u = NULL; /* avoid false maybe-uninitialized warning */ + int r; + + assert(message); + + r = mac_selinux_access_check(message, "start", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = aux_scope_from_message(m, message, &u, error); + if (r < 0) + return r; + + return bus_unit_queue_job(message, u, JOB_START, JOB_REPLACE, 0, error); +} + const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -2948,6 +3064,7 @@ const sd_bus_vtable bus_manager_vtable[] = { BUS_PROPERTY_DUAL_TIMESTAMP("InitRDTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_INITRD]), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("UserspaceTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_USERSPACE]), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("FinishTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_FINISH]), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("ShutdownStartTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START]), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("SecurityStartTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_SECURITY_START]), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("SecurityFinishTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_SECURITY_FINISH]), SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsStartTimestamp", offsetof(Manager, timestamps[MANAGER_TIMESTAMP_GENERATORS_START]), SD_BUS_VTABLE_PROPERTY_CONST), @@ -3045,6 +3162,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_PROPERTY("DefaultOOMPolicy", "s", bus_property_get_oom_policy, offsetof(Manager, defaults.oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("DefaultOOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CtrlAltDelBurstAction", "s", bus_property_get_emergency_action, offsetof(Manager, cad_burst_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SoftRebootsCount", "u", bus_property_get_unsigned, offsetof(Manager, soft_reboots_count), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD_WITH_ARGS("GetUnit", SD_BUS_ARGS("s", name), @@ -3491,6 +3609,11 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_RESULT("a(suuutuusu)", entries), method_dump_unit_descriptor_store, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("StartAuxiliaryScope", + SD_BUS_ARGS("s", name, "ah", pidfds, "t", flags, "a(sv)", properties), + SD_BUS_RESULT("o", job), + method_start_aux_scope, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL_WITH_ARGS("UnitNew", SD_BUS_ARGS("s", id, "o", unit), diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c index 7dbbdd0..f6a9ea9 100644 --- a/src/core/dbus-mount.c +++ b/src/core/dbus-mount.c @@ -6,6 +6,7 @@ #include "dbus-kill.h" #include "dbus-mount.h" #include "dbus-util.h" +#include "fstab-util.h" #include "mount.h" #include "string-util.h" #include "unit.h" @@ -62,7 +63,7 @@ const sd_bus_vtable bus_mount_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Options", "s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Mount, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid.pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -88,6 +89,7 @@ static int bus_mount_set_transient_property( sd_bus_error *error) { Unit *u = UNIT(m); + int r; assert(m); assert(name); @@ -98,8 +100,31 @@ static int bus_mount_set_transient_property( if (streq(name, "Where")) return bus_set_transient_path(u, name, &m->where, message, flags, error); - if (streq(name, "What")) - return bus_set_transient_string(u, name, &m->parameters_fragment.what, message, flags, error); + if (streq(name, "What")) { + _cleanup_free_ char *path = NULL; + const char *v; + + r = sd_bus_message_read(message, "s", &v); + if (r < 0) + return r; + + if (!isempty(v)) { + path = fstab_node_to_udev_node(v); + if (!path) + return -ENOMEM; + + /* path_is_valid is not used - see the comment for config_parse_mount_node */ + if (strlen(path) >= PATH_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Resolved What=%s too long", path); + } + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + free_and_replace(m->parameters_fragment.what, path); + unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "What=%s", strempty(m->parameters_fragment.what)); + } + + return 1; + } if (streq(name, "Options")) return bus_set_transient_string(u, name, &m->parameters_fragment.options, message, flags, error); diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c index 78196a1..165aa65 100644 --- a/src/core/dbus-scope.c +++ b/src/core/dbus-scope.c @@ -3,6 +3,7 @@ #include "alloc-util.h" #include "bus-common-errors.h" #include "bus-get-properties.h" +#include "bus-util.h" #include "dbus-cgroup.h" #include "dbus-kill.h" #include "dbus-manager.h" @@ -84,7 +85,7 @@ static int bus_scope_set_transient_property( return bus_set_transient_oom_policy(u, name, &s->oom_policy, message, flags, error); if (streq(name, "PIDs")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL; unsigned n = 0; r = sd_bus_message_enter_container(message, 'a', "u"); @@ -94,7 +95,7 @@ static int bus_scope_set_transient_property( for (;;) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; uint32_t upid; - pid_t pid; + PidRef *p; r = sd_bus_message_read(message, "u", &upid); if (r < 0) @@ -103,28 +104,27 @@ static int bus_scope_set_transient_property( break; if (upid == 0) { - if (!creds) { - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (!pidref_is_set(&sender_pidref)) { + r = bus_query_sender_pidref(message, &sender_pidref); if (r < 0) return r; } - r = sd_bus_creds_get_pid(creds, &pid); + p = &sender_pidref; + } else { + r = pidref_set_pid(&pidref, upid); if (r < 0) return r; - } else - pid = (uid_t) upid; - r = pidref_set_pid(&pidref, pid); - if (r < 0) - return r; + p = &pidref; + } - r = unit_pid_attachable(u, &pidref, error); + r = unit_pid_attachable(u, p, error); if (r < 0) return r; if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - r = unit_watch_pidref(u, &pidref, /* exclusive= */ false); + r = unit_watch_pidref(u, p, /* exclusive= */ false); if (r < 0 && r != -EEXIST) return r; } diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index cc478f4..ff970df 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -166,9 +166,7 @@ static int bus_service_method_mount(sd_bus_message *message, void *userdata, sd_ r = bus_verify_manage_units_async_full( u, is_image ? "mount-image" : "bind-mount", - CAP_SYS_ADMIN, N_("Authentication is required to mount on '$(unit)'."), - true, message, error); if (r < 0) diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c index e77e9e5..03c5b4a 100644 --- a/src/core/dbus-socket.c +++ b/src/core/dbus-socket.c @@ -86,6 +86,7 @@ const sd_bus_vtable bus_socket_vtable[] = { SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassFileDescriptorsToExec", "b", bus_property_get_bool, offsetof(Socket, pass_fds_to_exec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("PassPacketInfo", "b", bus_property_get_bool, offsetof(Socket, pass_pktinfo), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Timestamping", "s", property_get_timestamping, offsetof(Socket, timestamping), SD_BUS_VTABLE_PROPERTY_CONST), @@ -190,6 +191,9 @@ static int bus_socket_set_transient_property( if (streq(name, "PassCredentials")) return bus_set_transient_bool(u, name, &s->pass_cred, message, flags, error); + if (streq(name, "PassFileDescriptorsToExec")) + return bus_set_transient_bool(u, name, &s->pass_fds_to_exec, message, flags, error); + if (streq(name, "PassSecurity")) return bus_set_transient_bool(u, name, &s->pass_sec, message, flags, error); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index 1a037b7..953cd51 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -7,6 +7,7 @@ #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-polkit.h" +#include "bus-util.h" #include "cgroup-util.h" #include "condition.h" #include "dbus-job.h" @@ -177,7 +178,7 @@ static int property_get_dependencies( return sd_bus_message_close_container(reply); } -static int property_get_requires_mounts_for( +static int property_get_mounts_for( sd_bus *bus, const char *path, const char *interface, @@ -408,9 +409,7 @@ int bus_unit_method_start_generic( r = bus_verify_manage_units_async_full( u, verb, - CAP_SYS_ADMIN, polkit_message_for_job[job_type], - true, message, error); if (r < 0) @@ -491,9 +490,7 @@ int bus_unit_method_enqueue_job(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_manage_units_async_full( u, jtype, - CAP_SYS_ADMIN, polkit_message_for_job[type], - true, message, error); if (r < 0) @@ -549,9 +546,7 @@ int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error * r = bus_verify_manage_units_async_full( u, "kill", - CAP_KILL, N_("Authentication is required to send a UNIX signal to the processes of '$(unit)'."), - true, message, error); if (r < 0) @@ -579,9 +574,7 @@ int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus r = bus_verify_manage_units_async_full( u, "reset-failed", - CAP_SYS_ADMIN, N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), - true, message, error); if (r < 0) @@ -611,9 +604,7 @@ int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_b r = bus_verify_manage_units_async_full( u, "set-property", - CAP_SYS_ADMIN, N_("Authentication is required to set properties on '$(unit)'."), - true, message, error); if (r < 0) @@ -641,9 +632,7 @@ int bus_unit_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *e r = bus_verify_manage_units_async_full( u, "ref", - CAP_SYS_ADMIN, - NULL, - false, + /* polkit_message= */ NULL, message, error); if (r < 0) @@ -712,9 +701,7 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error r = bus_verify_manage_units_async_full( u, "clean", - CAP_DAC_OVERRIDE, N_("Authentication is required to delete files and directories associated with '$(unit)'."), - true, message, error); if (r < 0) @@ -736,22 +723,13 @@ int bus_unit_method_clean(sd_bus_message *message, void *userdata, sd_bus_error } static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userdata, sd_bus_error *error, FreezerAction action) { - const char* perm; - int (*method)(Unit*); Unit *u = ASSERT_PTR(userdata); - bool reply_no_delay = false; int r; assert(message); assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); - if (action == FREEZER_FREEZE) { - perm = "stop"; - method = unit_freeze; - } else { - perm = "start"; - method = unit_thaw; - } + const char *perm = action == FREEZER_FREEZE ? "stop" : "start"; r = mac_selinux_unit_access_check(u, message, perm, error); if (r < 0) @@ -760,9 +738,7 @@ static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userda r = bus_verify_manage_units_async_full( u, perm, - CAP_SYS_ADMIN, N_("Authentication is required to freeze or thaw the processes of '$(unit)' unit."), - true, message, error); if (r < 0) @@ -770,19 +746,21 @@ static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userda if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = method(u); + r = unit_freezer_action(u, action); if (r == -EOPNOTSUPP) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit '%s' does not support freezing.", u->id); + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit does not support freeze/thaw"); if (r == -EBUSY) - return sd_bus_error_set(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job."); + return sd_bus_error_set(error, BUS_ERROR_UNIT_BUSY, "Unit has a pending job"); if (r == -EHOSTDOWN) - return sd_bus_error_set(error, BUS_ERROR_UNIT_INACTIVE, "Unit is inactive."); + return sd_bus_error_set(error, BUS_ERROR_UNIT_INACTIVE, "Unit is not active"); if (r == -EALREADY) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Previously requested freezer operation for unit '%s' is still in progress.", u->id); + return sd_bus_error_set(error, BUS_ERROR_UNIT_BUSY, "Previously requested freezer operation for unit is still in progress"); + if (r == -ECHILD) + return sd_bus_error_set(error, SD_BUS_ERROR_FAILED, "Unit is frozen by a parent slice"); if (r < 0) return r; - if (r == 0) - reply_no_delay = true; + + bool reply_now = r == 0; if (u->pending_freezer_invocation) { bus_unit_send_pending_freezer_message(u, true); @@ -791,7 +769,7 @@ static int bus_unit_method_freezer_generic(sd_bus_message *message, void *userda u->pending_freezer_invocation = sd_bus_message_ref(message); - if (reply_no_delay) { + if (reply_now) { r = bus_unit_send_pending_freezer_message(u, false); if (r < 0) return r; @@ -879,7 +857,8 @@ const sd_bus_vtable bus_unit_vtable[] = { SD_BUS_PROPERTY("StopPropagatedFrom", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SliceOf", "as", property_get_dependencies, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiresMountsFor", "as", property_get_requires_mounts_for, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiresMountsFor", "as", property_get_mounts_for, offsetof(Unit, mounts_for[UNIT_MOUNT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WantsMountsFor", "as", property_get_mounts_for, offsetof(Unit, mounts_for[UNIT_MOUNT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("AccessSELinuxContext", "s", NULL, offsetof(Unit, access_selinux_context), SD_BUS_VTABLE_PROPERTY_CONST), @@ -1235,12 +1214,32 @@ static int property_get_cgroup( * indicates the root cgroup, which we report as "/". c) all * other cases we report as-is. */ - if (u->cgroup_path) - t = empty_to_root(u->cgroup_path); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + + if (crt && crt->cgroup_path) + t = empty_to_root(crt->cgroup_path); return sd_bus_message_append(reply, "s", t); } +static int property_get_cgroup_id( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = ASSERT_PTR(userdata); + + assert(bus); + assert(reply); + + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + return sd_bus_message_append(reply, "t", crt ? crt->cgroup_id : UINT64_C(0)); +} + static int append_process(sd_bus_message *reply, const char *p, PidRef *pid, Set *pids) { _cleanup_free_ char *buf = NULL, *cmdline = NULL; int r; @@ -1299,7 +1298,7 @@ static int append_cgroup(sd_bus_message *reply, const char *p, Set *pids) { * threaded domain cgroup contains the PIDs of all processes in the subtree and is not * readable in the subtree proper. */ - r = cg_read_pidref(f, &pidref); + r = cg_read_pidref(f, &pidref, /* flags = */ 0); if (IN_SET(r, 0, -EOPNOTSUPP)) break; if (r < 0) @@ -1369,8 +1368,10 @@ int bus_unit_method_get_processes(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; - if (u->cgroup_path) { - r = append_cgroup(reply, u->cgroup_path, pids); + CGroupRuntime *crt; + crt = unit_get_cgroup_runtime(u); + if (crt && crt->cgroup_path) { + r = append_cgroup(reply, crt->cgroup_path, pids); if (r < 0) return r; } @@ -1441,6 +1442,28 @@ static int property_get_io_counter( return sd_bus_message_append(reply, "t", value); } +static int property_get_effective_limit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t value = CGROUP_LIMIT_MAX; + Unit *u = ASSERT_PTR(userdata); + ssize_t type; + + assert(bus); + assert(reply); + assert(property); + + assert_se((type = cgroup_effective_limit_type_from_string(property)) >= 0); + (void) unit_get_effective_limit(u, type, &value); + return sd_bus_message_append(reply, "t", value); +} + int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; _cleanup_set_free_ Set *pids = NULL; @@ -1478,7 +1501,7 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u))) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not active, refusing."); - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID, &creds); + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds); if (r < 0) return r; @@ -1489,7 +1512,6 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd _cleanup_(pidref_freep) PidRef *pidref = NULL; uid_t process_uid, sender_uid; uint32_t upid; - pid_t pid; r = sd_bus_message_read(message, "u", &upid); if (r < 0) @@ -1498,13 +1520,14 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd break; if (upid == 0) { - r = sd_bus_creds_get_pid(creds, &pid); + _cleanup_(pidref_done) PidRef p = PIDREF_NULL; + r = bus_creds_get_pidref(creds, &p); if (r < 0) return r; - } else - pid = (uid_t) upid; - r = pidref_new_from_pid(pid, &pidref); + r = pidref_dup(&p, &pidref); + } else + r = pidref_new_from_pid(upid, &pidref); if (r < 0) return r; @@ -1530,9 +1553,9 @@ int bus_unit_method_attach_processes(sd_bus_message *message, void *userdata, sd return sd_bus_error_set_errnof(error, r, "Failed to retrieve process UID: %m"); if (process_uid != sender_uid) - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by client's UID. Refusing.", pid); + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by client's UID. Refusing.", pidref->pid); if (process_uid != u->ref_uid) - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by target unit's UID. Refusing.", pid); + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Process " PID_FMT " not owned by target unit's UID. Refusing.", pidref->pid); } r = set_ensure_consume(&pids, &pidref_hash_ops_free, TAKE_PTR(pidref)); @@ -1555,17 +1578,20 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), - SD_BUS_PROPERTY("ControlGroupId", "t", NULL, offsetof(Unit, cgroup_id), 0), + SD_BUS_PROPERTY("ControlGroupId", "t", property_get_cgroup_id, 0, 0), SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), SD_BUS_PROPERTY("MemoryPeak", "t", property_get_memory_accounting, 0, 0), SD_BUS_PROPERTY("MemorySwapCurrent", "t", property_get_memory_accounting, 0, 0), SD_BUS_PROPERTY("MemorySwapPeak", "t", property_get_memory_accounting, 0, 0), SD_BUS_PROPERTY("MemoryZSwapCurrent", "t", property_get_memory_accounting, 0, 0), SD_BUS_PROPERTY("MemoryAvailable", "t", property_get_available_memory, 0, 0), + SD_BUS_PROPERTY("EffectiveMemoryMax", "t", property_get_effective_limit, 0, 0), + SD_BUS_PROPERTY("EffectiveMemoryHigh", "t", property_get_effective_limit, 0, 0), SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), SD_BUS_PROPERTY("EffectiveCPUs", "ay", property_get_cpuset_cpus, 0, 0), SD_BUS_PROPERTY("EffectiveMemoryNodes", "ay", property_get_cpuset_mems, 0, 0), SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0), + SD_BUS_PROPERTY("EffectiveTasksMax", "t", property_get_effective_limit, 0, 0), SD_BUS_PROPERTY("IPIngressBytes", "t", property_get_ip_counter, 0, 0), SD_BUS_PROPERTY("IPIngressPackets", "t", property_get_ip_counter, 0, 0), SD_BUS_PROPERTY("IPEgressBytes", "t", property_get_ip_counter, 0, 0), @@ -1576,16 +1602,16 @@ const sd_bus_vtable bus_unit_cgroup_vtable[] = { SD_BUS_PROPERTY("IOWriteOperations", "t", property_get_io_counter, 0, 0), SD_BUS_METHOD_WITH_ARGS("GetProcesses", - SD_BUS_NO_ARGS, - SD_BUS_ARGS("a(sus)", processes), - bus_unit_method_get_processes, - SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_NO_ARGS, + SD_BUS_ARGS("a(sus)", processes), + bus_unit_method_get_processes, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("AttachProcesses", - SD_BUS_ARGS("s", subcgroup, "au", pids), - SD_BUS_NO_RESULT, - bus_unit_method_attach_processes, - SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_ARGS("s", subcgroup, "au", pids), + SD_BUS_NO_RESULT, + bus_unit_method_attach_processes, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_VTABLE_END }; @@ -2210,7 +2236,7 @@ static int bus_unit_set_transient_property( return bus_set_transient_emergency_action(u, name, &u->job_timeout_action, message, flags, error); if (streq(name, "JobTimeoutRebootArgument")) - return bus_set_transient_string(u, name, &u->job_timeout_reboot_arg, message, flags, error); + return bus_set_transient_reboot_parameter(u, name, &u->job_timeout_reboot_arg, message, flags, error); if (streq(name, "StartLimitIntervalUSec")) return bus_set_transient_usec(u, name, &u->start_ratelimit.interval, message, flags, error); @@ -2234,7 +2260,7 @@ static int bus_unit_set_transient_property( return bus_set_transient_exit_status(u, name, &u->success_action_exit_status, message, flags, error); if (streq(name, "RebootArgument")) - return bus_set_transient_string(u, name, &u->reboot_arg, message, flags, error); + return bus_set_transient_reboot_parameter(u, name, &u->reboot_arg, message, flags, error); if (streq(name, "CollectMode")) return bus_set_transient_collect_mode(u, name, &u->collect_mode, message, flags, error); @@ -2261,7 +2287,9 @@ static int bus_unit_set_transient_property( u->documentation = strv_free(u->documentation); unit_write_settingf(u, flags, name, "%s=", name); } else { - strv_extend_strv(&u->documentation, l, false); + r = strv_extend_strv(&u->documentation, l, /* filter_duplicates= */ false); + if (r < 0) + return r; STRV_FOREACH(p, l) unit_write_settingf(u, flags, name, "%s=%s", name, *p); @@ -2308,7 +2336,7 @@ static int bus_unit_set_transient_property( return 1; - } else if (streq(name, "RequiresMountsFor")) { + } else if (STR_IN_SET(name, "RequiresMountsFor", "WantsMountsFor")) { _cleanup_strv_free_ char **l = NULL; r = sd_bus_message_read_strv(message, &l); @@ -2328,9 +2356,9 @@ static int bus_unit_set_transient_property( return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path specified in %s is not normalized: %s", name, *p); if (!UNIT_WRITE_FLAGS_NOOP(flags)) { - r = unit_require_mounts_for(u, *p, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, *p, UNIT_DEPENDENCY_FILE, unit_mount_dependency_type_from_string(name)); if (r < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to add required mount \"%s\": %m", *p); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to add requested mount \"%s\": %m", *p); unit_write_settingf(u, flags, name, "%s=%s", name, *p); } diff --git a/src/core/dbus-util.c b/src/core/dbus-util.c index d680a64..b871d89 100644 --- a/src/core/dbus-util.c +++ b/src/core/dbus-util.c @@ -6,6 +6,7 @@ #include "escape.h" #include "parse-util.h" #include "path-util.h" +#include "reboot-util.h" #include "unit-printf.h" #include "user-util.h" #include "unit.h" @@ -39,6 +40,7 @@ static bool valid_user_group_name_or_id_relaxed(const char *u) { BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(user_relaxed, valid_user_group_name_or_id_relaxed); BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(path, path_is_absolute); +BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(reboot_parameter, reboot_parameter_is_valid); int bus_set_transient_string( Unit *u, @@ -151,9 +153,7 @@ int bus_set_transient_usec_internal( int bus_verify_manage_units_async_full( Unit *u, const char *verb, - int capability, const char *polkit_message, - bool interactive, sd_bus_message *call, sd_bus_error *error) { @@ -171,11 +171,8 @@ int bus_verify_manage_units_async_full( return bus_verify_polkit_async( call, - capability, "org.freedesktop.systemd1.manage-units", details, - interactive, - UID_INVALID, &u->manager->polkit_registry, error); } diff --git a/src/core/dbus-util.h b/src/core/dbus-util.h index 9464b25..0fc3a94 100644 --- a/src/core/dbus-util.h +++ b/src/core/dbus-util.h @@ -239,6 +239,7 @@ int bus_set_transient_mode_t(Unit *u, const char *name, mode_t *p, sd_bus_messag int bus_set_transient_unsigned(Unit *u, const char *name, unsigned *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_user_relaxed(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_path(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); +int bus_set_transient_reboot_parameter(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_string(Unit *u, const char *name, char **p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_bool(Unit *u, const char *name, bool *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); int bus_set_transient_tristate(Unit *u, const char *name, int *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error); @@ -249,7 +250,7 @@ static inline int bus_set_transient_usec(Unit *u, const char *name, usec_t *p, s static inline int bus_set_transient_usec_fix_0(Unit *u, const char *name, usec_t *p, sd_bus_message *message, UnitWriteFlags flags, sd_bus_error *error) { return bus_set_transient_usec_internal(u, name, p, true, message, flags, error); } -int bus_verify_manage_units_async_full(Unit *u, const char *verb, int capability, const char *polkit_message, bool interactive, sd_bus_message *call, sd_bus_error *error); +int bus_verify_manage_units_async_full(Unit *u, const char *verb, const char *polkit_message, sd_bus_message *call, sd_bus_error *error); int bus_read_mount_options(sd_bus_message *message, sd_bus_error *error, MountOptions **ret_options, char **ret_format_str, const char *separator); diff --git a/src/core/dbus.c b/src/core/dbus.c index ba2cec4..1c6f6fc 100644 --- a/src/core/dbus.c +++ b/src/core/dbus.c @@ -232,6 +232,8 @@ static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_er return 0; path = sd_bus_message_get_path(message); + if (!path) + return 0; if (object_path_startswith("/org/freedesktop/systemd1", path)) { r = mac_selinux_access_check(message, verb, error); @@ -241,25 +243,20 @@ static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_er return 0; } - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return 0; + if (streq(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = sd_bus_creds_get_pid(creds, &pid); + r = bus_query_sender_pidref(message, &pidref); if (r < 0) return 0; - u = manager_get_unit_by_pid(m, pid); + u = manager_get_unit_by_pidref(m, &pidref); } else { r = manager_get_job_from_dbus_path(m, path, &j); if (r >= 0) u = j->unit; else - manager_load_unit_from_dbus_path(m, path, NULL, &u); + (void) manager_load_unit_from_dbus_path(m, path, NULL, &u); } if (!u) return 0; @@ -280,24 +277,19 @@ static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_ assert(bus); assert(path); - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + if (streq(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; sd_bus_message *message; - pid_t pid; message = sd_bus_get_current_message(bus); if (!message) return 0; - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); + r = bus_query_sender_pidref(message, &pidref); if (r < 0) return r; - u = manager_get_unit_by_pid(m, pid); + u = manager_get_unit_by_pidref(m, &pidref); if (!u) return 0; } else { @@ -739,7 +731,7 @@ static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void log_debug("Accepting direct incoming connection from " PID_FMT " (%s) [%s]", pid, strna(comm), strna(description)); } - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC); if (r < 0) { log_warning_errno(r, "Failed to attach new connection bus to event loop: %m"); return 0; @@ -847,7 +839,7 @@ int bus_init_api(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to connect to API bus: %m"); - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_error_errno(r, "Failed to attach API bus to event loop: %m"); @@ -904,7 +896,7 @@ int bus_init_system(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to connect to system bus: %m"); - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + r = sd_bus_attach_event(bus, m->event, EVENT_PRIORITY_IPC); if (r < 0) return log_error_errno(r, "Failed to attach system bus to event loop: %m"); @@ -1073,7 +1065,7 @@ void bus_done(Manager *m) { assert(!m->subscribed); m->deserialized_subscribed = strv_free(m->deserialized_subscribed); - bus_verify_polkit_async_registry_free(m->polkit_registry); + m->polkit_registry = hashmap_free(m->polkit_registry); } int bus_fdset_add_all(Manager *m, FDSet *fds) { @@ -1121,31 +1113,29 @@ int bus_foreach_bus( int (*send_message)(sd_bus *bus, void *userdata), void *userdata) { - sd_bus *b; - int r, ret = 0; + int r = 0; + + assert(m); + assert(send_message); /* Send to all direct buses, unconditionally */ + sd_bus *b; SET_FOREACH(b, m->private_buses) { /* Don't bother with enqueuing these messages to clients that haven't started yet */ if (sd_bus_is_ready(b) <= 0) continue; - r = send_message(b, userdata); - if (r < 0) - ret = r; + RET_GATHER(r, send_message(b, userdata)); } /* Send to API bus, but only if somebody is subscribed */ if (m->api_bus && (sd_bus_track_count(m->subscribed) > 0 || - sd_bus_track_count(subscribed2) > 0)) { - r = send_message(m->api_bus, userdata); - if (r < 0) - ret = r; - } + sd_bus_track_count(subscribed2) > 0)) + RET_GATHER(r, send_message(m->api_bus, userdata)); - return ret; + return r; } void bus_track_serialize(sd_bus_track *t, FILE *f, const char *prefix) { @@ -1189,22 +1179,46 @@ int bus_track_coldplug(Manager *m, sd_bus_track **t, bool recursive, char **l) { } int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-units", NULL, false, UID_INVALID, &m->polkit_registry, error); + return bus_verify_polkit_async( + call, + "org.freedesktop.systemd1.manage-units", + /* details= */ NULL, + &m->polkit_registry, + error); } int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-unit-files", NULL, false, UID_INVALID, &m->polkit_registry, error); + return bus_verify_polkit_async( + call, + "org.freedesktop.systemd1.manage-unit-files", + /* details= */ NULL, + &m->polkit_registry, + error); } int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.reload-daemon", NULL, false, UID_INVALID, &m->polkit_registry, error); + return bus_verify_polkit_async( + call, + "org.freedesktop.systemd1.reload-daemon", + /* details= */ NULL, + &m->polkit_registry, error); } int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error); + return bus_verify_polkit_async( + call, + "org.freedesktop.systemd1.set-environment", + /* details= */ NULL, + &m->polkit_registry, + error); } int bus_verify_bypass_dump_ratelimit_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.bypass-dump-ratelimit", NULL, false, UID_INVALID, &m->polkit_registry, error); + return bus_verify_polkit_async( + call, + "org.freedesktop.systemd1.bypass-dump-ratelimit", + /* details= */ NULL, + &m->polkit_registry, + error); } uint64_t manager_bus_n_queued_write(Manager *m) { diff --git a/src/core/device.c b/src/core/device.c index 6b2d7c3..d856767 100644 --- a/src/core/device.c +++ b/src/core/device.c @@ -119,10 +119,9 @@ static int device_set_sysfs(Device *d, const char *sysfs) { } static void device_init(Unit *u) { - Device *d = DEVICE(u); + Device *d = ASSERT_PTR(DEVICE(u)); - assert(d); - assert(UNIT(d)->load_state == UNIT_STUB); + assert(u->load_state == UNIT_STUB); /* In contrast to all other unit types we timeout jobs waiting * for devices by default. This is because they otherwise wait @@ -137,9 +136,7 @@ static void device_init(Unit *u) { } static void device_done(Unit *u) { - Device *d = DEVICE(u); - - assert(d); + Device *d = ASSERT_PTR(DEVICE(u)); device_unset_sysfs(d); d->deserialized_sysfs = mfree(d->deserialized_sysfs); @@ -258,9 +255,8 @@ static void device_update_found_by_name(Manager *m, const char *path, DeviceFoun } static int device_coldplug(Unit *u) { - Device *d = DEVICE(u); + Device *d = ASSERT_PTR(DEVICE(u)); - assert(d); assert(d->state == DEVICE_DEAD); /* First, let's put the deserialized state and found mask into effect, if we have it. */ @@ -336,9 +332,7 @@ static int device_coldplug(Unit *u) { } static void device_catchup(Unit *u) { - Device *d = DEVICE(u); - - assert(d); + Device *d = ASSERT_PTR(DEVICE(u)); /* Second, let's update the state with the enumerated state */ device_update_found_one(d, d->enumerated_found, DEVICE_FOUND_MASK); @@ -405,11 +399,9 @@ static int device_found_from_string_many(const char *name, DeviceFound *ret) { } static int device_serialize(Unit *u, FILE *f, FDSet *fds) { + Device *d = ASSERT_PTR(DEVICE(u)); _cleanup_free_ char *s = NULL; - Device *d = DEVICE(u); - assert(d); - assert(u); assert(f); assert(fds); @@ -428,11 +420,9 @@ static int device_serialize(Unit *u, FILE *f, FDSet *fds) { } static int device_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Device *d = DEVICE(u); + Device *d = ASSERT_PTR(DEVICE(u)); int r; - assert(d); - assert(u); assert(key); assert(value); assert(fds); @@ -472,10 +462,11 @@ static int device_deserialize_item(Unit *u, const char *key, const char *value, } static void device_dump(Unit *u, FILE *f, const char *prefix) { - Device *d = DEVICE(u); + Device *d = ASSERT_PTR(DEVICE(u)); _cleanup_free_ char *s = NULL; - assert(d); + assert(f); + assert(prefix); (void) device_found_to_string_many(d->found, &s); @@ -495,15 +486,15 @@ static void device_dump(Unit *u, FILE *f, const char *prefix) { } static UnitActiveState device_active_state(Unit *u) { - assert(u); + Device *d = ASSERT_PTR(DEVICE(u)); - return state_translation_table[DEVICE(u)->state]; + return state_translation_table[d->state]; } static const char *device_sub_state_to_string(Unit *u) { - assert(u); + Device *d = ASSERT_PTR(DEVICE(u)); - return device_state_to_string(DEVICE(u)->state); + return device_state_to_string(d->state); } static int device_update_description(Unit *u, sd_device *dev, const char *path) { @@ -538,12 +529,11 @@ static int device_update_description(Unit *u, sd_device *dev, const char *path) } static int device_add_udev_wants(Unit *u, sd_device *dev) { + Device *d = ASSERT_PTR(DEVICE(u)); _cleanup_strv_free_ char **added = NULL; const char *wants, *property; - Device *d = DEVICE(u); int r; - assert(d); assert(dev); property = MANAGER_IS_USER(u->manager) ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; @@ -646,6 +636,8 @@ static void device_upgrade_mount_deps(Unit *u) { /* Let's upgrade Requires= to BindsTo= on us. (Used when SYSTEMD_MOUNT_DEVICE_BOUND is set) */ + assert(u); + HASHMAP_FOREACH_KEY(v, other, unit_get_dependencies(u, UNIT_REQUIRED_BY)) { if (other->type != UNIT_MOUNT) continue; @@ -706,16 +698,18 @@ static int device_setup_unit(Manager *m, sd_device *dev, const char *path, bool unit_add_to_load_queue(u); } - if (!DEVICE(u)->path) { - DEVICE(u)->path = strdup(path); - if (!DEVICE(u)->path) + Device *d = ASSERT_PTR(DEVICE(u)); + + if (!d->path) { + d->path = strdup(path); + if (!d->path) return log_oom(); } /* If this was created via some dependency and has not actually been seen yet ->sysfs will not be * initialized. Hence initialize it if necessary. */ if (sysfs) { - r = device_set_sysfs(DEVICE(u), sysfs); + r = device_set_sysfs(d, sysfs); if (r < 0) return log_unit_error_errno(u, r, "Failed to set sysfs path %s: %m", sysfs); @@ -730,11 +724,11 @@ static int device_setup_unit(Manager *m, sd_device *dev, const char *path, bool * by systemd before the device appears on its radar. In this case the device unit is partially * initialized and includes the deps on the mount unit but at that time the "bind mounts" flag wasn't * present. Fix this up now. */ - if (dev && device_is_bound_by_mounts(DEVICE(u), dev)) + if (dev && device_is_bound_by_mounts(d, dev)) device_upgrade_mount_deps(u); if (units) { - r = set_ensure_put(units, NULL, DEVICE(u)); + r = set_ensure_put(units, NULL, d); if (r < 0) return log_unit_error_errno(u, r, "Failed to store unit: %m"); } @@ -950,10 +944,7 @@ static int device_setup_units(Manager *m, sd_device *dev, Set **ready_units, Set } static Unit *device_following(Unit *u) { - Device *d = DEVICE(u); - Device *first = NULL; - - assert(d); + Device *d = ASSERT_PTR(DEVICE(u)), *first = NULL; if (startswith(u->id, "sys-")) return NULL; @@ -973,16 +964,15 @@ static Unit *device_following(Unit *u) { return UNIT(first); } -static int device_following_set(Unit *u, Set **_set) { - Device *d = DEVICE(u); +static int device_following_set(Unit *u, Set **ret) { + Device *d = ASSERT_PTR(DEVICE(u)); _cleanup_set_free_ Set *set = NULL; int r; - assert(d); - assert(_set); + assert(ret); if (LIST_JUST_US(same_sysfs, d)) { - *_set = NULL; + *ret = NULL; return 0; } @@ -1002,7 +992,7 @@ static int device_following_set(Unit *u, Set **_set) { return r; } - *_set = TAKE_PTR(set); + *ret = TAKE_PTR(set); return 1; } @@ -1061,6 +1051,9 @@ static void device_enumerate(Manager *m) { _cleanup_set_free_ Set *ready_units = NULL, *not_ready_units = NULL; Device *d; + if (device_is_processed(dev) <= 0) + continue; + if (device_setup_units(m, dev, &ready_units, ¬_ready_units) < 0) continue; diff --git a/src/core/dynamic-user.c b/src/core/dynamic-user.c index 2bf9094..11de2ba 100644 --- a/src/core/dynamic-user.c +++ b/src/core/dynamic-user.c @@ -20,7 +20,7 @@ #include "stdio-util.h" #include "string-util.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" /* Takes a value generated randomly or by hashing and turns it into a UID in the right range */ @@ -143,7 +143,6 @@ static int dynamic_user_acquire(Manager *m, const char *name, DynamicUser** ret) } static int make_uid_symlinks(uid_t uid, const char *name, bool b) { - char path1[STRLEN("/run/systemd/dynamic-uid/direct:") + DECIMAL_STR_MAX(uid_t) + 1]; const char *path2; int r = 0, k; @@ -293,8 +292,8 @@ static int pick_uid(char **suggested_paths, const char *name, uid_t *ret_uid) { } /* Some superficial check whether this UID/GID might already be taken by some static user */ - if (getpwuid(candidate) || - getgrgid((gid_t) candidate) || + if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0 || + getgrgid_malloc((gid_t) candidate, /* ret= */ NULL) >= 0 || search_ipc(candidate, (gid_t) candidate) != 0) { (void) unlink(lock_path); continue; @@ -419,30 +418,26 @@ static int dynamic_user_realize( /* First, let's parse this as numeric UID */ r = parse_uid(d->name, &num); if (r < 0) { - struct passwd *p; - struct group *g; + _cleanup_free_ struct passwd *p = NULL; + _cleanup_free_ struct group *g = NULL; if (is_user) { /* OK, this is not a numeric UID. Let's see if there's a user by this name */ - p = getpwnam(d->name); - if (p) { + if (getpwnam_malloc(d->name, &p) >= 0) { num = p->pw_uid; gid = p->pw_gid; } else { /* if the user does not exist but the group with the same name exists, refuse operation */ - g = getgrnam(d->name); - if (g) + if (getgrnam_malloc(d->name, /* ret= */ NULL) >= 0) return -EILSEQ; } } else { /* Let's see if there's a group by this name */ - g = getgrnam(d->name); - if (g) + if (getgrnam_malloc(d->name, &g) >= 0) num = (uid_t) g->gr_gid; else { /* if the group does not exist but the user with the same name exists, refuse operation */ - p = getpwnam(d->name); - if (p) + if (getpwnam_malloc(d->name, /* ret= */ NULL) >= 0) return -EILSEQ; } } @@ -484,13 +479,12 @@ static int dynamic_user_realize( uid_lock_fd = new_uid_lock_fd; } } else if (is_user && !uid_is_dynamic(num)) { - struct passwd *p; + _cleanup_free_ struct passwd *p = NULL; /* Statically allocated user may have different uid and gid. So, let's obtain the gid. */ - errno = 0; - p = getpwuid(num); - if (!p) - return errno_or_else(ESRCH); + r = getpwuid_malloc(num, &p); + if (r < 0) + return r; gid = p->pw_gid; } @@ -658,7 +652,7 @@ void dynamic_user_deserialize_one(Manager *m, const char *value, FDSet *fds, Dyn /* Parse the serialization again, after a daemon reload */ - r = extract_many_words(&value, NULL, 0, &name, &s0, &s1, NULL); + r = extract_many_words(&value, NULL, 0, &name, &s0, &s1); if (r != 3 || !isempty(value)) { log_debug("Unable to parse dynamic user line."); return; @@ -761,7 +755,6 @@ int dynamic_user_lookup_name(Manager *m, const char *name, uid_t *ret) { 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(m); @@ -784,20 +777,14 @@ int dynamic_creds_make(Manager *m, const char *user, const char *group, DynamicC r = dynamic_user_acquire(m, user, &creds->user); if (r < 0) return r; - - acquired = true; } - if (creds->user && (!group || streq_ptr(user, group))) - creds->group = dynamic_user_ref(creds->user); - else if (group) { + if (group && !streq_ptr(user, group)) { r = dynamic_user_acquire(m, group, &creds->group); - if (r < 0) { - if (acquired) - creds->user = dynamic_user_unref(creds->user); + if (r < 0) return r; - } - } + } else + creds->group = ASSERT_PTR(dynamic_user_ref(creds->user)); *ret = TAKE_PTR(creds); diff --git a/src/core/emergency-action.c b/src/core/emergency-action.c index e2cd931..dbda6e5 100644 --- a/src/core/emergency-action.c +++ b/src/core/emergency-action.c @@ -13,22 +13,22 @@ #include "virt.h" static const char* const emergency_action_table[_EMERGENCY_ACTION_MAX] = { - [EMERGENCY_ACTION_NONE] = "none", - [EMERGENCY_ACTION_REBOOT] = "reboot", - [EMERGENCY_ACTION_REBOOT_FORCE] = "reboot-force", - [EMERGENCY_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", - [EMERGENCY_ACTION_POWEROFF] = "poweroff", - [EMERGENCY_ACTION_POWEROFF_FORCE] = "poweroff-force", + [EMERGENCY_ACTION_NONE] = "none", + [EMERGENCY_ACTION_EXIT] = "exit", + [EMERGENCY_ACTION_EXIT_FORCE] = "exit-force", + [EMERGENCY_ACTION_REBOOT] = "reboot", + [EMERGENCY_ACTION_REBOOT_FORCE] = "reboot-force", + [EMERGENCY_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", + [EMERGENCY_ACTION_POWEROFF] = "poweroff", + [EMERGENCY_ACTION_POWEROFF_FORCE] = "poweroff-force", [EMERGENCY_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate", - [EMERGENCY_ACTION_EXIT] = "exit", - [EMERGENCY_ACTION_EXIT_FORCE] = "exit-force", - [EMERGENCY_ACTION_SOFT_REBOOT] = "soft-reboot", - [EMERGENCY_ACTION_SOFT_REBOOT_FORCE] = "soft-reboot-force", - [EMERGENCY_ACTION_KEXEC] = "kexec", - [EMERGENCY_ACTION_KEXEC_FORCE] = "kexec-force", - [EMERGENCY_ACTION_HALT] = "halt", - [EMERGENCY_ACTION_HALT_FORCE] = "halt-force", - [EMERGENCY_ACTION_HALT_IMMEDIATE] = "halt-immediate", + [EMERGENCY_ACTION_SOFT_REBOOT] = "soft-reboot", + [EMERGENCY_ACTION_SOFT_REBOOT_FORCE] = "soft-reboot-force", + [EMERGENCY_ACTION_KEXEC] = "kexec", + [EMERGENCY_ACTION_KEXEC_FORCE] = "kexec-force", + [EMERGENCY_ACTION_HALT] = "halt", + [EMERGENCY_ACTION_HALT_FORCE] = "halt-force", + [EMERGENCY_ACTION_HALT_IMMEDIATE] = "halt-immediate", }; static void log_and_status(Manager *m, bool warn, const char *message, const char *reason) { @@ -216,7 +216,7 @@ int parse_emergency_action( if (x < 0) return -EINVAL; - if (runtime_scope != RUNTIME_SCOPE_SYSTEM && x != EMERGENCY_ACTION_NONE && x < _EMERGENCY_ACTION_FIRST_USER_ACTION) + if (runtime_scope != RUNTIME_SCOPE_SYSTEM && x > _EMERGENCY_ACTION_LAST_USER_ACTION) return -EOPNOTSUPP; *ret = x; diff --git a/src/core/emergency-action.h b/src/core/emergency-action.h index 33e0ec6..6bec475 100644 --- a/src/core/emergency-action.h +++ b/src/core/emergency-action.h @@ -7,15 +7,15 @@ typedef enum EmergencyAction { EMERGENCY_ACTION_NONE, + EMERGENCY_ACTION_EXIT, + EMERGENCY_ACTION_EXIT_FORCE, + _EMERGENCY_ACTION_LAST_USER_ACTION = EMERGENCY_ACTION_EXIT_FORCE, EMERGENCY_ACTION_REBOOT, EMERGENCY_ACTION_REBOOT_FORCE, EMERGENCY_ACTION_REBOOT_IMMEDIATE, EMERGENCY_ACTION_POWEROFF, EMERGENCY_ACTION_POWEROFF_FORCE, EMERGENCY_ACTION_POWEROFF_IMMEDIATE, - EMERGENCY_ACTION_EXIT, - _EMERGENCY_ACTION_FIRST_USER_ACTION = EMERGENCY_ACTION_EXIT, - EMERGENCY_ACTION_EXIT_FORCE, EMERGENCY_ACTION_SOFT_REBOOT, EMERGENCY_ACTION_SOFT_REBOOT_FORCE, EMERGENCY_ACTION_KEXEC, diff --git a/src/core/exec-credential.c b/src/core/exec-credential.c index 6bcfb68..f4cff57 100644 --- a/src/core/exec-credential.c +++ b/src/core/exec-credential.c @@ -9,6 +9,7 @@ #include "fileio.h" #include "glob-util.h" #include "io-util.h" +#include "iovec-util.h" #include "label-util.h" #include "mkdir-label.h" #include "mount-util.h" @@ -48,6 +49,12 @@ DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( char, string_hash_func, string_compare_func, ExecLoadCredential, exec_load_credential_free); +bool exec_params_need_credentials(const ExecParameters *p) { + assert(p); + + return p->flags & (EXEC_SETUP_CREDENTIALS|EXEC_SETUP_CREDENTIALS_FRESH); +} + bool exec_context_has_credentials(const ExecContext *c) { assert(c); @@ -56,16 +63,15 @@ bool exec_context_has_credentials(const ExecContext *c) { !set_isempty(c->import_credentials); } -bool exec_context_has_encrypted_credentials(ExecContext *c) { - ExecLoadCredential *load_cred; - ExecSetCredential *set_cred; - +bool exec_context_has_encrypted_credentials(const ExecContext *c) { assert(c); + const ExecLoadCredential *load_cred; HASHMAP_FOREACH(load_cred, c->load_credentials) if (load_cred->encrypted) return true; + const ExecSetCredential *set_cred; HASHMAP_FOREACH(set_cred, c->set_credentials) if (set_cred->encrypted) return true; @@ -106,7 +112,7 @@ int exec_context_get_credential_directory( assert(unit); assert(ret); - if (!exec_context_has_credentials(context)) { + if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) { *ret = NULL; return 0; } @@ -172,6 +178,10 @@ static int write_credential( _cleanup_close_ int fd = -EBADF; int r; + assert(dfd >= 0); + assert(id); + assert(data || size == 0); + r = tempfn_random_child("", "cred", &tmp); if (r < 0) return r; @@ -224,7 +234,6 @@ typedef enum CredentialSearchPath { } CredentialSearchPath; static char **credential_search_path(const ExecParameters *params, CredentialSearchPath path) { - _cleanup_strv_free_ char **l = NULL; assert(params); @@ -243,9 +252,8 @@ static char **credential_search_path(const ExecParameters *params, CredentialSea } if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) { - if (params->received_credentials_directory) - if (strv_extend(&l, params->received_credentials_directory) < 0) - return NULL; + if (strv_extend(&l, params->received_credentials_directory) < 0) + return NULL; if (strv_extend_strv(&l, CONF_PATHS_STRV("credstore"), /* filter_duplicates= */ true) < 0) return NULL; @@ -271,20 +279,29 @@ static int maybe_decrypt_and_write_credential( size_t size, uint64_t *left) { - _cleanup_free_ void *plaintext = NULL; + _cleanup_(iovec_done_erase) struct iovec plaintext = {}; size_t add; int r; - if (encrypted) { - size_t plaintext_size = 0; + assert(dir_fd >= 0); + assert(id); + assert(left); - r = decrypt_credential_and_warn(id, now(CLOCK_REALTIME), NULL, NULL, data, size, - &plaintext, &plaintext_size); + if (encrypted) { + r = decrypt_credential_and_warn( + id, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + getuid(), + &IOVEC_MAKE(data, size), + CREDENTIAL_ANY_SCOPE, + &plaintext); if (r < 0) return r; - data = plaintext; - size = plaintext_size; + data = plaintext.iov_base; + size = plaintext.iov_len; } add = strlen(id) + size; @@ -302,7 +319,7 @@ static int maybe_decrypt_and_write_credential( static int load_credential_glob( const char *path, bool encrypted, - char **search_path, + char * const *search_path, ReadFullFileFlags flags, int write_dfd, uid_t uid, @@ -312,6 +329,11 @@ static int load_credential_glob( int r; + assert(path); + assert(search_path); + assert(write_dfd >= 0); + assert(left); + STRV_FOREACH(d, search_path) { _cleanup_globfree_ glob_t pglob = {}; _cleanup_free_ char *j = NULL; @@ -326,38 +348,36 @@ static int load_credential_glob( if (r < 0) return r; - for (size_t n = 0; n < pglob.gl_pathc; n++) { + FOREACH_ARRAY(p, pglob.gl_pathv, pglob.gl_pathc) { _cleanup_free_ char *fn = NULL; _cleanup_(erase_and_freep) char *data = NULL; size_t size; /* path is absolute, hence pass AT_FDCWD as nop dir fd here */ r = read_full_file_full( - AT_FDCWD, - pglob.gl_pathv[n], - UINT64_MAX, - encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX, - flags, - NULL, - &data, &size); + AT_FDCWD, + *p, + UINT64_MAX, + encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX, + flags, + NULL, + &data, &size); if (r < 0) - return log_debug_errno(r, "Failed to read credential '%s': %m", - pglob.gl_pathv[n]); + return log_debug_errno(r, "Failed to read credential '%s': %m", *p); - r = path_extract_filename(pglob.gl_pathv[n], &fn); + r = path_extract_filename(*p, &fn); if (r < 0) - return log_debug_errno(r, "Failed to extract filename from '%s': %m", - pglob.gl_pathv[n]); + return log_debug_errno(r, "Failed to extract filename from '%s': %m", *p); r = maybe_decrypt_and_write_credential( - write_dfd, - fn, - encrypted, - uid, - gid, - ownership_ok, - data, size, - left); + write_dfd, + fn, + encrypted, + uid, + gid, + ownership_ok, + data, size, + left); if (r == -EEXIST) continue; if (r < 0) @@ -423,7 +443,7 @@ static int load_credential( /* Pass some minimal info about the unit and the credential name we are looking to acquire * via the source socket address in case we read off an AF_UNIX socket. */ - if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, id) < 0) + if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), unit, id) < 0) return -ENOMEM; missing_ok = false; @@ -447,7 +467,7 @@ static int load_credential( maxsz = encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX; - if (search_path) { + if (search_path) STRV_FOREACH(d, search_path) { _cleanup_free_ char *j = NULL; @@ -465,7 +485,7 @@ static int load_credential( if (r != -ENOENT) break; } - } else if (source) + else if (source) r = read_full_file_full( read_dfd, source, UINT64_MAX, @@ -484,7 +504,8 @@ static int load_credential( * * Also, if the source file doesn't exist, but a fallback is set via SetCredentials= * we are fine, too. */ - log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", path); + log_full_errno(hashmap_contains(context->set_credentials, id) ? LOG_DEBUG : LOG_INFO, + r, "Couldn't read inherited credential '%s', skipping: %m", path); return 0; } if (r < 0) @@ -518,6 +539,9 @@ static int load_cred_recurse_dir_cb( _cleanup_free_ char *sub_id = NULL; int r; + assert(path); + assert(de); + if (event != RECURSE_DIR_ENTRY) return RECURSE_DIR_CONTINUE; @@ -574,6 +598,8 @@ static int acquire_credentials( int r; assert(context); + assert(params); + assert(unit); assert(p); dfd = open(p, O_DIRECTORY|O_CLOEXEC); @@ -618,8 +644,7 @@ static int acquire_credentials( &left); else /* Directory */ - r = recurse_dir( - sub_fd, + r = recurse_dir(sub_fd, /* path= */ lc->id, /* recurse_dir() will suffix the subdir paths from here to the top-level id */ /* statx_mask= */ 0, /* n_depth_max= */ UINT_MAX, @@ -684,7 +709,7 @@ static int acquire_credentials( /* Finally, we add in literally specified credentials. If the credentials already exist, we'll not * add them, so that they can act as a "default" if the same credential is specified multiple times. */ HASHMAP_FOREACH(sc, context->set_credentials) { - _cleanup_(erase_and_freep) void *plaintext = NULL; + _cleanup_(iovec_done_erase) struct iovec plaintext = {}; const char *data; size_t size, add; @@ -698,11 +723,20 @@ static int acquire_credentials( return log_debug_errno(errno, "Failed to test if credential %s exists: %m", sc->id); if (sc->encrypted) { - r = decrypt_credential_and_warn(sc->id, now(CLOCK_REALTIME), NULL, NULL, sc->data, sc->size, &plaintext, &size); + r = decrypt_credential_and_warn( + sc->id, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + getuid(), + &IOVEC_MAKE(sc->data, sc->size), + CREDENTIAL_ANY_SCOPE, + &plaintext); if (r < 0) return r; - data = plaintext; + data = plaintext.iov_base; + size = plaintext.iov_len; } else { data = sc->data; size = sc->size; @@ -754,17 +788,42 @@ static int setup_credentials_internal( uid_t uid, gid_t gid) { + bool final_mounted; int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true * if we mounted something; false if we definitely can't mount anything */ - bool final_mounted; - const char *where; assert(context); + assert(params); + assert(unit); assert(final); assert(workspace); + r = path_is_mount_point(final); + if (r < 0) + return r; + final_mounted = r > 0; + + if (final_mounted) { + if (FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) { + r = umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) + return r; + + final_mounted = false; + } else { + /* We can reuse the previous credential dir */ + r = dir_is_empty(final, /* ignore_hidden_or_backup = */ false); + if (r < 0) + return r; + if (r == 0) { + log_debug("Credential dir for unit '%s' already set up, skipping.", unit); + return 0; + } + } + } + if (reuse_workspace) { - r = path_is_mount_point(workspace, NULL, 0); + r = path_is_mount_point(workspace); if (r < 0) return r; if (r > 0) @@ -775,40 +834,19 @@ static int setup_credentials_internal( } else workspace_mounted = -1; /* ditto */ - r = path_is_mount_point(final, NULL, 0); - if (r < 0) - return r; - if (r > 0) { - /* If the final place already has something mounted, we use that. If the workspace also has - * something mounted we assume it's actually the same mount (but with MS_RDONLY - * different). */ - final_mounted = true; - - if (workspace_mounted < 0) { - /* If the final place is mounted, but the workspace isn't, then let's bind mount - * the final version to the workspace, and make it writable, so that we can make - * changes */ - - r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL); - if (r < 0) - return r; - - r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL); - if (r < 0) - return r; - - workspace_mounted = true; - } - } else - final_mounted = false; + /* If both the final place and the workspace are mounted, we have no mounts to set up, based on + * the assumption that they're actually the same tmpfs (but the latter with MS_RDONLY different). + * If the workspace is not mounted, we just bind the final place over and make it writable. */ + must_mount = must_mount || final_mounted; if (workspace_mounted < 0) { - /* Nothing is mounted on the workspace yet, let's try to mount something now */ - - r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false); - if (r < 0) { - /* If that didn't work, try to make a bind mount from the final to the workspace, so - * that we can make it writable there. */ + if (!final_mounted) + /* Nothing is mounted on the workspace yet, let's try to mount a new tmpfs if + * not using the final place. */ + r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false); + if (final_mounted || r < 0) { + /* If using final place or failed to mount new tmpfs, make a bind mount from + * the final to the workspace, so that we can make it writable there. */ r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL); if (r < 0) { if (!ERRNO_IS_PRIVILEGE(r)) @@ -821,12 +859,19 @@ static int setup_credentials_internal( return r; /* If we lack privileges to bind mount stuff, then let's gracefully proceed - * for compat with container envs, and just use the final dir as is. */ + * for compat with container envs, and just use the final dir as is. + * Final place must not be mounted in this case (refused by must_mount + * above) */ workspace_mounted = false; } else { /* Make the new bind mount writable (i.e. drop MS_RDONLY) */ - r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL); + r = mount_nofollow_verbose(LOG_DEBUG, + NULL, + workspace, + NULL, + MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), + NULL); if (r < 0) return r; @@ -836,34 +881,26 @@ static int setup_credentials_internal( workspace_mounted = true; } - assert(!must_mount || workspace_mounted > 0); - where = workspace_mounted ? workspace : final; + assert(workspace_mounted >= 0); + assert(!must_mount || workspace_mounted); + + const char *where = workspace_mounted ? workspace : final; (void) label_fix_full(AT_FDCWD, where, final, 0); r = acquire_credentials(context, params, unit, where, uid, gid, workspace_mounted); - if (r < 0) - return r; - - if (workspace_mounted) { - bool install; - - /* Determine if we should actually install the prepared mount in the final location by bind - * mounting it there. We do so only if the mount is not established there already, and if the - * mount is actually non-empty (i.e. carries at least one credential). Not that in the best - * case we are doing all this in a mount namespace, thus no one else will see that we - * allocated a file system we are getting rid of again here. */ + if (r < 0) { + /* If we're using final place as workspace, and failed to acquire credentials, we might + * have left half-written creds there. Let's get rid of the whole mount, so future + * calls won't reuse it. */ if (final_mounted) - install = false; /* already installed */ - else { - r = dir_is_empty(where, /* ignore_hidden_or_backup= */ false); - if (r < 0) - return r; + (void) umount_verbose(LOG_DEBUG, final, MNT_DETACH|UMOUNT_NOFOLLOW); - install = r == 0; /* install only if non-empty */ - } + return r; + } - if (install) { + if (workspace_mounted) { + if (!final_mounted) { /* Make workspace read-only now, so that any bind mount we make from it defaults to * read-only too */ r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL); @@ -873,7 +910,7 @@ static int setup_credentials_internal( /* And mount it to the final place, read-only */ r = mount_nofollow_verbose(LOG_DEBUG, workspace, final, NULL, MS_MOVE, NULL); } else - /* Otherwise get rid of it */ + /* Otherwise we just get rid of the bind mount of final place */ r = umount_verbose(LOG_DEBUG, workspace, MNT_DETACH|UMOUNT_NOFOLLOW); if (r < 0) return r; @@ -905,15 +942,16 @@ int exec_setup_credentials( assert(context); assert(params); + assert(unit); - if (!exec_context_has_credentials(context)) + if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context)) return 0; if (!params->prefix[EXEC_DIRECTORY_RUNTIME]) return -EINVAL; - /* This where we'll place stuff when we are done; this main credentials directory is world-readable, - * and the subdir we mount over with a read-only file system readable by the service's user */ + /* This is where we'll place stuff when we are done; the main credentials directory is world-readable, + * and the subdir we mount over with a read-only file system readable by the service's user. */ q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials"); if (!q) return -ENOMEM; diff --git a/src/core/exec-credential.h b/src/core/exec-credential.h index 6f836fb..70bb46b 100644 --- a/src/core/exec-credential.h +++ b/src/core/exec-credential.h @@ -34,8 +34,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(ExecLoadCredential*, exec_load_credential_free); extern const struct hash_ops exec_set_credential_hash_ops; extern const struct hash_ops exec_load_credential_hash_ops; -bool exec_context_has_encrypted_credentials(ExecContext *c); +bool exec_params_need_credentials(const ExecParameters *p); + bool exec_context_has_credentials(const ExecContext *c); +bool exec_context_has_encrypted_credentials(const ExecContext *c); int exec_context_get_credential_directory( const ExecContext *context, diff --git a/src/core/exec-invoke.c b/src/core/exec-invoke.c index 8e6de15..ee8db04 100644 --- a/src/core/exec-invoke.c +++ b/src/core/exec-invoke.c @@ -22,7 +22,7 @@ #include "argv-util.h" #include "barrier.h" #include "bpf-dlopen.h" -#include "bpf-lsm.h" +#include "bpf-restrict-fs.h" #include "btrfs-util.h" #include "capability-util.h" #include "cgroup-setup.h" @@ -41,6 +41,7 @@ #include "hexdecoct.h" #include "io-util.h" #include "iovec-util.h" +#include "journal-send.h" #include "missing_ioprio.h" #include "missing_prctl.h" #include "missing_securebits.h" @@ -59,52 +60,13 @@ #include "strv.h" #include "terminal-util.h" #include "utmp-wtmp.h" +#include "vpick.h" #define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) #define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) #define SNDBUF_SIZE (8*1024*1024) -static int shift_fds(int fds[], size_t n_fds) { - if (n_fds <= 0) - return 0; - - /* Modifies the fds array! (sorts it) */ - - assert(fds); - - for (int start = 0;;) { - int restart_from = -1; - - for (int i = start; i < (int) n_fds; i++) { - int nfd; - - /* Already at right index? */ - if (fds[i] == i+3) - continue; - - nfd = fcntl(fds[i], F_DUPFD, i + 3); - if (nfd < 0) - return -errno; - - safe_close(fds[i]); - fds[i] = nfd; - - /* Hmm, the fd we wanted isn't free? Then - * let's remember that and try again from here */ - if (nfd != i+3 && restart_from < 0) - restart_from = i; - } - - if (restart_from < 0) - break; - - start = restart_from; - } - - return 0; -} - static int flag_fds( const int fds[], size_t n_socket_fds, @@ -198,9 +160,11 @@ static int connect_journal_socket( const char *j; int r; - j = log_namespace ? - strjoina("/run/systemd/journal.", log_namespace, "/stdout") : - "/run/systemd/journal/stdout"; + assert(fd >= 0); + + j = journal_stream_path(log_namespace); + if (!j) + return -EINVAL; if (gid_is_valid(gid)) { oldgid = getgid(); @@ -449,7 +413,7 @@ static int setup_input( case EXEC_INPUT_DATA: { int fd; - fd = acquire_data_fd(context->stdin_data, context->stdin_data_size, 0); + fd = acquire_data_fd_full(context->stdin_data, context->stdin_data_size, /* flags = */ 0); if (fd < 0) return fd; @@ -670,12 +634,8 @@ static int chown_terminal(int fd, uid_t uid) { assert(fd >= 0); /* Before we chown/chmod the TTY, let's ensure this is actually a tty */ - if (isatty(fd) < 1) { - if (IN_SET(errno, EINVAL, ENOTTY)) - return 0; /* not a tty */ - - return -errno; - } + if (!isatty_safe(fd)) + return 0; /* This might fail. What matters are the results. */ r = fchmod_and_chown(fd, TTY_MODE, uid, GID_INVALID); @@ -1126,7 +1086,8 @@ static int setup_pam( gid_t gid, const char *tty, char ***env, /* updated on success */ - const int fds[], size_t n_fds) { + const int fds[], size_t n_fds, + int exec_fd) { #if HAVE_PAM @@ -1141,7 +1102,7 @@ static int setup_pam( sigset_t old_ss; int pam_code = PAM_SUCCESS, r; bool close_session = false; - pid_t pam_pid = 0, parent_pid; + pid_t parent_pid; int flags = 0; assert(name); @@ -1196,7 +1157,7 @@ static int setup_pam( pam_code = pam_setcred(handle, PAM_ESTABLISH_CRED | flags); if (pam_code != PAM_SUCCESS) - log_debug("pam_setcred() failed, ignoring: %s", pam_strerror(handle, pam_code)); + log_debug("pam_setcred(PAM_ESTABLISH_CRED) failed, ignoring: %s", pam_strerror(handle, pam_code)); pam_code = pam_open_session(handle, flags); if (pam_code != PAM_SUCCESS) @@ -1212,15 +1173,15 @@ static int setup_pam( /* Block SIGTERM, so that we know that it won't get lost in the child */ - assert_se(sigprocmask_many(SIG_BLOCK, &old_ss, SIGTERM, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, &old_ss, SIGTERM) >= 0); parent_pid = getpid_cached(); - r = safe_fork("(sd-pam)", 0, &pam_pid); + r = safe_fork("(sd-pam)", 0, NULL); if (r < 0) goto fail; if (r == 0) { - int sig, ret = EXIT_PAM; + int ret = EXIT_PAM; /* The child's job is to reset the PAM session on termination */ barrier_set_role(&barrier, BARRIER_CHILD); @@ -1229,17 +1190,18 @@ static int setup_pam( * those fds are open here that have been opened by PAM. */ (void) close_many(fds, n_fds); + /* Also close the 'exec_fd' in the child, since the service manager waits for the EOF induced + * by the execve() to wait for completion, and if we'd keep the fd open here in the child + * we'd never signal completion. */ + exec_fd = safe_close(exec_fd); + /* Drop privileges - we don't need any to pam_close_session and this will make * PR_SET_PDEATHSIG work in most cases. If this fails, ignore the error - but expect sd-pam * threads to fail to exit normally */ - r = maybe_setgroups(0, NULL); + r = fully_set_uid_gid(uid, gid, /* supplementary_gids= */ NULL, /* n_supplementary_gids= */ 0); if (r < 0) - log_warning_errno(r, "Failed to setgroups() in sd-pam: %m"); - if (setresgid(gid, gid, gid) < 0) - log_warning_errno(errno, "Failed to setresgid() in sd-pam: %m"); - if (setresuid(uid, uid, uid) < 0) - log_warning_errno(errno, "Failed to setresuid() in sd-pam: %m"); + log_warning_errno(r, "Failed to drop privileges in sd-pam: %m"); (void) ignore_signals(SIGPIPE); @@ -1258,21 +1220,13 @@ static int setup_pam( /* Check if our parent process might already have died? */ if (getppid() == parent_pid) { sigset_t ss; + int sig; assert_se(sigemptyset(&ss) >= 0); assert_se(sigaddset(&ss, SIGTERM) >= 0); - for (;;) { - if (sigwait(&ss, &sig) < 0) { - if (errno == EINTR) - continue; - - goto child_finish; - } - - assert(sig == SIGTERM); - break; - } + assert_se(sigwait(&ss, &sig) == 0); + assert(sig == SIGTERM); } /* If our parent died we'll end the session */ @@ -1361,7 +1315,7 @@ static void rename_process_from_path(const char *path) { process_name[1+l] = ')'; process_name[1+l+1] = 0; - rename_process(process_name); + (void) rename_process(process_name); } static bool context_has_address_families(const ExecContext *c) { @@ -1725,7 +1679,7 @@ static int apply_restrict_filesystems(const ExecContext *c, const ExecParameters if (!exec_context_restrict_filesystems_set(c)) return 0; - if (p->bpf_outer_map_fd < 0) { + if (p->bpf_restrict_fs_map_fd < 0) { /* LSM BPF is unsupported or lsm_bpf_setup failed */ log_exec_debug(c, p, "LSM BPF not supported, skipping RestrictFileSystems="); return 0; @@ -1736,7 +1690,7 @@ static int apply_restrict_filesystems(const ExecContext *c, const ExecParameters if (r < 0) return r; - return lsm_bpf_restrict_filesystems(c->restrict_filesystems, p->cgroup_id, p->bpf_outer_map_fd, c->restrict_filesystems_allow_list); + return bpf_restrict_fs_update(c->restrict_filesystems, p->cgroup_id, p->bpf_restrict_fs_map_fd, c->restrict_filesystems_allow_list); } #endif @@ -1817,10 +1771,10 @@ static const char *exec_directory_env_name_to_string(ExecDirectoryType t); /* And this table also maps ExecDirectoryType, to the environment variable we pass the selected directory to * the service payload in. */ static const char* const exec_directory_env_name_table[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = "RUNTIME_DIRECTORY", - [EXEC_DIRECTORY_STATE] = "STATE_DIRECTORY", - [EXEC_DIRECTORY_CACHE] = "CACHE_DIRECTORY", - [EXEC_DIRECTORY_LOGS] = "LOGS_DIRECTORY", + [EXEC_DIRECTORY_RUNTIME] = "RUNTIME_DIRECTORY", + [EXEC_DIRECTORY_STATE] = "STATE_DIRECTORY", + [EXEC_DIRECTORY_CACHE] = "CACHE_DIRECTORY", + [EXEC_DIRECTORY_LOGS] = "LOGS_DIRECTORY", [EXEC_DIRECTORY_CONFIGURATION] = "CONFIGURATION_DIRECTORY", }; @@ -1907,7 +1861,7 @@ static int build_environment( "Failed to determine user credentials for root: %m"); } - bool set_user_login_env = c->set_login_environment >= 0 ? c->set_login_environment : (c->user || c->dynamic_user); + bool set_user_login_env = exec_context_get_set_login_environment(c); if (username) { x = strjoin("USER=", username); @@ -1961,7 +1915,7 @@ static int build_environment( * to inherit the $TERM set for PID 1. This is useful for containers so that the $TERM the * container manager passes to PID 1 ends up all the way in the console login shown. */ - if (path_equal_ptr(tty_path, "/dev/console") && getppid() == 1) + if (path_equal(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; @@ -2315,10 +2269,10 @@ static int setup_exec_directory( int *exit_status) { static const int exit_status_table[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = EXIT_RUNTIME_DIRECTORY, - [EXEC_DIRECTORY_STATE] = EXIT_STATE_DIRECTORY, - [EXEC_DIRECTORY_CACHE] = EXIT_CACHE_DIRECTORY, - [EXEC_DIRECTORY_LOGS] = EXIT_LOGS_DIRECTORY, + [EXEC_DIRECTORY_RUNTIME] = EXIT_RUNTIME_DIRECTORY, + [EXEC_DIRECTORY_STATE] = EXIT_STATE_DIRECTORY, + [EXEC_DIRECTORY_CACHE] = EXIT_CACHE_DIRECTORY, + [EXEC_DIRECTORY_LOGS] = EXIT_LOGS_DIRECTORY, [EXEC_DIRECTORY_CONFIGURATION] = EXIT_CONFIGURATION_DIRECTORY, }; int r; @@ -2338,10 +2292,10 @@ static int setup_exec_directory( gid = 0; } - for (size_t i = 0; i < context->directories[type].n_items; i++) { + FOREACH_ARRAY(i, context->directories[type].items, context->directories[type].n_items) { _cleanup_free_ char *p = NULL, *pp = NULL; - p = path_join(params->prefix[type], context->directories[type].items[i].path); + p = path_join(params->prefix[type], i->path); if (!p) { r = -ENOMEM; goto fail; @@ -2357,7 +2311,7 @@ static int setup_exec_directory( * doesn't exist, then we likely are upgrading from an older systemd version that * didn't know the more recent addition to the xdg-basedir spec: the $XDG_STATE_HOME * directory. In older systemd versions EXEC_DIRECTORY_STATE was aliased to - * EXEC_DIRECTORY_CONFIGURATION, with the advent of $XDG_STATE_HOME is is now + * EXEC_DIRECTORY_CONFIGURATION, with the advent of $XDG_STATE_HOME it is now * separated. If a service has both dirs configured but only the configuration dir * exists and the state dir does not, we assume we are looking at an update * situation. Hence, create a compatibility symlink, so that all expectations are @@ -2378,9 +2332,9 @@ static int setup_exec_directory( * under the configuration hierarchy. */ if (type == EXEC_DIRECTORY_STATE) - q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], context->directories[type].items[i].path); + q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], i->path); else if (type == EXEC_DIRECTORY_LOGS) - q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], "log", context->directories[type].items[i].path); + q = path_join(params->prefix[EXEC_DIRECTORY_CONFIGURATION], "log", i->path); else assert_not_reached(); if (!q) { @@ -2443,7 +2397,7 @@ static int setup_exec_directory( if (r < 0) goto fail; - if (!path_extend(&pp, context->directories[type].items[i].path)) { + if (!path_extend(&pp, i->path)) { r = -ENOMEM; goto fail; } @@ -2477,7 +2431,7 @@ static int setup_exec_directory( goto fail; } - if (!context->directories[type].items[i].only_create) { + if (!i->only_create) { /* And link it up from the original place. * Notes * 1) If a mount namespace is going to be used, then this symlink remains on @@ -2514,7 +2468,7 @@ static int setup_exec_directory( if (r < 0) goto fail; - q = path_join(params->prefix[type], "private", context->directories[type].items[i].path); + q = path_join(params->prefix[type], "private", i->path); if (!q) { r = -ENOMEM; goto fail; @@ -2568,7 +2522,7 @@ static int setup_exec_directory( params, "%s \'%s\' already exists but the mode is different. " "(File system: %o %sMode: %o)", - exec_directory_type_to_string(type), context->directories[type].items[i].path, + exec_directory_type_to_string(type), i->path, st.st_mode & 07777, exec_directory_type_to_string(type), context->directories[type].mode & 07777); continue; @@ -2599,10 +2553,8 @@ static int setup_exec_directory( /* If we are not going to run in a namespace, set up the symlinks - otherwise * they are set up later, to allow configuring empty var/run/etc. */ if (!needs_mount_namespace) - for (size_t i = 0; i < context->directories[type].n_items; i++) { - r = create_many_symlinks(params->prefix[type], - context->directories[type].items[i].path, - context->directories[type].items[i].symlinks); + FOREACH_ARRAY(i, context->directories[type].items, context->directories[type].n_items) { + r = create_many_symlinks(params->prefix[type], i->path, i->symlinks); if (r < 0) goto fail; } @@ -2669,8 +2621,8 @@ static int compile_bind_mounts( if (!params->prefix[t]) continue; - for (size_t i = 0; i < context->directories[t].n_items; i++) - n += !context->directories[t].items[i].only_create; + FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) + n += !i->only_create; } if (n <= 0) { @@ -2684,8 +2636,7 @@ static int compile_bind_mounts( if (!bind_mounts) return -ENOMEM; - for (size_t i = 0; i < context->n_bind_mounts; i++) { - BindMount *item = context->bind_mounts + i; + FOREACH_ARRAY(item, context->bind_mounts, context->n_bind_mounts) { _cleanup_free_ char *s = NULL, *d = NULL; s = strdup(item->source); @@ -2729,18 +2680,18 @@ static int compile_bind_mounts( return r; } - for (size_t i = 0; i < context->directories[t].n_items; i++) { + FOREACH_ARRAY(i, context->directories[t].items, context->directories[t].n_items) { _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(). */ - if (context->directories[t].items[i].only_create) + if (i->only_create) continue; if (exec_directory_is_private(context, t)) - s = path_join(params->prefix[t], "private", context->directories[t].items[i].path); + s = path_join(params->prefix[t], "private", i->path); else - s = path_join(params->prefix[t], context->directories[t].items[i].path); + s = path_join(params->prefix[t], i->path); if (!s) return -ENOMEM; @@ -2749,7 +2700,7 @@ static int compile_bind_mounts( /* When RootDirectory= or RootImage= are set, then the symbolic link to the private * directory is not created on the root directory. So, let's bind-mount the directory * on the 'non-private' place. */ - d = path_join(params->prefix[t], context->directories[t].items[i].path); + d = path_join(params->prefix[t], i->path); else d = strdup(s); if (!d) @@ -2758,10 +2709,8 @@ static int compile_bind_mounts( bind_mounts[h++] = (BindMount) { .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, - .ignore_enoent = false, }; } } @@ -2791,14 +2740,14 @@ static int compile_symlinks( assert(params); assert(ret_symlinks); - for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) { - for (size_t i = 0; i < context->directories[dt].n_items; i++) { + for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) + FOREACH_ARRAY(i, context->directories[dt].items, context->directories[dt].n_items) { _cleanup_free_ char *private_path = NULL, *path = NULL; - STRV_FOREACH(symlink, context->directories[dt].items[i].symlinks) { + STRV_FOREACH(symlink, i->symlinks) { _cleanup_free_ char *src_abs = NULL, *dst_abs = NULL; - src_abs = path_join(params->prefix[dt], context->directories[dt].items[i].path); + src_abs = path_join(params->prefix[dt], i->path); dst_abs = path_join(params->prefix[dt], *symlink); if (!src_abs || !dst_abs) return -ENOMEM; @@ -2810,14 +2759,14 @@ static int compile_symlinks( if (!exec_directory_is_private(context, dt) || exec_context_with_rootfs(context) || - context->directories[dt].items[i].only_create) + i->only_create) continue; - private_path = path_join(params->prefix[dt], "private", context->directories[dt].items[i].path); + private_path = path_join(params->prefix[dt], "private", i->path); if (!private_path) return -ENOMEM; - path = path_join(params->prefix[dt], context->directories[dt].items[i].path); + path = path_join(params->prefix[dt], i->path); if (!path) return -ENOMEM; @@ -2825,18 +2774,16 @@ static int compile_symlinks( if (r < 0) return r; } - } /* We make the host's os-release available via a symlink, so that we can copy it atomically * and readers will never get a half-written version. Note that, while the paths specified here are * absolute, when they are processed in namespace.c they will be made relative automatically, i.e.: * 'os-release -> .os-release-stage/os-release' is what will be created. */ if (setup_os_release_symlink) { - r = strv_extend(&symlinks, "/run/host/.os-release-stage/os-release"); - if (r < 0) - return r; - - r = strv_extend(&symlinks, "/run/host/os-release"); + r = strv_extend_many( + &symlinks, + "/run/host/.os-release-stage/os-release", + "/run/host/os-release"); if (r < 0) return r; } @@ -2877,8 +2824,8 @@ static bool insist_on_sandboxing( /* If there are any bind mounts set that don't map back onto themselves, fs namespacing becomes * essential. */ - for (size_t i = 0; i < n_bind_mounts; i++) - if (!path_equal(bind_mounts[i].source, bind_mounts[i].destination)) + FOREACH_ARRAY(i, bind_mounts, n_bind_mounts) + if (!path_equal(i->source, i->destination)) return true; if (context->log_namespace) @@ -2887,13 +2834,33 @@ static bool insist_on_sandboxing( return false; } -static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) { +static int setup_ephemeral( + const ExecContext *context, + ExecRuntime *runtime, + char **root_image, /* both input and output! modified if ephemeral logic enabled */ + char **root_directory) { /* ditto */ + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *new_root = NULL; int r; + assert(context); + assert(root_image); + assert(root_directory); + + if (!*root_image && !*root_directory) + return 0; + if (!runtime || !runtime->ephemeral_copy) return 0; + assert(runtime->ephemeral_storage_socket[0] >= 0); + assert(runtime->ephemeral_storage_socket[1] >= 0); + + new_root = strdup(runtime->ephemeral_copy); + if (!new_root) + return log_oom_debug(); + r = posix_lock(runtime->ephemeral_storage_socket[0], LOCK_EX); if (r < 0) return log_debug_errno(r, "Failed to lock ephemeral storage socket: %m"); @@ -2904,28 +2871,23 @@ static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) { if (fd >= 0) /* We got an fd! That means ephemeral has already been set up, so nothing to do here. */ return 0; - if (fd != -EAGAIN) return log_debug_errno(fd, "Failed to receive file descriptor queued on ephemeral storage socket: %m"); - log_debug("Making ephemeral snapshot of %s to %s", - context->root_image ?: context->root_directory, runtime->ephemeral_copy); + if (*root_image) { + log_debug("Making ephemeral copy of %s to %s", *root_image, new_root); - if (context->root_image) - fd = copy_file(context->root_image, runtime->ephemeral_copy, O_EXCL, 0600, - COPY_LOCK_BSD|COPY_REFLINK|COPY_CRTIME); - else - fd = btrfs_subvol_snapshot_at(AT_FDCWD, context->root_directory, - AT_FDCWD, runtime->ephemeral_copy, - BTRFS_SNAPSHOT_FALLBACK_COPY | - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | - BTRFS_SNAPSHOT_RECURSIVE | - BTRFS_SNAPSHOT_LOCK_BSD); - if (fd < 0) - return log_debug_errno(fd, "Failed to snapshot %s to %s: %m", - context->root_image ?: context->root_directory, runtime->ephemeral_copy); + fd = copy_file(*root_image, + new_root, + O_EXCL, + 0600, + COPY_LOCK_BSD| + COPY_REFLINK| + COPY_CRTIME); + if (fd < 0) + return log_debug_errno(fd, "Failed to copy image %s to %s: %m", + *root_image, new_root); - if (context->root_image) { /* A root image might be subject to lots of random writes so let's try to disable COW on it * which tends to not perform well in combination with lots of random writes. * @@ -2934,13 +2896,35 @@ static int setup_ephemeral(const ExecContext *context, ExecRuntime *runtime) { */ r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL); if (r < 0) - log_debug_errno(fd, "Failed to disable copy-on-write for %s, ignoring: %m", runtime->ephemeral_copy); + log_debug_errno(r, "Failed to disable copy-on-write for %s, ignoring: %m", new_root); + } else { + assert(*root_directory); + + log_debug("Making ephemeral snapshot of %s to %s", *root_directory, new_root); + + fd = btrfs_subvol_snapshot_at( + AT_FDCWD, *root_directory, + AT_FDCWD, new_root, + BTRFS_SNAPSHOT_FALLBACK_COPY | + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY | + BTRFS_SNAPSHOT_RECURSIVE | + BTRFS_SNAPSHOT_LOCK_BSD); + if (fd < 0) + return log_debug_errno(fd, "Failed to snapshot directory %s to %s: %m", + *root_directory, new_root); } r = send_one_fd(runtime->ephemeral_storage_socket[1], fd, MSG_DONTWAIT); if (r < 0) return log_debug_errno(r, "Failed to queue file descriptor on ephemeral storage socket: %m"); + if (*root_image) + free_and_replace(*root_image, new_root); + else { + assert(*root_directory); + free_and_replace(*root_directory, new_root); + } + return 1; } @@ -3000,22 +2984,80 @@ static int verity_settings_prepare( return 0; } +static int pick_versions( + const ExecContext *context, + const ExecParameters *params, + char **ret_root_image, + char **ret_root_directory) { + + int r; + + assert(context); + assert(params); + assert(ret_root_image); + assert(ret_root_directory); + + if (context->root_image) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + context->root_image, + &pick_filter_image_raw, + PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE, + &result); + if (r < 0) + return r; + + if (!result.path) + return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_image); + + *ret_root_image = TAKE_PTR(result.path); + *ret_root_directory = NULL; + return r; + } + + if (context->root_directory) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + context->root_directory, + &pick_filter_image_dir, + PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE, + &result); + if (r < 0) + return r; + + if (!result.path) + return log_exec_debug_errno(context, params, SYNTHETIC_ERRNO(ENOENT), "No matching entry in .v/ directory %s found.", context->root_directory); + + *ret_root_image = NULL; + *ret_root_directory = TAKE_PTR(result.path); + return r; + } + + *ret_root_image = *ret_root_directory = NULL; + return 0; +} + static int apply_mount_namespace( ExecCommandFlags command_flags, const ExecContext *context, const ExecParameters *params, ExecRuntime *runtime, const char *memory_pressure_path, + bool needs_sandboxing, char **error_path) { _cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT; _cleanup_strv_free_ char **empty_directories = NULL, **symlinks = NULL, **read_write_paths_cleanup = NULL; _cleanup_free_ char *creds_path = NULL, *incoming_dir = NULL, *propagate_dir = NULL, - *extension_dir = NULL, *host_os_release_stage = NULL; - const char *root_dir = NULL, *root_image = NULL, *tmp_dir = NULL, *var_tmp_dir = NULL; + *extension_dir = NULL, *host_os_release_stage = NULL, *root_image = NULL, *root_dir = NULL; + const char *tmp_dir = NULL, *var_tmp_dir = NULL; char **read_write_paths; - bool needs_sandboxing, setup_os_release_symlink; + bool setup_os_release_symlink; BindMount *bind_mounts = NULL; size_t n_bind_mounts = 0; int r; @@ -3025,14 +3067,21 @@ static int apply_mount_namespace( CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many); if (params->flags & EXEC_APPLY_CHROOT) { - r = setup_ephemeral(context, runtime); + r = pick_versions( + context, + params, + &root_image, + &root_dir); if (r < 0) return r; - if (context->root_image) - root_image = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_image; - else - root_dir = (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_directory; + r = setup_ephemeral( + context, + runtime, + &root_image, + &root_dir); + if (r < 0) + return r; } r = compile_bind_mounts(context, params, &bind_mounts, &n_bind_mounts, &empty_directories); @@ -3054,7 +3103,6 @@ static int apply_mount_namespace( } else read_write_paths = context->read_write_paths; - needs_sandboxing = (params->flags & EXEC_APPLY_SANDBOXING) && !(command_flags & EXEC_COMMAND_FULLY_PRIVILEGED); if (needs_sandboxing) { /* The runtime struct only contains the parent of the private /tmp, which is non-accessible * to world users. Inside of it there's a /tmp that is sticky, and that's the one we want to @@ -3084,11 +3132,9 @@ static int apply_mount_namespace( params, "shared mount propagation hidden by other fs namespacing unit settings: ignoring"); - if (FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) { - r = exec_context_get_credential_directory(context, params, params->unit_id, &creds_path); - if (r < 0) - return r; - } + r = exec_context_get_credential_directory(context, params, params->unit_id, &creds_path); + if (r < 0) + return r; if (params->runtime_scope == RUNTIME_SCOPE_SYSTEM) { propagate_dir = path_join("/run/systemd/propagate/", params->unit_id); @@ -3246,31 +3292,39 @@ static int apply_working_directory( const char *home, int *exit_status) { - const char *d, *wd; + const char *wd; + int r; assert(context); assert(exit_status); if (context->working_directory_home) { - if (!home) { *exit_status = EXIT_CHDIR; return -ENXIO; } wd = home; - } else wd = empty_to_root(context->working_directory); if (params->flags & EXEC_APPLY_CHROOT) - d = wd; - else - d = prefix_roota((runtime ? runtime->ephemeral_copy : NULL) ?: context->root_directory, wd); + r = RET_NERRNO(chdir(wd)); + else { + _cleanup_close_ int dfd = -EBADF; + + r = chase(wd, + (runtime ? runtime->ephemeral_copy : NULL) ?: context->root_directory, + CHASE_PREFIX_ROOT|CHASE_AT_RESOLVE_IN_ROOT, + /* ret_path= */ NULL, + &dfd); + if (r >= 0) + r = RET_NERRNO(fchdir(dfd)); + } - if (chdir(d) < 0 && !context->working_directory_missing_ok) { + if (r < 0 && !context->working_directory_missing_ok) { *exit_status = EXIT_CHDIR; - return -errno; + return r; } return 0; @@ -3459,7 +3513,7 @@ static int close_remaining_fds( const int *fds, size_t n_fds) { size_t n_dont_close = 0; - int dont_close[n_fds + 15]; + int dont_close[n_fds + 16]; assert(params); @@ -3495,6 +3549,9 @@ static int close_remaining_fds( if (params->user_lookup_fd >= 0) dont_close[n_dont_close++] = params->user_lookup_fd; + if (params->handoff_timestamp_fd >= 0) + dont_close[n_dont_close++] = params->handoff_timestamp_fd; + assert(n_dont_close <= ELEMENTSOF(dont_close)); return close_all_fds(dont_close, n_dont_close); @@ -3528,26 +3585,29 @@ static int send_user_lookup( return 0; } -static int acquire_home(const ExecContext *c, uid_t uid, const char** home, char **buf) { +static int acquire_home(const ExecContext *c, const char **home, char **ret_buf) { int r; assert(c); assert(home); - assert(buf); + assert(ret_buf); /* If WorkingDirectory=~ is set, try to acquire a usable home directory. */ - if (*home) + if (*home) /* Already acquired from get_fixed_user()? */ return 0; if (!c->working_directory_home) return 0; - r = get_home_dir(buf); + if (c->dynamic_user) + return -EADDRNOTAVAIL; + + r = get_home_dir(ret_buf); if (r < 0) return r; - *home = *buf; + *home = *ret_buf; return 1; } @@ -3641,11 +3701,12 @@ static int add_shifted_fd(int *fds, size_t fds_size, size_t *n_fds, int *fd) { } static int connect_unix_harder(const ExecContext *c, const ExecParameters *p, const OpenFile *of, int ofd) { + static const int socket_types[] = { SOCK_DGRAM, SOCK_STREAM, SOCK_SEQPACKET }; + union sockaddr_union addr = { .un.sun_family = AF_UNIX, }; socklen_t sa_len; - static const int socket_types[] = { SOCK_DGRAM, SOCK_STREAM, SOCK_SEQPACKET }; int r; assert(c); @@ -3655,43 +3716,35 @@ static int connect_unix_harder(const ExecContext *c, const ExecParameters *p, co r = sockaddr_un_set_path(&addr.un, FORMAT_PROC_FD_PATH(ofd)); if (r < 0) - return log_exec_error_errno(c, p, r, "Failed to set sockaddr for %s: %m", of->path); - + return log_exec_error_errno(c, p, r, "Failed to set sockaddr for '%s': %m", of->path); sa_len = r; - for (size_t i = 0; i < ELEMENTSOF(socket_types); i++) { + FOREACH_ELEMENT(i, socket_types) { _cleanup_close_ int fd = -EBADF; - fd = socket(AF_UNIX, socket_types[i] | SOCK_CLOEXEC, 0); + fd = socket(AF_UNIX, *i|SOCK_CLOEXEC, 0); if (fd < 0) - return log_exec_error_errno(c, - p, - errno, - "Failed to create socket for %s: %m", + return log_exec_error_errno(c, p, + errno, "Failed to create socket for '%s': %m", of->path); r = RET_NERRNO(connect(fd, &addr.sa, sa_len)); - if (r == -EPROTOTYPE) - continue; - if (r < 0) - return log_exec_error_errno(c, - p, - r, - "Failed to connect socket for %s: %m", + if (r >= 0) + return TAKE_FD(fd); + if (r != -EPROTOTYPE) + return log_exec_error_errno(c, p, + r, "Failed to connect to socket for '%s': %m", of->path); - - return TAKE_FD(fd); } - return log_exec_error_errno(c, - p, - SYNTHETIC_ERRNO(EPROTOTYPE), "Failed to connect socket for \"%s\".", + return log_exec_error_errno(c, p, + SYNTHETIC_ERRNO(EPROTOTYPE), "No suitable socket type to connect to socket '%s'.", of->path); } static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const OpenFile *of) { - struct stat st; _cleanup_close_ int fd = -EBADF, ofd = -EBADF; + struct stat st; assert(c); assert(p); @@ -3699,10 +3752,10 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const ofd = open(of->path, O_PATH | O_CLOEXEC); if (ofd < 0) - return log_exec_error_errno(c, p, errno, "Could not open \"%s\": %m", of->path); + return log_exec_error_errno(c, p, errno, "Failed to open '%s' as O_PATH: %m", of->path); if (fstat(ofd, &st) < 0) - return log_exec_error_errno(c, p, errno, "Failed to stat %s: %m", of->path); + return log_exec_error_errno(c, p, errno, "Failed to stat '%s': %m", of->path); if (S_ISSOCK(st.st_mode)) { fd = connect_unix_harder(c, p, of, ofd); @@ -3710,10 +3763,11 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const return fd; if (FLAGS_SET(of->flags, OPENFILE_READ_ONLY) && shutdown(fd, SHUT_WR) < 0) - return log_exec_error_errno(c, p, errno, "Failed to shutdown send for socket %s: %m", + return log_exec_error_errno(c, p, + errno, "Failed to shutdown send for socket '%s': %m", of->path); - log_exec_debug(c, p, "socket %s opened (fd=%d)", of->path, fd); + log_exec_debug(c, p, "Opened socket '%s' as fd %d.", of->path, fd); } else { int flags = FLAGS_SET(of->flags, OPENFILE_READ_ONLY) ? O_RDONLY : O_RDWR; if (FLAGS_SET(of->flags, OPENFILE_APPEND)) @@ -3723,9 +3777,9 @@ static int get_open_file_fd(const ExecContext *c, const ExecParameters *p, const fd = fd_reopen(ofd, flags | O_CLOEXEC); if (fd < 0) - return log_exec_error_errno(c, p, fd, "Failed to open file %s: %m", of->path); + return log_exec_error_errno(c, p, fd, "Failed to reopen file '%s': %m", of->path); - log_exec_debug(c, p, "file %s opened (fd=%d)", of->path, fd); + log_exec_debug(c, p, "Opened file '%s' as fd %d.", of->path, fd); } return TAKE_FD(fd); @@ -3744,7 +3798,9 @@ static int collect_open_file_fds(const ExecContext *c, ExecParameters *p, size_t fd = get_open_file_fd(c, p, of); if (fd < 0) { if (FLAGS_SET(of->flags, OPENFILE_GRACEFUL)) { - log_exec_debug_errno(c, p, fd, "Failed to get OpenFile= file descriptor for %s, ignoring: %m", of->path); + log_exec_warning_errno(c, p, fd, + "Failed to get OpenFile= file descriptor for '%s', ignoring: %m", + of->path); continue; } @@ -3758,9 +3814,7 @@ static int collect_open_file_fds(const ExecContext *c, ExecParameters *p, size_t if (r < 0) return r; - p->fds[*n_fds] = TAKE_FD(fd); - - (*n_fds)++; + p->fds[(*n_fds)++] = TAKE_FD(fd); } return 0; @@ -3810,7 +3864,7 @@ static bool exec_context_need_unprivileged_private_users( context->private_ipc || context->ipc_namespace_path || context->private_mounts > 0 || - context->mount_apivfs || + context->mount_apivfs > 0 || context->n_bind_mounts > 0 || context->n_temporary_filesystems > 0 || context->root_directory || @@ -3920,6 +3974,52 @@ static void exec_params_close(ExecParameters *p) { p->stderr_fd = safe_close(p->stderr_fd); } +static int exec_fd_mark_hot( + const ExecContext *c, + ExecParameters *p, + bool hot, + int *reterr_exit_status) { + + assert(c); + assert(p); + + if (p->exec_fd < 0) + return 0; + + uint8_t x = hot; + + if (write(p->exec_fd, &x, sizeof(x)) < 0) { + if (reterr_exit_status) + *reterr_exit_status = EXIT_EXEC; + return log_exec_error_errno(c, p, errno, "Failed to mark exec_fd as %s: %m", hot ? "hot" : "cold"); + } + + return 1; +} + +static int send_handoff_timestamp( + const ExecContext *c, + ExecParameters *p, + int *reterr_exit_status) { + + assert(c); + assert(p); + + if (p->handoff_timestamp_fd < 0) + return 0; + + dual_timestamp dt; + dual_timestamp_now(&dt); + + if (send(p->handoff_timestamp_fd, (const usec_t[2]) { dt.realtime, dt.monotonic }, sizeof(usec_t) * 2, 0) < 0) { + if (reterr_exit_status) + *reterr_exit_status = EXIT_EXEC; + return log_exec_error_errno(c, p, errno, "Failed to send handoff timestamp: %m"); + } + + return 1; +} + int exec_invoke( const ExecCommand *command, const ExecContext *context, @@ -3974,6 +4074,8 @@ int exec_invoke( assert(params); assert(exit_status); + /* This should be mostly redundant, as the log level is also passed as an argument of the executor, + * and is already applied earlier. Just for safety. */ if (context->log_level_max >= 0) log_set_max_level(context->log_level_max); @@ -4049,7 +4151,7 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to get OpenFile= file descriptors: %m"); } - int keep_fds[n_fds + 3]; + int keep_fds[n_fds + 4]; memcpy_safe(keep_fds, params->fds, n_fds * sizeof(int)); n_keep_fds = n_fds; @@ -4059,8 +4161,14 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to collect shifted fd: %m"); } + r = add_shifted_fd(keep_fds, ELEMENTSOF(keep_fds), &n_keep_fds, ¶ms->handoff_timestamp_fd); + if (r < 0) { + *exit_status = EXIT_FDS; + return log_exec_error_errno(context, params, r, "Failed to collect shifted fd: %m"); + } + #if HAVE_LIBBPF - r = add_shifted_fd(keep_fds, ELEMENTSOF(keep_fds), &n_keep_fds, ¶ms->bpf_outer_map_fd); + r = add_shifted_fd(keep_fds, ELEMENTSOF(keep_fds), &n_keep_fds, ¶ms->bpf_restrict_fs_map_fd); if (r < 0) { *exit_status = EXIT_FDS; return log_exec_error_errno(context, params, r, "Failed to collect shifted fd: %m"); @@ -4099,7 +4207,7 @@ int exec_invoke( *exit_status = EXIT_CONFIRM; return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(ECANCELED), - "Execution cancelled by the user"); + "Execution cancelled by the user."); } } @@ -4141,12 +4249,12 @@ int exec_invoke( if (!uid_is_valid(uid)) { *exit_status = EXIT_USER; - return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(ESRCH), "UID validation failed for \""UID_FMT"\"", uid); + return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(ESRCH), "UID validation failed for \""UID_FMT"\".", uid); } if (!gid_is_valid(gid)) { *exit_status = EXIT_USER; - return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(ESRCH), "GID validation failed for \""GID_FMT"\"", gid); + return log_exec_error_errno(context, params, SYNTHETIC_ERRNO(ESRCH), "GID validation failed for \""GID_FMT"\".", gid); } if (runtime->dynamic_creds->user) @@ -4186,7 +4294,7 @@ int exec_invoke( params->user_lookup_fd = safe_close(params->user_lookup_fd); - r = acquire_home(context, uid, &home, &home_buffer); + r = acquire_home(context, &home, &home_buffer); if (r < 0) { *exit_status = EXIT_CHDIR; return log_exec_error_errno(context, params, r, "Failed to determine $HOME for user: %m"); @@ -4210,9 +4318,10 @@ int exec_invoke( r = cg_attach_everywhere(params->cgroup_supported, p, 0, NULL, NULL); if (r == -EUCLEAN) { *exit_status = EXIT_CGROUP; - return log_exec_error_errno(context, params, r, "Failed to attach process to cgroup %s " + return log_exec_error_errno(context, params, r, + "Failed to attach process to cgroup '%s', " "because the cgroup or one of its parents or " - "siblings is in the threaded mode: %m", p); + "siblings is in the threaded mode.", p); } if (r < 0) { *exit_status = EXIT_CGROUP; @@ -4242,13 +4351,20 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to set up standard input: %m"); } - r = setup_output(context, params, STDOUT_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); + _cleanup_free_ char *fname = NULL; + r = path_extract_filename(command->path, &fname); + if (r < 0) { + *exit_status = EXIT_STDOUT; + return log_exec_error_errno(context, params, r, "Failed to extract filename from path %s: %m", command->path); + } + + r = setup_output(context, params, STDOUT_FILENO, socket_fd, named_iofds, fname, uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDOUT; return log_exec_error_errno(context, params, r, "Failed to set up standard output: %m"); } - r = setup_output(context, params, STDERR_FILENO, socket_fd, named_iofds, basename(command->path), uid, gid, &journal_stream_dev, &journal_stream_ino); + r = setup_output(context, params, STDERR_FILENO, socket_fd, named_iofds, fname, uid, gid, &journal_stream_dev, &journal_stream_ino); if (r < 0) { *exit_status = EXIT_STDERR; return log_exec_error_errno(context, params, r, "Failed to set up standard error output: %m"); @@ -4445,12 +4561,10 @@ int exec_invoke( return log_exec_error_errno(context, params, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]); } - if (FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) { - r = exec_setup_credentials(context, params, params->unit_id, uid, gid); - if (r < 0) { - *exit_status = EXIT_CREDENTIALS; - return log_exec_error_errno(context, params, r, "Failed to set up credentials: %m"); - } + r = exec_setup_credentials(context, params, params->unit_id, uid, gid); + if (r < 0) { + *exit_status = EXIT_CREDENTIALS; + return log_exec_error_errno(context, params, r, "Failed to set up credentials: %m"); } r = build_environment( @@ -4567,7 +4681,7 @@ int exec_invoke( * wins here. (See above.) */ /* All fds passed in the fds array will be closed in the pam child process. */ - r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds); + r = setup_pam(context->pam_name, username, uid, gid, context->tty_path, &accum_env, params->fds, n_fds, params->exec_fd); if (r < 0) { *exit_status = EXIT_PAM; return log_exec_error_errno(context, params, r, "Failed to set up PAM session: %m"); @@ -4639,7 +4753,7 @@ int exec_invoke( if (ns_type_supported(NAMESPACE_IPC)) { r = setup_shareable_ns(runtime->shared->ipcns_storage_socket, CLONE_NEWIPC); - if (r == -EPERM) + if (ERRNO_IS_NEG_PRIVILEGE(r)) log_exec_warning_errno(context, params, r, "PrivateIPC=yes is configured, but IPC namespace setup failed, ignoring: %m"); else if (r < 0) { @@ -4657,7 +4771,13 @@ int exec_invoke( if (needs_mount_namespace) { _cleanup_free_ char *error_path = NULL; - r = apply_mount_namespace(command->flags, context, params, runtime, memory_pressure_path, &error_path); + r = apply_mount_namespace(command->flags, + context, + params, + runtime, + memory_pressure_path, + needs_sandboxing, + &error_path); if (r < 0) { *exit_status = EXIT_NAMESPACE; return log_exec_error_errno(context, params, r, "Failed to set up mount namespacing%s%s: %m", @@ -4672,7 +4792,7 @@ int exec_invoke( } if (context->memory_ksm >= 0) - if (prctl(PR_SET_MEMORY_MERGE, context->memory_ksm) < 0) { + if (prctl(PR_SET_MEMORY_MERGE, context->memory_ksm, 0, 0, 0) < 0) { if (ERRNO_IS_NOT_SUPPORTED(errno)) log_exec_debug_errno(context, params, @@ -4731,26 +4851,16 @@ int exec_invoke( _cleanup_close_ int executable_fd = -EBADF; r = find_executable_full(command->path, /* root= */ NULL, context->exec_search_path, false, &executable, &executable_fd); if (r < 0) { - if (r != -ENOMEM && (command->flags & EXEC_COMMAND_IGNORE_FAILURE)) { - log_exec_struct_errno(context, params, LOG_INFO, r, - "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, - LOG_EXEC_INVOCATION_ID(params), - LOG_EXEC_MESSAGE(params, - "Executable %s missing, skipping: %m", - command->path), - "EXECUTABLE=%s", command->path); - *exit_status = EXIT_SUCCESS; - return 0; - } - *exit_status = EXIT_EXEC; - return log_exec_struct_errno(context, params, LOG_INFO, r, - "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, - LOG_EXEC_INVOCATION_ID(params), - LOG_EXEC_MESSAGE(params, - "Failed to locate executable %s: %m", - command->path), - "EXECUTABLE=%s", command->path); + log_exec_struct_errno(context, params, LOG_NOTICE, r, + "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, + LOG_EXEC_MESSAGE(params, + "Unable to locate executable '%s': %m", + command->path), + "EXECUTABLE=%s", command->path); + /* If the error will be ignored by manager, tune down the log level here. Missing executable + * is very much expected in this case. */ + return r != -ENOMEM && FLAGS_SET(command->flags, EXEC_COMMAND_IGNORE_FAILURE) ? 1 : r; } r = add_shifted_fd(keep_fds, ELEMENTSOF(keep_fds), &n_keep_fds, &executable_fd); @@ -4791,15 +4901,16 @@ int exec_invoke( /* 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(). But first, close the remaining sockets in the context objects. */ + * more. We do keep exec_fd and handoff_timestamp_fd however, if we have it, since we need to keep + * them open until the final execve(). But first, close the remaining sockets in the context + * objects. */ exec_runtime_close(runtime); exec_params_close(params); r = close_all_fds(keep_fds, n_keep_fds); if (r >= 0) - r = shift_fds(params->fds, n_fds); + r = pack_fds(params->fds, n_fds); if (r >= 0) r = flag_fds(params->fds, n_socket_fds, n_fds, context->non_blocking); if (r < 0) { @@ -4945,8 +5056,10 @@ int exec_invoke( } } - /* Apply working directory here, because the working directory might be on NFS and only the user running - * this service might have the correct privilege to change to the working directory */ + /* Apply working directory here, because the working directory might be on NFS and only the user + * running this service might have the correct privilege to change to the working directory. Also, it + * is absolutely 💣 crucial 💣 we applied all mount namespacing rearrangements before this, so that + * the cwd cannot be used to pin directories outside of the sandbox. */ r = apply_working_directory(context, params, runtime, home, exit_status); if (r < 0) return log_exec_error_errno(context, params, r, "Changing to the requested working directory failed: %m"); @@ -5206,31 +5319,29 @@ int exec_invoke( log_command_line(context, params, "Executing", executable, final_argv); - if (params->exec_fd >= 0) { - uint8_t hot = 1; + /* We have finished with all our initializations. Let's now let the manager know that. From this + * point on, if the manager sees POLLHUP on the exec_fd, then execve() was successful. */ - /* We have finished with all our initializations. Let's now let the manager know that. From this point - * on, if the manager sees POLLHUP on the exec_fd, then execve() was successful. */ + r = exec_fd_mark_hot(context, params, /* hot= */ true, exit_status); + if (r < 0) + return r; - if (write(params->exec_fd, &hot, sizeof(hot)) < 0) { - *exit_status = EXIT_EXEC; - return log_exec_error_errno(context, params, errno, "Failed to enable exec_fd: %m"); - } + /* As last thing before the execve(), let's send the handoff timestamp */ + r = send_handoff_timestamp(context, params, exit_status); + if (r < 0) { + /* If this handoff timestamp failed, let's undo the marking as hot */ + (void) exec_fd_mark_hot(context, params, /* hot= */ false, /* reterr_exit_status= */ NULL); + return r; } - r = fexecve_or_execve(executable_fd, executable, final_argv, accum_env); - - if (params->exec_fd >= 0) { - uint8_t hot = 0; + /* NB: we leave executable_fd, exec_fd, handoff_timestamp_fd open here. This is safe, because they + * have O_CLOEXEC set, and the execve() below will thus automatically close them. In fact, for + * exec_fd this is pretty much the whole raison d'etre. */ - /* The execve() failed. This means the exec_fd is still open. Which means we need to tell the manager - * that POLLHUP on it no longer means execve() succeeded. */ + r = fexecve_or_execve(executable_fd, executable, final_argv, accum_env); - if (write(params->exec_fd, &hot, sizeof(hot)) < 0) { - *exit_status = EXIT_EXEC; - return log_exec_error_errno(context, params, errno, "Failed to disable exec_fd: %m"); - } - } + /* The execve() failed, let's undo the marking as hot */ + (void) exec_fd_mark_hot(context, params, /* hot= */ false, /* reterr_exit_status= */ NULL); *exit_status = EXIT_EXEC; return log_exec_error_errno(context, params, r, "Failed to execute %s: %m", executable); diff --git a/src/core/execute-serialize.c b/src/core/execute-serialize.c index b1e716e..ecd1e70 100644 --- a/src/core/execute-serialize.c +++ b/src/core/execute-serialize.c @@ -230,6 +230,10 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { return r; } + r = serialize_bool(f, "exec-cgroup-context-memory-zswap-writeback", c->memory_zswap_writeback); + if (r < 0) + return r; + if (c->memory_limit != CGROUP_LIMIT_MAX) { r = serialize_item_format(f, "exec-cgroup-context-memory-limit", "%" PRIu64, c->memory_limit); if (r < 0) @@ -373,8 +377,7 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) { if (il->limits[type] == cgroup_io_limit_defaults[type]) continue; - key = strjoin("exec-cgroup-context-io-device-limit-", - cgroup_io_limit_type_to_string(type)); + key = strjoin("exec-cgroup-context-io-device-limit-", cgroup_io_limit_type_to_string(type)); if (!key) return -ENOMEM; @@ -678,6 +681,11 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { r = safe_atou64(val, &c->startup_memory_zswap_max); if (r < 0) return r; + } else if ((val = startswith(l, "exec-cgroup-context-memory-zswap-writeback="))) { + r = parse_boolean(val); + if (r < 0) + return r; + c->memory_zswap_writeback = r; } else if ((val = startswith(l, "exec-cgroup-context-memory-limit="))) { r = safe_atou64(val, &c->memory_limit); if (r < 0) @@ -789,7 +797,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *rwm = NULL; CGroupDevicePermissions p; - r = extract_many_words(&val, " ", 0, &path, &rwm, NULL); + r = extract_many_words(&val, " ", 0, &path, &rwm); if (r < 0) return r; if (r == 0) @@ -806,7 +814,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *weight = NULL; CGroupIODeviceWeight *a = NULL; - r = extract_many_words(&val, " ", 0, &path, &weight, NULL); + r = extract_many_words(&val, " ", 0, &path, &weight); if (r < 0) return r; if (r != 2) @@ -835,7 +843,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *target = NULL; CGroupIODeviceLatency *a = NULL; - r = extract_many_words(&val, " ", 0, &path, &target, NULL); + r = extract_many_words(&val, " ", 0, &path, &target); if (r < 0) return r; if (r != 2) @@ -865,7 +873,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { CGroupIODeviceLimit *limit = NULL; CGroupIOLimitType t; - r = extract_many_words(&val, "= ", 0, &type, &path, &limits, NULL); + r = extract_many_words(&val, "= ", 0, &type, &path, &limits); if (r < 0) return r; if (r != 3) @@ -900,7 +908,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *weight = NULL; CGroupBlockIODeviceWeight *a = NULL; - r = extract_many_words(&val, " ", 0, &path, &weight, NULL); + r = extract_many_words(&val, " ", 0, &path, &weight); if (r < 0) return r; if (r != 2) @@ -921,7 +929,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *bw = NULL; CGroupBlockIODeviceBandwidth *a = NULL; - r = extract_many_words(&val, " ", 0, &path, &bw, NULL); + r = extract_many_words(&val, " ", 0, &path, &bw); if (r < 0) return r; if (r != 2) @@ -951,7 +959,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *path = NULL, *bw = NULL; CGroupBlockIODeviceBandwidth *a = NULL; - r = extract_many_words(&val, " ", 0, &path, &bw, NULL); + r = extract_many_words(&val, " ", 0, &path, &bw); if (r < 0) return r; if (r != 2) @@ -1019,7 +1027,7 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) { _cleanup_free_ char *type = NULL, *path = NULL; uint32_t t; - r = extract_many_words(&val, " ", 0, &type, &path, NULL); + r = extract_many_words(&val, " ", 0, &type, &path); if (r < 0) return r; if (r != 2) @@ -1365,8 +1373,12 @@ static int exec_parameters_serialize(const ExecParameters *p, const ExecContext if (r < 0) return r; + r = serialize_fd(f, fds, "exec-parameters-handoff-timestamp-fd", p->handoff_timestamp_fd); + if (r < 0) + return r; + if (c && exec_context_restrict_filesystems_set(c)) { - r = serialize_fd(f, fds, "exec-parameters-bpf-outer-map-fd", p->bpf_outer_map_fd); + r = serialize_fd(f, fds, "exec-parameters-bpf-outer-map-fd", p->bpf_restrict_fs_map_fd); if (r < 0) return r; } @@ -1479,8 +1491,8 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { return log_oom_debug(); /* Ensure we don't leave any FD uninitialized on error, it makes the fuzzer sad */ - for (size_t i = 0; i < p->n_socket_fds + p->n_storage_fds; ++i) - p->fds[i] = -EBADF; + FOREACH_ARRAY(i, p->fds, p->n_socket_fds + p->n_storage_fds) + *i = -EBADF; r = deserialize_fd_many(fds, val, p->n_socket_fds + p->n_storage_fds, p->fds); if (r < 0) @@ -1522,7 +1534,7 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { _cleanup_free_ char *type = NULL, *prefix = NULL; ExecDirectoryType dt; - r = extract_many_words(&val, "= ", 0, &type, &prefix, NULL); + r = extract_many_words(&val, "= ", 0, &type, &prefix); if (r < 0) return r; if (r == 0) @@ -1585,7 +1597,7 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->stdin_fd = fd; + close_and_replace(p->stdin_fd, fd); } else if ((val = startswith(l, "exec-parameters-stdout-fd="))) { int fd; @@ -1594,7 +1606,7 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->stdout_fd = fd; + close_and_replace(p->stdout_fd, fd); } else if ((val = startswith(l, "exec-parameters-stderr-fd="))) { int fd; @@ -1603,7 +1615,7 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->stderr_fd = fd; + close_and_replace(p->stderr_fd, fd); } else if ((val = startswith(l, "exec-parameters-exec-fd="))) { int fd; @@ -1611,7 +1623,15 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->exec_fd = fd; + close_and_replace(p->exec_fd, fd); + } else if ((val = startswith(l, "exec-parameters-handoff-timestamp-fd="))) { + int fd; + + fd = deserialize_fd(fds, val); + if (fd < 0) + continue; + + close_and_replace(p->handoff_timestamp_fd, fd); } else if ((val = startswith(l, "exec-parameters-bpf-outer-map-fd="))) { int fd; @@ -1619,13 +1639,13 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->bpf_outer_map_fd = fd; + close_and_replace(p->bpf_restrict_fs_map_fd, fd); } else if ((val = startswith(l, "exec-parameters-notify-socket="))) { r = free_and_strdup(&p->notify_socket, val); if (r < 0) return r; } else if ((val = startswith(l, "exec-parameters-open-file="))) { - OpenFile *of = NULL; + OpenFile *of; r = open_file_parse(val, &of); if (r < 0) @@ -1643,7 +1663,7 @@ static int exec_parameters_deserialize(ExecParameters *p, FILE *f, FDSet *fds) { if (fd < 0) continue; - p->user_lookup_fd = fd; + close_and_replace(p->user_lookup_fd, fd); } else if ((val = startswith(l, "exec-parameters-files-env="))) { r = deserialize_strv(val, &p->files_env); if (r < 0) @@ -1812,6 +1832,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; + r = serialize_item_tristate(f, "exec-context-mount-api-vfs", c->mount_apivfs); + if (r < 0) + return r; + r = serialize_item_tristate(f, "exec-context-memory-ksm", c->memory_ksm); if (r < 0) return r; @@ -1868,20 +1892,10 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; - if (c->mount_apivfs_set) { - r = serialize_bool(f, "exec-context-mount-api-vfs", c->mount_apivfs); - if (r < 0) - return r; - } - r = serialize_bool_elide(f, "exec-context-same-pgrp", c->same_pgrp); if (r < 0) return r; - r = serialize_bool_elide(f, "exec-context-cpu-sched-reset-on-fork", c->cpu_sched_reset_on_fork); - if (r < 0) - return r; - r = serialize_bool(f, "exec-context-ignore-sigpipe", c->ignore_sigpipe); if (r < 0) return r; @@ -2154,6 +2168,8 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (r < 0) return r; + /* This is also passed to executor as an argument. So, the information should be redundant in general. + * But, let's keep this as is for consistency with other elements of ExecContext. See exec_spawn(). */ r = serialize_item_format(f, "exec-context-log-level-max", "%d", c->log_level_max); if (r < 0) return r; @@ -2538,14 +2554,14 @@ static int exec_context_serialize(const ExecContext *c, FILE *f) { if (base64mem(sc->data, sc->size, &data) < 0) return log_oom_debug(); - r = serialize_item_format(f, "exec-context-set-credentials", "%s %s %s", sc->id, yes_no(sc->encrypted), data); + r = serialize_item_format(f, "exec-context-set-credentials", "%s %s %s", sc->id, data, yes_no(sc->encrypted)); if (r < 0) return r; } ExecLoadCredential *lc; HASHMAP_FOREACH(lc, c->load_credentials) { - r = serialize_item_format(f, "exec-context-load-credentials", "%s %s %s", lc->id, yes_no(lc->encrypted), lc->path); + r = serialize_item_format(f, "exec-context-load-credentials", "%s %s %s", lc->id, lc->path, yes_no(lc->encrypted)); if (r < 0) return r; } @@ -2636,7 +2652,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { break; p = word; - r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&p, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return r; if (r == 0) @@ -2669,12 +2685,12 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { return r; } else if ((val = startswith(l, "exec-context-root-hash="))) { c->root_hash = mfree(c->root_hash); - r = unhexmem(val, strlen(val), &c->root_hash, &c->root_hash_size); + r = unhexmem(val, &c->root_hash, &c->root_hash_size); if (r < 0) return r; } else if ((val = startswith(l, "exec-context-root-hash-sig="))) { c->root_hash_sig = mfree(c->root_hash_sig); - r= unbase64mem(val, strlen(val), &c->root_hash_sig, &c->root_hash_sig_size); + r= unbase64mem(val, &c->root_hash_sig, &c->root_hash_sig_size); if (r < 0) return r; } else if ((val = startswith(l, "exec-context-root-ephemeral="))) { @@ -2695,6 +2711,10 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { r = safe_atoi(val, &c->private_mounts); if (r < 0) return r; + } else if ((val = startswith(l, "exec-context-mount-api-vfs="))) { + r = safe_atoi(val, &c->mount_apivfs); + if (r < 0) + return r; } else if ((val = startswith(l, "exec-context-memory-ksm="))) { r = safe_atoi(val, &c->memory_ksm); if (r < 0) @@ -2762,22 +2782,11 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { c->protect_system = protect_system_from_string(val); if (c->protect_system < 0) return -EINVAL; - } else if ((val = startswith(l, "exec-context-mount-api-vfs="))) { - r = parse_boolean(val); - if (r < 0) - return r; - c->mount_apivfs = r; - c->mount_apivfs_set = true; } else if ((val = startswith(l, "exec-context-same-pgrp="))) { r = parse_boolean(val); if (r < 0) return r; c->same_pgrp = r; - } else if ((val = startswith(l, "exec-context-cpu-sched-reset-on-fork="))) { - r = parse_boolean(val); - if (r < 0) - return r; - c->cpu_sched_reset_on_fork = r; } else if ((val = startswith(l, "exec-context-non-blocking="))) { r = parse_boolean(val); if (r < 0) @@ -2828,7 +2837,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { _cleanup_free_ char *type = NULL, *mode = NULL; ExecDirectoryType dt; - r = extract_many_words(&val, "= ", 0, &type, &mode, NULL); + r = extract_many_words(&val, "= ", 0, &type, &mode); if (r < 0) return r; if (r == 0 || !mode) @@ -2854,7 +2863,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { break; p = tuple; - r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create, NULL); + r = extract_many_words(&p, ":", EXTRACT_UNESCAPE_SEPARATORS, &path, &only_create); if (r < 0) return r; if (r < 2) @@ -3054,7 +3063,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (c->stdin_data) return -EINVAL; /* duplicated */ - r = unbase64mem(val, strlen(val), &c->stdin_data, &c->stdin_data_size); + r = unbase64mem(val, &c->stdin_data, &c->stdin_data_size); if (r < 0) return r; } else if ((val = startswith(l, "exec-context-tty-path="))) { @@ -3098,6 +3107,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { if (r < 0) return r; } else if ((val = startswith(l, "exec-context-log-level-max="))) { + /* See comment in serialization. */ r = safe_atoi(val, &c->log_level_max); if (r < 0) return r; @@ -3314,7 +3324,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { } else if ((val = startswith(l, "exec-context-temporary-filesystems="))) { _cleanup_free_ char *path = NULL, *options = NULL; - r = extract_many_words(&val, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &path, &options, NULL); + r = extract_many_words(&val, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &path, &options); if (r < 0) return r; if (r < 1) @@ -3392,7 +3402,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { _cleanup_free_ char *s_id = NULL, *s_errno_num = NULL; int id, errno_num; - r = extract_many_words(&val, NULL, 0, &s_id, &s_errno_num, NULL); + r = extract_many_words(&val, NULL, 0, &s_id, &s_errno_num); if (r < 0) return r; if (r != 2) @@ -3432,7 +3442,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { _cleanup_free_ char *s_id = NULL, *s_errno_num = NULL; int id, errno_num; - r = extract_many_words(&val, " ", 0, &s_id, &s_errno_num, NULL); + r = extract_many_words(&val, " ", 0, &s_id, &s_errno_num); if (r < 0) return r; if (r != 2) @@ -3505,8 +3515,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &source, - &destination, - NULL); + &destination); if (r < 0) return r; if (r == 0) @@ -3538,8 +3547,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, - &opts, - NULL); + &opts); if (r < 0) return r; if (r == 0) @@ -3619,8 +3627,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, - &opts, - NULL); + &opts); if (r < 0) return r; if (r == 0) @@ -3669,7 +3676,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { _cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL; _cleanup_free_ char *id = NULL, *encrypted = NULL, *data = NULL; - r = extract_many_words(&val, " ", 0, &id, &encrypted, &data, NULL); + r = extract_many_words(&val, " ", EXTRACT_DONT_COALESCE_SEPARATORS, &id, &data, &encrypted); if (r < 0) return r; if (r != 3) @@ -3688,7 +3695,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { .encrypted = r, }; - r = unbase64mem(data, strlen(data), &sc->data, &sc->size); + r = unbase64mem(data, &sc->data, &sc->size); if (r < 0) return r; @@ -3701,7 +3708,7 @@ static int exec_context_deserialize(ExecContext *c, FILE *f) { _cleanup_(exec_load_credential_freep) ExecLoadCredential *lc = NULL; _cleanup_free_ char *id = NULL, *encrypted = NULL, *path = NULL; - r = extract_many_words(&val, " ", 0, &id, &encrypted, &path, NULL); + r = extract_many_words(&val, " ", EXTRACT_DONT_COALESCE_SEPARATORS, &id, &path, &encrypted); if (r < 0) return r; if (r != 3) diff --git a/src/core/execute.c b/src/core/execute.c index 8dbdfcf..513e95e 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -147,7 +147,7 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) const char *path = exec_context_tty_path(context); - if (p && p->stdin_fd >= 0 && isatty(p->stdin_fd)) + if (p && p->stdin_fd >= 0 && isatty_safe(p->stdin_fd)) fd = p->stdin_fd; else if (path && (context->tty_path || is_terminal_input(context->std_input) || is_terminal_output(context->std_output) || is_terminal_output(context->std_error))) { @@ -162,9 +162,11 @@ void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) * that will be closed automatically, and operate on it for convenience. */ lock_fd = lock_dev_console(); if (ERRNO_IS_NEG_PRIVILEGE(lock_fd)) - log_debug_errno(lock_fd, "No privileges to lock /dev/console, proceeding without: %m"); + log_debug_errno(lock_fd, "No privileges to lock /dev/console, proceeding without lock: %m"); + else if (ERRNO_IS_NEG_DEVICE_ABSENT(lock_fd)) + log_debug_errno(lock_fd, "Device /dev/console does not exist, proceeding without lock: %m"); else if (lock_fd < 0) - return (void) log_debug_errno(lock_fd, "Failed to lock /dev/console: %m"); + log_warning_errno(lock_fd, "Failed to lock /dev/console, proceeding without lock: %m"); if (context->tty_vhangup) (void) terminal_vhangup_fd(fd); @@ -351,19 +353,18 @@ static void log_command_line(Unit *unit, const char *msg, const char *executable static int exec_context_load_environment(const Unit *unit, const ExecContext *c, char ***l); -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - ExecParameters *params, - ExecRuntime *runtime, - const CGroupContext *cgroup_context, - pid_t *ret) { +int exec_spawn( + Unit *unit, + ExecCommand *command, + const ExecContext *context, + ExecParameters *params, + ExecRuntime *runtime, + const CGroupContext *cgroup_context, + PidRef *ret) { - char serialization_fd_number[DECIMAL_STR_MAX(int) + 1]; - _cleanup_free_ char *subcgroup_path = NULL, *log_level = NULL, *executor_path = NULL; + _cleanup_free_ char *subcgroup_path = NULL, *max_log_levels = NULL, *executor_path = NULL; _cleanup_fdset_free_ FDSet *fdset = NULL; _cleanup_fclose_ FILE *f = NULL; - pid_t pid; int r; assert(unit); @@ -371,10 +372,11 @@ int exec_spawn(Unit *unit, assert(unit->manager->executor_fd >= 0); assert(command); assert(context); - assert(ret); assert(params); - assert(params->fds || (params->n_socket_fds + params->n_storage_fds <= 0)); + assert(!params->fds || FLAGS_SET(params->flags, EXEC_PASS_FDS)); + assert(params->fds || (params->n_socket_fds + params->n_storage_fds == 0)); assert(!params->files_env); /* We fill this field, ensure it comes NULL-initialized to us */ + assert(ret); LOG_CONTEXT_PUSH_UNIT(unit); @@ -404,8 +406,8 @@ int exec_spawn(Unit *unit, * child's memory.max, serialize all the state needed to start the unit, and pass it to the * systemd-executor binary. clone() with CLONE_VM + CLONE_VFORK will pause the parent until the exec * and ensure all memory is shared. The child immediately execs the new binary so the delay should - * be minimal. Once glibc provides a clone3 wrapper we can switch to that, and clone directly in the - * target cgroup. */ + * be minimal. If glibc 2.39 is available pidfd_spawn() is used in order to get a race-free pid fd + * and to clone directly into the target cgroup (if we booted with cgroupv2). */ r = open_serialization_file("sd-executor-state", &f); if (r < 0) @@ -430,39 +432,57 @@ int exec_spawn(Unit *unit, if (r < 0) return log_unit_error_errno(unit, r, "Failed to set O_CLOEXEC on serialized fds: %m"); - r = log_level_to_string_alloc(log_get_max_level(), &log_level); + /* If LogLevelMax= is specified, then let's use the specified log level at the beginning of the + * executor process. To achieve that the specified log level is passed as an argument, rather than + * the one for the manager process. */ + r = log_max_levels_to_string(context->log_level_max >= 0 ? context->log_level_max : log_get_max_level(), &max_log_levels); if (r < 0) - return log_unit_error_errno(unit, r, "Failed to convert log level to string: %m"); + return log_unit_error_errno(unit, r, "Failed to convert max log levels to string: %m"); r = fd_get_path(unit->manager->executor_fd, &executor_path); if (r < 0) return log_unit_error_errno(unit, r, "Failed to get executor path from fd: %m"); + char serialization_fd_number[DECIMAL_STR_MAX(int)]; xsprintf(serialization_fd_number, "%i", fileno(f)); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + dual_timestamp start_timestamp; + + /* Record the start timestamp before we fork so that it is guaranteed to be earlier than the + * handoff timestamp. */ + dual_timestamp_now(&start_timestamp); + /* The executor binary is pinned, to avoid compatibility problems during upgrades. */ r = posix_spawn_wrapper( FORMAT_PROC_FD_PATH(unit->manager->executor_fd), STRV_MAKE(executor_path, "--deserialize", serialization_fd_number, - "--log-level", log_level, + "--log-level", max_log_levels, "--log-target", log_target_to_string(manager_get_executor_log_target(unit->manager))), environ, - &pid); + cg_unified() > 0 ? subcgroup_path : NULL, + &pidref); + if (r == -EUCLEAN && subcgroup_path) + return log_unit_error_errno(unit, r, + "Failed to spawn process into cgroup '%s', because the cgroup " + "or one of its parents or siblings is in the threaded mode.", + subcgroup_path); if (r < 0) return log_unit_error_errno(unit, r, "Failed to spawn executor: %m"); - - log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); - /* We add the new process to the cgroup both in the child (so that we can be sure that no user code is ever * executed outside of the cgroup) and in the parent (so that we can be sure that when we kill the cgroup the * process will be killed too). */ - if (subcgroup_path) - (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, subcgroup_path, pid); + if (r == 0 && subcgroup_path) + (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, subcgroup_path, pidref.pid); + /* r > 0: Already in the right cgroup thanks to CLONE_INTO_CGROUP */ + + log_unit_debug(unit, "Forked %s as " PID_FMT " (%s CLONE_INTO_CGROUP)", + command->path, pidref.pid, r > 0 ? "via" : "without"); - exec_status_start(&command->exec_status, pid); + exec_status_start(&command->exec_status, pidref.pid, &start_timestamp); - *ret = pid; + *ret = TAKE_PIDREF(pidref); return 0; } @@ -491,6 +511,7 @@ void exec_context_init(ExecContext *c) { .tty_rows = UINT_MAX, .tty_cols = UINT_MAX, .private_mounts = -1, + .mount_apivfs = -1, .memory_ksm = -1, .set_login_environment = -1, }; @@ -664,13 +685,19 @@ void exec_command_done_array(ExecCommand *c, size_t n) { exec_command_done(i); } +ExecCommand* exec_command_free(ExecCommand *c) { + if (!c) + return NULL; + + exec_command_done(c); + return mfree(c); +} + ExecCommand* exec_command_free_list(ExecCommand *c) { ExecCommand *i; - while ((i = LIST_POP(command, c))) { - exec_command_done(i); - free(i); - } + while ((i = LIST_POP(command, c))) + exec_command_free(i); return NULL; } @@ -1396,7 +1423,7 @@ bool exec_context_maintains_privileges(const ExecContext *c) { if (!c->user) return true; - if (streq(c->user, "root") || streq(c->user, "0")) + if (STR_IN_SET(c->user, "root", "0")) return true; return false; @@ -1421,8 +1448,8 @@ bool exec_context_get_effective_mount_apivfs(const ExecContext *c) { assert(c); /* Explicit setting wins */ - if (c->mount_apivfs_set) - return c->mount_apivfs; + if (c->mount_apivfs >= 0) + return c->mount_apivfs > 0; /* Default to "yes" if root directory or image are specified */ if (exec_context_with_rootfs(c)) @@ -1657,6 +1684,15 @@ uint64_t exec_context_get_timer_slack_nsec(const ExecContext *c) { return (uint64_t) MAX(r, 0); } +bool exec_context_get_set_login_environment(const ExecContext *c) { + assert(c); + + if (c->set_login_environment >= 0) + return c->set_login_environment; + + return c->user || c->dynamic_user || c->pam_name; +} + char** exec_context_get_syscall_filter(const ExecContext *c) { _cleanup_strv_free_ char **l = NULL; @@ -1787,14 +1823,17 @@ char** exec_context_get_restrict_filesystems(const ExecContext *c) { return l ? TAKE_PTR(l) : strv_new(NULL); } -void exec_status_start(ExecStatus *s, pid_t pid) { +void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts) { assert(s); *s = (ExecStatus) { .pid = pid, }; - dual_timestamp_now(&s->start_timestamp); + if (ts) + s->start_timestamp = *ts; + else + dual_timestamp_now(&s->start_timestamp); } void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status) { @@ -1814,6 +1853,19 @@ void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int (void) utmp_put_dead_process(context->utmp_id, pid, code, status); } +void exec_status_handoff(ExecStatus *s, const struct ucred *ucred, const dual_timestamp *ts) { + assert(s); + assert(ucred); + assert(ts); + + if (ucred->pid != s->pid) + *s = (ExecStatus) { + .pid = ucred->pid, + }; + + s->handoff_timestamp = *ts; +} + void exec_status_reset(ExecStatus *s) { assert(s); @@ -1836,19 +1888,45 @@ void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix) { if (dual_timestamp_is_set(&s->start_timestamp)) fprintf(f, "%sStart Timestamp: %s\n", - prefix, FORMAT_TIMESTAMP(s->start_timestamp.realtime)); + prefix, FORMAT_TIMESTAMP_STYLE(s->start_timestamp.realtime, TIMESTAMP_US)); + + if (dual_timestamp_is_set(&s->handoff_timestamp) && dual_timestamp_is_set(&s->start_timestamp) && + s->handoff_timestamp.monotonic > s->start_timestamp.monotonic) + fprintf(f, + "%sHandoff Timestamp: %s since start\n", + prefix, + FORMAT_TIMESPAN(usec_sub_unsigned(s->handoff_timestamp.monotonic, s->start_timestamp.monotonic), 1)); + else + fprintf(f, + "%sHandoff Timestamp: %s\n", + prefix, FORMAT_TIMESTAMP_STYLE(s->handoff_timestamp.realtime, TIMESTAMP_US)); + + if (dual_timestamp_is_set(&s->exit_timestamp)) { + + if (dual_timestamp_is_set(&s->handoff_timestamp) && s->exit_timestamp.monotonic > s->handoff_timestamp.monotonic) + fprintf(f, + "%sExit Timestamp: %s since handoff\n", + prefix, + FORMAT_TIMESPAN(usec_sub_unsigned(s->exit_timestamp.monotonic, s->handoff_timestamp.monotonic), 1)); + else if (dual_timestamp_is_set(&s->start_timestamp) && s->exit_timestamp.monotonic > s->start_timestamp.monotonic) + fprintf(f, + "%sExit Timestamp: %s since start\n", + prefix, + FORMAT_TIMESPAN(usec_sub_unsigned(s->exit_timestamp.monotonic, s->start_timestamp.monotonic), 1)); + else + fprintf(f, + "%sExit Timestamp: %s\n", + prefix, FORMAT_TIMESTAMP_STYLE(s->exit_timestamp.realtime, TIMESTAMP_US)); - if (dual_timestamp_is_set(&s->exit_timestamp)) fprintf(f, - "%sExit Timestamp: %s\n" "%sExit Code: %s\n" "%sExit Status: %i\n", - prefix, FORMAT_TIMESTAMP(s->exit_timestamp.realtime), prefix, sigchld_code_to_string(s->code), prefix, s->status); + } } -static void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { _cleanup_free_ char *cmd = NULL; const char *prefix2; @@ -1951,8 +2029,7 @@ static char *destroy_tree(char *path) { } void exec_shared_runtime_done(ExecSharedRuntime *rt) { - if (!rt) - return; + assert(rt); if (rt->manager) (void) hashmap_remove(rt->manager->exec_shared_runtime_by_id, rt->id); @@ -1965,8 +2042,10 @@ void exec_shared_runtime_done(ExecSharedRuntime *rt) { } static ExecSharedRuntime* exec_shared_runtime_free(ExecSharedRuntime *rt) { - exec_shared_runtime_done(rt); + if (!rt) + return NULL; + exec_shared_runtime_done(rt); return mfree(rt); } @@ -2090,15 +2169,13 @@ static int exec_shared_runtime_make( return r; } - if (exec_needs_network_namespace(c)) { + if (exec_needs_network_namespace(c)) if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, netns_storage_socket) < 0) return -errno; - } - if (exec_needs_ipc_namespace(c)) { + if (exec_needs_ipc_namespace(c)) if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, ipcns_storage_socket) < 0) return -errno; - } r = exec_shared_runtime_add(m, id, &tmp_dir, &var_tmp_dir, netns_storage_socket, ipcns_storage_socket, ret); if (r < 0) @@ -2488,7 +2565,7 @@ void exec_params_shallow_clear(ExecParameters *p) { p->fds = mfree(p->fds); p->exec_fd = safe_close(p->exec_fd); p->user_lookup_fd = -EBADF; - p->bpf_outer_map_fd = -EBADF; + p->bpf_restrict_fs_map_fd = -EBADF; p->unit_id = mfree(p->unit_id); p->invocation_id = SD_ID128_NULL; p->invocation_id_string[0] = '\0'; @@ -2643,46 +2720,46 @@ ExecCleanMask exec_clean_mask_from_string(const char *s) { } static const char* const exec_input_table[_EXEC_INPUT_MAX] = { - [EXEC_INPUT_NULL] = "null", - [EXEC_INPUT_TTY] = "tty", + [EXEC_INPUT_NULL] = "null", + [EXEC_INPUT_TTY] = "tty", [EXEC_INPUT_TTY_FORCE] = "tty-force", - [EXEC_INPUT_TTY_FAIL] = "tty-fail", - [EXEC_INPUT_SOCKET] = "socket", - [EXEC_INPUT_NAMED_FD] = "fd", - [EXEC_INPUT_DATA] = "data", - [EXEC_INPUT_FILE] = "file", + [EXEC_INPUT_TTY_FAIL] = "tty-fail", + [EXEC_INPUT_SOCKET] = "socket", + [EXEC_INPUT_NAMED_FD] = "fd", + [EXEC_INPUT_DATA] = "data", + [EXEC_INPUT_FILE] = "file", }; DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { - [EXEC_OUTPUT_INHERIT] = "inherit", - [EXEC_OUTPUT_NULL] = "null", - [EXEC_OUTPUT_TTY] = "tty", - [EXEC_OUTPUT_KMSG] = "kmsg", - [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", - [EXEC_OUTPUT_JOURNAL] = "journal", + [EXEC_OUTPUT_INHERIT] = "inherit", + [EXEC_OUTPUT_NULL] = "null", + [EXEC_OUTPUT_TTY] = "tty", + [EXEC_OUTPUT_KMSG] = "kmsg", + [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", + [EXEC_OUTPUT_JOURNAL] = "journal", [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", - [EXEC_OUTPUT_SOCKET] = "socket", - [EXEC_OUTPUT_NAMED_FD] = "fd", - [EXEC_OUTPUT_FILE] = "file", - [EXEC_OUTPUT_FILE_APPEND] = "append", - [EXEC_OUTPUT_FILE_TRUNCATE] = "truncate", + [EXEC_OUTPUT_SOCKET] = "socket", + [EXEC_OUTPUT_NAMED_FD] = "fd", + [EXEC_OUTPUT_FILE] = "file", + [EXEC_OUTPUT_FILE_APPEND] = "append", + [EXEC_OUTPUT_FILE_TRUNCATE] = "truncate", }; DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { - [EXEC_UTMP_INIT] = "init", + [EXEC_UTMP_INIT] = "init", [EXEC_UTMP_LOGIN] = "login", - [EXEC_UTMP_USER] = "user", + [EXEC_UTMP_USER] = "user", }; DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); static const char* const exec_preserve_mode_table[_EXEC_PRESERVE_MODE_MAX] = { - [EXEC_PRESERVE_NO] = "no", - [EXEC_PRESERVE_YES] = "yes", + [EXEC_PRESERVE_NO] = "no", + [EXEC_PRESERVE_YES] = "yes", [EXEC_PRESERVE_RESTART] = "restart", }; @@ -2690,10 +2767,10 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(exec_preserve_mode, ExecPreserveMode, EX /* This table maps ExecDirectoryType to the setting it is configured with in the unit */ static const char* const exec_directory_type_table[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory", - [EXEC_DIRECTORY_STATE] = "StateDirectory", - [EXEC_DIRECTORY_CACHE] = "CacheDirectory", - [EXEC_DIRECTORY_LOGS] = "LogsDirectory", + [EXEC_DIRECTORY_RUNTIME] = "RuntimeDirectory", + [EXEC_DIRECTORY_STATE] = "StateDirectory", + [EXEC_DIRECTORY_CACHE] = "CacheDirectory", + [EXEC_DIRECTORY_LOGS] = "LogsDirectory", [EXEC_DIRECTORY_CONFIGURATION] = "ConfigurationDirectory", }; @@ -2724,10 +2801,10 @@ DEFINE_STRING_TABLE_LOOKUP(exec_directory_type_mode, ExecDirectoryType); * one is supposed to be generic enough to be used for unit types that don't use ExecContext and per-unit * directories, specifically .timer units with their timestamp touch file. */ static const char* const exec_resource_type_table[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = "runtime", - [EXEC_DIRECTORY_STATE] = "state", - [EXEC_DIRECTORY_CACHE] = "cache", - [EXEC_DIRECTORY_LOGS] = "logs", + [EXEC_DIRECTORY_RUNTIME] = "runtime", + [EXEC_DIRECTORY_STATE] = "state", + [EXEC_DIRECTORY_CACHE] = "cache", + [EXEC_DIRECTORY_LOGS] = "logs", [EXEC_DIRECTORY_CONFIGURATION] = "configuration", }; @@ -2736,7 +2813,7 @@ DEFINE_STRING_TABLE_LOOKUP(exec_resource_type, ExecDirectoryType); static const char* const exec_keyring_mode_table[_EXEC_KEYRING_MODE_MAX] = { [EXEC_KEYRING_INHERIT] = "inherit", [EXEC_KEYRING_PRIVATE] = "private", - [EXEC_KEYRING_SHARED] = "shared", + [EXEC_KEYRING_SHARED] = "shared", }; DEFINE_STRING_TABLE_LOOKUP(exec_keyring_mode, ExecKeyringMode); diff --git a/src/core/execute.h b/src/core/execute.h index 5a6927a..107ae25 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -91,6 +91,7 @@ typedef enum ExecKeyringMode { struct ExecStatus { dual_timestamp start_timestamp; dual_timestamp exit_timestamp; + dual_timestamp handoff_timestamp; pid_t pid; int code; /* as in siginfo_t::si_code */ int status; /* as in siginfo_t::si_status */ @@ -199,7 +200,6 @@ struct ExecContext { bool nice_set:1; bool ioprio_set:1; bool cpu_sched_set:1; - bool mount_apivfs_set:1; /* This is not exposed to the user but available internally. We need it to make sure that whenever we * spawn /usr/bin/mount it is run in the same process group as us so that the autofs logic detects @@ -312,6 +312,7 @@ struct ExecContext { ProcSubset proc_subset; /* subset= */ int private_mounts; + int mount_apivfs; int memory_ksm; bool private_tmp; bool private_network; @@ -326,7 +327,6 @@ struct ExecContext { ProtectSystem protect_system; ProtectHome protect_home; bool protect_hostname; - bool mount_apivfs; bool dynamic_user; bool remove_ipc; @@ -390,22 +390,23 @@ static inline bool exec_context_with_rootfs(const ExecContext *c) { } typedef enum ExecFlags { - EXEC_APPLY_SANDBOXING = 1 << 0, - EXEC_APPLY_CHROOT = 1 << 1, - EXEC_APPLY_TTY_STDIN = 1 << 2, - EXEC_PASS_LOG_UNIT = 1 << 3, /* Whether to pass the unit name to the service's journal stream connection */ - EXEC_CHOWN_DIRECTORIES = 1 << 4, /* chown() the runtime/state/cache/log directories to the user we run as, under all conditions */ - EXEC_NSS_DYNAMIC_BYPASS = 1 << 5, /* Set the SYSTEMD_NSS_DYNAMIC_BYPASS environment variable, to disable nss-systemd blocking on PID 1, for use by dbus-daemon */ - EXEC_CGROUP_DELEGATE = 1 << 6, - EXEC_IS_CONTROL = 1 << 7, - EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */ - EXEC_WRITE_CREDENTIALS = 1 << 9, /* Set up the credential store logic */ + EXEC_APPLY_SANDBOXING = 1 << 0, + EXEC_APPLY_CHROOT = 1 << 1, + EXEC_APPLY_TTY_STDIN = 1 << 2, + EXEC_PASS_LOG_UNIT = 1 << 3, /* Whether to pass the unit name to the service's journal stream connection */ + EXEC_CHOWN_DIRECTORIES = 1 << 4, /* chown() the runtime/state/cache/log directories to the user we run as, under all conditions */ + EXEC_NSS_DYNAMIC_BYPASS = 1 << 5, /* Set the SYSTEMD_NSS_DYNAMIC_BYPASS environment variable, to disable nss-systemd blocking on PID 1, for use by dbus-daemon */ + EXEC_CGROUP_DELEGATE = 1 << 6, + EXEC_IS_CONTROL = 1 << 7, + EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */ + EXEC_SETUP_CREDENTIALS = 1 << 9, /* Set up the credential store logic */ + EXEC_SETUP_CREDENTIALS_FRESH = 1 << 10, /* Set up a new credential store (disable reuse) */ /* The following are not used by execute.c, but by consumers internally */ - EXEC_PASS_FDS = 1 << 10, - EXEC_SETENV_RESULT = 1 << 11, - EXEC_SET_WATCHDOG = 1 << 12, - EXEC_SETENV_MONITOR_RESULT = 1 << 13, /* Pass exit status to OnFailure= and OnSuccess= dependencies. */ + EXEC_PASS_FDS = 1 << 11, + EXEC_SETENV_RESULT = 1 << 12, + EXEC_SET_WATCHDOG = 1 << 13, + EXEC_SETENV_MONITOR_RESULT = 1 << 14, /* Pass exit status to OnFailure= and OnSuccess= dependencies. */ } ExecFlags; /* Parameters for a specific invocation of a command. This structure is put together right before a command is @@ -442,7 +443,7 @@ struct ExecParameters { int stdout_fd; int stderr_fd; - /* An fd that is closed by the execve(), and thus will result in EOF when the execve() is done */ + /* An fd that is closed by the execve(), and thus will result in EOF when the execve() is done. */ int exec_fd; char *notify_socket; @@ -453,7 +454,9 @@ struct ExecParameters { char **files_env; int user_lookup_fd; - int bpf_outer_map_fd; + int handoff_timestamp_fd; + + int bpf_restrict_fs_map_fd; /* Used for logging in the executor functions */ char *unit_id; @@ -461,34 +464,40 @@ struct ExecParameters { char invocation_id_string[SD_ID128_STRING_MAX]; }; -#define EXEC_PARAMETERS_INIT(_flags) \ - (ExecParameters) { \ - .flags = (_flags), \ - .stdin_fd = -EBADF, \ - .stdout_fd = -EBADF, \ - .stderr_fd = -EBADF, \ - .exec_fd = -EBADF, \ - .bpf_outer_map_fd = -EBADF, \ - .user_lookup_fd = -EBADF, \ - }; +#define EXEC_PARAMETERS_INIT(_flags) \ + (ExecParameters) { \ + .flags = (_flags), \ + .stdin_fd = -EBADF, \ + .stdout_fd = -EBADF, \ + .stderr_fd = -EBADF, \ + .exec_fd = -EBADF, \ + .bpf_restrict_fs_map_fd = -EBADF, \ + .user_lookup_fd = -EBADF, \ + .handoff_timestamp_fd = -EBADF, \ + } #include "unit.h" #include "dynamic-user.h" -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - ExecParameters *exec_params, - ExecRuntime *runtime, - const CGroupContext *cgroup_context, - pid_t *ret); +int exec_spawn( + Unit *unit, + ExecCommand *command, + const ExecContext *context, + ExecParameters *exec_params, + ExecRuntime *runtime, + const CGroupContext *cgroup_context, + PidRef *ret); void exec_command_done(ExecCommand *c); void exec_command_done_array(ExecCommand *c, size_t n); +ExecCommand* exec_command_free(ExecCommand *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(ExecCommand*, exec_command_free); ExecCommand* exec_command_free_list(ExecCommand *c); void exec_command_free_array(ExecCommand **c, size_t n); void exec_command_reset_status_array(ExecCommand *c, size_t n); void exec_command_reset_status_list_array(ExecCommand **c, size_t n); + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); void exec_command_append_list(ExecCommand **l, ExecCommand *e); int exec_command_set(ExecCommand *c, const char *path, ...) _sentinel_; @@ -527,14 +536,16 @@ int exec_context_get_nice(const ExecContext *c); int exec_context_get_cpu_sched_policy(const ExecContext *c); int exec_context_get_cpu_sched_priority(const ExecContext *c); uint64_t exec_context_get_timer_slack_nsec(const ExecContext *c); +bool exec_context_get_set_login_environment(const ExecContext *c); char** exec_context_get_syscall_filter(const ExecContext *c); char** exec_context_get_syscall_archs(const ExecContext *c); char** exec_context_get_syscall_log(const ExecContext *c); char** exec_context_get_address_families(const ExecContext *c); char** exec_context_get_restrict_filesystems(const ExecContext *c); -void exec_status_start(ExecStatus *s, pid_t pid); +void exec_status_start(ExecStatus *s, pid_t pid, const dual_timestamp *ts); void exec_status_exit(ExecStatus *s, const ExecContext *context, pid_t pid, int code, int status); +void exec_status_handoff(ExecStatus *s, const struct ucred *ucred, const dual_timestamp *ts); void exec_status_dump(const ExecStatus *s, FILE *f, const char *prefix); void exec_status_reset(ExecStatus *s); @@ -613,23 +624,23 @@ bool exec_needs_ipc_namespace(const ExecContext *context); #define LOG_EXEC_INVOCATION_ID_FIELD_FORMAT(ep) \ ((ep)->runtime_scope == RUNTIME_SCOPE_USER ? "USER_INVOCATION_ID=%s" : "INVOCATION_ID=%s") -#define log_exec_full_errno_zerook(ec, ep, level, error, ...) \ - ({ \ - const ExecContext *_c = (ec); \ - const ExecParameters *_p = (ep); \ - const int _l = (level); \ - bool _do_log = !(log_get_max_level() < LOG_PRI(_l) || \ - !(_c->log_level_max < 0 || \ - _c->log_level_max >= LOG_PRI(_l))); \ - LOG_CONTEXT_PUSH_IOV(_c->log_extra_fields, \ - _c->n_log_extra_fields); \ - !_do_log ? -ERRNO_VALUE(error) : \ - log_object_internal(_l, error, PROJECT_FILE, \ - __LINE__, __func__, \ - LOG_EXEC_ID_FIELD(_p), \ - _p->unit_id, \ - LOG_EXEC_INVOCATION_ID_FIELD(_p), \ - _p->invocation_id_string, ##__VA_ARGS__); \ +#define log_exec_full_errno_zerook(ec, ep, level, error, ...) \ + ({ \ + const ExecContext *_c = (ec); \ + const ExecParameters *_p = (ep); \ + const int _l = (level); \ + bool _do_log = _c->log_level_max < 0 || \ + _c->log_level_max >= LOG_PRI(_l); \ + LOG_CONTEXT_PUSH_IOV(_c->log_extra_fields, \ + _c->n_log_extra_fields); \ + !_do_log ? -ERRNO_VALUE(error) : \ + log_object_internal(_l, error, \ + PROJECT_FILE, __LINE__, __func__, \ + LOG_EXEC_ID_FIELD(_p), \ + _p->unit_id, \ + LOG_EXEC_INVOCATION_ID_FIELD(_p), \ + _p->invocation_id_string, \ + ##__VA_ARGS__); \ }) #define log_exec_full_errno(ec, ep, level, error, ...) \ @@ -653,48 +664,34 @@ bool exec_needs_ipc_namespace(const ExecContext *context); #define log_exec_warning_errno(ec, ep, error, ...) log_exec_full_errno(ec, ep, LOG_WARNING, error, __VA_ARGS__) #define log_exec_error_errno(ec, ep, error, ...) log_exec_full_errno(ec, ep, LOG_ERR, error, __VA_ARGS__) -#define log_exec_struct_errno(ec, ep, level, error, ...) \ - ({ \ - const ExecContext *_c = (ec); \ - const ExecParameters *_p = (ep); \ - const int _l = (level); \ - bool _do_log = !(_c->log_level_max < 0 || \ - _c->log_level_max >= LOG_PRI(_l)); \ - LOG_CONTEXT_PUSH_IOV(_c->log_extra_fields, \ - _c->n_log_extra_fields); \ - _do_log ? \ - log_struct_errno(_l, error, __VA_ARGS__, LOG_EXEC_ID_FIELD_FORMAT(_p), _p->unit_id) : \ - -ERRNO_VALUE(error); \ - }) - -#define log_exec_struct(ec, ep, level, ...) log_exec_struct_errno(ec, ep, level, 0, __VA_ARGS__) - -#define log_exec_struct_iovec_errno(ec, ep, level, error, iovec, n_iovec) \ - ({ \ - const ExecContext *_c = (ec); \ - const ExecParameters *_p = (ep); \ - const int _l = (level); \ - bool _do_log = !(_c->log_level_max < 0 || \ - _c->log_level_max >= LOG_PRI(_l)); \ - LOG_CONTEXT_PUSH_IOV(_c->log_extra_fields, \ - _c->n_log_extra_fields); \ - _do_log ? \ - log_struct_iovec_errno(_l, error, iovec, n_iovec) : \ - -ERRNO_VALUE(error); \ - }) - -#define log_exec_struct_iovec(ec, ep, level, iovec, n_iovec) log_exec_struct_iovec_errno(ec, ep, level, 0, iovec, n_iovec) - /* Like LOG_MESSAGE(), but with the unit name prefixed. */ #define LOG_EXEC_MESSAGE(ep, fmt, ...) LOG_MESSAGE("%s: " fmt, (ep)->unit_id, ##__VA_ARGS__) #define LOG_EXEC_ID(ep) LOG_EXEC_ID_FIELD_FORMAT(ep), (ep)->unit_id #define LOG_EXEC_INVOCATION_ID(ep) LOG_EXEC_INVOCATION_ID_FIELD_FORMAT(ep), (ep)->invocation_id_string -#define _LOG_CONTEXT_PUSH_EXEC(ec, ep, p, c) \ - const ExecContext *c = (ec); \ - const ExecParameters *p = (ep); \ +#define log_exec_struct_errno(ec, ep, level, error, ...) \ + ({ \ + const ExecContext *_c = (ec); \ + const ExecParameters *_p = (ep); \ + const int _l = (level); \ + bool _do_log = _c->log_level_max < 0 || \ + _c->log_level_max >= LOG_PRI(_l); \ + LOG_CONTEXT_PUSH_IOV(_c->log_extra_fields, \ + _c->n_log_extra_fields); \ + !_do_log ? -ERRNO_VALUE(error) : \ + log_struct_errno(_l, error, \ + LOG_EXEC_ID(_p), \ + LOG_EXEC_INVOCATION_ID(_p), \ + __VA_ARGS__); \ + }) + +#define log_exec_struct(ec, ep, level, ...) log_exec_struct_errno(ec, ep, level, 0, __VA_ARGS__) + +#define _LOG_CONTEXT_PUSH_EXEC(ec, ep, p, c) \ + const ExecContext *c = (ec); \ + const ExecParameters *p = (ep); \ LOG_CONTEXT_PUSH_KEY_VALUE(LOG_EXEC_ID_FIELD(p), p->unit_id); \ - LOG_CONTEXT_PUSH_KEY_VALUE(LOG_EXEC_INVOCATION_ID_FIELD(p), p->invocation_id_string); \ + LOG_CONTEXT_PUSH_KEY_VALUE(LOG_EXEC_INVOCATION_ID_FIELD(p), p->invocation_id_string); \ LOG_CONTEXT_PUSH_IOV(c->log_extra_fields, c->n_log_extra_fields) #define LOG_CONTEXT_PUSH_EXEC(ec, ep) \ diff --git a/src/core/executor.c b/src/core/executor.c index b2716ef..bd0c742 100644 --- a/src/core/executor.c +++ b/src/core/executor.c @@ -245,12 +245,13 @@ static int run(int argc, char *argv[]) { log_exec_struct_errno(&context, ¶ms, LOG_ERR, r, "MESSAGE_ID=" SD_MESSAGE_SPAWN_FAILED_STR, - LOG_EXEC_INVOCATION_ID(¶ms), LOG_EXEC_MESSAGE(¶ms, "Failed at step %s spawning %s: %m", status, command.path), "EXECUTABLE=%s", command.path); } else - assert(exit_status == EXIT_SUCCESS); /* When 'skip' is chosen in the confirm spawn prompt */ + /* r == 0: 'skip' is chosen in the confirm spawn prompt + * r > 0: expected/ignored failure, do not log at error level */ + assert((r == 0) == (exit_status == EXIT_SUCCESS)); return exit_status; } diff --git a/src/core/fuzz-execute-serialize.c b/src/core/fuzz-execute-serialize.c index 6069efd..5b2dc95 100644 --- a/src/core/fuzz-execute-serialize.c +++ b/src/core/fuzz-execute-serialize.c @@ -56,7 +56,7 @@ static void exec_fuzz_one(FILE *f, FDSet *fdset) { params.stderr_fd = -EBADF; params.exec_fd = -EBADF; params.user_lookup_fd = -EBADF; - params.bpf_outer_map_fd = -EBADF; + params.bpf_restrict_fs_map_fd = -EBADF; if (!params.fds) params.n_socket_fds = params.n_storage_fds = 0; for (size_t i = 0; params.fds && i < params.n_socket_fds + params.n_storage_fds; i++) diff --git a/src/core/generator-setup.c b/src/core/generator-setup.c index 00d6ad6..b16211e 100644 --- a/src/core/generator-setup.c +++ b/src/core/generator-setup.c @@ -8,7 +8,7 @@ #include "rm-rf.h" int lookup_paths_mkdir_generator(LookupPaths *p) { - int r, q; + int r; assert(p); @@ -16,14 +16,8 @@ int lookup_paths_mkdir_generator(LookupPaths *p) { return -EINVAL; r = mkdir_p_label(p->generator, 0755); - - q = mkdir_p_label(p->generator_early, 0755); - if (q < 0 && r >= 0) - r = q; - - q = mkdir_p_label(p->generator_late, 0755); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, mkdir_p_label(p->generator_early, 0755)); + RET_GATHER(r, mkdir_p_label(p->generator_late, 0755)); return r; } diff --git a/src/core/import-creds.c b/src/core/import-creds.c index 48f3160..f27ffed 100644 --- a/src/core/import-creds.c +++ b/src/core/import-creds.c @@ -80,7 +80,7 @@ static int acquire_credential_directory(ImportCredentialContext *c, const char * if (c->target_dir_fd >= 0) return c->target_dir_fd; - r = path_is_mount_point(path, NULL, 0); + r = path_is_mount_point(path); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to determine if %s is a mount point: %m", path); @@ -314,7 +314,7 @@ static int proc_cmdline_callback(const char *key, const char *value, void *data) colon++; if (base64) { - r = unbase64mem(colon, SIZE_MAX, &binary, &l); + r = unbase64mem(colon, &binary, &l); if (r < 0) { log_warning_errno(r, "Failed to decode binary credential '%s' data, ignoring: %m", n); return 0; @@ -519,13 +519,13 @@ static int parse_smbios_strings(ImportCredentialContext *c, const char *data, si return log_oom(); if (!credential_name_valid(cn)) { - log_warning("SMBIOS credential name '%s' is not valid, ignoring: %m", cn); + log_warning("SMBIOS credential name '%s' is not valid, ignoring.", cn); continue; } /* Optionally base64 decode the data, if requested, to allow binary credentials */ if (unbase64) { - r = unbase64mem(eq + 1, nul - (eq + 1), &buf, &buflen); + r = unbase64mem_full(eq + 1, nul - (eq + 1), /* secure = */ false, &buf, &buflen); if (r < 0) { log_warning_errno(r, "Failed to base64 decode credential '%s', ignoring: %m", cn); continue; @@ -753,7 +753,7 @@ static int merge_credentials_trusted(const char *creds_dir) { return 0; /* Do not try to merge initrd credentials into foreign credentials directories */ - if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) { + if (!path_equal(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) { log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set."); return 0; } @@ -815,7 +815,6 @@ static int setenv_notify_socket(void) { static int report_credentials_per_func(const char *title, int (*get_directory_func)(const char **ret)) { _cleanup_free_ DirectoryEntries *de = NULL; - _cleanup_close_ int dir_fd = -EBADF; _cleanup_free_ char *ll = NULL; const char *d = NULL; int r, c = 0; @@ -831,11 +830,7 @@ static int report_credentials_per_func(const char *title, int (*get_directory_fu return log_warning_errno(r, "Failed to determine %s directory: %m", title); } - dir_fd = open(d, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (dir_fd < 0) - return log_warning_errno(errno, "Failed to open credentials directory %s: %m", d); - - r = readdir_all(dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); + r = readdir_all_at(AT_FDCWD, d, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); if (r < 0) return log_warning_errno(r, "Failed to enumerate credentials directory %s: %m", d); diff --git a/src/core/job.c b/src/core/job.c index e78c2a7..2f19468 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -133,6 +133,7 @@ Job* job_free(Job *j) { static void job_set_state(Job *j, JobState state) { assert(j); + assert(j->manager); assert(state >= 0); assert(state < _JOB_STATE_MAX); @@ -145,15 +146,15 @@ static void job_set_state(Job *j, JobState state) { return; if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; + j->manager->n_running_jobs++; else { assert(j->state == JOB_WAITING); - assert(j->unit->manager->n_running_jobs > 0); + assert(j->manager->n_running_jobs > 0); - j->unit->manager->n_running_jobs--; + j->manager->n_running_jobs--; - if (j->unit->manager->n_running_jobs <= 0) - j->unit->manager->jobs_in_progress_event_source = sd_event_source_disable_unref(j->unit->manager->jobs_in_progress_event_source); + if (j->manager->n_running_jobs <= 0) + j->manager->jobs_in_progress_event_source = sd_event_source_disable_unref(j->manager->jobs_in_progress_event_source); } } @@ -281,6 +282,8 @@ int job_install_deserialized(Job *j) { Job **pj; int r; + assert(j); + assert(j->manager); assert(!j->installed); if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) @@ -307,7 +310,7 @@ int job_install_deserialized(Job *j) { j->installed = true; if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; + j->manager->n_running_jobs++; log_unit_debug(j->unit, "Reinstalled deserialized job %s/%s as %u", @@ -633,16 +636,19 @@ static const char* job_done_message_format(Unit *u, JobType t, JobResult result) [JOB_UNSUPPORTED] = "Starting of %s unsupported.", [JOB_COLLECTED] = "Unnecessary job was removed for %s.", [JOB_ONCE] = "Unit %s has been started before and cannot be started again.", + [JOB_FROZEN] = "Cannot start frozen unit %s.", }; static const char* const generic_finished_stop_job[_JOB_RESULT_MAX] = { [JOB_DONE] = "Stopped %s.", [JOB_FAILED] = "Stopped %s with error.", [JOB_TIMEOUT] = "Timed out stopping %s.", + [JOB_FROZEN] = "Cannot stop frozen unit %s.", }; static const char* const generic_finished_reload_job[_JOB_RESULT_MAX] = { [JOB_DONE] = "Reloaded %s.", [JOB_FAILED] = "Reload failed for %s.", [JOB_TIMEOUT] = "Timed out reloading %s.", + [JOB_FROZEN] = "Cannot reload frozen unit %s.", }; /* When verify-active detects the unit is inactive, report it. * Most likely a DEPEND warning from a requisiting unit will @@ -704,6 +710,7 @@ static const struct { [JOB_UNSUPPORTED] = { LOG_WARNING, ANSI_HIGHLIGHT_YELLOW, "UNSUPP" }, [JOB_COLLECTED] = { LOG_INFO, }, [JOB_ONCE] = { LOG_ERR, ANSI_HIGHLIGHT_RED, " ONCE " }, + [JOB_FROZEN] = { LOG_ERR, ANSI_HIGHLIGHT_RED, "FROZEN" }, }; static const char* job_done_mid(JobType type, JobResult result) { @@ -954,6 +961,8 @@ int job_run_and_invalidate(Job *j) { r = job_finish_and_invalidate(j, JOB_DEPENDENCY, true, false); else if (r == -ESTALE) r = job_finish_and_invalidate(j, JOB_ONCE, true, false); + else if (r == -EDEADLK) + r = job_finish_and_invalidate(j, JOB_FROZEN, true, false); else if (r < 0) r = job_finish_and_invalidate(j, JOB_FAILED, true, false); } @@ -1011,7 +1020,7 @@ int job_finish_and_invalidate(Job *j, JobResult result, bool recursive, bool alr goto finish; } - if (IN_SET(result, JOB_FAILED, JOB_INVALID)) + if (IN_SET(result, JOB_FAILED, JOB_INVALID, JOB_FROZEN)) j->manager->n_failed_jobs++; job_uninstall(j); @@ -1369,6 +1378,7 @@ int job_coldplug(Job *j) { void job_shutdown_magic(Job *j) { assert(j); + assert(j->manager); /* The shutdown target gets some special treatment here: we * tell the kernel to begin with flushing its disk caches, to @@ -1381,16 +1391,19 @@ void job_shutdown_magic(Job *j) { if (j->type != JOB_START) return; - if (!MANAGER_IS_SYSTEM(j->unit->manager)) + if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) return; - if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) + /* This is the very beginning of the shutdown phase, so take the timestamp here */ + dual_timestamp_now(j->manager->timestamps + MANAGER_TIMESTAMP_SHUTDOWN_START); + + if (!MANAGER_IS_SYSTEM(j->manager)) return; /* In case messages on console has been disabled on boot */ - j->unit->manager->no_console_output = false; + j->manager->no_console_output = false; - manager_invalidate_startup_units(j->unit->manager); + manager_invalidate_startup_units(j->manager); if (detect_container() > 0) return; @@ -1430,6 +1443,7 @@ bool job_may_gc(Job *j) { Unit *other; assert(j); + assert(j->manager); /* Checks whether this job should be GC'ed away. We only do this for jobs of units that have no effect on their * own and just track external state. For now the only unit type that qualifies for this are .device units. @@ -1450,7 +1464,7 @@ bool job_may_gc(Job *j) { * referenced by one, and reset this whenever we notice that no private bus connections are around. This means * the GC is a bit too conservative when it comes to jobs created by private bus connections. */ if (j->ref_by_private_bus) { - if (set_isempty(j->unit->manager->private_buses)) + if (set_isempty(j->manager->private_buses)) j->ref_by_private_bus = false; else return false; @@ -1473,6 +1487,7 @@ bool job_may_gc(Job *j) { void job_add_to_gc_queue(Job *j) { assert(j); + assert(j->manager); if (j->in_gc_queue) return; @@ -1480,7 +1495,7 @@ void job_add_to_gc_queue(Job *j) { if (!job_may_gc(j)) return; - LIST_PREPEND(gc_queue, j->unit->manager->gc_job_queue, j); + LIST_PREPEND(gc_queue, j->manager->gc_job_queue, j); j->in_gc_queue = true; } @@ -1645,6 +1660,7 @@ static const char* const job_result_table[_JOB_RESULT_MAX] = { [JOB_UNSUPPORTED] = "unsupported", [JOB_COLLECTED] = "collected", [JOB_ONCE] = "once", + [JOB_FROZEN] = "frozen", }; DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); diff --git a/src/core/job.h b/src/core/job.h index 891d87a..8318b52 100644 --- a/src/core/job.h +++ b/src/core/job.h @@ -96,6 +96,7 @@ enum JobResult { JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ JOB_COLLECTED, /* Job was garbage collected, since nothing needed it anymore */ JOB_ONCE, /* Unit was started before, and hence can't be started again */ + JOB_FROZEN, /* Unit is currently frozen, so we can't safely operate on it */ _JOB_RESULT_MAX, _JOB_RESULT_INVALID = -EINVAL, }; diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c index b8e3f7a..c39b136 100644 --- a/src/core/kmod-setup.c +++ b/src/core/kmod-setup.c @@ -9,28 +9,13 @@ #include "fileio.h" #include "kmod-setup.h" #include "macro.h" +#include "module-util.h" #include "recurse-dir.h" #include "string-util.h" #include "strv.h" #include "virt.h" #if HAVE_KMOD -#include "module-util.h" - -static void systemd_kmod_log( - void *data, - int priority, - const char *file, int line, - const char *fn, - const char *format, - va_list args) { - - /* library logging is enabled at debug only */ - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_DEBUG, 0, file, line, fn, format, args); - REENABLE_WARNING; -} - static int match_modalias_recurse_dir_cb( RecurseDirEvent event, const char *path, @@ -113,12 +98,11 @@ static bool in_qemu(void) { int kmod_setup(void) { #if HAVE_KMOD - static const struct { const char *module; const char *path; - bool warn_if_unavailable:1; - bool warn_if_module:1; + bool warn_if_unavailable; + bool warn_if_module; bool (*condition_fn)(void); } kmod_table[] = { /* This one we need to load explicitly, since auto-loading on use doesn't work @@ -166,34 +150,32 @@ int kmod_setup(void) { { "tpm", "/sys/class/tpmrm", false, false, efi_has_tpm2 }, #endif }; - _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL; - unsigned i; + + int r; if (have_effective_cap(CAP_SYS_MODULE) <= 0) return 0; - for (i = 0; i < ELEMENTSOF(kmod_table); i++) { - if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0) + _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL; + FOREACH_ELEMENT(kmod, kmod_table) { + if (kmod->path && access(kmod->path, F_OK) >= 0) continue; - if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn()) + if (kmod->condition_fn && !kmod->condition_fn()) continue; - if (kmod_table[i].warn_if_module) + if (kmod->warn_if_module) log_debug("Your kernel apparently lacks built-in %s support. Might be " "a good idea to compile it in. We'll now try to work around " - "this by loading the module...", kmod_table[i].module); + "this by loading the module...", kmod->module); if (!ctx) { - ctx = kmod_new(NULL, NULL); - if (!ctx) - return log_oom(); - - kmod_set_log_fn(ctx, systemd_kmod_log, NULL); - kmod_load_resources(ctx); + r = module_setup_context(&ctx); + if (r < 0) + return log_error_errno(r, "Failed to initialize kmod context: %m"); } - (void) module_load_and_warn(ctx, kmod_table[i].module, kmod_table[i].warn_if_unavailable); + (void) module_load_and_warn(ctx, kmod->module, kmod->warn_if_unavailable); } #endif diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in index 45f9ab0..df219d8 100644 --- a/src/core/load-fragment-gperf.gperf.in +++ b/src/core/load-fragment-gperf.gperf.in @@ -136,7 +136,7 @@ {{type}}.ProtectSystem, config_parse_protect_system, 0, offsetof({{type}}, exec_context.protect_system) {{type}}.ProtectHome, config_parse_protect_home, 0, offsetof({{type}}, exec_context.protect_home) {{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}}.MountAPIVFS, config_parse_tristate, 0, offsetof({{type}}, exec_context.mount_apivfs) {{type}}.Personality, config_parse_personality, 0, offsetof({{type}}, exec_context.personality) {{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) @@ -220,6 +220,7 @@ {{type}}.StartupMemorySwapMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context) {{type}}.MemoryZSwapMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context) {{type}}.StartupMemoryZSwapMax, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context) +{{type}}.MemoryZSwapWriteback, config_parse_bool, 0, offsetof({{type}}, cgroup_context.memory_zswap_writeback) {{type}}.MemoryLimit, config_parse_memory_limit, 0, offsetof({{type}}, cgroup_context) {{type}}.DeviceAllow, config_parse_device_allow, 0, offsetof({{type}}, cgroup_context) {{type}}.DevicePolicy, config_parse_device_policy, 0, offsetof({{type}}, cgroup_context.device_policy) @@ -309,7 +310,8 @@ Unit.PartOf, config_parse_unit_deps, Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0 -Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 +Unit.RequiresMountsFor, config_parse_unit_mounts_for, 0, 0 +Unit.WantsMountsFor, config_parse_unit_mounts_for, 0, 0 Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) @@ -325,7 +327,7 @@ Unit.IgnoreOnSnapshot, config_parse_warn_compat, Unit.JobTimeoutSec, config_parse_job_timeout_sec, 0, 0 Unit.JobRunningTimeoutSec, config_parse_job_running_timeout_sec, 0, 0 Unit.JobTimeoutAction, config_parse_emergency_action, 0, offsetof(Unit, job_timeout_action) -Unit.JobTimeoutRebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, job_timeout_reboot_arg) +Unit.JobTimeoutRebootArgument, config_parse_reboot_parameter, 0, offsetof(Unit, job_timeout_reboot_arg) Unit.StartLimitIntervalSec, config_parse_sec, 0, offsetof(Unit, start_ratelimit.interval) {# The following is a legacy alias name for compatibility #} Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_ratelimit.interval) @@ -335,7 +337,7 @@ Unit.FailureAction, config_parse_emergency_action, Unit.SuccessAction, config_parse_emergency_action, 0, offsetof(Unit, success_action) Unit.FailureActionExitStatus, config_parse_exit_status, 0, offsetof(Unit, failure_action_exit_status) Unit.SuccessActionExitStatus, config_parse_exit_status, 0, offsetof(Unit, success_action_exit_status) -Unit.RebootArgument, config_parse_unit_string_printf, 0, offsetof(Unit, reboot_arg) +Unit.RebootArgument, config_parse_reboot_parameter, 0, offsetof(Unit, reboot_arg) Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions) @@ -498,6 +500,7 @@ Socket.FreeBind, config_parse_bool, Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) +Socket.PassFileDescriptorsToExec, config_parse_bool, 0, offsetof(Socket, pass_fds_to_exec) Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) Socket.PassPacketInfo, config_parse_bool, 0, offsetof(Socket, pass_pktinfo) Socket.Timestamping, config_parse_socket_timestamping, 0, offsetof(Socket, timestamping) @@ -530,7 +533,7 @@ Socket.SELinuxContextFromNet, config_parse_warn_compat, {{ EXEC_CONTEXT_CONFIG_ITEMS('Socket') }} {{ CGROUP_CONTEXT_CONFIG_ITEMS('Socket') }} {{ KILL_CONTEXT_CONFIG_ITEMS('Socket') }} -Mount.What, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.what) +Mount.What, config_parse_mount_node, 0, offsetof(Mount, parameters_fragment.what) Mount.Where, config_parse_unit_path_printf, 0, offsetof(Mount, where) Mount.Options, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.options) Mount.Type, config_parse_unit_string_printf, 0, offsetof(Mount, parameters_fragment.fstype) @@ -547,7 +550,7 @@ Automount.Where, config_parse_unit_path_printf, Automount.ExtraOptions, config_parse_unit_string_printf, 0, offsetof(Automount, extra_options) Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) Automount.TimeoutIdleSec, config_parse_sec_fix_0, 0, offsetof(Automount, timeout_idle_usec) -Swap.What, config_parse_unit_path_printf, 0, offsetof(Swap, parameters_fragment.what) +Swap.What, config_parse_mount_node, 0, offsetof(Swap, parameters_fragment.what) Swap.Priority, config_parse_swap_priority, 0, 0 Swap.Options, config_parse_unit_string_printf, 0, offsetof(Swap, parameters_fragment.options) Swap.TimeoutSec, config_parse_sec_fix_0, 0, offsetof(Swap, timeout_usec) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 0baf08e..5ae6888 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -16,8 +16,8 @@ #include "all-units.h" #include "alloc-util.h" #include "bpf-firewall.h" -#include "bpf-lsm.h" #include "bpf-program.h" +#include "bpf-restrict-fs.h" #include "bpf-socket-bind.h" #include "bus-error.h" #include "bus-internal.h" @@ -38,6 +38,7 @@ #include "fileio.h" #include "firewall-util.h" #include "fs-util.h" +#include "fstab-util.h" #include "hexdecoct.h" #include "iovec-util.h" #include "ioprio-util.h" @@ -56,6 +57,7 @@ #include "pcre2-util.h" #include "percent-util.h" #include "process-util.h" +#include "reboot-util.h" #include "seccomp-util.h" #include "securebits-util.h" #include "selinux-util.h" @@ -248,7 +250,7 @@ int unit_is_likely_recursive_template_dependency(Unit *u, const char *name, cons /* Fragment paths should also be equal as a custom fragment for a specific template instance * wouldn't necessarily lead to infinite recursion. */ - if (!path_equal_ptr(u->fragment_path, fragment_path)) + if (!path_equal(u->fragment_path, fragment_path)) return false; if (!contains_instance_specifier_superset(format)) @@ -361,6 +363,40 @@ int config_parse_unit_string_printf( return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); } +int config_parse_reboot_parameter( + 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_free_ char *k = NULL; + const Unit *u = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(line); + assert(rvalue); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue); + return 0; + } + + if (!reboot_parameter_is_valid(k)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid reboot parameter '%s', ignoring.", k); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + int config_parse_unit_strv_printf( const char *unit, const char *filename, @@ -433,8 +469,9 @@ int config_parse_colon_separated_paths( const char *rvalue, void *data, void *userdata) { + char ***sv = ASSERT_PTR(data); - const Unit *u = userdata; + const Unit *u = ASSERT_PTR(userdata); int r; assert(filename); @@ -574,17 +611,13 @@ int config_parse_socket_listen( void *data, void *userdata) { + Socket *s = ASSERT_PTR(SOCKET(data)); _cleanup_free_ SocketPort *p = NULL; - SocketPort *tail; - Socket *s; int r; assert(filename); assert(lvalue); assert(rvalue); - assert(data); - - s = SOCKET(data); if (isempty(rvalue)) { /* An empty assignment removes all ports */ @@ -592,10 +625,15 @@ int config_parse_socket_listen( return 0; } - p = new0(SocketPort, 1); + p = new(SocketPort, 1); if (!p) return log_oom(); + *p = (SocketPort) { + .socket = s, + .fd = -EBADF, + }; + if (ltype != SOCKET_SOCKET) { _cleanup_free_ char *k = NULL; @@ -605,7 +643,11 @@ int config_parse_socket_listen( return 0; } - r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + PathSimplifyWarnFlags flags = PATH_CHECK_ABSOLUTE; + if (ltype != SOCKET_SPECIAL) + flags |= PATH_CHECK_NON_API_VFS; + + r = path_simplify_and_warn(k, flags, unit, filename, line, lvalue); if (r < 0) return 0; @@ -619,7 +661,7 @@ int config_parse_socket_listen( p->type = ltype; } else if (streq(lvalue, "ListenNetlink")) { - _cleanup_free_ char *k = NULL; + _cleanup_free_ char *k = NULL; r = unit_path_printf(UNIT(s), rvalue, &k); if (r < 0) { @@ -644,7 +686,7 @@ int config_parse_socket_listen( return 0; } - if (k[0] == '/') { /* Only for AF_UNIX file system sockets… */ + if (path_is_absolute(k)) { /* Only for AF_UNIX file system sockets… */ r = patch_var_run(unit, filename, line, lvalue, &k); if (r < 0) return r; @@ -674,16 +716,7 @@ int config_parse_socket_listen( p->type = SOCKET_SOCKET; } - p->fd = -EBADF; - p->auxiliary_fds = NULL; - p->n_auxiliary_fds = 0; - p->socket = s; - - tail = LIST_FIND_TAIL(port, s->ports); - LIST_INSERT_AFTER(port, s->ports, tail, p); - - p = NULL; - + LIST_APPEND(port, s->ports, TAKE_PTR(p)); return 0; } @@ -858,9 +891,7 @@ int config_parse_exec( void *userdata) { ExecCommand **e = ASSERT_PTR(data); - const Unit *u = userdata; - const char *p; - bool semicolon; + const Unit *u = ASSERT_PTR(userdata); int r; assert(filename); @@ -875,15 +906,11 @@ int config_parse_exec( return 0; } - p = rvalue; + const char *p = rvalue; + bool semicolon; + do { _cleanup_free_ char *path = NULL, *firstword = NULL; - ExecCommandFlags flags = 0; - bool ignore = false, separate_argv0 = false; - _cleanup_free_ ExecCommand *nce = NULL; - _cleanup_strv_free_ char **n = NULL; - size_t nlen = 0; - const char *f; semicolon = false; @@ -897,25 +924,30 @@ int config_parse_exec( continue; } - f = firstword; - for (;;) { - /* We accept an absolute path as first argument. If it's prefixed with - and the path doesn't - * exist, we ignore it instead of erroring out; if it's prefixed with @, we allow overriding of - * argv[0]; if it's prefixed with :, we will not do environment variable substitution; - * if it's prefixed with +, it will be run with full privileges and no sandboxing; if - * it's prefixed with '!' we apply sandboxing, but do not change user/group credentials; if - * it's prefixed with '!!', then we apply user/group credentials if the kernel supports ambient - * capabilities -- if it doesn't we don't apply the credentials themselves, but do apply most - * other sandboxing, with some special exceptions for changing UID. + const char *f = firstword; + bool ignore, separate_argv0 = false; + ExecCommandFlags flags = 0; + + for (;; f++) { + /* We accept an absolute path as first argument. Valid prefixes and their effect: + * + * "-": Ignore if the path doesn't exist + * "@": Allow overriding argv[0] (supplied as a separate argument) + * ":": Disable environment variable substitution + * "+": Run with full privileges and no sandboxing + * "!": Apply sandboxing except for user/group credentials + * "!!": Apply user/group credentials if the kernel supports ambient capabilities - + * if it doesn't we don't apply the credentials themselves, but do apply + * most other sandboxing, with some special exceptions for changing UID. * - * The idea is that '!!' may be used to write services that can take benefit of systemd's - * UID/GID dropping if the kernel supports ambient creds, but provide an automatic fallback to - * privilege dropping within the daemon if the kernel does not offer that. */ + * The idea is that '!!' may be used to write services that can take benefit of + * systemd's UID/GID dropping if the kernel supports ambient creds, but provide + * an automatic fallback to privilege dropping within the daemon if the kernel + * does not offer that. */ - if (*f == '-' && !(flags & EXEC_COMMAND_IGNORE_FAILURE)) { + if (*f == '-' && !(flags & EXEC_COMMAND_IGNORE_FAILURE)) flags |= EXEC_COMMAND_IGNORE_FAILURE; - ignore = true; - } else if (*f == '@' && !separate_argv0) + else if (*f == '@' && !separate_argv0) separate_argv0 = true; else if (*f == ':' && !(flags & EXEC_COMMAND_NO_ENV_EXPAND)) flags |= EXEC_COMMAND_NO_ENV_EXPAND; @@ -928,9 +960,10 @@ int config_parse_exec( flags |= EXEC_COMMAND_AMBIENT_MAGIC; } else break; - f++; } + ignore = FLAGS_SET(flags, EXEC_COMMAND_IGNORE_FAILURE); + r = unit_path_printf(u, f, &path); if (r < 0) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, r, @@ -940,19 +973,18 @@ int config_parse_exec( } if (isempty(path)) { - /* First word is either "-" or "@" with no command. */ log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, - "Empty path in command line%s: '%s'", + "Empty path in command line%s: %s", ignore ? ", ignoring" : "", rvalue); return ignore ? 0 : -ENOEXEC; } if (!string_is_safe(path)) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, - "Executable name contains special characters%s: %s", + "Executable path contains special characters%s: %s", ignore ? ", ignoring" : "", path); return ignore ? 0 : -ENOEXEC; } - if (endswith(path, "/")) { + if (path_implies_directory(path)) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, "Executable path specifies a directory%s: %s", ignore ? ", ignoring" : "", path); @@ -966,92 +998,71 @@ int config_parse_exec( return ignore ? 0 : -ENOEXEC; } - if (!separate_argv0) { - char *w = NULL; - - if (!GREEDY_REALLOC0(n, nlen + 2)) - return log_oom(); + _cleanup_strv_free_ char **args = NULL; - w = strdup(path); - if (!w) + if (!separate_argv0) + if (strv_extend(&args, path) < 0) return log_oom(); - n[nlen++] = w; - n[nlen] = NULL; - } - - path_simplify(path); while (!isempty(p)) { _cleanup_free_ char *word = NULL, *resolved = NULL; - /* Check explicitly for an unquoted semicolon as - * command separator token. */ + /* Check explicitly for an unquoted semicolon as command separator token. */ if (p[0] == ';' && (!p[1] || strchr(WHITESPACE, p[1]))) { p++; - p += strspn(p, WHITESPACE); + p = skip_leading_chars(p, /* bad = */ NULL); semicolon = true; break; } /* Check for \; explicitly, to not confuse it with \\; or "\;" or "\\;" etc. - * extract_first_word() would return the same for all of those. */ + * extract_first_word() would return the same for all of those. */ if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) { - char *w; - p += 2; - p += strspn(p, WHITESPACE); + p = skip_leading_chars(p, /* bad = */ NULL); - if (!GREEDY_REALLOC0(n, nlen + 2)) + if (strv_extend(&args, ";") < 0) return log_oom(); - w = strdup(";"); - if (!w) - return log_oom(); - n[nlen++] = w; - n[nlen] = NULL; continue; } r = extract_first_word_and_warn(&p, &word, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); - if (r == 0) - break; if (r < 0) return ignore ? 0 : -ENOEXEC; + if (r == 0) + break; r = unit_full_printf(u, word, &resolved); if (r < 0) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, r, - "Failed to resolve unit specifiers in %s%s: %m", + "Failed to resolve unit specifiers in '%s'%s: %m", word, ignore ? ", ignoring" : ""); return ignore ? 0 : -ENOEXEC; } - if (!GREEDY_REALLOC(n, nlen + 2)) + if (strv_consume(&args, TAKE_PTR(resolved)) < 0) return log_oom(); - - n[nlen++] = TAKE_PTR(resolved); - n[nlen] = NULL; } - if (!n || !n[0]) { + if (strv_isempty(args)) { log_syntax(unit, ignore ? LOG_WARNING : LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument%s: %s", ignore ? ", ignoring" : "", rvalue); return ignore ? 0 : -ENOEXEC; } - nce = new0(ExecCommand, 1); - if (!nce) + ExecCommand *nec = new(ExecCommand, 1); + if (!nec) return log_oom(); - nce->argv = TAKE_PTR(n); - nce->path = TAKE_PTR(path); - nce->flags = flags; - - exec_command_append_list(e, nce); + *nec = (ExecCommand) { + .path = path_simplify(TAKE_PTR(path)), + .argv = TAKE_PTR(args), + .flags = flags, + }; - /* Do not _cleanup_free_ these. */ - nce = NULL; + exec_command_append_list(e, nec); rvalue = p; } while (semicolon); @@ -1254,7 +1265,7 @@ int config_parse_exec_input_data( return 0; } - r = unbase64mem(rvalue, SIZE_MAX, &p, &sz); + r = unbase64mem(rvalue, &p, &sz); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode base64 data, ignoring: %s", rvalue); @@ -1520,43 +1531,6 @@ int config_parse_exec_cpu_sched_policy(const char *unit, return 0; } -int config_parse_exec_mount_apivfs(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) { - - ExecContext *c = ASSERT_PTR(data); - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - c->mount_apivfs_set = false; - c->mount_apivfs = false; - return 0; - } - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_WARNING, filename, line, k, - "Failed to parse boolean value, ignoring: %s", - rvalue); - return 0; - } - - c->mount_apivfs_set = true; - c->mount_apivfs = k; - return 0; -} - int config_parse_numa_mask(const char *unit, const char *filename, unsigned line, @@ -1748,7 +1722,7 @@ int config_parse_exec_root_hash( } /* We have a roothash to decode, eg: RootHash=012345789abcdef */ - r = unhexmem(rvalue, strlen(rvalue), &roothash_decoded, &roothash_decoded_size); + r = unhexmem(rvalue, &roothash_decoded, &roothash_decoded_size); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHash=, ignoring: %s", rvalue); return 0; @@ -1816,7 +1790,7 @@ int config_parse_exec_root_hash_sig( } /* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */ - r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size); + r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to decode RootHashSignature=, ignoring: %s", rvalue); return 0; @@ -2634,6 +2608,7 @@ int config_parse_working_directory( assert(rvalue); if (isempty(rvalue)) { + c->working_directory_missing_ok = false; c->working_directory_home = false; c->working_directory = mfree(c->working_directory); return 0; @@ -2659,7 +2634,7 @@ int config_parse_working_directory( return missing_ok ? 0 : -ENOEXEC; } - r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE | (missing_ok ? 0 : PATH_CHECK_FATAL), unit, filename, line, lvalue); + r = path_simplify_and_warn(k, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS|(missing_ok ? 0 : PATH_CHECK_FATAL), unit, filename, line, lvalue); if (r < 0) return missing_ok ? 0 : -ENOEXEC; @@ -2697,7 +2672,7 @@ int config_parse_unit_env_file(const char *unit, return 0; } - r = unit_full_printf_full(u, rvalue, PATH_MAX, &n); + r = unit_path_printf(u, rvalue, &n); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue); return 0; @@ -3152,7 +3127,7 @@ int config_parse_unit_condition_string( return 0; } -int config_parse_unit_requires_mounts_for( +int config_parse_unit_mounts_for( const char *unit, const char *filename, unsigned line, @@ -3171,6 +3146,7 @@ int config_parse_unit_requires_mounts_for( assert(lvalue); assert(rvalue); assert(data); + assert(STR_IN_SET(lvalue, "RequiresMountsFor", "WantsMountsFor")); for (const char *p = rvalue;;) { _cleanup_free_ char *word = NULL, *resolved = NULL; @@ -3196,9 +3172,9 @@ int config_parse_unit_requires_mounts_for( if (r < 0) continue; - r = unit_require_mounts_for(u, resolved, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, resolved, UNIT_DEPENDENCY_FILE, unit_mount_dependency_type_from_string(lvalue)); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to add required mount '%s', ignoring: %m", resolved); + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to add requested mount '%s', ignoring: %m", resolved); continue; } } @@ -3695,7 +3671,7 @@ int config_parse_restrict_filesystems( break; } - r = lsm_bpf_parse_filesystem( + r = bpf_restrict_fs_parse_filesystem( word, &c->restrict_filesystems, FILESYSTEM_PARSE_LOG| @@ -4693,7 +4669,7 @@ int config_parse_exec_directories( _cleanup_free_ char *src = NULL, *dest = NULL; const char *q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &src, &dest); if (r == -ENOMEM) return log_oom(); if (r <= 0) { @@ -4908,11 +4884,8 @@ int config_parse_load_credential( void *data, void *userdata) { - _cleanup_free_ char *word = NULL, *k = NULL, *q = NULL; ExecContext *context = ASSERT_PTR(data); - bool encrypted = ltype; - Unit *u = userdata; - const char *p; + const Unit *u = ASSERT_PTR(userdata); int r; assert(filename); @@ -4925,7 +4898,10 @@ int config_parse_load_credential( return 0; } - p = rvalue; + _cleanup_free_ char *word = NULL, *id = NULL, *path = NULL; + const char *p = rvalue; + bool encrypted = ltype; + r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r == -ENOMEM) return log_oom(); @@ -4934,35 +4910,35 @@ int config_parse_load_credential( return 0; } - r = unit_cred_printf(u, word, &k); + r = unit_cred_printf(u, word, &id); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word); return 0; } - if (!credential_name_valid(k)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k); + if (!credential_name_valid(id)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", id); return 0; } if (isempty(p)) { /* If only one field is specified take it as shortcut for inheriting a credential named * the same way from our parent */ - q = strdup(k); - if (!q) + path = strdup(id); + if (!path) return log_oom(); } else { - r = unit_path_printf(u, p, &q); + r = unit_path_printf(u, p, &path); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", p); return 0; } - if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential source \"%s\" not valid, ignoring.", q); + if (path_is_absolute(path) ? !path_is_normalized(path) : !credential_name_valid(path)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential source \"%s\" not valid, ignoring.", path); return 0; } } - r = hashmap_put_credential(&context->load_credentials, k, q, encrypted); + r = hashmap_put_credential(&context->load_credentials, id, path, encrypted); if (r < 0) return log_error_errno(r, "Failed to store load credential '%s': %m", rvalue); @@ -5236,7 +5212,7 @@ int config_parse_bind_paths( void *userdata) { ExecContext *c = ASSERT_PTR(data); - const Unit *u = userdata; + const Unit *u = ASSERT_PTR(userdata); int r; assert(filename); @@ -5267,7 +5243,7 @@ int config_parse_bind_paths( if (r == 0) break; - r = unit_full_printf_full(u, source, PATH_MAX, &sresolved); + r = unit_path_printf(u, source, &sresolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", source); @@ -5396,7 +5372,7 @@ int config_parse_mount_images( return 0; q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -5420,7 +5396,7 @@ int config_parse_mount_images( continue; } - r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS, unit, filename, line, lvalue); if (r < 0) continue; @@ -5436,7 +5412,7 @@ int config_parse_mount_images( continue; } - r = path_simplify_and_warn(dresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + r = path_simplify_and_warn(dresolved, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS, unit, filename, line, lvalue); if (r < 0) continue; @@ -5445,7 +5421,7 @@ int config_parse_mount_images( MountOptions *o = NULL; PartitionDesignator partition_designator; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -5578,7 +5554,7 @@ int config_parse_extension_images( continue; } - r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS, unit, filename, line, lvalue); if (r < 0) continue; @@ -5587,7 +5563,7 @@ int config_parse_extension_images( MountOptions *o = NULL; PartitionDesignator partition_designator; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -5799,7 +5775,7 @@ int config_parse_pid_file( return log_oom(); /* Check that the result is a sensible path */ - r = path_simplify_and_warn(n, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + r = path_simplify_and_warn(n, PATH_CHECK_ABSOLUTE|PATH_CHECK_NON_API_VFS, unit, filename, line, lvalue); if (r < 0) return r; @@ -6095,7 +6071,7 @@ int config_parse_restrict_network_interfaces( break; } - if (!ifname_valid(word)) { + if (!ifname_valid_full(word, IFNAME_VALID_ALTERNATIVE)) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid interface name, ignoring: %s", word); continue; } @@ -6112,6 +6088,47 @@ int config_parse_restrict_network_interfaces( return 0; } +int config_parse_mount_node( + 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) { + + const Unit *u = ASSERT_PTR(userdata); + _cleanup_free_ char *resolved = NULL, *path = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = unit_full_printf(u, rvalue, &resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue); + return 0; + } + + path = fstab_node_to_udev_node(resolved); + if (!path) + return log_oom(); + + /* The source passed is not necessarily something we understand, and we pass it as-is to mount/swapon, + * so path_is_valid is not used. But let's check for basic sanity, i.e. if the source is longer than + * PATH_MAX, you're likely doing something wrong. */ + if (strlen(path) >= PATH_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Resolved mount path '%s' too long, ignoring.", path); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata); +} + static int merge_by_names(Unit *u, Set *names, const char *id) { char *k; int r; @@ -6316,8 +6333,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_nsec, "NANOSECONDS" }, { config_parse_namespace_path_strv, "PATH [...]" }, { config_parse_bind_paths, "PATH[:PATH[:OPTIONS]] [...]" }, - { config_parse_unit_requires_mounts_for, - "PATH [...]" }, + { config_parse_unit_mounts_for, "PATH [...]" }, { config_parse_exec_mount_propagation_flag, "MOUNTFLAG" }, { config_parse_unit_string_printf, "STRING" }, @@ -6365,6 +6381,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_job_mode_isolate, "BOOLEAN" }, { config_parse_personality, "PERSONALITY" }, { config_parse_log_filter_patterns, "REGEX" }, + { config_parse_mount_node, "NODE" }, }; const char *prev = NULL; diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 6919805..005b915 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -23,6 +23,7 @@ void unit_dump_config_items(FILE *f); CONFIG_PARSER_PROTOTYPE(config_parse_unit_deps); CONFIG_PARSER_PROTOTYPE(config_parse_obsolete_unit_deps); CONFIG_PARSER_PROTOTYPE(config_parse_unit_string_printf); +CONFIG_PARSER_PROTOTYPE(config_parse_reboot_parameter); CONFIG_PARSER_PROTOTYPE(config_parse_unit_strv_printf); CONFIG_PARSER_PROTOTYPE(config_parse_unit_path_printf); CONFIG_PARSER_PROTOTYPE(config_parse_colon_separated_paths); @@ -71,7 +72,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_unit_condition_string); CONFIG_PARSER_PROTOTYPE(config_parse_kill_mode); CONFIG_PARSER_PROTOTYPE(config_parse_notify_access); CONFIG_PARSER_PROTOTYPE(config_parse_emergency_action); -CONFIG_PARSER_PROTOTYPE(config_parse_unit_requires_mounts_for); +CONFIG_PARSER_PROTOTYPE(config_parse_unit_mounts_for); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_filter); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_archs); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_errno); @@ -159,6 +160,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_log_filter_patterns); CONFIG_PARSER_PROTOTYPE(config_parse_open_file); CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch); CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set); +CONFIG_PARSER_PROTOTYPE(config_parse_mount_node); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/main.c b/src/core/main.c index 1ed968d..4b8a315 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -21,7 +21,7 @@ #include "architecture.h" #include "argv-util.h" #if HAVE_LIBBPF -#include "bpf-lsm.h" +#include "bpf-restrict-fs.h" #endif #include "build.h" #include "bus-error.h" @@ -68,6 +68,7 @@ #include "manager-serialize.h" #include "mkdir-label.h" #include "mount-setup.h" +#include "mount-util.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -87,6 +88,7 @@ #include "special.h" #include "stat-util.h" #include "stdio-util.h" +#include "string-table.h" #include "strv.h" #include "switch-root.h" #include "sysctl-util.h" @@ -121,7 +123,7 @@ static RuntimeScope arg_runtime_scope; bool arg_dump_core; int arg_crash_chvt; bool arg_crash_shell; -bool arg_crash_reboot; +CrashAction arg_crash_action; static char *arg_confirm_spawn; static ShowStatus arg_show_status; static StatusUnitFormat arg_status_unit_format; @@ -140,6 +142,7 @@ static char **arg_default_environment; static char **arg_manager_environment; static uint64_t arg_capability_bounding_set; static bool arg_no_new_privs; +static int arg_protect_system; static nsec_t arg_timer_slack_nsec; static Set* arg_syscall_archs; static FILE* arg_serialization; @@ -159,6 +162,16 @@ static char **saved_env = NULL; static int parse_configuration(const struct rlimit *saved_rlimit_nofile, const struct rlimit *saved_rlimit_memlock); +static const char* const crash_action_table[_CRASH_ACTION_MAX] = { + [CRASH_FREEZE] = "freeze", + [CRASH_REBOOT] = "reboot", + [CRASH_POWEROFF] = "poweroff", +}; + +DEFINE_STRING_TABLE_LOOKUP(crash_action, CrashAction); + +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_crash_action, crash_action, CrashAction, CRASH_FREEZE, "Invalid crash action"); + static int manager_find_user_config_paths(char ***ret_files, char ***ret_dirs) { _cleanup_free_ char *base = NULL; _cleanup_strv_free_ char **files = NULL, **dirs = NULL; @@ -206,13 +219,17 @@ static int console_setup(void) { r = proc_cmdline_tty_size("/dev/console", &rows, &cols); if (r < 0) - log_warning_errno(r, "Failed to get terminal size, ignoring: %m"); + log_warning_errno(r, "Failed to get /dev/console size, ignoring: %m"); else { r = terminal_set_size_fd(tty_fd, NULL, rows, cols); if (r < 0) - log_warning_errno(r, "Failed to set terminal size, ignoring: %m"); + log_warning_errno(r, "Failed to set /dev/console size, ignoring: %m"); } + r = terminal_reset_ansi_seq(tty_fd); + if (r < 0) + log_warning_errno(r, "Failed to reset /dev/console using ANSI sequences, ignoring: %m"); + return 0; } @@ -273,7 +290,18 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (r < 0) log_warning_errno(r, "Failed to parse crash reboot switch %s, ignoring: %m", value); else - arg_crash_reboot = r; + arg_crash_action = r ? CRASH_REBOOT : CRASH_FREEZE; + + } else if (proc_cmdline_key_streq(key, "systemd.crash_action")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = crash_action_from_string(value); + if (r < 0) + log_warning_errno(r, "Failed to parse crash action switch %s, ignoring: %m", value); + else + arg_crash_action = r; } else if (proc_cmdline_key_streq(key, "systemd.confirm_spawn")) { char *s; @@ -462,7 +490,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (proc_cmdline_value_missing(key, value)) return 0; - r = unbase64mem(value, SIZE_MAX, &p, &sz); + r = unbase64mem(value, &p, &sz); if (r < 0) log_warning_errno(r, "Failed to parse systemd.random_seed= argument, ignoring: %s", value); @@ -610,6 +638,73 @@ static int config_parse_oom_score_adjust( return 0; } +static int config_parse_protect_system_pid1( + 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) { + + int *v = ASSERT_PTR(data), r; + + /* This is modelled after the per-service ProtectSystem= setting, but a bit more restricted on one + * hand, and more automatic in another. i.e. we currently only support yes/no (not "strict" or + * "full"). And we will enable this automatically for the initrd unless configured otherwise. + * + * We might extend this later to match more closely what the per-service ProtectSystem= can do, but + * this is not trivial, due to ordering constraints: besides /usr/ we don't really have much mounted + * at the moment we enable this logic. */ + + if (isempty(rvalue) || streq(rvalue, "auto")) { + *v = -1; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse ProtectSystem= argument '%s', ignoring: %m", rvalue); + return 0; + } + + *v = r; + return 0; +} + +static int config_parse_crash_reboot( + 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) { + + CrashAction *v = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + *v = CRASH_REBOOT; + return 0; + } + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse CrashReboot= argument '%s', ignoring: %m", rvalue); + return 0; + } + + *v = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; + return 0; +} + static int parse_config_file(void) { const ConfigTableItem items[] = { { "Manager", "LogLevel", config_parse_level2, 0, NULL }, @@ -621,7 +716,8 @@ static int parse_config_file(void) { { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, &arg_crash_chvt }, { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, &arg_crash_chvt }, { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, + { "Manager", "CrashReboot", config_parse_crash_reboot, 0, &arg_crash_action }, + { "Manager", "CrashAction", config_parse_crash_action, 0, &arg_crash_action }, { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, { "Manager", "StatusUnitFormat", config_parse_status_unit_format, 0, &arg_status_unit_format }, { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, &arg_cpu_affinity }, @@ -637,6 +733,7 @@ static int parse_config_file(void) { { "Manager", "RuntimeWatchdogPreGovernor", config_parse_string, CONFIG_PARSE_STRING_SAFE, &arg_watchdog_pretimeout_governor }, { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, { "Manager", "NoNewPrivileges", config_parse_bool, 0, &arg_no_new_privs }, + { "Manager", "ProtectSystem", config_parse_protect_system_pid1, 0, &arg_protect_system }, #if HAVE_SECCOMP { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, #else @@ -696,11 +793,12 @@ static int parse_config_file(void) { }; if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) - (void) config_parse_config_file("system.conf", - "Manager\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, - NULL); + (void) config_parse_standard_file_with_dropins( + "systemd/system.conf", + "Manager\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); else { _cleanup_strv_free_ char **files = NULL, **dirs = NULL; int r; @@ -769,8 +867,8 @@ static void set_manager_settings(Manager *m) { m->cad_burst_action = arg_cad_burst_action; /* Note that we don't do structured initialization here, otherwise it will reset the rate limit * counter on every daemon-reload. */ - m->reload_ratelimit.interval = arg_reload_limit_interval_sec; - m->reload_ratelimit.burst = arg_reload_limit_burst; + m->reload_reexec_ratelimit.interval = arg_reload_limit_interval_sec; + m->reload_reexec_ratelimit.burst = arg_reload_limit_burst; manager_set_watchdog(m, WATCHDOG_RUNTIME, arg_runtime_watchdog); manager_set_watchdog(m, WATCHDOG_REBOOT, arg_reboot_watchdog); @@ -935,9 +1033,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_CRASH_REBOOT: - r = parse_boolean_argument("--crash-reboot", optarg, &arg_crash_reboot); + r = parse_boolean_argument("--crash-reboot", optarg, NULL); if (r < 0) return r; + arg_crash_action = r > 0 ? CRASH_REBOOT : CRASH_FREEZE; + break; + + case ARG_CRASH_ACTION: + r = crash_action_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash action \"%s\": %m", optarg); + arg_crash_action = r; break; case ARG_CONFIRM_SPAWN: @@ -1053,7 +1159,7 @@ static int help(void) { " --unit=UNIT Set default unit\n" " --dump-core[=BOOL] Dump core on crash\n" " --crash-vt=NR Change to specified VT on crash\n" - " --crash-reboot[=BOOL] Reboot on crash\n" + " --crash-action=ACTION Specify what to do on crash\n" " --crash-shell[=BOOL] Run shell on crash\n" " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" " --show-status[=BOOL] Show status updates on the console during boot\n" @@ -1265,7 +1371,7 @@ static void test_usr(void) { log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " "Some things will probably break (sometimes even silently) in mysterious ways. " - "Consult https://www.freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); + "Consult https://systemd.io/SEPARATE_USR_IS_BROKEN for more information."); } static int enforce_syscall_archs(Set *archs) { @@ -1277,7 +1383,7 @@ static int enforce_syscall_archs(Set *archs) { r = seccomp_restrict_archs(arg_syscall_archs); if (r < 0) - return log_error_errno(r, "Failed to enforce system call architecture restrication: %m"); + return log_error_errno(r, "Failed to enforce system call architecture restriction: %m"); #endif return 0; } @@ -1435,7 +1541,7 @@ static int fixup_environment(void) { return -errno; /* The kernels sets HOME=/ for init. Let's undo this. */ - if (path_equal_ptr(getenv("HOME"), "/")) + if (path_equal(getenv("HOME"), "/")) assert_se(unsetenv("HOME") == 0); return 0; @@ -1467,32 +1573,37 @@ static int become_shutdown(int objective, int retval) { [MANAGER_KEXEC] = "kexec", }; - char log_level[STRLEN("--log-level=") + DECIMAL_STR_MAX(int)], - timeout[STRLEN("--timeout=") + DECIMAL_STR_MAX(usec_t) + STRLEN("us")], + char 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; + _cleanup_free_ char *max_log_levels = NULL; usec_t watchdog_timer = 0; int r; assert(objective >= 0 && objective < _MANAGER_OBJECTIVE_MAX); assert(table[objective]); - xsprintf(log_level, "--log-level=%d", log_get_max_level()); xsprintf(timeout, "--timeout=%" PRI_USEC "us", arg_defaults.timeout_stop_usec); - const char* command_line[10] = { + const char* command_line[11] = { 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; + size_t pos = 3; assert(command_line[pos-1]); assert(!command_line[pos]); + (void) log_max_levels_to_string(log_get_max_level(), &max_log_levels); + + if (max_log_levels) { + command_line[pos++] = "--log-level"; + command_line[pos++] = max_log_levels; + } + switch (log_get_target()) { case LOG_TARGET_KMSG: @@ -1538,7 +1649,7 @@ static int become_shutdown(int objective, int retval) { (void) watchdog_setup_pretimeout(0); (void) watchdog_setup_pretimeout_governor(NULL); r = watchdog_setup(watchdog_timer); - watchdog_close(r < 0); + watchdog_close(/* disarm= */ r < 0); /* The environment block: */ @@ -1684,6 +1795,35 @@ static void initialize_core_pattern(bool skip_setup) { arg_early_core_pattern); } +static void apply_protect_system(bool skip_setup) { + int r; + + if (skip_setup || getpid_cached() != 1 || arg_protect_system == 0) + return; + + if (arg_protect_system < 0 && !in_initrd()) { + log_debug("ProtectSystem=auto selected, but not running in an initrd, skipping."); + return; + } + + r = make_mount_point("/usr"); + if (r < 0) { + log_warning_errno(r, "Failed to make /usr/ a mount point, ignoring: %m"); + return; + } + + if (mount_nofollow_verbose( + LOG_WARNING, + /* what= */ NULL, + "/usr", + /* fstype= */ NULL, + MS_BIND|MS_REMOUNT|MS_RDONLY, + /* options= */ NULL) < 0) + return; + + log_info("Successfully made /usr/ read-only."); +} + static void update_cpu_affinity(bool skip_setup) { _cleanup_free_ char *mask = NULL; @@ -1966,6 +2106,16 @@ static int invoke_main_loop( "MESSAGE_ID=" SD_MESSAGE_CORE_MAINLOOP_FAILED_STR); } + /* Ensure shutdown timestamp is taken even when bypassing the job engine */ + if (IN_SET(objective, + MANAGER_SOFT_REBOOT, + MANAGER_REBOOT, + MANAGER_KEXEC, + MANAGER_HALT, + MANAGER_POWEROFF) && + !dual_timestamp_is_set(m->timestamps + MANAGER_TIMESTAMP_SHUTDOWN_START)) + dual_timestamp_now(m->timestamps + MANAGER_TIMESTAMP_SHUTDOWN_START); + switch (objective) { case MANAGER_RELOAD: { @@ -2133,9 +2283,9 @@ static void log_execution_mode(bool *ret_first_boot) { /* Let's check whether we are in first boot. First, check if an override was * specified on the kernel command line. If yes, we honour that. */ - r = proc_cmdline_get_bool("systemd.condition-first-boot", /* flags = */ 0, &first_boot); + r = proc_cmdline_get_bool("systemd.condition_first_boot", /* flags = */ 0, &first_boot); if (r < 0) - log_debug_errno(r, "Failed to parse systemd.condition-first-boot= kernel command line argument, ignoring: %m"); + log_debug_errno(r, "Failed to parse systemd.condition_first_boot= kernel command line argument, ignoring: %m"); if (r > 0) log_full(first_boot ? LOG_INFO : LOG_DEBUG, @@ -2221,12 +2371,6 @@ static int initialize_runtime( install_crash_handler(); if (!skip_setup) { - r = mount_cgroup_controllers(); - if (r < 0) { - *ret_error_message = "Failed to mount cgroup hierarchies"; - return r; - } - /* Pull credentials from various sources into a common credential directory (we do * this here, before setting up the machine ID, so that we can use credential info * for setting up the machine ID) */ @@ -2493,7 +2637,7 @@ static void setenv_manager_environment(void) { r = putenv_dup(*p, true); if (r < 0) - log_warning_errno(errno, "Failed to setenv \"%s\", ignoring: %m", *p); + log_warning_errno(r, "Failed to setenv \"%s\", ignoring: %m", *p); } } @@ -2507,7 +2651,7 @@ static void reset_arguments(void) { arg_dump_core = true; arg_crash_chvt = -1; arg_crash_shell = false; - arg_crash_reboot = false; + arg_crash_action = CRASH_FREEZE; arg_confirm_spawn = mfree(arg_confirm_spawn); arg_show_status = _SHOW_STATUS_INVALID; arg_status_unit_format = STATUS_UNIT_FORMAT_DEFAULT; @@ -2531,6 +2675,7 @@ static void reset_arguments(void) { arg_capability_bounding_set = CAP_MASK_UNSET; arg_no_new_privs = false; + arg_protect_system = -1; arg_timer_slack_nsec = NSEC_INFINITY; arg_syscall_archs = set_free(arg_syscall_archs); @@ -2952,6 +3097,24 @@ int main(int argc, char *argv[]) { goto finish; } + if (!skip_setup) { + /* Before we actually start deleting cgroup v1 code, make it harder to boot + * in cgroupv1 mode first. See also #30852. */ + + r = mount_cgroup_legacy_controllers(loaded_policy); + if (r < 0) { + if (r == -ERFKILL) + error_message = "Refusing to run under cgroup v1, SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 not specified on kernel command line"; + else + error_message = "Failed to mount cgroup v1 hierarchy"; + goto finish; + } + if (r > 0) { + log_full(LOG_CRIT, "Legacy cgroup v1 support selected. This is no longer supported. Will proceed anyway after 30s."); + (void) usleep_safe(30 * USEC_PER_SEC); + } + } + /* The efivarfs is now mounted, let's lock down the system token. */ lock_down_efi_variables(); @@ -3038,9 +3201,12 @@ int main(int argc, char *argv[]) { cmdline_take_random_seed(); } - /* A core pattern might have been specified via the cmdline. */ + /* A core pattern might have been specified via the cmdline. */ initialize_core_pattern(skip_setup); + /* Make /usr/ read-only */ + apply_protect_system(skip_setup); + /* Close logging fds, in order not to confuse collecting passed fds and terminal logic below */ log_close(); @@ -3196,7 +3362,8 @@ finish: #endif if (r < 0) - (void) sd_notifyf(0, "ERRNO=%i", -r); + (void) sd_notifyf(/* unset_environment= */ false, + "ERRNO=%i", -r); /* Try to invoke the shutdown binary unless we already failed. * If we failed above, we want to freeze after finishing cleanup. */ @@ -3209,7 +3376,8 @@ finish: /* 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); + (void) sd_notifyf(/* unset_environment= */ false, + "EXIT_STATUS=%i", retval); watchdog_free_device(); arg_watchdog_device = mfree(arg_watchdog_device); diff --git a/src/core/main.h b/src/core/main.h index b12a1cc..1949a08 100644 --- a/src/core/main.h +++ b/src/core/main.h @@ -1,9 +1,21 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include +typedef enum CrashAction { + CRASH_FREEZE, + CRASH_REBOOT, + CRASH_POWEROFF, + _CRASH_ACTION_MAX, + _CRASH_ACTION_INVALID = -EINVAL, +} CrashAction; + +const char* crash_action_to_string(CrashAction action); +CrashAction crash_action_from_string(const char *action); + extern bool arg_dump_core; extern int arg_crash_chvt; extern bool arg_crash_shell; -extern bool arg_crash_reboot; +extern CrashAction arg_crash_action; diff --git a/src/core/manager-dump.c b/src/core/manager-dump.c index 6c32d78..a12d50c 100644 --- a/src/core/manager-dump.c +++ b/src/core/manager-dump.c @@ -64,7 +64,7 @@ static void manager_dump_header(Manager *m, FILE *f, const char *prefix) { * stable between versions. We take the liberty to restructure it entirely between versions and * add/remove fields at will. */ - fprintf(f, "%sManager: systemd " STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")\n", strempty(prefix)); + fprintf(f, "%sManager: systemd " PROJECT_VERSION_FULL " (" GIT_VERSION ")\n", strempty(prefix)); fprintf(f, "%sFeatures: %s\n", strempty(prefix), systemd_features); for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { diff --git a/src/core/manager-serialize.c b/src/core/manager-serialize.c index 1ac2636..b4af82b 100644 --- a/src/core/manager-serialize.c +++ b/src/core/manager-serialize.c @@ -23,11 +23,12 @@ int manager_open_serialization(Manager *m, FILE **ret_f) { return open_serialization_file("systemd-state", ret_f); } -static bool manager_timestamp_shall_serialize(ManagerTimestamp t) { - if (!in_initrd()) +static bool manager_timestamp_shall_serialize(ManagerObjective o, ManagerTimestamp t) { + if (!in_initrd() && o != MANAGER_SOFT_REBOOT) return true; - /* The following timestamps only apply to the host system, hence only serialize them there */ + /* The following timestamps only apply to the host system (or first boot in case of soft-reboot), + * hence only serialize them there. */ return !IN_SET(t, MANAGER_TIMESTAMP_USERSPACE, MANAGER_TIMESTAMP_FINISH, MANAGER_TIMESTAMP_SECURITY_START, MANAGER_TIMESTAMP_SECURITY_FINISH, @@ -108,10 +109,13 @@ int manager_serialize( (void) serialize_usec(f, "pretimeout-watchdog-overridden", m->watchdog_overridden[WATCHDOG_PRETIMEOUT]); (void) serialize_item(f, "pretimeout-watchdog-governor-overridden", m->watchdog_pretimeout_governor_overridden); + (void) serialize_item(f, "previous-objective", manager_objective_to_string(m->objective)); + (void) serialize_item_format(f, "soft-reboots-count", "%u", m->soft_reboots_count); + for (ManagerTimestamp q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { _cleanup_free_ char *joined = NULL; - if (!manager_timestamp_shall_serialize(q)) + if (!manager_timestamp_shall_serialize(m->objective, q)) continue; joined = strjoin(manager_timestamp_to_string(q), "-timestamp"); @@ -139,21 +143,19 @@ int manager_serialize( } if (m->user_lookup_fds[0] >= 0) { - int copy0, copy1; - - copy0 = fdset_put_dup(fds, m->user_lookup_fds[0]); - if (copy0 < 0) - return log_error_errno(copy0, "Failed to add user lookup fd to serialization: %m"); - - copy1 = fdset_put_dup(fds, m->user_lookup_fds[1]); - if (copy1 < 0) - return log_error_errno(copy1, "Failed to add user lookup fd to serialization: %m"); + r = serialize_fd_many(f, fds, "user-lookup", m->user_lookup_fds, 2); + if (r < 0) + return r; + } - (void) serialize_item_format(f, "user-lookup", "%i %i", copy0, copy1); + if (m->handoff_timestamp_fds[0] >= 0) { + r = serialize_fd_many(f, fds, "handoff-timestamp-fds", m->handoff_timestamp_fds, 2); + if (r < 0) + return r; } (void) serialize_ratelimit(f, "dump-ratelimit", &m->dump_ratelimit); - (void) serialize_ratelimit(f, "reload-ratelimit", &m->reload_ratelimit); + (void) serialize_ratelimit(f, "reload-reexec-ratelimit", &m->reload_reexec_ratelimit); bus_track_serialize(m->subscribed, f, "subscribed"); @@ -443,10 +445,10 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { if (r < 0) return r; - } else if (startswith(l, "env=")) { - r = deserialize_environment(l + 4, &m->client_environment); + } else if ((val = startswith(l, "env="))) { + r = deserialize_environment(val, &m->client_environment); if (r < 0) - log_notice_errno(r, "Failed to parse environment entry: \"%s\", ignoring: %m", l); + log_notice_errno(r, "Failed to parse environment entry: \"%s\", ignoring: %m", val); } else if ((val = startswith(l, "notify-fd="))) { int fd; @@ -454,8 +456,7 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { fd = deserialize_fd(fds, val); if (fd >= 0) { m->notify_event_source = sd_event_source_disable_unref(m->notify_event_source); - safe_close(m->notify_fd); - m->notify_fd = fd; + close_and_replace(m->notify_fd, fd); } } else if ((val = startswith(l, "notify-socket="))) { @@ -469,21 +470,26 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { fd = deserialize_fd(fds, val); if (fd >= 0) { m->cgroups_agent_event_source = sd_event_source_disable_unref(m->cgroups_agent_event_source); - safe_close(m->cgroups_agent_fd); - m->cgroups_agent_fd = fd; + close_and_replace(m->cgroups_agent_fd, fd); } } else if ((val = startswith(l, "user-lookup="))) { - int fd0, fd1; - - if (sscanf(val, "%i %i", &fd0, &fd1) != 2 || fd0 < 0 || fd1 < 0 || fd0 == fd1 || !fdset_contains(fds, fd0) || !fdset_contains(fds, fd1)) - log_notice("Failed to parse user lookup fd, ignoring: %s", val); - else { - m->user_lookup_event_source = sd_event_source_disable_unref(m->user_lookup_event_source); - safe_close_pair(m->user_lookup_fds); - m->user_lookup_fds[0] = fdset_remove(fds, fd0); - m->user_lookup_fds[1] = fdset_remove(fds, fd1); - } + + m->user_lookup_event_source = sd_event_source_disable_unref(m->user_lookup_event_source); + safe_close_pair(m->user_lookup_fds); + + r = deserialize_fd_many(fds, val, 2, m->user_lookup_fds); + if (r < 0) + log_warning_errno(r, "Failed to parse user-lookup fds: \"%s\", ignoring: %m", val); + + } else if ((val = startswith(l, "handoff-timestamp-fds="))) { + + m->handoff_timestamp_event_source = sd_event_source_disable_unref(m->handoff_timestamp_event_source); + safe_close_pair(m->handoff_timestamp_fds); + + r = deserialize_fd_many(fds, val, 2, m->handoff_timestamp_fds); + if (r < 0) + log_warning_errno(r, "Failed to parse handoff-timestamp fds: \"%s\", ignoring: %m", val); } else if ((val = startswith(l, "dynamic-user="))) dynamic_user_deserialize_one(m, val, fds, NULL); @@ -495,8 +501,9 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { (void) exec_shared_runtime_deserialize_one(m, val, fds); else if ((val = startswith(l, "subscribed="))) { - if (strv_extend(&m->deserialized_subscribed, val) < 0) - return -ENOMEM; + r = strv_extend(&m->deserialized_subscribed, val); + if (r < 0) + return r; } else if ((val = startswith(l, "varlink-server-socket-address="))) { if (!m->varlink_server && MANAGER_IS_SYSTEM(m)) { r = manager_varlink_init(m); @@ -516,9 +523,25 @@ int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { (void) varlink_server_deserialize_one(m->varlink_server, val, fds); } else if ((val = startswith(l, "dump-ratelimit="))) deserialize_ratelimit(&m->dump_ratelimit, "dump-ratelimit", val); - else if ((val = startswith(l, "reload-ratelimit="))) - deserialize_ratelimit(&m->reload_ratelimit, "reload-ratelimit", val); - else { + else if ((val = startswith(l, "reload-reexec-ratelimit="))) + deserialize_ratelimit(&m->reload_reexec_ratelimit, "reload-reexec-ratelimit", val); + else if ((val = startswith(l, "soft-reboots-count="))) { + unsigned n; + + if (safe_atou(val, &n) < 0) + log_notice("Failed to parse soft reboots counter '%s', ignoring.", val); + else + m->soft_reboots_count = n; + } else if ((val = startswith(l, "previous-objective="))) { + ManagerObjective objective; + + objective = manager_objective_from_string(val); + if (objective < 0) + log_notice("Failed to parse previous objective '%s', ignoring.", val); + else + m->previous_objective = objective; + + } else { ManagerTimestamp q; for (q = 0; q < _MANAGER_TIMESTAMP_MAX; q++) { diff --git a/src/core/manager.c b/src/core/manager.c index 88eebfc..90e72b0 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -25,6 +24,7 @@ #include "alloc-util.h" #include "audit-fd.h" #include "boot-timestamps.h" +#include "build-path.h" #include "bus-common-errors.h" #include "bus-error.h" #include "bus-kernel.h" @@ -36,6 +36,7 @@ #include "constants.h" #include "core-varlink.h" #include "creds-util.h" +#include "daemon-util.h" #include "dbus-job.h" #include "dbus-manager.h" #include "dbus-unit.h" @@ -55,6 +56,7 @@ #include "inotify-util.h" #include "install.h" #include "io-util.h" +#include "iovec-util.h" #include "label-util.h" #include "load-fragment.h" #include "locale-setup.h" @@ -88,6 +90,7 @@ #include "strxcpyx.h" #include "sysctl-util.h" #include "syslog-util.h" +#include "taint.h" #include "terminal-util.h" #include "time-util.h" #include "transaction.h" @@ -122,6 +125,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_handoff_timestamp_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); static int manager_dispatch_sigchld(sd_event_source *source, void *userdata); @@ -263,12 +267,11 @@ static void manager_print_jobs_in_progress(Manager *m) { strempty(status_text)); } - sd_notifyf(false, - "STATUS=%sUser job %s/%s running (%s / %s)...", - job_of_n, - ident, - job_type_to_string(j->type), - time, limit); + (void) sd_notifyf(/* unset_environment= */ false, + "STATUS=%sUser job %s/%s running (%s / %s)...", + job_of_n, + ident, job_type_to_string(j->type), + time, limit); m->status_ready = false; } @@ -397,7 +400,7 @@ static int manager_setup_time_change(Manager *m) { return log_error_errno(r, "Failed to create time change event source: %m"); /* Schedule this slightly earlier than the .timer event sources */ - r = sd_event_source_set_priority(m->time_change_event_source, SD_EVENT_PRIORITY_NORMAL-1); + r = sd_event_source_set_priority(m->time_change_event_source, EVENT_PRIORITY_TIME_CHANGE); if (r < 0) return log_error_errno(r, "Failed to set priority of time change event sources: %m"); @@ -464,7 +467,7 @@ static int manager_setup_timezone_change(Manager *m) { return log_error_errno(r, "Failed to create timezone change event source: %m"); /* Schedule this slightly earlier than the .timer event sources */ - r = sd_event_source_set_priority(new_event, SD_EVENT_PRIORITY_NORMAL-1); + r = sd_event_source_set_priority(new_event, EVENT_PRIORITY_TIME_ZONE); if (r < 0) return log_error_errno(r, "Failed to set priority of timezone change event sources: %m"); @@ -482,21 +485,19 @@ static int enable_special_signals(Manager *m) { if (MANAGER_IS_TEST_RUN(m)) return 0; - /* Enable that we get SIGINT on control-alt-del. In containers - * this will fail with EPERM (older) or EINVAL (newer), so - * ignore that. */ + /* Enable that we get SIGINT on control-alt-del. In containers this will fail with EPERM (older) or + * EINVAL (newer), so ignore that. */ if (reboot(RB_DISABLE_CAD) < 0 && !IN_SET(errno, EPERM, EINVAL)) - log_warning_errno(errno, "Failed to enable ctrl-alt-del handling: %m"); + log_warning_errno(errno, "Failed to enable ctrl-alt-del handling, ignoring: %m"); fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - /* Support systems without virtual console */ - if (fd != -ENOENT) - log_warning_errno(errno, "Failed to open /dev/tty0: %m"); - } else { + if (fd < 0) + /* Support systems without virtual console (ENOENT) gracefully */ + log_full_errno(fd == -ENOENT ? LOG_DEBUG : LOG_WARNING, fd, "Failed to open /dev/tty0, ignoring: %m"); + else { /* Enable that we get SIGWINCH on kbrequest */ if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) - log_warning_errno(errno, "Failed to enable kbrequest handling: %m"); + log_warning_errno(errno, "Failed to enable kbrequest handling, ignoring: %m"); } return 0; @@ -592,10 +593,21 @@ static int manager_setup_signals(Manager *m) { * notify processing can still figure out to which process/service a message belongs, before we reap the * process. Also, process this before handling cgroup notifications, so that we always collect child exit * status information before detecting that there's no process in a cgroup. */ - r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-6); + r = sd_event_source_set_priority(m->signal_event_source, EVENT_PRIORITY_SIGNALS); if (r < 0) return r; + /* Report to supervisor that we now process the above signals. We report this as level "2", to + * indicate that we support more than sysvinit's signals (of course, sysvinit never sent this + * message, but conceptually it makes sense to consider level "1" to be equivalent to sysvinit's + * signal handling). Also, by setting this to "2" people looking for this hopefully won't + * misunderstand this as a boolean concept. Signal level 2 shall refer to the signals PID 1 + * understands at the time of release of systemd v256, i.e. including basic SIGRTMIN+18 handling for + * memory pressure and stuff. When more signals are hooked up (or more SIGRTMIN+18 multiplex + * operations added, this level should be increased). */ + (void) sd_notify(/* unset_environment= */ false, + "X_SYSTEMD_SIGNALS_LEVEL=2"); + if (MANAGER_IS_SYSTEM(m)) return enable_special_signals(m); @@ -641,16 +653,13 @@ static char** sanitize_environment(char **l) { "TRIGGER_TIMER_REALTIME_USEC", "TRIGGER_UNIT", "WATCHDOG_PID", - "WATCHDOG_USEC", - NULL); + "WATCHDOG_USEC"); /* Let's order the environment alphabetically, just to make it pretty */ return strv_sort(l); } int manager_default_environment(Manager *m) { - int r; - assert(m); m->transient_environment = strv_free(m->transient_environment); @@ -661,21 +670,39 @@ int manager_default_environment(Manager *m) { * * The initial passed environment is untouched to keep /proc/self/environ valid; it is used * for tagging the init process inside containers. */ - m->transient_environment = strv_new("PATH=" DEFAULT_PATH); - if (!m->transient_environment) + char *path = strjoin("PATH=", default_PATH()); + if (!path) + return log_oom(); + + if (strv_consume(&m->transient_environment, path) < 0) return log_oom(); /* Import locale variables LC_*= from configuration */ (void) locale_setup(&m->transient_environment); } else { - /* The user manager passes its own environment along to its children, except for $PATH. */ + /* The user manager passes its own environment along to its children, except for $PATH and + * session envs. */ + m->transient_environment = strv_copy(environ); if (!m->transient_environment) return log_oom(); - r = strv_env_replace_strdup(&m->transient_environment, "PATH=" DEFAULT_USER_PATH); - if (r < 0) + char *path = strjoin("PATH=", default_user_PATH()); + if (!path) + return log_oom(); + + if (strv_env_replace_consume(&m->transient_environment, path) < 0) return log_oom(); + + /* Envvars set for our 'manager' class session are private and should not be propagated + * to children. Also it's likely that the graphical session will set these on their own. */ + strv_env_unset_many(m->transient_environment, + "XDG_SESSION_ID", + "XDG_SESSION_CLASS", + "XDG_SESSION_TYPE", + "XDG_SESSION_DESKTOP", + "XDG_SEAT", + "XDG_VTNR"); } sanitize_environment(m->transient_environment); @@ -689,18 +716,18 @@ static int manager_setup_prefix(Manager *m) { }; static const struct table_entry paths_system[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = { SD_PATH_SYSTEM_RUNTIME, NULL }, - [EXEC_DIRECTORY_STATE] = { SD_PATH_SYSTEM_STATE_PRIVATE, NULL }, - [EXEC_DIRECTORY_CACHE] = { SD_PATH_SYSTEM_STATE_CACHE, NULL }, - [EXEC_DIRECTORY_LOGS] = { SD_PATH_SYSTEM_STATE_LOGS, NULL }, + [EXEC_DIRECTORY_RUNTIME] = { SD_PATH_SYSTEM_RUNTIME, NULL }, + [EXEC_DIRECTORY_STATE] = { SD_PATH_SYSTEM_STATE_PRIVATE, NULL }, + [EXEC_DIRECTORY_CACHE] = { SD_PATH_SYSTEM_STATE_CACHE, NULL }, + [EXEC_DIRECTORY_LOGS] = { SD_PATH_SYSTEM_STATE_LOGS, NULL }, [EXEC_DIRECTORY_CONFIGURATION] = { SD_PATH_SYSTEM_CONFIGURATION, NULL }, }; static const struct table_entry paths_user[_EXEC_DIRECTORY_TYPE_MAX] = { - [EXEC_DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME, NULL }, - [EXEC_DIRECTORY_STATE] = { SD_PATH_USER_STATE_PRIVATE, NULL }, - [EXEC_DIRECTORY_CACHE] = { SD_PATH_USER_STATE_CACHE, NULL }, - [EXEC_DIRECTORY_LOGS] = { SD_PATH_USER_STATE_PRIVATE, "log" }, + [EXEC_DIRECTORY_RUNTIME] = { SD_PATH_USER_RUNTIME, NULL }, + [EXEC_DIRECTORY_STATE] = { SD_PATH_USER_STATE_PRIVATE, NULL }, + [EXEC_DIRECTORY_CACHE] = { SD_PATH_USER_STATE_CACHE, NULL }, + [EXEC_DIRECTORY_LOGS] = { SD_PATH_USER_STATE_PRIVATE, "log" }, [EXEC_DIRECTORY_CONFIGURATION] = { SD_PATH_USER_CONFIGURATION, NULL }, }; @@ -736,7 +763,7 @@ static int manager_setup_run_queue(Manager *m) { if (r < 0) return r; - r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE); + r = sd_event_source_set_priority(m->run_queue_event_source, EVENT_PRIORITY_RUN_QUEUE); if (r < 0) return r; @@ -759,7 +786,7 @@ static int manager_setup_sigchld_event_source(Manager *m) { if (r < 0) return r; - r = sd_event_source_set_priority(m->sigchld_event_source, SD_EVENT_PRIORITY_NORMAL-7); + r = sd_event_source_set_priority(m->sigchld_event_source, EVENT_PRIORITY_SIGCHLD); if (r < 0) return r; @@ -861,6 +888,7 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, *m = (Manager) { .runtime_scope = runtime_scope, .objective = _MANAGER_OBJECTIVE_INVALID, + .previous_objective = _MANAGER_OBJECTIVE_INVALID, .status_unit_format = STATUS_UNIT_FORMAT_DEFAULT, @@ -878,6 +906,7 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, .cgroups_agent_fd = -EBADF, .signal_fd = -EBADF, .user_lookup_fds = EBADF_PAIR, + .handoff_timestamp_fds = EBADF_PAIR, .private_listen_fd = -EBADF, .dev_autofs_fd = -EBADF, .cgroup_inotify_fd = -EBADF, @@ -992,8 +1021,8 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, return r; #if HAVE_LIBBPF - if (MANAGER_IS_SYSTEM(m) && lsm_bpf_supported(/* initialize = */ true)) { - r = lsm_bpf_setup(m); + if (MANAGER_IS_SYSTEM(m) && bpf_restrict_fs_supported(/* initialize = */ true)) { + r = bpf_restrict_fs_setup(m); if (r < 0) log_warning_errno(r, "Failed to setup LSM BPF, ignoring: %m"); } @@ -1013,42 +1042,19 @@ int manager_new(RuntimeScope runtime_scope, ManagerTestRunFlags test_run_flags, if (r < 0 && r != -EEXIST) return r; + } - m->executor_fd = open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH); - if (m->executor_fd < 0) - return log_emergency_errno(errno, - "Failed to open executor binary '%s': %m", - SYSTEMD_EXECUTOR_BINARY_PATH); - } else if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) { - _cleanup_free_ char *self_exe = NULL, *executor_path = NULL; - _cleanup_close_ int self_dir_fd = -EBADF; - int level = LOG_DEBUG; - - /* Prefer sd-executor from the same directory as the test, e.g.: when running unit tests from the - * build directory. Fallback to working directory and then the installation path. */ - r = readlink_and_make_absolute("/proc/self/exe", &self_exe); - if (r < 0) - return r; - - self_dir_fd = open_parent(self_exe, O_CLOEXEC|O_PATH|O_DIRECTORY, 0); - if (self_dir_fd < 0) - return self_dir_fd; - - m->executor_fd = RET_NERRNO(openat(self_dir_fd, "systemd-executor", O_CLOEXEC|O_PATH)); - if (m->executor_fd == -ENOENT) - m->executor_fd = RET_NERRNO(openat(AT_FDCWD, "systemd-executor", O_CLOEXEC|O_PATH)); - if (m->executor_fd == -ENOENT) { - m->executor_fd = RET_NERRNO(open(SYSTEMD_EXECUTOR_BINARY_PATH, O_CLOEXEC|O_PATH)); - level = LOG_WARNING; /* Tests should normally use local builds */ - } + if (!FLAGS_SET(test_run_flags, MANAGER_TEST_DONT_OPEN_EXECUTOR)) { + m->executor_fd = pin_callout_binary(SYSTEMD_EXECUTOR_BINARY_PATH); if (m->executor_fd < 0) - return m->executor_fd; + return log_debug_errno(m->executor_fd, "Failed to pin executor binary: %m"); + _cleanup_free_ char *executor_path = NULL; r = fd_get_path(m->executor_fd, &executor_path); if (r < 0) return r; - log_full(level, "Using systemd-executor binary from '%s'.", executor_path); + log_debug("Using systemd-executor binary from '%s'.", executor_path); } /* Note that we do not set up the notify fd here. We do that after deserialization, @@ -1113,7 +1119,7 @@ static int manager_setup_notify(Manager *m) { /* Process notification messages a bit earlier than SIGCHLD, so that we can still identify to which * service an exit message belongs. */ - r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-8); + r = sd_event_source_set_priority(m->notify_event_source, EVENT_PRIORITY_NOTIFY); if (r < 0) return log_error_errno(r, "Failed to set priority of notify event source: %m"); @@ -1187,7 +1193,7 @@ static int manager_setup_cgroups_agent(Manager *m) { /* Process cgroups notifications early. Note that when the agent notification is received * we'll just enqueue the unit in the cgroup empty queue, hence pick a high priority than * that. Also see handling of cgroup inotify for the unified cgroup stuff. */ - r = sd_event_source_set_priority(m->cgroups_agent_event_source, SD_EVENT_PRIORITY_NORMAL-9); + r = sd_event_source_set_priority(m->cgroups_agent_event_source, EVENT_PRIORITY_CGROUP_AGENT); if (r < 0) return log_error_errno(r, "Failed to set priority of cgroups agent event source: %m"); @@ -1236,13 +1242,13 @@ static int manager_setup_user_lookup_fd(Manager *m) { if (!m->user_lookup_event_source) { r = sd_event_add_io(m->event, &m->user_lookup_event_source, m->user_lookup_fds[0], EPOLLIN, manager_dispatch_user_lookup_fd, m); if (r < 0) - return log_error_errno(errno, "Failed to allocate user lookup event source: %m"); + return log_error_errno(r, "Failed to allocate user lookup event source: %m"); /* Process even earlier than the notify event source, so that we always know first about valid UID/GID * resolutions */ - r = sd_event_source_set_priority(m->user_lookup_event_source, SD_EVENT_PRIORITY_NORMAL-11); + r = sd_event_source_set_priority(m->user_lookup_event_source, EVENT_PRIORITY_USER_LOOKUP); if (r < 0) - return log_error_errno(errno, "Failed to set priority of user lookup event source: %m"); + return log_error_errno(r, "Failed to set priority of user lookup event source: %m"); (void) sd_event_source_set_description(m->user_lookup_event_source, "user-lookup"); } @@ -1250,6 +1256,49 @@ static int manager_setup_user_lookup_fd(Manager *m) { return 0; } +static int manager_setup_handoff_timestamp_fd(Manager *m) { + int r; + + assert(m); + + /* Set up the socket pair used for passing timestamps back when the executor processes we fork + * off invokes execve(), i.e. when we hand off control to our payload processes. */ + + if (m->handoff_timestamp_fds[0] < 0) { + m->handoff_timestamp_event_source = sd_event_source_disable_unref(m->handoff_timestamp_event_source); + safe_close_pair(m->handoff_timestamp_fds); + + if (socketpair(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, m->handoff_timestamp_fds) < 0) + return log_error_errno(errno, "Failed to allocate handoff timestamp socket: %m"); + + /* Make sure children never have to block */ + (void) fd_increase_rxbuf(m->handoff_timestamp_fds[0], NOTIFY_RCVBUF_SIZE); + + r = setsockopt_int(m->handoff_timestamp_fds[0], SOL_SOCKET, SO_PASSCRED, true); + if (r < 0) + return log_error_errno(r, "SO_PASSCRED failed: %m"); + + /* Mark the receiving socket as O_NONBLOCK (but leave sending side as-is) */ + r = fd_nonblock(m->handoff_timestamp_fds[0], true); + if (r < 0) + return log_error_errno(r, "Failed to make handoff timestamp socket O_NONBLOCK: %m"); + } + + if (!m->handoff_timestamp_event_source) { + r = sd_event_add_io(m->event, &m->handoff_timestamp_event_source, m->handoff_timestamp_fds[0], EPOLLIN, manager_dispatch_handoff_timestamp_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate handoff timestamp event source: %m"); + + r = sd_event_source_set_priority(m->handoff_timestamp_event_source, EVENT_PRIORITY_HANDOFF_TIMESTAMP); + if (r < 0) + return log_error_errno(r, "Failed to set priority of handoff timestamp event source: %m"); + + (void) sd_event_source_set_description(m->handoff_timestamp_event_source, "handoff-timestamp"); + } + + return 0; +} + static unsigned manager_dispatch_cleanup_queue(Manager *m) { Unit *u; unsigned n = 0; @@ -1664,12 +1713,14 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->jobs_in_progress_event_source); sd_event_source_unref(m->run_queue_event_source); sd_event_source_unref(m->user_lookup_event_source); + sd_event_source_unref(m->handoff_timestamp_event_source); sd_event_source_unref(m->memory_pressure_event_source); safe_close(m->signal_fd); safe_close(m->notify_fd); safe_close(m->cgroups_agent_fd); safe_close_pair(m->user_lookup_fds); + safe_close_pair(m->handoff_timestamp_fds); manager_close_ask_password(m); @@ -1679,7 +1730,7 @@ Manager* manager_free(Manager *m) { free(m->notify_socket); - lookup_paths_free(&m->lookup_paths); + lookup_paths_done(&m->lookup_paths); strv_free(m->transient_environment); strv_free(m->client_environment); @@ -1691,8 +1742,10 @@ Manager* manager_free(Manager *m) { unit_defaults_done(&m->defaults); - assert(hashmap_isempty(m->units_requiring_mounts_for)); - hashmap_free(m->units_requiring_mounts_for); + FOREACH_ARRAY(map, m->units_needing_mounts_for, _UNIT_MOUNT_DEPENDENCY_TYPE_MAX) { + assert(hashmap_isempty(*map)); + hashmap_free(*map); + } hashmap_free(m->uid_refs); hashmap_free(m->gid_refs); @@ -1708,7 +1761,7 @@ Manager* manager_free(Manager *m) { m->fw_ctx = fw_ctx_free(m->fw_ctx); #if BPF_FRAMEWORK - lsm_bpf_destroy(m->restrict_fs); + bpf_restrict_fs_destroy(m->restrict_fs); #endif safe_close(m->executor_fd); @@ -1802,7 +1855,7 @@ static void manager_distribute_fds(Manager *m, FDSet *fds) { HASHMAP_FOREACH(u, m->units) { - if (fdset_size(fds) <= 0) + if (fdset_isempty(fds)) break; if (!UNIT_VTABLE(u)->distribute_fds) @@ -1973,6 +2026,20 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo return log_error_errno(r, "Deserialization failed: %m"); } + if (m->previous_objective >= 0) { + if (IN_SET(m->previous_objective, MANAGER_REEXECUTE, MANAGER_SOFT_REBOOT, MANAGER_SWITCH_ROOT)) + log_debug("Launching as effect of a '%s' operation.", + manager_objective_to_string(m->previous_objective)); + else + log_warning("Got unexpected previous objective '%s', ignoring.", + manager_objective_to_string(m->previous_objective)); + } + + /* If we are in a new soft-reboot iteration bump the counter now before starting units, so + * that they can reliably read it. We get the previous objective from serialized state. */ + if (m->previous_objective == MANAGER_SOFT_REBOOT) + m->soft_reboots_count++; + /* Any fds left? Find some unit which wants them. This is useful to allow container managers to pass * some file descriptors to us pre-initialized. This enables socket-based activation of entire * containers. */ @@ -1994,6 +2061,11 @@ int manager_startup(Manager *m, FILE *serialization, FDSet *fds, const char *roo /* This shouldn't fail, except if things are really broken. */ return r; + r = manager_setup_handoff_timestamp_fd(m); + if (r < 0) + /* This shouldn't fail, except if things are really broken. */ + return r; + /* Connect to the bus if we are good for it */ manager_setup_bus(m); @@ -2203,8 +2275,8 @@ static int manager_dispatch_target_deps_queue(Manager *m) { if (n_targets < 0) return n_targets; - for (int i = 0; i < n_targets; i++) { - r = unit_add_default_target_dependency(u, targets[i]); + FOREACH_ARRAY(i, targets, n_targets) { + r = unit_add_default_target_dependency(u, *i); if (r < 0) return r; } @@ -2303,7 +2375,7 @@ int manager_load_unit_prepare( Unit *unit = manager_get_unit(m, name); if (unit) { - /* The time-based cache allows to start new units without daemon-reload, + /* The time-based cache allows new units to be started without daemon-reload, * but if they are already referenced (because of dependencies or ordering) * then we have to force a load of the fragment. As an optimization, check * first if anything in the usual paths was modified since the last time @@ -2403,7 +2475,7 @@ void manager_clear_jobs(Manager *m) { job_finish_and_invalidate(j, JOB_CANCELED, false, false); } -void manager_unwatch_pidref(Manager *m, PidRef *pid) { +void manager_unwatch_pidref(Manager *m, const PidRef *pid) { assert(m); for (;;) { @@ -2586,22 +2658,70 @@ static void manager_invoke_notify_message( UNIT_VTABLE(u)->notify_message(u, ucred, tags, fds); else if (DEBUG_LOGGING) { - _cleanup_free_ char *buf = NULL, *x = NULL, *y = NULL; + _cleanup_free_ char *joined = strv_join(tags, ", "); + char buf[CELLESCAPE_DEFAULT_LENGTH]; + + log_unit_debug(u, "Got notification message from unexpected unit type, ignoring: %s", + joined ? cellescape(buf, sizeof(buf), joined) : "(null)"); + } +} + +static int manager_get_units_for_pidref(Manager *m, const PidRef *pidref, Unit ***ret_units) { + /* Determine array of every unit that is interested in the specified process */ + + assert(m); + assert(pidref_is_set(pidref)); - buf = strv_join(tags, ", "); - if (buf) - x = ellipsize(buf, 20, 90); - if (x) - y = cescape(x); + Unit *u1, *u2, **array; + u1 = manager_get_unit_by_pidref_cgroup(m, pidref); + u2 = hashmap_get(m->watch_pids, pidref); + array = hashmap_get(m->watch_pids_more, pidref); + + size_t n = 0; + if (u1) + n++; + if (u2) + n++; + if (array) + for (size_t j = 0; array[j]; j++) + n++; + + assert(n <= INT_MAX); /* Make sure we can reasonably return the counter as "int" */ + + if (ret_units) { + _cleanup_free_ Unit **units = NULL; + + if (n > 0) { + units = new(Unit*, n + 1); + if (!units) + return -ENOMEM; + + /* We return a dense array, and put the "main" unit first, i.e. unit in whose cgroup + * the process currently is. Note that we do not bother with filtering duplicates + * here. */ + + size_t i = 0; + if (u1) + units[i++] = u1; + if (u2) + units[i++] = u2; + if (array) + for (size_t j = 0; array[j]; j++) + units[i++] = array[j]; + assert(i == n); + + units[i] = NULL; /* end array in an extra NULL */ + } - log_unit_debug(u, "Got notification message \"%s\", ignoring.", strnull(y)); + *ret_units = TAKE_PTR(units); } + + return (int) n; } static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - - _cleanup_fdset_free_ FDSet *fds = NULL; Manager *m = ASSERT_PTR(userdata); + _cleanup_fdset_free_ FDSet *fds = NULL; char buf[NOTIFY_BUFFER_MAX+1]; struct iovec iovec = { .iov_base = buf, @@ -2618,12 +2738,9 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t struct cmsghdr *cmsg; struct ucred *ucred = NULL; - _cleanup_free_ Unit **array_copy = NULL; _cleanup_strv_free_ char **tags = NULL; - Unit *u1, *u2, **array; int r, *fd_array = NULL; size_t n_fds = 0; - bool found = false; ssize_t n; assert(m->notify_fd == fd); @@ -2711,39 +2828,22 @@ static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t PidRef pidref = PIDREF_MAKE_FROM_PID(ucred->pid); /* Notify every unit that might be interested, which might be multiple. */ - u1 = manager_get_unit_by_pidref_cgroup(m, &pidref); - u2 = hashmap_get(m->watch_pids, &pidref); - array = hashmap_get(m->watch_pids_more, &pidref); - if (array) { - size_t k = 0; + _cleanup_free_ Unit **array = NULL; - while (array[k]) - k++; - - array_copy = newdup(Unit*, array, k+1); - if (!array_copy) - log_oom(); - } - /* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle - * duplicate units make sure we only invoke each unit's handler once. */ - if (u1) { - manager_invoke_notify_message(m, u1, ucred, tags, fds); - found = true; - } - if (u2) { - manager_invoke_notify_message(m, u2, ucred, tags, fds); - found = true; + int n_array = manager_get_units_for_pidref(m, &pidref, &array); + if (n_array < 0) { + log_warning_errno(n_array, "Failed to determine units for PID " PID_FMT ", ignoring: %m", ucred->pid); + return 0; } - if (array_copy) - for (size_t i = 0; array_copy[i]; i++) { - manager_invoke_notify_message(m, array_copy[i], ucred, tags, fds); - found = true; - } - - if (!found) - log_warning("Cannot find unit for notify message of PID "PID_FMT", ignoring.", ucred->pid); + if (n_array == 0) + log_debug("Cannot find unit for notify message of PID "PID_FMT", ignoring.", ucred->pid); + else + /* And now invoke the per-unit callbacks. Note that manager_invoke_notify_message() will handle + * duplicate units – making sure we only invoke each unit's handler once. */ + FOREACH_ARRAY(u, array, n_array) + manager_invoke_notify_message(m, *u, ucred, tags, fds); - if (fdset_size(fds) > 0) + if (!fdset_isempty(fds)) log_warning("Got extra auxiliary fds with notification message, closing them."); return 0; @@ -2792,10 +2892,7 @@ static int manager_dispatch_sigchld(sd_event_source *source, void *userdata) { goto turn_off; if (IN_SET(si.si_code, CLD_EXITED, CLD_KILLED, CLD_DUMPED)) { - _cleanup_free_ Unit **array_copy = NULL; _cleanup_free_ char *name = NULL; - Unit *u1, *u2, **array; - (void) pid_get_comm(si.si_pid, &name); log_debug("Child "PID_FMT" (%s) died (code=%s, status=%i/%s)", @@ -2813,41 +2910,27 @@ static int manager_dispatch_sigchld(sd_event_source *source, void *userdata) { * pidfd here any more even if we wanted (since the process just exited). */ PidRef pidref = PIDREF_MAKE_FROM_PID(si.si_pid); - /* And now figure out the unit this belongs to, it might be multiple... */ - u1 = manager_get_unit_by_pidref_cgroup(m, &pidref); - u2 = hashmap_get(m->watch_pids, &pidref); - array = hashmap_get(m->watch_pids_more, &pidref); - if (array) { - size_t n = 0; - - /* Count how many entries the array has */ - while (array[n]) - n++; - - /* Make a copy of the array so that we don't trip up on the array changing beneath us */ - array_copy = newdup(Unit*, array, n+1); - if (!array_copy) - log_oom(); - } - - /* Finally, execute them all. Note that u1, u2 and the array might contain duplicates, but - * that's fine, manager_invoke_sigchld_event() will ensure we only invoke the handlers once for - * each iteration. */ - if (u1) { - /* We check for oom condition, in case we got SIGCHLD before the oom notification. - * We only do this for the cgroup the PID belonged to. */ - (void) unit_check_oom(u1); + /* And now figure out the units this belongs to, there might be multiple... */ + _cleanup_free_ Unit **array = NULL; + int n_array = manager_get_units_for_pidref(m, &pidref, &array); + if (n_array < 0) + log_warning_errno(n_array, "Failed to get units for process " PID_FMT ", ignoring: %m", si.si_pid); + else if (n_array == 0) + log_debug("Got SIGCHLD for process " PID_FMT " we weren't interested in, ignoring.", si.si_pid); + else { + /* We check for an OOM condition, in case we got SIGCHLD before the OOM notification. + * We only do this for the cgroup the PID belonged to, which is the f */ + (void) unit_check_oom(array[0]); /* We check if systemd-oomd performed a kill so that we log and notify appropriately */ - (void) unit_check_oomd_kill(u1); + (void) unit_check_oomd_kill(array[0]); - manager_invoke_sigchld_event(m, u1, &si); + /* Finally, execute them all. Note that the array might contain duplicates, but that's fine, + * manager_invoke_sigchld_event() will ensure we only invoke the handlers once for each + * iteration. */ + FOREACH_ARRAY(u, array, n_array) + manager_invoke_sigchld_event(m, *u, &si); } - if (u2) - manager_invoke_sigchld_event(m, u2, &si); - if (array_copy) - for (size_t i = 0; array_copy[i]; i++) - manager_invoke_sigchld_event(m, array_copy[i], &si); } /* And now, we actually reap the zombie. */ @@ -2878,8 +2961,8 @@ static void manager_start_special(Manager *m, const char *name, JobMode mode) { log_info("Activating special unit %s...", s); - sd_notifyf(false, - "STATUS=Activating special unit %s...", s); + (void) sd_notifyf(/* unset_environment= */ false, + "STATUS=Activating special unit %s...", s); m->status_ready = false; } @@ -2986,7 +3069,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t r = manager_get_dump_string(m, /* patterns= */ NULL, &dump); if (r < 0) { - log_warning_errno(errno, "Failed to acquire manager dump: %m"); + log_warning_errno(r, "Failed to acquire manager dump: %m"); break; } @@ -3008,9 +3091,9 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t const char *target; JobMode mode; } target_table[] = { - [0] = { SPECIAL_DEFAULT_TARGET, JOB_ISOLATE }, - [1] = { SPECIAL_RESCUE_TARGET, JOB_ISOLATE }, - [2] = { SPECIAL_EMERGENCY_TARGET, JOB_ISOLATE }, + [0] = { SPECIAL_DEFAULT_TARGET, JOB_ISOLATE }, + [1] = { SPECIAL_RESCUE_TARGET, JOB_ISOLATE }, + [2] = { SPECIAL_EMERGENCY_TARGET, JOB_ISOLATE }, [3] = { SPECIAL_HALT_TARGET, JOB_REPLACE_IRREVERSIBLY }, [4] = { SPECIAL_POWEROFF_TARGET, JOB_REPLACE_IRREVERSIBLY }, [5] = { SPECIAL_REBOOT_TARGET, JOB_REPLACE_IRREVERSIBLY }, @@ -3077,7 +3160,7 @@ static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t r = manager_get_dump_jobs_string(m, /* patterns= */ NULL, " ", &dump_jobs); if (r < 0) { - log_warning_errno(errno, "Failed to acquire manager jobs dump: %m"); + log_warning_errno(r, "Failed to acquire manager jobs dump: %m"); break; } @@ -3371,16 +3454,18 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { const char *msg; int audit_fd, r; + assert(m); + assert(u); + if (!MANAGER_IS_SYSTEM(m)) return; - audit_fd = get_audit_fd(); - if (audit_fd < 0) + /* Don't generate audit events if the service was already started and we're just deserializing */ + if (MANAGER_IS_RELOADING(m)) return; - /* Don't generate audit events if the service was already - * started and we're just deserializing */ - if (MANAGER_IS_RELOADING(m)) + audit_fd = get_audit_fd(); + if (audit_fd < 0) return; r = unit_name_to_prefix_and_instance(u->id, &p); @@ -3399,21 +3484,22 @@ void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { log_warning_errno(errno, "Failed to send audit message, ignoring: %m"); } #endif - } void manager_send_unit_plymouth(Manager *m, Unit *u) { _cleanup_free_ char *message = NULL; int c, r; - /* Don't generate plymouth events if the service was already - * started and we're just deserializing */ - if (MANAGER_IS_RELOADING(m)) - return; + assert(m); + assert(u); if (!MANAGER_IS_SYSTEM(m)) return; + /* Don't generate plymouth events if the service was already started and we're just deserializing */ + if (MANAGER_IS_RELOADING(m)) + return; + if (detect_container() > 0) return; @@ -3431,6 +3517,27 @@ void manager_send_unit_plymouth(Manager *m, Unit *u) { "Failed to communicate with plymouth: %m"); } +void manager_send_unit_supervisor(Manager *m, Unit *u, bool active) { + assert(m); + assert(u); + + /* Notify a "supervisor" process about our progress, i.e. a container manager, hypervisor, or + * surrounding service manager. */ + + if (MANAGER_IS_RELOADING(m)) + return; + + if (!UNIT_VTABLE(u)->notify_supervisor) + return; + + if (in_initrd()) /* Only send these once we left the initrd */ + return; + + (void) sd_notifyf(/* unset_environment= */ false, + active ? "X_SYSTEMD_UNIT_ACTIVE=%s" : "X_SYSTEMD_UNIT_INACTIVE=%s", + u->id); +} + usec_t manager_get_watchdog(Manager *m, WatchdogType t) { assert(m); @@ -3566,7 +3673,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); + lookup_paths_done(&m->lookup_paths); exec_shared_runtime_vacuum(m); dynamic_user_vacuum(m, false); m->uid_refs = hashmap_free(m->uid_refs); @@ -3601,6 +3708,7 @@ int manager_reload(Manager *m) { (void) manager_setup_notify(m); (void) manager_setup_cgroups_agent(m); (void) manager_setup_user_lookup_fd(m); + (void) manager_setup_handoff_timestamp_fd(m); /* Third, fire things up! */ manager_coldplug(m); @@ -3645,8 +3753,6 @@ bool manager_unit_inactive_or_pending(Manager *m, const char *name) { } static void log_taint_string(Manager *m) { - _cleanup_free_ char *taint = NULL; - assert(m); if (MANAGER_IS_USER(m) || m->taint_logged) @@ -3654,7 +3760,7 @@ static void log_taint_string(Manager *m) { m->taint_logged = true; /* only check for taint once */ - taint = manager_taint_string(m); + _cleanup_free_ char *taint = taint_string(); if (isempty(taint)) return; @@ -3670,7 +3776,19 @@ static void manager_notify_finished(Manager *m) { if (MANAGER_IS_TEST_RUN(m)) return; - if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) { + if (MANAGER_IS_SYSTEM(m) && m->soft_reboots_count > 0) { + /* The soft-reboot case, where we only report data for the last reboot */ + firmware_usec = loader_usec = initrd_usec = kernel_usec = 0; + total_usec = userspace_usec = usec_sub_unsigned(m->timestamps[MANAGER_TIMESTAMP_FINISH].monotonic, + m->timestamps[MANAGER_TIMESTAMP_SHUTDOWN_START].monotonic); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_STARTUP_FINISHED_STR, + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Soft-reboot finished in %s, counter is now at %u.", + FORMAT_TIMESPAN(total_usec, USEC_PER_MSEC), + m->soft_reboots_count)); + } else if (MANAGER_IS_SYSTEM(m) && detect_container() <= 0) { char buf[FORMAT_TIMESPAN_MAX + STRLEN(" (firmware) + ") + FORMAT_TIMESPAN_MAX + STRLEN(" (loader) + ")] = {}; char *p = buf; @@ -3740,7 +3858,7 @@ static void manager_notify_finished(Manager *m) { log_taint_string(m); } -static void user_manager_send_ready(Manager *m) { +static void manager_send_ready_user_scope(Manager *m) { int r; assert(m); @@ -3749,7 +3867,7 @@ static void user_manager_send_ready(Manager *m) { if (!MANAGER_IS_USER(m) || m->ready_sent) return; - r = sd_notify(false, + r = sd_notify(/* unset_environment= */ false, "READY=1\n" "STATUS=Reached " SPECIAL_BASIC_TARGET "."); if (r < 0) @@ -3759,14 +3877,19 @@ static void user_manager_send_ready(Manager *m) { m->status_ready = false; } -static void manager_send_ready(Manager *m) { +static void manager_send_ready_system_scope(Manager *m) { int r; + assert(m); + + if (!MANAGER_IS_SYSTEM(m)) + return; + + /* Skip the notification if nothing changed. */ if (m->ready_sent && m->status_ready) - /* Skip the notification if nothing changed. */ return; - r = sd_notify(false, + r = sd_notify(/* unset_environment= */ false, "READY=1\n" "STATUS=Ready."); if (r < 0) @@ -3790,7 +3913,7 @@ static void manager_check_basic_target(Manager *m) { return; /* For user managers, send out READY=1 as soon as we reach basic.target */ - user_manager_send_ready(m); + manager_send_ready_user_scope(m); /* Log the taint string as soon as we reach basic.target */ log_taint_string(m); @@ -3808,7 +3931,7 @@ void manager_check_finished(Manager *m) { manager_check_basic_target(m); - if (hashmap_size(m->jobs) > 0) { + if (!hashmap_isempty(m->jobs)) { if (m->jobs_in_progress_event_source) /* Ignore any failure, this is only for feedback */ (void) sd_event_source_set_time(m->jobs_in_progress_event_source, @@ -3821,7 +3944,7 @@ void manager_check_finished(Manager *m) { if (hashmap_buckets(m->jobs) > hashmap_size(m->units) / 10) m->jobs = hashmap_free(m->jobs); - manager_send_ready(m); + manager_send_ready_system_scope(m); /* Notify Type=idle units that we are done now */ manager_close_idle_pipe(m); @@ -3851,9 +3974,7 @@ void manager_send_reloading(Manager *m) { assert(m); /* Let whoever invoked us know that we are now reloading */ - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "MONOTONIC_USEC=" USEC_FMT "\n", now(CLOCK_MONOTONIC)); + (void) notify_reloading_full(/* status = */ NULL); /* And ensure that we'll send READY=1 again as soon as we are ready again */ m->ready_sent = false; @@ -3878,8 +3999,8 @@ static int manager_run_environment_generators(Manager *m) { _cleanup_strv_free_ char **paths = NULL; void* args[] = { [STDOUT_GENERATE] = &tmp, - [STDOUT_COLLECT] = &tmp, - [STDOUT_CONSUME] = &m->transient_environment, + [STDOUT_COLLECT] = &tmp, + [STDOUT_CONSUME] = &m->transient_environment, }; int r; @@ -4040,7 +4161,7 @@ static int manager_run_generators(Manager *m) { /* On some systems /tmp/ doesn't exist, and on some other systems we cannot create it at all. Avoid * trying to mount a private tmpfs on it as there's no one size fits all. */ - if (is_dir("/tmp", /* follow= */ false) > 0) + if (is_dir("/tmp", /* follow= */ false) > 0 && !MANAGER_IS_TEST_RUN(m)) flags |= FORK_PRIVATE_TMP; r = safe_fork("(sd-gens)", flags, NULL); @@ -4373,7 +4494,7 @@ void manager_override_show_status(Manager *m, ShowStatus mode, const char *reaso set_show_status_marker(show_status_on(mode)); } -const char *manager_get_confirm_spawn(Manager *m) { +const char* manager_get_confirm_spawn(Manager *m) { static int last_errno = 0; struct stat st; int r; @@ -4478,14 +4599,15 @@ void manager_status_printf(Manager *m, StatusType type, const char *status, cons va_end(ap); } -Set* manager_get_units_requiring_mounts_for(Manager *m, const char *path) { +Set* manager_get_units_needing_mounts_for(Manager *m, const char *path, UnitMountDependencyType t) { assert(m); assert(path); + assert(t >= 0 && t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX); if (path_equal(path, "/")) path = ""; - return hashmap_get(m->units_requiring_mounts_for, path); + return hashmap_get(m->units_needing_mounts_for[t], path); } int manager_update_failed_units(Manager *m, Unit *u, bool failed) { @@ -4542,7 +4664,7 @@ ManagerState manager_state(Manager *m) { } /* Are there any failed units? If so, we are in degraded mode */ - if (set_size(m->failed_units) > 0) + if (!set_isempty(m->failed_units)) return MANAGER_DEGRADED; return MANAGER_RUNNING; @@ -4701,20 +4823,19 @@ static void manager_vacuum(Manager *m) { exec_shared_runtime_vacuum(m); } -int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { +static int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { struct buffer { uid_t uid; gid_t gid; char unit_name[UNIT_NAME_MAX+1]; } _packed_ buffer; - Manager *m = userdata; + Manager *m = ASSERT_PTR(userdata); ssize_t l; size_t n; Unit *u; - assert_se(source); - assert_se(m); + assert(source); /* Invoked whenever a child process succeeded resolving its user/group to use and sent us the * resulting UID/GID in a datagram. We parse the datagram here and pass it off to the unit, so that @@ -4763,76 +4884,71 @@ int manager_dispatch_user_lookup_fd(sd_event_source *source, int fd, uint32_t re return 0; } -static int short_uid_range(const char *path) { - _cleanup_(uid_range_freep) UidRange *p = NULL; - int r; - - assert(path); - - /* Taint systemd if we the UID range assigned to this environment doesn't at least cover 0…65534, - * i.e. from root to nobody. */ - - r = uid_range_load_userns(&p, path); - if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) - return false; - if (r < 0) - return log_debug_errno(r, "Failed to load %s: %m", path); - - return !uid_range_covers(p, 0, 65535); -} - -char* manager_taint_string(const Manager *m) { - /* Returns a "taint string", e.g. "local-hwclock:var-run-bad". Only things that are detected at - * runtime should be tagged here. For stuff that is known during compilation, emit a warning in the - * configuration phase. */ - - assert(m); - - const char* stage[12] = {}; - size_t n = 0; - - _cleanup_free_ char *usrbin = NULL; - if (readlink_malloc("/bin", &usrbin) < 0 || !PATH_IN_SET(usrbin, "usr/bin", "/usr/bin")) - stage[n++] = "unmerged-usr"; +static int manager_dispatch_handoff_timestamp_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + usec_t ts[2] = {}; + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control; + struct msghdr msghdr = { + .msg_iov = &IOVEC_MAKE(ts, sizeof(ts)), + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + ssize_t n; - if (access("/proc/cgroups", F_OK) < 0) - stage[n++] = "cgroups-missing"; + assert(source); - if (cg_all_unified() == 0) - stage[n++] = "cgroupsv1"; + n = recvmsg_safe(m->handoff_timestamp_fds[0], &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC|MSG_TRUNC); + if (ERRNO_IS_NEG_TRANSIENT(n)) + return 0; /* Spurious wakeup, try again */ + if (n == -EXFULL) { + log_warning("Got message with truncated control, ignoring."); + return 0; + } + if (n < 0) + return log_error_errno(n, "Failed to receive handoff timestamp message: %m"); - if (clock_is_localtime(NULL) > 0) - stage[n++] = "local-hwclock"; + if (msghdr.msg_flags & MSG_TRUNC) { + log_warning("Got truncated handoff timestamp message, ignoring."); + return 0; + } + if (n != sizeof(ts)) { + log_warning("Got handoff timestamp message of unexpected size %zi (expected %zu), ignoring.", n, sizeof(ts)); + return 0; + } - if (os_release_support_ended(NULL, /* quiet= */ true, NULL) > 0) - stage[n++] = "support-ended"; + struct ucred *ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred); + if (!ucred || !pid_is_valid(ucred->pid)) { + log_warning("Received notify message without valid credentials. Ignoring."); + return 0; + } - _cleanup_free_ char *destination = NULL; - if (readlink_malloc("/var/run", &destination) < 0 || - !PATH_IN_SET(destination, "../run", "/run")) - stage[n++] = "var-run-bad"; + log_debug("Got handoff timestamp event for PID " PID_FMT ".", ucred->pid); - _cleanup_free_ char *overflowuid = NULL, *overflowgid = NULL; - if (read_one_line_file("/proc/sys/kernel/overflowuid", &overflowuid) >= 0 && - !streq(overflowuid, "65534")) - stage[n++] = "overflowuid-not-65534"; - if (read_one_line_file("/proc/sys/kernel/overflowgid", &overflowgid) >= 0 && - !streq(overflowgid, "65534")) - stage[n++] = "overflowgid-not-65534"; + _cleanup_free_ Unit **units = NULL; + int n_units = manager_get_units_for_pidref(m, &PIDREF_MAKE_FROM_PID(ucred->pid), &units); + if (n_units < 0) { + log_warning_errno(n_units, "Unable to determine units for PID " PID_FMT ", ignoring: %m", ucred->pid); + return 0; + } + if (n_units == 0) { + log_debug("Got handoff timestamp for process " PID_FMT " we are not interested in, ignoring.", ucred->pid); + return 0; + } - struct utsname uts; - assert_se(uname(&uts) >= 0); - if (strverscmp_improved(uts.release, KERNEL_BASELINE_VERSION) < 0) - stage[n++] = "old-kernel"; + dual_timestamp dt = { + .realtime = ts[0], + .monotonic = ts[1], + }; - if (short_uid_range("/proc/self/uid_map") > 0) - stage[n++] = "short-uid-range"; - if (short_uid_range("/proc/self/gid_map") > 0) - stage[n++] = "short-gid-range"; + FOREACH_ARRAY(u, units, n_units) { + if (!UNIT_VTABLE(*u)->notify_handoff_timestamp) + continue; - assert(n < ELEMENTSOF(stage) - 1); /* One extra for NULL terminator */ + UNIT_VTABLE(*u)->notify_handoff_timestamp(*u, ucred, &dt); + } - return strv_join((char**) stage, ":"); + return 0; } void manager_ref_console(Manager *m) { @@ -4988,14 +5104,13 @@ LogTarget manager_get_executor_log_target(Manager *m) { assert(m); /* If journald is not available tell sd-executor to go to kmsg, as it might be starting journald */ + if (!MANAGER_IS_TEST_RUN(m) && !manager_journal_is_running(m)) + return LOG_TARGET_KMSG; - if (manager_journal_is_running(m)) - return log_get_target(); - - return LOG_TARGET_KMSG; + return log_get_target(); } -static const char *const manager_state_table[_MANAGER_STATE_MAX] = { +static const char* const manager_state_table[_MANAGER_STATE_MAX] = { [MANAGER_INITIALIZING] = "initializing", [MANAGER_STARTING] = "starting", [MANAGER_RUNNING] = "running", @@ -5006,7 +5121,22 @@ static const char *const manager_state_table[_MANAGER_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState); -static const char *const manager_timestamp_table[_MANAGER_TIMESTAMP_MAX] = { +static const char* const manager_objective_table[_MANAGER_OBJECTIVE_MAX] = { + [MANAGER_OK] = "ok", + [MANAGER_EXIT] = "exit", + [MANAGER_RELOAD] = "reload", + [MANAGER_REEXECUTE] = "reexecute", + [MANAGER_REBOOT] = "reboot", + [MANAGER_SOFT_REBOOT] = "soft-reboot", + [MANAGER_POWEROFF] = "poweroff", + [MANAGER_HALT] = "halt", + [MANAGER_KEXEC] = "kexec", + [MANAGER_SWITCH_ROOT] = "switch-root", +}; + +DEFINE_STRING_TABLE_LOOKUP(manager_objective, ManagerObjective); + +static const char* const manager_timestamp_table[_MANAGER_TIMESTAMP_MAX] = { [MANAGER_TIMESTAMP_FIRMWARE] = "firmware", [MANAGER_TIMESTAMP_LOADER] = "loader", [MANAGER_TIMESTAMP_KERNEL] = "kernel", @@ -5026,6 +5156,7 @@ static const char *const manager_timestamp_table[_MANAGER_TIMESTAMP_MAX] = { [MANAGER_TIMESTAMP_INITRD_GENERATORS_FINISH] = "initrd-generators-finish", [MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_START] = "initrd-units-load-start", [MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_FINISH] = "initrd-units-load-finish", + [MANAGER_TIMESTAMP_SHUTDOWN_START] = "shutdown-start", }; DEFINE_STRING_TABLE_LOOKUP(manager_timestamp, ManagerTimestamp); diff --git a/src/core/manager.h b/src/core/manager.h index d96eb7b..0641b27 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -120,6 +120,9 @@ typedef enum ManagerTimestamp { MANAGER_TIMESTAMP_INITRD_GENERATORS_FINISH, MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_START, MANAGER_TIMESTAMP_INITRD_UNITS_LOAD_FINISH, + + MANAGER_TIMESTAMP_SHUTDOWN_START, + _MANAGER_TIMESTAMP_MAX, _MANAGER_TIMESTAMP_INVALID = -EINVAL, } ManagerTimestamp; @@ -137,6 +140,7 @@ typedef enum WatchdogType { #include "path-lookup.h" #include "show-status.h" #include "unit-name.h" +#include "unit.h" typedef enum ManagerTestRunFlags { MANAGER_TEST_NORMAL = 0, /* run normally */ @@ -282,6 +286,9 @@ struct Manager { int user_lookup_fds[2]; sd_event_source *user_lookup_event_source; + int handoff_timestamp_fds[2]; + sd_event_source *handoff_timestamp_event_source; + RuntimeScope runtime_scope; LookupPaths lookup_paths; @@ -375,6 +382,8 @@ struct Manager { bool etc_localtime_accessible; ManagerObjective objective; + /* Objective as it was before serialization, mostly to detect soft-reboots */ + ManagerObjective previous_objective; /* Flags */ bool dispatching_load_queue; @@ -438,10 +447,9 @@ struct Manager { /* This is true before and after switching root. */ bool switching_root; - /* This maps all possible path prefixes to the units needing - * them. It's a hashmap with a path string as key and a Set as - * value where Unit objects are contained. */ - Hashmap *units_requiring_mounts_for; + /* These map all possible path prefixes to the units needing them. They are hashmaps with a path + * string as key, and a Set as value where Unit objects are contained. */ + Hashmap *units_needing_mounts_for[_UNIT_MOUNT_DEPENDENCY_TYPE_MAX]; /* Used for processing polkit authorization responses */ Hashmap *polkit_registry; @@ -488,8 +496,8 @@ struct Manager { /* Reference to RestrictFileSystems= BPF program */ struct restrict_fs_bpf *restrict_fs; - /* Allow users to configure a rate limit for Reload() operations */ - RateLimit reload_ratelimit; + /* Allow users to configure a rate limit for Reload()/Reexecute() operations */ + RateLimit reload_reexec_ratelimit; /* Dump*() are slow, so always rate limit them to 10 per 10 minutes */ RateLimit dump_ratelimit; @@ -501,6 +509,8 @@ struct Manager { /* Pin the systemd-executor binary, so that it never changes until re-exec, ensuring we don't have * serialization/deserialization compatibility issues during upgrades. */ int executor_fd; + + unsigned soft_reboots_count; }; static inline usec_t manager_default_timeout_abort_usec(Manager *m) { @@ -550,7 +560,7 @@ int manager_propagate_reload(Manager *m, Unit *unit, JobMode mode, sd_bus_error void manager_clear_jobs(Manager *m); -void manager_unwatch_pidref(Manager *m, PidRef *pid); +void manager_unwatch_pidref(Manager *m, const PidRef *pid); unsigned manager_dispatch_load_queue(Manager *m); @@ -575,6 +585,7 @@ void manager_reset_failed(Manager *m); void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); void manager_send_unit_plymouth(Manager *m, Unit *u); +void manager_send_unit_supervisor(Manager *m, Unit *u, bool active); bool manager_unit_inactive_or_pending(Manager *m, const char *name); @@ -596,7 +607,7 @@ double manager_get_progress(Manager *m); void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) _printf_(4,5); -Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); +Set* manager_get_units_needing_mounts_for(Manager *m, const char *path, UnitMountDependencyType t); ManagerState manager_state(Manager *m); @@ -608,8 +619,6 @@ int manager_ref_uid(Manager *m, uid_t uid, bool clean_ipc); void manager_unref_gid(Manager *m, gid_t gid, bool destroy_now); int manager_ref_gid(Manager *m, gid_t gid, bool clean_ipc); -char* manager_taint_string(const Manager *m); - void manager_ref_console(Manager *m); void manager_unref_console(Manager *m); @@ -619,13 +628,16 @@ void manager_restore_original_log_level(Manager *m); void manager_override_log_target(Manager *m, LogTarget target); void manager_restore_original_log_target(Manager *m); -const char *manager_state_to_string(ManagerState m) _const_; +const char* manager_get_confirm_spawn(Manager *m); +void manager_disable_confirm_spawn(void); + +const char* manager_state_to_string(ManagerState m) _const_; ManagerState manager_state_from_string(const char *s) _pure_; -const char *manager_get_confirm_spawn(Manager *m); -void manager_disable_confirm_spawn(void); +const char* manager_objective_to_string(ManagerObjective m) _const_; +ManagerObjective manager_objective_from_string(const char *s) _pure_; -const char *manager_timestamp_to_string(ManagerTimestamp m) _const_; +const char* manager_timestamp_to_string(ManagerTimestamp m) _const_; ManagerTimestamp manager_timestamp_from_string(const char *s) _pure_; ManagerTimestamp manager_timestamp_initrd_mangle(ManagerTimestamp s); @@ -644,3 +656,26 @@ OOMPolicy oom_policy_from_string(const char *s) _pure_; void unit_defaults_init(UnitDefaults *defaults, RuntimeScope scope); void unit_defaults_done(UnitDefaults *defaults); + +enum { + /* most important … */ + EVENT_PRIORITY_USER_LOOKUP = SD_EVENT_PRIORITY_NORMAL-11, + EVENT_PRIORITY_MOUNT_TABLE = SD_EVENT_PRIORITY_NORMAL-10, + EVENT_PRIORITY_SWAP_TABLE = SD_EVENT_PRIORITY_NORMAL-10, + EVENT_PRIORITY_CGROUP_AGENT = SD_EVENT_PRIORITY_NORMAL-9, /* cgroupv1 */ + EVENT_PRIORITY_CGROUP_INOTIFY = SD_EVENT_PRIORITY_NORMAL-9, /* cgroupv2 */ + EVENT_PRIORITY_CGROUP_OOM = SD_EVENT_PRIORITY_NORMAL-8, + EVENT_PRIORITY_HANDOFF_TIMESTAMP = SD_EVENT_PRIORITY_NORMAL-7, + EVENT_PRIORITY_EXEC_FD = SD_EVENT_PRIORITY_NORMAL-6, + EVENT_PRIORITY_NOTIFY = SD_EVENT_PRIORITY_NORMAL-5, + EVENT_PRIORITY_SIGCHLD = SD_EVENT_PRIORITY_NORMAL-4, + EVENT_PRIORITY_SIGNALS = SD_EVENT_PRIORITY_NORMAL-3, + EVENT_PRIORITY_CGROUP_EMPTY = SD_EVENT_PRIORITY_NORMAL-2, + EVENT_PRIORITY_TIME_CHANGE = SD_EVENT_PRIORITY_NORMAL-1, + EVENT_PRIORITY_TIME_ZONE = SD_EVENT_PRIORITY_NORMAL-1, + EVENT_PRIORITY_IPC = SD_EVENT_PRIORITY_NORMAL, + EVENT_PRIORITY_REWATCH_PIDS = SD_EVENT_PRIORITY_IDLE, + EVENT_PRIORITY_SERVICE_WATCHDOG = SD_EVENT_PRIORITY_IDLE+1, + EVENT_PRIORITY_RUN_QUEUE = SD_EVENT_PRIORITY_IDLE+2, + /* … to least important */ +}; diff --git a/src/core/meson.build b/src/core/meson.build index 7701d3d..7a2012a 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -7,7 +7,8 @@ libcore_sources = files( 'bpf-devices.c', 'bpf-firewall.c', 'bpf-foreign.c', - 'bpf-lsm.c', + 'bpf-restrict-fs.c', + 'bpf-restrict-ifaces.c', 'bpf-socket-bind.c', 'cgroup.c', 'core-varlink.c', @@ -51,7 +52,6 @@ libcore_sources = files( 'mount.c', 'namespace.c', 'path.c', - 'restrict-ifaces.c', 'scope.c', 'selinux-access.c', 'selinux-setup.c', @@ -61,6 +61,7 @@ libcore_sources = files( 'smack-setup.c', 'socket.c', 'swap.c', + 'taint.c', 'target.c', 'timer.c', 'transaction.c', @@ -125,7 +126,7 @@ libcore = shared_library( libaudit, libblkid, libdl, - libkmod, + libkmod_cflags, libm, libmount, libpam, diff --git a/src/core/mount.c b/src/core/mount.c index 3c4971c..ebafcaf 100644 --- a/src/core/mount.c +++ b/src/core/mount.c @@ -39,18 +39,18 @@ #define RETRY_UMOUNT_MAX 32 static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = UNIT_INACTIVE, - [MOUNT_MOUNTING] = UNIT_ACTIVATING, - [MOUNT_MOUNTING_DONE] = UNIT_ACTIVATING, - [MOUNT_MOUNTED] = UNIT_ACTIVE, - [MOUNT_REMOUNTING] = UNIT_RELOADING, - [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_DEAD] = UNIT_INACTIVE, + [MOUNT_MOUNTING] = UNIT_ACTIVATING, + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVATING, + [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_RELOADING, + [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, - [MOUNT_FAILED] = UNIT_FAILED, - [MOUNT_CLEANING] = UNIT_MAINTENANCE, + [MOUNT_FAILED] = UNIT_FAILED, + [MOUNT_CLEANING] = UNIT_MAINTENANCE, }; static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); @@ -171,24 +171,9 @@ static bool mount_propagate_stop(Mount *m) { * otherwise let's not bother. */ } -static bool mount_needs_quota(const MountParameters *p) { - assert(p); - - if (p->fstype && !fstype_needs_quota(p->fstype)) - return false; - - if (mount_is_bind(p)) - return false; - - return fstab_test_option(p->options, - "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); -} - static void mount_init(Unit *u) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); - assert(m); - assert(u); assert(u->load_state == UNIT_STUB); m->timeout_usec = u->manager->defaults.timeout_start_usec; @@ -218,12 +203,7 @@ static int mount_arm_timer(Mount *m, bool relative, usec_t usec) { static void mount_unwatch_control_pid(Mount *m) { assert(m); - - if (!pidref_is_set(&m->control_pid)) - return; - - unit_unwatch_pidref(UNIT(m), &m->control_pid); - pidref_done(&m->control_pid); + unit_unwatch_pidref_done(UNIT(m), &m->control_pid); } static void mount_parameters_done(MountParameters *p) { @@ -235,9 +215,7 @@ static void mount_parameters_done(MountParameters *p) { } static void mount_done(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); + Mount *m = ASSERT_PTR(MOUNT(u)); m->where = mfree(m->where); @@ -245,6 +223,7 @@ static void mount_done(Unit *u) { mount_parameters_done(&m->parameters_fragment); m->exec_runtime = exec_runtime_free(m->exec_runtime); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); m->control_command = NULL; @@ -262,6 +241,8 @@ static int update_parameters_proc_self_mountinfo( MountParameters *p; int r, q, w; + assert(m); + p = &m->parameters_proc_self_mountinfo; r = free_and_strdup(&p->what, what); @@ -281,8 +262,6 @@ static int update_parameters_proc_self_mountinfo( static int mount_add_mount_dependencies(Mount *m) { MountParameters *pm; - Unit *other; - Set *s; int r; assert(m); @@ -296,7 +275,7 @@ static int mount_add_mount_dependencies(Mount *m) { if (r < 0) return r; - r = unit_require_mounts_for(UNIT(m), parent, UNIT_DEPENDENCY_IMPLICIT); + r = unit_add_mounts_for(UNIT(m), parent, UNIT_DEPENDENCY_IMPLICIT, UNIT_MOUNT_REQUIRES); if (r < 0) return r; } @@ -308,30 +287,43 @@ static int mount_add_mount_dependencies(Mount *m) { path_is_absolute(pm->what) && (mount_is_bind(pm) || mount_is_loop(pm) || !mount_is_network(pm))) { - r = unit_require_mounts_for(UNIT(m), pm->what, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(UNIT(m), pm->what, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); if (r < 0) return r; } /* Adds in dependencies to other units that use this path or paths further down in the hierarchy */ - s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); - SET_FOREACH(other, s) { - - if (other->load_state != UNIT_LOADED) - continue; - - if (other == UNIT(m)) - continue; - - r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true, UNIT_DEPENDENCY_PATH); - if (r < 0) - return r; - - if (UNIT(m)->fragment_path) { - /* If we have fragment configuration, then make this dependency required */ - r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true, UNIT_DEPENDENCY_PATH); + for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; ++t) { + Unit *other; + Set *s = manager_get_units_needing_mounts_for(UNIT(m)->manager, m->where, t); + + SET_FOREACH(other, s) { + if (other->load_state != UNIT_LOADED) + continue; + + if (other == UNIT(m)) + continue; + + r = unit_add_dependency( + other, + UNIT_AFTER, + UNIT(m), + /* add_reference= */ true, + UNIT_DEPENDENCY_PATH); if (r < 0) return r; + + if (UNIT(m)->fragment_path) { + /* If we have fragment configuration, then make this dependency required/wanted */ + r = unit_add_dependency( + other, + unit_mount_dependency_type_to_dependency_type(t), + UNIT(m), + /* add_reference= */ true, + UNIT_DEPENDENCY_PATH); + if (r < 0) + return r; + } } } @@ -413,39 +405,9 @@ static int mount_add_device_dependencies(Mount *m) { return 0; } -static int mount_add_quota_dependencies(Mount *m) { - MountParameters *p; - int r; - - assert(m); - - if (!MANAGER_IS_SYSTEM(UNIT(m)->manager)) - return 0; - - p = get_mount_parameters_fragment(m); - if (!p) - return 0; - - if (!mount_needs_quota(p)) - return 0; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, - /* add_reference= */ true, UNIT_DEPENDENCY_FILE); - if (r < 0) - return r; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, - /* add_reference= */true, UNIT_DEPENDENCY_FILE); - if (r < 0) - return r; - - return 0; -} - static bool mount_is_extrinsic(Unit *u) { + Mount *m = ASSERT_PTR(MOUNT(u)); MountParameters *p; - Mount *m = MOUNT(u); - assert(m); /* Returns true for all units that are "magic" and should be excluded from the usual * start-up and shutdown dependencies. We call them "extrinsic" here, as they are generally @@ -501,10 +463,7 @@ static int mount_add_default_ordering_dependencies(Mount *m, MountParameters *p, after = SPECIAL_LOCAL_FS_PRE_TARGET; before = SPECIAL_INITRD_USR_FS_TARGET; - } else if (mount_is_credentials(m)) - after = before = NULL; - - else if (mount_is_network(p)) { + } else if (mount_is_network(p)) { after = SPECIAL_REMOTE_FS_PRE_TARGET; before = SPECIAL_REMOTE_FS_TARGET; @@ -645,6 +604,9 @@ static int mount_add_non_exec_dependencies(Mount *m) { if (!m->where) return 0; + if (mount_is_credentials(m)) + UNIT(m)->default_dependencies = false; + /* Adds in all dependencies directly responsible for ordering the mount, as opposed to dependencies * resulting from the ExecContext and such. */ @@ -656,10 +618,6 @@ static int mount_add_non_exec_dependencies(Mount *m) { if (r < 0) return r; - r = mount_add_quota_dependencies(m); - if (r < 0) - return r; - r = mount_add_default_dependencies(m); if (r < 0) return r; @@ -668,11 +626,9 @@ static int mount_add_non_exec_dependencies(Mount *m) { } static int mount_add_extras(Mount *m) { - Unit *u = UNIT(m); + Unit *u = UNIT(ASSERT_PTR(m)); int r; - assert(m); - /* Note: this call might be called after we already have been loaded once (and even when it has already been * activated), in case data from /proc/self/mountinfo has changed. This means all code here needs to be ready * to run with an already set up unit. */ @@ -717,7 +673,7 @@ static int mount_add_extras(Mount *m) { } static void mount_load_root_mount(Unit *u) { - assert(u); + Mount *m = ASSERT_PTR(MOUNT(u)); if (!unit_has_name(u, SPECIAL_ROOT_MOUNT)) return; @@ -726,37 +682,35 @@ static void mount_load_root_mount(Unit *u) { u->default_dependencies = false; /* The stdio/kmsg bridge socket is on /, in order to avoid a dep loop, don't use kmsg logging for -.mount */ - MOUNT(u)->exec_context.std_output = EXEC_OUTPUT_NULL; - MOUNT(u)->exec_context.std_input = EXEC_INPUT_NULL; + m->exec_context.std_output = EXEC_OUTPUT_NULL; + m->exec_context.std_input = EXEC_INPUT_NULL; if (!u->description) u->description = strdup("Root Mount"); } static int mount_load(Unit *u) { - Mount *m = MOUNT(u); - int r, q = 0; + Mount *m = ASSERT_PTR(MOUNT(u)); + int r; - assert(m); - assert(u); assert(u->load_state == UNIT_STUB); mount_load_root_mount(u); - bool fragment_optional = m->from_proc_self_mountinfo || u->perpetual; - r = unit_load_fragment_and_dropin(u, !fragment_optional); + bool from_kernel = m->from_proc_self_mountinfo || u->perpetual; + + r = unit_load_fragment_and_dropin(u, /* fragment_required = */ !from_kernel); /* Add in some extras. Note we do this in all cases (even if we failed to load the unit) when announced by the * kernel, because we need some things to be set up no matter what when the kernel establishes a mount and thus * we need to update the state in our unit to track it. After all, consider that we don't allow changing the * 'slice' field for a unit once it is active. */ - if (u->load_state == UNIT_LOADED || m->from_proc_self_mountinfo || u->perpetual) - q = mount_add_extras(m); + if (u->load_state == UNIT_LOADED || from_kernel) + RET_GATHER(r, mount_add_extras(m)); if (r < 0) return r; - if (q < 0) - return q; + if (u->load_state != UNIT_LOADED) return 0; @@ -765,6 +719,7 @@ static int mount_load(Unit *u) { static void mount_set_state(Mount *m, MountState state) { MountState old_state; + assert(m); if (m->state != state) @@ -787,10 +742,9 @@ static void mount_set_state(Mount *m, MountState state) { } static int mount_coldplug(Unit *u) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); int r; - assert(m); assert(m->state == MOUNT_DEAD); if (m->deserialized_state == m->state) @@ -809,17 +763,17 @@ static int mount_coldplug(Unit *u) { return r; } - if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED)) + if (!IN_SET(m->deserialized_state, MOUNT_DEAD, MOUNT_FAILED)) { (void) unit_setup_exec_runtime(u); + (void) unit_setup_cgroup_runtime(u); + } mount_set_state(m, m->deserialized_state); return 0; } static void mount_catchup(Unit *u) { - Mount *m = MOUNT(ASSERT_PTR(u)); - - assert(m); + Mount *m = ASSERT_PTR(MOUNT(u)); /* Adjust the deserialized state. See comments in mount_process_proc_self_mountinfo(). */ if (m->from_proc_self_mountinfo) @@ -854,12 +808,15 @@ static void mount_catchup(Unit *u) { } static void mount_dump(Unit *u, FILE *f, const char *prefix) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); MountParameters *p; + const char *prefix2; - assert(m); assert(f); + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + p = get_mount_parameters(m); fprintf(f, @@ -904,14 +861,22 @@ static void mount_dump(Unit *u, FILE *f, const char *prefix) { exec_context_dump(&m->exec_context, f, prefix); kill_context_dump(&m->kill_context, f, prefix); cgroup_context_dump(UNIT(m), f, prefix); + + for (MountExecCommand c = 0; c < _MOUNT_EXEC_COMMAND_MAX; c++) { + if (!m->exec_command[c].argv) + continue; + + fprintf(f, "%s%s %s:\n", + prefix, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), mount_exec_command_to_string(c)); + + exec_command_dump(m->exec_command + c, f, prefix2); + } } static int mount_spawn(Mount *m, ExecCommand *c, PidRef *ret_pid) { - _cleanup_(exec_params_shallow_clear) ExecParameters exec_params = EXEC_PARAMETERS_INIT( EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - pid_t pid; int r; assert(m); @@ -936,11 +901,7 @@ static int mount_spawn(Mount *m, ExecCommand *c, PidRef *ret_pid) { &exec_params, m->exec_runtime, &m->cgroup_context, - &pid); - if (r < 0) - return r; - - r = pidref_set_pid(&pidref, pid); + &pidref); if (r < 0) return r; @@ -1025,13 +986,7 @@ static void mount_enter_signal(Mount *m, MountState state, MountResult f) { if (m->result == MOUNT_SUCCESS) m->result = f; - r = unit_kill_context( - UNIT(m), - &m->kill_context, - state_to_kill_operation(state), - /* main_pid= */ NULL, - &m->control_pid, - /* main_pid_alien= */ false); + r = unit_kill_context(UNIT(m), state_to_kill_operation(state)); if (r < 0) { log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); goto fail; @@ -1166,9 +1121,9 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete } static void mount_enter_mounting(Mount *m) { - int r; MountParameters *p; bool source_is_dir = true; + int r; assert(m); @@ -1192,6 +1147,34 @@ static void mount_enter_mounting(Mount *m) { if (r < 0 && r != -EEXIST) log_unit_warning_errno(UNIT(m), r, "Failed to create mount point '%s', ignoring: %m", m->where); + /* If we are asked to create an OverlayFS, create the upper/work directories if they are missing */ + if (p && streq_ptr(p->fstype, "overlay")) { + _cleanup_strv_free_ char **dirs = NULL; + + r = fstab_filter_options( + p->options, + "upperdir\0workdir\0", + /* ret_namefound= */ NULL, + /* ret_value= */ NULL, + &dirs, + /* ret_filtered= */ NULL); + if (r < 0) + log_unit_warning_errno( + UNIT(m), + r, + "Failed to determine upper directory for OverlayFS, ignoring: %m"); + else + STRV_FOREACH(d, dirs) { + r = mkdir_p_label(*d, m->directory_mode); + if (r < 0 && r != -EEXIST) + log_unit_warning_errno( + UNIT(m), + r, + "Failed to create overlay directory '%s', ignoring: %m", + *d); + } + } + if (source_is_dir) unit_warn_if_dir_nonempty(UNIT(m), m->where); unit_warn_leftover_processes(UNIT(m), unit_log_leftover_process_start); @@ -1249,8 +1232,8 @@ static void mount_set_reload_result(Mount *m, MountResult result) { } static void mount_enter_remounting(Mount *m) { - int r; MountParameters *p; + int r; assert(m); @@ -1312,15 +1295,15 @@ static void mount_cycle_clear(Mount *m) { m->result = MOUNT_SUCCESS; m->reload_result = MOUNT_SUCCESS; exec_command_reset_status_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); - UNIT(m)->reset_accounting = true; + + if (m->cgroup_runtime) + m->cgroup_runtime->reset_accounting = true; } static int mount_start(Unit *u) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); int r; - assert(m); - /* We cannot fulfill this request right now, try again later * please! */ if (IN_SET(m->state, @@ -1347,9 +1330,7 @@ static int mount_start(Unit *u) { } static int mount_stop(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); + Mount *m = ASSERT_PTR(MOUNT(u)); /* When we directly call umount() for a path, then the state of the corresponding mount unit may be * outdated. Let's re-read mountinfo now and update the state. */ @@ -1401,9 +1382,8 @@ static int mount_stop(Unit *u) { } static int mount_reload(Unit *u) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); - assert(m); assert(m->state == MOUNT_MOUNTED); mount_enter_remounting(m); @@ -1412,9 +1392,8 @@ static int mount_reload(Unit *u) { } static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); - assert(m); assert(f); assert(fds); @@ -1431,11 +1410,9 @@ static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { } static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); int r; - assert(m); - assert(u); assert(key); assert(value); assert(fds); @@ -1495,21 +1472,19 @@ static int mount_deserialize_item(Unit *u, const char *key, const char *value, F } static UnitActiveState mount_active_state(Unit *u) { - assert(u); + Mount *m = ASSERT_PTR(MOUNT(u)); - return state_translation_table[MOUNT(u)->state]; + return state_translation_table[m->state]; } static const char *mount_sub_state_to_string(Unit *u) { - assert(u); + Mount *m = ASSERT_PTR(MOUNT(u)); - return mount_state_to_string(MOUNT(u)->state); + return mount_state_to_string(m->state); } static bool mount_may_gc(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); + Mount *m = ASSERT_PTR(MOUNT(u)); if (m->from_proc_self_mountinfo) return false; @@ -1518,10 +1493,9 @@ static bool mount_may_gc(Unit *u) { } static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); MountResult f; - assert(m); assert(pid >= 0); if (pid != m->control_pid.pid) @@ -1653,9 +1627,8 @@ static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { } static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Mount *m = MOUNT(userdata); + Mount *m = ASSERT_PTR(MOUNT(userdata)); - assert(m); assert(m->timer_event_source == source); switch (m->state) { @@ -1738,6 +1711,7 @@ static int mount_setup_new_unit( Unit **ret) { _cleanup_(unit_freep) Unit *u = NULL; + Mount *mnt; int r; assert(m); @@ -1749,24 +1723,26 @@ static int mount_setup_new_unit( if (r < 0) return r; + mnt = ASSERT_PTR(MOUNT(u)); + r = free_and_strdup(&u->source_path, "/proc/self/mountinfo"); if (r < 0) return r; - r = free_and_strdup(&MOUNT(u)->where, where); + r = free_and_strdup(&mnt->where, where); if (r < 0) return r; - r = update_parameters_proc_self_mountinfo(MOUNT(u), what, options, fstype); + r = update_parameters_proc_self_mountinfo(mnt, what, options, fstype); if (r < 0) return r; /* This unit was generated because /proc/self/mountinfo reported it. Remember this, so that by the * time we load the unit file for it (and thus add in extra deps right after) we know what source to * attributes the deps to. */ - MOUNT(u)->from_proc_self_mountinfo = true; + mnt->from_proc_self_mountinfo = true; - r = mount_add_non_exec_dependencies(MOUNT(u)); + r = mount_add_non_exec_dependencies(mnt); if (r < 0) return r; @@ -1787,14 +1763,16 @@ static int mount_setup_existing_unit( const char *fstype, MountProcFlags *ret_flags) { + Mount *m = ASSERT_PTR(MOUNT(u)); int r; assert(u); + assert(where); assert(ret_flags); - if (!MOUNT(u)->where) { - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) + if (!m->where) { + m->where = strdup(where); + if (!m->where) return -ENOMEM; } @@ -1802,10 +1780,9 @@ static int mount_setup_existing_unit( * for the current unit. Note that the flags field is reset on each iteration of reading * /proc/self/mountinfo, hence we know for sure anything already set here is from the current * iteration and thus worthy of taking into account. */ - MountProcFlags flags = - MOUNT(u)->proc_flags | MOUNT_PROC_IS_MOUNTED; + MountProcFlags flags = m->proc_flags | MOUNT_PROC_IS_MOUNTED; - r = update_parameters_proc_self_mountinfo(MOUNT(u), what, options, fstype); + r = update_parameters_proc_self_mountinfo(m, what, options, fstype); if (r < 0) return r; if (r > 0) @@ -1818,12 +1795,12 @@ static int mount_setup_existing_unit( * from the serialized state), and need to catch up. Since we know that the MOUNT_MOUNTING state is * reached when we wait for the mount to appear we hence can assume that if we are in it, we are * actually seeing it established for the first time. */ - if (!MOUNT(u)->from_proc_self_mountinfo || MOUNT(u)->state == MOUNT_MOUNTING) + if (!m->from_proc_self_mountinfo || m->state == MOUNT_MOUNTING) flags |= MOUNT_PROC_JUST_MOUNTED; - MOUNT(u)->from_proc_self_mountinfo = true; + m->from_proc_self_mountinfo = true; - if (IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR)) { + if (UNIT_IS_LOAD_ERROR(u->load_state)) { /* The unit was previously not found or otherwise not loaded. Now that the unit shows up in * /proc/self/mountinfo we should reconsider it this, hence set it to UNIT_LOADED. */ u->load_state = UNIT_LOADED; @@ -1835,7 +1812,7 @@ static int mount_setup_existing_unit( if (FLAGS_SET(flags, MOUNT_PROC_JUST_CHANGED)) { /* If things changed, then make sure that all deps are regenerated. Let's * first remove all automatic deps, and then add in the new ones. */ - r = mount_add_non_exec_dependencies(MOUNT(u)); + r = mount_add_non_exec_dependencies(m); if (r < 0) return r; } @@ -1950,14 +1927,27 @@ static void mount_shutdown(Manager *m) { m->mount_monitor = NULL; } +static void mount_handoff_timestamp( + Unit *u, + const struct ucred *ucred, + const dual_timestamp *ts) { + + Mount *m = ASSERT_PTR(MOUNT(u)); + + assert(ucred); + assert(ts); + + if (m->control_pid.pid == ucred->pid && m->control_command) { + exec_status_handoff(&m->control_command->exec_status, ucred, ts); + unit_add_to_dbus_queue(u); + } +} + static int mount_get_timeout(Unit *u, usec_t *timeout) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); usec_t t; int r; - assert(m); - assert(u); - if (!m->timer_event_source) return 0; @@ -2063,7 +2053,7 @@ static void mount_enumerate(Manager *m) { goto fail; } - r = sd_event_source_set_priority(m->mount_event_source, SD_EVENT_PRIORITY_NORMAL-10); + r = sd_event_source_set_priority(m->mount_event_source, EVENT_PRIORITY_MOUNT_TABLE); if (r < 0) { log_error_errno(r, "Failed to adjust mount watch priority: %m"); goto fail; @@ -2330,19 +2320,15 @@ fail: } static int mount_can_clean(Unit *u, ExecCleanMask *ret) { - Mount *m = MOUNT(u); - - assert(m); + Mount *m = ASSERT_PTR(MOUNT(u)); return exec_context_get_clean_mask(&m->exec_context, ret); } static int mount_can_start(Unit *u) { - Mount *m = MOUNT(u); + Mount *m = ASSERT_PTR(MOUNT(u)); int r; - assert(m); - r = unit_test_start_limit(u); if (r < 0) { mount_enter_dead(m, MOUNT_FAILURE_START_LIMIT_HIT, /* flush_result = */ false); @@ -2440,6 +2426,7 @@ 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), + .cgroup_runtime_offset = offsetof(Mount, cgroup_runtime), .sections = "Unit\0" @@ -2482,6 +2469,8 @@ const UnitVTable mount_vtable = { .reset_failed = mount_reset_failed, + .notify_handoff_timestamp = mount_handoff_timestamp, + .control_pid = mount_control_pid, .bus_set_property = bus_mount_set_property, diff --git a/src/core/mount.h b/src/core/mount.h index 6712c16..a029dc8 100644 --- a/src/core/mount.h +++ b/src/core/mount.h @@ -79,6 +79,7 @@ struct Mount { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + CGroupRuntime *cgroup_runtime; MountState state, deserialized_state; diff --git a/src/core/namespace.c b/src/core/namespace.c index 88681aa..6c0dc94 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -47,6 +47,7 @@ #include "tmpfile-util.h" #include "umask-util.h" #include "user-util.h" +#include "vpick.h" #define DEV_MOUNT_OPTIONS (MS_NOSUID|MS_STRICTATIME|MS_NOEXEC) @@ -500,9 +501,24 @@ static int append_extensions( /* First, prepare a mount for each image, but these won't be visible to the unit, instead * they will be mounted in our propagate directory, and used as a source for the overlay. */ for (size_t i = 0; i < n; i++) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; _cleanup_free_ char *mount_point = NULL; const MountImage *m = mount_images + i; + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + m->source, + &pick_filter_image_raw, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) + return r; + if (!result.path) + return log_debug_errno( + SYNTHETIC_ERRNO(ENOENT), + "No matching entry in .v/ directory %s found.", + m->source); + if (asprintf(&mount_point, "%s/%zu", extension_dir, i) < 0) return -ENOMEM; @@ -524,7 +540,7 @@ static int append_extensions( .path_malloc = TAKE_PTR(mount_point), .image_options_const = m->mount_options, .ignore = m->ignore_enoent, - .source_const = m->source, + .source_malloc = TAKE_PTR(result.path), .mode = MOUNT_EXTENSION_IMAGE, .has_prefix = true, }; @@ -534,7 +550,8 @@ static int append_extensions( * Bind mount them in the same location as the ExtensionImages, so that we * can check that they are valid trees (extension-release.d). */ STRV_FOREACH(extension_directory, extension_directories) { - _cleanup_free_ char *mount_point = NULL, *source = NULL; + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + _cleanup_free_ char *mount_point = NULL; const char *e = *extension_directory; bool ignore_enoent = false; @@ -551,9 +568,19 @@ static int append_extensions( if (startswith(e, "+")) e++; - source = strdup(e); - if (!source) - return -ENOMEM; + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + e, + &pick_filter_image_dir, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) + return r; + if (!result.path) + return log_debug_errno( + SYNTHETIC_ERRNO(ENOENT), + "No matching entry in .v/ directory %s found.", + e); for (size_t j = 0; hierarchies && hierarchies[j]; ++j) { char *prefixed_hierarchy = path_join(mount_point, hierarchies[j]); @@ -571,7 +598,7 @@ static int append_extensions( *me = (MountEntry) { .path_malloc = TAKE_PTR(mount_point), - .source_malloc = TAKE_PTR(source), + .source_malloc = TAKE_PTR(result.path), .mode = MOUNT_EXTENSION_DIRECTORY, .ignore = ignore_enoent, .has_prefix = true, @@ -626,8 +653,7 @@ static int append_tmpfs_mounts(MountList *ml, const TemporaryFileSystem *tmpfs, return log_debug_errno(r, "Failed to parse mount option '%s': %m", str); ro = flags & MS_RDONLY; - if (ro) - flags ^= MS_RDONLY; + flags &= ~MS_RDONLY; MountEntry *me = mount_list_extend(ml); if (!me) @@ -876,42 +902,41 @@ static void drop_outside_root(MountList *ml, const char *root_directory) { ml->n_mounts = t - ml->mounts; } -static int clone_device_node( - const char *d, - const char *temporary_mount, - bool *make_devnode) { - +static int clone_device_node(const char *node, const char *temporary_mount, bool *make_devnode) { _cleanup_free_ char *sl = NULL; - const char *dn, *bn, *t; + const char *dn, *bn; struct stat st; int r; - if (stat(d, &st) < 0) { + assert(node); + assert(path_is_absolute(node)); + assert(temporary_mount); + assert(make_devnode); + + if (stat(node, &st) < 0) { if (errno == ENOENT) { - log_debug_errno(errno, "Device node '%s' to clone does not exist, ignoring.", d); + log_debug_errno(errno, "Device node '%s' to clone does not exist.", node); return -ENXIO; } - return log_debug_errno(errno, "Failed to stat() device node '%s' to clone, ignoring: %m", d); + return log_debug_errno(errno, "Failed to stat() device node '%s' to clone: %m", node); } - if (!S_ISBLK(st.st_mode) && - !S_ISCHR(st.st_mode)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Device node '%s' to clone is not a device node, ignoring.", - d); + r = stat_verify_device_node(&st); + if (r < 0) + return log_debug_errno(r, "Cannot clone device node '%s': %m", node); - dn = strjoina(temporary_mount, d); + dn = strjoina(temporary_mount, node); /* First, try to create device node properly */ if (*make_devnode) { - mac_selinux_create_file_prepare(d, st.st_mode); + mac_selinux_create_file_prepare(node, st.st_mode); r = mknod(dn, st.st_mode, st.st_rdev); mac_selinux_create_file_clear(); if (r >= 0) goto add_symlink; if (errno != EPERM) - return log_debug_errno(errno, "mknod failed for %s: %m", d); + return log_debug_errno(errno, "Failed to mknod '%s': %m", node); /* This didn't work, let's not try this again for the next iterations. */ *make_devnode = false; @@ -921,17 +946,17 @@ static int clone_device_node( * Do not prepare device-node SELinux label (see issue 13762) */ r = mknod(dn, S_IFREG, 0); if (r < 0 && errno != EEXIST) - return log_debug_errno(errno, "mknod() fallback failed for '%s': %m", d); + return log_debug_errno(errno, "Failed to mknod dummy device node for '%s': %m", node); /* Fallback to bind-mounting: The assumption here is that all used device nodes carry standard * properties. Specifically, the devices nodes we bind-mount should either be owned by root:root or * root:tty (e.g. /dev/tty, /dev/ptmx) and should not carry ACLs. */ - r = mount_nofollow_verbose(LOG_DEBUG, d, dn, NULL, MS_BIND, NULL); + r = mount_nofollow_verbose(LOG_DEBUG, node, dn, NULL, MS_BIND, NULL); if (r < 0) return r; add_symlink: - bn = path_startswith(d, "/dev/"); + bn = path_startswith(node, "/dev/"); if (!bn) return 0; @@ -944,14 +969,27 @@ add_symlink: (void) mkdir_parents(sl, 0755); - t = strjoina("../", bn); + const char *t = strjoina("../", bn); if (symlink(t, sl) < 0) log_debug_errno(errno, "Failed to symlink '%s' to '%s', ignoring: %m", t, sl); return 0; } -static char *settle_runtime_dir(RuntimeScope scope) { +static int bind_mount_device_dir(const char *temporary_mount, const char *dir) { + const char *t; + + assert(temporary_mount); + assert(dir); + assert(path_is_absolute(dir)); + + t = strjoina(temporary_mount, dir); + + (void) mkdir(t, 0755); + return mount_nofollow_verbose(LOG_DEBUG, dir, t, NULL, MS_BIND, NULL); +} + +static char* settle_runtime_dir(RuntimeScope scope) { char *runtime_dir; if (scope != RUNTIME_SCOPE_USER) @@ -992,8 +1030,8 @@ static int mount_private_dev(MountEntry *m, RuntimeScope scope) { "/dev/urandom\0" "/dev/tty\0"; - _cleanup_free_ char *temporary_mount = NULL; - const char *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL; + _cleanup_(rmdir_and_freep) char *temporary_mount = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *dev = NULL; bool can_mknod = true; int r; @@ -1003,67 +1041,56 @@ static int mount_private_dev(MountEntry *m, RuntimeScope scope) { if (r < 0) return r; - dev = strjoina(temporary_mount, "/dev"); + dev = path_join(temporary_mount, "dev"); + if (!dev) + return -ENOMEM; + (void) mkdir(dev, 0755); r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", dev, "tmpfs", DEV_MOUNT_OPTIONS, "mode=0755" TMPFS_LIMITS_PRIVATE_DEV); if (r < 0) - goto fail; + return r; r = label_fix_full(AT_FDCWD, dev, "/dev", 0); - if (r < 0) { - log_debug_errno(r, "Failed to fix label of '%s' as /dev: %m", dev); - goto fail; - } + if (r < 0) + return log_debug_errno(r, "Failed to fix label of '%s' as /dev/: %m", dev); - devpts = strjoina(temporary_mount, "/dev/pts"); - (void) mkdir(devpts, 0755); - r = mount_nofollow_verbose(LOG_DEBUG, "/dev/pts", devpts, NULL, MS_BIND, NULL); + r = bind_mount_device_dir(temporary_mount, "/dev/pts"); if (r < 0) - goto fail; + return r; /* /dev/ptmx can either be a device node or a symlink to /dev/pts/ptmx. * When /dev/ptmx a device node, /dev/pts/ptmx has 000 permissions making it inaccessible. * Thus, in that case make a clone. * In nspawn and other containers it will be a symlink, in that case make it a symlink. */ r = is_symlink("/dev/ptmx"); - if (r < 0) { - log_debug_errno(r, "Failed to detect whether /dev/ptmx is a symlink or not: %m"); - goto fail; - } else if (r > 0) { - devptmx = strjoina(temporary_mount, "/dev/ptmx"); - if (symlink("pts/ptmx", devptmx) < 0) { - r = log_debug_errno(errno, "Failed to create a symlink '%s' to pts/ptmx: %m", devptmx); - goto fail; - } + if (r < 0) + return log_debug_errno(r, "Failed to detect whether /dev/ptmx is a symlink or not: %m"); + if (r > 0) { + const char *devptmx = strjoina(temporary_mount, "/dev/ptmx"); + if (symlink("pts/ptmx", devptmx) < 0) + return log_debug_errno(errno, "Failed to create symlink '%s' to pts/ptmx: %m", devptmx); } else { r = clone_device_node("/dev/ptmx", temporary_mount, &can_mknod); if (r < 0) - goto fail; + return r; } - devshm = strjoina(temporary_mount, "/dev/shm"); - (void) mkdir(devshm, 0755); - r = mount_nofollow_verbose(LOG_DEBUG, "/dev/shm", devshm, NULL, MS_BIND, NULL); + r = bind_mount_device_dir(temporary_mount, "/dev/shm"); if (r < 0) - goto fail; - - devmqueue = strjoina(temporary_mount, "/dev/mqueue"); - (void) mkdir(devmqueue, 0755); - (void) mount_nofollow_verbose(LOG_DEBUG, "/dev/mqueue", devmqueue, NULL, MS_BIND, NULL); + return r; - devhugepages = strjoina(temporary_mount, "/dev/hugepages"); - (void) mkdir(devhugepages, 0755); - (void) mount_nofollow_verbose(LOG_DEBUG, "/dev/hugepages", devhugepages, NULL, MS_BIND, NULL); + FOREACH_STRING(d, "/dev/mqueue", "/dev/hugepages") + (void) bind_mount_device_dir(temporary_mount, d); - devlog = strjoina(temporary_mount, "/dev/log"); + const char *devlog = strjoina(temporary_mount, "/dev/log"); if (symlink("/run/systemd/journal/dev-log", devlog) < 0) - log_debug_errno(errno, "Failed to create a symlink '%s' to /run/systemd/journal/dev-log, ignoring: %m", devlog); + log_debug_errno(errno, "Failed to create symlink '%s' to /run/systemd/journal/dev-log, ignoring: %m", devlog); NULSTR_FOREACH(d, devnodes) { r = clone_device_node(d, temporary_mount, &can_mknod); /* ENXIO means the *source* is not a device file, skip creation in that case */ if (r < 0 && r != -ENXIO) - goto fail; + return r; } r = dev_setup(temporary_mount, UID_INVALID, GID_INVALID); @@ -1081,31 +1108,10 @@ static int mount_private_dev(MountEntry *m, RuntimeScope scope) { r = mount_nofollow_verbose(LOG_DEBUG, dev, mount_entry_path(m), NULL, MS_MOVE, NULL); if (r < 0) - goto fail; - - (void) rmdir(dev); - (void) rmdir(temporary_mount); + return r; + dev = rmdir_and_free(dev); /* Mount is successfully moved, do not umount() */ return 1; - -fail: - if (devpts) - (void) umount_verbose(LOG_DEBUG, devpts, UMOUNT_NOFOLLOW); - - if (devshm) - (void) umount_verbose(LOG_DEBUG, devshm, UMOUNT_NOFOLLOW); - - if (devhugepages) - (void) umount_verbose(LOG_DEBUG, devhugepages, UMOUNT_NOFOLLOW); - - if (devmqueue) - (void) umount_verbose(LOG_DEBUG, devmqueue, UMOUNT_NOFOLLOW); - - (void) umount_verbose(LOG_DEBUG, dev, UMOUNT_NOFOLLOW); - (void) rmdir(dev); - (void) rmdir(temporary_mount); - - return r; } static int mount_bind_dev(const MountEntry *m) { @@ -1118,7 +1124,7 @@ static int mount_bind_dev(const MountEntry *m) { (void) mkdir_p_label(mount_entry_path(m), 0755); - r = path_is_mount_point(mount_entry_path(m), NULL, 0); + r = path_is_mount_point(mount_entry_path(m)); if (r < 0) return log_debug_errno(r, "Unable to determine whether /dev is already mounted: %m"); if (r > 0) /* make this a NOP if /dev is already a mount point */ @@ -1138,7 +1144,7 @@ static int mount_bind_sysfs(const MountEntry *m) { (void) mkdir_p_label(mount_entry_path(m), 0755); - r = path_is_mount_point(mount_entry_path(m), NULL, 0); + r = path_is_mount_point(mount_entry_path(m)); if (r < 0) return log_debug_errno(r, "Unable to determine whether /sys is already mounted: %m"); if (r > 0) /* make this a NOP if /sys is already a mount point */ @@ -1185,7 +1191,7 @@ static int mount_private_apivfs( /* When we do not have enough privileges to mount a new instance, fall back to use an * existing mount. */ - r = path_is_mount_point(entry_path, /* root = */ NULL, /* flags = */ 0); + r = path_is_mount_point(entry_path); if (r < 0) return log_debug_errno(r, "Unable to determine whether '%s' is already mounted: %m", entry_path); if (r > 0) @@ -1300,7 +1306,7 @@ static int mount_run(const MountEntry *m) { assert(m); - r = path_is_mount_point(mount_entry_path(m), NULL, 0); + r = path_is_mount_point(mount_entry_path(m)); if (r < 0 && r != -ENOENT) return log_debug_errno(r, "Unable to determine whether /run is already mounted: %m"); if (r > 0) /* make this a NOP if /run is already a mount point */ @@ -1354,7 +1360,7 @@ static int mount_image( if (r < 0) return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory)); 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)); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s'.", empty_to_root(root_directory)); } r = verity_dissect_and_mount( @@ -1448,6 +1454,8 @@ static int follow_symlink( _cleanup_free_ char *target = NULL; int r; + assert(m); + /* Let's chase symlinks, but only one step at a time. That's because depending where the symlink points we * might need to change the order in which we mount stuff. Hence: let's normalize piecemeal, and do one step at * a time by specifying CHASE_STEP. This function returns 0 if we resolved one step, and > 0 if we reached the @@ -1469,7 +1477,7 @@ static int follow_symlink( mount_entry_consume_prefix(m, TAKE_PTR(target)); - m->n_followed ++; + m->n_followed++; return 0; } @@ -1524,7 +1532,7 @@ static int apply_one_mount( r = mode_to_inaccessible_node(runtime_dir, target.st_mode, &inaccessible); if (r < 0) return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), - "File type not supported for inaccessible mounts. Note that symlinks are not allowed"); + "File type not supported for inaccessible mounts. Note that symlinks are not allowed."); what = inaccessible; break; } @@ -1534,7 +1542,7 @@ static int apply_one_mount( case MOUNT_READ_WRITE_IMPLICIT: case MOUNT_EXEC: case MOUNT_NOEXEC: - r = path_is_mount_point(mount_entry_path(m), root_directory, 0); + r = path_is_mount_point_full(mount_entry_path(m), root_directory, /* flags = */ 0); if (r == -ENOENT && m->ignore) return 0; if (r < 0) @@ -1575,7 +1583,7 @@ static int apply_one_mount( if (r < 0) return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory)); 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)); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'ID' field not found or empty in 'os-release' data of OS tree '%s'.", empty_to_root(root_directory)); r = load_extension_release_pairs(mount_entry_source(m), class, extension_name, /* relax_extension_release_check= */ false, &extension_release); if (r == -ENOENT && m->ignore) @@ -1588,13 +1596,13 @@ static int apply_one_mount( host_os_release_id, host_os_release_version_id, host_os_release_level, - /* host_extension_scope */ NULL, /* Leave empty, we need to accept both system and portable */ + /* host_extension_scope = */ NULL, /* Leave empty, we need to accept both system and portable */ extension_release, class); - 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) return log_debug_errno(r, "Failed to compare directory %s extension-release metadata with the root's os-release: %m", extension_name); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Directory %s extension-release metadata does not match the root's.", extension_name); _fallthrough_; } @@ -2049,9 +2057,9 @@ static bool root_read_only( } static bool home_read_only( - char** read_only_paths, - char** inaccessible_paths, - char** empty_directories, + char * const *read_only_paths, + char * const *inaccessible_paths, + char * const *empty_directories, const BindMount *bind_mounts, size_t n_bind_mounts, const TemporaryFileSystem *temporary_filesystems, @@ -2070,13 +2078,13 @@ static bool home_read_only( prefixed_path_strv_contains(empty_directories, "/home")) return true; - for (size_t i = 0; i < n_temporary_filesystems; i++) - if (path_equal(temporary_filesystems[i].path, "/home")) + FOREACH_ARRAY(i, temporary_filesystems, n_temporary_filesystems) + if (path_equal(i->path, "/home")) return true; /* If /home is overmounted with some dir from the host it's not writable. */ - for (size_t i = 0; i < n_bind_mounts; i++) - if (path_equal(bind_mounts[i].destination, "/home")) + FOREACH_ARRAY(i, bind_mounts, n_bind_mounts) + if (path_equal(i->destination, "/home")) return true; return false; @@ -2088,6 +2096,7 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_strv_free_ char **hierarchies = NULL; _cleanup_(mount_list_done) MountList ml = {}; + _cleanup_close_ int userns_fd = -EBADF; bool require_prefix = false; const char *root; DissectImageFlags dissect_image_flags = @@ -2099,7 +2108,8 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { DISSECT_IMAGE_USR_NO_ROOT | DISSECT_IMAGE_GROWFS | DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES; + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; int r; assert(p); @@ -2123,40 +2133,57 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { SET_FLAG(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE, p->verity && p->verity->data_path); - r = loop_device_make_by_path( - p->root_image, - FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */, - /* sector_size= */ UINT32_MAX, - FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, - LOCK_SH, - &loop_device); - if (r < 0) - return log_debug_errno(r, "Failed to create loop device for root image: %m"); - - r = dissect_loop_device( - loop_device, - p->verity, - p->root_image_options, - p->root_image_policy, - dissect_image_flags, - &dissected_image); - if (r < 0) - return log_debug_errno(r, "Failed to dissect image: %m"); + if (p->runtime_scope == RUNTIME_SCOPE_SYSTEM) { + /* In system mode we mount directly */ - r = dissected_image_load_verity_sig_partition( - dissected_image, - loop_device->fd, - p->verity); - if (r < 0) - return r; + r = loop_device_make_by_path( + p->root_image, + FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_DEVICE_READ_ONLY) ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */, + /* sector_size= */ UINT32_MAX, + FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, + LOCK_SH, + &loop_device); + if (r < 0) + return log_debug_errno(r, "Failed to create loop device for root image: %m"); + + r = dissect_loop_device( + loop_device, + p->verity, + p->root_image_options, + p->root_image_policy, + dissect_image_flags, + &dissected_image); + if (r < 0) + return log_debug_errno(r, "Failed to dissect image: %m"); - r = dissected_image_decrypt( - dissected_image, - NULL, - p->verity, - dissect_image_flags); - if (r < 0) - return log_debug_errno(r, "Failed to decrypt dissected image: %m"); + r = dissected_image_load_verity_sig_partition( + dissected_image, + loop_device->fd, + p->verity); + if (r < 0) + return r; + + r = dissected_image_decrypt( + dissected_image, + NULL, + p->verity, + dissect_image_flags); + if (r < 0) + return log_debug_errno(r, "Failed to decrypt dissected image: %m"); + } else { + userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to open our own user namespace: %m"); + + r = mountfsd_mount_image( + p->root_image, + userns_fd, + p->root_image_policy, + dissect_image_flags, + &dissected_image); + if (r < 0) + return r; + } } if (p->root_directory) @@ -2520,16 +2547,18 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { root, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, - /* userns_fd= */ -EBADF, + userns_fd, dissect_image_flags); if (r < 0) return log_debug_errno(r, "Failed to mount root image: %m"); /* Now release the block device lock, so that udevd is free to call BLKRRPART on the device * if it likes. */ - r = loop_device_flock(loop_device, LOCK_UN); - if (r < 0) - return log_debug_errno(r, "Failed to release lock on loopback block device: %m"); + if (loop_device) { + r = loop_device_flock(loop_device, LOCK_UN); + if (r < 0) + return log_debug_errno(r, "Failed to release lock on loopback block device: %m"); + } r = dissected_image_relinquish(dissected_image); if (r < 0) @@ -2538,7 +2567,7 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { } else if (p->root_directory) { /* A root directory is specified. Turn its directory into bind mount, if it isn't one yet. */ - r = path_is_mount_point(root, NULL, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(root, /* root = */ NULL, AT_SYMLINK_FOLLOW); if (r < 0) return log_debug_errno(r, "Failed to detect that %s is a mount point or not: %m", root); if (r == 0) { @@ -2595,9 +2624,9 @@ int setup_namespace(const NamespaceParameters *p, char **error_path) { void bind_mount_free_many(BindMount *b, size_t n) { assert(b || n == 0); - for (size_t i = 0; i < n; i++) { - free(b[i].source); - free(b[i].destination); + FOREACH_ARRAY(i, b, n) { + free(i->source); + free(i->destination); } free(b); @@ -2625,7 +2654,7 @@ int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) { *b = c; - c[(*n) ++] = (BindMount) { + c[(*n)++] = (BindMount) { .source = TAKE_PTR(s), .destination = TAKE_PTR(d), .read_only = item->read_only, @@ -2694,7 +2723,7 @@ int mount_image_add(MountImage **m, size_t *n, const MountImage *item) { *m = c; - c[(*n) ++] = (MountImage) { + c[(*n)++] = (MountImage) { .source = TAKE_PTR(s), .destination = TAKE_PTR(d), .mount_options = TAKE_PTR(options), @@ -2745,7 +2774,7 @@ int temporary_filesystem_add( *t = c; - c[(*n) ++] = (TemporaryFileSystem) { + c[(*n)++] = (TemporaryFileSystem) { .path = TAKE_PTR(p), .options = TAKE_PTR(o), }; diff --git a/src/core/path.c b/src/core/path.c index ef00c20..fdb6ca4 100644 --- a/src/core/path.c +++ b/src/core/path.c @@ -90,7 +90,7 @@ int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) { /* If this is a symlink watch both the symlink inode and where it points to. If the inode is * not a symlink both calls will install the same watch, which is redundant and doesn't * hurt. */ - for (int follow_symlink = 0; follow_symlink < 2; follow_symlink ++) { + for (int follow_symlink = 0; follow_symlink < 2; follow_symlink++) { uint32_t f = flags; SET_FLAG(f, IN_DONT_FOLLOW, !follow_symlink); @@ -249,6 +249,8 @@ static bool path_spec_check_good(PathSpec *s, bool initial, bool from_trigger_no static void path_spec_mkdir(PathSpec *s, mode_t mode) { int r; + assert(s); + if (IN_SET(s->type, PATH_EXISTS, PATH_EXISTS_GLOB)) return; @@ -260,6 +262,10 @@ static void path_spec_mkdir(PathSpec *s, mode_t mode) { static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { const char *type; + assert(s); + assert(f); + assert(prefix); + assert_se(type = path_type_to_string(s->type)); fprintf(f, "%s%s: %s\n", prefix, type, s->path); } @@ -272,9 +278,8 @@ void path_spec_done(PathSpec *s) { } static void path_init(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); - assert(u); assert(u->load_state == UNIT_STUB); p->directory_mode = 0755; @@ -295,9 +300,7 @@ void path_free_specs(Path *p) { } static void path_done(Unit *u) { - Path *p = PATH(u); - - assert(p); + Path *p = ASSERT_PTR(PATH(u)); p->trigger_notify_event_source = sd_event_source_disable_unref(p->trigger_notify_event_source); path_free_specs(p); @@ -309,7 +312,7 @@ static int path_add_mount_dependencies(Path *p) { assert(p); LIST_FOREACH(spec, s, p->specs) { - r = unit_require_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(UNIT(p), s->path, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); if (r < 0) return r; } @@ -389,10 +392,9 @@ static int path_add_extras(Path *p) { } static int path_load(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); int r; - assert(u); assert(u->load_state == UNIT_STUB); r = unit_load_fragment_and_dropin(u, true); @@ -410,11 +412,11 @@ static int path_load(Unit *u) { } static void path_dump(Unit *u, FILE *f, const char *prefix) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); Unit *trigger; - assert(p); assert(f); + assert(prefix); trigger = UNIT_TRIGGER(u); @@ -461,6 +463,7 @@ static int path_watch(Path *p) { static void path_set_state(Path *p, PathState state) { PathState old_state; + assert(p); if (p->state != state) @@ -481,9 +484,8 @@ static void path_set_state(Path *p, PathState state) { static void path_enter_waiting(Path *p, bool initial, bool from_trigger_notify); static int path_coldplug(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); - assert(p); assert(p->state == PATH_DEAD); if (p->deserialized_state != p->state) { @@ -625,10 +627,9 @@ static void path_mkdir(Path *p) { } static int path_start(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); int r; - assert(p); assert(IN_SET(p->state, PATH_DEAD, PATH_FAILED)); r = unit_test_trigger_loaded(u); @@ -648,9 +649,8 @@ static int path_start(Unit *u) { } static int path_stop(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); - assert(p); assert(IN_SET(p->state, PATH_WAITING, PATH_RUNNING)); path_enter_dead(p, PATH_SUCCESS); @@ -658,9 +658,8 @@ static int path_stop(Unit *u) { } static int path_serialize(Unit *u, FILE *f, FDSet *fds) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); - assert(u); assert(f); assert(fds); @@ -688,9 +687,8 @@ static int path_serialize(Unit *u, FILE *f, FDSet *fds) { } static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); - assert(u); assert(key); assert(value); assert(fds); @@ -755,28 +753,24 @@ static int path_deserialize_item(Unit *u, const char *key, const char *value, FD } static UnitActiveState path_active_state(Unit *u) { - assert(u); + Path *p = ASSERT_PTR(PATH(u)); - return state_translation_table[PATH(u)->state]; + return state_translation_table[p->state]; } static const char *path_sub_state_to_string(Unit *u) { - assert(u); + Path *p = ASSERT_PTR(PATH(u)); - return path_state_to_string(PATH(u)->state); + return path_state_to_string(p->state); } static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - PathSpec *s = userdata, *found = NULL; - Path *p; + PathSpec *s = ASSERT_PTR(userdata), *found = NULL; + Path *p = ASSERT_PTR(PATH(s->unit)); int changed; - assert(s); - assert(s->unit); assert(fd >= 0); - p = PATH(s->unit); - if (!IN_SET(p->state, PATH_WAITING, PATH_RUNNING)) return 0; @@ -827,10 +821,9 @@ static int path_trigger_notify_on_defer(sd_event_source *s, void *userdata) { } static void path_trigger_notify_impl(Unit *u, Unit *other, bool on_defer) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); int r; - assert(u); assert(other); /* Invoked whenever the unit we trigger changes state or gains or loses a job */ @@ -897,9 +890,7 @@ static void path_trigger_notify(Unit *u, Unit *other) { } static void path_reset_failed(Unit *u) { - Path *p = PATH(u); - - assert(p); + Path *p = ASSERT_PTR(PATH(u)); if (p->state == PATH_FAILED) path_set_state(p, PATH_DEAD); @@ -908,11 +899,9 @@ static void path_reset_failed(Unit *u) { } static int path_can_start(Unit *u) { - Path *p = PATH(u); + Path *p = ASSERT_PTR(PATH(u)); int r; - assert(p); - r = unit_test_start_limit(u); if (r < 0) { path_enter_dead(p, PATH_FAILURE_START_LIMIT_HIT); @@ -961,13 +950,11 @@ static int activation_details_path_deserialize(const char *key, const char *valu } static int activation_details_path_append_env(ActivationDetails *details, char ***strv) { - ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(details); + ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details)); char *s; int r; - assert(details); assert(strv); - assert(p); if (isempty(p->trigger_path_filename)) return 0; @@ -984,21 +971,15 @@ static int activation_details_path_append_env(ActivationDetails *details, char * } static int activation_details_path_append_pair(ActivationDetails *details, char ***strv) { - ActivationDetailsPath *p = ACTIVATION_DETAILS_PATH(details); + ActivationDetailsPath *p = ASSERT_PTR(ACTIVATION_DETAILS_PATH(details)); int r; - assert(details); assert(strv); - assert(p); if (isempty(p->trigger_path_filename)) return 0; - r = strv_extend(strv, "trigger_path"); - if (r < 0) - return r; - - r = strv_extend(strv, p->trigger_path_filename); + r = strv_extend_many(strv, "trigger_path", p->trigger_path_filename); if (r < 0) return r; diff --git a/src/core/restrict-ifaces.c b/src/core/restrict-ifaces.c deleted file mode 100644 index 4dd8656..0000000 --- a/src/core/restrict-ifaces.c +++ /dev/null @@ -1,200 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "fd-util.h" -#include "restrict-ifaces.h" -#include "netlink-util.h" - -#if BPF_FRAMEWORK -/* libbpf, clang and llc compile time dependencies are satisfied */ - -#include "bpf-dlopen.h" -#include "bpf-link.h" -#include "bpf-util.h" -#include "bpf/restrict_ifaces/restrict-ifaces-skel.h" - -static struct restrict_ifaces_bpf *restrict_ifaces_bpf_free(struct restrict_ifaces_bpf *obj) { - restrict_ifaces_bpf__destroy(obj); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct restrict_ifaces_bpf *, restrict_ifaces_bpf_free); - -static int prepare_restrict_ifaces_bpf( - Unit* u, - bool is_allow_list, - const Set *restrict_network_interfaces, - struct restrict_ifaces_bpf **ret_object) { - - _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char *iface; - int r, map_fd; - - assert(ret_object); - - obj = restrict_ifaces_bpf__open(); - if (!obj) - return log_unit_full_errno(u, u ? LOG_ERR : LOG_DEBUG, errno, "restrict-interfaces: Failed to open BPF object: %m"); - - r = sym_bpf_map__set_max_entries(obj->maps.sd_restrictif, MAX(set_size(restrict_network_interfaces), 1u)); - if (r != 0) - return log_unit_full_errno(u, u ? LOG_ERR : LOG_WARNING, r, - "restrict-interfaces: Failed to resize BPF map '%s': %m", - sym_bpf_map__name(obj->maps.sd_restrictif)); - - obj->rodata->is_allow_list = is_allow_list; - - r = restrict_ifaces_bpf__load(obj); - if (r != 0) - return log_unit_full_errno(u, u ? LOG_ERR : LOG_DEBUG, r, "restrict-interfaces: Failed to load BPF object: %m"); - - map_fd = sym_bpf_map__fd(obj->maps.sd_restrictif); - - SET_FOREACH(iface, restrict_network_interfaces) { - uint8_t dummy = 0; - int ifindex; - - ifindex = rtnl_resolve_interface(&rtnl, iface); - if (ifindex < 0) { - log_unit_warning_errno(u, ifindex, - "restrict-interfaces: Couldn't find index of network interface '%s', ignoring: %m", - iface); - continue; - } - - if (sym_bpf_map_update_elem(map_fd, &ifindex, &dummy, BPF_ANY)) - return log_unit_full_errno(u, u ? LOG_ERR : LOG_WARNING, errno, - "restrict-interfaces: Failed to update BPF map '%s' fd: %m", - sym_bpf_map__name(obj->maps.sd_restrictif)); - } - - *ret_object = TAKE_PTR(obj); - return 0; -} - -int restrict_network_interfaces_supported(void) { - _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; - static int supported = -1; - int r; - - if (supported >= 0) - return supported; - - if (!cgroup_bpf_supported()) - return (supported = false); - - if (!compat_libbpf_probe_bpf_prog_type(BPF_PROG_TYPE_CGROUP_SKB, /*opts=*/NULL)) { - log_debug("restrict-interfaces: BPF program type cgroup_skb is not supported"); - return (supported = false); - } - - r = prepare_restrict_ifaces_bpf(NULL, true, NULL, &obj); - if (r < 0) { - log_debug_errno(r, "restrict-interfaces: Failed to load BPF object: %m"); - return (supported = false); - } - - return (supported = bpf_can_link_program(obj->progs.sd_restrictif_i)); -} - -static int restrict_network_interfaces_install_impl(Unit *u) { - _cleanup_(bpf_link_freep) struct bpf_link *egress_link = NULL, *ingress_link = NULL; - _cleanup_(restrict_ifaces_bpf_freep) struct restrict_ifaces_bpf *obj = NULL; - _cleanup_free_ char *cgroup_path = NULL; - _cleanup_close_ int cgroup_fd = -EBADF; - CGroupContext *cc; - int r; - - cc = unit_get_cgroup_context(u); - if (!cc) - return 0; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, NULL, &cgroup_path); - if (r < 0) - return log_unit_error_errno(u, r, "restrict-interfaces: Failed to get cgroup path: %m"); - - if (!cc->restrict_network_interfaces) - return 0; - - r = prepare_restrict_ifaces_bpf(u, - cc->restrict_network_interfaces_is_allow_list, - cc->restrict_network_interfaces, - &obj); - if (r < 0) - return r; - - cgroup_fd = open(cgroup_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0); - if (cgroup_fd < 0) - return -errno; - - ingress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_i, cgroup_fd); - r = sym_libbpf_get_error(ingress_link); - if (r != 0) - return log_unit_error_errno(u, r, "restrict-interfaces: Failed to create ingress cgroup link: %m"); - - egress_link = sym_bpf_program__attach_cgroup(obj->progs.sd_restrictif_e, cgroup_fd); - r = sym_libbpf_get_error(egress_link); - if (r != 0) - return log_unit_error_errno(u, r, "restrict-interfaces: Failed to create egress cgroup link: %m"); - - u->restrict_ifaces_ingress_bpf_link = TAKE_PTR(ingress_link); - u->restrict_ifaces_egress_bpf_link = TAKE_PTR(egress_link); - - return 0; -} - -int restrict_network_interfaces_install(Unit *u) { - int r = restrict_network_interfaces_install_impl(u); - fdset_close(u->initial_restric_ifaces_link_fds); - return r; -} - -int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds) { - int r; - - assert(u); - - r = bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", u->restrict_ifaces_ingress_bpf_link); - if (r < 0) - return r; - - return bpf_serialize_link(f, fds, "restrict-ifaces-bpf-fd", u->restrict_ifaces_egress_bpf_link); -} - -int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd) { - int r; - - assert(u); - - if (!u->initial_restric_ifaces_link_fds) { - u->initial_restric_ifaces_link_fds = fdset_new(); - if (!u->initial_restric_ifaces_link_fds) - return log_oom(); - } - - r = fdset_put(u->initial_restric_ifaces_link_fds, fd); - if (r < 0) - return log_unit_error_errno(u, r, - "restrict-interfaces: Failed to put restrict-ifaces-bpf-fd %d to restored fdset: %m", fd); - - return 0; -} - -#else /* ! BPF_FRAMEWORK */ -int restrict_network_interfaces_supported(void) { - return 0; -} - -int restrict_network_interfaces_install(Unit *u) { - return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EOPNOTSUPP), - "restrict-interfaces: Failed to install; BPF programs built from source code are not supported: %m"); -} - -int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds) { - return 0; -} - -int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd) { - return 0; -} -#endif diff --git a/src/core/restrict-ifaces.h b/src/core/restrict-ifaces.h deleted file mode 100644 index 6e7a824..0000000 --- a/src/core/restrict-ifaces.h +++ /dev/null @@ -1,16 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "fdset.h" -#include "unit.h" - -typedef struct Unit Unit; - -int restrict_network_interfaces_supported(void); -int restrict_network_interfaces_install(Unit *u); - -int serialize_restrict_network_interfaces(Unit *u, FILE *f, FDSet *fds); - -/* Add BPF link fd created before daemon-reload or daemon-reexec. - * FDs will be closed at the end of restrict_network_interfaces_install. */ -int restrict_network_interfaces_add_initial_link_fd(Unit *u, int fd); diff --git a/src/core/scope.c b/src/core/scope.c index 2841280..cfa2aeb 100644 --- a/src/core/scope.c +++ b/src/core/scope.c @@ -23,21 +23,20 @@ #include "user-util.h" static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { - [SCOPE_DEAD] = UNIT_INACTIVE, - [SCOPE_START_CHOWN] = UNIT_ACTIVATING, - [SCOPE_RUNNING] = UNIT_ACTIVE, - [SCOPE_ABANDONED] = UNIT_ACTIVE, + [SCOPE_DEAD] = UNIT_INACTIVE, + [SCOPE_START_CHOWN] = UNIT_ACTIVATING, + [SCOPE_RUNNING] = UNIT_ACTIVE, + [SCOPE_ABANDONED] = UNIT_ACTIVE, [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SCOPE_FAILED] = UNIT_FAILED, + [SCOPE_FAILED] = UNIT_FAILED, }; static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); static void scope_init(Unit *u) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); - assert(u); assert(u->load_state == UNIT_STUB); s->runtime_max_usec = USEC_INFINITY; @@ -48,9 +47,7 @@ static void scope_init(Unit *u) { } static void scope_done(Unit *u) { - Scope *s = SCOPE(u); - - assert(u); + Scope *s = ASSERT_PTR(SCOPE(u)); s->controller = mfree(s->controller); s->controller_track = sd_bus_track_unref(s->controller_track); @@ -84,6 +81,7 @@ static int scope_arm_timer(Scope *s, bool relative, usec_t usec) { static void scope_set_state(Scope *s, ScopeState state) { ScopeState old_state; + assert(s); if (s->state != state) @@ -101,7 +99,8 @@ static void scope_set_state(Scope *s, ScopeState state) { } if (state != old_state) - log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state)); + log_unit_debug(UNIT(s), "Changed %s -> %s", + scope_state_to_string(old_state), scope_state_to_string(state)); unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true); } @@ -181,10 +180,9 @@ static int scope_add_extras(Scope *s) { } static int scope_load(Unit *u) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); int r; - assert(s); assert(u->load_state == UNIT_STUB); if (!u->transient && !MANAGER_IS_RELOADING(u->manager)) @@ -227,10 +225,9 @@ static usec_t scope_coldplug_timeout(Scope *s) { } static int scope_coldplug(Unit *u) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); int r; - assert(s); assert(s->state == SCOPE_DEAD); if (s->deserialized_state == s->state) @@ -260,10 +257,10 @@ static int scope_coldplug(Unit *u) { } static void scope_dump(Unit *u, FILE *f, const char *prefix) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); - assert(s); assert(f); + assert(prefix); fprintf(f, "%sScope State: %s\n" @@ -277,7 +274,7 @@ static void scope_dump(Unit *u, FILE *f, const char *prefix) { prefix, FORMAT_TIMESPAN(s->runtime_rand_extra_usec, USEC_PER_SEC), prefix, oom_policy_to_string(s->oom_policy)); - cgroup_context_dump(UNIT(s), f, prefix); + cgroup_context_dump(u, f, prefix); kill_context_dump(&s->kill_context, f, prefix); } @@ -317,13 +314,9 @@ static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { else { r = unit_kill_context( UNIT(s), - &s->kill_context, state != SCOPE_STOP_SIGTERM ? KILL_KILL : s->was_abandoned ? KILL_TERMINATE_AND_LOG : - KILL_TERMINATE, - /* main_pid= */ NULL, - /* control_pid= */ NULL, - /* main_pid_alien= */ false); + KILL_TERMINATE); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); goto fail; @@ -350,13 +343,15 @@ fail: } static int scope_enter_start_chown(Scope *s) { + Unit *u = UNIT(ASSERT_PTR(s)); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - Unit *u = UNIT(s); int r; - assert(s); assert(s->user); + if (!s->cgroup_runtime) + return -EINVAL; + r = scope_arm_timer(s, /* relative= */ true, u->manager->defaults.timeout_start_usec); if (r < 0) return r; @@ -389,7 +384,7 @@ static int scope_enter_start_chown(Scope *s) { } } - r = cg_set_access(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, uid, gid); + r = cg_set_access(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_runtime->cgroup_path, uid, gid); if (r < 0) { log_unit_error_errno(UNIT(s), r, "Failed to adjust control group access: %m"); _exit(EXIT_CGROUP); @@ -411,11 +406,9 @@ fail: } static int scope_enter_running(Scope *s) { - Unit *u = UNIT(s); + Unit *u = UNIT(ASSERT_PTR(s)); int r; - assert(s); - (void) bus_scope_track_controller(s); r = unit_acquire_invocation_id(u); @@ -458,9 +451,7 @@ fail: } static int scope_start(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); + Scope *s = ASSERT_PTR(SCOPE(u)); if (unit_has_name(u, SPECIAL_INIT_SCOPE)) return -EPERM; @@ -489,9 +480,7 @@ static int scope_start(Unit *u) { } static int scope_stop(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); + Scope *s = ASSERT_PTR(SCOPE(u)); if (IN_SET(s->state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) return 0; @@ -503,9 +492,7 @@ static int scope_stop(Unit *u) { } static void scope_reset_failed(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); + Scope *s = ASSERT_PTR(SCOPE(u)); if (s->state == SCOPE_FAILED) scope_set_state(s, SCOPE_DEAD); @@ -514,7 +501,7 @@ static void scope_reset_failed(Unit *u) { } static int scope_get_timeout(Unit *u, usec_t *timeout) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); usec_t t; int r; @@ -532,10 +519,9 @@ static int scope_get_timeout(Unit *u, usec_t *timeout) { } static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); PidRef *pid; - assert(s); assert(f); assert(fds); @@ -552,10 +538,9 @@ static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { } static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); int r; - assert(u); assert(key); assert(value); assert(fds); @@ -600,8 +585,7 @@ static int scope_deserialize_item(Unit *u, const char *key, const char *value, F } static void scope_notify_cgroup_empty_event(Unit *u) { - Scope *s = SCOPE(u); - assert(u); + Scope *s = ASSERT_PTR(SCOPE(u)); log_unit_debug(u, "cgroup is empty"); @@ -610,7 +594,7 @@ static void scope_notify_cgroup_empty_event(Unit *u) { } static void scope_notify_cgroup_oom_event(Unit *u, bool managed_oom) { - Scope *s = SCOPE(u); + Scope *s = ASSERT_PTR(SCOPE(u)); if (managed_oom) log_unit_debug(u, "Process(es) of control group were killed by systemd-oomd."); @@ -642,9 +626,7 @@ static void scope_notify_cgroup_oom_event(Unit *u, bool managed_oom) { } static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Scope *s = SCOPE(u); - - assert(s); + Scope *s = ASSERT_PTR(SCOPE(u)); if (s->state == SCOPE_START_CHOWN) { if (!is_clean_exit(code, status, EXIT_CLEAN_COMMAND, NULL)) @@ -662,9 +644,8 @@ static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { } static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Scope *s = SCOPE(userdata); + Scope *s = ASSERT_PTR(SCOPE(userdata)); - assert(s); assert(s->timer_event_source == source); switch (s->state) { @@ -726,15 +707,15 @@ int scope_abandon(Scope *s) { } static UnitActiveState scope_active_state(Unit *u) { - assert(u); + Scope *s = ASSERT_PTR(SCOPE(u)); - return state_translation_table[SCOPE(u)->state]; + return state_translation_table[s->state]; } static const char *scope_sub_state_to_string(Unit *u) { - assert(u); + Scope *s = ASSERT_PTR(SCOPE(u)); - return scope_state_to_string(SCOPE(u)->state); + return scope_state_to_string(s->state); } static void scope_enumerate_perpetual(Manager *m) { @@ -782,6 +763,7 @@ const UnitVTable scope_vtable = { .object_size = sizeof(Scope), .cgroup_context_offset = offsetof(Scope, cgroup_context), .kill_context_offset = offsetof(Scope, kill_context), + .cgroup_runtime_offset = offsetof(Scope, cgroup_runtime), .sections = "Unit\0" @@ -806,8 +788,7 @@ const UnitVTable scope_vtable = { .start = scope_start, .stop = scope_stop, - .freeze = unit_freeze_vtable_common, - .thaw = unit_thaw_vtable_common, + .freezer_action = unit_cgroup_freezer_action, .get_timeout = scope_get_timeout, diff --git a/src/core/scope.h b/src/core/scope.h index c9574a3..1090431 100644 --- a/src/core/scope.h +++ b/src/core/scope.h @@ -21,6 +21,7 @@ struct Scope { CGroupContext cgroup_context; KillContext kill_context; + CGroupRuntime *cgroup_runtime; ScopeState state, deserialized_state; ScopeResult result; diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c index 62181a6..a67a520 100644 --- a/src/core/selinux-access.c +++ b/src/core/selinux-access.c @@ -193,7 +193,6 @@ int mac_selinux_access_check_internal( assert(message); assert(permission); assert(function); - assert(error); r = access_init(error); if (r <= 0) @@ -248,7 +247,7 @@ int mac_selinux_access_check_internal( tclass = "system"; } - sd_bus_creds_get_cmdline(creds, &cmdline); + (void) sd_bus_creds_get_cmdline(creds, &cmdline); cl = strv_join(cmdline, " "); struct audit_info audit_info = { @@ -268,7 +267,7 @@ int mac_selinux_access_check_internal( log_full_errno_zerook(LOG_DEBUG, r, "SELinux access check scon=%s tcon=%s tclass=%s perm=%s state=%s function=%s path=%s cmdline=%s: %m", - scon, acon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(unit_path), strna(empty_to_null(cl))); + scon, acon, tclass, permission, enforce ? "enforcing" : "permissive", function, strna(unit_path), empty_to_na(cl)); return enforce ? r : 0; } diff --git a/src/core/service.c b/src/core/service.c index ffe92d2..8ec27c4 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -24,6 +24,7 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" +#include "io-util.h" #include "load-dropin.h" #include "load-fragment.h" #include "log.h" @@ -34,6 +35,7 @@ #include "path-util.h" #include "process-util.h" #include "random-util.h" +#include "selinux-util.h" #include "serialize.h" #include "service.h" #include "signal-util.h" @@ -49,61 +51,61 @@ #define service_spawn(...) service_spawn_internal(__func__, __VA_ARGS__) static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_CONDITION] = UNIT_ACTIVATING, - [SERVICE_START_PRE] = UNIT_ACTIVATING, - [SERVICE_START] = UNIT_ACTIVATING, - [SERVICE_START_POST] = UNIT_ACTIVATING, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, - [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_WATCHDOG] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE, + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_CONDITION] = UNIT_ACTIVATING, + [SERVICE_START_PRE] = UNIT_ACTIVATING, + [SERVICE_START] = UNIT_ACTIVATING, + [SERVICE_START_POST] = UNIT_ACTIVATING, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, + [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_WATCHDOG] = UNIT_DEACTIVATING, + [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_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING, - [SERVICE_CLEANING] = UNIT_MAINTENANCE, + [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, + [SERVICE_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING, + [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; /* For Type=idle we never want to delay any other jobs, hence we * consider idle jobs active as soon as we start working on them */ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_CONDITION] = UNIT_ACTIVE, - [SERVICE_START_PRE] = UNIT_ACTIVE, - [SERVICE_START] = UNIT_ACTIVE, - [SERVICE_START_POST] = UNIT_ACTIVE, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, - [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_WATCHDOG] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_DEAD_BEFORE_AUTO_RESTART] = UNIT_INACTIVE, + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_CONDITION] = UNIT_ACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVE, + [SERVICE_START] = UNIT_ACTIVE, + [SERVICE_START_POST] = UNIT_ACTIVE, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, + [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_WATCHDOG] = UNIT_DEACTIVATING, + [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_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING, - [SERVICE_CLEANING] = UNIT_MAINTENANCE, + [SERVICE_DEAD_RESOURCES_PINNED] = UNIT_INACTIVE, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING, + [SERVICE_AUTO_RESTART_QUEUED] = UNIT_ACTIVATING, + [SERVICE_CLEANING] = UNIT_MAINTENANCE, }; static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata); @@ -114,6 +116,25 @@ static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t ev static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); static void service_enter_reload_by_notify(Service *s); +static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) { + return IN_SET(state, + 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); +} + +static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) { + return IN_SET(state, + SERVICE_CONDITION, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + 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, + SERVICE_CLEANING); +} + static void service_init(Unit *u) { Service *s = SERVICE(u); @@ -151,25 +172,17 @@ static void service_init(Unit *u) { static void service_unwatch_control_pid(Service *s) { assert(s); - - if (!pidref_is_set(&s->control_pid)) - return; - - unit_unwatch_pidref(UNIT(s), &s->control_pid); - pidref_done(&s->control_pid); + unit_unwatch_pidref_done(UNIT(s), &s->control_pid); } static void service_unwatch_main_pid(Service *s) { assert(s); - - if (!pidref_is_set(&s->main_pid)) - return; - - unit_unwatch_pidref(UNIT(s), &s->main_pid); - pidref_done(&s->main_pid); + unit_unwatch_pidref_done(UNIT(s), &s->main_pid); } static void service_unwatch_pid_file(Service *s) { + assert(s); + if (!s->pid_file_pathspec) return; @@ -179,42 +192,41 @@ static void service_unwatch_pid_file(Service *s) { s->pid_file_pathspec = mfree(s->pid_file_pathspec); } -static int service_set_main_pidref(Service *s, PidRef *pidref) { +static int service_set_main_pidref(Service *s, PidRef pidref_consume, const dual_timestamp *start_timestamp) { + _cleanup_(pidref_done) PidRef pidref = pidref_consume; int r; assert(s); - /* Takes ownership of the specified pidref on success, but not on failure. */ + /* Takes ownership of the specified pidref on both success and failure. */ - if (!pidref_is_set(pidref)) + if (!pidref_is_set(&pidref)) return -ESRCH; - if (pidref->pid <= 1) + if (pidref.pid <= 1) return -EINVAL; - if (pidref_is_self(pidref)) + if (pidref_is_self(&pidref)) return -EINVAL; - if (pidref_equal(&s->main_pid, pidref) && s->main_pid_known) { - pidref_done(pidref); + if (s->main_pid_known && pidref_equal(&s->main_pid, &pidref)) return 0; - } - if (!pidref_equal(&s->main_pid, pidref)) { + if (!pidref_equal(&s->main_pid, &pidref)) { service_unwatch_main_pid(s); - exec_status_start(&s->main_exec_status, pidref->pid); + exec_status_start(&s->main_exec_status, pidref.pid, start_timestamp); } - s->main_pid = TAKE_PIDREF(*pidref); + s->main_pid = TAKE_PIDREF(pidref); s->main_pid_known = true; r = pidref_is_my_child(&s->main_pid); if (r < 0) log_unit_warning_errno(UNIT(s), r, "Can't determine if process "PID_FMT" is our child, assuming it is not: %m", s->main_pid.pid); - else if (r == 0) + else if (r == 0) // FIXME: Supervise through pidfd here log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", s->main_pid.pid); - s->main_pid_alien = r <= 0; + return 0; } @@ -290,7 +302,7 @@ static void service_start_watchdog(Service *s) { /* Let's process everything else which might be a sign * of living before we consider a service died. */ - r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE); + r = sd_event_source_set_priority(s->watchdog_event_source, EVENT_PRIORITY_SERVICE_WATCHDOG); } if (r < 0) log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m"); @@ -429,7 +441,7 @@ static void service_release_fd_store(Service *s) { static void service_release_stdio_fd(Service *s) { assert(s); - if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stdout_fd < 0) + if (s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) return; log_unit_debug(UNIT(s), "Releasing stdin/stdout/stderr file descriptors."); @@ -438,10 +450,9 @@ static void service_release_stdio_fd(Service *s) { 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); - assert(s); +static void service_done(Unit *u) { + Service *s = ASSERT_PTR(SERVICE(u)); open_file_free_many(&s->open_files); @@ -449,6 +460,7 @@ static void service_done(Unit *u) { s->status_text = mfree(s->status_text); 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; @@ -511,7 +523,8 @@ static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do 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); + 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) /* Our store is full. Use this errno rather than E[NM]FILE to distinguish from the case @@ -545,17 +558,16 @@ static int service_add_fd_store(Service *s, int fd_in, const char *name, bool do r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fs->fd, 0, on_fd_store_io, fs); if (r < 0 && r != -EPERM) /* EPERM indicates fds that aren't pollable, which is OK */ return r; - else if (r >= 0) + if (r >= 0) (void) sd_event_source_set_description(fs->event_source, "service-fd-store"); } + log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fs->fd, fs->fdname); + fs->service = s; - LIST_PREPEND(fd_store, s->fd_store, fs); + LIST_PREPEND(fd_store, s->fd_store, TAKE_PTR(fs)); s->n_fd_store++; - log_unit_debug(UNIT(s), "Added fd %i (%s) to fd store.", fs->fd, fs->fdname); - - TAKE_PTR(fs); return 1; /* fd newly stored */ } @@ -654,9 +666,6 @@ static int service_verify(Service *s) { if (s->type == SERVICE_ONESHOT && IN_SET(s->restart, SERVICE_RESTART_ALWAYS, SERVICE_RESTART_ON_SUCCESS)) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has Restart= set to either always or on-success, which isn't allowed for Type=oneshot services. Refusing."); - if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) - return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has RestartForceExitStatus= set, which isn't allowed for Type=oneshot services. Refusing."); - if (s->type == SERVICE_ONESHOT && s->exit_type == SERVICE_EXIT_CGROUP) return log_unit_error_errno(UNIT(s), SYNTHETIC_ERRNO(ENOEXEC), "Service has ExitType=cgroup set, which isn't allowed for Type=oneshot services. Refusing."); @@ -856,7 +865,7 @@ static int service_add_extras(Service *s) { } static int service_load(Unit *u) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); int r; r = unit_load_fragment_and_dropin(u, true); @@ -901,21 +910,19 @@ static void service_dump_fdstore(Service *s, FILE *f, const char *prefix) { "%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), + strna(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)); + strna(accmode_to_string(flags))); } } static void service_dump(Unit *u, FILE *f, const char *prefix) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); const char *prefix2; - assert(s); - prefix = strempty(prefix); prefix2 = strjoina(prefix, "\t"); @@ -1016,8 +1023,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { if (!s->exec_command[c]) continue; - fprintf(f, "%s-> %s:\n", - prefix, service_exec_command_to_string(c)); + fprintf(f, "%s%s %s:\n", + prefix, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), service_exec_command_to_string(c)); exec_command_dump_list(s->exec_command[c], f, prefix2); } @@ -1159,7 +1166,7 @@ static int service_load_pid_file(Service *s, bool may_warn) { } else log_unit_debug(UNIT(s), "Main PID loaded: "PID_FMT, pidref.pid); - r = service_set_main_pidref(s, &pidref); + r = service_set_main_pidref(s, TAKE_PIDREF(pidref), /* start_timestamp = */ NULL); if (r < 0) return r; @@ -1189,7 +1196,7 @@ static void service_search_main_pid(Service *s) { return; log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid.pid); - if (service_set_main_pidref(s, &pid) < 0) + if (service_set_main_pidref(s, TAKE_PIDREF(pid), /* start_timestamp = */ NULL) < 0) return; r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); @@ -1224,22 +1231,12 @@ static void service_set_state(Service *s, ServiceState state) { SERVICE_CLEANING)) s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source); - if (!IN_SET(state, - 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)) { + if (!SERVICE_STATE_WITH_MAIN_PROCESS(state)) { service_unwatch_main_pid(s); s->main_command = NULL; } - if (!IN_SET(state, - SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - 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, - SERVICE_CLEANING)) { + if (!SERVICE_STATE_WITH_CONTROL_PROCESS(state)) { service_unwatch_control_pid(s); s->control_command = NULL; s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; @@ -1326,12 +1323,7 @@ static int service_coldplug(Unit *u) { if (pidref_is_set(&s->main_pid) && pidref_is_unwaited(&s->main_pid) > 0 && - (IN_SET(s->deserialized_state, - 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))) { + SERVICE_STATE_WITH_MAIN_PROCESS(s->deserialized_state)) { r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); if (r < 0) return r; @@ -1339,12 +1331,7 @@ static int service_coldplug(Unit *u) { if (pidref_is_set(&s->control_pid) && pidref_is_unwaited(&s->control_pid) > 0 && - IN_SET(s->deserialized_state, - SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - 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, - SERVICE_CLEANING)) { + SERVICE_STATE_WITH_CONTROL_PROCESS(s->deserialized_state)) { r = unit_watch_pidref(UNIT(s), &s->control_pid, /* exclusive= */ false); if (r < 0) return r; @@ -1357,6 +1344,7 @@ static int service_coldplug(Unit *u) { SERVICE_DEAD_RESOURCES_PINNED)) { (void) unit_enqueue_rewatch_pids(u); (void) unit_setup_exec_runtime(u); + (void) unit_setup_cgroup_runtime(u); } if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) @@ -1418,13 +1406,12 @@ static int service_collect_fds( UNIT_FOREACH_DEPENDENCY(u, UNIT(s), UNIT_ATOM_TRIGGERED_BY) { _cleanup_free_ int *cfds = NULL; - Socket *sock; int cn_fds; - - if (u->type != UNIT_SOCKET) - continue; + Socket *sock; sock = SOCKET(u); + if (!sock) + continue; cn_fds = socket_collect_fds(sock, &cfds); if (cn_fds < 0) @@ -1436,18 +1423,8 @@ static int service_collect_fds( if (!rfds) { rfds = TAKE_PTR(cfds); rn_socket_fds = cn_fds; - } else { - int *t; - - t = reallocarray(rfds, rn_socket_fds + cn_fds, sizeof(int)); - if (!t) - return -ENOMEM; - - memcpy(t + rn_socket_fds, cfds, cn_fds * sizeof(int)); - - rfds = t; - rn_socket_fds += cn_fds; - } + } else if (!GREEDY_REALLOC_APPEND(rfds, rn_socket_fds, cfds, cn_fds)) + return -ENOMEM; r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); if (r < 0) @@ -1510,9 +1487,10 @@ static int service_allocate_exec_fd_event_source( if (r < 0) return log_unit_error_errno(UNIT(s), r, "Failed to allocate exec_fd event source: %m"); - /* This is a bit lower priority than SIGCHLD, as that carries a lot more interesting failure information */ + /* This is a bit higher priority than SIGCHLD, to make sure we don't confuse the case "failed to + * start" from the case "succeeded to start, but failed immediately after". */ - r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_NORMAL-3); + r = sd_event_source_set_priority(source, EVENT_PRIORITY_EXEC_FD); if (r < 0) return log_unit_error_errno(UNIT(s), r, "Failed to adjust priority of exec_fd event source: %m"); @@ -1602,12 +1580,52 @@ static Service *service_get_triggering_service(Service *s) { return NULL; } +static ExecFlags service_exec_flags(ServiceExecCommand command_id, ExecFlags cred_flag) { + /* All service main/control processes honor sandboxing and namespacing options (except those + explicitly excluded in service_spawn()) */ + ExecFlags flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT; + + assert(command_id >= 0); + assert(command_id < _SERVICE_EXEC_COMMAND_MAX); + assert((cred_flag & ~(EXEC_SETUP_CREDENTIALS_FRESH|EXEC_SETUP_CREDENTIALS)) == 0); + assert((cred_flag != 0) == (command_id == SERVICE_EXEC_START)); + + /* Control processes spawned before main process also get tty access */ + if (IN_SET(command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE, SERVICE_EXEC_START)) + flags |= EXEC_APPLY_TTY_STDIN; + + /* All start phases get access to credentials. ExecStartPre= gets a new credential store upon + * every invocation, so that updating credential files through it works. When the first main process + * starts, passed creds become stable. Also see 'cred_flag'. */ + if (command_id == SERVICE_EXEC_START_PRE) + flags |= EXEC_SETUP_CREDENTIALS_FRESH; + if (command_id == SERVICE_EXEC_START_POST) + flags |= EXEC_SETUP_CREDENTIALS; + + if (IN_SET(command_id, SERVICE_EXEC_START_PRE, SERVICE_EXEC_START)) + flags |= EXEC_SETENV_MONITOR_RESULT; + + if (command_id == SERVICE_EXEC_START) + return flags|cred_flag|EXEC_PASS_FDS|EXEC_SET_WATCHDOG; + + flags |= EXEC_IS_CONTROL; + + /* Put control processes spawned later than main process under .control sub-cgroup if appropriate */ + if (!IN_SET(command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE)) + flags |= EXEC_CONTROL_CGROUP; + + if (IN_SET(command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST)) + flags |= EXEC_SETENV_RESULT; + + return flags; +} + static int service_spawn_internal( const char *caller, Service *s, ExecCommand *c, - usec_t timeout, ExecFlags flags, + usec_t timeout, PidRef *ret_pid) { _cleanup_(exec_params_shallow_clear) ExecParameters exec_params = EXEC_PARAMETERS_INIT(flags); @@ -1615,7 +1633,6 @@ static int service_spawn_internal( _cleanup_strv_free_ char **final_env = NULL, **our_env = NULL; _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; size_t n_env = 0; - pid_t pid; int r; assert(caller); @@ -1631,7 +1648,7 @@ static int service_spawn_internal( assert(!s->exec_fd_event_source); - if (flags & EXEC_IS_CONTROL) { + if (FLAGS_SET(exec_params.flags, EXEC_IS_CONTROL)) { /* If this is a control process, mask the permissions/chroot application if this is requested. */ if (s->permissions_start_only) exec_params.flags &= ~EXEC_APPLY_SANDBOXING; @@ -1639,7 +1656,7 @@ static int service_spawn_internal( exec_params.flags &= ~EXEC_APPLY_CHROOT; } - if ((flags & EXEC_PASS_FDS) || + if (FLAGS_SET(exec_params.flags, EXEC_PASS_FDS) || s->exec_context.std_input == EXEC_INPUT_SOCKET || s->exec_context.std_output == EXEC_OUTPUT_SOCKET || s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { @@ -1654,10 +1671,12 @@ static int service_spawn_internal( exec_params.open_files = s->open_files; + exec_params.flags |= EXEC_PASS_FDS; + log_unit_debug(UNIT(s), "Passing %zu fds to service", exec_params.n_socket_fds + exec_params.n_storage_fds); } - if (!FLAGS_SET(flags, EXEC_IS_CONTROL) && s->type == SERVICE_EXEC) { + if (!FLAGS_SET(exec_params.flags, EXEC_IS_CONTROL) && s->type == SERVICE_EXEC) { r = service_allocate_exec_fd(s, &exec_fd_source, &exec_params.exec_fd); if (r < 0) return r; @@ -1671,7 +1690,7 @@ static int service_spawn_internal( if (!our_env) return -ENOMEM; - if (service_exec_needs_notify_socket(s, flags)) { + if (service_exec_needs_notify_socket(s, exec_params.flags)) { if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) return -ENOMEM; @@ -1730,10 +1749,10 @@ static int service_spawn_internal( Service *env_source = NULL; const char *monitor_prefix; - if (flags & EXEC_SETENV_RESULT) { + if (FLAGS_SET(exec_params.flags, EXEC_SETENV_RESULT)) { env_source = s; monitor_prefix = ""; - } else if (flags & EXEC_SETENV_MONITOR_RESULT) { + } else if (FLAGS_SET(exec_params.flags, EXEC_SETENV_MONITOR_RESULT)) { env_source = service_get_triggering_service(s); monitor_prefix = "MONITOR_"; } @@ -1751,18 +1770,15 @@ static int service_spawn_internal( r = asprintf(our_env + n_env++, "%sEXIT_STATUS=%i", monitor_prefix, env_source->main_exec_status.status); else r = asprintf(our_env + n_env++, "%sEXIT_STATUS=%s", monitor_prefix, signal_to_string(env_source->main_exec_status.status)); - if (r < 0) return -ENOMEM; } if (env_source != s) { - if (!sd_id128_is_null(UNIT(env_source)->invocation_id)) { - r = asprintf(our_env + n_env++, "%sINVOCATION_ID=" SD_ID128_FORMAT_STR, - monitor_prefix, SD_ID128_FORMAT_VAL(UNIT(env_source)->invocation_id)); - if (r < 0) + if (!sd_id128_is_null(UNIT(env_source)->invocation_id)) + if (asprintf(our_env + n_env++, "%sINVOCATION_ID=" SD_ID128_FORMAT_STR, + monitor_prefix, SD_ID128_FORMAT_VAL(UNIT(env_source)->invocation_id)) < 0) return -ENOMEM; - } if (asprintf(our_env + n_env++, "%sUNIT=%s", monitor_prefix, UNIT(env_source)->id) < 0) return -ENOMEM; @@ -1806,17 +1822,13 @@ static int service_spawn_internal( &exec_params, s->exec_runtime, &s->cgroup_context, - &pid); + &pidref); if (r < 0) return r; s->exec_fd_event_source = TAKE_PTR(exec_fd_source); s->exec_fd_hot = false; - r = pidref_set_pid(&pidref, pid); - if (r < 0) - return r; - r = unit_watch_pidref(UNIT(s), &pidref, /* exclusive= */ true); if (r < 0) return r; @@ -1864,10 +1876,10 @@ static int cgroup_good(Service *s) { /* Returns 0 if the cgroup is empty or doesn't exist, > 0 if it is exists and is populated, < 0 if we can't * figure it out */ - if (!UNIT(s)->cgroup_path) + if (!s->cgroup_runtime || !s->cgroup_runtime->cgroup_path) return 0; - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path); + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, s->cgroup_runtime->cgroup_path); if (r < 0) return r; @@ -1876,6 +1888,7 @@ static int cgroup_good(Service *s) { static bool service_shall_restart(Service *s, const char **reason) { assert(s); + assert(reason); /* Don't restart after manual stops */ if (s->forbid_restart) { @@ -1891,6 +1904,13 @@ static bool service_shall_restart(Service *s, const char **reason) { /* Restart if the exit code/status are configured as restart triggers */ if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) { + /* Don't allow Type=oneshot services to restart on success. Note that Restart=always/on-success + * is already rejected in service_verify. */ + if (s->type == SERVICE_ONESHOT && s->result == SERVICE_SUCCESS) { + *reason = "service type and exit status"; + return false; + } + *reason = "forced by exit status"; return true; } @@ -1962,7 +1982,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) } else if (s->result == SERVICE_SKIP_CONDITION) { unit_log_skip(UNIT(s), service_result_to_string(s->result)); end_state = service_determine_dead_state(s); - restart_state = SERVICE_DEAD_BEFORE_AUTO_RESTART; + restart_state = _SERVICE_STATE_INVALID; /* Never restart if skipped due to condition failure */ } else { unit_log_failure(UNIT(s), service_result_to_string(s->result)); end_state = SERVICE_FAILED; @@ -1984,8 +2004,10 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) if (allow_restart) { usec_t restart_usec_next; + assert(restart_state >= 0 && restart_state < _SERVICE_STATE_MAX); + /* 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 + * state (i.e. a state indicating deactivation), and then one 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 @@ -1999,8 +2021,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) r = service_arm_timer(s, /* relative= */ true, restart_usec_next); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to install restart timer: %m"); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, /* allow_restart= */ false); - return; + return service_enter_dead(s, SERVICE_FAILURE_RESOURCES, /* allow_restart= */ false); } log_unit_debug(UNIT(s), "Next restart interval calculated as: %s", FORMAT_TIMESPAN(restart_usec_next, 0)); @@ -2064,8 +2085,8 @@ static void service_enter_stop_post(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_stop_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_IS_CONTROL|EXEC_SETENV_RESULT|EXEC_CONTROL_CGROUP, &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop-post' task: %m"); @@ -2118,13 +2139,7 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f (void) unit_enqueue_rewatch_pids(UNIT(s)); kill_operation = state_to_kill_operation(s, state); - r = unit_kill_context( - UNIT(s), - &s->kill_context, - kill_operation, - &s->main_pid, - &s->control_pid, - s->main_pid_alien); + r = unit_kill_context(UNIT(s), kill_operation); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); goto fail; @@ -2193,8 +2208,8 @@ static void service_enter_stop(Service *s, ServiceResult f) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_stop_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_SETENV_RESULT|EXEC_CONTROL_CGROUP, &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'stop' task: %m"); @@ -2209,6 +2224,7 @@ static void service_enter_stop(Service *s, ServiceResult f) { static bool service_good(Service *s) { int main_pid_ok; + assert(s); if (s->type == SERVICE_DBUS && !s->bus_name_good) @@ -2265,6 +2281,7 @@ static void service_enter_running(Service *s, ServiceResult f) { static void service_enter_start_post(Service *s) { int r; + assert(s); service_unwatch_control_pid(s); @@ -2277,8 +2294,8 @@ static void service_enter_start_post(Service *s) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_start_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_CONTROL_CGROUP, &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-post' task: %m"); @@ -2387,43 +2404,44 @@ static void service_enter_start(Service *s) { r = service_spawn(s, c, + service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH), timeout, - EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_WRITE_CREDENTIALS|EXEC_SETENV_MONITOR_RESULT, &pidref); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start' task: %m"); goto fail; } - if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { - /* For simple services we immediately start - * the START_POST binaries. */ + assert(pidref.pid == c->exec_status.pid); - (void) service_set_main_pidref(s, &pidref); - service_enter_start_post(s); - - } else if (s->type == SERVICE_FORKING) { + switch (s->type) { - /* For forking services we wait until the start - * process exited. */ + case SERVICE_SIMPLE: + case SERVICE_IDLE: + /* For simple services we immediately start the START_POST binaries. */ + (void) service_set_main_pidref(s, TAKE_PIDREF(pidref), &c->exec_status.start_timestamp); + return service_enter_start_post(s); + case SERVICE_FORKING: + /* For forking services we wait until the start process exited. */ pidref_done(&s->control_pid); s->control_pid = TAKE_PIDREF(pidref); - service_set_state(s, SERVICE_START); - - } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD, SERVICE_EXEC)) { + return service_set_state(s, SERVICE_START); + + case SERVICE_ONESHOT: /* For oneshot services we wait until the start process exited, too, but it is our main process. */ + case SERVICE_EXEC: + case SERVICE_DBUS: + case SERVICE_NOTIFY: + case SERVICE_NOTIFY_RELOAD: + /* For D-Bus services we know the main pid right away, but wait for the bus name to appear + * on the bus. 'notify' and 'exec' services wait for readiness notification and EOF + * on exec_fd, respectively. */ + (void) service_set_main_pidref(s, TAKE_PIDREF(pidref), &c->exec_status.start_timestamp); + return service_set_state(s, SERVICE_START); - /* For oneshot services we wait until the start process exited, too, but it is our main process. */ - - /* For D-Bus services we know the main pid right away, but wait for the bus name to appear on the - * bus. 'notify' and 'exec' services are similar. */ - - (void) service_set_main_pidref(s, &pidref); - service_set_state(s, SERVICE_START); - } else + default: assert_not_reached(); - - return; + } fail: service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); @@ -2447,8 +2465,8 @@ static void service_enter_start_pre(Service *s) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_start_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN|EXEC_SETENV_MONITOR_RESULT|EXEC_WRITE_CREDENTIALS, &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'start-pre' task: %m"); @@ -2484,10 +2502,9 @@ static void service_enter_condition(Service *s) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_start_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_APPLY_TTY_STDIN, &s->control_pid); - if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'exec-condition' task: %m"); goto fail; @@ -2527,11 +2544,9 @@ static void service_enter_restart(Service *s) { /* Count the jobs we enqueue for restarting. This counter is maintained as long as the unit isn't * fully stopped, i.e. as long as it remains up or remains in auto-start states. The user can reset * the counter explicitly however via the usual "systemctl reset-failure" logic. */ - s->n_restarts ++; + 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)), @@ -2595,8 +2610,8 @@ static void service_enter_reload(Service *s) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), s->timeout_start_usec, - EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_CONTROL_CGROUP, &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn 'reload' task: %m"); @@ -2651,13 +2666,8 @@ static void service_run_next_control(Service *s) { r = service_spawn(s, s->control_command, + service_exec_flags(s->control_command_id, /* cred_flag = */ 0), 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)| - (IN_SET(s->control_command_id, SERVICE_EXEC_START_POST, SERVICE_EXEC_RELOAD, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST) ? EXEC_CONTROL_CGROUP : 0), &s->control_pid); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn next control task: %m"); @@ -2688,8 +2698,8 @@ static void service_run_next_main(Service *s) { r = service_spawn(s, s->main_command, + service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS), 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_WRITE_CREDENTIALS, &pidref); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to spawn next main task: %m"); @@ -2697,7 +2707,7 @@ static void service_run_next_main(Service *s) { return; } - (void) service_set_main_pidref(s, &pidref); + (void) service_set_main_pidref(s, TAKE_PIDREF(pidref), &s->main_command->exec_status.start_timestamp); } static int service_start(Unit *u) { @@ -2755,16 +2765,16 @@ static int service_start(Unit *u) { s->flush_n_restarts = false; } - u->reset_accounting = true; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt) + crt->reset_accounting = true; service_enter_condition(s); return 1; } static int service_stop(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); /* Don't create restart jobs from manual stops. */ s->forbid_restart = true; @@ -2821,9 +2831,7 @@ static int service_stop(Unit *u) { } static int service_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); @@ -2832,9 +2840,7 @@ static int service_reload(Unit *u) { } static bool service_can_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); return s->exec_command[SERVICE_EXEC_RELOAD] || s->type == SERVICE_NOTIFY_RELOAD; @@ -2858,14 +2864,13 @@ static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, const } static int service_serialize_exec_command(Unit *u, FILE *f, const ExecCommand *command) { + Service *s = ASSERT_PTR(SERVICE(u)); _cleanup_free_ char *args = NULL, *p = NULL; - Service *s = SERVICE(u); const char *type, *key; ServiceExecCommand id; size_t length = 0; unsigned idx; - assert(s); assert(f); if (!command) @@ -2927,10 +2932,9 @@ static int service_serialize_exec_command(Unit *u, FILE *f, const ExecCommand *c } static int service_serialize(Unit *u, FILE *f, FDSet *fds) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); int r; - assert(u); assert(f); assert(fds); @@ -2996,13 +3000,14 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { if (!c) return log_oom(); - (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %i", copy, c, fs->do_poll); + (void) serialize_item_format(f, "fd-store-fd", "%i \"%s\" %s", copy, c, one_zero(fs->do_poll)); } if (s->main_exec_status.pid > 0) { (void) serialize_item_format(f, "main-exec-status-pid", PID_FMT, s->main_exec_status.pid); (void) serialize_dual_timestamp(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); (void) serialize_dual_timestamp(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); + (void) serialize_dual_timestamp(f, "main-exec-status-handoff", &s->main_exec_status.handoff_timestamp); if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { (void) serialize_item_format(f, "main-exec-status-code", "%i", s->main_exec_status.code); @@ -3033,14 +3038,14 @@ int service_deserialize_exec_command( const char *key, const char *value) { - Service *s = SERVICE(u); - int r; - unsigned idx = 0, i; - bool control, found = false, last = false; - ServiceExecCommand id = _SERVICE_EXEC_COMMAND_INVALID; + Service *s = ASSERT_PTR(SERVICE(u)); ExecCommand *command = NULL; + ServiceExecCommand id = _SERVICE_EXEC_COMMAND_INVALID; _cleanup_free_ char *path = NULL; _cleanup_strv_free_ char **argv = NULL; + unsigned idx = 0, i; + bool control, found = false, last = false; + int r; enum ExecCommandState { STATE_EXEC_COMMAND_TYPE, @@ -3051,7 +3056,6 @@ int service_deserialize_exec_command( _STATE_EXEC_COMMAND_INVALID = -EINVAL, } state; - assert(s); assert(key); assert(value); @@ -3096,7 +3100,7 @@ int service_deserialize_exec_command( case STATE_EXEC_COMMAND_ARGS: r = strv_extend(&argv, arg); if (r < 0) - return -ENOMEM; + return r; break; default: assert_not_reached(); @@ -3139,10 +3143,9 @@ int service_deserialize_exec_command( } static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); int r; - assert(u); assert(key); assert(value); assert(fds); @@ -3179,10 +3182,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, (void) deserialize_pidref(fds, value, &s->control_pid); } else if (streq(key, "main-pid")) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + PidRef pidref; if (!pidref_is_set(&s->main_pid) && deserialize_pidref(fds, value, &pidref) >= 0) - (void) service_set_main_pidref(s, &pidref); + (void) service_set_main_pidref(s, pidref, /* start_timestamp = */ NULL); } else if (streq(key, "main-pid-known")) { int b; @@ -3239,9 +3242,9 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, _cleanup_close_ int fd = -EBADF; int do_poll; - r = extract_first_word(&value, &fdv, NULL, 0); - if (r <= 0) { - log_unit_debug(u, "Failed to parse fd-store-fd value, ignoring: %s", value); + r = extract_many_words(&value, " ", EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE, &fdv, &fdn, &fdp); + if (r < 2 || r > 3) { + log_unit_debug(u, "Failed to deserialize fd-store-fd, ignoring: %s", value); return 0; } @@ -3249,24 +3252,17 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, if (fd < 0) return 0; - r = extract_first_word(&value, &fdn, NULL, EXTRACT_CUNESCAPE | EXTRACT_UNQUOTE); - if (r <= 0) { - log_unit_debug(u, "Failed to parse fd-store-fd value, ignoring: %s", value); - return 0; - } - - r = extract_first_word(&value, &fdp, NULL, 0); - if (r == 0) { - /* If the value is not present, we assume the default */ - do_poll = 1; - } else if (r < 0 || (r = safe_atoi(fdp, &do_poll)) < 0) { - log_unit_debug_errno(u, r, "Failed to parse fd-store-fd value \"%s\", ignoring: %m", value); + do_poll = r == 3 ? parse_boolean(fdp) : true; + if (do_poll < 0) { + log_unit_debug_errno(u, do_poll, + "Failed to deserialize fd-store-fd do_poll, ignoring: %s", fdp); return 0; } r = service_add_fd_store(s, fd, fdn, do_poll); if (r < 0) { - log_unit_debug_errno(u, r, "Failed to store deserialized fd %i, ignoring: %m", fd); + log_unit_debug_errno(u, r, + "Failed to store deserialized fd '%s', ignoring: %m", fdn); return 0; } @@ -3296,6 +3292,8 @@ 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, "main-exec-status-handoff")) + deserialize_dual_timestamp(value, &s->main_exec_status.handoff_timestamp); else if (streq(key, "notify-access-override")) { NotifyAccess notify_access; @@ -3383,13 +3381,12 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, } static UnitActiveState service_active_state(Unit *u) { + Service *s = ASSERT_PTR(SERVICE(u)); const UnitActiveState *table; - assert(u); - - table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; + table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; - return table[SERVICE(u)->state]; + return table[s->state]; } static const char *service_sub_state_to_string(Unit *u) { @@ -3399,9 +3396,7 @@ static const char *service_sub_state_to_string(Unit *u) { } static bool service_may_gc(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); /* Never clean up services that still have a process around, even if the service is formally dead. Note that * unit_may_gc() already checked our cgroup for us, we just check our two additional PIDs, too, in case they @@ -3422,6 +3417,7 @@ static bool service_may_gc(Unit *u) { static int service_retry_pid_file(Service *s) { int r; + assert(s); assert(s->pid_file); assert(IN_SET(s->state, SERVICE_START, SERVICE_START_POST)); @@ -3438,6 +3434,8 @@ static int service_retry_pid_file(Service *s) { static int service_watch_pid_file(Service *s) { int r; + assert(s); + log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); r = path_spec_watch(s->pid_file_pathspec, service_dispatch_inotify_io); @@ -3457,6 +3455,7 @@ static int service_watch_pid_file(Service *s) { static int service_demand_pid_file(Service *s) { _cleanup_free_ PathSpec *ps = NULL; + assert(s); assert(s->pid_file); assert(!s->pid_file_pathspec); @@ -3485,11 +3484,8 @@ static int service_demand_pid_file(Service *s) { static int service_dispatch_inotify_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { PathSpec *p = ASSERT_PTR(userdata); - Service *s; + Service *s = ASSERT_PTR(SERVICE(p->unit)); - s = SERVICE(p->unit); - - assert(s); assert(fd >= 0); assert(IN_SET(s->state, SERVICE_START, SERVICE_START_POST)); assert(s->pid_file_pathspec); @@ -3515,20 +3511,19 @@ fail: } static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { - Service *s = SERVICE(userdata); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(userdata)); log_unit_debug(UNIT(s), "got exec-fd event"); /* If Type=exec is set, we'll consider a service started successfully the instant we invoked execve() - * successfully for it. We implement this through a pipe() towards the child, which the kernel automatically - * closes for us due to O_CLOEXEC on execve() in the child, which then triggers EOF on the pipe in the - * parent. We need to be careful however, as there are other reasons that we might cause the child's side of - * the pipe to be closed (for example, a simple exit()). To deal with that we'll ignore EOFs on the pipe unless - * the child signalled us first that it is about to call the execve(). It does so by sending us a simple - * non-zero byte via the pipe. We also provide the child with a way to inform us in case execve() failed: if it - * sends a zero byte we'll ignore POLLHUP on the fd again. */ + * successfully for it. We implement this through a pipe() towards the child, which the kernel + * automatically closes for us due to O_CLOEXEC on execve() in the child, which then triggers EOF on + * the pipe in the parent. We need to be careful however, as there are other reasons that we might + * cause the child's side of the pipe to be closed (for example, a simple exit()). To deal with that + * we'll ignore EOFs on the pipe unless the child signalled us first that it is about to call the + * execve(). It does so by sending us a simple non-zero byte via the pipe. We also provide the child + * with a way to inform us in case execve() failed: if it sends a zero byte we'll ignore POLLHUP on + * the fd again. */ for (;;) { uint8_t x; @@ -3541,8 +3536,7 @@ static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t ev return log_unit_error_errno(UNIT(s), errno, "Failed to read from exec_fd: %m"); } - if (n == 0) { /* EOF → the event we are waiting for */ - + if (n == 0) { /* EOF → the event we are waiting for in case of Type=exec */ s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source); if (s->exec_fd_hot) { /* Did the child tell us to expect EOF now? */ @@ -3561,16 +3555,13 @@ static int service_dispatch_exec_io(sd_event_source *source, int fd, uint32_t ev /* A byte was read → this turns on/off the exec fd logic */ assert(n == sizeof(x)); + s->exec_fd_hot = x; } - - return 0; } static void service_notify_cgroup_empty_event(Unit *u) { - Service *s = SERVICE(u); - - assert(u); + Service *s = ASSERT_PTR(SERVICE(u)); log_unit_debug(u, "Control group is empty."); @@ -3647,7 +3638,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { } static void service_notify_cgroup_oom_event(Unit *u, bool managed_oom) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); if (managed_oom) log_unit_debug(u, "Process(es) of control group were killed by systemd-oomd."); @@ -3702,12 +3693,12 @@ static void service_notify_cgroup_oom_event(Unit *u, bool managed_oom) { } static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Service *s = ASSERT_PTR(SERVICE(u)); bool notify_dbus = true; - Service *s = SERVICE(u); ServiceResult f; ExitClean clean_mode; + int r; - assert(s); assert(pid >= 0); /* Oneshot services and non-SERVICE_EXEC_START commands should not be @@ -3918,7 +3909,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { s->control_command->command_next && f == SERVICE_SUCCESS) { - /* There is another command to * execute, so let's do that. */ + /* There is another command to execute, so let's do that. */ log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); service_run_next_control(s); @@ -3959,7 +3950,6 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { if (s->pid_file) { bool has_start_post; - int r; /* Let's try to load the pid file here if we can. * The PID file might actually be created by a START_POST @@ -3986,8 +3976,6 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { } if (s->pid_file) { - int r; - r = service_load_pid_file(s, true); if (r < 0) { r = service_demand_pid_file(s); @@ -4076,9 +4064,8 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { } static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); + Service *s = ASSERT_PTR(SERVICE(userdata)); - assert(s); assert(source == s->timer_event_source); switch (s->state) { @@ -4275,10 +4262,9 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us } static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); + Service *s = ASSERT_PTR(SERVICE(userdata)); usec_t watchdog_usec; - assert(s); assert(source == s->watchdog_event_source); watchdog_usec = service_get_watchdog_usec(s); @@ -4295,35 +4281,49 @@ static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void return 0; } -static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) { +static void service_force_watchdog(Service *s) { assert(s); + if (!UNIT(s)->manager->service_watchdogs) + return; + + log_unit_error(UNIT(s), "Watchdog request (last status: %s)!", + s->status_text ?: ""); + + service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG); +} + +static bool service_notify_message_authorized(Service *s, pid_t pid) { + assert(s); + assert(pid_is_valid(pid)); + 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); + /* Warn level only if no notifications are expected */ + log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception is disabled", pid); return false; } if (notify_access == NOTIFY_MAIN && pid != s->main_pid.pid) { if (pidref_is_set(&s->main_pid)) - 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.pid); + log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid.pid); else - log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); + log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); return false; } if (notify_access == NOTIFY_EXEC && pid != s->main_pid.pid && pid != s->control_pid.pid) { if (pidref_is_set(&s->main_pid) && pidref_is_set(&s->control_pid)) - 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.pid, s->control_pid.pid); + log_unit_debug(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.pid, s->control_pid.pid); else if (pidref_is_set(&s->main_pid)) - 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.pid); + log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid.pid); else if (pidref_is_set(&s->control_pid)) - log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid.pid); + log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for control PID "PID_FMT, pid, s->control_pid.pid); else - log_unit_warning(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid); + log_unit_debug(UNIT(s), "Got notification message from PID "PID_FMT", but reception only permitted for main PID and control PID which are currently not known", pid); return false; } @@ -4331,44 +4331,35 @@ static bool service_notify_message_authorized(Service *s, pid_t pid, FDSet *fds) return true; } -static void service_force_watchdog(Service *s) { - if (!UNIT(s)->manager->service_watchdogs) - return; - - log_unit_error(UNIT(s), "Watchdog request (last status: %s)!", - s->status_text ?: ""); - - service_enter_signal(s, SERVICE_STOP_WATCHDOG, SERVICE_FAILURE_WATCHDOG); -} - static void service_notify_message( Unit *u, const struct ucred *ucred, char * const *tags, FDSet *fds) { - Service *s = SERVICE(u); - bool notify_dbus = false; - usec_t monotonic_usec = USEC_INFINITY; - const char *e; + Service *s = ASSERT_PTR(SERVICE(u)); int r; - assert(u); assert(ucred); - if (!service_notify_message_authorized(s, ucred->pid, fds)) + if (!service_notify_message_authorized(s, ucred->pid)) return; if (DEBUG_LOGGING) { - _cleanup_free_ char *cc = NULL; - - cc = strv_join(tags, ", "); + _cleanup_free_ char *cc = strv_join(tags, ", "); log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", ucred->pid, empty_to_na(cc)); } + usec_t monotonic_usec = USEC_INFINITY; + bool notify_dbus = false; + const char *e; + /* Interpret MAINPID= */ e = strv_find_startswith(tags, "MAINPID="); - if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) { + if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, + SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_SIGTERM)) { + _cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL; r = pidref_set_pidstr(&new_main_pid, e); @@ -4384,10 +4375,10 @@ static void service_notify_message( log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, but we'll accept it as the request to change it came from a privileged process.", new_main_pid.pid); r = 1; } else - log_unit_debug(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid); + log_unit_warning(u, "New main PID "PID_FMT" does not belong to service, refusing.", new_main_pid.pid); } if (r > 0) { - (void) service_set_main_pidref(s, &new_main_pid); + (void) service_set_main_pidref(s, TAKE_PIDREF(new_main_pid), /* start_timestamp = */ NULL); r = unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); if (r < 0) @@ -4585,11 +4576,36 @@ static void service_notify_message( unit_add_to_dbus_queue(u); } +static void service_handoff_timestamp( + Unit *u, + const struct ucred *ucred, + const dual_timestamp *ts) { + + Service *s = ASSERT_PTR(SERVICE(u)); + + assert(ucred); + assert(ts); + + if (s->main_pid.pid == ucred->pid) { + if (s->main_command) + exec_status_handoff(&s->main_command->exec_status, ucred, ts); + + exec_status_handoff(&s->main_exec_status, ucred, ts); + } else if (s->control_pid.pid == ucred->pid && s->control_command) + exec_status_handoff(&s->control_command->exec_status, ucred, ts); + else + return; + + unit_add_to_dbus_queue(u); +} + static int service_get_timeout(Unit *u, usec_t *timeout) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); uint64_t t; int r; + assert(timeout); + if (!s->timer_event_source) return 0; @@ -4604,7 +4620,7 @@ static int service_get_timeout(Unit *u, usec_t *timeout) { } static usec_t service_get_timeout_start_usec(Unit *u) { - Service *s = SERVICE(ASSERT_PTR(u)); + Service *s = ASSERT_PTR(SERVICE(u)); return s->timeout_start_usec; } @@ -4624,16 +4640,14 @@ static bool pick_up_pid_from_bus_name(Service *s) { } static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { + Service *s = ASSERT_PTR(SERVICE(userdata)); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; const sd_bus_error *e; - Unit *u = ASSERT_PTR(userdata); uint32_t pid; - Service *s; int r; assert(reply); - s = SERVICE(u); s->bus_name_pid_lookup_slot = sd_bus_slot_unref(s->bus_name_pid_lookup_slot); if (!s->bus_name || !pick_up_pid_from_bus_name(s)) @@ -4658,20 +4672,17 @@ static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, s return 1; } - log_unit_debug(u, "D-Bus name %s is now owned by process " PID_FMT, s->bus_name, pidref.pid); + log_unit_debug(UNIT(s), "D-Bus name %s is now owned by process " PID_FMT, s->bus_name, pidref.pid); - (void) service_set_main_pidref(s, &pidref); + (void) service_set_main_pidref(s, TAKE_PIDREF(pidref), /* start_timestamp = */ NULL); (void) unit_watch_pidref(UNIT(s), &s->main_pid, /* exclusive= */ false); return 1; } static void service_bus_name_owner_change(Unit *u, const char *new_owner) { - - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); int r; - assert(s); - if (new_owner) log_unit_debug(u, "D-Bus name %s now owned by %s", s->bus_name, new_owner); else @@ -4721,7 +4732,7 @@ int service_set_socket_fd( Service *s, int fd, Socket *sock, - SocketPeer *peer, + SocketPeer *peer, /* reference to object is donated to us on success */ bool selinux_context_net) { _cleanup_free_ char *peer_text = NULL; @@ -4729,6 +4740,7 @@ int service_set_socket_fd( assert(s); assert(fd >= 0); + assert(sock); /* This is called by the socket code when instantiating a new service for a stream socket and the socket needs * to be configured. We take ownership of the passed fd on success. */ @@ -4760,12 +4772,13 @@ int service_set_socket_fd( return r; } - r = unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false, UNIT_DEPENDENCY_IMPLICIT); + r = unit_add_two_dependencies(UNIT(s), UNIT_AFTER, UNIT_TRIGGERED_BY, UNIT(sock), false, UNIT_DEPENDENCY_IMPLICIT); if (r < 0) - return r; + return log_unit_debug_errno(UNIT(s), r, + "Failed to add After=/TriggeredBy= dependencies on socket unit: %m"); s->socket_fd = fd; - s->socket_peer = socket_peer_ref(peer); + s->socket_peer = peer; s->socket_fd_selinux_context_net = selinux_context_net; unit_ref_set(&s->accept_socket, UNIT(s), UNIT(sock)); @@ -4773,9 +4786,7 @@ int service_set_socket_fd( } static void service_reset_failed(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); if (s->state == SERVICE_FAILED) service_set_state(s, service_determine_dead_state(s)); @@ -4787,8 +4798,13 @@ static void service_reset_failed(Unit *u) { s->flush_n_restarts = false; } -static PidRef* service_main_pid(Unit *u) { - return &ASSERT_PTR(SERVICE(u))->main_pid; +static PidRef* service_main_pid(Unit *u, bool *ret_is_alien) { + Service *s = ASSERT_PTR(SERVICE(u)); + + if (ret_is_alien) + *ret_is_alien = s->main_pid_alien; + + return &s->main_pid; } static PidRef* service_control_pid(Unit *u) { @@ -4796,9 +4812,7 @@ static PidRef* service_control_pid(Unit *u) { } static bool service_needs_console(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); /* We provide our own implementation of this here, instead of relying of the generic implementation * unit_needs_console() provides, since we want to return false if we are in SERVICE_EXITED state. */ @@ -4826,9 +4840,7 @@ static bool service_needs_console(Unit *u) { } static int service_exit_status(Unit *u) { - Service *s = SERVICE(u); - - assert(u); + Service *s = ASSERT_PTR(SERVICE(u)); if (s->main_exec_status.pid <= 0 || !dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) @@ -4841,20 +4853,17 @@ static int service_exit_status(Unit *u) { } static const char* service_status_text(Unit *u) { - Service *s = SERVICE(u); - - assert(s); + Service *s = ASSERT_PTR(SERVICE(u)); return s->status_text; } static int service_clean(Unit *u, ExecCleanMask mask) { + Service *s = ASSERT_PTR(SERVICE(u)); _cleanup_strv_free_ char **l = NULL; bool may_clean_fdstore = false; - Service *s = SERVICE(u); int r; - assert(s); assert(mask != 0); if (!IN_SET(s->state, SERVICE_DEAD, SERVICE_DEAD_RESOURCES_PINNED)) @@ -4910,11 +4919,10 @@ fail: } static int service_can_clean(Unit *u, ExecCleanMask *ret) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); ExecCleanMask mask = 0; int r; - assert(s); assert(ret); r = exec_context_get_clean_mask(&s->exec_context, &mask); @@ -4928,10 +4936,12 @@ static int service_can_clean(Unit *u, ExecCleanMask *ret) { return 0; } -static const char *service_finished_job(Unit *u, JobType t, JobResult result) { +static const char* service_finished_job(Unit *u, JobType t, JobResult result) { + Service *s = ASSERT_PTR(SERVICE(u)); + if (t == JOB_START && result == JOB_DONE && - SERVICE(u)->type == SERVICE_ONESHOT) + s->type == SERVICE_ONESHOT) return "Finished %s."; /* Fall back to generic */ @@ -4939,11 +4949,9 @@ static const char *service_finished_job(Unit *u, JobType t, JobResult result) { } static int service_can_start(Unit *u) { - Service *s = SERVICE(u); + Service *s = ASSERT_PTR(SERVICE(u)); int r; - assert(s); - /* Make sure we don't enter a busy loop of some kind. */ r = unit_test_start_limit(u); if (r < 0) { @@ -4955,7 +4963,7 @@ static int service_can_start(Unit *u) { } static void service_release_resources(Unit *u) { - Service *s = SERVICE(ASSERT_PTR(u)); + Service *s = ASSERT_PTR(SERVICE(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 */ @@ -4978,6 +4986,52 @@ static void service_release_resources(Unit *u) { service_set_state(s, SERVICE_DEAD); } +int service_determine_exec_selinux_label(Service *s, char **ret) { + int r; + + assert(s); + assert(ret); + + if (!mac_selinux_use()) + return -ENODATA; + + /* Returns the SELinux label used for execution of the main service binary */ + + if (s->exec_context.selinux_context) + /* Prefer the explicitly configured label if there is one */ + return strdup_to(ret, s->exec_context.selinux_context); + + if (s->exec_context.root_image || + s->exec_context.n_extension_images > 0 || + !strv_isempty(s->exec_context.extension_directories)) /* We cannot chase paths through images */ + return log_unit_debug_errno(UNIT(s), SYNTHETIC_ERRNO(ENODATA), "Service with RootImage=, ExtensionImages= or ExtensionDirectories= set, cannot determine socket SELinux label before activation, ignoring."); + + ExecCommand *c = s->exec_command[SERVICE_EXEC_START]; + if (!c) + return -ENODATA; + + _cleanup_free_ char *path = NULL; + r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL); + if (r < 0) { + log_unit_debug_errno(UNIT(s), r, "Failed to resolve service binary '%s', ignoring.", c->path); + return -ENODATA; + } + + r = mac_selinux_get_create_label_from_exe(path, ret); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_unit_debug_errno(UNIT(s), r, "Reading SELinux label off binary '%s' is not supported, ignoring.", path); + return -ENODATA; + } + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_unit_debug_errno(UNIT(s), r, "Can't read SELinux label off binary '%s', due to privileges, ignoring.", path); + return -ENODATA; + } + if (r < 0) + return log_unit_debug_errno(UNIT(s), r, "Failed to read SELinux label off binary '%s': %m", path); + + return 0; +} + static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { [SERVICE_RESTART_NO] = "no", [SERVICE_RESTART_ON_SUCCESS] = "on-success", @@ -4992,7 +5046,7 @@ DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); static const char* const service_restart_mode_table[_SERVICE_RESTART_MODE_MAX] = { [SERVICE_RESTART_MODE_NORMAL] = "normal", - [SERVICE_RESTART_MODE_DIRECT] = "direct", + [SERVICE_RESTART_MODE_DIRECT] = "direct", }; DEFINE_STRING_TABLE_LOOKUP(service_restart_mode, ServiceRestartMode); @@ -5080,6 +5134,7 @@ 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), + .cgroup_runtime_offset = offsetof(Service, cgroup_runtime), .sections = "Unit\0" @@ -5110,8 +5165,7 @@ const UnitVTable service_vtable = { .clean = service_clean, .can_clean = service_can_clean, - .freeze = unit_freeze_vtable_common, - .thaw = unit_thaw_vtable_common, + .freezer_action = unit_cgroup_freezer_action, .serialize = service_serialize, .deserialize_item = service_deserialize_item, @@ -5130,6 +5184,7 @@ const UnitVTable service_vtable = { .notify_cgroup_empty = service_notify_cgroup_empty_event, .notify_cgroup_oom = service_notify_cgroup_oom_event, .notify_message = service_notify_message, + .notify_handoff_timestamp = service_handoff_timestamp, .main_pid = service_main_pid, .control_pid = service_control_pid, diff --git a/src/core/service.h b/src/core/service.h index e85302e..59598f7 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -168,6 +168,8 @@ struct Service { /* Runtime data of the execution context */ ExecRuntime *exec_runtime; + CGroupRuntime *cgroup_runtime; + PidRef main_pid, control_pid; /* if we are a socket activated service instance, store information of the connection/peer/socket */ @@ -255,6 +257,8 @@ void service_release_socket_fd(Service *s); usec_t service_restart_usec_next(Service *s); +int service_determine_exec_selinux_label(Service *s, char **ret); + const char* service_restart_to_string(ServiceRestart i) _const_; ServiceRestart service_restart_from_string(const char *s) _pure_; diff --git a/src/core/show-status.c b/src/core/show-status.c index 5b003ba..57ad4db 100644 --- a/src/core/show-status.c +++ b/src/core/show-status.c @@ -38,13 +38,13 @@ int parse_show_status(const char *v, ShowStatus *ret) { int status_vprintf(const char *status, ShowStatusFlags flags, const char *format, va_list ap) { static const char status_indent[] = " "; /* "[" STATUS "] " */ + static bool prev_ephemeral = false; static int dumb = -1; _cleanup_free_ char *s = NULL; _cleanup_close_ int fd = -EBADF; struct iovec iovec[7] = {}; int n = 0; - static bool prev_ephemeral; assert(format); @@ -75,7 +75,7 @@ int status_vprintf(const char *status, ShowStatusFlags flags, const char *format if (c <= 0) c = 80; - sl = status ? sizeof(status_indent)-1 : 0; + sl = status ? strlen(status_indent) : 0; emax = c - sl - 1; if (emax < 3) diff --git a/src/core/slice.c b/src/core/slice.c index fb4f23c..4e71976 100644 --- a/src/core/slice.c +++ b/src/core/slice.c @@ -16,8 +16,8 @@ #include "unit.h" static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { - [SLICE_DEAD] = UNIT_INACTIVE, - [SLICE_ACTIVE] = UNIT_ACTIVE + [SLICE_DEAD] = UNIT_INACTIVE, + [SLICE_ACTIVE] = UNIT_ACTIVE, }; static void slice_init(Unit *u) { @@ -27,32 +27,29 @@ static void slice_init(Unit *u) { u->ignore_on_isolate = true; } -static void slice_set_state(Slice *t, SliceState state) { +static void slice_set_state(Slice *s, SliceState state) { SliceState old_state; - assert(t); - if (t->state != state) - bus_unit_send_pending_change_signal(UNIT(t), false); + assert(s); + + if (s->state != state) + bus_unit_send_pending_change_signal(UNIT(s), false); - old_state = t->state; - t->state = state; + old_state = s->state; + s->state = state; if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - slice_state_to_string(old_state), - slice_state_to_string(state)); + log_unit_debug(UNIT(s), "Changed %s -> %s", + slice_state_to_string(old_state), slice_state_to_string(state)); - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true); + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true); } static int slice_add_parent_slice(Slice *s) { - Unit *u = UNIT(s); + Unit *u = UNIT(ASSERT_PTR(s)); _cleanup_free_ char *a = NULL; int r; - assert(s); - if (UNIT_GET_SLICE(u)) return 0; @@ -151,10 +148,9 @@ static int slice_load_system_slice(Unit *u) { } static int slice_load(Unit *u) { - Slice *s = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); int r; - assert(s); assert(u->load_state == UNIT_STUB); r = slice_load_root_slice(u); @@ -196,36 +192,35 @@ static int slice_load(Unit *u) { } static int slice_coldplug(Unit *u) { - Slice *t = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); - assert(t); - assert(t->state == SLICE_DEAD); + assert(s->state == SLICE_DEAD); - if (t->deserialized_state != t->state) - slice_set_state(t, t->deserialized_state); + if (s->deserialized_state != s->state) + slice_set_state(s, s->deserialized_state); return 0; } static void slice_dump(Unit *u, FILE *f, const char *prefix) { - Slice *t = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); - assert(t); + assert(s); assert(f); + assert(prefix); fprintf(f, "%sSlice State: %s\n", - prefix, slice_state_to_string(t->state)); + prefix, slice_state_to_string(s->state)); - cgroup_context_dump(UNIT(t), f, prefix); + cgroup_context_dump(u, f, prefix); } static int slice_start(Unit *u) { - Slice *t = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); int r; - assert(t); - assert(t->state == SLICE_DEAD); + assert(s->state == SLICE_DEAD); r = unit_acquire_invocation_id(u); if (r < 0) @@ -234,27 +229,25 @@ static int slice_start(Unit *u) { (void) unit_realize_cgroup(u); (void) unit_reset_accounting(u); - slice_set_state(t, SLICE_ACTIVE); + slice_set_state(s, SLICE_ACTIVE); return 1; } static int slice_stop(Unit *u) { - Slice *t = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); - assert(t); - assert(t->state == SLICE_ACTIVE); + assert(s->state == SLICE_ACTIVE); /* We do not need to destroy the cgroup explicitly, * unit_notify() will do that for us anyway. */ - slice_set_state(t, SLICE_DEAD); + slice_set_state(s, SLICE_DEAD); return 1; } static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { - Slice *s = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); - assert(s); assert(f); assert(fds); @@ -264,9 +257,8 @@ static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { } static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Slice *s = SLICE(u); + Slice *s = ASSERT_PTR(SLICE(u)); - assert(u); assert(key); assert(value); assert(fds); @@ -276,26 +268,26 @@ static int slice_deserialize_item(Unit *u, const char *key, const char *value, F state = slice_state_from_string(value); if (state < 0) - log_debug("Failed to parse state value %s", value); + log_unit_debug(u, "Failed to parse state: %s", value); else s->deserialized_state = state; } else - log_debug("Unknown serialization key '%s'", key); + log_unit_debug(u, "Unknown serialization key: %s", key); return 0; } static UnitActiveState slice_active_state(Unit *u) { - assert(u); + Slice *s = ASSERT_PTR(SLICE(u)); - return state_translation_table[SLICE(u)->state]; + return state_translation_table[s->state]; } static const char *slice_sub_state_to_string(Unit *u) { - assert(u); + Slice *s = ASSERT_PTR(SLICE(u)); - return slice_state_to_string(SLICE(u)->state); + return slice_state_to_string(s->state); } static int slice_make_perpetual(Manager *m, const char *name, Unit **ret) { @@ -347,46 +339,47 @@ static void slice_enumerate_perpetual(Manager *m) { (void) slice_make_perpetual(m, SPECIAL_SYSTEM_SLICE, NULL); } -static bool slice_freezer_action_supported_by_children(Unit *s) { +static bool slice_can_freeze(Unit *s) { Unit *member; assert(s); - UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) { - - if (member->type == UNIT_SLICE && - !slice_freezer_action_supported_by_children(member)) + UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) + if (!unit_can_freeze(member)) return false; - - if (!UNIT_VTABLE(member)->freeze) - return false; - } - return true; } static int slice_freezer_action(Unit *s, FreezerAction action) { + FreezerAction child_action; Unit *member; int r; assert(s); - assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); - - if (action == FREEZER_FREEZE && !slice_freezer_action_supported_by_children(s)) { + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_PARENT_FREEZE, + FREEZER_THAW, FREEZER_PARENT_THAW)); + + if (action == FREEZER_FREEZE && !slice_can_freeze(s)) { + /* We're intentionally only checking for FREEZER_FREEZE here and ignoring the + * _BY_PARENT variant. If we're being frozen by parent, that means someone has + * already checked if we can be frozen further up the call stack. No point to + * redo that work */ log_unit_warning(s, "Requested freezer operation is not supported by all children of the slice"); return 0; } - UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) { - if (!member->cgroup_realized) - continue; + if (action == FREEZER_FREEZE) + child_action = FREEZER_PARENT_FREEZE; + else if (action == FREEZER_THAW) + child_action = FREEZER_PARENT_THAW; + else + child_action = action; - if (action == FREEZER_FREEZE) - r = UNIT_VTABLE(member)->freeze(member); - else if (UNIT_VTABLE(member)->thaw) - r = UNIT_VTABLE(member)->thaw(member); + UNIT_FOREACH_DEPENDENCY(member, s, UNIT_ATOM_SLICE_OF) { + if (UNIT_VTABLE(member)->freezer_action) + r = UNIT_VTABLE(member)->freezer_action(member, child_action); else - /* Thawing is requested but no corresponding method is available, ignore. */ + /* Only thawing will reach here, since freezing checks for a method in can_freeze */ r = 0; if (r < 0) return r; @@ -395,27 +388,10 @@ static int slice_freezer_action(Unit *s, FreezerAction action) { return unit_cgroup_freezer_action(s, action); } -static int slice_freeze(Unit *s) { - assert(s); - - return slice_freezer_action(s, FREEZER_FREEZE); -} - -static int slice_thaw(Unit *s) { - assert(s); - - return slice_freezer_action(s, FREEZER_THAW); -} - -static bool slice_can_freeze(Unit *s) { - assert(s); - - return slice_freezer_action_supported_by_children(s); -} - const UnitVTable slice_vtable = { .object_size = sizeof(Slice), .cgroup_context_offset = offsetof(Slice, cgroup_context), + .cgroup_runtime_offset = offsetof(Slice, cgroup_runtime), .sections = "Unit\0" @@ -436,8 +412,7 @@ const UnitVTable slice_vtable = { .start = slice_start, .stop = slice_stop, - .freeze = slice_freeze, - .thaw = slice_thaw, + .freezer_action = slice_freezer_action, .can_freeze = slice_can_freeze, .serialize = slice_serialize, diff --git a/src/core/slice.h b/src/core/slice.h index e2f9274..004349d 100644 --- a/src/core/slice.h +++ b/src/core/slice.h @@ -11,6 +11,8 @@ struct Slice { SliceState state, deserialized_state; CGroupContext cgroup_context; + + CGroupRuntime *cgroup_runtime; }; extern const UnitVTable slice_vtable; diff --git a/src/core/socket.c b/src/core/socket.c index 9adae16..41147d4 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -53,29 +53,44 @@ struct SocketPeer { Socket *socket; union sockaddr_union peer; socklen_t peer_salen; + struct ucred peer_cred; }; static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = UNIT_INACTIVE, - [SOCKET_START_PRE] = UNIT_ACTIVATING, - [SOCKET_START_CHOWN] = UNIT_ACTIVATING, - [SOCKET_START_POST] = UNIT_ACTIVATING, - [SOCKET_LISTENING] = UNIT_ACTIVE, - [SOCKET_RUNNING] = UNIT_ACTIVE, - [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, + [SOCKET_DEAD] = UNIT_INACTIVE, + [SOCKET_START_PRE] = UNIT_ACTIVATING, + [SOCKET_START_CHOWN] = UNIT_ACTIVATING, + [SOCKET_START_POST] = UNIT_ACTIVATING, + [SOCKET_LISTENING] = UNIT_ACTIVE, + [SOCKET_RUNNING] = UNIT_ACTIVE, + [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_STOP_POST] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_FAILED] = UNIT_FAILED, - [SOCKET_CLEANING] = UNIT_MAINTENANCE, + [SOCKET_STOP_POST] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_FAILED] = UNIT_FAILED, + [SOCKET_CLEANING] = UNIT_MAINTENANCE, }; static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); static void flush_ports(Socket *s); +static bool SOCKET_STATE_WITH_PROCESS(SocketState state) { + return IN_SET(state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL, + SOCKET_CLEANING); +} + static void socket_init(Unit *u) { Socket *s = SOCKET(u); @@ -108,12 +123,7 @@ static void socket_init(Unit *u) { static void socket_unwatch_control_pid(Socket *s) { assert(s); - - if (!pidref_is_set(&s->control_pid)) - return; - - unit_unwatch_pidref(UNIT(s), &s->control_pid); - pidref_done(&s->control_pid); + unit_unwatch_pidref_done(UNIT(s), &s->control_pid); } static void socket_cleanup_fd_list(SocketPort *p) { @@ -144,11 +154,9 @@ void socket_free_ports(Socket *s) { } static void socket_done(Unit *u) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); SocketPeer *p; - assert(s); - socket_free_ports(s); while ((p = set_steal_first(s->peers_by_address))) @@ -157,6 +165,7 @@ static void socket_done(Unit *u) { s->peers_by_address = set_free(s->peers_by_address); s->exec_runtime = exec_runtime_free(s->exec_runtime); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); s->control_command = NULL; @@ -221,7 +230,7 @@ static int socket_add_mount_dependencies(Socket *s) { if (!path) continue; - r = unit_require_mounts_for(UNIT(s), path, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(UNIT(s), path, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); if (r < 0) return r; } @@ -243,6 +252,7 @@ static int socket_add_device_dependencies(Socket *s) { static int socket_add_default_dependencies(Socket *s) { int r; + assert(s); if (!UNIT(s)->default_dependencies) @@ -263,6 +273,7 @@ static int socket_add_default_dependencies(Socket *s) { static bool socket_has_exec(Socket *s) { unsigned i; + assert(s); for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) @@ -273,11 +284,9 @@ static bool socket_has_exec(Socket *s) { } static int socket_add_extras(Socket *s) { - Unit *u = UNIT(s); + Unit *u = UNIT(ASSERT_PTR(s)); int r; - assert(s); - /* Pick defaults for the trigger limit, if nothing was explicitly configured. We pick a relatively high limit * in Accept=yes mode, and a lower limit for Accept=no. Reason: in Accept=yes mode we are invoking accept() * ourselves before the trigger limit can hit, thus incoming connections are taken off the socket queue quickly @@ -406,11 +415,13 @@ static void peer_address_hash_func(const SocketPeer *s, struct siphash *state) { assert(s); if (s->peer.sa.sa_family == AF_INET) - siphash24_compress(&s->peer.in.sin_addr, sizeof(s->peer.in.sin_addr), state); + siphash24_compress_typesafe(s->peer.in.sin_addr, state); else if (s->peer.sa.sa_family == AF_INET6) - siphash24_compress(&s->peer.in6.sin6_addr, sizeof(s->peer.in6.sin6_addr), state); + siphash24_compress_typesafe(s->peer.in6.sin6_addr, state); else if (s->peer.sa.sa_family == AF_VSOCK) - siphash24_compress(&s->peer.vm.svm_cid, sizeof(s->peer.vm.svm_cid), state); + siphash24_compress_typesafe(s->peer.vm.svm_cid, state); + else if (s->peer.sa.sa_family == AF_UNIX) + siphash24_compress_typesafe(s->peer_cred.uid, state); else assert_not_reached(); } @@ -429,6 +440,8 @@ static int peer_address_compare_func(const SocketPeer *x, const SocketPeer *y) { return memcmp(&x->peer.in6.sin6_addr, &y->peer.in6.sin6_addr, sizeof(x->peer.in6.sin6_addr)); case AF_VSOCK: return CMP(x->peer.vm.svm_cid, y->peer.vm.svm_cid); + case AF_UNIX: + return CMP(x->peer_cred.uid, y->peer_cred.uid); } assert_not_reached(); } @@ -436,10 +449,9 @@ static int peer_address_compare_func(const SocketPeer *x, const SocketPeer *y) { DEFINE_PRIVATE_HASH_OPS(peer_address_hash_ops, SocketPeer, peer_address_hash_func, peer_address_compare_func); static int socket_load(Unit *u) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(u); assert(u->load_state == UNIT_STUB); r = unit_load_fragment_and_dropin(u, true); @@ -457,16 +469,22 @@ static int socket_load(Unit *u) { return socket_verify(s); } -static SocketPeer *socket_peer_new(void) { +static SocketPeer *socket_peer_dup(const SocketPeer *q) { SocketPeer *p; + assert(q); + p = new(SocketPeer, 1); if (!p) return NULL; *p = (SocketPeer) { .n_ref = 1, + .peer = q->peer, + .peer_salen = q->peer_salen, + .peer_cred = q->peer_cred, }; + return p; } @@ -483,36 +501,46 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(SocketPeer, socket_peer, socket_peer_free); int socket_acquire_peer(Socket *s, int fd, SocketPeer **ret) { _cleanup_(socket_peer_unrefp) SocketPeer *remote = NULL; - SocketPeer sa = { + SocketPeer key = { .peer_salen = sizeof(union sockaddr_union), + .peer_cred = UCRED_INVALID, }, *i; int r; - assert(fd >= 0); assert(s); + assert(fd >= 0); assert(ret); - if (getpeername(fd, &sa.peer.sa, &sa.peer_salen) < 0) + if (getpeername(fd, &key.peer.sa, &key.peer_salen) < 0) return log_unit_error_errno(UNIT(s), errno, "getpeername() failed: %m"); - if (!IN_SET(sa.peer.sa.sa_family, AF_INET, AF_INET6, AF_VSOCK)) { + switch (key.peer.sa.sa_family) { + case AF_INET: + case AF_INET6: + case AF_VSOCK: + break; + + case AF_UNIX: + r = getpeercred(fd, &key.peer_cred); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to get peer credentials of socket: %m"); + break; + + default: *ret = NULL; return 0; } - i = set_get(s->peers_by_address, &sa); + i = set_get(s->peers_by_address, &key); if (i) { *ret = socket_peer_ref(i); return 1; } - remote = socket_peer_new(); + remote = socket_peer_dup(&key); if (!remote) return log_oom(); - remote->peer = sa.peer; - remote->peer_salen = sa.peer_salen; - r = set_ensure_put(&s->peers_by_address, &peer_address_hash_ops, remote); if (r < 0) return log_unit_error_errno(UNIT(s), r, "Failed to insert peer info into hash table: %m"); @@ -540,10 +568,9 @@ static const char* listen_lookup(int family, int type) { } static void socket_dump(Unit *u, FILE *f, const char *prefix) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); const char *prefix2, *str; - assert(s); assert(f); prefix = strempty(prefix); @@ -563,6 +590,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { "%sTransparent: %s\n" "%sBroadcast: %s\n" "%sPassCredentials: %s\n" + "%sPassFileDescriptorsToExec: %s\n" "%sPassSecurity: %s\n" "%sPassPacketInfo: %s\n" "%sTCPCongestion: %s\n" @@ -583,6 +611,7 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { prefix, yes_no(s->transparent), prefix, yes_no(s->broadcast), prefix, yes_no(s->pass_cred), + prefix, yes_no(s->pass_fds_to_exec), prefix, yes_no(s->pass_sec), prefix, yes_no(s->pass_pktinfo), prefix, strna(s->tcp_congestion), @@ -776,8 +805,8 @@ static void socket_dump(Unit *u, FILE *f, const char *prefix) { if (!s->exec_command[c]) continue; - fprintf(f, "%s-> %s:\n", - prefix, socket_exec_command_to_string(c)); + fprintf(f, "%s%s %s:\n", + prefix, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), socket_exec_command_to_string(c)); exec_command_dump_list(s->exec_command[c], f, prefix2); } @@ -1274,6 +1303,9 @@ static int socket_symlink(Socket *s) { static int usbffs_write_descs(int fd, Service *s) { int r; + assert(fd >= 0); + assert(s); + if (!s->usb_function_descriptors || !s->usb_function_strings) return -EINVAL; @@ -1339,12 +1371,17 @@ clear: } int socket_load_service_unit(Socket *s, int cfd, Unit **ret) { + int r; + /* Figure out what the unit that will be used to handle the connections on the socket looks like. * * If cfd < 0, then we don't have a connection yet. In case of Accept=yes sockets, use a fake * instance name. */ + assert(s); + assert(ret); + if (UNIT_ISSET(s->service)) { *ret = UNIT_DEREF(s->service); return 0; @@ -1355,7 +1392,6 @@ int socket_load_service_unit(Socket *s, int cfd, Unit **ret) { /* Build the instance name and load the unit */ _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; - int r; r = unit_name_to_prefix(UNIT(s)->id, &prefix); if (r < 0) @@ -1385,50 +1421,26 @@ int socket_load_service_unit(Socket *s, int cfd, Unit **ret) { } static int socket_determine_selinux_label(Socket *s, char **ret) { + Unit *service; int r; assert(s); assert(ret); - Unit *service; - ExecCommand *c; - const char *exec_context; - _cleanup_free_ char *path = NULL; - - r = socket_load_service_unit(s, -1, &service); - if (r == -ENODATA) - goto no_label; + r = socket_load_service_unit(s, /* cfd= */ -EBADF, &service); + if (r == -ENODATA) { + *ret = NULL; + return 0; + } if (r < 0) return r; - exec_context = SERVICE(service)->exec_context.selinux_context; - if (exec_context) { - char *con; - - con = strdup(exec_context); - if (!con) - return -ENOMEM; - - *ret = TAKE_PTR(con); + r = service_determine_exec_selinux_label(SERVICE(service), ret); + if (r == -ENODATA) { + *ret = NULL; return 0; } - - c = SERVICE(service)->exec_command[SERVICE_EXEC_START]; - if (!c) - goto no_label; - - r = chase(c->path, SERVICE(service)->exec_context.root_directory, CHASE_PREFIX_ROOT, &path, NULL); - if (r < 0) - goto no_label; - - r = mac_selinux_get_create_label_from_exe(path, ret); - if (IN_SET(r, -EPERM, -EOPNOTSUPP)) - goto no_label; return r; - -no_label: - *ret = NULL; - return 0; } static int socket_address_listen_do( @@ -1794,6 +1806,7 @@ static int socket_check_open(Socket *s) { static void socket_set_state(Socket *s, SocketState state) { SocketState old_state; + assert(s); if (s->state != state) @@ -1802,18 +1815,7 @@ static void socket_set_state(Socket *s, SocketState state) { old_state = s->state; s->state = state; - if (!IN_SET(state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL, - SOCKET_CLEANING)) { - + if (!SOCKET_STATE_WITH_PROCESS(state)) { s->timer_event_source = sd_event_source_disable_unref(s->timer_event_source); socket_unwatch_control_pid(s); s->control_command = NULL; @@ -1841,10 +1843,9 @@ static void socket_set_state(Socket *s, SocketState state) { } static int socket_coldplug(Unit *u) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(s); assert(s->state == SOCKET_DEAD); if (s->deserialized_state == s->state) @@ -1852,17 +1853,7 @@ static int socket_coldplug(Unit *u) { if (pidref_is_set(&s->control_pid) && pidref_is_unwaited(&s->control_pid) > 0 && - IN_SET(s->deserialized_state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL, - SOCKET_CLEANING)) { + SOCKET_STATE_WITH_PROCESS(s->deserialized_state)) { r = unit_watch_pidref(UNIT(s), &s->control_pid, /* exclusive= */ false); if (r < 0) @@ -1911,11 +1902,9 @@ static int socket_coldplug(Unit *u) { } static int socket_spawn(Socket *s, ExecCommand *c, PidRef *ret_pid) { - _cleanup_(exec_params_shallow_clear) ExecParameters exec_params = EXEC_PARAMETERS_INIT( EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - pid_t pid; int r; assert(s); @@ -1934,17 +1923,33 @@ static int socket_spawn(Socket *s, ExecCommand *c, PidRef *ret_pid) { if (r < 0) return r; + /* Note that ExecStartPre= command doesn't inherit any FDs. It runs before we open listen FDs. */ + if (s->pass_fds_to_exec) { + _cleanup_strv_free_ char **fd_names = NULL; + _cleanup_free_ int *fds = NULL; + int n_fds; + + n_fds = socket_collect_fds(s, &fds); + if (n_fds < 0) + return n_fds; + + r = strv_extend_n(&fd_names, socket_fdname(s), n_fds); + if (r < 0) + return r; + + exec_params.flags |= EXEC_PASS_FDS; + exec_params.fds = TAKE_PTR(fds); + exec_params.fd_names = TAKE_PTR(fd_names); + exec_params.n_socket_fds = n_fds; + } + r = exec_spawn(UNIT(s), c, &s->exec_context, &exec_params, s->exec_runtime, &s->cgroup_context, - &pid); - if (r < 0) - return r; - - r = pidref_set_pid(&pidref, pid); + &pidref); if (r < 0) return r; @@ -2052,6 +2057,7 @@ static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); static void socket_enter_stop_post(Socket *s, SocketResult f) { int r; + assert(s); if (s->result == SOCKET_SUCCESS) @@ -2094,13 +2100,7 @@ static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { if (s->result == SOCKET_SUCCESS) s->result = f; - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state_to_kill_operation(s, state), - /* main_pid= */ NULL, - &s->control_pid, - /* main_pid_alien= */ false); + r = unit_kill_context(UNIT(s), state_to_kill_operation(s, state)); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); goto fail; @@ -2134,6 +2134,7 @@ fail: static void socket_enter_stop_pre(Socket *s, SocketResult f) { int r; + assert(s); if (s->result == SOCKET_SUCCESS) @@ -2160,6 +2161,7 @@ static void socket_enter_stop_pre(Socket *s, SocketResult f) { static void socket_enter_listening(Socket *s) { int r; + assert(s); if (!s->accept && s->flush_pending) { @@ -2179,6 +2181,7 @@ static void socket_enter_listening(Socket *s) { static void socket_enter_start_post(Socket *s) { int r; + assert(s); socket_unwatch_control_pid(s); @@ -2235,6 +2238,7 @@ fail: static void socket_enter_start_pre(Socket *s) { int r; + assert(s); socket_unwatch_control_pid(s); @@ -2278,7 +2282,6 @@ static void socket_enter_running(Socket *s, int cfd_in) { /* Note that this call takes possession of the connection fd passed. It either has to assign it * somewhere or close it. */ _cleanup_close_ int cfd = cfd_in; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2315,8 +2318,8 @@ static void socket_enter_running(Socket *s, int cfd_in) { if (!pending) { if (!UNIT_ISSET(s->service)) { - r = log_unit_warning_errno(UNIT(s), SYNTHETIC_ERRNO(ENOENT), - "Service to activate vanished, refusing activation."); + log_unit_warning(UNIT(s), + "Service to activate vanished, refusing activation."); goto fail; } @@ -2347,7 +2350,10 @@ static void socket_enter_running(Socket *s, int cfd_in) { if (r > 0 && p->n_ref > s->max_connections_per_source) { _cleanup_free_ char *t = NULL; - (void) sockaddr_pretty(&p->peer.sa, p->peer_salen, true, false, &t); + if (p->peer.sa.sa_family == AF_UNIX) + (void) asprintf(&t, "UID " UID_FMT, p->peer_cred.uid); + else + (void) sockaddr_pretty(&p->peer.sa, p->peer_salen, /* translate_ipv6= */ true, /* include_port= */ false, &t); log_unit_warning(UNIT(s), "Too many incoming connections (%u) from source %s, dropping connection.", @@ -2357,18 +2363,15 @@ static void socket_enter_running(Socket *s, int cfd_in) { } r = socket_load_service_unit(s, cfd, &service); - if (r < 0) { - if (ERRNO_IS_DISCONNECT(r)) - return; - - log_unit_warning_errno(UNIT(s), r, "Failed to load connection service unit: %m"); + if (ERRNO_IS_NEG_DISCONNECT(r)) + return; + if (r < 0 || UNIT_IS_LOAD_ERROR(service->load_state)) { + log_unit_warning_errno(UNIT(s), r < 0 ? r : service->load_error, + "Failed to load connection service unit: %m"); goto fail; } - - r = unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, service, - false, UNIT_DEPENDENCY_IMPLICIT); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to add Before=/Triggers= dependencies on connection unit: %m"); + if (service->load_state == UNIT_MASKED) { + log_unit_warning(UNIT(s), "Connection service unit is masked, refusing."); goto fail; } @@ -2383,7 +2386,10 @@ static void socket_enter_running(Socket *s, int cfd_in) { goto fail; } - TAKE_FD(cfd); /* We passed ownership of the fd to the service now. Forget it here. */ + /* We passed ownership of the fd and socket peer to the service now. */ + TAKE_FD(cfd); + TAKE_PTR(p); + s->n_connections++; r = manager_add_job(UNIT(s)->manager, JOB_START, service, JOB_REPLACE, NULL, &error, NULL); @@ -2405,13 +2411,9 @@ refuse: return; queue_error: - if (ERRNO_IS_RESOURCE(r)) - log_unit_warning(UNIT(s), "Failed to queue service startup job: %s", - bus_error_message(&error, r)); - else - log_unit_warning(UNIT(s), "Failed to queue service startup job (Maybe the service file is missing or not a %s unit?): %s", - cfd >= 0 ? "template" : "non-template", - bus_error_message(&error, r)); + log_unit_warning_errno(UNIT(s), r, "Failed to queue service startup job%s: %s", + cfd >= 0 && !ERRNO_IS_RESOURCE(r) ? " (Maybe the service is missing or is a template unit?)" : "", + bus_error_message(&error, r)); fail: socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); @@ -2444,11 +2446,9 @@ static void socket_run_next(Socket *s) { } static int socket_start(Unit *u) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(s); - /* We cannot fulfill this request right now, try again later * please! */ if (IN_SET(s->state, @@ -2496,16 +2496,15 @@ static int socket_start(Unit *u) { s->result = SOCKET_SUCCESS; exec_command_reset_status_list_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); - u->reset_accounting = true; + if (s->cgroup_runtime) + s->cgroup_runtime->reset_accounting = true; socket_enter_start_pre(s); return 1; } static int socket_stop(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); + Socket *s = ASSERT_PTR(SOCKET(u)); /* Already on it */ if (IN_SET(s->state, @@ -2540,10 +2539,9 @@ static int socket_stop(Unit *u) { } static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(u); assert(f); assert(fds); @@ -2595,10 +2593,9 @@ static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { } static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(u); assert(key); assert(value); @@ -2836,9 +2833,7 @@ static int socket_deserialize_item(Unit *u, const char *key, const char *value, } static void socket_distribute_fds(Unit *u, FDSet *fds) { - Socket *s = SOCKET(u); - - assert(u); + Socket *s = ASSERT_PTR(SOCKET(u)); LIST_FOREACH(port, p, s->ports) { int fd; @@ -2860,15 +2855,15 @@ static void socket_distribute_fds(Unit *u, FDSet *fds) { } static UnitActiveState socket_active_state(Unit *u) { - assert(u); + Socket *s = ASSERT_PTR(SOCKET(u)); - return state_translation_table[SOCKET(u)->state]; + return state_translation_table[s->state]; } static const char *socket_sub_state_to_string(Unit *u) { - assert(u); + Socket *s = ASSERT_PTR(SOCKET(u)); - return socket_state_to_string(SOCKET(u)->state); + return socket_state_to_string(s->state); } int socket_port_to_address(const SocketPort *p, char **ret) { @@ -2906,7 +2901,6 @@ int socket_port_to_address(const SocketPort *p, char **ret) { } const char* socket_port_type_to_string(SocketPort *p) { - assert(p); switch (p->type) { @@ -2968,9 +2962,7 @@ SocketType socket_port_type_from_string(const char *s) { } static bool socket_may_gc(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); + Socket *s = ASSERT_PTR(SOCKET(u)); return s->n_connections == 0; } @@ -3108,10 +3100,9 @@ fail: } static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); SocketResult f; - assert(s); assert(pid >= 0); if (pid != s->control_pid.pid) @@ -3215,9 +3206,8 @@ static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { } static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Socket *s = SOCKET(userdata); + Socket *s = ASSERT_PTR(SOCKET(userdata)); - assert(s); assert(s->timer_event_source == source); switch (s->state) { @@ -3289,12 +3279,11 @@ static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *use return 0; } -int socket_collect_fds(Socket *s, int **fds) { - size_t k = 0, n = 0; - int *rfds; +int socket_collect_fds(Socket *s, int **ret) { + size_t n = 0, k = 0; assert(s); - assert(fds); + assert(ret); /* Called from the service code for requesting our fds */ @@ -3304,25 +3293,25 @@ int socket_collect_fds(Socket *s, int **fds) { n += p->n_auxiliary_fds; } - if (n <= 0) { - *fds = NULL; + if (n == 0) { + *ret = NULL; return 0; } - rfds = new(int, n); - if (!rfds) + int *fds = new(int, n); + if (!fds) return -ENOMEM; LIST_FOREACH(port, p, s->ports) { if (p->fd >= 0) - rfds[k++] = p->fd; - for (size_t i = 0; i < p->n_auxiliary_fds; ++i) - rfds[k++] = p->auxiliary_fds[i]; + fds[k++] = p->fd; + FOREACH_ARRAY(i, p->auxiliary_fds, p->n_auxiliary_fds) + fds[k++] = *i; } assert(k == n); - *fds = rfds; + *ret = fds; return (int) n; } @@ -3353,9 +3342,8 @@ void socket_connection_unref(Socket *s) { } static void socket_trigger_notify(Unit *u, Unit *other) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); - assert(u); assert(other); /* Filter out invocations with bogus state */ @@ -3390,8 +3378,24 @@ static void socket_trigger_notify(Unit *u, Unit *other) { socket_set_state(s, SOCKET_RUNNING); } +static void socket_handoff_timestamp( + Unit *u, + const struct ucred *ucred, + const dual_timestamp *ts) { + + Socket *s = ASSERT_PTR(SOCKET(u)); + + assert(ucred); + assert(ts); + + if (s->control_pid.pid == ucred->pid && s->control_command) { + exec_status_handoff(&s->control_command->exec_status, ucred, ts); + unit_add_to_dbus_queue(u); + } +} + static int socket_get_timeout(Unit *u, usec_t *timeout) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); usec_t t; int r; @@ -3423,11 +3427,10 @@ static PidRef *socket_control_pid(Unit *u) { } static int socket_clean(Unit *u, ExecCleanMask mask) { + Socket *s = ASSERT_PTR(SOCKET(u)); _cleanup_strv_free_ char **l = NULL; - Socket *s = SOCKET(u); int r; - assert(s); assert(mask != 0); if (s->state != SOCKET_DEAD) @@ -3467,19 +3470,15 @@ fail: } static int socket_can_clean(Unit *u, ExecCleanMask *ret) { - Socket *s = SOCKET(u); - - assert(s); + Socket *s = ASSERT_PTR(SOCKET(u)); return exec_context_get_clean_mask(&s->exec_context, ret); } static int socket_can_start(Unit *u) { - Socket *s = SOCKET(u); + Socket *s = ASSERT_PTR(SOCKET(u)); int r; - assert(s); - r = unit_test_start_limit(u); if (r < 0) { socket_enter_dead(s, SOCKET_FAILURE_START_LIMIT_HIT); @@ -3494,7 +3493,7 @@ static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { [SOCKET_EXEC_START_CHOWN] = "ExecStartChown", [SOCKET_EXEC_START_POST] = "ExecStartPost", [SOCKET_EXEC_STOP_PRE] = "ExecStopPre", - [SOCKET_EXEC_STOP_POST] = "ExecStopPost" + [SOCKET_EXEC_STOP_POST] = "ExecStopPost", }; DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); @@ -3508,7 +3507,7 @@ static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { [SOCKET_FAILURE_CORE_DUMP] = "core-dump", [SOCKET_FAILURE_START_LIMIT_HIT] = "start-limit-hit", [SOCKET_FAILURE_TRIGGER_LIMIT_HIT] = "trigger-limit-hit", - [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" + [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit", }; DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); @@ -3552,6 +3551,7 @@ 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), + .cgroup_runtime_offset = offsetof(Socket, cgroup_runtime), .sections = "Unit\0" @@ -3596,6 +3596,8 @@ const UnitVTable socket_vtable = { .reset_failed = socket_reset_failed, + .notify_handoff_timestamp = socket_handoff_timestamp, + .control_pid = socket_control_pid, .bus_set_property = bus_socket_set_property, diff --git a/src/core/socket.h b/src/core/socket.h index 0983e8c..5e3929c 100644 --- a/src/core/socket.h +++ b/src/core/socket.h @@ -92,6 +92,7 @@ struct Socket { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + CGroupRuntime *cgroup_runtime; /* For Accept=no sockets refers to the one service we'll * activate. For Accept=yes sockets is either NULL, or filled @@ -128,6 +129,7 @@ struct Socket { bool transparent; bool broadcast; bool pass_cred; + bool pass_fds_to_exec; bool pass_sec; bool pass_pktinfo; SocketTimestamping timestamping; @@ -170,7 +172,7 @@ int socket_acquire_peer(Socket *s, int fd, SocketPeer **p); DEFINE_TRIVIAL_CLEANUP_FUNC(SocketPeer*, socket_peer_unref); /* Called from the service code when collecting fds */ -int socket_collect_fds(Socket *s, int **fds); +int socket_collect_fds(Socket *s, int **ret); /* Called from the service code when a per-connection service ended */ void socket_connection_unref(Socket *s); diff --git a/src/core/swap.c b/src/core/swap.c index 682c2b9..c4d2ba8 100644 --- a/src/core/swap.c +++ b/src/core/swap.c @@ -30,15 +30,15 @@ #include "virt.h" static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = UNIT_INACTIVE, - [SWAP_ACTIVATING] = UNIT_ACTIVATING, - [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, - [SWAP_ACTIVE] = UNIT_ACTIVE, - [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, + [SWAP_DEAD] = UNIT_INACTIVE, + [SWAP_ACTIVATING] = UNIT_ACTIVATING, + [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, + [SWAP_ACTIVE] = UNIT_ACTIVE, + [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, - [SWAP_FAILED] = UNIT_FAILED, - [SWAP_CLEANING] = UNIT_MAINTENANCE, + [SWAP_FAILED] = UNIT_FAILED, + [SWAP_CLEANING] = UNIT_MAINTENANCE, }; static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); @@ -68,9 +68,7 @@ static const char *swap_sub_state_to_string(Unit *u) { } static bool swap_may_gc(Unit *u) { - Swap *s = SWAP(u); - - assert(s); + Swap *s = ASSERT_PTR(SWAP(u)); if (s->from_proc_swaps) return false; @@ -134,10 +132,9 @@ static int swap_set_devnode(Swap *s, const char *devnode) { } static void swap_init(Unit *u) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); - assert(s); - assert(UNIT(s)->load_state == UNIT_STUB); + assert(u->load_state == UNIT_STUB); s->timeout_usec = u->manager->defaults.timeout_start_usec; @@ -152,18 +149,11 @@ static void swap_init(Unit *u) { static void swap_unwatch_control_pid(Swap *s) { assert(s); - - if (!pidref_is_set(&s->control_pid)) - return; - - unit_unwatch_pidref(UNIT(s), &s->control_pid); - pidref_done(&s->control_pid); + unit_unwatch_pidref_done(UNIT(s), &s->control_pid); } static void swap_done(Unit *u) { - Swap *s = SWAP(u); - - assert(s); + Swap *s = ASSERT_PTR(SWAP(u)); swap_unset_proc_swaps(s); swap_set_devnode(s, NULL); @@ -173,6 +163,7 @@ static void swap_done(Unit *u) { s->parameters_fragment.options = mfree(s->parameters_fragment.options); s->exec_runtime = exec_runtime_free(s->exec_runtime); + exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); s->control_command = NULL; @@ -255,6 +246,7 @@ static int swap_verify(Swap *s) { _cleanup_free_ char *e = NULL; int r; + assert(s); assert(UNIT(s)->load_state == UNIT_LOADED); r = unit_name_from_path(s->what, ".swap", &e); @@ -321,7 +313,7 @@ static int swap_add_extras(Swap *s) { return r; } - r = unit_require_mounts_for(UNIT(s), s->what, UNIT_DEPENDENCY_IMPLICIT); + r = unit_add_mounts_for(UNIT(s), s->what, UNIT_DEPENDENCY_IMPLICIT, UNIT_MOUNT_REQUIRES); if (r < 0) return r; @@ -353,25 +345,22 @@ static int swap_add_extras(Swap *s) { } static int swap_load(Unit *u) { - Swap *s = SWAP(u); - int r, q = 0; + Swap *s = ASSERT_PTR(SWAP(u)); + int r; - assert(s); assert(u->load_state == UNIT_STUB); /* Load a .swap file */ - bool fragment_optional = s->from_proc_swaps; - r = unit_load_fragment_and_dropin(u, !fragment_optional); + r = unit_load_fragment_and_dropin(u, /* fragment_required = */ !s->from_proc_swaps); /* Add in some extras, and do so either when we successfully loaded something or when /proc/swaps is * already active. */ if (u->load_state == UNIT_LOADED || s->from_proc_swaps) - q = swap_add_extras(s); + RET_GATHER(r, swap_add_extras(s)); if (r < 0) return r; - if (q < 0) - return q; + if (u->load_state != UNIT_LOADED) return 0; @@ -385,11 +374,11 @@ static int swap_setup_unit( int priority, bool set_flags) { + _cleanup_(unit_freep) Unit *new_unit = NULL; _cleanup_free_ char *e = NULL; - bool delete = false; - Unit *u = NULL; + Unit *u; + Swap *s; int r; - SwapParameters *p; assert(m); assert(what); @@ -397,70 +386,61 @@ static int swap_setup_unit( r = unit_name_from_path(what, ".swap", &e); if (r < 0) - return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m"); + return log_error_errno(r, "Failed to generate unit name from path: %m"); u = manager_get_unit(m, e); - if (u && - SWAP(u)->from_proc_swaps && - !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), - "Swap %s appeared twice with different device paths %s and %s", - e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps); - - if (!u) { - delete = true; + if (u) { + s = ASSERT_PTR(SWAP(u)); + + if (s->from_proc_swaps && + !path_equal(s->parameters_proc_swaps.what, what_proc_swaps)) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(EEXIST), + "Swap appeared twice with different device paths %s and %s, refusing.", + s->parameters_proc_swaps.what, what_proc_swaps); + } else { + r = unit_new_for_name(m, sizeof(Swap), e, &new_unit); + if (r < 0) + return log_warning_errno(r, "Failed to load swap unit '%s': %m", e); - r = unit_new_for_name(m, sizeof(Swap), e, &u); - if (r < 0) { - log_unit_warning_errno(u, r, "Failed to load swap unit: %m"); - goto fail; - } + u = new_unit; + s = ASSERT_PTR(SWAP(u)); - SWAP(u)->what = strdup(what); - if (!SWAP(u)->what) { - r = log_oom(); - goto fail; - } + s->what = strdup(what); + if (!s->what) + return log_oom(); unit_add_to_load_queue(u); - } else - delete = false; + } - p = &SWAP(u)->parameters_proc_swaps; + SwapParameters *p = &s->parameters_proc_swaps; if (!p->what) { p->what = strdup(what_proc_swaps); - if (!p->what) { - r = log_oom(); - goto fail; - } + if (!p->what) + return log_oom(); } - /* The unit is definitely around now, mark it as loaded if it was previously referenced but could not be - * loaded. After all we can load it now, from the data in /proc/swaps. */ - if (IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR)) { + /* The unit is definitely around now, mark it as loaded if it was previously referenced but + * could not be loaded. After all we can load it now, from the data in /proc/swaps. */ + if (UNIT_IS_LOAD_ERROR(u->load_state)) { u->load_state = UNIT_LOADED; u->load_error = 0; } if (set_flags) { - SWAP(u)->is_active = true; - SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; + s->is_active = true; + s->just_activated = !s->from_proc_swaps; } - SWAP(u)->from_proc_swaps = true; + s->from_proc_swaps = true; p->priority = priority; p->priority_set = true; unit_add_to_dbus_queue(u); - return 0; + TAKE_PTR(new_unit); -fail: - if (delete) - unit_free(u); - - return r; + return 0; } static void swap_process_new(Manager *m, const char *device, int prio, bool set_flags) { @@ -541,11 +521,10 @@ static void swap_set_state(Swap *s, SwapState state) { } static int swap_coldplug(Unit *u) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); SwapState new_state = SWAP_DEAD; int r; - assert(s); assert(s->state == SWAP_DEAD); if (s->deserialized_state != s->state) @@ -569,20 +548,25 @@ static int swap_coldplug(Unit *u) { return r; } - if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) + if (!IN_SET(new_state, SWAP_DEAD, SWAP_FAILED)) { (void) unit_setup_exec_runtime(u); + (void) unit_setup_cgroup_runtime(u); + } swap_set_state(s, new_state); return 0; } static void swap_dump(Unit *u, FILE *f, const char *prefix) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); SwapParameters *p; + const char *prefix2; - assert(s); assert(f); + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + if (s->from_proc_swaps) p = &s->parameters_proc_swaps; else if (s->from_fragment) @@ -628,14 +612,23 @@ static void swap_dump(Unit *u, FILE *f, const char *prefix) { exec_context_dump(&s->exec_context, f, prefix); kill_context_dump(&s->kill_context, f, prefix); cgroup_context_dump(UNIT(s), f, prefix); + + for (SwapExecCommand c = 0; c < _SWAP_EXEC_COMMAND_MAX; c++) { + if (!s->exec_command[c].argv) + continue; + + fprintf(f, "%s%s %s:\n", + prefix, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), swap_exec_command_to_string(c)); + + exec_command_dump(s->exec_command + c, f, prefix2); + } + } static int swap_spawn(Swap *s, ExecCommand *c, PidRef *ret_pid) { - _cleanup_(exec_params_shallow_clear) ExecParameters exec_params = EXEC_PARAMETERS_INIT( EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN); _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - pid_t pid; int r; assert(s); @@ -660,11 +653,7 @@ static int swap_spawn(Swap *s, ExecCommand *c, PidRef *ret_pid) { &exec_params, s->exec_runtime, &s->cgroup_context, - &pid); - if (r < 0) - return r; - - r = pidref_set_pid(&pidref, pid); + &pidref); if (r < 0) return r; @@ -734,13 +723,7 @@ static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { if (s->result == SWAP_SUCCESS) s->result = f; - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state_to_kill_operation(s, state), - /* main_pid= */ NULL, - &s->control_pid, - /* main_pid_alien= */ false); + r = unit_kill_context(UNIT(s), state_to_kill_operation(s, state)); if (r < 0) { log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); goto fail; @@ -870,7 +853,9 @@ static void swap_cycle_clear(Swap *s) { s->result = SWAP_SUCCESS; exec_command_reset_status_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); - UNIT(s)->reset_accounting = true; + + if (s->cgroup_runtime) + s->cgroup_runtime->reset_accounting = true; } static int swap_start(Unit *u) { @@ -913,9 +898,7 @@ static int swap_start(Unit *u) { } static int swap_stop(Unit *u) { - Swap *s = SWAP(u); - - assert(s); + Swap *s = ASSERT_PTR(SWAP(u)); switch (s->state) { @@ -949,9 +932,8 @@ static int swap_stop(Unit *u) { } static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); - assert(s); assert(f); assert(fds); @@ -966,9 +948,8 @@ static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { } static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); - assert(s); assert(fds); if (streq(key, "state")) { @@ -1009,10 +990,9 @@ static int swap_deserialize_item(Unit *u, const char *key, const char *value, FD } static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); SwapResult f; - assert(s); assert(pid >= 0); if (pid != s->control_pid.pid) @@ -1086,9 +1066,8 @@ static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { } static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Swap *s = SWAP(userdata); + Swap *s = ASSERT_PTR(SWAP(userdata)); - assert(s); assert(s->timer_event_source == source); switch (s->state) { @@ -1261,12 +1240,10 @@ static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, v return swap_process_proc_swaps(m); } -static Unit *swap_following(Unit *u) { - Swap *s = SWAP(u); +static Unit* swap_following(Unit *u) { + Swap *s = ASSERT_PTR(SWAP(u)); Swap *first = NULL; - assert(s); - /* If the user configured the swap through /etc/fstab or * a device unit, follow that. */ @@ -1298,16 +1275,15 @@ static Unit *swap_following(Unit *u) { return UNIT(first); } -static int swap_following_set(Unit *u, Set **_set) { - Swap *s = SWAP(u); +static int swap_following_set(Unit *u, Set **ret) { + Swap *s = ASSERT_PTR(SWAP(u)); _cleanup_set_free_ Set *set = NULL; int r; - assert(s); - assert(_set); + assert(ret); if (LIST_JUST_US(same_devnode, s)) { - *_set = NULL; + *ret = NULL; return 0; } @@ -1321,7 +1297,7 @@ static int swap_following_set(Unit *u, Set **_set) { return r; } - *_set = TAKE_PTR(set); + *ret = TAKE_PTR(set); return 1; } @@ -1358,7 +1334,7 @@ static void swap_enumerate(Manager *m) { /* Dispatch this before we dispatch SIGCHLD, so that * we always get the events from /proc/swaps before * the SIGCHLD of /sbin/swapon. */ - r = sd_event_source_set_priority(m->swap_event_source, SD_EVENT_PRIORITY_NORMAL-10); + r = sd_event_source_set_priority(m->swap_event_source, EVENT_PRIORITY_SWAP_TABLE); if (r < 0) { log_error_errno(r, "Failed to change /proc/swaps priority: %m"); goto fail; @@ -1422,28 +1398,22 @@ int swap_process_device_new(Manager *m, sd_device *dev) { int swap_process_device_remove(Manager *m, sd_device *dev) { const char *dn; - int r; Swap *s; + int r; r = sd_device_get_devname(dev, &dn); if (r < 0) return 0; - while ((s = hashmap_get(m->swaps_by_devnode, dn))) { - int q; - - q = swap_set_devnode(s, NULL); - if (q < 0) - r = q; - } + r = 0; + while ((s = hashmap_get(m->swaps_by_devnode, dn))) + RET_GATHER(r, swap_set_devnode(s, NULL)); return r; } static void swap_reset_failed(Unit *u) { - Swap *s = SWAP(u); - - assert(s); + Swap *s = ASSERT_PTR(SWAP(u)); if (s->state == SWAP_FAILED) swap_set_state(s, SWAP_DEAD); @@ -1452,14 +1422,27 @@ static void swap_reset_failed(Unit *u) { s->clean_result = SWAP_SUCCESS; } +static void swap_handoff_timestamp( + Unit *u, + const struct ucred *ucred, + const dual_timestamp *ts) { + + Swap *s = ASSERT_PTR(SWAP(u)); + + assert(ucred); + assert(ts); + + if (s->control_pid.pid == ucred->pid && s->control_command) { + exec_status_handoff(&s->control_command->exec_status, ucred, ts); + unit_add_to_dbus_queue(u); + } +} + static int swap_get_timeout(Unit *u, usec_t *timeout) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); usec_t t; int r; - assert(s); - assert(u); - if (!s->timer_event_source) return 0; @@ -1493,11 +1476,10 @@ static PidRef* swap_control_pid(Unit *u) { } static int swap_clean(Unit *u, ExecCleanMask mask) { + Swap *s = ASSERT_PTR(SWAP(u)); _cleanup_strv_free_ char **l = NULL; - Swap *s = SWAP(u); int r; - assert(s); assert(mask != 0); if (s->state != SWAP_DEAD) @@ -1537,19 +1519,15 @@ fail: } static int swap_can_clean(Unit *u, ExecCleanMask *ret) { - Swap *s = SWAP(u); - - assert(s); + Swap *s = ASSERT_PTR(SWAP(u)); return exec_context_get_clean_mask(&s->exec_context, ret); } static int swap_can_start(Unit *u) { - Swap *s = SWAP(u); + Swap *s = ASSERT_PTR(SWAP(u)); int r; - assert(s); - r = unit_test_start_limit(u); if (r < 0) { swap_enter_dead(s, SWAP_FAILURE_START_LIMIT_HIT); @@ -1605,6 +1583,7 @@ 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), + .cgroup_runtime_offset = offsetof(Swap, cgroup_runtime), .sections = "Unit\0" @@ -1645,6 +1624,8 @@ const UnitVTable swap_vtable = { .reset_failed = swap_reset_failed, + .notify_handoff_timestamp = swap_handoff_timestamp, + .control_pid = swap_control_pid, .bus_set_property = bus_swap_set_property, diff --git a/src/core/swap.h b/src/core/swap.h index ef20f0f..d9bbd37 100644 --- a/src/core/swap.h +++ b/src/core/swap.h @@ -70,6 +70,7 @@ struct Swap { CGroupContext cgroup_context; ExecRuntime *exec_runtime; + CGroupRuntime *cgroup_runtime; SwapState state, deserialized_state; diff --git a/src/core/system.conf.in b/src/core/system.conf.in index 05eb681..1c08aa4 100644 --- a/src/core/system.conf.in +++ b/src/core/system.conf.in @@ -26,7 +26,7 @@ #ShowStatus=yes #CrashChangeVT=no #CrashShell=no -#CrashReboot=no +#CrashAction=freeze #CtrlAltDelBurstAction=reboot-force #CPUAffinity= #NUMAPolicy=default @@ -39,6 +39,7 @@ #WatchdogDevice= #CapabilityBoundingSet= #NoNewPrivileges=no +#ProtectSystem=auto #SystemCallArchitectures= #TimerSlackNSec= #StatusUnitFormat={{STATUS_UNIT_FORMAT_DEFAULT_STR}} diff --git a/src/core/taint.c b/src/core/taint.c new file mode 100644 index 0000000..969b37f --- /dev/null +++ b/src/core/taint.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "clock-util.h" +#include "errno-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "os-util.h" +#include "path-util.h" +#include "strv.h" +#include "taint.h" +#include "uid-range.h" + +static int short_uid_gid_range(UIDRangeUsernsMode mode) { + _cleanup_(uid_range_freep) UIDRange *p = NULL; + int r; + + /* Taint systemd if we the UID/GID range assigned to this environment doesn't at least cover 0…65534, + * i.e. from root to nobody. */ + + r = uid_range_load_userns(/* path= */ NULL, mode, &p); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return false; + if (r < 0) + return log_debug_errno(r, "Failed to load uid_map or gid_map: %m"); + + return !uid_range_covers(p, 0, 65535); +} + +char* taint_string(void) { + const char *stage[12] = {}; + size_t n = 0; + + /* Returns a "taint string", e.g. "local-hwclock:var-run-bad". Only things that are detected at + * runtime should be tagged here. For stuff that is known during compilation, emit a warning in the + * configuration phase. */ + + _cleanup_free_ char *bin = NULL, *usr_sbin = NULL, *var_run = NULL; + + if (readlink_malloc("/bin", &bin) < 0 || !PATH_IN_SET(bin, "usr/bin", "/usr/bin")) + stage[n++] = "unmerged-usr"; + + /* Note that the check is different from default_PATH(), as we want to taint on uncanonical symlinks + * too. */ + if (readlink_malloc("/usr/sbin", &usr_sbin) < 0 || !PATH_IN_SET(usr_sbin, "bin", "/usr/bin")) + stage[n++] = "unmerged-bin"; + + if (readlink_malloc("/var/run", &var_run) < 0 || !PATH_IN_SET(var_run, "../run", "/run")) + stage[n++] = "var-run-bad"; + + if (cg_all_unified() == 0) + stage[n++] = "cgroupsv1"; + + if (clock_is_localtime(NULL) > 0) + stage[n++] = "local-hwclock"; + + if (os_release_support_ended(NULL, /* quiet= */ true, NULL) > 0) + stage[n++] = "support-ended"; + + struct utsname uts; + assert_se(uname(&uts) >= 0); + if (strverscmp_improved(uts.release, KERNEL_BASELINE_VERSION) < 0) + stage[n++] = "old-kernel"; + + _cleanup_free_ char *overflowuid = NULL, *overflowgid = NULL; + if (read_one_line_file("/proc/sys/kernel/overflowuid", &overflowuid) >= 0 && + !streq(overflowuid, "65534")) + stage[n++] = "overflowuid-not-65534"; + if (read_one_line_file("/proc/sys/kernel/overflowgid", &overflowgid) >= 0 && + !streq(overflowgid, "65534")) + stage[n++] = "overflowgid-not-65534"; + + if (short_uid_gid_range(UID_RANGE_USERNS_INSIDE) > 0) + stage[n++] = "short-uid-range"; + if (short_uid_gid_range(GID_RANGE_USERNS_INSIDE) > 0) + stage[n++] = "short-gid-range"; + + assert(n < ELEMENTSOF(stage) - 1); /* One extra for NULL terminator */ + + return strv_join((char**) stage, ":"); +} diff --git a/src/core/taint.h b/src/core/taint.h new file mode 100644 index 0000000..2e514e3 --- /dev/null +++ b/src/core/taint.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +char* taint_string(void); diff --git a/src/core/target.c b/src/core/target.c index 8f2a331..15866e9 100644 --- a/src/core/target.c +++ b/src/core/target.c @@ -11,12 +11,13 @@ #include "unit.h" static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = UNIT_INACTIVE, - [TARGET_ACTIVE] = UNIT_ACTIVE + [TARGET_DEAD] = UNIT_INACTIVE, + [TARGET_ACTIVE] = UNIT_ACTIVE, }; static void target_set_state(Target *t, TargetState state) { TargetState old_state; + assert(t); if (t->state != state) @@ -26,10 +27,8 @@ static void target_set_state(Target *t, TargetState state) { t->state = state; if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - target_state_to_string(old_state), - target_state_to_string(state)); + log_unit_debug(UNIT(t), "Changed %s -> %s", + target_state_to_string(old_state), target_state_to_string(state)); unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], /* reload_success = */ true); } @@ -56,8 +55,8 @@ static int target_add_default_dependencies(Target *t) { if (n_others < 0) return n_others; - for (int i = 0; i < n_others; i++) { - r = unit_add_default_target_dependency(others[i], UNIT(t)); + FOREACH_ARRAY(i, others, n_others) { + r = unit_add_default_target_dependency(*i, UNIT(t)); if (r < 0) return r; } @@ -70,11 +69,9 @@ static int target_add_default_dependencies(Target *t) { } static int target_load(Unit *u) { - Target *t = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); int r; - assert(t); - r = unit_load_fragment_and_dropin(u, true); if (r < 0) return r; @@ -87,9 +84,8 @@ static int target_load(Unit *u) { } static int target_coldplug(Unit *u) { - Target *t = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); - assert(t); assert(t->state == TARGET_DEAD); if (t->deserialized_state != t->state) @@ -99,10 +95,10 @@ static int target_coldplug(Unit *u) { } static void target_dump(Unit *u, FILE *f, const char *prefix) { - Target *t = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); - assert(t); assert(f); + assert(prefix); fprintf(f, "%sTarget State: %s\n", @@ -110,10 +106,9 @@ static void target_dump(Unit *u, FILE *f, const char *prefix) { } static int target_start(Unit *u) { - Target *t = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); int r; - assert(t); assert(t->state == TARGET_DEAD); r = unit_acquire_invocation_id(u); @@ -125,9 +120,8 @@ static int target_start(Unit *u) { } static int target_stop(Unit *u) { - Target *t = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); - assert(t); assert(t->state == TARGET_ACTIVE); target_set_state(t, TARGET_DEAD); @@ -135,21 +129,18 @@ static int target_stop(Unit *u) { } static int target_serialize(Unit *u, FILE *f, FDSet *fds) { - Target *s = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); - assert(s); assert(f); assert(fds); - (void) serialize_item(f, "state", target_state_to_string(s->state)); + (void) serialize_item(f, "state", target_state_to_string(t->state)); return 0; } static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Target *s = TARGET(u); + Target *t = ASSERT_PTR(TARGET(u)); - assert(s); - assert(u); assert(key); assert(value); assert(fds); @@ -159,26 +150,26 @@ static int target_deserialize_item(Unit *u, const char *key, const char *value, state = target_state_from_string(value); if (state < 0) - log_debug("Failed to parse state value %s", value); + log_unit_debug(u, "Failed to parse state: %s", value); else - s->deserialized_state = state; + t->deserialized_state = state; } else - log_debug("Unknown serialization key '%s'", key); + log_unit_debug(u, "Unknown serialization key: %s", key); return 0; } static UnitActiveState target_active_state(Unit *u) { - assert(u); + Target *t = ASSERT_PTR(TARGET(u)); - return state_translation_table[TARGET(u)->state]; + return state_translation_table[t->state]; } static const char *target_sub_state_to_string(Unit *u) { - assert(u); + Target *t = ASSERT_PTR(TARGET(u)); - return target_state_to_string(TARGET(u)->state); + return target_state_to_string(t->state); } const UnitVTable target_vtable = { @@ -213,4 +204,6 @@ const UnitVTable target_vtable = { [JOB_DONE] = "Stopped target %s.", }, }, + + .notify_supervisor = true, }; diff --git a/src/core/timer.c b/src/core/timer.c index 3c41a25..d7ce473 100644 --- a/src/core/timer.c +++ b/src/core/timer.c @@ -25,19 +25,18 @@ #include "virt.h" static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = UNIT_INACTIVE, + [TIMER_DEAD] = UNIT_INACTIVE, [TIMER_WAITING] = UNIT_ACTIVE, [TIMER_RUNNING] = UNIT_ACTIVE, [TIMER_ELAPSED] = UNIT_ACTIVE, - [TIMER_FAILED] = UNIT_FAILED + [TIMER_FAILED] = UNIT_FAILED, }; static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); static void timer_init(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(u); assert(u->load_state == UNIT_STUB); t->next_elapse_monotonic_or_boottime = USEC_INFINITY; @@ -58,9 +57,7 @@ void timer_free_values(Timer *t) { } static void timer_done(Unit *u) { - Timer *t = TIMER(u); - - assert(t); + Timer *t = ASSERT_PTR(TIMER(u)); timer_free_values(t); @@ -141,7 +138,7 @@ static int timer_setup_persistent(Timer *t) { if (MANAGER_IS_SYSTEM(UNIT(t)->manager)) { - r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers", UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(UNIT(t), "/var/lib/systemd/timers", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); if (r < 0) return r; @@ -192,19 +189,18 @@ static uint64_t timer_get_fixed_delay_hash(Timer *t) { } siphash24_init(&state, hash_key); - siphash24_compress(&machine_id, sizeof(sd_id128_t), &state); + siphash24_compress_typesafe(machine_id, &state); siphash24_compress_boolean(MANAGER_IS_SYSTEM(UNIT(t)->manager), &state); - siphash24_compress(&uid, sizeof(uid_t), &state); + siphash24_compress_typesafe(uid, &state); siphash24_compress_string(UNIT(t)->id, &state); return siphash24_finalize(&state); } static int timer_load(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); int r; - assert(u); assert(u->load_state == UNIT_STUB); r = unit_load_fragment_and_dropin(u, true); @@ -231,9 +227,12 @@ static int timer_load(Unit *u) { } static void timer_dump(Unit *u, FILE *f, const char *prefix) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); Unit *trigger; + assert(f); + assert(prefix); + trigger = UNIT_TRIGGER(u); fprintf(f, @@ -279,6 +278,7 @@ static void timer_dump(Unit *u, FILE *f, const char *prefix) { static void timer_set_state(Timer *t, TimerState state) { TimerState old_state; + assert(t); if (t->state != state) @@ -303,9 +303,8 @@ static void timer_set_state(Timer *t, TimerState state) { static void timer_enter_waiting(Timer *t, bool time_change); static int timer_coldplug(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(t); assert(t->state == TIMER_DEAD); if (t->deserialized_state == t->state) @@ -634,10 +633,9 @@ fail: } static int timer_start(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); int r; - assert(t); assert(IN_SET(t->state, TIMER_DEAD, TIMER_FAILED)); r = unit_test_trigger_loaded(u); @@ -682,9 +680,8 @@ static int timer_start(Unit *u) { } static int timer_stop(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(t); assert(IN_SET(t->state, TIMER_WAITING, TIMER_RUNNING, TIMER_ELAPSED)); timer_enter_dead(t, TIMER_SUCCESS); @@ -692,9 +689,8 @@ static int timer_stop(Unit *u) { } static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(u); assert(f); assert(fds); @@ -711,9 +707,8 @@ static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { } static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(u); assert(key); assert(value); assert(fds); @@ -747,21 +742,19 @@ static int timer_deserialize_item(Unit *u, const char *key, const char *value, F } static UnitActiveState timer_active_state(Unit *u) { - assert(u); + Timer *t = ASSERT_PTR(TIMER(u)); - return state_translation_table[TIMER(u)->state]; + return state_translation_table[t->state]; } static const char *timer_sub_state_to_string(Unit *u) { - assert(u); + Timer *t = ASSERT_PTR(TIMER(u)); - return timer_state_to_string(TIMER(u)->state); + return timer_state_to_string(t->state); } static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { - Timer *t = TIMER(userdata); - - assert(t); + Timer *t = ASSERT_PTR(TIMER(userdata)); if (t->state != TIMER_WAITING) return 0; @@ -772,9 +765,8 @@ static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { } static void timer_trigger_notify(Unit *u, Unit *other) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(u); assert(other); /* Filter out invocations with bogus state */ @@ -812,9 +804,7 @@ static void timer_trigger_notify(Unit *u, Unit *other) { } static void timer_reset_failed(Unit *u) { - Timer *t = TIMER(u); - - assert(t); + Timer *t = ASSERT_PTR(TIMER(u)); if (t->state == TIMER_FAILED) timer_set_state(t, TIMER_DEAD); @@ -823,11 +813,9 @@ static void timer_reset_failed(Unit *u) { } static void timer_time_change(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); usec_t ts; - assert(u); - if (t->state != TIMER_WAITING) return; @@ -849,9 +837,7 @@ static void timer_time_change(Unit *u) { } static void timer_timezone_change(Unit *u) { - Timer *t = TIMER(u); - - assert(u); + Timer *t = ASSERT_PTR(TIMER(u)); if (t->state != TIMER_WAITING) return; @@ -866,10 +852,9 @@ static void timer_timezone_change(Unit *u) { } static int timer_clean(Unit *u, ExecCleanMask mask) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); int r; - assert(t); assert(mask != 0); if (t->state != TIMER_DEAD) @@ -892,9 +877,8 @@ static int timer_clean(Unit *u, ExecCleanMask mask) { } static int timer_can_clean(Unit *u, ExecCleanMask *ret) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); - assert(t); assert(ret); *ret = t->persistent ? EXEC_CLEAN_STATE : 0; @@ -902,11 +886,9 @@ static int timer_can_clean(Unit *u, ExecCleanMask *ret) { } static int timer_can_start(Unit *u) { - Timer *t = TIMER(u); + Timer *t = ASSERT_PTR(TIMER(u)); int r; - assert(t); - r = unit_test_start_limit(u); if (r < 0) { timer_enter_dead(t, TIMER_FAILURE_START_LIMIT_HIT); @@ -917,9 +899,8 @@ static int timer_can_start(Unit *u) { } static void activation_details_timer_serialize(ActivationDetails *details, FILE *f) { - ActivationDetailsTimer *t = ACTIVATION_DETAILS_TIMER(details); + ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); - assert(details); assert(f); assert(t); @@ -950,10 +931,9 @@ static int activation_details_timer_deserialize(const char *key, const char *val } static int activation_details_timer_append_env(ActivationDetails *details, char ***strv) { - ActivationDetailsTimer *t = ACTIVATION_DETAILS_TIMER(details); + ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); int r; - assert(details); assert(strv); assert(t); @@ -972,10 +952,9 @@ static int activation_details_timer_append_env(ActivationDetails *details, char } static int activation_details_timer_append_pair(ActivationDetails *details, char ***strv) { - ActivationDetailsTimer *t = ACTIVATION_DETAILS_TIMER(details); + ActivationDetailsTimer *t = ASSERT_PTR(ACTIVATION_DETAILS_TIMER(details)); int r; - assert(details); assert(strv); assert(t); @@ -1014,7 +993,7 @@ static const char* const timer_base_table[_TIMER_BASE_MAX] = { [TIMER_STARTUP] = "OnStartupSec", [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", - [TIMER_CALENDAR] = "OnCalendar" + [TIMER_CALENDAR] = "OnCalendar", }; DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); diff --git a/src/core/transaction.c b/src/core/transaction.c index a81c40f..ab6e699 100644 --- a/src/core/transaction.c +++ b/src/core/transaction.c @@ -446,10 +446,10 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi * the graph over 'before' edges in the actual job execution order. We traverse over both unit * ordering dependencies and we test with job_compare() whether it is the 'before' edge in the job * execution ordering. */ - for (size_t d = 0; d < ELEMENTSOF(directions); d++) { + FOREACH_ELEMENT(d, directions) { Unit *u; - UNIT_FOREACH_DEPENDENCY(u, j->unit, directions[d]) { + UNIT_FOREACH_DEPENDENCY(u, j->unit, *d) { Job *o; /* Is there a job for this unit? */ @@ -463,7 +463,7 @@ static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsi } /* Cut traversing if the job j is not really *before* o. */ - if (job_compare(j, o, directions[d]) >= 0) + if (job_compare(j, o, *d) >= 0) continue; r = transaction_verify_order_one(tr, o, j, generation, e); @@ -964,7 +964,7 @@ int transaction_add_job_and_dependencies( if (type != JOB_STOP) { r = bus_unit_validate_load_state(unit, e); - /* The time-based cache allows to start new units without daemon-reload, but if they are + /* The time-based cache allows new units to be started without daemon-reload, but if they are * already referenced (because of dependencies or ordering) then we have to force a load of * the fragment. As an optimization, check first if anything in the usual paths was modified * since the last time the cache was loaded. Also check if the last time an attempt to load diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c index 9f95984..f25e2e3 100644 --- a/src/core/unit-printf.c +++ b/src/core/unit-printf.c @@ -4,6 +4,7 @@ #include "cgroup-util.h" #include "format-util.h" #include "macro.h" +#include "sd-path.h" #include "specifier.h" #include "string-util.h" #include "strv.h" @@ -86,68 +87,46 @@ static void bad_specifier(const Unit *u, char specifier) { static int specifier_cgroup(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const Unit *u = ASSERT_PTR(userdata); + CGroupRuntime *crt = unit_get_cgroup_runtime(u); bad_specifier(u, specifier); - if (u->cgroup_path) { - char *n; - - n = strdup(u->cgroup_path); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; - } + if (crt && crt->cgroup_path) + return strdup_to(ret, crt->cgroup_path); return unit_default_cgroup_path(u, ret); } static int specifier_cgroup_root(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const Unit *u = ASSERT_PTR(userdata); - char *n; bad_specifier(u, specifier); - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return strdup_to(ret, u->manager->cgroup_root); } static int specifier_cgroup_slice(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const Unit *u = ASSERT_PTR(userdata), *slice; - char *n; bad_specifier(u, specifier); slice = UNIT_GET_SLICE(u); if (slice) { - if (slice->cgroup_path) - n = strdup(slice->cgroup_path); - else - return unit_default_cgroup_path(slice, ret); - } else - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; + CGroupRuntime *crt = unit_get_cgroup_runtime(slice); - *ret = n; - return 0; + if (crt && crt->cgroup_path) + return strdup_to(ret, crt->cgroup_path); + + return unit_default_cgroup_path(slice, ret); + } + + return strdup_to(ret, u->manager->cgroup_root); } static int specifier_special_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const Unit *u = ASSERT_PTR(userdata); - char *n; - - n = strdup(u->manager->prefix[PTR_TO_UINT(data)]); - if (!n) - return -ENOMEM; - *ret = n; - return 0; + return strdup_to(ret, u->manager->prefix[PTR_TO_UINT(data)]); } static int specifier_credentials_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { @@ -164,6 +143,14 @@ static int specifier_credentials_dir(char specifier, const void *data, const cha return 0; } +static int specifier_shared_data_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const Unit *u = ASSERT_PTR(userdata); + + assert(ret); + + return sd_path_lookup(MANAGER_IS_SYSTEM(u->manager) ? SD_PATH_SYSTEM_SHARED : SD_PATH_USER_SHARED, NULL, ret); +} + int unit_name_printf(const Unit *u, const char* format, char **ret) { /* * This will use the passed string as format string and replace the following specifiers (which should all be @@ -208,6 +195,7 @@ int unit_full_printf_full(const Unit *u, const char *format, size_t max_length, * * %C: the cache directory root (e.g. /var/cache or $XDG_CACHE_HOME) * %d: the credentials directory ($CREDENTIALS_DIRECTORY) + * %D: the shared data root (e.g. /usr/share or $XDG_DATA_HOME) * %E: the configuration directory root (e.g. /etc or $XDG_CONFIG_HOME) * %L: the log directory root (e.g. /var/log or $XDG_STATE_HOME/log) * %S: the state directory root (e.g. /var/lib or $XDG_STATE_HOME) @@ -245,6 +233,7 @@ int unit_full_printf_full(const Unit *u, const char *format, size_t max_length, { 'C', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CACHE) }, { 'd', specifier_credentials_dir, NULL }, + { 'D', specifier_shared_data_dir, NULL }, { 'E', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_CONFIGURATION) }, { 'L', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_LOGS) }, { 'S', specifier_special_directory, UINT_TO_PTR(EXEC_DIRECTORY_STATE) }, diff --git a/src/core/unit-serialize.c b/src/core/unit-serialize.c index fe4221c..175e327 100644 --- a/src/core/unit-serialize.c +++ b/src/core/unit-serialize.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "bpf-restrict-ifaces.h" #include "bpf-socket-bind.h" #include "bus-util.h" #include "dbus.h" @@ -7,29 +8,11 @@ #include "fileio.h" #include "format-util.h" #include "parse-util.h" -#include "restrict-ifaces.h" #include "serialize.h" #include "string-table.h" #include "unit-serialize.h" #include "user-util.h" -static int serialize_cgroup_mask(FILE *f, const char *key, CGroupMask mask) { - _cleanup_free_ char *s = NULL; - int r; - - assert(f); - assert(key); - - if (mask == 0) - return 0; - - r = cg_mask_to_string(mask, &s); - if (r < 0) - return log_error_errno(r, "Failed to format cgroup mask: %m"); - - return serialize_item(f, key, s); -} - /* Make sure out values fit in the bitfield. */ assert_cc(_UNIT_MARKER_MAX <= sizeof(((Unit){}).markers) * 8); @@ -69,40 +52,6 @@ static int deserialize_markers(Unit *u, const char *value) { } } -static const char* const ip_accounting_metric_field_table[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { - [CGROUP_IP_INGRESS_BYTES] = "ip-accounting-ingress-bytes", - [CGROUP_IP_INGRESS_PACKETS] = "ip-accounting-ingress-packets", - [CGROUP_IP_EGRESS_BYTES] = "ip-accounting-egress-bytes", - [CGROUP_IP_EGRESS_PACKETS] = "ip-accounting-egress-packets", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(ip_accounting_metric_field, CGroupIPAccountingMetric); - -static const char* const io_accounting_metric_field_base_table[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { - [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-base", - [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-base", - [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-base", - [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-base", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(io_accounting_metric_field_base, CGroupIOAccountingMetric); - -static const char* const io_accounting_metric_field_last_table[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { - [CGROUP_IO_READ_BYTES] = "io-accounting-read-bytes-last", - [CGROUP_IO_WRITE_BYTES] = "io-accounting-write-bytes-last", - [CGROUP_IO_READ_OPERATIONS] = "io-accounting-read-operations-last", - [CGROUP_IO_WRITE_OPERATIONS] = "io-accounting-write-operations-last", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(io_accounting_metric_field_last, CGroupIOAccountingMetric); - -static const char* const memory_accounting_metric_field_last_table[_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1] = { - [CGROUP_MEMORY_PEAK] = "memory-accounting-peak", - [CGROUP_MEMORY_SWAP_PEAK] = "memory-accounting-swap-peak", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(memory_accounting_metric_field_last, CGroupMemoryAccountingMetric); - int unit_serialize_state(Unit *u, FILE *f, FDSet *fds, bool switching_root) { int r; @@ -158,48 +107,7 @@ int unit_serialize_state(Unit *u, FILE *f, FDSet *fds, bool switching_root) { (void) serialize_bool(f, "exported-log-rate-limit-interval", u->exported_log_ratelimit_interval); (void) serialize_bool(f, "exported-log-rate-limit-burst", u->exported_log_ratelimit_burst); - (void) serialize_item_format(f, "cpu-usage-base", "%" PRIu64, u->cpu_usage_base); - if (u->cpu_usage_last != NSEC_INFINITY) - (void) serialize_item_format(f, "cpu-usage-last", "%" PRIu64, u->cpu_usage_last); - - if (u->managed_oom_kill_last > 0) - (void) serialize_item_format(f, "managed-oom-kill-last", "%" PRIu64, u->managed_oom_kill_last); - - if (u->oom_kill_last > 0) - (void) serialize_item_format(f, "oom-kill-last", "%" PRIu64, u->oom_kill_last); - - for (CGroupIOAccountingMetric im = 0; im < _CGROUP_IO_ACCOUNTING_METRIC_MAX; im++) { - (void) serialize_item_format(f, io_accounting_metric_field_base_to_string(im), "%" PRIu64, u->io_accounting_base[im]); - - if (u->io_accounting_last[im] != UINT64_MAX) - (void) serialize_item_format(f, io_accounting_metric_field_last_to_string(im), "%" PRIu64, u->io_accounting_last[im]); - } - - for (CGroupMemoryAccountingMetric metric = 0; metric <= _CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST; metric++) { - uint64_t v; - - r = unit_get_memory_accounting(u, metric, &v); - if (r >= 0) - (void) serialize_item_format(f, memory_accounting_metric_field_last_to_string(metric), "%" PRIu64, v); - } - - if (u->cgroup_path) - (void) serialize_item(f, "cgroup", u->cgroup_path); - - (void) serialize_bool(f, "cgroup-realized", u->cgroup_realized); - (void) serialize_cgroup_mask(f, "cgroup-realized-mask", u->cgroup_realized_mask); - (void) serialize_cgroup_mask(f, "cgroup-enabled-mask", u->cgroup_enabled_mask); - (void) serialize_cgroup_mask(f, "cgroup-invalidated-mask", u->cgroup_invalidated_mask); - - (void) bpf_serialize_socket_bind(u, f, fds); - - (void) bpf_program_serialize_attachment(f, fds, "ip-bpf-ingress-installed", u->ip_bpf_ingress_installed); - (void) bpf_program_serialize_attachment(f, fds, "ip-bpf-egress-installed", u->ip_bpf_egress_installed); - (void) bpf_program_serialize_attachment(f, fds, "bpf-device-control-installed", u->bpf_device_control_installed); - (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-ingress-installed", u->ip_bpf_custom_ingress_installed); - (void) bpf_program_serialize_attachment_set(f, fds, "ip-bpf-custom-egress-installed", u->ip_bpf_custom_egress_installed); - - (void) serialize_restrict_network_interfaces(u, f, fds); + (void) cgroup_runtime_serialize(u, f, fds); if (uid_is_valid(u->ref_uid)) (void) serialize_item_format(f, "ref-uid", UID_FMT, u->ref_uid); @@ -214,14 +122,6 @@ int unit_serialize_state(Unit *u, FILE *f, FDSet *fds, bool switching_root) { bus_track_serialize(u->bus_track, f, "ref"); - for (CGroupIPAccountingMetric m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { - uint64_t v; - - r = unit_get_ip_accounting(u, m, &v); - if (r >= 0) - (void) serialize_item_format(f, ip_accounting_metric_field_to_string(m), "%" PRIu64, v); - } - if (!switching_root) { if (u->job) { fputs("job\n", f); @@ -297,7 +197,6 @@ int unit_deserialize_state(Unit *u, FILE *f, FDSet *fds) { for (;;) { _cleanup_free_ char *l = NULL; - ssize_t m; size_t k; char *v; @@ -380,76 +279,7 @@ int unit_deserialize_state(Unit *u, FILE *f, FDSet *fds) { else if (MATCH_DESERIALIZE("exported-log-rate-limit-burst", l, v, parse_boolean, u->exported_log_ratelimit_burst)) continue; - else if (MATCH_DESERIALIZE_IMMEDIATE("cpu-usage-base", l, v, safe_atou64, u->cpu_usage_base) || - MATCH_DESERIALIZE_IMMEDIATE("cpuacct-usage-base", l, v, safe_atou64, u->cpu_usage_base)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("cpu-usage-last", l, v, safe_atou64, u->cpu_usage_last)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("managed-oom-kill-last", l, v, safe_atou64, u->managed_oom_kill_last)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("oom-kill-last", l, v, safe_atou64, u->oom_kill_last)) - continue; - - else if (streq(l, "cgroup")) { - r = unit_set_cgroup_path(u, v); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); - - (void) unit_watch_cgroup(u); - (void) unit_watch_cgroup_memory(u); - - continue; - - } else if (MATCH_DESERIALIZE("cgroup-realized", l, v, parse_boolean, u->cgroup_realized)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("cgroup-realized-mask", l, v, cg_mask_from_string, u->cgroup_realized_mask)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("cgroup-enabled-mask", l, v, cg_mask_from_string, u->cgroup_enabled_mask)) - continue; - - else if (MATCH_DESERIALIZE_IMMEDIATE("cgroup-invalidated-mask", l, v, cg_mask_from_string, u->cgroup_invalidated_mask)) - continue; - - else if (STR_IN_SET(l, "ipv4-socket-bind-bpf-link-fd", "ipv6-socket-bind-bpf-link-fd")) { - int fd; - - fd = deserialize_fd(fds, v); - if (fd >= 0) - (void) bpf_socket_bind_add_initial_link_fd(u, fd); - continue; - - } else if (streq(l, "ip-bpf-ingress-installed")) { - (void) bpf_program_deserialize_attachment(v, fds, &u->ip_bpf_ingress_installed); - continue; - } else if (streq(l, "ip-bpf-egress-installed")) { - (void) bpf_program_deserialize_attachment(v, fds, &u->ip_bpf_egress_installed); - continue; - } else if (streq(l, "bpf-device-control-installed")) { - (void) bpf_program_deserialize_attachment(v, fds, &u->bpf_device_control_installed); - continue; - - } else if (streq(l, "ip-bpf-custom-ingress-installed")) { - (void) bpf_program_deserialize_attachment_set(v, fds, &u->ip_bpf_custom_ingress_installed); - continue; - } else if (streq(l, "ip-bpf-custom-egress-installed")) { - (void) bpf_program_deserialize_attachment_set(v, fds, &u->ip_bpf_custom_egress_installed); - continue; - - } else if (streq(l, "restrict-ifaces-bpf-fd")) { - int fd; - - fd = deserialize_fd(fds, v); - if (fd >= 0) - (void) restrict_network_interfaces_add_initial_link_fd(u, fd); - - continue; - - } else if (streq(l, "ref-uid")) { + else if (streq(l, "ref-uid")) { uid_t uid; r = parse_uid(v, &uid); @@ -499,55 +329,6 @@ int unit_deserialize_state(Unit *u, FILE *f, FDSet *fds) { continue; } - m = memory_accounting_metric_field_last_from_string(l); - if (m >= 0) { - uint64_t c; - - r = safe_atou64(v, &c); - if (r < 0) - log_unit_debug(u, "Failed to parse memory accounting last value %s, ignoring.", v); - else - u->memory_accounting_last[m] = c; - continue; - } - - /* Check if this is an IP accounting metric serialization field */ - m = ip_accounting_metric_field_from_string(l); - if (m >= 0) { - uint64_t c; - - r = safe_atou64(v, &c); - if (r < 0) - log_unit_debug(u, "Failed to parse IP accounting value %s, ignoring.", v); - else - u->ip_accounting_extra[m] = c; - continue; - } - - m = io_accounting_metric_field_base_from_string(l); - if (m >= 0) { - uint64_t c; - - r = safe_atou64(v, &c); - if (r < 0) - log_unit_debug(u, "Failed to parse IO accounting base value %s, ignoring.", v); - else - u->io_accounting_base[m] = c; - continue; - } - - m = io_accounting_metric_field_last_from_string(l); - if (m >= 0) { - uint64_t c; - - r = safe_atou64(v, &c); - if (r < 0) - log_unit_debug(u, "Failed to parse IO accounting last value %s, ignoring.", v); - else - u->io_accounting_last[m] = c; - continue; - } - 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); @@ -556,6 +337,13 @@ int unit_deserialize_state(Unit *u, FILE *f, FDSet *fds) { /* Returns positive if key was handled by the call */ continue; + r = cgroup_runtime_deserialize_one(u, l, v, fds); + if (r < 0) { + log_unit_warning(u, "Failed to deserialize cgroup runtime parameter '%s, ignoring.", l); + continue; + } else if (r > 0) + continue; /* was handled */ + if (UNIT_VTABLE(u)->deserialize_item) { r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); if (r < 0) @@ -574,7 +362,9 @@ int unit_deserialize_state(Unit *u, FILE *f, FDSet *fds) { /* Let's make sure that everything that is deserialized also gets any potential new cgroup settings * applied after we are done. For that we invalidate anything already realized, so that we can * realize it again. */ - if (u->cgroup_realized) { + CGroupRuntime *crt; + crt = unit_get_cgroup_runtime(u); + if (crt && crt->cgroup_realized) { unit_invalidate_cgroup(u, _CGROUP_MASK_ALL); unit_invalidate_cgroup_bpf(u); } @@ -661,8 +451,8 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { prefix2 = strjoina(prefix, "\t"); fprintf(f, - "%s-> Unit %s:\n", - prefix, u->id); + "%s%s Unit %s:\n", + prefix, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), u->id); SET_FOREACH(t, u->aliases) fprintf(f, "%s\tAlias: %s\n", prefix, t); @@ -707,23 +497,25 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } if (UNIT_HAS_CGROUP_CONTEXT(u)) { + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + fprintf(f, "%s\tSlice: %s\n" "%s\tCGroup: %s\n" "%s\tCGroup realized: %s\n", prefix, strna(unit_slice_name(u)), - prefix, strna(u->cgroup_path), - prefix, yes_no(u->cgroup_realized)); + prefix, strna(crt ? crt->cgroup_path : NULL), + prefix, yes_no(crt ? crt->cgroup_realized : false)); - if (u->cgroup_realized_mask != 0) { + if (crt && crt->cgroup_realized_mask != 0) { _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(u->cgroup_realized_mask, &s); + (void) cg_mask_to_string(crt->cgroup_realized_mask, &s); fprintf(f, "%s\tCGroup realized mask: %s\n", prefix, strnull(s)); } - if (u->cgroup_enabled_mask != 0) { + if (crt && crt->cgroup_enabled_mask != 0) { _cleanup_free_ char *s = NULL; - (void) cg_mask_to_string(u->cgroup_enabled_mask, &s); + (void) cg_mask_to_string(crt->cgroup_enabled_mask, &s); fprintf(f, "%s\tCGroup enabled mask: %s\n", prefix, strnull(s)); } @@ -831,21 +623,26 @@ void unit_dump(Unit *u, FILE *f, const char *prefix) { } } - if (!hashmap_isempty(u->requires_mounts_for)) { - UnitDependencyInfo di; - const char *path; + for (UnitMountDependencyType type = 0; type < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; type++) + if (!hashmap_isempty(u->mounts_for[type])) { + UnitDependencyInfo di; + const char *path; - HASHMAP_FOREACH_KEY(di.data, path, u->requires_mounts_for) { - bool space = false; + HASHMAP_FOREACH_KEY(di.data, path, u->mounts_for[type]) { + bool space = false; - fprintf(f, "%s\tRequiresMountsFor: %s (", prefix, path); + fprintf(f, + "%s\t%s: %s (", + prefix, + unit_mount_dependency_type_to_string(type), + path); - print_unit_dependency_mask(f, "origin", di.origin_mask, &space); - print_unit_dependency_mask(f, "destination", di.destination_mask, &space); + print_unit_dependency_mask(f, "origin", di.origin_mask, &space); + print_unit_dependency_mask(f, "destination", di.destination_mask, &space); - fputs(")\n", f); + fputs(")\n", f); + } } - } if (u->load_state == UNIT_LOADED) { diff --git a/src/core/unit.c b/src/core/unit.c index 2fc9f5a..2d40618 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -67,27 +67,29 @@ #endif /* Thresholds for logging at INFO level about resource consumption */ -#define MENTIONWORTHY_CPU_NSEC (1 * NSEC_PER_SEC) -#define MENTIONWORTHY_IO_BYTES (1024 * 1024ULL) -#define MENTIONWORTHY_IP_BYTES (0ULL) +#define MENTIONWORTHY_CPU_NSEC (1 * NSEC_PER_SEC) +#define MENTIONWORTHY_MEMORY_BYTES (64 * U64_MB) +#define MENTIONWORTHY_IO_BYTES (1 * U64_MB) +#define MENTIONWORTHY_IP_BYTES UINT64_C(0) -/* Thresholds for logging at INFO level about resource consumption */ -#define NOTICEWORTHY_CPU_NSEC (10*60 * NSEC_PER_SEC) /* 10 minutes */ -#define NOTICEWORTHY_IO_BYTES (10 * 1024 * 1024ULL) /* 10 MB */ -#define NOTICEWORTHY_IP_BYTES (128 * 1024 * 1024ULL) /* 128 MB */ +/* Thresholds for logging at NOTICE level about resource consumption */ +#define NOTICEWORTHY_CPU_NSEC (10 * NSEC_PER_MINUTE) +#define NOTICEWORTHY_MEMORY_BYTES (512 * U64_MB) +#define NOTICEWORTHY_IO_BYTES (10 * U64_MB) +#define NOTICEWORTHY_IP_BYTES (128 * U64_MB) const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = &service_vtable, - [UNIT_SOCKET] = &socket_vtable, - [UNIT_TARGET] = &target_vtable, - [UNIT_DEVICE] = &device_vtable, - [UNIT_MOUNT] = &mount_vtable, + [UNIT_SERVICE] = &service_vtable, + [UNIT_SOCKET] = &socket_vtable, + [UNIT_TARGET] = &target_vtable, + [UNIT_DEVICE] = &device_vtable, + [UNIT_MOUNT] = &mount_vtable, [UNIT_AUTOMOUNT] = &automount_vtable, - [UNIT_SWAP] = &swap_vtable, - [UNIT_TIMER] = &timer_vtable, - [UNIT_PATH] = &path_vtable, - [UNIT_SLICE] = &slice_vtable, - [UNIT_SCOPE] = &scope_vtable, + [UNIT_SWAP] = &swap_vtable, + [UNIT_TIMER] = &timer_vtable, + [UNIT_PATH] = &path_vtable, + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable, }; Unit* unit_new(Manager *m, size_t size) { @@ -107,29 +109,13 @@ Unit* unit_new(Manager *m, size_t size) { u->unit_file_preset = -1; u->on_failure_job_mode = JOB_REPLACE; u->on_success_job_mode = JOB_FAIL; - u->cgroup_control_inotify_wd = -1; - u->cgroup_memory_inotify_wd = -1; u->job_timeout = USEC_INFINITY; u->job_running_timeout = USEC_INFINITY; u->ref_uid = UID_INVALID; u->ref_gid = GID_INVALID; - u->cpu_usage_last = NSEC_INFINITY; - - unit_reset_memory_accounting_last(u); - unit_reset_io_accounting_last(u); - - u->cgroup_invalidated_mask |= CGROUP_MASK_BPF_FIREWALL; u->failure_action_exit_status = u->success_action_exit_status = -1; - u->ip_accounting_ingress_map_fd = -EBADF; - u->ip_accounting_egress_map_fd = -EBADF; - - u->ipv4_allow_map_fd = -EBADF; - u->ipv6_allow_map_fd = -EBADF; - u->ipv4_deny_map_fd = -EBADF; - u->ipv6_deny_map_fd = -EBADF; - u->last_section_private = -1; u->start_ratelimit = (const RateLimit) { @@ -137,7 +123,13 @@ Unit* unit_new(Manager *m, size_t size) { m->defaults.start_limit_burst, }; - u->auto_start_stop_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_SEC, .burst = 16 }; + u->auto_start_stop_ratelimit = (const RateLimit) { + .interval = 10 * USEC_PER_SEC, + .burst = 16 + }; + + unit_reset_memory_accounting_last(u); + unit_reset_io_accounting_last(u); return u; } @@ -251,12 +243,12 @@ int unit_add_name(Unit *u, const char *text) { if (unit_name_is_valid(text, UNIT_NAME_TEMPLATE)) { if (!u->instance) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), - "instance is not set when adding name '%s': %m", text); + "Instance is not set when adding name '%s'.", text); r = unit_name_replace_instance(text, u->instance, &name); if (r < 0) return log_unit_debug_errno(u, r, - "failed to build instance name from '%s': %m", text); + "Failed to build instance name from '%s': %m", text); } else { name = strdup(text); if (!name) @@ -268,47 +260,47 @@ int unit_add_name(Unit *u, const char *text) { if (hashmap_contains(u->manager->units, name)) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EEXIST), - "unit already exist when adding name '%s': %m", name); + "Unit already exist when adding name '%s'.", name); if (!unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), - "name '%s' is invalid: %m", name); + "Name '%s' is invalid.", name); t = unit_name_to_type(name); if (t < 0) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), - "failed to derive unit type from name '%s': %m", name); + "failed to derive unit type from name '%s'.", name); if (u->type != _UNIT_TYPE_INVALID && t != u->type) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), - "unit type is illegal: u->type(%d) and t(%d) for name '%s': %m", + "Unit type is illegal: u->type(%d) and t(%d) for name '%s'.", u->type, t, name); r = unit_name_to_instance(name, &instance); if (r < 0) - return log_unit_debug_errno(u, r, "failed to extract instance from name '%s': %m", name); + return log_unit_debug_errno(u, r, "Failed to extract instance from name '%s': %m", name); if (instance && !unit_type_may_template(t)) - return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), "templates are not allowed for name '%s': %m", name); + return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), "Templates are not allowed for name '%s'.", name); /* Ensure that this unit either has no instance, or that the instance matches. */ if (u->type != _UNIT_TYPE_INVALID && !streq_ptr(u->instance, instance)) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EINVAL), - "cannot add name %s, the instances don't match (\"%s\" != \"%s\").", + "Cannot add name %s, the instances don't match (\"%s\" != \"%s\").", name, instance, u->instance); if (u->id && !unit_type_may_alias(t)) return log_unit_debug_errno(u, SYNTHETIC_ERRNO(EEXIST), - "cannot add name %s, aliases are not allowed for %s units.", + "Cannot add name %s, aliases are not allowed for %s units.", name, unit_type_to_string(t)); if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) - return log_unit_warning_errno(u, SYNTHETIC_ERRNO(E2BIG), "cannot add name, manager has too many units: %m"); + return log_unit_warning_errno(u, SYNTHETIC_ERRNO(E2BIG), "Cannot add name, manager has too many units."); /* Add name to the global hashmap first, because that's easier to undo */ r = hashmap_put(u->manager->units, name, u); if (r < 0) - return log_unit_debug_errno(u, r, "add unit to hashmap failed for name '%s': %m", text); + return log_unit_debug_errno(u, r, "Add unit to hashmap failed for name '%s': %m", text); if (u->id) { r = unit_add_alias(u, name); /* unit_add_alias() takes ownership of the name on success */ @@ -475,7 +467,7 @@ bool unit_may_gc(Unit *u) { break; case COLLECT_INACTIVE_OR_FAILED: - if (!IN_SET(state, UNIT_INACTIVE, UNIT_FAILED)) + if (!UNIT_IS_INACTIVE_OR_FAILED(state)) return false; break; @@ -488,16 +480,11 @@ bool unit_may_gc(Unit *u) { if (unit_success_failure_handler_has_jobs(u)) return false; - if (u->cgroup_path) { - /* If the unit has a cgroup, then check whether there's anything in it. If so, we should stay - * around. Units with active processes should never be collected. */ - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to determine whether cgroup %s is empty: %m", empty_to_root(u->cgroup_path)); - if (r <= 0) - return false; - } + /* If the unit has a cgroup, then check whether there's anything in it. If so, we should stay + * around. Units with active processes should never be collected. */ + r = unit_cgroup_is_empty(u); + if (r <= 0 && r != -ENXIO) + return false; /* ENXIO means: currently not realized */ if (!UNIT_VTABLE(u)->may_gc) return true; @@ -689,38 +676,39 @@ static void unit_remove_transient(Unit *u) { } } -static void unit_free_requires_mounts_for(Unit *u) { +static void unit_free_mounts_for(Unit *u) { assert(u); - for (;;) { - _cleanup_free_ char *path = NULL; + for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; ++t) { + for (;;) { + _cleanup_free_ char *path = NULL; + + path = hashmap_steal_first_key(u->mounts_for[t]); + if (!path) + break; - path = hashmap_steal_first_key(u->requires_mounts_for); - if (!path) - break; - else { char s[strlen(path) + 1]; PATH_FOREACH_PREFIX_MORE(s, path) { char *y; Set *x; - x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + x = hashmap_get2(u->manager->units_needing_mounts_for[t], s, (void**) &y); if (!x) continue; (void) set_remove(x, u); if (set_isempty(x)) { - (void) hashmap_remove(u->manager->units_requiring_mounts_for, y); + assert_se(hashmap_remove(u->manager->units_needing_mounts_for[t], y)); free(y); set_free(x); } } } - } - u->requires_mounts_for = hashmap_free(u->requires_mounts_for); + u->mounts_for[t] = hashmap_free(u->mounts_for[t]); + } } static void unit_done(Unit *u) { @@ -769,7 +757,7 @@ Unit* unit_free(Unit *u) { u->deserialized_refs = strv_free(u->deserialized_refs); u->pending_freezer_invocation = sd_bus_message_unref(u->pending_freezer_invocation); - unit_free_requires_mounts_for(u); + unit_free_mounts_for(u); SET_FOREACH(t, u->aliases) hashmap_remove_value(u->manager->units, t, u); @@ -801,12 +789,6 @@ Unit* unit_free(Unit *u) { if (u->on_console) manager_unref_console(u->manager); - fdset_free(u->initial_socket_bind_link_fds); -#if BPF_FRAMEWORK - bpf_link_free(u->ipv4_socket_bind_link); - bpf_link_free(u->ipv6_socket_bind_link); -#endif - unit_release_cgroup(u); if (!MANAGER_IS_RELOADING(u->manager)) @@ -863,16 +845,6 @@ Unit* unit_free(Unit *u) { bpf_firewall_close(u); - hashmap_free(u->bpf_foreign_by_key); - - bpf_program_free(u->bpf_device_control_installed); - -#if BPF_FRAMEWORK - bpf_link_free(u->restrict_ifaces_ingress_bpf_link); - bpf_link_free(u->restrict_ifaces_egress_bpf_link); -#endif - fdset_free(u->initial_restric_ifaces_link_fds); - condition_free_list(u->conditions); condition_free_list(u->asserts); @@ -902,32 +874,6 @@ FreezerState unit_freezer_state(Unit *u) { return u->freezer_state; } -int unit_freezer_state_kernel(Unit *u, FreezerState *ret) { - char *values[1] = {}; - int r; - - assert(u); - - r = cg_get_keyed_attribute(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.events", - STRV_MAKE("frozen"), values); - if (r < 0) - return r; - - r = _FREEZER_STATE_INVALID; - - if (values[0]) { - if (streq(values[0], "0")) - r = FREEZER_RUNNING; - else if (streq(values[0], "1")) - r = FREEZER_FROZEN; - } - - free(values[0]); - *ret = r; - - return 0; -} - UnitActiveState unit_active_state(Unit *u) { assert(u); @@ -1277,20 +1223,24 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { /* Unlike unit_add_dependency() or friends, this always returns 0 on success. */ - if (c->working_directory && !c->working_directory_missing_ok) { - r = unit_require_mounts_for(u, c->working_directory, UNIT_DEPENDENCY_FILE); + if (c->working_directory) { + r = unit_add_mounts_for( + u, + c->working_directory, + UNIT_DEPENDENCY_FILE, + c->working_directory_missing_ok ? UNIT_MOUNT_WANTS : UNIT_MOUNT_REQUIRES); if (r < 0) return r; } if (c->root_directory) { - r = unit_require_mounts_for(u, c->root_directory, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, c->root_directory, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; } if (c->root_image) { - r = unit_require_mounts_for(u, c->root_image, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, c->root_image, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; } @@ -1299,14 +1249,14 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { if (!u->manager->prefix[dt]) continue; - for (size_t i = 0; i < c->directories[dt].n_items; i++) { + FOREACH_ARRAY(i, c->directories[dt].items, c->directories[dt].n_items) { _cleanup_free_ char *p = NULL; - p = path_join(u->manager->prefix[dt], c->directories[dt].items[i].path); + p = path_join(u->manager->prefix[dt], i->path); if (!p) return -ENOMEM; - r = unit_require_mounts_for(u, p, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, p, UNIT_DEPENDENCY_FILE, UNIT_MOUNT_REQUIRES); if (r < 0) return r; } @@ -1326,16 +1276,11 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { } if (c->private_tmp) { - - /* FIXME: for now we make a special case for /tmp and add a weak dependency on - * tmp.mount so /tmp being masked is supported. However there's no reason to treat - * /tmp specifically and masking other mount units should be handled more - * gracefully too, see PR#16894. */ - r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_WANTS, "tmp.mount", true, UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, "/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; - r = unit_require_mounts_for(u, "/var/tmp", UNIT_DEPENDENCY_FILE); + r = unit_add_mounts_for(u, "/var/tmp", UNIT_DEPENDENCY_FILE, UNIT_MOUNT_WANTS); if (r < 0) return r; @@ -1366,23 +1311,26 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) { * is run first. */ if (c->log_namespace) { - _cleanup_free_ char *socket_unit = NULL, *varlink_socket_unit = NULL; - - r = unit_name_build_from_type("systemd-journald", c->log_namespace, UNIT_SOCKET, &socket_unit); - if (r < 0) - return r; + static const struct { + const char *template; + UnitType type; + } deps[] = { + { "systemd-journald", UNIT_SOCKET, }, + { "systemd-journald-varlink", UNIT_SOCKET, }, + { "systemd-journald-sync", UNIT_SERVICE, }, + }; - r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, socket_unit, true, UNIT_DEPENDENCY_FILE); - if (r < 0) - return r; + FOREACH_ELEMENT(i, deps) { + _cleanup_free_ char *unit = NULL; - r = unit_name_build_from_type("systemd-journald-varlink", c->log_namespace, UNIT_SOCKET, &varlink_socket_unit); - if (r < 0) - return r; + r = unit_name_build_from_type(i->template, c->log_namespace, i->type, &unit); + if (r < 0) + return r; - r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, varlink_socket_unit, true, UNIT_DEPENDENCY_FILE); - if (r < 0) - return r; + r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, unit, true, UNIT_DEPENDENCY_FILE); + if (r < 0) + return r; + } } else { r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE); if (r < 0) @@ -1515,6 +1463,7 @@ int unit_add_default_target_dependency(Unit *u, Unit *target) { static int unit_add_slice_dependencies(Unit *u) { Unit *slice; + assert(u); if (!UNIT_HAS_CGROUP_CONTEXT(u)) @@ -1526,8 +1475,12 @@ static int unit_add_slice_dependencies(Unit *u) { UnitDependencyMask mask = u->type == UNIT_SLICE ? UNIT_DEPENDENCY_IMPLICIT : UNIT_DEPENDENCY_FILE; slice = UNIT_GET_SLICE(u); - if (slice) + if (slice) { + if (!IN_SET(slice->freezer_state, FREEZER_RUNNING, FREEZER_THAWING)) + u->freezer_state = FREEZER_FROZEN_BY_PARENT; + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, slice, true, mask); + } if (unit_has_name(u, SPECIAL_ROOT_SLICE)) return 0; @@ -1536,51 +1489,72 @@ static int unit_add_slice_dependencies(Unit *u) { } static int unit_add_mount_dependencies(Unit *u) { - UnitDependencyInfo di; - const char *path; bool changed = false; int r; assert(u); - HASHMAP_FOREACH_KEY(di.data, path, u->requires_mounts_for) { - char prefix[strlen(path) + 1]; + for (UnitMountDependencyType t = 0; t < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX; ++t) { + UnitDependencyInfo di; + const char *path; - PATH_FOREACH_PREFIX_MORE(prefix, path) { - _cleanup_free_ char *p = NULL; - Unit *m; + HASHMAP_FOREACH_KEY(di.data, path, u->mounts_for[t]) { - r = unit_name_from_path(prefix, ".mount", &p); - if (r == -EINVAL) - continue; /* If the path cannot be converted to a mount unit name, then it's - * not manageable as a unit by systemd, and hence we don't need a - * dependency on it. Let's thus silently ignore the issue. */ - if (r < 0) - return r; + char prefix[strlen(ASSERT_PTR(path)) + 1]; - m = manager_get_unit(u->manager, p); - if (!m) { - /* Make sure to load the mount unit if it exists. If so the dependencies on - * this unit will be added later during the loading of the mount unit. */ - (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); - continue; - } - if (m == u) - continue; + PATH_FOREACH_PREFIX_MORE(prefix, path) { + _cleanup_free_ char *p = NULL; + Unit *m; - if (m->load_state != UNIT_LOADED) - continue; + r = unit_name_from_path(prefix, ".mount", &p); + if (r == -EINVAL) + continue; /* If the path cannot be converted to a mount unit name, + * then it's not manageable as a unit by systemd, and + * hence we don't need a dependency on it. Let's thus + * silently ignore the issue. */ + if (r < 0) + return r; - r = unit_add_dependency(u, UNIT_AFTER, m, true, di.origin_mask); - if (r < 0) - return r; - changed = changed || r > 0; + m = manager_get_unit(u->manager, p); + if (!m) { + /* Make sure to load the mount unit if it exists. If so the + * dependencies on this unit will be added later during the loading + * of the mount unit. */ + (void) manager_load_unit_prepare( + u->manager, + p, + /* path= */NULL, + /* e= */NULL, + &m); + continue; + } + if (m == u) + continue; - if (m->fragment_path) { - r = unit_add_dependency(u, UNIT_REQUIRES, m, true, di.origin_mask); + if (m->load_state != UNIT_LOADED) + continue; + + r = unit_add_dependency( + u, + UNIT_AFTER, + m, + /* add_reference= */ true, + di.origin_mask); if (r < 0) return r; changed = changed || r > 0; + + if (m->fragment_path) { + r = unit_add_dependency( + u, + unit_mount_dependency_type_to_dependency_type(t), + m, + /* add_reference= */ true, + di.origin_mask); + if (r < 0) + return r; + changed = changed || r > 0; + } } } } @@ -1959,6 +1933,10 @@ int unit_start(Unit *u, ActivationDetails *details) { return unit_start(following, details); } + /* Check to make sure the unit isn't frozen */ + if (u->freezer_state != FREEZER_RUNNING) + return -EDEADLK; + /* Check our ability to start early so that failure conditions don't cause us to enter a busy loop. */ if (UNIT_VTABLE(u)->can_start) { r = UNIT_VTABLE(u)->can_start(u); @@ -1975,7 +1953,6 @@ int unit_start(Unit *u, ActivationDetails *details) { * waits for a holdoff timer to elapse before it will start again. */ unit_add_to_dbus_queue(u); - unit_cgroup_freezer_action(u, FREEZER_THAW); if (!u->activation_details) /* Older details object wins */ u->activation_details = activation_details_ref(details); @@ -2010,6 +1987,7 @@ bool unit_can_isolate(Unit *u) { * -EBADR: This unit type does not support stopping. * -EALREADY: Unit is already stopped. * -EAGAIN: An operation is already in progress. Retry later. + * -EDEADLK: Unit is frozen */ int unit_stop(Unit *u) { UnitActiveState state; @@ -2027,11 +2005,14 @@ int unit_stop(Unit *u) { return unit_stop(following); } + /* Check to make sure the unit isn't frozen */ + if (u->freezer_state != FREEZER_RUNNING) + return -EDEADLK; + if (!UNIT_VTABLE(u)->stop) return -EBADR; unit_add_to_dbus_queue(u); - unit_cgroup_freezer_action(u, FREEZER_THAW); return UNIT_VTABLE(u)->stop(u); } @@ -2056,6 +2037,7 @@ bool unit_can_stop(Unit *u) { * -EBADR: This unit type does not support reloading. * -ENOEXEC: Unit is not started. * -EAGAIN: An operation is already in progress. Retry later. + * -EDEADLK: Unit is frozen. */ int unit_reload(Unit *u) { UnitActiveState state; @@ -2082,6 +2064,10 @@ int unit_reload(Unit *u) { return unit_reload(following); } + /* Check to make sure the unit isn't frozen */ + if (u->freezer_state != FREEZER_RUNNING) + return -EDEADLK; + unit_add_to_dbus_queue(u); if (!UNIT_VTABLE(u)->reload) { @@ -2090,8 +2076,6 @@ int unit_reload(Unit *u) { return 0; } - unit_cgroup_freezer_action(u, FREEZER_THAW); - return UNIT_VTABLE(u)->reload(u); } @@ -2238,16 +2222,16 @@ static void retroactively_start_dependencies(Unit *u) { UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_START_REPLACE) /* Requires= + BindsTo= */ if (!unit_has_dependency(u, UNIT_ATOM_AFTER, other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL); + (void) manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL, NULL); UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_START_FAIL) /* Wants= */ if (!unit_has_dependency(u, UNIT_ATOM_AFTER, other) && !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL, NULL); + (void) manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL, NULL); UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_STOP_ON_START) /* Conflicts= (and inverse) */ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); + (void) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); } static void retroactively_stop_dependencies(Unit *u) { @@ -2259,7 +2243,7 @@ static void retroactively_stop_dependencies(Unit *u) { /* Pull down units which are bound to us recursively if enabled */ UNIT_FOREACH_DEPENDENCY(other, u, UNIT_ATOM_RETROACTIVE_STOP_ON_STOP) /* BoundBy= */ if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); + (void) manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL, NULL); } void unit_start_on_failure( @@ -2291,7 +2275,7 @@ void unit_start_on_failure( log_unit_warning_errno( u, r, "Failed to enqueue %s job, ignoring: %s", dependency_name, bus_error_message(&error, r)); - n_jobs ++; + n_jobs++; } if (n_jobs >= 0) @@ -2318,273 +2302,179 @@ static int raise_level(int log_level, bool condition_info, bool condition_notice } static int unit_log_resources(Unit *u) { - struct iovec iovec[1 + 2 + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4]; - bool any_traffic = false, have_ip_accounting = false, any_io = false, have_io_accounting = false; - _cleanup_free_ char *igress = NULL, *egress = NULL, *rr = NULL, *wr = NULL; - int log_level = LOG_DEBUG; /* May be raised if resources consumed over a threshold */ - size_t n_message_parts = 0, n_iovec = 0; - char* message_parts[1 + 2 + 2 + 2 + 1], *t; - nsec_t nsec = NSEC_INFINITY; - uint64_t memory_peak = UINT64_MAX, memory_swap_peak = UINT64_MAX; - int r; - const char* const ip_fields[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { - [CGROUP_IP_INGRESS_BYTES] = "IP_METRIC_INGRESS_BYTES", - [CGROUP_IP_INGRESS_PACKETS] = "IP_METRIC_INGRESS_PACKETS", - [CGROUP_IP_EGRESS_BYTES] = "IP_METRIC_EGRESS_BYTES", - [CGROUP_IP_EGRESS_PACKETS] = "IP_METRIC_EGRESS_PACKETS", - }; - const char* const io_fields[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { - [CGROUP_IO_READ_BYTES] = "IO_METRIC_READ_BYTES", - [CGROUP_IO_WRITE_BYTES] = "IO_METRIC_WRITE_BYTES", - [CGROUP_IO_READ_OPERATIONS] = "IO_METRIC_READ_OPERATIONS", - [CGROUP_IO_WRITE_OPERATIONS] = "IO_METRIC_WRITE_OPERATIONS", + + static const struct { + const char *journal_field; + const char *message_suffix; + } memory_fields[_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1] = { + [CGROUP_MEMORY_PEAK] = { "MEMORY_PEAK", "memory peak" }, + [CGROUP_MEMORY_SWAP_PEAK] = { "MEMORY_SWAP_PEAK", "memory swap peak" }, + }, ip_fields[_CGROUP_IP_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IP_INGRESS_BYTES] = { "IP_METRIC_INGRESS_BYTES", "incoming IP traffic" }, + [CGROUP_IP_EGRESS_BYTES] = { "IP_METRIC_EGRESS_BYTES", "outgoing IP traffic" }, + [CGROUP_IP_INGRESS_PACKETS] = { "IP_METRIC_INGRESS_PACKETS", NULL }, + [CGROUP_IP_EGRESS_PACKETS] = { "IP_METRIC_EGRESS_PACKETS", NULL }, + }, io_fields[_CGROUP_IO_ACCOUNTING_METRIC_MAX] = { + [CGROUP_IO_READ_BYTES] = { "IO_METRIC_READ_BYTES", "read from disk" }, + [CGROUP_IO_WRITE_BYTES] = { "IO_METRIC_WRITE_BYTES", "written to disk" }, + [CGROUP_IO_READ_OPERATIONS] = { "IO_METRIC_READ_OPERATIONS", NULL }, + [CGROUP_IO_WRITE_OPERATIONS] = { "IO_METRIC_WRITE_OPERATIONS", NULL }, }; + struct iovec *iovec = NULL; + size_t n_iovec = 0; + _cleanup_free_ char *message = NULL, *t = NULL; + nsec_t cpu_nsec = NSEC_INFINITY; + int log_level = LOG_DEBUG; /* May be raised if resources consumed over a threshold */ + assert(u); + CLEANUP_ARRAY(iovec, n_iovec, iovec_array_free); + + iovec = new(struct iovec, 1 + (_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1) + + _CGROUP_IP_ACCOUNTING_METRIC_MAX + _CGROUP_IO_ACCOUNTING_METRIC_MAX + 4); + if (!iovec) + return log_oom(); + /* Invoked whenever a unit enters failed or dead state. Logs information about consumed resources if resource * accounting was enabled for a unit. It does this in two ways: a friendly human readable string with reduced * information and the complete data in structured fields. */ - (void) unit_get_cpu_usage(u, &nsec); - if (nsec != NSEC_INFINITY) { + (void) unit_get_cpu_usage(u, &cpu_nsec); + if (cpu_nsec != NSEC_INFINITY) { /* Format the CPU time for inclusion in the structured log message */ - if (asprintf(&t, "CPU_USAGE_NSEC=%" PRIu64, nsec) < 0) { - r = log_oom(); - goto finish; - } - iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + if (asprintf(&t, "CPU_USAGE_NSEC=%" PRIu64, cpu_nsec) < 0) + return log_oom(); + iovec[n_iovec++] = IOVEC_MAKE_STRING(TAKE_PTR(t)); /* Format the CPU time for inclusion in the human language message string */ - t = strjoin("consumed ", FORMAT_TIMESPAN(nsec / NSEC_PER_USEC, USEC_PER_MSEC), " CPU time"); - if (!t) { - r = log_oom(); - goto finish; - } - - message_parts[n_message_parts++] = t; + if (strextendf_with_separator(&message, ", ", + "Consumed %s CPU time", + FORMAT_TIMESPAN(cpu_nsec / NSEC_PER_USEC, USEC_PER_MSEC)) < 0) + return log_oom(); log_level = raise_level(log_level, - nsec > MENTIONWORTHY_CPU_NSEC, - nsec > NOTICEWORTHY_CPU_NSEC); + cpu_nsec > MENTIONWORTHY_CPU_NSEC, + cpu_nsec > NOTICEWORTHY_CPU_NSEC); } - (void) unit_get_memory_accounting(u, CGROUP_MEMORY_PEAK, &memory_peak); - if (memory_peak != UINT64_MAX) { - /* Format peak memory for inclusion in the structured log message */ - if (asprintf(&t, "MEMORY_PEAK=%" PRIu64, memory_peak) < 0) { - r = log_oom(); - goto finish; - } - iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + for (CGroupMemoryAccountingMetric metric = 0; metric <= _CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST; metric++) { + uint64_t value = UINT64_MAX; - /* Format peak memory for inclusion in the human language message string */ - t = strjoin(FORMAT_BYTES(memory_peak), " memory peak"); - if (!t) { - r = log_oom(); - goto finish; - } - message_parts[n_message_parts++] = t; - } + assert(memory_fields[metric].journal_field); + assert(memory_fields[metric].message_suffix); - (void) unit_get_memory_accounting(u, CGROUP_MEMORY_SWAP_PEAK, &memory_swap_peak); - if (memory_swap_peak != UINT64_MAX) { - /* Format peak swap memory for inclusion in the structured log message */ - if (asprintf(&t, "MEMORY_SWAP_PEAK=%" PRIu64, memory_swap_peak) < 0) { - r = log_oom(); - goto finish; - } - iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + (void) unit_get_memory_accounting(u, metric, &value); + if (value == UINT64_MAX) + continue; - /* Format peak swap memory for inclusion in the human language message string */ - t = strjoin(FORMAT_BYTES(memory_swap_peak), " memory swap peak"); - if (!t) { - r = log_oom(); - goto finish; - } - message_parts[n_message_parts++] = t; + if (asprintf(&t, "%s=%" PRIu64, memory_fields[metric].journal_field, value) < 0) + return log_oom(); + iovec[n_iovec++] = IOVEC_MAKE_STRING(TAKE_PTR(t)); + + /* If value is 0, we don't log it in the MESSAGE= field. */ + if (value == 0) + continue; + + if (strextendf_with_separator(&message, ", ", "%s %s", + FORMAT_BYTES(value), memory_fields[metric].message_suffix) < 0) + return log_oom(); + + log_level = raise_level(log_level, + value > MENTIONWORTHY_MEMORY_BYTES, + value > NOTICEWORTHY_MEMORY_BYTES); } for (CGroupIOAccountingMetric k = 0; k < _CGROUP_IO_ACCOUNTING_METRIC_MAX; k++) { uint64_t value = UINT64_MAX; - assert(io_fields[k]); + assert(io_fields[k].journal_field); (void) unit_get_io_accounting(u, k, k > 0, &value); if (value == UINT64_MAX) continue; - have_io_accounting = true; - if (value > 0) - any_io = true; - /* Format IO accounting data for inclusion in the structured log message */ - if (asprintf(&t, "%s=%" PRIu64, io_fields[k], value) < 0) { - r = log_oom(); - goto finish; - } - iovec[n_iovec++] = IOVEC_MAKE_STRING(t); + if (asprintf(&t, "%s=%" PRIu64, io_fields[k].journal_field, value) < 0) + return log_oom(); + iovec[n_iovec++] = IOVEC_MAKE_STRING(TAKE_PTR(t)); + + /* If value is 0, we don't log it in the MESSAGE= field. */ + if (value == 0) + continue; /* Format the IO accounting data for inclusion in the human language message string, but only * for the bytes counters (and not for the operations counters) */ - if (k == CGROUP_IO_READ_BYTES) { - assert(!rr); - rr = strjoin("read ", strna(FORMAT_BYTES(value)), " from disk"); - if (!rr) { - r = log_oom(); - goto finish; - } - } else if (k == CGROUP_IO_WRITE_BYTES) { - assert(!wr); - wr = strjoin("written ", strna(FORMAT_BYTES(value)), " to disk"); - if (!wr) { - r = log_oom(); - goto finish; - } - } + if (io_fields[k].message_suffix) { + if (strextendf_with_separator(&message, ", ", "%s %s", + FORMAT_BYTES(value), io_fields[k].message_suffix) < 0) + return log_oom(); - if (IN_SET(k, CGROUP_IO_READ_BYTES, CGROUP_IO_WRITE_BYTES)) log_level = raise_level(log_level, value > MENTIONWORTHY_IO_BYTES, value > NOTICEWORTHY_IO_BYTES); - } - - if (have_io_accounting) { - if (any_io) { - if (rr) - message_parts[n_message_parts++] = TAKE_PTR(rr); - if (wr) - message_parts[n_message_parts++] = TAKE_PTR(wr); - - } else { - char *k; - - k = strdup("no IO"); - if (!k) { - r = log_oom(); - goto finish; - } - - message_parts[n_message_parts++] = k; } } for (CGroupIPAccountingMetric m = 0; m < _CGROUP_IP_ACCOUNTING_METRIC_MAX; m++) { uint64_t value = UINT64_MAX; - assert(ip_fields[m]); + assert(ip_fields[m].journal_field); (void) unit_get_ip_accounting(u, m, &value); if (value == UINT64_MAX) continue; - have_ip_accounting = true; - if (value > 0) - any_traffic = true; - /* Format IP accounting data for inclusion in the structured log message */ - if (asprintf(&t, "%s=%" PRIu64, ip_fields[m], value) < 0) { - r = log_oom(); - goto finish; - } - iovec[n_iovec++] = IOVEC_MAKE_STRING(t); - - /* Format the IP accounting data for inclusion in the human language message string, but only for the - * bytes counters (and not for the packets counters) */ - if (m == CGROUP_IP_INGRESS_BYTES) { - assert(!igress); - igress = strjoin("received ", strna(FORMAT_BYTES(value)), " IP traffic"); - if (!igress) { - r = log_oom(); - goto finish; - } - } else if (m == CGROUP_IP_EGRESS_BYTES) { - assert(!egress); - egress = strjoin("sent ", strna(FORMAT_BYTES(value)), " IP traffic"); - if (!egress) { - r = log_oom(); - goto finish; - } - } + if (asprintf(&t, "%s=%" PRIu64, ip_fields[m].journal_field, value) < 0) + return log_oom(); + iovec[n_iovec++] = IOVEC_MAKE_STRING(TAKE_PTR(t)); + + /* If value is 0, we don't log it in the MESSAGE= field. */ + if (value == 0) + continue; + + /* Format the IP accounting data for inclusion in the human language message string, but only + * for the bytes counters (and not for the packets counters) */ + if (ip_fields[m].message_suffix) { + if (strextendf_with_separator(&message, ", ", "%s %s", + FORMAT_BYTES(value), ip_fields[m].message_suffix) < 0) + return log_oom(); - if (IN_SET(m, CGROUP_IP_INGRESS_BYTES, CGROUP_IP_EGRESS_BYTES)) log_level = raise_level(log_level, value > MENTIONWORTHY_IP_BYTES, value > NOTICEWORTHY_IP_BYTES); - } - - /* This check is here because it is the earliest point following all possible log_level assignments. If - * log_level is assigned anywhere after this point, move this check. */ - if (!unit_log_level_test(u, log_level)) { - r = 0; - goto finish; - } - - if (have_ip_accounting) { - if (any_traffic) { - if (igress) - message_parts[n_message_parts++] = TAKE_PTR(igress); - if (egress) - message_parts[n_message_parts++] = TAKE_PTR(egress); - - } else { - char *k; - - k = strdup("no IP traffic"); - if (!k) { - r = log_oom(); - goto finish; - } - - message_parts[n_message_parts++] = k; } } + /* This check is here because it is the earliest point following all possible log_level assignments. + * (If log_level is assigned anywhere after this point, move this check.) */ + if (!unit_log_level_test(u, log_level)) + return 0; + /* Is there any accounting data available at all? */ if (n_iovec == 0) { - r = 0; - goto finish; - } - - if (n_message_parts == 0) - t = strjoina("MESSAGE=", u->id, ": Completed."); - else { - _cleanup_free_ char *joined = NULL; - - message_parts[n_message_parts] = NULL; - - joined = strv_join(message_parts, ", "); - if (!joined) { - r = log_oom(); - goto finish; - } - - joined[0] = ascii_toupper(joined[0]); - t = strjoina("MESSAGE=", u->id, ": ", joined, "."); + assert(!message); + return 0; } - /* The following four fields we allocate on the stack or are static strings, we hence don't want to free them, - * and hence don't increase n_iovec for them */ - iovec[n_iovec] = IOVEC_MAKE_STRING(t); - iovec[n_iovec + 1] = IOVEC_MAKE_STRING("MESSAGE_ID=" SD_MESSAGE_UNIT_RESOURCES_STR); - - t = strjoina(u->manager->unit_log_field, u->id); - iovec[n_iovec + 2] = IOVEC_MAKE_STRING(t); - - t = strjoina(u->manager->invocation_log_field, u->invocation_id_string); - iovec[n_iovec + 3] = IOVEC_MAKE_STRING(t); + t = strjoin("MESSAGE=", u->id, ": ", message ?: "Completed", "."); + if (!t) + return log_oom(); + iovec[n_iovec++] = IOVEC_MAKE_STRING(TAKE_PTR(t)); - log_unit_struct_iovec(u, log_level, iovec, n_iovec + 4); - r = 0; + if (!set_iovec_string_field(iovec, &n_iovec, "MESSAGE_ID=", SD_MESSAGE_UNIT_RESOURCES_STR)) + return log_oom(); -finish: - free_many_charp(message_parts, n_message_parts); + if (!set_iovec_string_field(iovec, &n_iovec, u->manager->unit_log_field, u->id)) + return log_oom(); - for (size_t i = 0; i < n_iovec; i++) - free(iovec[i].iov_base); + if (!set_iovec_string_field(iovec, &n_iovec, u->manager->invocation_log_field, u->invocation_id_string)) + return log_oom(); - return r; + log_unit_struct_iovec(u, log_level, iovec, n_iovec); + return 0; } static void unit_update_on_console(Unit *u) { @@ -2796,12 +2686,14 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su unit_emit_audit_start(u); manager_send_unit_plymouth(m, u); + manager_send_unit_supervisor(m, u, /* active= */ true); } if (UNIT_IS_INACTIVE_OR_FAILED(ns) && !UNIT_IS_INACTIVE_OR_FAILED(os)) { /* This unit just stopped/failed. */ unit_emit_audit_stop(u, ns); + manager_send_unit_supervisor(m, u, /* active= */ false); unit_log_resources(u); } @@ -2859,7 +2751,7 @@ void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_su } } -int unit_watch_pidref(Unit *u, PidRef *pid, bool exclusive) { +int unit_watch_pidref(Unit *u, const PidRef *pid, bool exclusive) { _cleanup_(pidref_freep) PidRef *pid_dup = NULL; int r; @@ -2943,7 +2835,7 @@ int unit_watch_pid(Unit *u, pid_t pid, bool exclusive) { return unit_watch_pidref(u, &pidref, exclusive); } -void unit_unwatch_pidref(Unit *u, PidRef *pid) { +void unit_unwatch_pidref(Unit *u, const PidRef *pid) { assert(u); assert(pidref_is_set(pid)); @@ -3005,6 +2897,16 @@ void unit_unwatch_all_pids(Unit *u) { u->pids = set_free(u->pids); } +void unit_unwatch_pidref_done(Unit *u, PidRef *pidref) { + assert(u); + + if (!pidref_is_set(pidref)) + return; + + unit_unwatch_pidref(u, pidref); + pidref_done(pidref); +} + static void unit_tidy_watch_pids(Unit *u) { PidRef *except1, *except2, *e; @@ -3030,7 +2932,7 @@ static int on_rewatch_pids_event(sd_event_source *s, void *userdata) { assert(s); unit_tidy_watch_pids(u); - unit_watch_all_pids(u); + (void) unit_watch_all_pids(u); /* If the PID set is empty now, then let's finish this off. */ unit_synthesize_cgroup_empty_event(u); @@ -3043,7 +2945,8 @@ int unit_enqueue_rewatch_pids(Unit *u) { assert(u); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (!crt || !crt->cgroup_path) return -ENOENT; r = cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER); @@ -3063,7 +2966,7 @@ int unit_enqueue_rewatch_pids(Unit *u) { if (r < 0) return log_error_errno(r, "Failed to allocate event source for tidying watched PIDs: %m"); - r = sd_event_source_set_priority(s, SD_EVENT_PRIORITY_IDLE); + r = sd_event_source_set_priority(s, EVENT_PRIORITY_REWATCH_PIDS); if (r < 0) return log_error_errno(r, "Failed to adjust priority of event source for tidying watched PIDs: %m"); @@ -3288,8 +3191,8 @@ int unit_add_dependency( if (u->manager && FLAGS_SET(u->manager->test_run_flags, MANAGER_TEST_RUN_IGNORE_DEPENDENCIES)) return 0; - /* Note that ordering a device unit after a unit is permitted since it allows to start its job - * running timeout at a specific time. */ + /* Note that ordering a device unit after a unit is permitted since it allows its job running + * timeout to be started at a specific time. */ if (FLAGS_SET(a, UNIT_ATOM_BEFORE) && other->type == UNIT_DEVICE) { log_unit_warning(u, "Dependency Before=%s ignored (.device units cannot be delayed)", other->id); return 0; @@ -3529,8 +3432,11 @@ int unit_set_slice(Unit *u, Unit *slice) { return 0; /* Disallow slice changes if @u is already bound to cgroups */ - if (UNIT_GET_SLICE(u) && u->cgroup_realized) - return -EBUSY; + if (UNIT_GET_SLICE(u)) { + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt && crt->cgroup_realized) + return -EBUSY; + } /* Remove any slices assigned prior; we should only have one UNIT_IN_SLICE dependency */ if (UNIT_GET_SLICE(u)) @@ -4019,28 +3925,25 @@ void unit_notify_cgroup_oom(Unit *u, bool managed_oom) { UNIT_VTABLE(u)->notify_cgroup_oom(u, managed_oom); } -static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { - _cleanup_set_free_ Set *pid_set = NULL; +static int unit_pid_set(Unit *u, Set **pid_set) { int r; - pid_set = set_new(NULL); - if (!pid_set) - return NULL; + assert(u); + assert(pid_set); + + set_clear(*pid_set); /* This updates input. */ /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(main_pid)); - if (r < 0) - return NULL; - } - if (control_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(control_pid)); - if (r < 0) - return NULL; - } + PidRef *pid; + FOREACH_ARGUMENT(pid, unit_main_pid(u), unit_control_pid(u)) + if (pidref_is_set(pid)) { + r = set_ensure_put(pid_set, NULL, PID_TO_PTR(pid->pid)); + if (r < 0) + return r; + } - return TAKE_PTR(pid_set); + return 0; } static int kill_common_log(const PidRef *pid, int signo, void *userdata) { @@ -4074,13 +3977,55 @@ static int kill_or_sigqueue(PidRef* pidref, int signo, int code, int value) { } } +static int unit_kill_one( + Unit *u, + PidRef *pidref, + const char *type, + int signo, + int code, + int value, + sd_bus_error *ret_error) { + + int r; + + assert(u); + assert(type); + + if (!pidref_is_set(pidref)) + return 0; + + _cleanup_free_ char *comm = NULL; + (void) pidref_get_comm(pidref, &comm); + + r = kill_or_sigqueue(pidref, signo, code, value); + if (r == -ESRCH) + return 0; + if (r < 0) { + /* Report this failure both to the logs and to the client */ + if (ret_error) + sd_bus_error_set_errnof( + ret_error, r, + "Failed to send signal SIG%s to %s process " PID_FMT " (%s): %m", + signal_to_string(signo), type, pidref->pid, strna(comm)); + + return log_unit_warning_errno( + u, r, + "Failed to send signal SIG%s to %s process " PID_FMT " (%s) on client request: %m", + signal_to_string(signo), type, pidref->pid, strna(comm)); + } + + log_unit_info(u, "Sent signal SIG%s to %s process " PID_FMT " (%s) on client request.", + signal_to_string(signo), type, pidref->pid, strna(comm)); + return 1; /* killed */ +} + int unit_kill( Unit *u, KillWho who, int signo, int code, int value, - sd_bus_error *error) { + sd_bus_error *ret_error) { PidRef *main_pid, *control_pid; bool killed = false; @@ -4100,110 +4045,71 @@ int unit_kill( control_pid = unit_control_pid(u); if (!UNIT_HAS_CGROUP_CONTEXT(u) && !main_pid && !control_pid) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit type does not support process killing."); + return sd_bus_error_setf(ret_error, SD_BUS_ERROR_NOT_SUPPORTED, "Unit type does not support process killing."); if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { if (!main_pid) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); + return sd_bus_error_setf(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); if (!pidref_is_set(main_pid)) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + return sd_bus_error_set_const(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); } if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { if (!control_pid) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); + return sd_bus_error_setf(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); if (!pidref_is_set(control_pid)) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + return sd_bus_error_set_const(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); } - if (pidref_is_set(control_pid) && - IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) { - _cleanup_free_ char *comm = NULL; - (void) pidref_get_comm(control_pid, &comm); - - r = kill_or_sigqueue(control_pid, signo, code, value); - if (r < 0) { - ret = r; - - /* Report this failure both to the logs and to the client */ - sd_bus_error_set_errnof( - error, r, - "Failed to send signal SIG%s to control process " PID_FMT " (%s): %m", - signal_to_string(signo), control_pid->pid, strna(comm)); - log_unit_warning_errno( - u, r, - "Failed to send signal SIG%s to control process " PID_FMT " (%s) on client request: %m", - signal_to_string(signo), control_pid->pid, strna(comm)); - } else { - log_unit_info(u, "Sent signal SIG%s to control process " PID_FMT " (%s) on client request.", - signal_to_string(signo), control_pid->pid, strna(comm)); - killed = true; - } + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) { + r = unit_kill_one(u, control_pid, "control", signo, code, value, ret_error); + RET_GATHER(ret, r); + killed = killed || r > 0; } - if (pidref_is_set(main_pid) && - IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) { - _cleanup_free_ char *comm = NULL; - (void) pidref_get_comm(main_pid, &comm); - - r = kill_or_sigqueue(main_pid, signo, code, value); - if (r < 0) { - if (ret == 0) { - ret = r; - - sd_bus_error_set_errnof( - error, r, - "Failed to send signal SIG%s to main process " PID_FMT " (%s): %m", - signal_to_string(signo), main_pid->pid, strna(comm)); - } - - log_unit_warning_errno( - u, r, - "Failed to send signal SIG%s to main process " PID_FMT " (%s) on client request: %m", - signal_to_string(signo), main_pid->pid, strna(comm)); - - } else { - log_unit_info(u, "Sent signal SIG%s to main process " PID_FMT " (%s) on client request.", - signal_to_string(signo), main_pid->pid, strna(comm)); - killed = true; - } + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) { + r = unit_kill_one(u, main_pid, "main", signo, code, value, ret >= 0 ? ret_error : NULL); + RET_GATHER(ret, r); + killed = killed || r > 0; } /* Note: if we shall enqueue rather than kill we won't do this via the cgroup mechanism, since it * doesn't really make much sense (and given that enqueued values are a relatively expensive * resource, and we shouldn't allow us to be subjects for such allocation sprees) */ - if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path && code == SI_USER) { - _cleanup_set_free_ Set *pid_set = NULL; + if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && code == SI_USER) { + CGroupRuntime *crt = unit_get_cgroup_runtime(u); - /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid ? main_pid->pid : 0, control_pid ? control_pid->pid : 0); - if (!pid_set) - return log_oom(); + if (crt && crt->cgroup_path) { + _cleanup_set_free_ Set *pid_set = NULL; - r = cg_kill_recursive(u->cgroup_path, signo, 0, pid_set, kill_common_log, u); - if (r < 0) { - if (!IN_SET(r, -ESRCH, -ENOENT)) { - if (ret == 0) { - ret = r; + /* Exclude the main/control pids from being killed via the cgroup */ + r = unit_pid_set(u, &pid_set); + if (r < 0) + return log_oom(); + r = cg_kill_recursive(crt->cgroup_path, signo, 0, pid_set, kill_common_log, u); + if (r < 0 && !IN_SET(r, -ESRCH, -ENOENT)) { + if (ret >= 0) sd_bus_error_set_errnof( - error, r, + ret_error, r, "Failed to send signal SIG%s to auxiliary processes: %m", signal_to_string(signo)); - } log_unit_warning_errno( u, r, "Failed to send signal SIG%s to auxiliary processes on client request: %m", signal_to_string(signo)); + + RET_GATHER(ret, r); } - } else - killed = true; + + killed = killed || r >= 0; + } } /* If the "fail" versions of the operation are requested, then complain if the set of processes we killed is empty */ - if (ret == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL)) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No matching processes to kill"); + if (ret >= 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL, KILL_MAIN_FAIL)) + return sd_bus_error_set_const(ret_error, BUS_ERROR_NO_SUCH_PROCESS, "No matching processes to kill"); return ret; } @@ -4316,6 +4222,21 @@ static int user_from_unit_name(Unit *u, char **ret) { return 0; } +static int unit_verify_contexts(const Unit *u, const ExecContext *ec) { + assert(u); + + if (!ec) + return 0; + + if (MANAGER_IS_USER(u->manager) && ec->dynamic_user) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "DynamicUser= enabled for user unit, which is not supported. Refusing."); + + if (ec->dynamic_user && ec->working_directory_home) + return log_unit_error_errno(u, SYNTHETIC_ERRNO(ENOEXEC), "WorkingDirectory=~ is not allowed under DynamicUser=yes. Refusing."); + + return 0; +} + int unit_patch_contexts(Unit *u) { CGroupContext *cc; ExecContext *ec; @@ -4337,16 +4258,14 @@ int unit_patch_contexts(Unit *u) { return -ENOMEM; } - if (MANAGER_IS_USER(u->manager) && - !ec->working_directory) { - + if (MANAGER_IS_USER(u->manager) && !ec->working_directory) { r = get_home_dir(&ec->working_directory); if (r < 0) return r; - /* Allow user services to run, even if the - * home directory is missing */ - ec->working_directory_missing_ok = true; + if (!ec->working_directory_home) + /* If home directory is implied by us, allow it to be missing. */ + ec->working_directory_missing_ok = true; } if (ec->private_devices) @@ -4390,8 +4309,8 @@ int unit_patch_contexts(Unit *u) { ec->restrict_suid_sgid = true; } - for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++) - exec_directory_sort(ec->directories + dt); + FOREACH_ARRAY(d, ec->directories, _EXEC_DIRECTORY_TYPE_MAX) + exec_directory_sort(d); } cc = unit_get_cgroup_context(u); @@ -4441,7 +4360,7 @@ int unit_patch_contexts(Unit *u) { } } - return 0; + return unit_verify_contexts(u, ec); } ExecContext *unit_get_exec_context(const Unit *u) { @@ -4458,7 +4377,7 @@ ExecContext *unit_get_exec_context(const Unit *u) { return (ExecContext*) ((uint8_t*) u + offset); } -KillContext *unit_get_kill_context(Unit *u) { +KillContext *unit_get_kill_context(const Unit *u) { size_t offset; assert(u); @@ -4472,7 +4391,7 @@ KillContext *unit_get_kill_context(Unit *u) { return (KillContext*) ((uint8_t*) u + offset); } -CGroupContext *unit_get_cgroup_context(Unit *u) { +CGroupContext *unit_get_cgroup_context(const Unit *u) { size_t offset; if (u->type < 0) @@ -4485,7 +4404,7 @@ CGroupContext *unit_get_cgroup_context(Unit *u) { return (CGroupContext*) ((uint8_t*) u + offset); } -ExecRuntime *unit_get_exec_runtime(Unit *u) { +ExecRuntime *unit_get_exec_runtime(const Unit *u) { size_t offset; if (u->type < 0) @@ -4498,6 +4417,19 @@ ExecRuntime *unit_get_exec_runtime(Unit *u) { return *(ExecRuntime**) ((uint8_t*) u + offset); } +CGroupRuntime *unit_get_cgroup_runtime(const Unit *u) { + size_t offset; + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->cgroup_runtime_offset; + if (offset <= 0) + return NULL; + + return *(CGroupRuntime**) ((uint8_t*) u + offset); +} + static const char* unit_drop_in_dir(Unit *u, UnitWriteFlags flags) { assert(u); @@ -4820,26 +4752,57 @@ static int operation_to_signal( } } -int unit_kill_context( +static int unit_kill_context_one( Unit *u, - KillContext *c, - KillOperation k, - PidRef* main_pid, - PidRef* control_pid, - bool main_pid_alien) { + const PidRef *pidref, + const char *type, + bool is_alien, + int sig, + bool send_sighup, + cg_kill_log_func_t log_func) { + int r; + + assert(u); + assert(type); + + /* This returns > 0 if it makes sense to wait for SIGCHLD for the process, == 0 if not. */ + + if (!pidref_is_set(pidref)) + return 0; + + if (log_func) + log_func(pidref, sig, u); + + r = pidref_kill_and_sigcont(pidref, sig); + if (r == -ESRCH) + return !is_alien; + if (r < 0) { + _cleanup_free_ char *comm = NULL; + + (void) pidref_get_comm(pidref, &comm); + return log_unit_warning_errno(u, r, "Failed to kill %s process " PID_FMT " (%s), ignoring: %m", type, pidref->pid, strna(comm)); + } + + if (send_sighup) + (void) pidref_kill(pidref, SIGHUP); + + return !is_alien; +} + +int unit_kill_context(Unit *u, KillOperation k) { bool wait_for_exit = false, send_sighup; cg_kill_log_func_t log_func = NULL; int sig, r; assert(u); - assert(c); /* Kill the processes belonging to this unit, in preparation for shutting the unit down. Returns > 0 * if we killed something worth waiting for, 0 otherwise. Do not confuse with unit_kill_common() * which is used for user-requested killing of unit processes. */ - if (c->kill_mode == KILL_NONE) + KillContext *c = unit_get_kill_context(u); + if (!c || c->kill_mode == KILL_NONE) return 0; bool noteworthy; @@ -4852,61 +4815,33 @@ int unit_kill_context( IN_SET(k, KILL_TERMINATE, KILL_TERMINATE_AND_LOG) && sig != SIGHUP; - if (pidref_is_set(main_pid)) { - if (log_func) - log_func(main_pid, sig, u); - - r = pidref_kill_and_sigcont(main_pid, sig); - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - (void) pidref_get_comm(main_pid, &comm); + bool is_alien; + PidRef *main_pid = unit_main_pid_full(u, &is_alien); + r = unit_kill_context_one(u, main_pid, "main", is_alien, sig, send_sighup, log_func); + wait_for_exit = wait_for_exit || r > 0; - log_unit_warning_errno(u, r, "Failed to kill main process " PID_FMT " (%s), ignoring: %m", main_pid->pid, strna(comm)); - } else { - if (!main_pid_alien) - wait_for_exit = true; + r = unit_kill_context_one(u, unit_control_pid(u), "control", /* is_alien = */ false, sig, send_sighup, log_func); + wait_for_exit = wait_for_exit || r > 0; - if (r != -ESRCH && send_sighup) - (void) pidref_kill(main_pid, SIGHUP); - } - } - - if (pidref_is_set(control_pid)) { - if (log_func) - log_func(control_pid, sig, u); - - r = pidref_kill_and_sigcont(control_pid, sig); - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - (void) pidref_get_comm(control_pid, &comm); - - log_unit_warning_errno(u, r, "Failed to kill control process " PID_FMT " (%s), ignoring: %m", control_pid->pid, strna(comm)); - } else { - wait_for_exit = true; - - if (r != -ESRCH && send_sighup) - (void) pidref_kill(control_pid, SIGHUP); - } - } - - if (u->cgroup_path && + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt && crt->cgroup_path && (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { _cleanup_set_free_ Set *pid_set = NULL; /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid ? main_pid->pid : 0, control_pid ? control_pid->pid : 0); - if (!pid_set) - return -ENOMEM; + r = unit_pid_set(u, &pid_set); + if (r < 0) + return r; r = cg_kill_recursive( - u->cgroup_path, + crt->cgroup_path, sig, CGROUP_SIGCONT|CGROUP_IGNORE_SELF, pid_set, log_func, u); if (r < 0) { if (!IN_SET(r, -EAGAIN, -ESRCH, -ENOENT)) - log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", empty_to_root(u->cgroup_path)); + log_unit_warning_errno(u, r, "Failed to kill control group %s, ignoring: %m", empty_to_root(crt->cgroup_path)); } else if (r > 0) { @@ -4922,14 +4857,12 @@ int unit_kill_context( wait_for_exit = true; if (send_sighup) { - set_free(pid_set); - - pid_set = unit_pid_set(main_pid ? main_pid->pid : 0, control_pid ? control_pid->pid : 0); - if (!pid_set) - return -ENOMEM; + r = unit_pid_set(u, &pid_set); + if (r < 0) + return r; (void) cg_kill_recursive( - u->cgroup_path, + crt->cgroup_path, SIGHUP, CGROUP_IGNORE_SELF, pid_set, @@ -4942,11 +4875,16 @@ int unit_kill_context( return wait_for_exit; } -int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) { +int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type) { + Hashmap **unit_map, **manager_map; int r; assert(u); assert(path); + assert(type >= 0 && type < _UNIT_MOUNT_DEPENDENCY_TYPE_MAX); + + unit_map = &u->mounts_for[type]; + manager_map = &u->manager->units_needing_mounts_for[type]; /* Registers a unit for requiring a certain path and all its prefixes. We keep a hashtable of these * paths in the unit (from the path to the UnitDependencyInfo structure indicating how to the @@ -4956,7 +4894,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) if (!path_is_absolute(path)) return -EINVAL; - if (hashmap_contains(u->requires_mounts_for, path)) /* Exit quickly if the path is already covered. */ + if (hashmap_contains(*unit_map, path)) /* Exit quickly if the path is already covered. */ return 0; /* Use the canonical form of the path as the stored key. We call path_is_normalized() @@ -4975,7 +4913,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) .origin_mask = mask }; - r = hashmap_ensure_put(&u->requires_mounts_for, &path_hash_ops, p, di.data); + r = hashmap_ensure_put(unit_map, &path_hash_ops, p, di.data); if (r < 0) return r; assert(r > 0); @@ -4985,11 +4923,11 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) PATH_FOREACH_PREFIX_MORE(prefix, path) { Set *x; - x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + x = hashmap_get(*manager_map, prefix); if (!x) { _cleanup_free_ char *q = NULL; - r = hashmap_ensure_allocated(&u->manager->units_requiring_mounts_for, &path_hash_ops); + r = hashmap_ensure_allocated(manager_map, &path_hash_ops); if (r < 0) return r; @@ -5001,7 +4939,7 @@ int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask) if (!x) return -ENOMEM; - r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + r = hashmap_put(*manager_map, q, x); if (r < 0) { set_free(x); return r; @@ -5035,8 +4973,7 @@ int unit_setup_exec_runtime(Unit *u) { if (*rt) return 0; - ec = unit_get_exec_context(u); - assert(ec); + ec = ASSERT_PTR(unit_get_exec_context(u)); r = unit_get_transitive_dependency_set(u, UNIT_ATOM_JOINS_NAMESPACE_OF, &units); if (r < 0) @@ -5073,6 +5010,21 @@ int unit_setup_exec_runtime(Unit *u) { return r; } +CGroupRuntime *unit_setup_cgroup_runtime(Unit *u) { + size_t offset; + + assert(u); + + offset = UNIT_VTABLE(u)->cgroup_runtime_offset; + assert(offset > 0); + + CGroupRuntime **rt = (CGroupRuntime**) ((uint8_t*) u + offset); + if (*rt) + return *rt; + + return (*rt = cgroup_runtime_new()); +} + bool unit_type_supported(UnitType t) { static int8_t cache[_UNIT_TYPE_MAX] = {}; /* -1: disabled, 1: enabled: 0: don't know */ int r; @@ -5178,12 +5130,14 @@ PidRef* unit_control_pid(Unit *u) { return NULL; } -PidRef* unit_main_pid(Unit *u) { +PidRef* unit_main_pid_full(Unit *u, bool *ret_is_alien) { assert(u); if (UNIT_VTABLE(u)->main_pid) - return UNIT_VTABLE(u)->main_pid(u); + return UNIT_VTABLE(u)->main_pid(u, ret_is_alien); + if (ret_is_alien) + *ret_is_alien = false; return NULL; } @@ -5393,7 +5347,6 @@ int unit_acquire_invocation_id(Unit *u) { } int unit_set_exec_params(Unit *u, ExecParameters *p) { - const char *confirm_spawn; int r; assert(u); @@ -5406,19 +5359,17 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) { p->runtime_scope = u->manager->runtime_scope; - confirm_spawn = manager_get_confirm_spawn(u->manager); - if (confirm_spawn) { - p->confirm_spawn = strdup(confirm_spawn); - if (!p->confirm_spawn) - return -ENOMEM; - } + r = strdup_to(&p->confirm_spawn, manager_get_confirm_spawn(u->manager)); + if (r < 0) + return r; p->cgroup_supported = u->manager->cgroup_supported; p->prefix = u->manager->prefix; SET_FLAG(p->flags, EXEC_PASS_LOG_UNIT|EXEC_CHOWN_DIRECTORIES, MANAGER_IS_SYSTEM(u->manager)); /* Copy parameters from unit */ - p->cgroup_path = u->cgroup_path; + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + p->cgroup_path = crt ? crt->cgroup_path : NULL; SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, unit_cgroup_delegate(u)); p->received_credentials_directory = u->manager->received_credentials_directory; @@ -5428,17 +5379,18 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) { p->fallback_smack_process_label = u->manager->defaults.smack_process_label; - if (u->manager->restrict_fs && p->bpf_outer_map_fd < 0) { - int fd = lsm_bpf_map_restrict_fs_fd(u); + if (u->manager->restrict_fs && p->bpf_restrict_fs_map_fd < 0) { + int fd = bpf_restrict_fs_map_fd(u); if (fd < 0) return fd; - p->bpf_outer_map_fd = fd; + p->bpf_restrict_fs_map_fd = fd; } p->user_lookup_fd = u->manager->user_lookup_fds[1]; + p->handoff_timestamp_fd = u->manager->handoff_timestamp_fds[1]; - p->cgroup_id = u->cgroup_id; + p->cgroup_id = crt ? crt->cgroup_id : 0; p->invocation_id = u->invocation_id; sd_id128_to_string(p->invocation_id, p->invocation_id_string); p->unit_id = strdup(u->id); @@ -5460,6 +5412,10 @@ int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret) { (void) unit_realize_cgroup(u); + CGroupRuntime *crt = unit_setup_cgroup_runtime(u); + if (!crt) + return -ENOMEM; + r = safe_fork(name, FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM, &pid); if (r < 0) return r; @@ -5482,10 +5438,10 @@ int unit_fork_helper_process(Unit *u, const char *name, PidRef *ret) { (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE); (void) ignore_signals(SIGPIPE); - if (u->cgroup_path) { - r = cg_attach_everywhere(u->manager->cgroup_supported, u->cgroup_path, 0, NULL, NULL); + if (crt->cgroup_path) { + r = cg_attach_everywhere(u->manager->cgroup_supported, crt->cgroup_path, 0, NULL, NULL); if (r < 0) { - log_unit_error_errno(u, r, "Failed to join unit cgroup %s: %m", empty_to_root(u->cgroup_path)); + log_unit_error_errno(u, r, "Failed to join unit cgroup %s: %m", empty_to_root(crt->cgroup_path)); _exit(EXIT_CGROUP); } } @@ -5880,9 +5836,10 @@ int unit_prepare_exec(Unit *u) { (void) unit_realize_cgroup(u); - if (u->reset_accounting) { + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + if (crt && crt->reset_accounting) { (void) unit_reset_accounting(u); - u->reset_accounting = false; + crt->reset_accounting = false; } unit_export_state_files(u); @@ -5942,11 +5899,13 @@ int unit_warn_leftover_processes(Unit *u, cg_kill_log_func_t log_func) { (void) unit_pick_cgroup_path(u); - if (!u->cgroup_path) + CGroupRuntime *crt = unit_get_cgroup_runtime(u); + + if (!crt || !crt->cgroup_path) return 0; return cg_kill_recursive( - u->cgroup_path, + crt->cgroup_path, /* sig= */ 0, /* flags= */ 0, /* set= */ NULL, @@ -5976,7 +5935,7 @@ bool unit_needs_console(Unit *u) { return exec_context_may_touch_console(ec); } -int unit_pid_attachable(Unit *u, PidRef *pid, sd_bus_error *error) { +int unit_pid_attachable(Unit *u, const PidRef *pid, sd_bus_error *error) { int r; assert(u); @@ -6213,19 +6172,98 @@ bool unit_can_isolate_refuse_manual(Unit *u) { return unit_can_isolate(u) && !u->refuse_manual_start; } +void unit_next_freezer_state(Unit *u, FreezerAction action, FreezerState *ret, FreezerState *ret_target) { + Unit *slice; + FreezerState curr, parent, next, tgt; + + assert(u); + assert(IN_SET(action, FREEZER_FREEZE, FREEZER_PARENT_FREEZE, + FREEZER_THAW, FREEZER_PARENT_THAW)); + assert(ret); + assert(ret_target); + + /* This function determines the correct freezer state transitions for a unit + * given the action being requested. It returns the next state, and also the "target", + * which is either FREEZER_FROZEN or FREEZER_RUNNING, depending on what actual state we + * ultimately want to achieve. */ + + curr = u->freezer_state; + slice = UNIT_GET_SLICE(u); + if (slice) + parent = slice->freezer_state; + else + parent = FREEZER_RUNNING; + + if (action == FREEZER_FREEZE) { + /* We always "promote" a freeze initiated by parent into a normal freeze */ + if (IN_SET(curr, FREEZER_FROZEN, FREEZER_FROZEN_BY_PARENT)) + next = FREEZER_FROZEN; + else + next = FREEZER_FREEZING; + } else if (action == FREEZER_THAW) { + /* Thawing is the most complicated operation here, because we can't thaw a unit + * if its parent is frozen. So we instead "demote" a normal freeze into a freeze + * initiated by parent if the parent is frozen */ + if (IN_SET(curr, FREEZER_RUNNING, FREEZER_THAWING, FREEZER_FREEZING_BY_PARENT, FREEZER_FROZEN_BY_PARENT)) + next = curr; + else if (curr == FREEZER_FREEZING) { + if (IN_SET(parent, FREEZER_RUNNING, FREEZER_THAWING)) + next = FREEZER_THAWING; + else + next = FREEZER_FREEZING_BY_PARENT; + } else { + assert(curr == FREEZER_FROZEN); + if (IN_SET(parent, FREEZER_RUNNING, FREEZER_THAWING)) + next = FREEZER_THAWING; + else + next = FREEZER_FROZEN_BY_PARENT; + } + } else if (action == FREEZER_PARENT_FREEZE) { + /* We need to avoid accidentally demoting units frozen manually */ + if (IN_SET(curr, FREEZER_FREEZING, FREEZER_FROZEN, FREEZER_FROZEN_BY_PARENT)) + next = curr; + else + next = FREEZER_FREEZING_BY_PARENT; + } else { + assert(action == FREEZER_PARENT_THAW); + + /* We don't want to thaw units from a parent if they were frozen + * manually, so for such units this action is a no-op */ + if (IN_SET(curr, FREEZER_RUNNING, FREEZER_FREEZING, FREEZER_FROZEN)) + next = curr; + else + next = FREEZER_THAWING; + } + + tgt = freezer_state_finish(next); + if (tgt == FREEZER_FROZEN_BY_PARENT) + tgt = FREEZER_FROZEN; + assert(IN_SET(tgt, FREEZER_RUNNING, FREEZER_FROZEN)); + + *ret = next; + *ret_target = tgt; +} + bool unit_can_freeze(Unit *u) { assert(u); + if (unit_has_name(u, SPECIAL_ROOT_SLICE) || unit_has_name(u, SPECIAL_INIT_SCOPE)) + return false; + if (UNIT_VTABLE(u)->can_freeze) return UNIT_VTABLE(u)->can_freeze(u); - return UNIT_VTABLE(u)->freeze; + return UNIT_VTABLE(u)->freezer_action; } void unit_frozen(Unit *u) { assert(u); - u->freezer_state = FREEZER_FROZEN; + u->freezer_state = u->freezer_state == FREEZER_FREEZING_BY_PARENT + ? FREEZER_FROZEN_BY_PARENT + : FREEZER_FROZEN; + + log_unit_debug(u, "Unit now %s.", freezer_state_to_string(u->freezer_state)); bus_unit_send_pending_freezer_message(u, false); } @@ -6235,19 +6273,19 @@ void unit_thawed(Unit *u) { u->freezer_state = FREEZER_RUNNING; + log_unit_debug(u, "Unit thawed."); + bus_unit_send_pending_freezer_message(u, false); } -static int unit_freezer_action(Unit *u, FreezerAction action) { +int unit_freezer_action(Unit *u, FreezerAction action) { UnitActiveState s; - int (*method)(Unit*); int r; assert(u); assert(IN_SET(action, FREEZER_FREEZE, FREEZER_THAW)); - method = action == FREEZER_FREEZE ? UNIT_VTABLE(u)->freeze : UNIT_VTABLE(u)->thaw; - if (!method || !cg_freezer_supported()) + if (!cg_freezer_supported() || !unit_can_freeze(u)) return -EOPNOTSUPP; if (u->job) @@ -6260,36 +6298,21 @@ static int unit_freezer_action(Unit *u, FreezerAction action) { if (s != UNIT_ACTIVE) return -EHOSTDOWN; - if ((IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING) && action == FREEZER_FREEZE) || - (u->freezer_state == FREEZER_THAWING && action == FREEZER_THAW)) + if (action == FREEZER_FREEZE && IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT)) return -EALREADY; + if (action == FREEZER_THAW && u->freezer_state == FREEZER_THAWING) + return -EALREADY; + if (action == FREEZER_THAW && IN_SET(u->freezer_state, FREEZER_FREEZING_BY_PARENT, FREEZER_FROZEN_BY_PARENT)) + return -ECHILD; - r = method(u); + r = UNIT_VTABLE(u)->freezer_action(u, action); if (r <= 0) return r; - assert(IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_THAWING)); - + assert(IN_SET(u->freezer_state, FREEZER_FREEZING, FREEZER_FREEZING_BY_PARENT, FREEZER_THAWING)); return 1; } -int unit_freeze(Unit *u) { - return unit_freezer_action(u, FREEZER_FREEZE); -} - -int unit_thaw(Unit *u) { - return unit_freezer_action(u, FREEZER_THAW); -} - -/* Wrappers around low-level cgroup freezer operations common for service and scope units */ -int unit_freeze_vtable_common(Unit *u) { - return unit_cgroup_freezer_action(u, FREEZER_FREEZE); -} - -int unit_thaw_vtable_common(Unit *u) { - return unit_cgroup_freezer_action(u, FREEZER_THAW); -} - Condition *unit_find_failed_condition(Unit *u) { Condition *failed_trigger = NULL; bool has_succeeded_trigger = false; @@ -6310,7 +6333,7 @@ Condition *unit_find_failed_condition(Unit *u) { } static const char* const collect_mode_table[_COLLECT_MODE_MAX] = { - [COLLECT_INACTIVE] = "inactive", + [COLLECT_INACTIVE] = "inactive", [COLLECT_INACTIVE_OR_FAILED] = "inactive-or-failed", }; @@ -6460,7 +6483,7 @@ int unit_compare_priority(Unit *a, Unit *b) { } const ActivationDetailsVTable * const activation_details_vtable[_UNIT_TYPE_MAX] = { - [UNIT_PATH] = &activation_details_path_vtable, + [UNIT_PATH] = &activation_details_path_vtable, [UNIT_TIMER] = &activation_details_timer_vtable, }; @@ -6596,11 +6619,7 @@ int activation_details_append_pair(ActivationDetails *details, char ***strv) { return 0; if (!isempty(details->trigger_unit_name)) { - r = strv_extend(strv, "trigger_unit"); - if (r < 0) - return r; - - r = strv_extend(strv, details->trigger_unit_name); + r = strv_extend_many(strv, "trigger_unit", details->trigger_unit_name); if (r < 0) return r; } @@ -6615,3 +6634,24 @@ int activation_details_append_pair(ActivationDetails *details, char ***strv) { } DEFINE_TRIVIAL_REF_UNREF_FUNC(ActivationDetails, activation_details, activation_details_free); + +static const char* const unit_mount_dependency_type_table[_UNIT_MOUNT_DEPENDENCY_TYPE_MAX] = { + [UNIT_MOUNT_WANTS] = "WantsMountsFor", + [UNIT_MOUNT_REQUIRES] = "RequiresMountsFor", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_mount_dependency_type, UnitMountDependencyType); + +UnitDependency unit_mount_dependency_type_to_dependency_type(UnitMountDependencyType t) { + switch (t) { + + case UNIT_MOUNT_WANTS: + return UNIT_WANTS; + + case UNIT_MOUNT_REQUIRES: + return UNIT_REQUIRES; + + default: + assert_not_reached(); + } +} diff --git a/src/core/unit.h b/src/core/unit.h index 60bc2e3..b135fec 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include #include #include @@ -8,6 +9,14 @@ #include "sd-id128.h" +/* Circular dependency with manager.h, needs to be defined before local includes */ +typedef enum UnitMountDependencyType { + UNIT_MOUNT_WANTS, + UNIT_MOUNT_REQUIRES, + _UNIT_MOUNT_DEPENDENCY_TYPE_MAX, + _UNIT_MOUNT_DEPENDENCY_TYPE_INVALID = -EINVAL, +} UnitMountDependencyType; + #include "bpf-program.h" #include "cgroup.h" #include "condition.h" @@ -55,7 +64,11 @@ static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { } static inline bool UNIT_IS_LOAD_COMPLETE(UnitLoadState t) { - return t >= 0 && t < _UNIT_LOAD_STATE_MAX && t != UNIT_STUB && t != UNIT_MERGED; + return t >= 0 && t < _UNIT_LOAD_STATE_MAX && !IN_SET(t, UNIT_STUB, UNIT_MERGED); +} + +static inline bool UNIT_IS_LOAD_ERROR(UnitLoadState t) { + return IN_SET(t, UNIT_NOT_FOUND, UNIT_BAD_SETTING, UNIT_ERROR); } /* Stores the 'reason' a dependency was created as a bit mask, i.e. due to which configuration source it came to be. We @@ -199,6 +212,7 @@ struct UnitRef { LIST_FIELDS(UnitRef, refs_by_target); }; +/* The generic, dynamic definition of the unit */ typedef struct Unit { Manager *manager; @@ -216,9 +230,9 @@ typedef struct Unit { * Hashmap(UnitDependency → Hashmap(Unit* → UnitDependencyInfo)) */ Hashmap *dependencies; - /* Similar, for RequiresMountsFor= path dependencies. The key is the path, the value the - * UnitDependencyInfo type */ - Hashmap *requires_mounts_for; + /* Similar, for RequiresMountsFor= and WantsMountsFor= path dependencies. The key is the path, the + * value the UnitDependencyInfo type */ + Hashmap *mounts_for[_UNIT_MOUNT_DEPENDENCY_TYPE_MAX]; char *description; char **documentation; @@ -361,74 +375,6 @@ typedef struct Unit { UnitFileState unit_file_state; PresetAction unit_file_preset; - /* Where the cpu.stat or cpuacct.usage was at the time the unit was started */ - nsec_t cpu_usage_base; - nsec_t cpu_usage_last; /* the most recently read value */ - - /* Most recently read value of memory accounting metrics */ - uint64_t memory_accounting_last[_CGROUP_MEMORY_ACCOUNTING_METRIC_CACHED_LAST + 1]; - - /* The current counter of OOM kills initiated by systemd-oomd */ - uint64_t managed_oom_kill_last; - - /* The current counter of the oom_kill field in the memory.events cgroup attribute */ - uint64_t oom_kill_last; - - /* Where the io.stat data was at the time the unit was started */ - uint64_t io_accounting_base[_CGROUP_IO_ACCOUNTING_METRIC_MAX]; - uint64_t io_accounting_last[_CGROUP_IO_ACCOUNTING_METRIC_MAX]; /* the most recently read value */ - - /* Counterparts in the cgroup filesystem */ - char *cgroup_path; - uint64_t cgroup_id; - CGroupMask cgroup_realized_mask; /* In which hierarchies does this unit's cgroup exist? (only relevant on cgroup v1) */ - CGroupMask cgroup_enabled_mask; /* Which controllers are enabled (or more correctly: enabled for the children) for this unit's cgroup? (only relevant on cgroup v2) */ - CGroupMask cgroup_invalidated_mask; /* A mask specifying controllers which shall be considered invalidated, and require re-realization */ - CGroupMask cgroup_members_mask; /* A cache for the controllers required by all children of this cgroup (only relevant for slice units) */ - - /* Inotify watch descriptors for watching cgroup.events and memory.events on cgroupv2 */ - int cgroup_control_inotify_wd; - int cgroup_memory_inotify_wd; - - /* Device Controller BPF program */ - BPFProgram *bpf_device_control_installed; - - /* IP BPF Firewalling/accounting */ - int ip_accounting_ingress_map_fd; - int ip_accounting_egress_map_fd; - uint64_t ip_accounting_extra[_CGROUP_IP_ACCOUNTING_METRIC_MAX]; - - int ipv4_allow_map_fd; - int ipv6_allow_map_fd; - int ipv4_deny_map_fd; - int ipv6_deny_map_fd; - BPFProgram *ip_bpf_ingress, *ip_bpf_ingress_installed; - BPFProgram *ip_bpf_egress, *ip_bpf_egress_installed; - - Set *ip_bpf_custom_ingress; - Set *ip_bpf_custom_ingress_installed; - Set *ip_bpf_custom_egress; - Set *ip_bpf_custom_egress_installed; - - /* BPF programs managed (e.g. loaded to kernel) by an entity external to systemd, - * attached to unit cgroup by provided program fd and attach type. */ - Hashmap *bpf_foreign_by_key; - - FDSet *initial_socket_bind_link_fds; -#if BPF_FRAMEWORK - /* BPF links to BPF programs attached to cgroup/bind{4|6} hooks and - * responsible for allowing or denying a unit to bind(2) to a socket - * address. */ - struct bpf_link *ipv4_socket_bind_link; - struct bpf_link *ipv6_socket_bind_link; -#endif - - FDSet *initial_restric_ifaces_link_fds; -#if BPF_FRAMEWORK - struct bpf_link *restrict_ifaces_ingress_bpf_link; - struct bpf_link *restrict_ifaces_egress_bpf_link; -#endif - /* Low-priority event source which is used to remove watched PIDs that have gone away, and subscribe to any new * ones which might have appeared. */ sd_event_source *rewatch_pids_event_source; @@ -499,12 +445,6 @@ typedef struct Unit { bool in_audit:1; bool on_console:1; - bool cgroup_realized:1; - bool cgroup_members_mask_valid:1; - - /* Reset cgroup accounting next time we fork something off */ - bool reset_accounting:1; - bool start_limit_hit:1; /* Did we already invoke unit_coldplug() for this unit? */ @@ -520,9 +460,6 @@ typedef struct Unit { bool exported_log_ratelimit_interval:1; bool exported_log_ratelimit_burst:1; - /* Whether we warned about clamping the CPU quota period */ - bool warned_clamping_cpu_quota_period:1; - /* When writing transient unit files, stores which section we stored last. If < 0, we didn't write any yet. If * == 0 we are in the [Unit] section, if > 0 we are in the unit type-specific section. */ signed int last_section_private:2; @@ -568,6 +505,7 @@ static inline bool UNIT_WRITE_FLAGS_NOOP(UnitWriteFlags flags) { #include "kill.h" +/* The static const, immutable data about a specific unit type */ typedef struct UnitVTable { /* How much memory does an object of this unit type need */ size_t object_size; @@ -584,11 +522,14 @@ typedef struct UnitVTable { * KillContext is found, if the unit type has that */ size_t kill_context_offset; - /* If greater than 0, the offset into the object where the - * pointer to ExecSharedRuntime is found, if the unit type has - * that */ + /* If greater than 0, the offset into the object where the pointer to ExecRuntime 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 CGroupRuntime is found, if the + * unit type has that */ + size_t cgroup_runtime_offset; + /* The name of the configuration file section with the private settings of this unit */ const char *private_section; @@ -633,9 +574,9 @@ typedef struct UnitVTable { /* Clear out the various runtime/state/cache/logs/configuration data */ int (*clean)(Unit *u, ExecCleanMask m); - /* Freeze the unit */ - int (*freeze)(Unit *u); - int (*thaw)(Unit *u); + /* Freeze or thaw the unit. Returns > 0 to indicate that the request will be handled asynchronously; unit_frozen + * or unit_thawed should be called once the operation is done. Returns 0 if done successfully, or < 0 on error. */ + int (*freezer_action)(Unit *u, FreezerAction a); bool (*can_freeze)(Unit *u); /* Return which kind of data can be cleaned */ @@ -691,6 +632,9 @@ typedef struct UnitVTable { /* Called whenever a process of this unit sends us a message */ void (*notify_message)(Unit *u, const struct ucred *ucred, char * const *tags, FDSet *fds); + /* Called whenever we learn a handoff timestamp */ + void (*notify_handoff_timestamp)(Unit *u, const struct ucred *ucred, const dual_timestamp *ts); + /* Called whenever a name this Unit registered for comes or goes away. */ void (*bus_name_owner_change)(Unit *u, const char *new_owner); @@ -722,10 +666,10 @@ typedef struct UnitVTable { /* Returns the start timeout of a unit */ usec_t (*get_timeout_start_usec)(Unit *u); - /* Returns the main PID if there is any defined, or 0. */ - PidRef* (*main_pid)(Unit *u); + /* Returns the main PID if there is any defined, or NULL. */ + PidRef* (*main_pid)(Unit *u, bool *ret_is_alien); - /* Returns the control PID if there is any defined, or 0. */ + /* Returns the control PID if there is any defined, or NULL. */ PidRef* (*control_pid)(Unit *u); /* Returns true if the unit currently needs access to the console */ @@ -794,6 +738,9 @@ typedef struct UnitVTable { /* If true, we'll notify plymouth about this unit */ bool notify_plymouth; + /* If true, we'll notify a surrounding VMM/container manager about this unit becoming available */ + bool notify_supervisor; + /* The audit events to generate on start + stop (or 0 if none shall be generated) */ int audit_start_message_type; int audit_stop_message_type; @@ -903,7 +850,6 @@ bool unit_has_name(const Unit *u, const char *name); UnitActiveState unit_active_state(Unit *u); FreezerState unit_freezer_state(Unit *u); -int unit_freezer_state_kernel(Unit *u, FreezerState *ret); const char* unit_sub_state_to_string(Unit *u); @@ -916,17 +862,18 @@ int unit_start(Unit *u, ActivationDetails *details); int unit_stop(Unit *u); int unit_reload(Unit *u); -int unit_kill(Unit *u, KillWho w, int signo, int code, int value, sd_bus_error *error); +int unit_kill(Unit *u, KillWho w, int signo, int code, int value, sd_bus_error *ret_error); void unit_notify_cgroup_oom(Unit *u, bool managed_oom); void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); -int unit_watch_pidref(Unit *u, PidRef *pid, bool exclusive); +int unit_watch_pidref(Unit *u, const PidRef *pid, bool exclusive); int unit_watch_pid(Unit *u, pid_t pid, bool exclusive); -void unit_unwatch_pidref(Unit *u, PidRef *pid); +void unit_unwatch_pidref(Unit *u, const PidRef *pid); void unit_unwatch_pid(Unit *u, pid_t pid); void unit_unwatch_all_pids(Unit *u); +void unit_unwatch_pidref_done(Unit *u, PidRef *pidref); int unit_enqueue_rewatch_pids(Unit *u); void unit_dequeue_rewatch_pids(Unit *u); @@ -984,12 +931,14 @@ void unit_ref_unset(UnitRef *ref); int unit_patch_contexts(Unit *u); ExecContext *unit_get_exec_context(const Unit *u) _pure_; -KillContext *unit_get_kill_context(Unit *u) _pure_; -CGroupContext *unit_get_cgroup_context(Unit *u) _pure_; +KillContext *unit_get_kill_context(const Unit *u) _pure_; +CGroupContext *unit_get_cgroup_context(const Unit *u) _pure_; -ExecRuntime *unit_get_exec_runtime(Unit *u) _pure_; +ExecRuntime *unit_get_exec_runtime(const Unit *u) _pure_; +CGroupRuntime *unit_get_cgroup_runtime(const Unit *u) _pure_; int unit_setup_exec_runtime(Unit *u); +CGroupRuntime *unit_setup_cgroup_runtime(Unit *u); const char* unit_escape_setting(const char *s, UnitWriteFlags flags, char **buf); char* unit_concat_strv(char **l, UnitWriteFlags flags); @@ -997,11 +946,11 @@ char* unit_concat_strv(char **l, UnitWriteFlags flags); int unit_write_setting(Unit *u, UnitWriteFlags flags, const char *name, const char *data); int unit_write_settingf(Unit *u, UnitWriteFlags mode, const char *name, const char *format, ...) _printf_(4,5); -int unit_kill_context(Unit *u, KillContext *c, KillOperation k, PidRef *main_pid, PidRef *control_pid, bool main_pid_alien); +int unit_kill_context(Unit *u, KillOperation k); int unit_make_transient(Unit *u); -int unit_require_mounts_for(Unit *u, const char *path, UnitDependencyMask mask); +int unit_add_mounts_for(Unit *u, const char *path, UnitDependencyMask mask, UnitMountDependencyType type); bool unit_type_supported(UnitType t); @@ -1012,7 +961,10 @@ bool unit_is_upheld_by_active(Unit *u, Unit **ret_culprit); bool unit_is_bound_by_inactive(Unit *u, Unit **ret_culprit); PidRef* unit_control_pid(Unit *u); -PidRef* unit_main_pid(Unit *u); +PidRef* unit_main_pid_full(Unit *u, bool *ret_is_alien); +static inline PidRef* unit_main_pid(Unit *u) { + return unit_main_pid_full(u, NULL); +} void unit_warn_if_dir_nonempty(Unit *u, const char* where); int unit_fail_if_noncanonical(Unit *u, const char* where); @@ -1046,7 +998,7 @@ int unit_warn_leftover_processes(Unit *u, cg_kill_log_func_t log_func); bool unit_needs_console(Unit *u); -int unit_pid_attachable(Unit *unit, PidRef *pid, sd_bus_error *error); +int unit_pid_attachable(Unit *unit, const PidRef *pid, sd_bus_error *error); static inline bool unit_has_job_type(Unit *u, JobType type) { return u && u->job && u->job->type == type; @@ -1086,21 +1038,21 @@ bool unit_can_stop_refuse_manual(Unit *u); bool unit_can_isolate_refuse_manual(Unit *u); bool unit_can_freeze(Unit *u); -int unit_freeze(Unit *u); +int unit_freezer_action(Unit *u, FreezerAction action); +void unit_next_freezer_state(Unit *u, FreezerAction a, FreezerState *ret, FreezerState *ret_tgt); void unit_frozen(Unit *u); - -int unit_thaw(Unit *u); void unit_thawed(Unit *u); -int unit_freeze_vtable_common(Unit *u); -int unit_thaw_vtable_common(Unit *u); - Condition *unit_find_failed_condition(Unit *u); int unit_arm_timer(Unit *u, sd_event_source **source, bool relative, usec_t usec, sd_event_time_handler_t handler); int unit_compare_priority(Unit *a, Unit *b); +UnitMountDependencyType unit_mount_dependency_type_from_string(const char *s) _const_; +const char* unit_mount_dependency_type_to_string(UnitMountDependencyType t) _const_; +UnitDependency unit_mount_dependency_type_to_dependency_type(UnitMountDependencyType t) _pure_; + /* Macros which append UNIT= or USER_UNIT= to the message */ #define log_unit_full_errno_zerook(unit, level, error, ...) \ diff --git a/src/coredump/coredump-vacuum.c b/src/coredump/coredump-vacuum.c index 7e0c98c..8e2febd 100644 --- a/src/coredump/coredump-vacuum.c +++ b/src/coredump/coredump-vacuum.c @@ -179,18 +179,12 @@ int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) { c = hashmap_get(h, UID_TO_PTR(uid)); if (c) { - if (t < c->oldest_mtime) { - char *n; - - n = strdup(de->d_name); - if (!n) - return log_oom(); - - free_and_replace(c->oldest_file, n); + r = free_and_strdup_warn(&c->oldest_file, de->d_name); + if (r < 0) + return r; c->oldest_mtime = t; } - } else { _cleanup_(vacuum_candidate_freep) VacuumCandidate *n = NULL; @@ -198,10 +192,9 @@ int coredump_vacuum(int exclude_fd, uint64_t keep_free, uint64_t max_use) { if (!n) return log_oom(); - n->oldest_file = strdup(de->d_name); - if (!n->oldest_file) - return log_oom(); - + r = free_and_strdup_warn(&n->oldest_file, de->d_name); + if (r < 0) + return r; n->oldest_mtime = t; r = hashmap_put(h, UID_TO_PTR(uid), n); diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 32c1766..a8ee8e0 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -51,7 +51,7 @@ #include "strv.h" #include "sync-util.h" #include "tmpfile-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" /* The maximum size up to which we process coredumps. We use 1G on 32-bit systems, and 32G on 64-bit systems */ @@ -178,8 +178,8 @@ static int parse_config(void) { int r; - r = config_parse_config_file( - "coredump.conf", + r = config_parse_standard_file_with_dropins( + "systemd/coredump.conf", "Coredump\0", config_item_table_lookup, items, @@ -630,8 +630,7 @@ static int allocate_journal_field(int fd, size_t size, char **ret, size_t *ret_s if (n < 0) return log_error_errno((int) n, "Failed to read core data: %m"); if ((size_t) n < size) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Core data too short."); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Core data too short."); *ret = TAKE_PTR(field); *ret_size = size + 9; @@ -1479,7 +1478,7 @@ static int forward_coredump_to_container(Context *context) { char buf[DECIMAL_STR_MAX(pid_t)]; const char *t = context->meta[i]; - switch(i) { + switch (i) { case META_ARGV_PID: xsprintf(buf, PID_FMT, ucred.pid); @@ -1550,7 +1549,7 @@ static int forward_coredump_to_container(Context *context) { if (r < 0) return log_debug_errno(r, "Failed to wait for child to terminate: %m"); if (r != EXIT_SUCCESS) - return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to process coredump in container."); return 0; } @@ -1558,7 +1557,7 @@ static int forward_coredump_to_container(Context *context) { static int process_kernel(int argc, char* argv[]) { _cleanup_(iovw_free_freep) struct iovec_wrapper *iovw = NULL; Context context = {}; - int r; + int r, signo; /* When we're invoked by the kernel, stdout/stderr are closed which is dangerous because the fds * could get reallocated. To avoid hard to debug issues, let's instead bind stdout/stderr to @@ -1587,6 +1586,12 @@ static int process_kernel(int argc, char* argv[]) { /* OK, now we know it's not the journal, hence we can make use of it now. */ log_set_target_and_open(LOG_TARGET_JOURNAL_OR_KMSG); + /* Log minimal metadata now, so it is not lost if the system is about to shut down. */ + log_info("Process %s (%s) of user %s terminated abnormally with signal %s/%s, processing...", + context.meta[META_ARGV_PID], context.meta[META_COMM], + context.meta[META_ARGV_UID], context.meta[META_ARGV_SIGNAL], + strna(safe_atoi(context.meta[META_ARGV_SIGNAL], &signo) >= 0 ? signal_to_string(signo) : NULL)); + r = in_same_namespace(getpid_cached(), context.pid, NAMESPACE_PID); if (r < 0) log_debug_errno(r, "Failed to check pidns of crashing process, ignoring: %m"); diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 84d4531..3a3cd7d 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -67,34 +67,38 @@ static bool arg_all = false; static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_debugger_args, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image, 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; - const char* prefix, *pattern; - pid_t pid; + const char *field; int r; if (strchr(match, '=')) - prefix = ""; - else if (strchr(match, '/')) { + field = NULL; + else if (is_path(match)) { r = path_make_absolute_cwd(match, &p); if (r < 0) return log_error_errno(r, "path_make_absolute_cwd(\"%s\"): %m", match); match = p; - prefix = "COREDUMP_EXE="; - } else if (parse_pid(match, &pid) >= 0) - prefix = "COREDUMP_PID="; + field = "COREDUMP_EXE"; + } else if (parse_pid(match, NULL) >= 0) + field = "COREDUMP_PID"; else - prefix = "COREDUMP_COMM="; + field = "COREDUMP_COMM"; - pattern = strjoina(prefix, match); - log_debug("Adding match: %s", pattern); - r = sd_journal_add_match(j, pattern, 0); + log_debug("Adding match: %s%s%s", strempty(field), field ? "=" : "", match); + if (field) + r = journal_add_match_pair(j, field, match); + else + r = sd_journal_add_match(j, match, SIZE_MAX); if (r < 0) - return log_error_errno(r, "Failed to add match \"%s\": %m", match); + return log_error_errno(r, "Failed to add match \"%s%s%s\": %m", + strempty(field), field ? "=" : "", match); return 0; } @@ -102,11 +106,11 @@ static int add_match(sd_journal *j, const char *match) { static int add_matches(sd_journal *j, char **matches) { int r; - r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, 0); + r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR, SIZE_MAX); if (r < 0) return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_COREDUMP_STR); - r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR, 0); + r = sd_journal_add_match(j, "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR, SIZE_MAX); if (r < 0) return log_error_errno(r, "Failed to add match \"%s\": %m", "MESSAGE_ID=" SD_MESSAGE_BACKTRACE_STR); @@ -126,19 +130,19 @@ static int acquire_journal(sd_journal **ret, char **matches) { assert(ret); if (arg_directory) { - r = sd_journal_open_directory(&j, arg_directory, 0); + r = sd_journal_open_directory(&j, arg_directory, SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journals in directory: %s: %m", arg_directory); } else if (arg_root) { - r = sd_journal_open_directory(&j, arg_root, SD_JOURNAL_OS_ROOT); + r = sd_journal_open_directory(&j, arg_root, SD_JOURNAL_OS_ROOT | SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journals in root directory: %s: %m", arg_root); } else if (arg_file) { - r = sd_journal_open_files(&j, (const char**)arg_file, 0); + r = sd_journal_open_files(&j, (const char**)arg_file, SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal files: %m"); } else { - r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, arg_all ? 0 : SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); } @@ -875,7 +879,7 @@ static int dump_list(int argc, char **argv, void *userdata) { verb_is_info = argc >= 1 && streq(argv[0], "info"); - r = acquire_journal(&j, argv + 1); + r = acquire_journal(&j, strv_skip(argv, 1)); if (r < 0) return r; @@ -1126,7 +1130,7 @@ static int dump_core(int argc, char **argv, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --field/-F only makes sense with list"); - r = acquire_journal(&j, argv + 1); + r = acquire_journal(&j, strv_skip(argv, 1)); if (r < 0) return r; @@ -1199,7 +1203,7 @@ static int run_debug(int argc, char **argv, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --field/-F only makes sense with list"); - r = acquire_journal(&j, argv + 1); + r = acquire_journal(&j, strv_skip(argv, 1)); if (r < 0) return r; @@ -1240,7 +1244,7 @@ static int run_debug(int argc, char **argv, void *userdata) { if (r < 0) return r; - r = strv_extend_strv(&debugger_call, STRV_MAKE(exe, "-c", path), false); + r = strv_extend_many(&debugger_call, exe, "-c", path); if (r < 0) return log_oom(); @@ -1249,14 +1253,14 @@ static int run_debug(int argc, char **argv, void *userdata) { const char *sysroot_cmd; sysroot_cmd = strjoina("set sysroot ", arg_root); - r = strv_extend_strv(&debugger_call, STRV_MAKE("-iex", sysroot_cmd), false); + r = strv_extend_many(&debugger_call, "-iex", sysroot_cmd); if (r < 0) return log_oom(); } else if (streq(arg_debugger, "lldb")) { const char *sysroot_cmd; sysroot_cmd = strjoina("platform select --sysroot ", arg_root, " host"); - r = strv_extend_strv(&debugger_call, STRV_MAKE("-O", sysroot_cmd), false); + r = strv_extend_many(&debugger_call, "-O", sysroot_cmd); if (r < 0) return log_oom(); } @@ -1392,7 +1396,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_VALIDATE_OS, + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/coredump/meson.build b/src/coredump/meson.build index a699746..bb81149 100644 --- a/src/coredump/meson.build +++ b/src/coredump/meson.build @@ -5,15 +5,10 @@ systemd_coredump_sources = files( 'coredump-vacuum.c', ) -common_link_with = [ - libshared, - libbasic_compress, -] - common_dependencies = [ - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ] @@ -22,7 +17,7 @@ executables += [ 'name' : 'systemd-coredump', 'conditions' : ['ENABLE_COREDUMP'], 'sources' : systemd_coredump_sources, - 'link_with' : common_link_with, + 'link_with' : [libshared], 'dependencies' : common_dependencies + [libacl], }, executable_template + { @@ -30,7 +25,7 @@ executables += [ 'public' : true, 'conditions' : ['ENABLE_COREDUMP'], 'sources' : files('coredumpctl.c'), - 'link_with' : common_link_with, + 'link_with' : [libshared], 'dependencies' : common_dependencies, }, test_template + { diff --git a/src/creds/creds.c b/src/creds/creds.c index 10d1171..1c8d957 100644 --- a/src/creds/creds.c +++ b/src/creds/creds.c @@ -4,6 +4,7 @@ #include #include "build.h" +#include "bus-polkit.h" #include "creds-util.h" #include "dirent-util.h" #include "escape.h" @@ -24,6 +25,9 @@ #include "terminal-util.h" #include "tpm2-pcr.h" #include "tpm2-util.h" +#include "user-util.h" +#include "varlink.h" +#include "varlink-io.systemd.Credentials.h" #include "verbs.h" typedef enum TranscodeMode { @@ -54,6 +58,9 @@ static usec_t arg_timestamp = USEC_INFINITY; static usec_t arg_not_after = USEC_INFINITY; static bool arg_pretty = false; static bool arg_quiet = false; +static bool arg_varlink = false; +static uid_t arg_uid = UID_INVALID; +static bool arg_allow_null = false; STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); @@ -228,7 +235,7 @@ static int verb_list(int argc, char **argv, void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)"); } - if ((arg_json_format_flags & JSON_FORMAT_OFF) && table_get_rows(t) <= 1) { + if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && table_isempty(t)) { log_info("No credentials"); return 0; } @@ -311,7 +318,7 @@ static int print_newline(FILE *f, const char *data, size_t l) { /* Don't bother unless this is a tty */ fd = fileno(f); - if (fd >= 0 && isatty(fd) <= 0) + if (fd >= 0 && !isatty_safe(fd)) return 0; if (fputc('\n', f) != '\n') @@ -375,9 +382,7 @@ static int verb_cat(int argc, char **argv, void *userdata) { int encrypted; if (!credential_name_valid(*cn)) { - log_error("Credential name '%s' is not valid.", *cn); - if (ret >= 0) - ret = -EINVAL; + RET_GATHER(ret, log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' is not valid.", *cn)); continue; } @@ -402,36 +407,41 @@ static int verb_cat(int argc, char **argv, void *userdata) { if (r >= 0) /* Found */ break; - log_error_errno(r, "Failed to read credential '%s': %m", *cn); - if (ret >= 0) - ret = r; + RET_GATHER(ret, log_error_errno(r, "Failed to read credential '%s': %m", *cn)); } if (encrypted >= 2) { /* Found nowhere */ - log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn); - if (ret >= 0) - ret = -ENOENT; - + RET_GATHER(ret, log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn)); continue; } if (encrypted) { - _cleanup_(erase_and_freep) void *plaintext = NULL; - size_t plaintext_size; - - r = decrypt_credential_and_warn( - *cn, - timestamp, - arg_tpm2_device, - arg_tpm2_signature, - data, size, - &plaintext, &plaintext_size); + _cleanup_(iovec_done_erase) struct iovec plaintext = {}; + + if (geteuid() != 0) + r = ipc_decrypt_credential( + *cn, + timestamp, + uid_is_valid(arg_uid) ? arg_uid : getuid(), + &IOVEC_MAKE(data, size), + CREDENTIAL_ANY_SCOPE, + &plaintext); + else + r = decrypt_credential_and_warn( + *cn, + timestamp, + arg_tpm2_device, + arg_tpm2_signature, + uid_is_valid(arg_uid) ? arg_uid : getuid(), + &IOVEC_MAKE(data, size), + CREDENTIAL_ANY_SCOPE, + &plaintext); if (r < 0) return r; erase_and_free(data); - data = TAKE_PTR(plaintext); - size = plaintext_size; + data = TAKE_PTR(plaintext.iov_base); + size = plaintext.iov_len; } r = write_blob(stdout, data, size); @@ -443,11 +453,9 @@ static int verb_cat(int argc, char **argv, void *userdata) { } static int verb_encrypt(int argc, char **argv, void *userdata) { + _cleanup_(iovec_done_erase) struct iovec plaintext = {}, output = {}; _cleanup_free_ char *base64_buf = NULL, *fname = NULL; - _cleanup_(erase_and_freep) char *plaintext = NULL; const char *input_path, *output_path, *name; - _cleanup_free_ void *output = NULL; - size_t plaintext_size, output_size; ssize_t base64_size; usec_t timestamp; int r; @@ -457,9 +465,9 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { input_path = empty_or_dash(argv[1]) ? NULL : argv[1]; if (input_path) - r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &plaintext, &plaintext_size); + r = read_full_file_full(AT_FDCWD, input_path, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &plaintext.iov_base, &plaintext.iov_len); else - r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, &plaintext, &plaintext_size); + r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_SIZE_MAX, READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &plaintext.iov_base, &plaintext.iov_len); if (r == -E2BIG) return log_error_errno(r, "Plaintext too long for credential (allowed size: %zu).", (size_t) CREDENTIAL_SIZE_MAX); if (r < 0) @@ -489,21 +497,33 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { if (arg_not_after != USEC_INFINITY && arg_not_after < timestamp) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential is invalidated before it is valid."); - r = encrypt_credential_and_warn( - arg_with_key, - name, - timestamp, - arg_not_after, - arg_tpm2_device, - arg_tpm2_pcr_mask, - arg_tpm2_public_key, - arg_tpm2_public_key_pcr_mask, - plaintext, plaintext_size, - &output, &output_size); + if (geteuid() != 0) + r = ipc_encrypt_credential( + name, + timestamp, + arg_not_after, + arg_uid, + &plaintext, + /* flags= */ 0, + &output); + else + r = encrypt_credential_and_warn( + arg_with_key, + name, + timestamp, + arg_not_after, + arg_tpm2_device, + arg_tpm2_pcr_mask, + arg_tpm2_public_key, + arg_tpm2_public_key_pcr_mask, + arg_uid, + &plaintext, + /* flags= */ 0, + &output); if (r < 0) return r; - base64_size = base64mem_full(output, output_size, arg_pretty ? 69 : 79, &base64_buf); + base64_size = base64mem_full(output.iov_base, output.iov_len, arg_pretty ? 69 : 79, &base64_buf); if (base64_size < 0) return base64_size; @@ -539,11 +559,10 @@ static int verb_encrypt(int argc, char **argv, void *userdata) { } static int verb_decrypt(int argc, char **argv, void *userdata) { - _cleanup_(erase_and_freep) void *plaintext = NULL; - _cleanup_free_ char *input = NULL, *fname = NULL; + _cleanup_(iovec_done_erase) struct iovec input = {}, plaintext = {}; + _cleanup_free_ char *fname = NULL; _cleanup_fclose_ FILE *output_file = NULL; const char *input_path, *output_path, *name; - size_t input_size, plaintext_size; usec_t timestamp; FILE *f; int r; @@ -553,9 +572,9 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { input_path = empty_or_dash(argv[1]) ? NULL : argv[1]; if (input_path) - r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, &input, &input_size); + r = read_full_file_full(AT_FDCWD, argv[1], UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, NULL, (char**) &input, &input.iov_len); else - r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, &input, &input_size); + r = read_full_stream_full(stdin, NULL, UINT64_MAX, CREDENTIAL_ENCRYPTED_SIZE_MAX, READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, (char**) &input, &input.iov_len); if (r == -E2BIG) return log_error_errno(r, "Data too long for encrypted credential (allowed size: %zu).", (size_t) CREDENTIAL_ENCRYPTED_SIZE_MAX); if (r < 0) @@ -582,13 +601,24 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME); - r = decrypt_credential_and_warn( - name, - timestamp, - arg_tpm2_device, - arg_tpm2_signature, - input, input_size, - &plaintext, &plaintext_size); + if (geteuid() != 0) + r = ipc_decrypt_credential( + name, + timestamp, + arg_uid, + &input, + /* flags= */ 0, + &plaintext); + else + r = decrypt_credential_and_warn( + name, + timestamp, + arg_tpm2_device, + arg_tpm2_signature, + arg_uid, + &input, + arg_allow_null ? CREDENTIAL_ALLOW_NULL : 0, + &plaintext); if (r < 0) return r; @@ -601,7 +631,7 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { } else f = stdout; - r = write_blob(f, plaintext, plaintext_size); + r = write_blob(f, plaintext.iov_base, plaintext.iov_len); if (r < 0) return r; @@ -609,14 +639,14 @@ static int verb_decrypt(int argc, char **argv, void *userdata) { } static int verb_setup(int argc, char **argv, void *userdata) { - size_t size; + _cleanup_(iovec_done_erase) struct iovec host_key = {}; int r; - r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, NULL, &size); + r = get_credential_host_secret(CREDENTIAL_SECRET_GENERATE|CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED, &host_key); if (r < 0) return log_error_errno(r, "Failed to setup credentials host key: %m"); - log_info("%zu byte credentials host key set up.", size); + log_info("%zu byte credentials host key set up.", host_key.iov_len); return EXIT_SUCCESS; } @@ -689,7 +719,7 @@ static int verb_help(int argc, char **argv, void *userdata) { " --timestamp=TIME Include specified timestamp in encrypted credential\n" " --not-after=TIME Include specified invalidation time in encrypted\n" " credential\n" - " --with-key=host|tpm2|host+tpm2|tpm2-absent|auto|auto-initrd\n" + " --with-key=host|tpm2|host+tpm2|null|auto|auto-initrd\n" " Which keys to encrypt with\n" " -H Shortcut for --with-key=host\n" " -T Shortcut for --with-key=tpm2\n" @@ -703,13 +733,17 @@ static int verb_help(int argc, char **argv, void *userdata) { " Specify TPM2 PCRs to seal against (public key)\n" " --tpm2-signature=PATH\n" " Specify signature for public key PCR policy\n" + " --user Select user-scoped credential encryption\n" + " --uid=UID Select user for scoped credentials\n" + " --allow-null Allow decrypting credentials with empty key\n" " -q --quiet Suppress output for 'has-tpm2' verb\n" - "\nSee the %2$s for details.\n" - , program_invocation_short_name - , link - , ansi_underline(), ansi_normal() - , ansi_highlight(), ansi_normal() - ); + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); return 0; } @@ -733,6 +767,9 @@ static int parse_argv(int argc, char *argv[]) { ARG_NAME, ARG_TIMESTAMP, ARG_NOT_AFTER, + ARG_USER, + ARG_UID, + ARG_ALLOW_NULL, }; static const struct option options[] = { @@ -755,6 +792,9 @@ static int parse_argv(int argc, char *argv[]) { { "timestamp", required_argument, NULL, ARG_TIMESTAMP }, { "not-after", required_argument, NULL, ARG_NOT_AFTER }, { "quiet", no_argument, NULL, 'q' }, + { "user", no_argument, NULL, ARG_USER }, + { "uid", required_argument, NULL, ARG_UID }, + { "allow-null", no_argument, NULL, ARG_ALLOW_NULL }, {} }; @@ -838,8 +878,8 @@ static int parse_argv(int argc, char *argv[]) { arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC; else if (STR_IN_SET(optarg, "host+tpm2-with-public-key", "tpm2-with-public-key+host")) arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK; - else if (streq(optarg, "tpm2-absent")) - arg_with_key = CRED_AES256_GCM_BY_TPM2_ABSENT; + else if (STR_IN_SET(optarg, "null", "tpm2-absent")) + arg_with_key = CRED_AES256_GCM_BY_NULL; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown key type: %s", optarg); @@ -916,6 +956,36 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_USER: + if (!uid_is_valid(arg_uid)) + arg_uid = getuid(); + + break; + + case ARG_UID: + if (isempty(optarg)) + arg_uid = UID_INVALID; + else if (streq(optarg, "self")) + arg_uid = getuid(); + else { + const char *name = optarg; + + r = get_user_creds( + &name, + &arg_uid, + /* ret_gid= */ NULL, + /* ret_home= */ NULL, + /* ret_shell= */ NULL, + /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to resolve user '%s': %m", optarg); + } + break; + + case ARG_ALLOW_NULL: + arg_allow_null = true; + break; + case 'q': arg_quiet = true; break; @@ -928,11 +998,31 @@ static int parse_argv(int argc, char *argv[]) { } } + if (uid_is_valid(arg_uid)) { + /* If a UID is specified, then switch to scoped credentials */ + + if (sd_id128_equal(arg_with_key, _CRED_AUTO)) + arg_with_key = _CRED_AUTO_SCOPED; + else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED)) + arg_with_key = CRED_AES256_GCM_BY_HOST_SCOPED; + else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED)) + arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED; + else if (sd_id128_in_set(arg_with_key, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) + arg_with_key = CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED; + else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected key not available in --uid= scoped mode, refusing."); + } + if (arg_tpm2_pcr_mask == UINT32_MAX) arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_BOOT; + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + arg_varlink = r; + return 1; } @@ -952,6 +1042,312 @@ static int creds_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +#define TIMESTAMP_FRESH_MAX (30*USEC_PER_SEC) + +static bool timestamp_is_fresh(usec_t x) { + usec_t n = now(CLOCK_REALTIME); + + /* We'll only allow unprivileged encryption/decryption for somehwhat "fresh" timestamps */ + + if (x > n) + return x - n <= TIMESTAMP_FRESH_MAX; + else + return n - x <= TIMESTAMP_FRESH_MAX; +} + +typedef enum CredentialScope { + CREDENTIAL_SYSTEM, + CREDENTIAL_USER, + /* One day we should add more here, for example, per-app/per-service credentials */ + _CREDENTIAL_SCOPE_MAX, + _CREDENTIAL_SCOPE_INVALID = -EINVAL, +} CredentialScope; + +static const char* credential_scope_table[_CREDENTIAL_SCOPE_MAX] = { + [CREDENTIAL_SYSTEM] = "system", + [CREDENTIAL_USER] = "user", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(credential_scope, CredentialScope); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_credential_scope, CredentialScope, credential_scope_from_string); + +typedef struct MethodEncryptParameters { + const char *name; + const char *text; + struct iovec data; + uint64_t timestamp; + uint64_t not_after; + CredentialScope scope; + uid_t uid; +} MethodEncryptParameters; + +static void method_encrypt_parameters_done(MethodEncryptParameters *p) { + assert(p); + + iovec_done_erase(&p->data); +} + +static int settle_scope( + Varlink *link, + CredentialScope *scope, + uid_t *uid, + CredentialFlags *flags, + bool *any_scope_after_polkit) { + + uid_t peer_uid; + int r; + + assert(link); + assert(scope); + assert(uid); + assert(flags); + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (*scope < 0) { + if (uid_is_valid(*uid)) + *scope = CREDENTIAL_USER; + else { + *scope = CREDENTIAL_SYSTEM; /* When encrypting, we spit out a system credential */ + *uid = peer_uid; /* When decrypting a user credential, use this UID */ + } + + if (peer_uid == 0) + *flags |= CREDENTIAL_ANY_SCOPE; + + if (any_scope_after_polkit) + *any_scope_after_polkit = true; + } else if (*scope == CREDENTIAL_USER) { + if (!uid_is_valid(*uid)) + *uid = peer_uid; + } else { + assert(*scope == CREDENTIAL_SYSTEM); + if (uid_is_valid(*uid)) + return varlink_error_invalid_parameter_name(link, "uid"); + } + + return 0; +} + +static int vl_method_encrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, name), 0 }, + { "text", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodEncryptParameters, text), 0 }, + { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodEncryptParameters, data), 0 }, + { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, timestamp), 0 }, + { "notAfter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodEncryptParameters, not_after), 0 }, + { "scope", JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodEncryptParameters, scope), 0 }, + { "uid", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uid_gid, offsetof(MethodEncryptParameters, uid), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + _cleanup_(method_encrypt_parameters_done) MethodEncryptParameters p = { + .timestamp = UINT64_MAX, + .not_after = UINT64_MAX, + .scope = _CREDENTIAL_SCOPE_INVALID, + .uid = UID_INVALID, + }; + _cleanup_(iovec_done) struct iovec output = {}; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + CredentialFlags cflags = 0; + bool timestamp_fresh; + uid_t peer_uid; + int r; + + assert(link); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.name && !credential_name_valid(p.name)) + return varlink_error_invalid_parameter_name(link, "name"); + /* Specifying both or neither the text string and the binary data is not allowed */ + if (!!p.text == !!p.data.iov_base) + return varlink_error_invalid_parameter_name(link, "data"); + if (p.timestamp == UINT64_MAX) { + p.timestamp = now(CLOCK_REALTIME); + timestamp_fresh = true; + } else + timestamp_fresh = timestamp_is_fresh(p.timestamp); + if (p.not_after != UINT64_MAX && p.not_after < p.timestamp) + return varlink_error_invalid_parameter_name(link, "notAfter"); + + r = settle_scope(link, &p.scope, &p.uid, &cflags, /* any_scope_after_polkit= */ NULL); + if (r < 0) + return r; + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + /* Relax security requirements if peer wants to encrypt credentials for themselves */ + bool own_scope = p.scope == CREDENTIAL_USER && p.uid == peer_uid; + + if (!own_scope || !timestamp_fresh) { + /* Insist on PK if client wants to encrypt for another user or the system, or if the timestamp was explicitly overridden. */ + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.credentials.encrypt", + /* details= */ NULL, + polkit_registry); + if (r <= 0) + return r; + } + + r = encrypt_credential_and_warn( + p.scope == CREDENTIAL_USER ? _CRED_AUTO_SCOPED : _CRED_AUTO, + p.name, + p.timestamp, + p.not_after, + arg_tpm2_device, + arg_tpm2_pcr_mask, + arg_tpm2_public_key, + arg_tpm2_public_key_pcr_mask, + p.uid, + p.text ? &IOVEC_MAKE_STRING(p.text) : &p.data, + cflags, + &output); + if (r == -ESRCH) + return varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + + r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("blob", &output))); + if (r < 0) + return r; + + /* Let's also mark the (theoretically encrypted) reply as sensitive, in case the NULL encryption scheme was used. */ + json_variant_sensitive(reply); + + return varlink_reply(link, reply); +} + +typedef struct MethodDecryptParameters { + const char *name; + struct iovec blob; + uint64_t timestamp; + CredentialScope scope; + uid_t uid; +} MethodDecryptParameters; + +static void method_decrypt_parameters_done(MethodDecryptParameters *p) { + assert(p); + + iovec_done_erase(&p->blob); +} + +static int vl_method_decrypt(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodDecryptParameters, name), 0 }, + { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(MethodDecryptParameters, blob), JSON_MANDATORY }, + { "timestamp", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(MethodDecryptParameters, timestamp), 0 }, + { "scope", JSON_VARIANT_STRING, dispatch_credential_scope, offsetof(MethodDecryptParameters, scope), 0 }, + { "uid", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uid_gid, offsetof(MethodDecryptParameters, uid), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + _cleanup_(method_decrypt_parameters_done) MethodDecryptParameters p = { + .timestamp = UINT64_MAX, + .scope = _CREDENTIAL_SCOPE_INVALID, + .uid = UID_INVALID, + }; + bool timestamp_fresh, any_scope_after_polkit = false; + _cleanup_(iovec_done_erase) struct iovec output = {}; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + CredentialFlags cflags = 0; + uid_t peer_uid; + int r; + + assert(link); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.name && !credential_name_valid(p.name)) + return varlink_error_invalid_parameter_name(link, "name"); + if (p.timestamp == UINT64_MAX) { + p.timestamp = now(CLOCK_REALTIME); + timestamp_fresh = true; + } else + timestamp_fresh = timestamp_is_fresh(p.timestamp); + + r = settle_scope(link, &p.scope, &p.uid, &cflags, &any_scope_after_polkit); + if (r < 0) + return r; + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + /* Relax security requirements if peer wants to encrypt credentials for themselves */ + bool own_scope = p.scope == CREDENTIAL_USER && p.uid == peer_uid; + bool ask_polkit = !own_scope || !timestamp_fresh; + for (;;) { + if (ask_polkit) { + r = varlink_verify_polkit_async( + link, + /* bus= */ NULL, + "io.systemd.credentials.decrypt", + /* details= */ NULL, + polkit_registry); + if (r <= 0) + return r; + + /* Now that we have authenticated, it's fine to allow unpriv clients access to system secrets */ + if (any_scope_after_polkit) + cflags |= CREDENTIAL_ANY_SCOPE; + } + + r = decrypt_credential_and_warn( + p.name, + p.timestamp, + arg_tpm2_device, + arg_tpm2_signature, + p.uid, + &p.blob, + cflags, + &output); + if (r != -EMEDIUMTYPE || ask_polkit || !any_scope_after_polkit) + break; + + /* So the secret was apparently intended for the system. Let's retry decrypting it after + * acquiring polkit's permission. */ + ask_polkit = true; + } + + if (r == -EBADMSG) + return varlink_error(link, "io.systemd.Credentials.BadFormat", NULL); + if (r == -EREMOTE) + return varlink_error(link, "io.systemd.Credentials.NameMismatch", NULL); + if (r == -ESTALE) + return varlink_error(link, "io.systemd.Credentials.TimeMismatch", NULL); + if (r == -ESRCH) + return varlink_error(link, "io.systemd.Credentials.NoSuchUser", NULL); + if (r == -EMEDIUMTYPE) + return varlink_error(link, "io.systemd.Credentials.BadScope", NULL); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + + r = json_build(&reply, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_IOVEC_BASE64("data", &output))); + if (r < 0) + return r; + + json_variant_sensitive(reply); + + return varlink_reply(link, reply); +} + static int run(int argc, char *argv[]) { int r; @@ -961,6 +1357,36 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_Credentials); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.Credentials.Encrypt", vl_method_encrypt, + "io.systemd.Credentials.Decrypt", vl_method_decrypt); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + varlink_server_set_userdata(varlink_server, &polkit_registry); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; + } + return creds_main(argc, argv); } diff --git a/src/creds/io.systemd.credentials.policy b/src/creds/io.systemd.credentials.policy new file mode 100644 index 0000000..b4e911f --- /dev/null +++ b/src/creds/io.systemd.credentials.policy @@ -0,0 +1,40 @@ + + + + + + + + The systemd Project + https://systemd.io + + + Allow encryption and signing of system credentials. + Authentication is required for an application to encrypt and sign a system credential. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + Allow decryption of system credentials. + Authentication is required for an application to decrypt a system credential. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + diff --git a/src/creds/meson.build b/src/creds/meson.build index 8557256..2483311 100644 --- a/src/creds/meson.build +++ b/src/creds/meson.build @@ -23,3 +23,6 @@ if install_sysconfdir install_emptydir(sysconfdir / 'credstore.encrypted', install_mode : 'rwx------') endif + +install_data('io.systemd.credentials.policy', + install_dir : polkitpolicydir) diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c index 2baeb92..baa630a 100644 --- a/src/cryptenroll/cryptenroll-fido2.c +++ b/src/cryptenroll/cryptenroll-fido2.c @@ -33,10 +33,10 @@ int load_volume_key_fido2( cd_node, device, /* until= */ 0, - /* headless= */ false, + "cryptenroll.fido2-pin", + ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED, &decrypted_key, - &decrypted_key_size, - ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED); + &decrypted_key_size); if (r == -EAGAIN) return log_error_errno(r, "FIDO2 token does not exist, or UV is blocked. Please try again."); if (r < 0) @@ -97,6 +97,7 @@ int enroll_fido2( /* user_display_name= */ node, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "drive-harddisk", + /* askpw_credential= */ "cryptenroll.fido2-pin", lock_with, cred_alg, &cid, &cid_size, diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c index d21df71..00a1a8e 100644 --- a/src/cryptenroll/cryptenroll-list.c +++ b/src/cryptenroll/cryptenroll-list.c @@ -114,7 +114,7 @@ int list_enrolled(struct crypt_device *cd) { return table_log_add_error(r); } - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No slots found."); return 0; } diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index c35b609..a9bd8a1 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -38,9 +38,8 @@ int load_volume_key_password( return log_error_errno(r, "Password from environment variable $PASSWORD did not work: %m"); } else { AskPasswordFlags ask_password_flags = ASK_PASSWORD_PUSH_CACHE|ASK_PASSWORD_ACCEPT_CACHED; - _cleanup_free_ char *question = NULL, *disk_path = NULL; + _cleanup_free_ char *question = NULL, *id = NULL, *disk_path = NULL; unsigned i = 5; - const char *id; question = strjoin("Please enter current passphrase for disk ", cd_node, ":"); if (!question) @@ -50,7 +49,17 @@ int load_volume_key_password( if (!disk_path) return log_oom(); - id = strjoina("cryptsetup:", disk_path); + id = strjoin("cryptenroll:", disk_path); + if (!id) + return log_oom(); + + AskPasswordRequest req = { + .message = question, + .icon = "drive-harddisk", + .id = id, + .keyring = "cryptenroll", + .credential = "cryptenroll.passphrase", + }; for (;;) { _cleanup_strv_free_erase_ char **passwords = NULL; @@ -59,10 +68,7 @@ int load_volume_key_password( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); - r = ask_password_auto( - question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY, - ask_password_flags, - &passwords); + r = ask_password_auto(&req, USEC_INFINITY, ask_password_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -105,9 +111,8 @@ int enroll_password( if (r < 0) return log_error_errno(r, "Failed to acquire password from environment: %m"); if (r == 0) { - _cleanup_free_ char *disk_path = NULL; + _cleanup_free_ char *disk_path = NULL, *id = NULL; unsigned i = 5; - const char *id; assert_se(node = crypt_get_device_name(cd)); @@ -117,7 +122,16 @@ int enroll_password( if (!disk_path) return log_oom(); - id = strjoina("cryptsetup:", disk_path); + id = strjoin("cryptenroll-new:", disk_path); + if (!id) + return log_oom(); + + AskPasswordRequest req = { + .icon = "drive-harddisk", + .id = id, + .keyring = "cryptenroll", + .credential = "cryptenroll.new-passphrase", + }; for (;;) { _cleanup_strv_free_erase_ char **passwords = NULL, **passwords2 = NULL; @@ -131,7 +145,9 @@ int enroll_password( if (!question) return log_oom(); - r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords); + req.message = question; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -142,7 +158,9 @@ int enroll_password( if (!question) return log_oom(); - r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords2); + req.message = question; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &passwords2); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c index 54b6b86..1e4be00 100644 --- a/src/cryptenroll/cryptenroll-pkcs11.c +++ b/src/cryptenroll/cryptenroll-pkcs11.c @@ -6,7 +6,30 @@ #include "memory-util.h" #include "openssl-util.h" #include "pkcs11-util.h" -#include "random-util.h" + +static int uri_set_private_class(const char *uri, char **ret_uri) { + _cleanup_(sym_p11_kit_uri_freep) P11KitUri *p11kit_uri = NULL; + _cleanup_free_ char *private_uri = NULL; + int r; + + r = uri_from_string(uri, &p11kit_uri); + if (r < 0) + return log_error_errno(r, "Failed to parse PKCS#11 URI '%s': %m", uri); + + if (sym_p11_kit_uri_get_attribute(p11kit_uri, CKA_CLASS)) { + CK_OBJECT_CLASS class = CKO_PRIVATE_KEY; + CK_ATTRIBUTE attribute = { CKA_CLASS, &class, sizeof(class) }; + + if (sym_p11_kit_uri_set_attribute(p11kit_uri, &attribute) != P11_KIT_URI_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to set class for URI '%s'.", uri); + + if (sym_p11_kit_uri_format(p11kit_uri, P11_KIT_URI_FOR_ANY, &private_uri) != P11_KIT_URI_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format PKCS#11 URI."); + } + + *ret_uri = TAKE_PTR(private_uri); + return 0; +} int enroll_pkcs11( struct crypt_device *cd, @@ -17,14 +40,13 @@ int enroll_pkcs11( _cleanup_(erase_and_freep) void *decrypted_key = NULL; _cleanup_(erase_and_freep) char *base64_encoded = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - _cleanup_free_ char *keyslot_as_string = NULL; - size_t decrypted_key_size, encrypted_key_size; - _cleanup_free_ void *encrypted_key = NULL; - _cleanup_(X509_freep) X509 *cert = NULL; + _cleanup_free_ char *keyslot_as_string = NULL, *private_uri = NULL; + size_t decrypted_key_size, saved_key_size; + _cleanup_free_ void *saved_key = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ssize_t base64_encoded_size; const char *node; - EVP_PKEY *pkey; - int keyslot, r; + int r; assert_se(cd); assert_se(volume_key); @@ -33,31 +55,20 @@ int enroll_pkcs11( assert_se(node = crypt_get_device_name(cd)); - r = pkcs11_acquire_certificate(uri, "volume enrollment operation", "drive-harddisk", &cert, NULL); + r = pkcs11_acquire_public_key( + uri, + "volume enrollment operation", + "drive-harddisk", + "cryptenroll.pkcs11-pin", + /* askpw_flags= */ 0, + &pkey, + /* ret_pin_used= */ NULL); if (r < 0) return r; - pkey = X509_get0_pubkey(cert); - if (!pkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); - - r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); if (r < 0) - return log_error_errno(r, "Failed to determine RSA public key size."); - - log_debug("Generating %zu bytes random key.", decrypted_key_size); - - decrypted_key = malloc(decrypted_key_size); - if (!decrypted_key) - return log_oom(); - - r = crypto_random_bytes(decrypted_key, decrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to generate random key: %m"); - - r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to encrypt key: %m"); + return log_error_errno(r, "Failed to generate volume keys: %m"); /* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by * keyboard, if that might ever end up being necessary.) */ @@ -69,7 +80,7 @@ int enroll_pkcs11( if (r < 0) return log_error_errno(r, "Failed to set minimal PBKDF: %m"); - keyslot = crypt_keyslot_add_by_volume_key( + int keyslot = crypt_keyslot_add_by_volume_key( cd, CRYPT_ANY_SLOT, volume_key, @@ -82,12 +93,19 @@ int enroll_pkcs11( if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return log_oom(); + /* Change 'type=cert' or 'type=public' in the provided URI to 'type=private' before storing in + a LUKS2 header. This allows users to use output of some PKCS#11 tools directly without + modifications. */ + r = uri_set_private_class(uri, &private_uri); + if (r < 0) + return r; + r = json_build(&v, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")), - JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), - JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(uri)), - JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)))); + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-pkcs11")), + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), + JSON_BUILD_PAIR("pkcs11-uri", JSON_BUILD_STRING(private_uri ?: uri)), + JSON_BUILD_PAIR("pkcs11-key", JSON_BUILD_BASE64(saved_key, saved_key_size)))); if (r < 0) return log_error_errno(r, "Failed to prepare PKCS#11 JSON token object: %m"); diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c index 2d93e13..1ee3525 100644 --- a/src/cryptenroll/cryptenroll-tpm2.c +++ b/src/cryptenroll/cryptenroll-tpm2.c @@ -3,10 +3,13 @@ #include "alloc-util.h" #include "ask-password-api.h" #include "cryptenroll-tpm2.h" +#include "cryptsetup-tpm2.h" #include "env-util.h" +#include "errno-util.h" #include "fileio.h" #include "hexdecoct.h" #include "json.h" +#include "log.h" #include "memory-util.h" #include "random-util.h" #include "sha256.h" @@ -25,7 +28,7 @@ static int search_policy_hash( if (hash_size == 0) return 0; - for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_free_ void *thash = NULL; size_t thash_size = 0; @@ -51,7 +54,7 @@ static int search_policy_hash( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = unhexmem(json_variant_string(w), SIZE_MAX, &thash, &thash_size); + r = unhexmem(json_variant_string(w), &thash, &thash_size); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid base64 data in 'tpm2-policy-hash' field."); @@ -84,28 +87,29 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { return log_error_errno( SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); + AskPasswordRequest req = { + .message = "Please enter TPM2 PIN:", + .icon = "drive-harddisk", + .keyring = "tpm2-pin", + .credential = "cryptenroll.new-tpm2-pin", + }; + pin = strv_free_erase(pin); r = ask_password_auto( - "Please enter TPM2 PIN:", - "drive-harddisk", - NULL, - "tpm2-pin", - "cryptenroll.tpm2-pin", - USEC_INFINITY, - 0, + &req, + /* until= */ USEC_INFINITY, + /* flags= */ 0, &pin); if (r < 0) return log_error_errno(r, "Failed to ask for user pin: %m"); assert(strv_length(pin) == 1); + req.message = "Please enter TPM2 PIN (repeat):"; + r = ask_password_auto( - "Please enter TPM2 PIN (repeat):", - "drive-harddisk", - NULL, - "tpm2-pin", - "cryptenroll.tpm2-pin", + &req, USEC_INFINITY, - 0, + /* flags= */ 0, &pin2); if (r < 0) return log_error_errno(r, "Failed to ask for user pin: %m"); @@ -129,6 +133,114 @@ static int get_pin(char **ret_pin_str, TPM2Flags *ret_flags) { return 0; } +int load_volume_key_tpm2( + struct crypt_device *cd, + const char *cd_node, + const char *device, + void *ret_vk, + size_t *ret_vks) { + + _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; + _cleanup_(erase_and_freep) char *passphrase = NULL; + ssize_t passphrase_size; + int r; + + assert_se(cd); + assert_se(cd_node); + assert_se(ret_vk); + assert_se(ret_vks); + + bool found_some = false; + int token = 0; /* first token to look at */ + + for (;;) { + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags tpm2_flags; + int keyslot; + + r = find_tpm2_auto_data( + cd, + UINT32_MAX, + token, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + &tpm2_flags, + &keyslot, + &token); + if (r == -ENXIO) + return log_full_errno(LOG_NOTICE, + SYNTHETIC_ERRNO(EAGAIN), + found_some + ? "No TPM2 metadata matching the current system state found in LUKS2 header." + : "No TPM2 metadata enrolled in LUKS2 header."); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) + /* TPM2 support not compiled in? */ + return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 support not available."); + if (r < 0) + return r; + + found_some = true; + + r = acquire_tpm2_key( + cd_node, + device, + hash_pcr_mask, + pcr_bank, + &pubkey, + pubkey_pcr_mask, + /* signature_path= */ NULL, + /* pcrlock_path= */ NULL, + primary_alg, + /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + tpm2_flags, + /* until= */ 0, + "cryptenroll.tpm2-pin", + /* askpw_flags= */ 0, + &decrypted_key); + if (IN_SET(r, -EACCES, -ENOLCK)) + return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed"); + if (r != -EPERM) + break; + + token++; /* try a different token next time */ + } + + if (r < 0) + return log_error_errno(r, "Unlocking via TPM2 device failed: %m"); + + passphrase_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &passphrase); + if (passphrase_size < 0) + return log_oom(); + + r = crypt_volume_key_get( + cd, + CRYPT_ANY_SLOT, + ret_vk, + ret_vks, + passphrase, + passphrase_size); + if (r < 0) + return log_error_errno(r, "Unlocking via TPM2 device failed: %m"); + + return r; +} + int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, @@ -137,22 +249,22 @@ int enroll_tpm2(struct crypt_device *cd, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, - const char *pubkey_path, + const char *pcr_pubkey_path, + bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, - const char *pcrlock_path) { + const char *pcrlock_path, + int *ret_slot_to_wipe) { - _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; - _cleanup_free_ void *srk_buf = NULL; - size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; - _cleanup_free_ void *blob = NULL, *pubkey = NULL; + _cleanup_(iovec_done) struct iovec srk = {}, blob = {}, pubkey = {}; + _cleanup_(iovec_done_erase) struct iovec secret = {}; const char *node; _cleanup_(erase_and_freep) char *pin_str = NULL; ssize_t base64_encoded_size; - int r, keyslot; + int r, keyslot, slot_to_wipe = -1; TPM2Flags flags = 0; uint8_t binary_salt[SHA256_DIGEST_SIZE] = {}; /* @@ -168,6 +280,7 @@ int enroll_tpm2(struct crypt_device *cd, assert(volume_key_size > 0); assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values)); assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + assert(ret_slot_to_wipe); assert_se(node = crypt_get_device_name(cd)); @@ -194,27 +307,33 @@ int enroll_tpm2(struct crypt_device *cd, } TPM2B_PUBLIC public = {}; - r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size); - if (r < 0) { - if (pubkey_path || signature_path || r != -ENOENT) - return log_error_errno(r, "Failed to read TPM PCR public key: %m"); - - log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); - pubkey_pcr_mask = 0; - } else { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); - if (r < 0) - return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); + /* Load the PCR public key if specified explicitly, or if no pcrlock policy was specified and + * automatic loading of PCR public keys wasn't disabled explicitly. The reason we turn this off when + * pcrlock is configured is simply that we currently not support both in combination. */ + if (pcr_pubkey_path || (load_pcr_pubkey && !pcrlock_path)) { + r = tpm2_load_pcr_public_key(pcr_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); + if (r < 0) { + if (pcr_pubkey_path || signature_path || r != -ENOENT) + return log_error_errno(r, "Failed to read TPM PCR public key: %m"); + + log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); + pubkey_pcr_mask = 0; + } else { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); + if (r < 0) + return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); - if (signature_path) { - /* Also try to load the signature JSON object, to verify that our enrollment will work. - * This is optional however, skip it if it's not explicitly provided. */ + if (signature_path) { + /* Also try to load the signature JSON object, to verify that our enrollment will work. + * This is optional however, skip it if it's not explicitly provided. */ - r = tpm2_load_pcr_signature(signature_path, &signature_json); - if (r < 0) - return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) + return log_error_errno(r, "Failed to read TPM PCR signature: %m"); + } } - } + } else + pubkey_pcr_mask = 0; bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); @@ -223,6 +342,8 @@ int enroll_tpm2(struct crypt_device *cd, r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); if (r < 0) return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find pcrlock policy %s.", pcrlock_path); any_pcr_value_specified = true; flags |= TPM2_FLAGS_USE_PCRLOCK; @@ -252,8 +373,10 @@ int enroll_tpm2(struct crypt_device *cd, uint16_t hash_pcr_bank = 0; uint32_t hash_pcr_mask = 0; + if (n_hash_pcr_values > 0) { size_t hash_count; + r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count); if (r < 0) return log_error_errno(r, "Could not get hash count: %m"); @@ -261,17 +384,28 @@ int enroll_tpm2(struct crypt_device *cd, if (hash_count > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); + /* If we use a literal PCR value policy, derive the bank to use from the algorithm specified on the hash values */ hash_pcr_bank = hash_pcr_values[0].hash; r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); if (r < 0) return log_error_errno(r, "Could not get hash mask: %m"); + } else if (pubkey_pcr_mask != 0) { + + /* If no literal PCR value policy is used, then let's determine the mask to use automatically + * from the measurements of the TPM. */ + r = tpm2_get_best_pcr_bank( + tpm2_context, + pubkey_pcr_mask, + &hash_pcr_bank); + if (r < 0) + return log_error_errno(r, "Failed to determine best PCR bank: %m"); } TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); r = tpm2_calculate_sealing_policy( hash_pcr_values, n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, use_pin, pcrlock_path ? &pcrlock_policy : NULL, &policy); @@ -283,21 +417,21 @@ int enroll_tpm2(struct crypt_device *cd, seal_key_handle, &device_key_public, /* attributes= */ NULL, - /* secret= */ NULL, /* secret_size= */ 0, + /* secret= */ NULL, &policy, pin_str, - &secret, &secret_size, - &blob, &blob_size, - &srk_buf, &srk_buf_size); + &secret, + &blob, + &srk); else r = tpm2_seal(tpm2_context, seal_key_handle, &policy, pin_str, - &secret, &secret_size, - &blob, &blob_size, + &secret, + &blob, /* ret_primary_alg= */ NULL, - &srk_buf, &srk_buf_size); + &srk); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); @@ -307,39 +441,42 @@ int enroll_tpm2(struct crypt_device *cd, log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); else if (r < 0) return r; - else { + else if (use_pin) { + log_debug("This PCR set is already enrolled, re-enrolling anyway to update PIN."); + slot_to_wipe = r; + } else { log_info("This PCR set is already enrolled, executing no operation."); + *ret_slot_to_wipe = slot_to_wipe; return r; /* return existing keyslot, so that wiping won't kill it */ } /* If possible, verify the sealed data object. */ - if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) { - _cleanup_(erase_and_freep) void *secret2 = NULL; - size_t secret2_size; + if ((!iovec_is_set(&pubkey) || signature_json) && !any_pcr_value_specified && !device_key) { + _cleanup_(iovec_done_erase) struct iovec secret2 = {}; log_debug("Unsealing for verification..."); r = tpm2_unseal(tpm2_context, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, signature_json, pin_str, pcrlock_path ? &pcrlock_policy : NULL, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - srk_buf, srk_buf_size, - &secret2, &secret2_size); + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + &srk, + &secret2); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0) + if (iovec_memcmp(&secret, &secret2) != 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); } /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ - base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); + base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); @@ -361,14 +498,14 @@ int enroll_tpm2(struct crypt_device *cd, keyslot, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - use_pin ? binary_salt : NULL, - use_pin ? sizeof(binary_salt) : 0, - srk_buf, srk_buf_size, + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + use_pin ? &IOVEC_MAKE(binary_salt, sizeof(binary_salt)) : NULL, + &srk, + pcrlock_path ? &pcrlock_policy.nv_handle : NULL, flags, &v); if (r < 0) @@ -379,5 +516,7 @@ int enroll_tpm2(struct crypt_device *cd, return log_error_errno(r, "Failed to add TPM2 JSON token to LUKS2 header: %m"); log_info("New TPM2 token enrolled as key slot %i.", keyslot); + + *ret_slot_to_wipe = slot_to_wipe; return keyslot; } diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h index 2fbcdd4..d722ed6 100644 --- a/src/cryptenroll/cryptenroll-tpm2.h +++ b/src/cryptenroll/cryptenroll-tpm2.h @@ -8,9 +8,15 @@ #include "tpm2-util.h" #if HAVE_TPM2 -int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path); +int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks); +int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe); #else -static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path) { +static inline int load_volume_key_tpm2(struct crypt_device *cd, const char *cd_node, const char *device, void *ret_vk, size_t *ret_vks) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 unlocking not supported."); +} + +static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcr_values, size_t n_hash_pcr_values, const char *pubkey_path, bool load_pcr_pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin, const char *pcrlock_path, int *ret_slot_to_wipe) { return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 key enrollment not supported."); } diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index 1cb6652..04352bf 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include "ask-password-api.h" +#include "blockdev-util.h" #include "build.h" #include "cryptenroll-fido2.h" #include "cryptenroll-list.h" @@ -13,6 +15,7 @@ #include "cryptenroll-wipe.h" #include "cryptenroll.h" #include "cryptsetup-util.h" +#include "devnum-util.h" #include "env-util.h" #include "escape.h" #include "fileio.h" @@ -33,6 +36,7 @@ static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; static char *arg_unlock_keyfile = NULL; static UnlockType arg_unlock_type = UNLOCK_PASSWORD; static char *arg_unlock_fido2_device = NULL; +static char *arg_unlock_tpm2_device = NULL; static char *arg_pkcs11_token_uri = NULL; static char *arg_fido2_device = NULL; static char *arg_tpm2_device = NULL; @@ -42,6 +46,7 @@ static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; static size_t arg_tpm2_n_hash_pcr_values = 0; static bool arg_tpm2_pin = false; static char *arg_tpm2_public_key = NULL; +static bool arg_tpm2_load_public_key = true; static uint32_t arg_tpm2_public_key_pcr_mask = 0; static char *arg_tpm2_signature = NULL; static char *arg_tpm2_pcrlock = NULL; @@ -61,6 +66,7 @@ assert_cc(sizeof(arg_wipe_slots_mask) * 8 >= _ENROLL_TYPE_MAX); STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep); STATIC_DESTRUCTOR_REGISTER(arg_unlock_fido2_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_unlock_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); @@ -98,6 +104,66 @@ static const char *const luks2_token_type_table[_ENROLL_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(luks2_token_type, EnrollType); +static int determine_default_node(void) { + int r; + + /* If no device is specified we'll default to the backing device of /var/. + * + * Why /var/ and not just / you ask? + * + * On most systems /var/ is going to be on the root fs, hence the outcome is usually the same. + * + * However, on systems where / and /var/ are separate it makes more sense to default to /var/ because + * that's where the persistent and variable data is placed (i.e. where LUKS should be used) while / + * doesn't really have to be variable and could as well be immutable or ephemeral. Hence /var/ should + * be a better default. + * + * Or to say this differently: it makes sense to support well systems with /var/ being on /. It also + * makes sense to support well systems with them being separate, and /var/ being variable and + * persistent. But any other kind of system appears much less interesting to support, and in that + * case people should just specify the device name explicitly. */ + + dev_t devno; + r = get_block_device("/var", &devno); + if (r < 0) + return log_error_errno(r, "Failed to determine block device backing /var/: %m"); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), + "File system /var/ is on not backed by a (single) whole block device."); + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_new_from_devnum(&dev, 'b', devno); + if (r < 0) + return log_error_errno(r, "Unable to access backing block device for /var/: %m"); + + const char *dm_uuid; + r = sd_device_get_property_value(dev, "DM_UUID", &dm_uuid); + if (r == -ENOENT) + return log_error_errno(r, "Backing block device of /var/ is not a DM device: %m"); + if (r < 0) + return log_error_errno(r, "Unable to query DM_UUID udev property of backing block device for /var/: %m"); + + if (!startswith(dm_uuid, "CRYPT-LUKS2-")) + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Block device backing /var/ is not a LUKS2 device."); + + _cleanup_(sd_device_unrefp) sd_device *origin = NULL; + r = block_device_get_originating(dev, &origin); + if (r < 0) + return log_error_errno(r, "Failed to get originating device of LUKS2 device backing /var/: %m"); + + const char *dp; + r = sd_device_get_devname(origin, &dp); + if (r < 0) + return log_error_errno(r, "Failed to get device path for LUKS2 device backing /var/: %m"); + + r = free_and_strdup_warn(&arg_node, dp); + if (r < 0) + return r; + + log_info("No device specified, defaulting to '%s'.", arg_node); + return 0; +} + static int help(void) { _cleanup_free_ char *link = NULL; int r; @@ -106,7 +172,7 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%1$s [OPTIONS...] BLOCK-DEVICE\n\n" + printf("%1$s [OPTIONS...] [BLOCK-DEVICE]\n\n" "%5$sEnroll a security token or authentication credential to a LUKS volume.%6$s\n\n" " -h --help Show this help\n" " --version Show package version\n" @@ -117,6 +183,8 @@ static int help(void) { " Use a file to unlock the volume\n" " --unlock-fido2-device=PATH\n" " Use a FIDO2 device to unlock the volume\n" + " --unlock-tpm2-device=PATH\n" + " Use a TPM2 device to unlock the volume\n" "\n%3$sSimple Enrollment:%4$s\n" " --password Enroll a user-supplied password\n" " --recovery-key Enroll a recovery key\n" @@ -172,6 +240,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_RECOVERY_KEY, ARG_UNLOCK_KEYFILE, ARG_UNLOCK_FIDO2_DEVICE, + ARG_UNLOCK_TPM2_DEVICE, ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, ARG_TPM2_DEVICE, @@ -197,6 +266,7 @@ static int parse_argv(int argc, char *argv[]) { { "recovery-key", no_argument, NULL, ARG_RECOVERY_KEY }, { "unlock-key-file", required_argument, NULL, ARG_UNLOCK_KEYFILE }, { "unlock-fido2-device", required_argument, NULL, ARG_UNLOCK_FIDO2_DEVICE }, + { "unlock-tpm2-device", required_argument, NULL, ARG_UNLOCK_TPM2_DEVICE }, { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, @@ -304,6 +374,26 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_UNLOCK_TPM2_DEVICE: { + _cleanup_free_ char *device = NULL; + + if (arg_unlock_type != UNLOCK_PASSWORD) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Multiple unlock methods specified at once, refusing."); + + assert(!arg_unlock_tpm2_device); + + if (!streq(optarg, "auto")) { + device = strdup(optarg); + if (!device) + return log_oom(); + } + + arg_unlock_type = UNLOCK_TPM2; + arg_unlock_tpm2_device = TAKE_PTR(device); + break; + } + case ARG_PKCS11_TOKEN_URI: { _cleanup_free_ char *uri = NULL; @@ -409,9 +499,17 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_TPM2_PUBLIC_KEY: + /* an empty argument disables loading a public key */ + if (isempty(optarg)) { + arg_tpm2_load_public_key = false; + arg_tpm2_public_key = mfree(arg_tpm2_public_key); + break; + } + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_public_key); if (r < 0) return r; + arg_tpm2_load_public_key = true; break; @@ -507,17 +605,23 @@ static int parse_argv(int argc, char *argv[]) { } } - if (optind >= argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "No block device node specified, refusing."); - if (argc > optind+1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments, refusing."); - r = parse_path_argument(argv[optind], false, &arg_node); - if (r < 0) - return r; + if (optind < argc) { + r = parse_path_argument(argv[optind], false, &arg_node); + if (r < 0) + return r; + } else { + if (wipe_requested()) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Wiping requested and no block device node specified, refusing."); + + r = determine_default_node(); + if (r < 0) + return r; + } if (arg_enroll_type == ENROLL_FIDO2) { @@ -533,31 +637,33 @@ static int parse_argv(int argc, char *argv[]) { } } - if (auto_pcrlock) { - assert(!arg_tpm2_pcrlock); + if (arg_enroll_type == ENROLL_TPM2) { + if (auto_pcrlock) { + assert(!arg_tpm2_pcrlock); - r = tpm2_pcrlock_search_file(NULL, NULL, &arg_tpm2_pcrlock); - if (r < 0) { - if (r != -ENOENT) - log_warning_errno(r, "Search for pcrlock.json failed, assuming it does not exist: %m"); - } else - log_info("Automatically using pcrlock policy '%s'.", arg_tpm2_pcrlock); - } + r = tpm2_pcrlock_search_file(NULL, NULL, &arg_tpm2_pcrlock); + if (r < 0) { + if (r != -ENOENT) + log_warning_errno(r, "Search for pcrlock.json failed, assuming it does not exist: %m"); + } else + log_info("Automatically using pcrlock policy '%s'.", arg_tpm2_pcrlock); + } - if (auto_public_key_pcr_mask) { - assert(arg_tpm2_public_key_pcr_mask == 0); - arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); - } + if (auto_public_key_pcr_mask) { + assert(arg_tpm2_public_key_pcr_mask == 0); + arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM2_PCR_KERNEL_BOOT); + } - if (auto_hash_pcr_values && !arg_tpm2_pcrlock) { /* Only lock to PCR 7 by default if no pcrlock policy is around (which is a better replacement) */ - assert(arg_tpm2_n_hash_pcr_values == 0); + if (auto_hash_pcr_values && !arg_tpm2_pcrlock) { /* Only lock to PCR 7 by default if no pcrlock policy is around (which is a better replacement) */ + assert(arg_tpm2_n_hash_pcr_values == 0); - if (!GREEDY_REALLOC_APPEND( - arg_tpm2_hash_pcr_values, - arg_tpm2_n_hash_pcr_values, - &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), - 1)) - return log_oom(); + if (!GREEDY_REALLOC_APPEND( + arg_tpm2_hash_pcr_values, + arg_tpm2_n_hash_pcr_values, + &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), + 1)) + return log_oom(); + } } return 1; @@ -571,7 +677,7 @@ static int check_for_homed(struct crypt_device *cd) { /* Politely refuse operating on homed volumes. The enrolled tokens for the user record and the LUKS2 * volume should not get out of sync. */ - for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < crypt_token_max(CRYPT_LUKS2); token++) { r = cryptsetup_get_token_as_json(cd, token, "systemd-homed", NULL); if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) continue; @@ -644,7 +750,7 @@ static int prepare_luks( r = crypt_load(cd, CRYPT_LUKS2, NULL); if (r < 0) - return log_error_errno(r, "Failed to load LUKS2 superblock: %m"); + return log_error_errno(r, "Failed to load LUKS2 superblock of %s: %m", arg_node); r = check_for_homed(cd); if (r < 0) @@ -666,6 +772,10 @@ static int prepare_luks( switch (arg_unlock_type) { + case UNLOCK_PASSWORD: + r = load_volume_key_password(cd, arg_node, vk, &vks); + break; + case UNLOCK_KEYFILE: r = load_volume_key_keyfile(cd, vk, &vks); break; @@ -674,8 +784,8 @@ static int prepare_luks( r = load_volume_key_fido2(cd, arg_node, arg_unlock_fido2_device, vk, &vks); break; - case UNLOCK_PASSWORD: - r = load_volume_key_password(cd, arg_node, vk, &vks); + case UNLOCK_TPM2: + r = load_volume_key_tpm2(cd, arg_node, arg_unlock_tpm2_device, vk, &vks); break; default: @@ -696,16 +806,17 @@ static int run(int argc, char *argv[]) { _cleanup_(crypt_freep) struct crypt_device *cd = NULL; _cleanup_(erase_and_freep) void *vk = NULL; size_t vks; - int slot, r; + int slot, slot_to_wipe, r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; + /* A delicious drop of snake oil */ + (void) mlockall(MCL_FUTURE); + cryptsetup_enable_logging(NULL); if (arg_enroll_type < 0) @@ -734,9 +845,21 @@ static int run(int argc, char *argv[]) { break; case ENROLL_TPM2: - slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock); + slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_load_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin, arg_tpm2_pcrlock, &slot_to_wipe); + + if (slot >= 0 && slot_to_wipe >= 0) { + /* Updating PIN on an existing enrollment */ + r = wipe_slots( + cd, + &slot_to_wipe, + /* n_explicit_slots= */ 1, + WIPE_EXPLICIT, + /* by_mask= */ 0, + /* except_slot= */ -1); + if (r < 0) + return r; + } break; - case _ENROLL_TYPE_INVALID: /* List enrolled slots if we are called without anything to enroll or wipe */ if (!wipe_requested()) diff --git a/src/cryptenroll/cryptenroll.h b/src/cryptenroll/cryptenroll.h index 335d9cc..08ded3e 100644 --- a/src/cryptenroll/cryptenroll.h +++ b/src/cryptenroll/cryptenroll.h @@ -17,6 +17,7 @@ typedef enum UnlockType { UNLOCK_PASSWORD, UNLOCK_KEYFILE, UNLOCK_FIDO2, + UNLOCK_TPM2, _UNLOCK_TYPE_MAX, _UNLOCK_TYPE_INVALID = -EINVAL, } UnlockType; diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 904e4cd..4db25d3 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -229,7 +229,8 @@ static int generate_device_umount(const char *name, static int print_dependencies(FILE *f, const char* device_path, const char* timeout_value, bool canfail) { int r; - assert(!canfail || timeout_value); + assert(f); + assert(device_path); if (STR_IN_SET(device_path, "-", "none")) /* None, nothing to do */ @@ -263,11 +264,13 @@ static int print_dependencies(FILE *f, const char* device_path, const char* time fprintf(f, "After=%1$s\n", unit); if (canfail) { fprintf(f, "Wants=%1$s\n", unit); - r = write_drop_in_format(arg_dest, unit, 90, "device-timeout", - "# Automatically generated by systemd-cryptsetup-generator \n\n" - "[Unit]\nJobRunningTimeoutSec=%s", timeout_value); - if (r < 0) - return log_error_errno(r, "Failed to write device drop-in: %m"); + if (timeout_value) { + r = write_drop_in_format(arg_dest, unit, 90, "device-timeout", + "# Automatically generated by systemd-cryptsetup-generator \n\n" + "[Unit]\nJobRunningTimeoutSec=%s", timeout_value); + if (r < 0) + return log_error_errno(r, "Failed to write device drop-in: %m"); + } } else fprintf(f, "Requires=%1$s\n", unit); } else { @@ -276,7 +279,7 @@ static int print_dependencies(FILE *f, const char* device_path, const char* time if (!escaped_path) return log_oom(); - fprintf(f, "RequiresMountsFor=%s\n", escaped_path); + fprintf(f, "%s=%s\n", canfail ? "WantsMountsFor" : "RequiresMountsFor", escaped_path); } return 0; @@ -486,7 +489,7 @@ static int create_disk( if (key_file && !keydev) { r = print_dependencies(f, key_file, keyfile_timeout_value, - /* canfail= */ keyfile_can_timeout > 0); + /* canfail= */ keyfile_can_timeout > 0 || nofail); if (r < 0) return r; } @@ -494,8 +497,8 @@ static int create_disk( /* Check if a header option was specified */ if (detached_header > 0 && !headerdev) { r = print_dependencies(f, header_path, - NULL, - /* canfail= */ false); /* header is always necessary */ + /* timeout_value= */ NULL, + /* canfail= */ nofail); if (r < 0) return r; } @@ -578,6 +581,8 @@ static crypto_device* crypt_device_free(crypto_device *d) { free(d->uuid); free(d->keyfile); free(d->keydev); + free(d->headerdev); + free(d->datadev); free(d->name); free(d->options); return mfree(d); diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index f991389..4b2b5bb 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -34,14 +34,14 @@ int decrypt_pkcs11_key( const void *key_data, /* … or key_data and key_data_size (for literal keys) */ size_t key_data_size, usec_t until, - bool headless, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { _cleanup_(pkcs11_crypt_device_callback_data_release) pkcs11_crypt_device_callback_data data = { .friendly_name = friendly_name, + .askpw_flags = askpw_flags, .until = until, - .headless = headless, }; int r; @@ -154,7 +154,7 @@ int find_pkcs11_auto_data( assert(!key); assert(key_size == 0); - r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size); + r = unbase64mem(json_variant_string(w), &key, &key_size); if (r < 0) return log_error_errno(r, "Failed to decode base64 encoded key."); } diff --git a/src/cryptsetup/cryptsetup-pkcs11.h b/src/cryptsetup/cryptsetup-pkcs11.h index 256c09a..22e6992 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.h +++ b/src/cryptsetup/cryptsetup-pkcs11.h @@ -19,7 +19,7 @@ int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size); @@ -42,7 +42,7 @@ static inline int decrypt_pkcs11_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, size_t *ret_decrypted_key_size) { diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c index fdb3b17..1efb7c5 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-fido2.c @@ -18,7 +18,7 @@ /* for libcryptsetup debug purpose */ _public_ const char *cryptsetup_token_version(void) { - return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" PROJECT_VERSION_FULL " (" GIT_VERSION ")"; } _public_ int cryptsetup_token_open_pin( @@ -34,7 +34,7 @@ _public_ int cryptsetup_token_open_pin( const char *json; _cleanup_(erase_and_freep) char *pin_string = NULL; - assert(!pin || pin_size); + assert(pin || pin_size == 0); assert(token >= 0); /* This must not fail at this moment (internal error) */ @@ -87,7 +87,7 @@ _public_ void cryptsetup_token_buffer_free(void *buffer, size_t buffer_len) { */ _public_ void cryptsetup_token_dump( struct crypt_device *cd /* is always LUKS2 context */, - const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + const char *json /* validated 'systemd-fido2' token if cryptsetup_token_validate is defined */) { int r; Fido2EnrollFlags required; @@ -154,7 +154,7 @@ _public_ void cryptsetup_token_dump( */ _public_ int cryptsetup_token_validate( struct crypt_device *cd, /* is always LUKS2 context */ - const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-tpm2' */) { + const char *json /* contains valid 'type' and 'keyslots' fields. 'type' is 'systemd-fido2' */) { int r; JsonVariant *w; @@ -172,7 +172,7 @@ _public_ int cryptsetup_token_validate( return 1; } - r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + r = unbase64mem(json_variant_string(w), NULL, NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'fido2-credential' field: %m"); @@ -182,7 +182,7 @@ _public_ int cryptsetup_token_validate( return 1; } - r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + r = unbase64mem(json_variant_string(w), NULL, NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded salt: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c index 2ac8a27..a9898ba 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-pkcs11.c @@ -18,7 +18,7 @@ /* for libcryptsetup debug purpose */ _public_ const char *cryptsetup_token_version(void) { - return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" PROJECT_VERSION_FULL " (" GIT_VERSION ")"; } _public_ int cryptsetup_token_open_pin( @@ -33,7 +33,7 @@ _public_ int cryptsetup_token_open_pin( const char *json; int r; - assert(!pin || pin_size); + assert(pin || pin_size == 0); assert(token >= 0); /* This must not fail at this moment (internal error) */ @@ -136,7 +136,7 @@ _public_ int cryptsetup_token_validate( return 1; } - r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + r = unbase64mem(json_variant_string(w), NULL, NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c index 6fee831..8b4754a 100644 --- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c @@ -20,7 +20,7 @@ /* for libcryptsetup debug purpose */ _public_ const char *cryptsetup_token_version(void) { - return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"; + return TOKEN_VERSION_MAJOR "." TOKEN_VERSION_MINOR " systemd-v" PROJECT_VERSION_FULL " (" GIT_VERSION ")"; } static int log_debug_open_error(struct crypt_device *cd, int r) { @@ -42,9 +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, *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_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; uint32_t hash_pcr_mask, pubkey_pcr_mask; systemd_tpm2_plugin_params params = { @@ -57,7 +56,7 @@ _public_ int cryptsetup_token_open_pin( int r; assert(token >= 0); - assert(!pin || pin_size > 0); + assert(pin || pin_size == 0); assert(ret_password); assert(ret_password_len); @@ -79,21 +78,17 @@ _public_ int cryptsetup_token_open_pin( r = tpm2_parse_luks2_json( v, - NULL, + /* ret_keyslot= */ NULL, &hash_pcr_mask, &pcr_bank, &pubkey, - &pubkey_size, &pubkey_pcr_mask, &primary_alg, &blob, - &blob_size, &policy_hash, - &policy_hash_size, &salt, - &salt_size, - &srk_buf, - &srk_buf_size, + &srk, + &pcrlock_nv, &flags); if (r < 0) return log_debug_open_error(cd, r); @@ -105,28 +100,24 @@ _public_ int cryptsetup_token_open_pin( params.device, hash_pcr_mask, pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, params.signature_path, pin_string, params.pcrlock_path, primary_alg, - blob, - blob_size, - policy_hash, - policy_hash_size, - salt, - salt_size, - srk_buf, - srk_buf_size, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, flags, - &decrypted_key, - &decrypted_key_size); + &decrypted_key); if (r < 0) return log_debug_open_error(cd, r); /* Before using this key as passphrase we base64 encode it, for compat with homed */ - base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_debug_open_error(cd, base64_encoded_size); @@ -177,9 +168,8 @@ _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, *srk_buf = NULL; + _cleanup_(iovec_done) struct iovec blob = {}, pubkey = {}, policy_hash = {}, salt = {}, srk = {}, pcrlock_nv = {}; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - 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; @@ -197,17 +187,13 @@ _public_ void cryptsetup_token_dump( &hash_pcr_mask, &pcr_bank, &pubkey, - &pubkey_size, &pubkey_pcr_mask, &primary_alg, &blob, - &blob_size, &policy_hash, - &policy_hash_size, &salt, - &salt_size, - &srk_buf, - &srk_buf_size, + &srk, + &pcrlock_nv, &flags); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); @@ -220,15 +206,15 @@ _public_ void cryptsetup_token_dump( if (!pubkey_pcrs_str) return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); - r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str); + r = crypt_dump_buffer_to_hex_string(blob.iov_base, blob.iov_len, &blob_str); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); - r = crypt_dump_buffer_to_hex_string(pubkey, pubkey_size, &pubkey_str); + r = crypt_dump_buffer_to_hex_string(pubkey.iov_base, pubkey.iov_len, &pubkey_str); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); - r = crypt_dump_buffer_to_hex_string(policy_hash, policy_hash_size, &policy_hash_str); + r = crypt_dump_buffer_to_hex_string(policy_hash.iov_base, policy_hash.iov_len, &policy_hash_str); if (r < 0) return (void) crypt_log_debug_errno(cd, r, "Cannot dump " TOKEN_NAME " content: %m"); @@ -241,8 +227,9 @@ _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-pcrlock: %s\n", true_false(flags & TPM2_FLAGS_USE_PCRLOCK)); - crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt)); - crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf)); + crypt_log(cd, "\ttpm2-salt: %s\n", true_false(iovec_is_set(&salt))); + crypt_log(cd, "\ttpm2-srk: %s\n", true_false(iovec_is_set(&srk))); + crypt_log(cd, "\ttpm2-pcrlock-nv: %s\n", true_false(iovec_is_set(&pcrlock_nv))); } /* @@ -326,7 +313,7 @@ _public_ int cryptsetup_token_validate( return 1; } - r = unbase64mem(json_variant_string(w), SIZE_MAX, NULL, NULL); + r = unbase64mem(json_variant_string(w), NULL, NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-blob' field: %m"); @@ -336,7 +323,7 @@ _public_ int cryptsetup_token_validate( return 1; } - r = unhexmem(json_variant_string(w), SIZE_MAX, NULL, NULL); + r = unhexmem(json_variant_string(w), NULL, NULL); if (r < 0) return crypt_log_debug_errno(cd, r, "Invalid base64 data in 'tpm2-policy-hash' field: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c index a1c85e6..5b38613 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c @@ -104,7 +104,7 @@ int parse_luks2_fido2_data( if (!w) return -EINVAL; - r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + r = unbase64mem(json_variant_string(w), &cid, &cid_size); if (r < 0) return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-credentials' field: %m"); @@ -112,7 +112,7 @@ int parse_luks2_fido2_data( if (!w) return -EINVAL; - r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + r = unbase64mem(json_variant_string(w), &salt, &salt_size); if (r < 0) return crypt_log_error_errno(cd, r, "Failed to parse 'fido2-salt' field: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c index 178fc7a..ac5100f 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c @@ -157,7 +157,7 @@ static int acquire_luks2_key_systemd( assert(params); data.friendly_name = params->friendly_name; - data.headless = params->headless; + data.askpw_credential = params->askpw_credential; data.askpw_flags = params->askpw_flags; data.until = params->until; @@ -260,7 +260,7 @@ int parse_luks2_pkcs11_data( if (!w) return -EINVAL; - r = unbase64mem(json_variant_string(w), SIZE_MAX, &key, &key_size); + r = unbase64mem(json_variant_string(w), &key, &key_size); if (r < 0) return crypt_log_debug_errno(cd, r, "Failed to decode base64 encoded key: %m."); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c index 846679f..08f901c 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c @@ -17,33 +17,27 @@ int acquire_luks2_key( const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, const char *pin, const char *pcrlock_path, uint16_t primary_alg, - const void *key_data, - size_t key_data_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, + const struct iovec *blob, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, TPM2Flags flags, - void **ret_decrypted_key, - size_t *ret_decrypted_key_size) { + struct iovec *ret_decrypted_key) { _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; _cleanup_free_ char *auto_device = NULL; _cleanup_(erase_and_freep) char *b64_salted_pin = NULL; int r; - assert(salt || salt_size == 0); + assert(iovec_is_valid(salt)); assert(ret_decrypted_key); - assert(ret_decrypted_key_size); if (!device) { r = tpm2_find_device_auto(&auto_device); @@ -58,10 +52,10 @@ int acquire_luks2_key( if ((flags & TPM2_FLAGS_USE_PIN) && !pin) return -ENOANO; - if (pin && salt_size > 0) { + if (pin && iovec_is_set(salt)) { uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; CLEANUP_ERASE(salted_pin); - r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin); + r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt->iov_base, salt->iov_len, salted_pin); if (r < 0) return log_error_errno(r, "Failed to perform PBKDF2: %m"); @@ -82,6 +76,14 @@ int acquire_luks2_key( r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); if (r < 0) return r; + if (r == 0) { + /* Not found? Then search among passed credentials */ + r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume."); + } } _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; @@ -92,16 +94,16 @@ int acquire_luks2_key( r = tpm2_unseal(tpm2_context, hash_pcr_mask, pcr_bank, - pubkey, pubkey_size, + pubkey, pubkey_pcr_mask, signature_json, pin, FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, primary_alg, - key_data, key_data_size, - policy_hash, policy_hash_size, - srk_buf, srk_buf_size, - ret_decrypted_key, ret_decrypted_key_size); + blob, + policy_hash, + srk, + ret_decrypted_key); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h index 8408bab..c3a01df 100644 --- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h @@ -10,21 +10,16 @@ int acquire_luks2_key( const char *device, uint32_t pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, uint32_t pubkey_pcr_mask, const char *signature_path, const char *pin, const char *pcrlock_path, uint16_t primary_alg, - const void *key_data, - size_t key_data_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, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, TPM2Flags flags, - void **ret_decrypted_key, - size_t *ret_decrypted_key_size); + struct iovec *decrypted_key); diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c deleted file mode 100644 index e7a38d4..0000000 --- a/src/cryptsetup/cryptsetup-tpm2.c +++ /dev/null @@ -1,314 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "cryptsetup-tpm2.h" -#include "env-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "json.h" -#include "parse-util.h" -#include "random-util.h" -#include "sha256.h" -#include "tpm2-util.h" - -static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) { - _cleanup_(erase_and_freep) char *pin_str = NULL; - _cleanup_strv_free_erase_ char **pin = NULL; - int r; - - assert(ret_pin_str); - - r = getenv_steal_erase("PIN", &pin_str); - if (r < 0) - return log_error_errno(r, "Failed to acquire PIN from environment: %m"); - if (!r) { - if (headless) - return log_error_errno( - SYNTHETIC_ERRNO(ENOPKG), - "PIN querying disabled via 'headless' option. " - "Use the '$PIN' environment variable."); - - pin = strv_free_erase(pin); - r = ask_password_auto( - "Please enter TPM2 PIN:", - "drive-harddisk", - NULL, - "tpm2-pin", - "cryptsetup.tpm2-pin", - until, - ask_password_flags, - &pin); - if (r < 0) - return log_error_errno(r, "Failed to ask for user pin: %m"); - assert(strv_length(pin) == 1); - - pin_str = strdup(pin[0]); - if (!pin_str) - return log_oom(); - } - - *ret_pin_str = TAKE_PTR(pin_str); - - return r; -} - -int acquire_tpm2_key( - const char *volume_name, - const char *device, - uint32_t hash_pcr_mask, - uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, - uint32_t pubkey_pcr_mask, - const char *signature_path, - const char *pcrlock_path, - uint16_t primary_alg, - const char *key_file, - size_t key_file_size, - uint64_t key_file_offset, - const void *key_data, - size_t key_data_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, - usec_t until, - bool headless, - AskPasswordFlags ask_password_flags, - void **ret_decrypted_key, - size_t *ret_decrypted_key_size) { - - _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; - _cleanup_free_ void *loaded_blob = NULL; - _cleanup_free_ char *auto_device = NULL; - size_t blob_size; - const void *blob; - int r; - - assert(salt || salt_size == 0); - - if (!device) { - r = tpm2_find_device_auto(&auto_device); - if (r == -ENODEV) - return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ - if (r < 0) - return log_error_errno(r, "Could not find TPM2 device: %m"); - - device = auto_device; - } - - if (key_data) { - blob = key_data; - blob_size = key_data_size; - } else { - _cleanup_free_ char *bindname = NULL; - - /* If we read the salt via AF_UNIX, make this client recognizable */ - if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0) - return log_oom(); - - r = read_full_file_full( - AT_FDCWD, key_file, - key_file_offset == 0 ? UINT64_MAX : key_file_offset, - key_file_size == 0 ? SIZE_MAX : key_file_size, - READ_FULL_FILE_CONNECT_SOCKET, - bindname, - (char**) &loaded_blob, &blob_size); - if (r < 0) - return r; - - blob = loaded_blob; - } - - if (pubkey_pcr_mask != 0) { - r = tpm2_load_pcr_signature(signature_path, &signature_json); - if (r < 0) - return log_error_errno(r, "Failed to load pcr signature: %m"); - } - - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; - - if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) { - r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); - if (r < 0) - return r; - } - - _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; - r = tpm2_context_new_or_warn(device, &tpm2_context); - if (r < 0) - return r; - - if (!(flags & TPM2_FLAGS_USE_PIN)) { - r = tpm2_unseal(tpm2_context, - hash_pcr_mask, - pcr_bank, - pubkey, pubkey_size, - pubkey_pcr_mask, - signature_json, - /* pin= */ NULL, - FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, - primary_alg, - blob, - blob_size, - policy_hash, - policy_hash_size, - srk_buf, - srk_buf_size, - ret_decrypted_key, - ret_decrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - - return r; - } - - for (int i = 5;; i--) { - _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; - - if (i <= 0) - return -EACCES; - - r = get_pin(until, ask_password_flags, headless, &pin_str); - if (r < 0) - return r; - - if (salt_size > 0) { - uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; - CLEANUP_ERASE(salted_pin); - - r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt, salt_size, salted_pin); - if (r < 0) - return log_error_errno(r, "Failed to perform PBKDF2: %m"); - - r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); - if (r < 0) - return log_error_errno(r, "Failed to base64 encode salted pin: %m"); - } else - /* no salting needed, backwards compat with non-salted pins */ - b64_salted_pin = TAKE_PTR(pin_str); - - r = tpm2_unseal(tpm2_context, - hash_pcr_mask, - pcr_bank, - pubkey, pubkey_size, - pubkey_pcr_mask, - signature_json, - b64_salted_pin, - pcrlock_path ? &pcrlock_policy : NULL, - primary_alg, - blob, - blob_size, - policy_hash, - policy_hash_size, - srk_buf, - srk_buf_size, - ret_decrypted_key, - ret_decrypted_key_size); - if (r < 0) { - log_error_errno(r, "Failed to unseal secret using TPM2: %m"); - - /* We get this error in case there is an authentication policy mismatch. This should - * not happen, but this avoids confusing behavior, just in case. */ - if (!IN_SET(r, -EPERM, -ENOLCK)) - continue; - } - - return r; - } -} - -int find_tpm2_auto_data( - struct crypt_device *cd, - uint32_t search_pcr_mask, - int start_token, - 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, - int *ret_keyslot, - int *ret_token) { - - int r, token; - - assert(cd); - - for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { - _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - 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; - int keyslot; - - r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); - if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) - continue; - if (r < 0) - return log_error_errno(r, "Failed to read JSON token data off disk: %m"); - - r = tpm2_parse_luks2_json( - v, - &keyslot, - &hash_pcr_mask, - &pcr_bank, - &pubkey, &pubkey_size, - &pubkey_pcr_mask, - &primary_alg, - &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; - if (r < 0) - return log_error_errno(r, "Failed to parse TPM2 JSON data: %m"); - - if (search_pcr_mask == UINT32_MAX || - search_pcr_mask == hash_pcr_mask) { - - if (start_token <= 0) - log_info("Automatically discovered security TPM2 token unlocks volume."); - - *ret_hash_pcr_mask = hash_pcr_mask; - *ret_pcr_bank = pcr_bank; - *ret_pubkey = TAKE_PTR(pubkey); - *ret_pubkey_size = pubkey_size; - *ret_pubkey_pcr_mask = pubkey_pcr_mask; - *ret_primary_alg = primary_alg; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; - *ret_policy_hash = TAKE_PTR(policy_hash); - *ret_policy_hash_size = policy_hash_size; - *ret_salt = TAKE_PTR(salt); - *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; - } - - /* PCR mask doesn't match what is configured, ignore this entry, let's see next */ - } - - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found."); -} diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h deleted file mode 100644 index a50a943..0000000 --- a/src/cryptsetup/cryptsetup-tpm2.h +++ /dev/null @@ -1,126 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#include "ask-password-api.h" -#include "cryptsetup-util.h" -#include "log.h" -#include "time-util.h" -#include "tpm2-util.h" - -#if HAVE_TPM2 - -int acquire_tpm2_key( - const char *volume_name, - const char *device, - uint32_t hash_pcr_mask, - uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, - uint32_t pubkey_pcr_mask, - const char *signature_path, - const char *pcrlock_path, - uint16_t primary_alg, - const char *key_file, - size_t key_file_size, - uint64_t key_file_offset, - const void *key_data, - size_t key_data_size, - const void *policy_hash, - 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, - AskPasswordFlags ask_password_flags, - void **ret_decrypted_key, - size_t *ret_decrypted_key_size); - -int find_tpm2_auto_data( - struct crypt_device *cd, - uint32_t search_pcr_mask, - int start_token, - 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_size, - TPM2Flags *ret_flags, - int *ret_keyslot, - int *ret_token); - -#else - -static inline int acquire_tpm2_key( - const char *volume_name, - const char *device, - uint32_t hash_pcr_mask, - uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, - uint32_t pubkey_pcr_mask, - const char *signature_path, - const char *pcrlock_path, - uint16_t primary_alg, - const char *key_file, - size_t key_file_size, - uint64_t key_file_offset, - const void *key_data, - size_t key_data_size, - const void *policy_hash, - 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, - AskPasswordFlags ask_password_flags, - void **ret_decrypted_key, - size_t *ret_decrypted_key_size) { - - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "TPM2 support not available."); -} - -static inline int find_tpm2_auto_data( - struct crypt_device *cd, - uint32_t search_pcr_mask, - int start_token, - 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_size, - TPM2Flags *ret_flags, - int *ret_keyslot, - int *ret_token) { - - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "TPM2 support not available."); -} - -#endif diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index 1822beb..85897ae 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -101,10 +101,12 @@ static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; static char *arg_tpm2_signature = NULL; static bool arg_tpm2_pin = false; static char *arg_tpm2_pcrlock = NULL; -static bool arg_headless = false; static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC; static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */ static char **arg_tpm2_measure_banks = NULL; +static char *arg_link_keyring = NULL; +static char *arg_link_key_type = NULL; +static char *arg_link_key_description = NULL; STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); @@ -118,6 +120,9 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_pcrlock, freep); +STATIC_DESTRUCTOR_REGISTER(arg_link_keyring, freep); +STATIC_DESTRUCTOR_REGISTER(arg_link_key_type, freep); +STATIC_DESTRUCTOR_REGISTER(arg_link_key_description, freep); static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = { [PASSPHRASE_REGULAR] = "passphrase", @@ -338,7 +343,7 @@ static int parse_one_option(const char *option) { arg_pkcs11_uri_auto = true; } else { if (!pkcs11_uri_valid(val)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "pkcs11-uri= parameter expects a PKCS#11 URI, refusing"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "pkcs11-uri= parameter expects a PKCS#11 URI, refusing."); r = free_and_strdup(&arg_pkcs11_uri, val); if (r < 0) @@ -368,7 +373,7 @@ static int parse_one_option(const char *option) { _cleanup_free_ void *cid = NULL; size_t cid_size; - r = unbase64mem(val, SIZE_MAX, &cid, &cid_size); + r = unbase64mem(val, &cid, &cid_size); if (r < 0) return log_error_errno(r, "Failed to decode FIDO2 CID data: %m"); @@ -498,9 +503,9 @@ static int parse_one_option(const char *option) { return 0; } - arg_headless = r; + SET_FLAG(arg_ask_password_flags, ASK_PASSWORD_HEADLESS, r); } else if (streq(option, "headless")) - arg_headless = true; + arg_ask_password_flags |= ASK_PASSWORD_HEADLESS; else if ((val = startswith(option, "token-timeout="))) { @@ -508,6 +513,56 @@ static int parse_one_option(const char *option) { if (r < 0) log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + } else if ((val = startswith(option, "link-volume-key="))) { +#ifdef HAVE_CRYPT_SET_KEYRING_TO_LINK + const char *sep, *c; + _cleanup_free_ char *keyring = NULL, *key_type = NULL, *key_description = NULL; + + /* Stick with cryptsetup --link-vk-to-keyring format + * ::%:, + * where % is optional and defaults to 'user'. + */ + if (!(sep = strstr(val, "::"))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %s", val); + + /* cryptsetup (cli) supports passed in various formats: + * - well-known keyrings prefixed with '@' (@u user, @s session, etc) + * - text descriptions prefixed with "%:" or "%keyring:". + * - text description with no prefix. + * - numeric keyring id (ignored in current patch set). */ + if (IN_SET(*val, '@', '%')) + keyring = strndup(val, sep - val); + else + /* add type prefix if missing (crypt_set_keyring_to_link() expects it) */ + keyring = strnappend("%:", val, sep - val); + if (!keyring) + return log_oom(); + + sep += 2; + + /* % is optional (and defaults to 'user') */ + if (*sep == '%') { + /* must be separated by colon */ + if (!(c = strchr(sep, ':'))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse link-volume-key= option value: %s", val); + + key_type = strndup(sep + 1, c - sep - 1); + if (!key_type) + return log_oom(); + + sep = c + 1; + } + + key_description = strdup(sep); + if (!key_description) + return log_oom(); + + free_and_replace(arg_link_keyring, keyring); + free_and_replace(arg_link_key_type, key_type); + free_and_replace(arg_link_key_description, key_description); +#else + log_error("Build lacks libcryptsetup support for linking volume keys in user specified kernel keyrings upon device activation, ignoring: %s", option); +#endif } else if (!streq(option, "x-initrd.attach")) log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); @@ -742,17 +797,16 @@ static int get_password( PassphraseType passphrase_type, char ***ret) { - _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL; + _cleanup_free_ char *friendly = NULL, *text = NULL, *disk_path = NULL, *id = NULL; _cleanup_strv_free_erase_ char **passwords = NULL; - char *id; - int r = 0; AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE; + int r; assert(vol); assert(src); assert(ret); - if (arg_headless) + if (FLAGS_SET(arg_ask_password_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Password querying disabled via 'headless' option."); friendly = friendly_disk_name(src, vol); @@ -766,11 +820,23 @@ static int get_password( if (!disk_path) return log_oom(); - id = strjoina("cryptsetup:", disk_path); + id = strjoin("cryptsetup:", disk_path); + if (!id) + return log_oom(); + + AskPasswordRequest req = { + .message = text, + .icon = "drive-harddisk", + .id = id, + .keyring = "cryptsetup", + .credential = "cryptsetup.passphrase", + }; - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until, - flags | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), - &passwords); + r = ask_password_auto( + &req, + until, + flags | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), + &passwords); if (r < 0) return log_error_errno(r, "Failed to query password: %m"); @@ -779,12 +845,19 @@ static int get_password( assert(strv_length(passwords) == 1); + text = mfree(text); if (asprintf(&text, "Please enter %s for disk %s (verification):", passphrase_type_to_string(passphrase_type), friendly) < 0) return log_oom(); - id = strjoina("cryptsetup-verification:", disk_path); + free(id); + id = strjoin("cryptsetup-verification:", disk_path); + if (!id) + return log_oom(); - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until, flags, &passwords2); + req.message = text; + req.id = id; + + r = ask_password_auto(&req, until, flags, &passwords2); if (r < 0) return log_error_errno(r, "Failed to query verification password: %m"); @@ -1192,12 +1265,11 @@ static int crypt_activate_by_token_pin_ask_password( const char *name, const char *type, usec_t until, - bool headless, void *userdata, uint32_t activation_flags, const char *message, - const char *key_name, - const char *credential_name) { + const char *keyring, + const char *credential) { #if HAVE_LIBCRYPTSETUP_PLUGINS AskPasswordFlags flags = arg_ask_password_flags | ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; @@ -1222,12 +1294,20 @@ static int crypt_activate_by_token_pin_ask_password( return r; } - if (headless) + if (FLAGS_SET(arg_ask_password_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); for (;;) { pins = strv_free_erase(pins); - r = ask_password_auto(message, "drive-harddisk", /* id= */ NULL, key_name, credential_name, until, flags, &pins); + + AskPasswordRequest req = { + .message = message, + .icon = "drive-harddisk", + .keyring = keyring, + .credential = credential, + }; + + r = ask_password_auto(&req, until, flags, &pins); if (r < 0) return r; @@ -1251,7 +1331,6 @@ static int attach_luks2_by_fido2_via_plugin( struct crypt_device *cd, const char *name, usec_t until, - bool headless, void *userdata, uint32_t activation_flags) { @@ -1260,7 +1339,6 @@ static int attach_luks2_by_fido2_via_plugin( name, "systemd-fido2", until, - headless, userdata, activation_flags, "Please enter security token PIN:", @@ -1315,7 +1393,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( for (;;) { if (use_libcryptsetup_plugin && !arg_fido2_cid) { - r = attach_luks2_by_fido2_via_plugin(cd, name, until, arg_headless, arg_fido2_device, flags); + r = attach_luks2_by_fido2_via_plugin(cd, name, until, arg_fido2_device, flags); if (IN_SET(r, -ENOTUNIQ, -ENXIO, -ENOENT)) return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Automatic FIDO2 metadata discovery was not possible because missing or not unique, falling back to traditional unlocking."); @@ -1331,10 +1409,11 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, - arg_headless, required, - &decrypted_key, &decrypted_key_size, - arg_ask_password_flags); + "cryptsetup.fido2-pin", + arg_ask_password_flags, + &decrypted_key, + &decrypted_key_size); else r = acquire_fido2_key_auto( cd, @@ -1342,9 +1421,10 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( friendly, arg_fido2_device, until, - arg_headless, - &decrypted_key, &decrypted_key_size, - arg_ask_password_flags); + "cryptsetup.fido2-pin", + arg_ask_password_flags, + &decrypted_key, + &decrypted_key_size); if (r >= 0) break; } @@ -1405,7 +1485,7 @@ static int attach_luks2_by_pkcs11_via_plugin( const char *name, const char *friendly_name, usec_t until, - bool headless, + const char *askpw_credential, uint32_t flags) { #if HAVE_LIBCRYPTSETUP_PLUGINS @@ -1417,7 +1497,7 @@ static int attach_luks2_by_pkcs11_via_plugin( systemd_pkcs11_plugin_params params = { .friendly_name = friendly_name, .until = until, - .headless = headless, + .askpw_credential = askpw_credential, .askpw_flags = arg_ask_password_flags, }; @@ -1481,7 +1561,13 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( for (;;) { if (use_libcryptsetup_plugin && arg_pkcs11_uri_auto) - r = attach_luks2_by_pkcs11_via_plugin(cd, name, friendly, until, arg_headless, flags); + r = attach_luks2_by_pkcs11_via_plugin( + cd, + name, + friendly, + until, + "cryptsetup.pkcs11-pin", + flags); else { r = decrypt_pkcs11_key( name, @@ -1490,7 +1576,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( key_file, arg_keyfile_size, arg_keyfile_offset, key_data, key_data_size, until, - arg_headless, + arg_ask_password_flags, &decrypted_key, &decrypted_key_size); if (r >= 0) break; @@ -1615,7 +1701,6 @@ static int attach_luks2_by_tpm2_via_plugin( struct crypt_device *cd, const char *name, usec_t until, - bool headless, uint32_t flags) { #if HAVE_LIBCRYPTSETUP_PLUGINS @@ -1635,7 +1720,6 @@ static int attach_luks2_by_tpm2_via_plugin( name, "systemd-tpm2", until, - headless, ¶ms, flags, "Please enter TPM2 PIN:", @@ -1650,18 +1734,16 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( struct crypt_device *cd, const char *name, const char *key_file, - const void *key_data, - size_t key_data_size, + const struct iovec *key_data, usec_t until, uint32_t flags, bool pass_volume_key) { _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *monitor = NULL; - _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(iovec_done_erase) struct iovec decrypted_key = {}; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_free_ char *friendly = NULL; int keyslot = arg_key_slot, r; - size_t decrypted_key_size; assert(cd); assert(name); @@ -1672,7 +1754,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( return log_oom(); for (;;) { - if (key_file || key_data) { + if (key_file || iovec_is_set(key_data)) { /* If key data is specified, use that */ r = acquire_tpm2_key( @@ -1680,21 +1762,22 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( arg_tpm2_device, arg_tpm2_pcr_mask == UINT32_MAX ? TPM2_PCR_MASK_DEFAULT : arg_tpm2_pcr_mask, UINT16_MAX, - /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey= */ NULL, /* pubkey_pcr_mask= */ 0, /* signature_path= */ NULL, /* pcrlock_path= */ NULL, /* primary_alg= */ 0, key_file, arg_keyfile_size, arg_keyfile_offset, - 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, + key_data, + /* policy_hash= */ NULL, /* we don't know the policy hash */ + /* salt= */ NULL, + /* srk= */ NULL, + /* pcrlock_nv= */ NULL, arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0, until, - arg_headless, + "cryptsetup.tpm2-pin", arg_ask_password_flags, - &decrypted_key, &decrypted_key_size); + &decrypted_key); if (r >= 0) break; if (IN_SET(r, -EACCES, -ENOLCK)) @@ -1707,7 +1790,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( return -EAGAIN; /* Mangle error code: let's make any form of TPM2 failure non-fatal. */ } } else { - r = attach_luks2_by_tpm2_via_plugin(cd, name, until, arg_headless, flags); + r = attach_luks2_by_tpm2_via_plugin(cd, name, until, flags); if (r >= 0) return 0; /* EAGAIN means: no tpm2 chip found @@ -1725,8 +1808,6 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( } if (r == -EOPNOTSUPP) { /* Plugin not available, let's process TPM2 stuff right here instead */ - _cleanup_free_ void *blob = NULL, *policy_hash = NULL; - size_t blob_size, policy_hash_size; bool found_some = false; int token = 0; /* first token to look at */ @@ -1735,8 +1816,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( * works. */ for (;;) { - _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL; - size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0; + _cleanup_(iovec_done) struct iovec pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}; uint32_t hash_pcr_mask, pubkey_pcr_mask; uint16_t pcr_bank, primary_alg; TPM2Flags tpm2_flags; @@ -1747,13 +1828,14 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( token, /* search for the token with this index, or any later index than this */ &hash_pcr_mask, &pcr_bank, - &pubkey, &pubkey_size, + &pubkey, &pubkey_pcr_mask, &primary_alg, - &blob, &blob_size, - &policy_hash, &policy_hash_size, - &salt, &salt_size, - &srk_buf, &srk_buf_size, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, &tpm2_flags, &keyslot, &token); @@ -1778,21 +1860,22 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( arg_tpm2_device, hash_pcr_mask, pcr_bank, - pubkey, pubkey_size, + &pubkey, pubkey_pcr_mask, arg_tpm2_signature, arg_tpm2_pcrlock, primary_alg, /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ - blob, blob_size, - policy_hash, policy_hash_size, - salt, salt_size, - srk_buf, srk_buf_size, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, tpm2_flags, until, - arg_headless, + "cryptsetup.tpm2-pin", arg_ask_password_flags, - &decrypted_key, &decrypted_key_size); + &decrypted_key); if (IN_SET(r, -EACCES, -ENOLCK)) return log_notice_errno(SYNTHETIC_ERRNO(EAGAIN), "TPM2 PIN unlock failed, falling back to traditional unlocking."); if (r != -EPERM) @@ -1837,17 +1920,16 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( log_debug("Got one or more potentially relevant udev events, rescanning for TPM2..."); } - assert(decrypted_key); if (pass_volume_key) - r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key.iov_base, decrypted_key.iov_len, flags); else { _cleanup_(erase_and_freep) char *base64_encoded = NULL; ssize_t base64_encoded_size; /* Before using this key as passphrase we base64 encode it, for compat with homed */ - base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + base64_encoded_size = base64mem(decrypted_key.iov_base, decrypted_key.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_oom(); @@ -2045,7 +2127,7 @@ static int attach_luks_or_plain_or_bitlk( crypt_get_device_name(cd)); if (arg_tpm2_device || arg_tpm2_device_auto) - return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); + return attach_luks_or_plain_or_bitlk_by_tpm2(cd, name, key_file, &IOVEC_MAKE(key_data, key_data_size), until, flags, pass_volume_key); if (arg_fido2_device || arg_fido2_device_auto) return attach_luks_or_plain_or_bitlk_by_fido2(cd, name, key_file, key_data, key_data_size, until, flags, pass_volume_key); if (arg_pkcs11_uri || arg_pkcs11_uri_auto) @@ -2289,6 +2371,15 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to load LUKS superblock on device %s: %m", crypt_get_device_name(cd)); +/* since cryptsetup 2.7.0 (Jan 2024) */ +#if HAVE_CRYPT_SET_KEYRING_TO_LINK + if (arg_link_key_description) { + r = crypt_set_keyring_to_link(cd, arg_link_key_description, NULL, arg_link_key_type, arg_link_keyring); + if (r < 0) + log_warning_errno(r, "Failed to set keyring or key description to link volume key in, ignoring: %m"); + } +#endif + if (arg_header) { r = crypt_set_data_device(cd, source); if (r < 0) @@ -2302,7 +2393,6 @@ static int run(int argc, char *argv[]) { volume, /* type= */ NULL, until, - arg_headless, /* userdata= */ NULL, flags, "Please enter LUKS2 token PIN:", @@ -2326,8 +2416,10 @@ static int run(int argc, char *argv[]) { } #endif + bool use_cached_passphrase = true; + _cleanup_strv_free_erase_ char **passwords = NULL; for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { - _cleanup_strv_free_erase_ char **passwords = NULL; + log_debug("Beginning attempt %u to unlock.", tries); /* When we were able to acquire multiple keys, let's always process them in this order: * @@ -2338,7 +2430,9 @@ static int run(int argc, char *argv[]) { * 5. We enquire the user for a password */ - if (!key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto && !arg_tpm2_device && !arg_tpm2_device_auto) { + if (!passwords && !key_file && !key_data && !arg_pkcs11_uri && !arg_pkcs11_uri_auto && !arg_fido2_device && !arg_fido2_device_auto && !arg_tpm2_device && !arg_tpm2_device_auto) { + + /* If we have nothing to try anymore, then acquire a new password */ if (arg_try_empty_password) { /* Hmm, let's try an empty password now, but only once */ @@ -2358,7 +2452,8 @@ static int run(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No passphrase or recovery key registered."); } - r = get_password(volume, source, until, tries == 0 && !arg_verify, passphrase_type, &passwords); + r = get_password(volume, source, until, use_cached_passphrase && !arg_verify, passphrase_type, &passwords); + use_cached_passphrase = false; if (r == -EAGAIN) continue; if (r < 0) @@ -2375,17 +2470,44 @@ static int run(int argc, char *argv[]) { if (r != -EAGAIN) return r; - /* Key not correct? Let's try again! */ + /* Key not correct? Let's try again, but let's invalidate one of the passed fields, + * so that we fallback to the next best thing. */ - key_file = NULL; - key_data = erase_and_free(key_data); - key_data_size = 0; - arg_pkcs11_uri = mfree(arg_pkcs11_uri); - arg_pkcs11_uri_auto = false; - arg_fido2_device = mfree(arg_fido2_device); - arg_fido2_device_auto = false; - arg_tpm2_device = mfree(arg_tpm2_device); - arg_tpm2_device_auto = false; + if (arg_tpm2_device || arg_tpm2_device_auto) { + arg_tpm2_device = mfree(arg_tpm2_device); + arg_tpm2_device_auto = false; + continue; + } + + if (arg_fido2_device || arg_fido2_device_auto) { + arg_fido2_device = mfree(arg_fido2_device); + arg_fido2_device_auto = false; + continue; + } + + if (arg_pkcs11_uri || arg_pkcs11_uri_auto) { + arg_pkcs11_uri = mfree(arg_pkcs11_uri); + arg_pkcs11_uri_auto = false; + continue; + } + + if (key_data) { + key_data = erase_and_free(key_data); + key_data_size = 0; + continue; + } + + if (key_file) { + key_file = NULL; + continue; + } + + if (passwords) { + passwords = strv_free_erase(passwords); + continue; + } + + log_debug("Prepared for next attempt to unlock."); } if (arg_tries != 0 && tries >= arg_tries) @@ -2395,7 +2517,7 @@ static int run(int argc, char *argv[]) { const char *volume = ASSERT_PTR(argv[optind + 1]); if (argc - optind >= 3) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach does not accept more than one argument."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach does not accept more than one argument."); if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); diff --git a/src/cryptsetup/meson.build b/src/cryptsetup/meson.build index 90e2be7..9ccc098 100644 --- a/src/cryptsetup/meson.build +++ b/src/cryptsetup/meson.build @@ -11,10 +11,6 @@ if conf.get('HAVE_P11KIT') == 1 systemd_cryptsetup_sources += files('cryptsetup-pkcs11.c') endif -if conf.get('HAVE_TPM2') == 1 - systemd_cryptsetup_sources += files('cryptsetup-tpm2.c') -endif - executables += [ executable_template + { 'name' : 'systemd-cryptsetup', diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 8a474c5..7637980 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -3,13 +3,17 @@ #include #include "alloc-util.h" +#include "creds-util.h" #include "dropin.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fileio-label.h" #include "generator.h" #include "initrd-util.h" -#include "mkdir-label.h" #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" +#include "recurse-dir.h" #include "special.h" #include "string-util.h" #include "strv.h" @@ -20,12 +24,15 @@ static const char *arg_dest = NULL; static char *arg_default_unit = NULL; static char **arg_mask = NULL; static char **arg_wants = NULL; -static char *arg_debug_shell = NULL; +static bool arg_debug_shell = false; +static char *arg_debug_tty = NULL; +static char *arg_default_debug_tty = NULL; STATIC_DESTRUCTOR_REGISTER(arg_default_unit, freep); STATIC_DESTRUCTOR_REGISTER(arg_mask, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_wants, strv_freep); -STATIC_DESTRUCTOR_REGISTER(arg_debug_shell, freep); +STATIC_DESTRUCTOR_REGISTER(arg_debug_tty, freep); +STATIC_DESTRUCTOR_REGISTER(arg_default_debug_tty, freep); static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -42,8 +49,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (r < 0) return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_mask, n); - if (r < 0) + if (strv_consume(&arg_mask, n) < 0) return log_oom(); } else if (streq(key, "systemd.wants")) { @@ -56,20 +62,24 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (r < 0) return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_wants, n); - if (r < 0) + if (strv_consume(&arg_wants, n) < 0) return log_oom(); } else if (proc_cmdline_key_streq(key, "systemd.debug_shell")) { - const char *t = NULL; r = value ? parse_boolean(value) : 1; - if (r < 0) - t = skip_dev_prefix(value); - else if (r > 0) - t = skip_dev_prefix(DEBUGTTY); + arg_debug_shell = r != 0; + if (r >= 0) + return 0; + + return free_and_strdup_warn(&arg_debug_tty, skip_dev_prefix(value)); + + } else if (proc_cmdline_key_streq(key, "systemd.default_debug_tty")) { - return free_and_strdup_warn(&arg_debug_shell, t); + if (proc_cmdline_value_missing(key, value)) + return 0; + + return free_and_strdup_warn(&arg_default_debug_tty, skip_dev_prefix(value)); } else if (streq(key, "systemd.unit")) { @@ -95,14 +105,12 @@ static int generate_mask_symlinks(void) { STRV_FOREACH(u, arg_mask) { _cleanup_free_ char *p = NULL; - p = path_join(empty_to_root(arg_dest), *u); + p = path_join(arg_dest, *u); if (!p) return log_oom(); if (symlink("/dev/null", p) < 0) - r = log_error_errno(errno, - "Failed to create mask symlink %s: %m", - p); + RET_GATHER(r, log_error_errno(errno, "Failed to create mask symlink '%s': %m", p)); } return r; @@ -127,33 +135,126 @@ static int generate_wants_symlinks(void) { if (!f) return log_oom(); - r = generator_add_symlink(arg_dest, target, "wants", f); - if (r < 0) - return r; + RET_GATHER(r, generator_add_symlink(arg_dest, target, "wants", f)); } return r; } -static void install_debug_shell_dropin(const char *dir) { +static int install_debug_shell_dropin(void) { + const char *tty = arg_debug_tty ?: arg_default_debug_tty; + int r; + + if (!tty || path_equal(tty, skip_dev_prefix(DEBUGTTY))) + return 0; + + r = write_drop_in_format(arg_dest, "debug-shell.service", 50, "tty", + "# Automatically generated by systemd-debug-generator\n\n" + "[Unit]\n" + "Description=Early root shell on /dev/%s FOR DEBUGGING ONLY\n" + "ConditionPathExists=\n" + "\n[Service]\n" + "TTYPath=/dev/%s\n", + tty, tty); + if (r < 0) + return log_warning_errno(r, "Failed to write drop-in for debug-shell.service: %m"); + + return 1; +} + +static int process_unit_credentials(const char *credentials_dir) { + _cleanup_free_ DirectoryEntries *des = NULL; int r; - if (streq(arg_debug_shell, skip_dev_prefix(DEBUGTTY))) - return; + assert(credentials_dir); - r = write_drop_in_format(dir, "debug-shell.service", 50, "tty", - "[Unit]\n" - "Description=Early root shell on /dev/%s FOR DEBUGGING ONLY\n" - "ConditionPathExists=\n" - "[Service]\n" - "TTYPath=/dev/%s", - arg_debug_shell, arg_debug_shell); + r = readdir_all_at(AT_FDCWD, credentials_dir, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); if (r < 0) - log_warning_errno(r, "Failed to write drop-in for debug-shell.service, ignoring: %m"); + return log_error_errno(r, "Failed to enumerate credentials from credentials directory '%s': %m", credentials_dir); + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + const char *unit, *dropin; + + if (de->d_type != DT_REG) + continue; + + unit = startswith(de->d_name, "systemd.extra-unit."); + dropin = startswith(de->d_name, "systemd.unit-dropin."); + + if (!unit && !dropin) + continue; + + _cleanup_free_ char *d = NULL; + + r = read_credential_with_decryption(de->d_name, (void**) &d, NULL); + if (r < 0) { + log_warning_errno(r, "Failed to read credential '%s', ignoring: %m", de->d_name); + continue; + } + + if (unit) { + _cleanup_free_ char *p = NULL; + + if (!unit_name_is_valid(unit, UNIT_NAME_ANY)) { + log_warning("Invalid unit name '%s' in credential '%s', ignoring.", + unit, de->d_name); + continue; + } + + p = path_join(arg_dest, unit); + if (!p) + return log_oom(); + + r = write_string_file_atomic_label(p, d); + if (r < 0) { + log_warning_errno(r, "Failed to write unit file '%s' from credential '%s', ignoring: %m", + unit, de->d_name); + continue; + } + + log_debug("Wrote unit file '%s' from credential '%s'", unit, de->d_name); + + } else if (dropin) { + _cleanup_free_ char *dropin_unit = NULL; + const char *tilde, *dropin_name; + + tilde = strchrnul(dropin, '~'); + dropin_unit = strndup(dropin, tilde - dropin); + if (!dropin_unit) + return log_oom(); + + if (!unit_name_is_valid(dropin_unit, UNIT_NAME_ANY)) { + log_warning("Invalid unit name '%s' in credential '%s', ignoring.", + dropin_unit, de->d_name); + continue; + } + + dropin_name = isempty(tilde) ? "50-credential" : tilde + 1; + if (isempty(dropin_name)) { + log_warning("Empty drop-in name for unit '%s' in credential '%s', ignoring.", + dropin_unit, de->d_name); + continue; + } + + r = write_drop_in(arg_dest, dropin_unit, /* level = */ UINT_MAX, dropin_name, d); + if (r < 0) { + log_warning_errno(r, "Failed to write drop-in '%s' for unit '%s' from credential '%s', ignoring: %m", + dropin_name, dropin_unit, de->d_name); + continue; + } + + log_debug("Wrote drop-in '%s' for unit '%s' from credential '%s'", dropin_name, dropin_unit, de->d_name); + } else + assert_not_reached(); + } + + return 0; } static int run(const char *dest, const char *dest_early, const char *dest_late) { - int r, q; + const char *credentials_dir; + int r; assert_se(arg_dest = dest_early); @@ -162,17 +263,22 @@ static int run(const char *dest, const char *dest_early, const char *dest_late) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); if (arg_debug_shell) { - r = strv_extend(&arg_wants, "debug-shell.service"); - if (r < 0) + if (strv_extend(&arg_wants, "debug-shell.service") < 0) return log_oom(); - install_debug_shell_dropin(arg_dest); + RET_GATHER(r, install_debug_shell_dropin()); } - r = generate_mask_symlinks(); - q = generate_wants_symlinks(); + if (get_credentials_dir(&credentials_dir) >= 0) + RET_GATHER(r, process_unit_credentials(credentials_dir)); - return r < 0 ? r : q; + if (get_encrypted_credentials_dir(&credentials_dir) >= 0) + RET_GATHER(r, process_unit_credentials(credentials_dir)); + + RET_GATHER(r, generate_mask_symlinks()); + RET_GATHER(r, generate_wants_symlinks()); + + return r; } DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/delta/delta.c b/src/delta/delta.c index 3337b7f..3433250 100644 --- a/src/delta/delta.c +++ b/src/delta/delta.c @@ -314,7 +314,7 @@ static int enumerate_dir( dirs[n_dirs] = strdup(de->d_name); if (!dirs[n_dirs]) return -ENOMEM; - n_dirs ++; + n_dirs++; } if (!dirent_is_file(de)) @@ -326,7 +326,7 @@ static int enumerate_dir( files[n_files] = strdup(de->d_name); if (!files[n_files]) return -ENOMEM; - n_files ++; + n_files++; } strv_sort(dirs); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index c858e6a..d68c29c 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -27,13 +27,16 @@ #include "format-util.h" #include "fs-util.h" #include "hexdecoct.h" +#include "libarchive-util.h" #include "log.h" #include "loop-util.h" #include "main-func.h" +#include "missing_syscall.h" #include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" #include "namespace-util.h" +#include "nsresource.h" #include "parse-argument.h" #include "parse-util.h" #include "path-util.h" @@ -46,8 +49,9 @@ #include "strv.h" #include "terminal-util.h" #include "tmpfile-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" +#include "vpick.h" static enum { ACTION_DISSECT, @@ -62,6 +66,7 @@ static enum { ACTION_COPY_TO, ACTION_DISCOVER, ACTION_VALIDATE, + ACTION_MAKE_ARCHIVE, } arg_action = ACTION_DISSECT; static char *arg_image = NULL; static char *arg_root = NULL; @@ -76,7 +81,8 @@ static DissectImageFlags arg_flags = DISSECT_IMAGE_USR_NO_ROOT | DISSECT_IMAGE_GROWFS | DISSECT_IMAGE_PIN_PARTITION_DEVICES | - DISSECT_IMAGE_ADD_PARTITION_DEVICES; + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; static VeritySettings arg_verity_settings = VERITY_SETTINGS_DEFAULT; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; @@ -87,6 +93,7 @@ static char **arg_argv = NULL; static char *arg_loop_ref = NULL; static ImagePolicy *arg_image_policy = NULL; static bool arg_mtree_hash = true; +static bool arg_via_service = false; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -116,6 +123,7 @@ static int help(void) { "%1$s [OPTIONS...] --with IMAGE [COMMAND…]\n" "%1$s [OPTIONS...] --copy-from IMAGE PATH [TARGET]\n" "%1$s [OPTIONS...] --copy-to IMAGE [SOURCE] PATH\n" + "%1$s [OPTIONS...] --make-archive IMAGE [TARGET]\n" "%1$s [OPTIONS...] --discover\n" "%1$s [OPTIONS...] --validate IMAGE\n" "\n%5$sDissect a Discoverable Disk Image (DDI).%6$s\n\n" @@ -150,13 +158,14 @@ static int help(void) { " -u --umount Unmount the image from the specified directory\n" " -U Shortcut for --umount --rmdir\n" " --attach Attach the disk image to a loopback block device\n" - " --detach Detach a loopback block device gain\n" + " --detach Detach a loopback block device again\n" " -l --list List all the files and directories of the specified\n" " OS image\n" " --mtree Show BSD mtree manifest of OS image\n" " --with Mount, run command, unmount\n" " -x --copy-from Copy files from image to host\n" " -a --copy-to Copy files from host to image\n" + " --make-archive Convert the DDI to an archive file\n" " --discover Discover DDIs in well known directories\n" " --validate Validate image and image policy\n" "\nSee the %2$s for details.\n", @@ -263,6 +272,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_POLICY, ARG_VALIDATE, ARG_MTREE_HASH, + ARG_MAKE_ARCHIVE, }; static const struct option options[] = { @@ -295,6 +305,7 @@ static int parse_argv(int argc, char *argv[]) { { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "validate", no_argument, NULL, ARG_VALIDATE }, { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, + { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, {} }; @@ -423,7 +434,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_free_ void *p = NULL; size_t l; - r = unhexmem(optarg, strlen(optarg), &p, &l); + r = unhexmem(optarg, &p, &l); if (r < 0) return log_error_errno(r, "Failed to parse root hash '%s': %m", optarg); if (l < sizeof(sd_id128_t)) @@ -441,7 +452,7 @@ static int parse_argv(int argc, char *argv[]) { void *p; if ((value = startswith(optarg, "base64:"))) { - r = unbase64mem(value, strlen(value), &p, &l); + r = unbase64mem(value, &p, &l); if (r < 0) return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); } else { @@ -518,6 +529,15 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_MAKE_ARCHIVE: + + r = dlopen_libarchive(); + if (r < 0) + return log_error_errno(r, "Archive support not available (compiled without libarchive, or libarchive not installed?)."); + + arg_action = ACTION_MAKE_ARCHIVE; + break; + case '?': return -EINVAL; @@ -600,6 +620,19 @@ static int parse_argv(int argc, char *argv[]) { arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; break; + case ACTION_MAKE_ARCHIVE: + if (argc < optind + 1 || argc > optind + 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Expected an image file, and an optional target path as only arguments."); + + r = parse_image_path_argument(argv[optind], &arg_root, &arg_image); + if (r < 0) + return r; + + arg_target = argc > optind + 1 ? empty_or_dash_to_null(argv[optind + 1]) : NULL; + arg_flags |= DISSECT_IMAGE_READ_ONLY | DISSECT_IMAGE_REQUIRE_ROOT; + break; + case ACTION_COPY_FROM: if (argc < optind + 2 || argc > optind + 3) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -674,6 +707,18 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + r = getenv_bool("SYSTEMD_USE_MOUNTFSD"); + if (r < 0) { + if (r != -ENXIO) + return log_error_errno(r, "Failed to parse $SYSTEMD_USE_MOUNTFSD: %m"); + } else + arg_via_service = r; + + if (!IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_DISCOVER, ACTION_VALIDATE) && geteuid() != 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root."); + + SET_FLAG(arg_flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH, isatty(STDIN_FILENO)); + return 1; } @@ -685,7 +730,7 @@ static int parse_argv_as_mount_helper(int argc, char *argv[]) { /* Implements util-linux "external helper" command line interface, as per mount(8) man page. */ while ((c = getopt(argc, argv, "sfnvN:o:t:")) >= 0) { - switch(c) { + switch (c) { case 'f': fake = true; @@ -811,7 +856,11 @@ static int get_extension_scopes(DissectedImage *m, ImageClass class, char ***ret return 1; } -static int action_dissect(DissectedImage *m, LoopDevice *d) { +static int action_dissect( + DissectedImage *m, + LoopDevice *d, + int userns_fd) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(table_unrefp) Table *t = NULL; _cleanup_free_ char *bn = NULL; @@ -819,7 +868,6 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { int r; assert(m); - assert(d); r = path_extract_filename(arg_image, &bn); if (r < 0) @@ -828,16 +876,15 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { if (arg_json_format_flags & (JSON_FORMAT_OFF|JSON_FORMAT_PRETTY|JSON_FORMAT_PRETTY_AUTO)) pager_open(arg_pager_flags); - if (arg_json_format_flags & JSON_FORMAT_OFF) - printf(" Name: %s%s%s\n", ansi_highlight(), bn, ansi_normal()); + if (arg_json_format_flags & JSON_FORMAT_OFF) { + printf(" File Name: %s%s%s\n", + ansi_highlight(), bn, ansi_normal()); - if (ioctl(d->fd, BLKGETSIZE64, &size) < 0) - log_debug_errno(errno, "Failed to query size of loopback device: %m"); - else if (arg_json_format_flags & JSON_FORMAT_OFF) - printf(" Size: %s\n", FORMAT_BYTES(size)); + printf(" Size: %s\n", + FORMAT_BYTES(m->image_size)); - if (arg_json_format_flags & JSON_FORMAT_OFF) { - printf(" Sec. Size: %" PRIu32 "\n", m->sector_size); + printf(" Sec. Size: %" PRIu32 "\n", + m->sector_size); printf(" Arch.: %s\n", strna(architecture_to_string(dissected_image_architecture(m)))); @@ -846,7 +893,7 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { fflush(stdout); } - r = dissected_image_acquire_metadata(m, 0); + r = dissected_image_acquire_metadata(m, userns_fd, /* extra_flags= */ 0); if (r == -ENXIO) return log_error_errno(r, "No root partition discovered."); if (r == -EUCLEAN) @@ -861,6 +908,9 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { return log_error_errno(r, "Failed to acquire image metadata: %m"); else if (arg_json_format_flags & JSON_FORMAT_OFF) { + if (m->image_name && !streq(m->image_name, bn)) + printf("Image Name: %s\n", m->image_name); + if (!sd_id128_is_null(m->image_uuid)) printf("Image UUID: %s\n", SD_ID128_TO_UUID_STRING(m->image_uuid)); @@ -963,6 +1013,11 @@ static int action_dissect(DissectedImage *m, LoopDevice *d) { table_set_ersatz_string(t, TABLE_ERSATZ_DASH); (void) table_set_align_percent(t, table_get_cell(t, 0, 9), 100); + /* Hide the device path if this is a loopback device that is not relinquished, since that means the + * device node is not going to be useful the instant our command exits */ + if ((!d || d->created) && (arg_json_format_flags & JSON_FORMAT_OFF)) + table_hide_column_from_display(t, 8); + for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) { DissectedPartition *p = m->partitions + i; @@ -1050,7 +1105,6 @@ static int action_mount(DissectedImage *m, LoopDevice *d) { int r; assert(m); - assert(d); assert(arg_action == ACTION_MOUNT); r = dissected_image_mount_and_warn( @@ -1063,9 +1117,11 @@ static int action_mount(DissectedImage *m, LoopDevice *d) { if (r < 0) return r; - r = loop_device_flock(d, LOCK_UN); - if (r < 0) - return log_error_errno(r, "Failed to unlock loopback block device: %m"); + if (d) { + r = loop_device_flock(d, LOCK_UN); + if (r < 0) + return log_error_errno(r, "Failed to unlock loopback block device: %m"); + } r = dissected_image_relinquish(m); if (r < 0) @@ -1095,7 +1151,6 @@ static int list_print_item( static int get_file_sha256(int inode_fd, uint8_t ret[static SHA256_DIGEST_SIZE]) { _cleanup_close_ int fd = -EBADF; - struct sha256_ctx ctx; /* convert O_PATH fd into a regular one */ fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC); @@ -1105,23 +1160,7 @@ static int get_file_sha256(int inode_fd, uint8_t ret[static SHA256_DIGEST_SIZE]) /* Calculating the SHA sum might be slow, hence let's flush STDOUT first, to give user an idea where we are slow. */ fflush(stdout); - sha256_init_ctx(&ctx); - - for (;;) { - uint8_t buffer[64 * 1024]; - ssize_t n; - - n = read(fd, buffer, sizeof(buffer)); - if (n < 0) - return -errno; - if (n == 0) - break; - - sha256_process_bytes(buffer, n, &ctx); - } - - sha256_finish_ctx(&ctx, ret); - return 0; + return sha256_fd(fd, UINT64_MAX, ret); } static const char *pick_color_for_uid_gid(uid_t uid) { @@ -1267,36 +1306,136 @@ static int mtree_print_item( return RECURSE_DIR_CONTINUE; } -static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(rmdir_and_freep) char *created_dir = NULL; - _cleanup_free_ char *temp = NULL; +#if HAVE_LIBARCHIVE +static int archive_item( + RecurseDirEvent event, + const char *path, + int dir_fd, + int inode_fd, + const struct dirent *de, + const struct statx *sx, + void *userdata) { + + struct archive *a = ASSERT_PTR(userdata); + int r; + + assert(path); + + if (!IN_SET(event, RECURSE_DIR_ENTER, RECURSE_DIR_ENTRY)) + return RECURSE_DIR_CONTINUE; + + assert(inode_fd >= 0); + assert(sx); + + log_debug("Archiving %s\n", path); + + _cleanup_(sym_archive_entry_freep) struct archive_entry *entry = NULL; + entry = sym_archive_entry_new(); + if (!entry) + return log_oom(); + + assert(FLAGS_SET(sx->stx_mask, STATX_TYPE|STATX_MODE)); + sym_archive_entry_set_pathname(entry, path); + sym_archive_entry_set_filetype(entry, sx->stx_mode); + + if (!S_ISLNK(sx->stx_mode)) + sym_archive_entry_set_perm(entry, sx->stx_mode); + + if (FLAGS_SET(sx->stx_mask, STATX_UID)) + sym_archive_entry_set_uid(entry, sx->stx_uid); + if (FLAGS_SET(sx->stx_mask, STATX_GID)) + sym_archive_entry_set_gid(entry, sx->stx_gid); + + if (S_ISREG(sx->stx_mode)) { + if (!FLAGS_SET(sx->stx_mask, STATX_SIZE)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Unable to determine file size of '%s'.", path); + + sym_archive_entry_set_size(entry, sx->stx_size); + } + + if (S_ISCHR(sx->stx_mode) || S_ISBLK(sx->stx_mode)) { + sym_archive_entry_set_rdevmajor(entry, sx->stx_rdev_major); + sym_archive_entry_set_rdevminor(entry, sx->stx_rdev_minor); + } + + /* We care about a modicum of reproducibility here, hence we don't save atime/btime here */ + if (FLAGS_SET(sx->stx_mask, STATX_MTIME)) + sym_archive_entry_set_mtime(entry, sx->stx_mtime.tv_sec, sx->stx_mtime.tv_nsec); + if (FLAGS_SET(sx->stx_mask, STATX_CTIME)) + sym_archive_entry_set_ctime(entry, sx->stx_ctime.tv_sec, sx->stx_ctime.tv_nsec); + + if (S_ISLNK(sx->stx_mode)) { + _cleanup_free_ char *s = NULL; + + assert(dir_fd >= 0); + assert(de); + + r = readlinkat_malloc(dir_fd, de->d_name, &s); + if (r < 0) + return log_error_errno(r, "Failed to read symlink target of '%s': %m", path); + + sym_archive_entry_set_symlink(entry, s); + } + + if (sym_archive_write_header(a, entry) != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive entry header: %s", sym_archive_error_string(a)); + + if (S_ISREG(sx->stx_mode)) { + _cleanup_close_ int data_fd = -EBADF; + + /* Convert the O_PATH fd in a proper fd */ + data_fd = fd_reopen(inode_fd, O_RDONLY|O_CLOEXEC); + if (data_fd < 0) + return log_error_errno(data_fd, "Failed to open '%s': %m", path); + + for (;;) { + char buffer[64*1024]; + ssize_t l; + + l = read(data_fd, buffer, sizeof(buffer)); + if (l < 0) + return log_error_errno(errno, "Failed to read '%s': %m", path); + if (l == 0) + break; + + la_ssize_t k; + k = sym_archive_write_data(a, buffer, l); + if (k < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to write archive data: %s", sym_archive_error_string(a)); + } + } + + return RECURSE_DIR_CONTINUE; +} +#endif + +static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopDevice *d, int userns_fd) { + _cleanup_(umount_and_freep) char *mounted_dir = NULL; + _cleanup_free_ char *t = NULL; const char *root; int r; - assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO)); + assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE)); if (arg_image) { assert(m); - assert(d); - r = detach_mount_namespace(); + if (userns_fd < 0) + r = detach_mount_namespace_harder(0, 0); + else + r = detach_mount_namespace_userns(userns_fd); if (r < 0) return log_error_errno(r, "Failed to detach mount namespace: %m"); - r = tempfn_random_child(NULL, program_invocation_short_name, &temp); + /* Create a place we can mount things onto soon. We use a fixed path shared by all invocations. Given + * the mounts are done in a mount namespace there's not going to be a collision here */ + r = get_common_dissect_directory(&t); if (r < 0) - return log_error_errno(r, "Failed to generate temporary mount directory: %m"); - - r = mkdir_p(temp, 0700); - if (r < 0) - return log_error_errno(r, "Failed to create mount point: %m"); - - created_dir = TAKE_PTR(temp); + return log_error_errno(r, "Failed generate private mount directory: %m"); r = dissected_image_mount_and_warn( m, - created_dir, + t, /* uid_shift= */ UID_INVALID, /* uid_range= */ UID_INVALID, /* userns_fd= */ -EBADF, @@ -1304,11 +1443,13 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { if (r < 0) return r; - mounted_dir = TAKE_PTR(created_dir); + mounted_dir = TAKE_PTR(t); - r = loop_device_flock(d, LOCK_UN); - if (r < 0) - return log_error_errno(r, "Failed to unlock loopback block device: %m"); + if (d) { + r = loop_device_flock(d, LOCK_UN); + if (r < 0) + return log_error_errno(r, "Failed to unlock loopback block device: %m"); + } r = dissected_image_relinquish(m); if (r < 0) @@ -1317,6 +1458,8 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { root = mounted_dir ?: arg_root; + dissected_image_close(m); + switch (arg_action) { case ACTION_COPY_FROM: { @@ -1466,6 +1609,68 @@ static int action_list_or_mtree_or_copy(DissectedImage *m, LoopDevice *d) { return 0; } + case ACTION_MAKE_ARCHIVE: { +#if HAVE_LIBARCHIVE + _cleanup_(unlink_and_freep) char *tar = NULL; + _cleanup_close_ int dfd = -EBADF; + _cleanup_fclose_ FILE *f = NULL; + + dfd = open(root, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (dfd < 0) + return log_error_errno(errno, "Failed to open mount directory: %m"); + + _cleanup_(sym_archive_write_freep) struct archive *a = sym_archive_write_new(); + if (!a) + return log_oom(); + + if (arg_target) + r = sym_archive_write_set_format_filter_by_ext(a, arg_target); + else + r = sym_archive_write_set_format_gnutar(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output format: %s", sym_archive_error_string(a)); + + if (arg_target) { + r = fopen_tmpfile_linkable(arg_target, O_WRONLY|O_CLOEXEC, &tar, &f); + if (r < 0) + return log_error_errno(r, "Failed to create target file '%s': %m", arg_target); + + r = sym_archive_write_open_FILE(a, f); + } else { + if (isatty(STDOUT_FILENO)) + return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Refusing to write archive to TTY."); + + r = sym_archive_write_open_fd(a, STDOUT_FILENO); + } + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to set libarchive output file: %s", sym_archive_error_string(a)); + + r = recurse_dir(dfd, + ".", + STATX_TYPE|STATX_MODE|STATX_UID|STATX_GID|STATX_SIZE|STATX_ATIME|STATX_CTIME, + UINT_MAX, + RECURSE_DIR_SORT|RECURSE_DIR_INODE_FD|RECURSE_DIR_TOPLEVEL, + archive_item, + a); + if (r < 0) + return log_error_errno(r, "Failed to make archive: %m"); + + r = sym_archive_write_close(a); + if (r != ARCHIVE_OK) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to finish writing archive: %s", sym_archive_error_string(a)); + + if (arg_target) { + r = flink_tmpfile(f, tar, arg_target, LINK_TMPFILE_REPLACE); + if (r < 0) + return log_error_errno(r, "Failed to move archive file into place: %m"); + } + + return 0; +#else + assert_not_reached(); +#endif + } + default: assert_not_reached(); } @@ -1538,7 +1743,6 @@ static int action_with(DissectedImage *m, LoopDevice *d) { int r, rcode; assert(m); - assert(d); assert(arg_action == ACTION_WITH); r = tempfn_random_child(NULL, program_invocation_short_name, &temp); @@ -1567,9 +1771,11 @@ static int action_with(DissectedImage *m, LoopDevice *d) { if (r < 0) return log_error_errno(r, "Failed to relinquish DM and loopback block devices: %m"); - r = loop_device_flock(d, LOCK_UN); - if (r < 0) - return log_error_errno(r, "Failed to unlock loopback block device: %m"); + if (d) { + r = loop_device_flock(d, LOCK_UN); + if (r < 0) + return log_error_errno(r, "Failed to unlock loopback block device: %m"); + } rcode = safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, NULL); if (rcode == 0) { @@ -1610,14 +1816,16 @@ static int action_with(DissectedImage *m, LoopDevice *d) { } /* Let's manually detach everything, to make things synchronous */ - r = loop_device_flock(d, LOCK_SH); - if (r < 0) - log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m"); + if (d) { + r = loop_device_flock(d, LOCK_SH); + if (r < 0) + log_warning_errno(r, "Failed to lock loopback block device, ignoring: %m"); + } r = umount_recursive(mounted_dir, 0); if (r < 0) log_warning_errno(r, "Failed to unmount '%s', ignoring: %m", mounted_dir); - else + else if (d) loop_device_unrelinquish(d); /* Let's try to destroy the loopback device */ created_dir = TAKE_PTR(mounted_dir); @@ -1705,6 +1913,8 @@ static int action_detach(const char *path) { struct stat st; int r; + assert(path); + fd = open(path, O_PATH|O_CLOEXEC); if (fd < 0) return log_error_errno(errno, "Failed to open '%s': %m", path); @@ -1738,26 +1948,13 @@ static int action_detach(const char *path) { FOREACH_DEVICE(e, d) { _cleanup_(loop_device_unrefp) LoopDevice *entry_loop = NULL; - const char *name, *devtype; - r = sd_device_get_sysname(d, &name); - if (r < 0) { - log_warning_errno(r, "Failed to get enumerated device's sysname, skipping: %m"); - continue; - } - - r = sd_device_get_devtype(d, &devtype); - if (r < 0) { - log_warning_errno(r, "Failed to get devtype of '%s', skipping: %m", name); - continue; - } - - if (!streq(devtype, "disk")) /* Filter out partition block devices */ + if (!device_is_devtype(d, "disk")) /* Filter out partition block devices */ continue; r = loop_device_open(d, O_RDONLY, LOCK_SH, &entry_loop); if (r < 0) { - log_warning_errno(r, "Failed to open loopback block device '%s', skipping: %m", name); + log_device_warning_errno(d, r, "Failed to open loopback block device, skipping: %m"); continue; } @@ -1815,8 +2012,8 @@ static int action_validate(void) { static int run(int argc, char *argv[]) { _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; - uint32_t loop_flags; - int open_flags, r; + _cleanup_close_ int userns_fd = -EBADF; + int r; log_setup(); @@ -1827,6 +2024,16 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_image) { + r = path_pick_update_warn( + &arg_image, + &pick_filter_image_raw, + PICK_ARCHITECTURE|PICK_TRIES, + /* ret_result= */ NULL); + if (r < 0) + return r; + } + switch (arg_action) { case ACTION_UMOUNT: return action_umount(arg_path); @@ -1838,7 +2045,7 @@ static int run(int argc, char *argv[]) { return action_discover(); default: - /* All other actions need the image dissected */ + /* All other actions need the image dissected (except for ACTION_VALIDATE, see below) */ break; } @@ -1854,50 +2061,92 @@ static int run(int argc, char *argv[]) { * hence if there's external Verity data * available we turn off partition table * support */ + } - if (arg_action == ACTION_VALIDATE) - return action_validate(); + 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; - if (arg_in_memory) - r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); - else - r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); - if (r < 0) - return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); + if (arg_image) { + /* First try locally, if we are allowed to */ + if (!arg_via_service) { + uint32_t loop_flags; + int open_flags; + + 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; + + if (arg_in_memory) + r = loop_device_make_by_path_memory(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); + else + r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); + if (r < 0) { + if (!ERRNO_IS_PRIVILEGE(r) || !IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO)) + return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); - if (arg_loop_ref) { - r = loop_device_set_filename(d, arg_loop_ref); - if (r < 0) - log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); + log_debug_errno(r, "Lacking permissions to set up loopback block device for %s, using service: %m", arg_image); + arg_via_service = true; + } else { + if (arg_loop_ref) { + r = loop_device_set_filename(d, arg_loop_ref); + if (r < 0) + log_warning_errno(r, "Failed to set loop reference string to '%s', ignoring: %m", arg_loop_ref); + } + + r = dissect_loop_device_and_warn( + d, + &arg_verity_settings, + /* mount_options= */ NULL, + arg_image_policy, + arg_flags, + &m); + if (r < 0) + return r; + + if (arg_action == ACTION_ATTACH) + return action_attach(m, d); + + r = dissected_image_load_verity_sig_partition( + m, + d->fd, + &arg_verity_settings); + if (r < 0) + return log_error_errno(r, "Failed to load verity signature partition: %m"); + + if (arg_action != ACTION_DISSECT) { + r = dissected_image_decrypt_interactively( + m, NULL, + &arg_verity_settings, + arg_flags); + if (r < 0) + return r; + } + } } - r = dissect_loop_device_and_warn( - d, - &arg_verity_settings, - /* mount_options= */ NULL, - arg_image_policy, - arg_flags, - &m); - if (r < 0) - return r; + /* Try via service */ + if (arg_via_service) { + if (arg_in_memory) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--in-memory= not supported when operating via systemd-mountfsd."); - if (arg_action == ACTION_ATTACH) - return action_attach(m, d); + if (arg_loop_ref) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--loop-ref= not supported when operating via systemd-mountfsd."); - r = dissected_image_load_verity_sig_partition( - m, - d->fd, - &arg_verity_settings); - if (r < 0) - return log_error_errno(r, "Failed to load verity signature partition: %m"); + if (verity_settings_set(&arg_verity_settings)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Externally configured verity settings not supported when operating via systemd-mountfsd."); + + /* Don't run things in private userns, if the mount shall be attached to the host */ + if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) { + userns_fd = nsresource_allocate_userns(/* name= */ NULL, UINT64_C(0x10000)); /* allocate 64K users by default */ + if (userns_fd < 0) + return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); + } - if (arg_action != ACTION_DISSECT) { - r = dissected_image_decrypt_interactively( - m, NULL, - &arg_verity_settings, - arg_flags); + r = mountfsd_mount_image( + arg_image, + userns_fd, + arg_image_policy, + arg_flags, + &m); if (r < 0) return r; } @@ -1906,7 +2155,7 @@ static int run(int argc, char *argv[]) { switch (arg_action) { case ACTION_DISSECT: - return action_dissect(m, d); + return action_dissect(m, d, userns_fd); case ACTION_MOUNT: return action_mount(m, d); @@ -1915,7 +2164,8 @@ static int run(int argc, char *argv[]) { case ACTION_MTREE: case ACTION_COPY_FROM: case ACTION_COPY_TO: - return action_list_or_mtree_or_copy(m, d); + case ACTION_MAKE_ARCHIVE: + return action_list_or_mtree_or_copy_or_make_archive(m, d, userns_fd); case ACTION_WITH: return action_with(m, d); diff --git a/src/environment-d-generator/environment-d-generator.c b/src/environment-d-generator/environment-d-generator.c index 90e31c9..fa751cb 100644 --- a/src/environment-d-generator/environment-d-generator.c +++ b/src/environment-d-generator/environment-d-generator.c @@ -17,7 +17,7 @@ static int environment_dirs(char ***ret) { _cleanup_free_ char *c = NULL; int r; - dirs = strv_new(CONF_PATHS_USR("environment.d"), NULL); + dirs = strv_new(CONF_PATHS("environment.d")); if (!dirs) return -ENOMEM; @@ -26,7 +26,7 @@ static int environment_dirs(char ***ret) { if (r < 0) return r; - r = strv_extend_front(&dirs, c); + r = strv_consume_prepend(&dirs, TAKE_PTR(c)); if (r < 0) return r; @@ -84,8 +84,7 @@ static int load_and_print(void) { static int run(int argc, char *argv[]) { int r; - log_parse_environment(); - log_open(); + log_setup(); if (argc > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index d402927..6afabef 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -89,6 +89,8 @@ 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_root_shell, freep); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); static bool press_any_key(void) { @@ -166,7 +168,7 @@ static int show_menu(char **x, unsigned n_columns, unsigned width, unsigned perc for (i = 0; i < per_column; i++) { - for (j = 0; j < n_columns; j ++) { + for (j = 0; j < n_columns; j++) { _cleanup_free_ char *e = NULL; if (j * per_column + i >= n) @@ -795,7 +797,11 @@ static int prompt_root_password(int rfd) { _cleanup_strv_free_erase_ char **a = NULL, **b = NULL; _cleanup_free_ char *error = NULL; - r = ask_password_tty(-1, msg1, NULL, 0, 0, NULL, &a); + AskPasswordRequest req = { + .message = msg1, + }; + + r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ 0, /* flag_file= */ NULL, &a); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); if (strv_length(a) != 1) @@ -815,7 +821,9 @@ static int prompt_root_password(int rfd) { else if (r == 0) log_warning("Password is weak, accepting anyway: %s", error); - r = ask_password_tty(-1, msg2, NULL, 0, 0, NULL, &b); + req.message = msg2; + + r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ 0, /* flag_file= */ NULL, &b); if (r < 0) return log_error_errno(r, "Failed to query root password: %m"); if (strv_length(b) != 1) @@ -1058,10 +1066,8 @@ static int process_root_account(int rfd) { 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); + return log_error_errno(r, "Verification of /etc/%s being regular file failed: %m", s); r = should_configure(pfd, s); if (r < 0) @@ -1091,12 +1097,11 @@ static int process_root_account(int rfd) { return log_error_errno(k, "Failed to check if directory file descriptor is root: %m"); if (arg_copy_root_shell && k == 0) { - struct passwd *p; + _cleanup_free_ struct passwd *p = NULL; - errno = 0; - p = getpwnam("root"); - if (!p) - return log_error_errno(errno_or_else(EIO), "Failed to find passwd entry for root: %m"); + r = getpwnam_malloc("root", &p); + if (r < 0) + return log_error_errno(r, "Failed to find passwd entry for root: %m"); r = free_and_strdup(&arg_root_shell, p->pw_shell); if (r < 0) @@ -1638,7 +1643,7 @@ static int reload_vconsole(sd_bus **bus) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, false, NULL); + r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return log_error_errno(r, "Failed to wait for systemd-vconsole-setup.service/restart: %m"); return 0; @@ -1671,8 +1676,8 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); if (r > 0 && !enabled) { - log_debug("Found systemd.firstboot=no kernel command line argument, terminating."); - return 0; /* disabled */ + log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); + arg_prompt_locale = arg_prompt_keymap = arg_prompt_timezone = arg_prompt_hostname = arg_prompt_root_password = arg_prompt_root_shell = false; } } @@ -1687,7 +1692,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_GROWFS, + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, &rfd, &loop_device); diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index 000ed69..4ec8989 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -54,7 +54,7 @@ static void start_target(const char *target, const char *mode) { log_info("Requesting %s/start/%s", target, mode); /* Start this unit only if we can replace basic.target with it */ - r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", "basic.target", target, mode); + r = bus_call_method(bus, bus_systemd_mgr, "StartUnitReplace", &error, NULL, "sss", SPECIAL_BASIC_TARGET, target, mode); /* Don't print a warning if we aren't called during startup */ if (r < 0 && !sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_JOB)) @@ -177,7 +177,7 @@ static int process_progress(int fd, FILE* console) { else if (feof(f)) r = 0; else - r = log_warning_errno(SYNTHETIC_ERRNO(errno), "Failed to parse progress pipe data"); + r = log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse progress pipe data."); break; } diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 016f3ba..b4df9d2 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -46,6 +46,7 @@ typedef enum MountPointFlags { MOUNT_GROWFS = 1 << 4, MOUNT_RW_ONLY = 1 << 5, MOUNT_PCRFS = 1 << 6, + MOUNT_QUOTA = 1 << 7, } MountPointFlags; typedef struct Mount { @@ -159,7 +160,7 @@ static int mount_array_add(bool for_initrd, const char *str) { assert(str); r = extract_many_words(&str, ":", EXTRACT_CUNESCAPE | EXTRACT_DONT_COALESCE_SEPARATORS, - &what, &where, &fstype, &options, NULL); + &what, &where, &fstype, &options); if (r < 0) return r; if (r < 2) @@ -177,7 +178,7 @@ static int mount_array_add_swap(bool for_initrd, const char *str) { assert(str); r = extract_many_words(&str, ":", EXTRACT_CUNESCAPE | EXTRACT_DONT_COALESCE_SEPARATORS, - &what, &options, NULL); + &what, &options); if (r < 0) return r; if (r < 1) @@ -191,6 +192,8 @@ static int mount_array_add_swap(bool for_initrd, const char *str) { static int write_options(FILE *f, const char *options) { _cleanup_free_ char *o = NULL; + assert(f); + if (isempty(options)) return 0; @@ -208,6 +211,9 @@ static int write_options(FILE *f, const char *options) { static int write_what(FILE *f, const char *what) { _cleanup_free_ char *w = NULL; + assert(f); + assert(what); + w = specifier_escape(what); if (!w) return log_oom(); @@ -324,25 +330,30 @@ static int write_timeout( const char *where, const char *opts, const char *filter, - const char *variable) { + const char *unit_setting) { _cleanup_free_ char *timeout = NULL; usec_t u; int r; + assert(f); + assert(where); + assert(filter); + assert(unit_setting); + r = fstab_filter_options(opts, filter, NULL, &timeout, NULL, NULL); if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); + return log_error_errno(r, "Failed to parse options for '%s': %m", where); if (r == 0) return 0; r = parse_sec_fix_0(timeout, &u); if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + log_warning_errno(r, "Failed to parse timeout '%s' for '%s', ignoring: %m", timeout, where); return 0; } - fprintf(f, "%s=%s\n", variable, FORMAT_TIMESPAN(u, 0)); + fprintf(f, "%s=%s\n", unit_setting, FORMAT_TIMESPAN(u, 0)); return 0; } @@ -359,109 +370,118 @@ static int write_mount_timeout(FILE *f, const char *where, const char *opts) { static int write_dependency( FILE *f, + const char *where, const char *opts, const char *filter, - const char *format) { + const char* const *unit_settings) { - _cleanup_strv_free_ char **names = NULL, **units = NULL; - _cleanup_free_ char *res = NULL; + _cleanup_strv_free_ char **unit_names = NULL; + _cleanup_free_ char *units = NULL; int r; assert(f); - assert(opts); + assert(filter); + assert(unit_settings); - r = fstab_filter_options(opts, filter, NULL, NULL, &names, NULL); + r = fstab_filter_options(opts, filter, NULL, NULL, &unit_names, NULL); if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); + return log_error_errno(r, "Failed to parse options for '%s': %m", where); if (r == 0) return 0; - STRV_FOREACH(s, names) { - char *x; + STRV_FOREACH(s, unit_names) { + _cleanup_free_ char *mangled = NULL; - r = unit_name_mangle_with_suffix(*s, "as dependency", 0, ".mount", &x); + r = unit_name_mangle_with_suffix(*s, "as dependency", 0, ".mount", &mangled); if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); + return log_error_errno(r, "Failed to generate dependency unit name for '%s': %m", where); - r = strv_consume(&units, x); - if (r < 0) + if (!strextend_with_separator(&units, " ", mangled)) return log_oom(); } - if (units) { - res = strv_join(units, " "); - if (!res) - return log_oom(); - - DISABLE_WARNING_FORMAT_NONLITERAL; - fprintf(f, format, res); - REENABLE_WARNING; - } + STRV_FOREACH(setting, unit_settings) + fprintf(f, "%s=%s\n", *setting, units); return 0; } -static int write_after(FILE *f, const char *opts) { - return write_dependency(f, opts, - "x-systemd.after\0", "After=%1$s\n"); +static int write_after(FILE *f, const char *where, const char *opts) { + return write_dependency(f, where, opts, + "x-systemd.after\0", STRV_MAKE_CONST("After")); } -static int write_requires_after(FILE *f, const char *opts) { - return write_dependency(f, opts, - "x-systemd.requires\0", "After=%1$s\nRequires=%1$s\n"); +static int write_requires_after(FILE *f, const char *where, const char *opts) { + return write_dependency(f, where, opts, + "x-systemd.requires\0", STRV_MAKE_CONST("Requires", "After")); } -static int write_before(FILE *f, const char *opts) { - return write_dependency(f, opts, - "x-systemd.before\0", "Before=%1$s\n"); +static int write_before(FILE *f, const char *where, const char *opts) { + return write_dependency(f, where, opts, + "x-systemd.before\0", STRV_MAKE_CONST("Before")); } -static int write_requires_mounts_for(FILE *f, const char *opts) { +static int write_mounts_for( + FILE *f, + const char *where, + const char *opts, + const char *filter, + const char *unit_setting) { + _cleanup_strv_free_ char **paths = NULL, **paths_escaped = NULL; - _cleanup_free_ char *res = NULL; int r; assert(f); - assert(opts); + assert(where); + assert(filter); + assert(unit_setting); - r = fstab_filter_options(opts, "x-systemd.requires-mounts-for\0", NULL, NULL, &paths, NULL); + r = fstab_filter_options(opts, filter, NULL, NULL, &paths, NULL); if (r < 0) - return log_warning_errno(r, "Failed to parse options: %m"); + return log_error_errno(r, "Failed to parse options for '%s': %m", where); if (r == 0) return 0; r = specifier_escape_strv(paths, &paths_escaped); if (r < 0) - return log_error_errno(r, "Failed to escape paths: %m"); - - res = strv_join(paths_escaped, " "); - if (!res) - return log_oom(); + return log_error_errno(r, "Failed to escape paths for '%s': %m", where); - fprintf(f, "RequiresMountsFor=%s\n", res); + fprintf(f, "%s=", unit_setting); + fputstrv(f, paths_escaped, NULL, NULL); + fputc('\n', f); return 0; } -static int write_extra_dependencies(FILE *f, const char *opts) { +static int write_extra_dependencies(FILE *f, const char *where, const char *opts) { int r; assert(f); - if (opts) { - r = write_after(f, opts); - if (r < 0) - return r; - r = write_requires_after(f, opts); - if (r < 0) - return r; - r = write_before(f, opts); - if (r < 0) - return r; - r = write_requires_mounts_for(f, opts); - if (r < 0) - return r; - } + if (isempty(opts)) + return 0; + + r = write_after(f, where, opts); + if (r < 0) + return r; + + r = write_requires_after(f, where, opts); + if (r < 0) + return r; + + r = write_before(f, where, opts); + if (r < 0) + return r; + + r = write_mounts_for(f, where, opts, + "x-systemd.requires-mounts-for\0", "RequiresMountsFor"); + if (r < 0) + return r; + + r = write_mounts_for(f, where, opts, + "x-systemd.wants-mounts-for\0", "WantsMountsFor"); + if (r < 0) + return r; return 0; } @@ -476,19 +496,10 @@ static int mandatory_mount_drop_unapplicable_options( assert(flags); assert(where); - assert(options); assert(ret_options); - if (!(*flags & (MOUNT_NOAUTO|MOUNT_NOFAIL|MOUNT_AUTOMOUNT))) { - _cleanup_free_ char *opts = NULL; - - opts = strdup(options); - if (!opts) - return -ENOMEM; - - *ret_options = TAKE_PTR(opts); - return 0; - } + if (!(*flags & (MOUNT_NOAUTO|MOUNT_NOFAIL|MOUNT_AUTOMOUNT))) + return strdup_to(ret_options, options); log_debug("Mount '%s' is mandatory, ignoring 'noauto', 'nofail', and 'x-systemd.automount' options.", where); @@ -522,7 +533,6 @@ static int add_mount( assert(what); assert(where); - assert(opts); assert(target_unit); assert(source); @@ -551,16 +561,16 @@ static int add_mount( if (r < 0) return r; - if (path_equal(where, "/")) { + if (PATH_IN_SET(where, "/", "/usr")) { r = mandatory_mount_drop_unapplicable_options(&flags, where, opts, &opts_root_filtered); if (r < 0) return r; opts = opts_root_filtered; if (!strv_isempty(wanted_by)) - log_debug("Ignoring 'x-systemd.wanted-by=' option for root device."); + log_debug("Ignoring 'x-systemd.wanted-by=' option for root/usr device."); if (!strv_isempty(required_by)) - log_debug("Ignoring 'x-systemd.required-by=' option for root device."); + log_debug("Ignoring 'x-systemd.required-by=' option for root/usr device."); required_by = strv_free(required_by); wanted_by = strv_free(wanted_by); @@ -594,13 +604,25 @@ static int add_mount( SET_FLAG(flags, MOUNT_NOFAIL, true); } - r = write_extra_dependencies(f, opts); + if (!strv_isempty(wanted_by) || !strv_isempty(required_by)) { + /* If x-systemd.{wanted,required}-by= is specified, target_unit is not used */ + target_unit = NULL; + + /* Don't set default ordering dependencies on local-fs.target or remote-fs.target, but we + * still need to conflict with umount.target. */ + fputs("DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "Before=umount.target\n", + f); + } + + r = write_extra_dependencies(f, where, opts); if (r < 0) return r; /* Order the mount unit we generate relative to target_unit, so that DefaultDependencies= on the * target unit won't affect us. */ - if (!FLAGS_SET(flags, MOUNT_NOFAIL)) + if (target_unit && !FLAGS_SET(flags, MOUNT_NOFAIL)) fprintf(f, "Before=%s\n", target_unit); if (passno != 0) { @@ -691,26 +713,19 @@ static int add_mount( } } - if (!FLAGS_SET(flags, MOUNT_AUTOMOUNT)) { - if (!FLAGS_SET(flags, MOUNT_NOAUTO) && strv_isempty(wanted_by) && strv_isempty(required_by)) { - r = generator_add_symlink(dest, target_unit, - (flags & MOUNT_NOFAIL) ? "wants" : "requires", name); - if (r < 0) + if (flags & MOUNT_QUOTA) { + r = generator_hook_up_quotacheck(dest, what, where, target_unit, fstype); + if (r < 0) { + if (r != -EOPNOTSUPP) return r; } else { - STRV_FOREACH(s, wanted_by) { - r = generator_add_symlink(dest, *s, "wants", name); - if (r < 0) - return r; - } - - STRV_FOREACH(s, required_by) { - r = generator_add_symlink(dest, *s, "requires", name); - if (r < 0) - return r; - } + r = generator_hook_up_quotaon(dest, where, target_unit); + if (r < 0) + return r; } - } else { + } + + if (FLAGS_SET(flags, MOUNT_AUTOMOUNT)) { r = unit_name_from_path(where, ".automount", &automount_name); if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); @@ -740,11 +755,37 @@ static int add_mount( r = fflush_and_check(f); if (r < 0) return log_error_errno(r, "Failed to write unit file %s: %m", automount_name); + } - r = generator_add_symlink(dest, target_unit, - (flags & MOUNT_NOFAIL) ? "wants" : "requires", automount_name); - if (r < 0) - return r; + if (target_unit) { + assert(strv_isempty(wanted_by)); + assert(strv_isempty(required_by)); + + /* noauto has no effect if x-systemd.automount is used */ + if (!FLAGS_SET(flags, MOUNT_NOAUTO) || automount_name) { + r = generator_add_symlink(dest, target_unit, + FLAGS_SET(flags, MOUNT_NOFAIL) ? "wants" : "requires", + automount_name ?: name); + if (r < 0) + return r; + } + } else { + const char *unit_name = automount_name ?: name; + + STRV_FOREACH(s, wanted_by) { + r = generator_add_symlink(dest, *s, "wants", unit_name); + if (r < 0) + return r; + } + + STRV_FOREACH(s, required_by) { + r = generator_add_symlink(dest, *s, "requires", unit_name); + if (r < 0) + return r; + } + + if ((flags & (MOUNT_NOAUTO|MOUNT_NOFAIL)) != 0) + log_warning("x-systemd.wanted-by= and/or x-systemd.required-by= specified, 'noauto' and 'nofail' have no effect."); } return true; @@ -791,7 +832,7 @@ static bool sysfs_check(void) { int r; if (cached < 0) { - r = getenv_bool_secure("SYSTEMD_SYSFS_CHECK"); + r = secure_getenv_bool("SYSTEMD_SYSFS_CHECK"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_SYSFS_CHECK, ignoring: %m"); cached = r != 0; @@ -816,12 +857,17 @@ static int add_sysusr_sysroot_usr_bind_mount(const char *source) { static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap) { MountPointFlags flags = 0; + if (isempty(options)) + return 0; + if (fstab_test_option(options, "x-systemd.makefs\0")) flags |= MOUNT_MAKEFS; if (fstab_test_option(options, "x-systemd.growfs\0")) flags |= MOUNT_GROWFS; if (fstab_test_option(options, "x-systemd.pcrfs\0")) flags |= MOUNT_PCRFS; + if (fstab_test_option(options, "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0" "prjquota\0")) + flags |= MOUNT_QUOTA; if (fstab_test_yes_no_option(options, "noauto\0" "auto\0")) flags |= MOUNT_NOAUTO; if (fstab_test_yes_no_option(options, "nofail\0" "fail\0")) @@ -891,7 +937,6 @@ static int parse_fstab_one( assert(what_original); assert(fstype); - assert(options); if (prefix_sysroot && !mount_in_initrd(where_original, options, accept_root)) return 0; @@ -1175,7 +1220,7 @@ static int add_sysroot_mount(void) { fstype, opts, is_device_path(what) ? 1 : 0, /* passno */ - flags, /* makefs off, pcrfs off, noauto off, nofail off, automount off */ + flags, /* makefs off, pcrfs off, quota off, noauto off, nofail off, automount off */ SPECIAL_INITRD_ROOT_FS_TARGET); } @@ -1563,7 +1608,7 @@ static int determine_usr(void) { * with /sysroot/etc/fstab available, and then we can write additional units based * on that file. */ static int run_generator(void) { - int r = 0; + int r; r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) diff --git a/src/fundamental/efivars-fundamental.h b/src/fundamental/efivars-fundamental.h index 2d25d22..01b18ec 100644 --- a/src/fundamental/efivars-fundamental.h +++ b/src/fundamental/efivars-fundamental.h @@ -33,6 +33,7 @@ #define EFI_STUB_FEATURE_CMDLINE_ADDONS (UINT64_C(1) << 5) #define EFI_STUB_FEATURE_CMDLINE_SMBIOS (UINT64_C(1) << 6) #define EFI_STUB_FEATURE_DEVICETREE_ADDONS (UINT64_C(1) << 7) +#define EFI_STUB_FEATURE_PICK_UP_CONFEXTS (UINT64_C(1) << 8) typedef enum SecureBootMode { SECURE_BOOT_UNSUPPORTED, diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h index 797330d..5ccbda5 100644 --- a/src/fundamental/macro-fundamental.h +++ b/src/fundamental/macro-fundamental.h @@ -158,6 +158,10 @@ __atomic_exchange_n(&(o), true, __ATOMIC_SEQ_CST); \ }) +#define U64_KB UINT64_C(1024) +#define U64_MB (UINT64_C(1024) * U64_KB) +#define U64_GB (UINT64_C(1024) * U64_MB) + #undef MAX #define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) #define __MAX(aq, a, bq, b) \ @@ -245,6 +249,30 @@ CONST_ISPOWEROF2(_x); \ })) +#define ADD_SAFE(ret, a, b) (!__builtin_add_overflow(a, b, ret)) +#define INC_SAFE(a, b) __INC_SAFE(UNIQ, a, b) +#define __INC_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + ADD_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + +#define SUB_SAFE(ret, a, b) (!__builtin_sub_overflow(a, b, ret)) +#define DEC_SAFE(a, b) __DEC_SAFE(UNIQ, a, b) +#define __DEC_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + SUB_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + +#define MUL_SAFE(ret, a, b) (!__builtin_mul_overflow(a, b, ret)) +#define MUL_ASSIGN_SAFE(a, b) __MUL_ASSIGN_SAFE(UNIQ, a, b) +#define __MUL_ASSIGN_SAFE(q, a, b) \ + ({ \ + const typeof(a) UNIQ_T(A, q) = (a); \ + MUL_SAFE(UNIQ_T(A, q), *UNIQ_T(A, q), b); \ + }) + #define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) #define __LESS_BY(aq, a, bq, b) \ ({ \ @@ -294,7 +322,7 @@ const typeof(y) UNIQ_T(A, q) = (y); \ const typeof(x) UNIQ_T(B, q) = DIV_ROUND_UP((x), UNIQ_T(A, q)); \ typeof(x) UNIQ_T(C, q); \ - __builtin_mul_overflow(UNIQ_T(B, q), UNIQ_T(A, q), &UNIQ_T(C, q)) ? (typeof(x)) -1 : UNIQ_T(C, q); \ + MUL_SAFE(&UNIQ_T(C, q), UNIQ_T(B, q), UNIQ_T(A, q)) ? UNIQ_T(C, q) : (typeof(x)) -1; \ }) #define ROUND_UP(x, y) __ROUND_UP(UNIQ, (x), (y)) diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build index b7ca6cf..f5f57ac 100644 --- a/src/fundamental/meson.build +++ b/src/fundamental/meson.build @@ -5,7 +5,7 @@ fundamental_include = include_directories('.') fundamental_sources = files( 'bootspec-fundamental.c', 'efivars-fundamental.c', - 'sha256.c', + 'sha256-fundamental.c', 'string-util-fundamental.c', 'uki.c', ) diff --git a/src/fundamental/sha256-fundamental.c b/src/fundamental/sha256-fundamental.c new file mode 100644 index 0000000..f8524ba --- /dev/null +++ b/src/fundamental/sha256-fundamental.c @@ -0,0 +1,278 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* Stolen from glibc and converted to our style. In glibc it comes with the following copyright blurb: */ + +/* Functions to compute SHA256 message digest of files or memory blocks. + according to the definition of SHA256 in FIPS 180-2. + Copyright (C) 2007-2022 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library 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. + + The GNU C Library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#if SD_BOOT +# include "efi-string.h" +#else +# include +#endif + +#include "macro-fundamental.h" +#include "sha256-fundamental.h" +#include "unaligned-fundamental.h" + +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +# define SWAP(n) \ + __builtin_bswap32(n) +# define SWAP64(n) \ + __builtin_bswap64(n) +#else +# define SWAP(n) (n) +# define SWAP64(n) (n) +#endif + +/* This array contains the bytes used to pad the buffer to the next + 64-byte boundary. (FIPS 180-2:5.1.1) */ +static const uint8_t fillbuf[64] = { + 0x80, 0 /* , 0, 0, ... */ +}; + +/* Constants for SHA256 from FIPS 180-2:4.2.2. */ +static const uint32_t K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, + 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, + 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, + 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, + 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, + 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 +}; + +static void sha256_process_block(const void *, size_t, struct sha256_ctx *); + +/* Initialize structure containing state of computation. + (FIPS 180-2:5.3.2) */ +void sha256_init_ctx(struct sha256_ctx *ctx) { + assert(ctx); + + ctx->H[0] = 0x6a09e667; + ctx->H[1] = 0xbb67ae85; + ctx->H[2] = 0x3c6ef372; + ctx->H[3] = 0xa54ff53a; + ctx->H[4] = 0x510e527f; + ctx->H[5] = 0x9b05688c; + ctx->H[6] = 0x1f83d9ab; + ctx->H[7] = 0x5be0cd19; + + ctx->total64 = 0; + ctx->buflen = 0; +} + +/* Process the remaining bytes in the internal buffer and the usual + prolog according to the standard and write the result to RESBUF. */ +uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]) { + /* Take yet unprocessed bytes into account. */ + uint32_t bytes = ctx->buflen; + size_t pad; + + assert(ctx); + assert(resbuf); + + /* Now count remaining bytes. */ + ctx->total64 += bytes; + + pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; + memcpy(&ctx->buffer[bytes], fillbuf, pad); + + /* Put the 64-bit file length in *bits* at the end of the buffer. */ + ctx->buffer32[(bytes + pad + 4) / 4] = SWAP(ctx->total[TOTAL64_low] << 3); + ctx->buffer32[(bytes + pad) / 4] = SWAP((ctx->total[TOTAL64_high] << 3) + | (ctx->total[TOTAL64_low] >> 29)); + + /* Process last bytes. */ + sha256_process_block(ctx->buffer, bytes + pad + 8, ctx); + + /* Put result from CTX in first 32 bytes following RESBUF. */ + for (size_t i = 0; i < 8; ++i) + unaligned_write_ne32(resbuf + i * sizeof(uint32_t), SWAP(ctx->H[i])); + return resbuf; +} + +void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx) { + assert(buffer); + assert(ctx); + + /* When we already have some bits in our internal buffer concatenate + both inputs first. */ + + if (ctx->buflen != 0) { + size_t left_over = ctx->buflen; + size_t add = 128 - left_over > len ? len : 128 - left_over; + + memcpy(&ctx->buffer[left_over], buffer, add); + ctx->buflen += add; + + if (ctx->buflen > 64) { + sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx); + + ctx->buflen &= 63; + /* The regions in the following copy operation cannot overlap. */ + memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63], + ctx->buflen); + } + + buffer = (const char *) buffer + add; + len -= add; + } + + /* Process available complete blocks. */ + if (len >= 64) { + if (IS_ALIGNED32(buffer)) { + sha256_process_block(buffer, len & ~63, ctx); + buffer = (const char *) buffer + (len & ~63); + len &= 63; + } else + while (len > 64) { + memcpy(ctx->buffer, buffer, 64); + sha256_process_block(ctx->buffer, 64, ctx); + buffer = (const char *) buffer + 64; + len -= 64; + } + } + + /* Move remaining bytes into internal buffer. */ + if (len > 0) { + size_t left_over = ctx->buflen; + + memcpy(&ctx->buffer[left_over], buffer, len); + left_over += len; + if (left_over >= 64) { + sha256_process_block(ctx->buffer, 64, ctx); + left_over -= 64; + memcpy(ctx->buffer, &ctx->buffer[64], left_over); + } + ctx->buflen = left_over; + } +} + +/* Process LEN bytes of BUFFER, accumulating context into CTX. + It is assumed that LEN % 64 == 0. */ +static void sha256_process_block(const void *buffer, size_t len, struct sha256_ctx *ctx) { + const uint32_t *words = ASSERT_PTR(buffer); + size_t nwords = len / sizeof(uint32_t); + + assert(ctx); + + uint32_t a = ctx->H[0]; + uint32_t b = ctx->H[1]; + uint32_t c = ctx->H[2]; + uint32_t d = ctx->H[3]; + uint32_t e = ctx->H[4]; + uint32_t f = ctx->H[5]; + uint32_t g = ctx->H[6]; + uint32_t h = ctx->H[7]; + + /* First increment the byte count. FIPS 180-2 specifies the possible + length of the file up to 2^64 bits. Here we only compute the + number of bytes. */ + ctx->total64 += len; + + /* Process all bytes in the buffer with 64 bytes in each round of + the loop. */ + while (nwords > 0) { + uint32_t W[64]; + uint32_t a_save = a; + uint32_t b_save = b; + uint32_t c_save = c; + uint32_t d_save = d; + uint32_t e_save = e; + uint32_t f_save = f; + uint32_t g_save = g; + uint32_t h_save = h; + + /* Operators defined in FIPS 180-2:4.1.2. */ +#define Ch(x, y, z) ((x & y) ^ (~x & z)) +#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) +#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22)) +#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25)) +#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3)) +#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10)) + + /* It is unfortunate that C does not provide an operator for + cyclic rotation. Hope the C compiler is smart enough. */ +#define CYCLIC(w, s) ((w >> s) | (w << (32 - s))) + + /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */ + for (size_t t = 0; t < 16; ++t) { + W[t] = SWAP (*words); + ++words; + } + for (size_t t = 16; t < 64; ++t) + W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16]; + + /* The actual computation according to FIPS 180-2:6.2.2 step 3. */ + for (size_t t = 0; t < 64; ++t) { + uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t]; + uint32_t T2 = S0 (a) + Maj (a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + /* Add the starting values of the context according to FIPS 180-2:6.2.2 + step 4. */ + a += a_save; + b += b_save; + c += c_save; + d += d_save; + e += e_save; + f += f_save; + g += g_save; + h += h_save; + + /* Prepare for the next round. */ + nwords -= 16; + } + + /* Put checksum in context given as argument. */ + ctx->H[0] = a; + ctx->H[1] = b; + ctx->H[2] = c; + ctx->H[3] = d; + ctx->H[4] = e; + ctx->H[5] = f; + ctx->H[6] = g; + ctx->H[7] = h; +} + +uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]) { + struct sha256_ctx ctx; + sha256_init_ctx(&ctx); + sha256_process_bytes(buffer, sz, &ctx); + return sha256_finish_ctx(&ctx, result); +} diff --git a/src/fundamental/sha256-fundamental.h b/src/fundamental/sha256-fundamental.h new file mode 100644 index 0000000..dbb08e3 --- /dev/null +++ b/src/fundamental/sha256-fundamental.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#define SHA256_DIGEST_SIZE 32 + +struct sha256_ctx { + uint32_t H[8]; + + union { + uint64_t total64; +#define TOTAL64_low (1 - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) +#define TOTAL64_high (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + uint32_t total[2]; + }; + + uint32_t buflen; + + union { + uint8_t buffer[128]; /* NB: always correctly aligned for UINT32. */ + uint32_t buffer32[32]; + uint64_t buffer64[16]; + }; +}; + +void sha256_init_ctx(struct sha256_ctx *ctx); +uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]); +void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx); + +static inline void sha256_process_bytes_and_size(const void *buffer, size_t len, struct sha256_ctx *ctx) { + sha256_process_bytes(&len, sizeof(len), ctx); + sha256_process_bytes(buffer, len, ctx); +} + +uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]); + +#define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {}) diff --git a/src/fundamental/sha256.c b/src/fundamental/sha256.c deleted file mode 100644 index 4389e9e..0000000 --- a/src/fundamental/sha256.c +++ /dev/null @@ -1,285 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -/* Stolen from glibc and converted to our style. In glibc it comes with the following copyright blurb: */ - -/* Functions to compute SHA256 message digest of files or memory blocks. - according to the definition of SHA256 in FIPS 180-2. - Copyright (C) 2007-2022 Free Software Foundation, Inc. - This file is part of the GNU C Library. - - The GNU C Library 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. - - The GNU C Library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with the GNU C Library; if not, see - . */ - -#include -#if SD_BOOT -# include "efi-string.h" -#else -# include -#endif - -#include "macro-fundamental.h" -#include "sha256.h" -#include "unaligned-fundamental.h" - -#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define SWAP(n) \ - (((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24)) -# define SWAP64(n) \ - (((n) << 56) \ - | (((n) & 0xff00) << 40) \ - | (((n) & 0xff0000) << 24) \ - | (((n) & 0xff000000) << 8) \ - | (((n) >> 8) & 0xff000000) \ - | (((n) >> 24) & 0xff0000) \ - | (((n) >> 40) & 0xff00) \ - | ((n) >> 56)) -#else -# define SWAP(n) (n) -# define SWAP64(n) (n) -#endif - -/* This array contains the bytes used to pad the buffer to the next - 64-byte boundary. (FIPS 180-2:5.1.1) */ -static const uint8_t fillbuf[64] = { - 0x80, 0 /* , 0, 0, ... */ -}; - -/* Constants for SHA256 from FIPS 180-2:4.2.2. */ -static const uint32_t K[64] = { - 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, - 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, - 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, - 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, - 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, - 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, - 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, - 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, - 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, - 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, - 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, - 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, - 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, - 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, - 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, - 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2 -}; - -static void sha256_process_block(const void *, size_t, struct sha256_ctx *); - -/* Initialize structure containing state of computation. - (FIPS 180-2:5.3.2) */ -void sha256_init_ctx(struct sha256_ctx *ctx) { - assert(ctx); - - ctx->H[0] = 0x6a09e667; - ctx->H[1] = 0xbb67ae85; - ctx->H[2] = 0x3c6ef372; - ctx->H[3] = 0xa54ff53a; - ctx->H[4] = 0x510e527f; - ctx->H[5] = 0x9b05688c; - ctx->H[6] = 0x1f83d9ab; - ctx->H[7] = 0x5be0cd19; - - ctx->total64 = 0; - ctx->buflen = 0; -} - -/* Process the remaining bytes in the internal buffer and the usual - prolog according to the standard and write the result to RESBUF. */ -uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]) { - /* Take yet unprocessed bytes into account. */ - uint32_t bytes = ctx->buflen; - size_t pad; - - assert(ctx); - assert(resbuf); - - /* Now count remaining bytes. */ - ctx->total64 += bytes; - - pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes; - memcpy(&ctx->buffer[bytes], fillbuf, pad); - - /* Put the 64-bit file length in *bits* at the end of the buffer. */ - ctx->buffer32[(bytes + pad + 4) / 4] = SWAP(ctx->total[TOTAL64_low] << 3); - ctx->buffer32[(bytes + pad) / 4] = SWAP((ctx->total[TOTAL64_high] << 3) - | (ctx->total[TOTAL64_low] >> 29)); - - /* Process last bytes. */ - sha256_process_block(ctx->buffer, bytes + pad + 8, ctx); - - /* Put result from CTX in first 32 bytes following RESBUF. */ - for (size_t i = 0; i < 8; ++i) - unaligned_write_ne32(resbuf + i * sizeof(uint32_t), SWAP(ctx->H[i])); - return resbuf; -} - -void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx) { - assert(buffer); - assert(ctx); - - /* When we already have some bits in our internal buffer concatenate - both inputs first. */ - - if (ctx->buflen != 0) { - size_t left_over = ctx->buflen; - size_t add = 128 - left_over > len ? len : 128 - left_over; - - memcpy(&ctx->buffer[left_over], buffer, add); - ctx->buflen += add; - - if (ctx->buflen > 64) { - sha256_process_block(ctx->buffer, ctx->buflen & ~63, ctx); - - ctx->buflen &= 63; - /* The regions in the following copy operation cannot overlap. */ - memcpy(ctx->buffer, &ctx->buffer[(left_over + add) & ~63], - ctx->buflen); - } - - buffer = (const char *) buffer + add; - len -= add; - } - - /* Process available complete blocks. */ - if (len >= 64) { - if (IS_ALIGNED32(buffer)) { - sha256_process_block(buffer, len & ~63, ctx); - buffer = (const char *) buffer + (len & ~63); - len &= 63; - } else - while (len > 64) { - memcpy(ctx->buffer, buffer, 64); - sha256_process_block(ctx->buffer, 64, ctx); - buffer = (const char *) buffer + 64; - len -= 64; - } - } - - /* Move remaining bytes into internal buffer. */ - if (len > 0) { - size_t left_over = ctx->buflen; - - memcpy(&ctx->buffer[left_over], buffer, len); - left_over += len; - if (left_over >= 64) { - sha256_process_block(ctx->buffer, 64, ctx); - left_over -= 64; - memcpy(ctx->buffer, &ctx->buffer[64], left_over); - } - ctx->buflen = left_over; - } -} - -/* Process LEN bytes of BUFFER, accumulating context into CTX. - It is assumed that LEN % 64 == 0. */ -static void sha256_process_block(const void *buffer, size_t len, struct sha256_ctx *ctx) { - const uint32_t *words = ASSERT_PTR(buffer); - size_t nwords = len / sizeof(uint32_t); - - assert(ctx); - - uint32_t a = ctx->H[0]; - uint32_t b = ctx->H[1]; - uint32_t c = ctx->H[2]; - uint32_t d = ctx->H[3]; - uint32_t e = ctx->H[4]; - uint32_t f = ctx->H[5]; - uint32_t g = ctx->H[6]; - uint32_t h = ctx->H[7]; - - /* First increment the byte count. FIPS 180-2 specifies the possible - length of the file up to 2^64 bits. Here we only compute the - number of bytes. */ - ctx->total64 += len; - - /* Process all bytes in the buffer with 64 bytes in each round of - the loop. */ - while (nwords > 0) { - uint32_t W[64]; - uint32_t a_save = a; - uint32_t b_save = b; - uint32_t c_save = c; - uint32_t d_save = d; - uint32_t e_save = e; - uint32_t f_save = f; - uint32_t g_save = g; - uint32_t h_save = h; - - /* Operators defined in FIPS 180-2:4.1.2. */ -#define Ch(x, y, z) ((x & y) ^ (~x & z)) -#define Maj(x, y, z) ((x & y) ^ (x & z) ^ (y & z)) -#define S0(x) (CYCLIC (x, 2) ^ CYCLIC (x, 13) ^ CYCLIC (x, 22)) -#define S1(x) (CYCLIC (x, 6) ^ CYCLIC (x, 11) ^ CYCLIC (x, 25)) -#define R0(x) (CYCLIC (x, 7) ^ CYCLIC (x, 18) ^ (x >> 3)) -#define R1(x) (CYCLIC (x, 17) ^ CYCLIC (x, 19) ^ (x >> 10)) - - /* It is unfortunate that C does not provide an operator for - cyclic rotation. Hope the C compiler is smart enough. */ -#define CYCLIC(w, s) ((w >> s) | (w << (32 - s))) - - /* Compute the message schedule according to FIPS 180-2:6.2.2 step 2. */ - for (size_t t = 0; t < 16; ++t) { - W[t] = SWAP (*words); - ++words; - } - for (size_t t = 16; t < 64; ++t) - W[t] = R1 (W[t - 2]) + W[t - 7] + R0 (W[t - 15]) + W[t - 16]; - - /* The actual computation according to FIPS 180-2:6.2.2 step 3. */ - for (size_t t = 0; t < 64; ++t) { - uint32_t T1 = h + S1 (e) + Ch (e, f, g) + K[t] + W[t]; - uint32_t T2 = S0 (a) + Maj (a, b, c); - h = g; - g = f; - f = e; - e = d + T1; - d = c; - c = b; - b = a; - a = T1 + T2; - } - - /* Add the starting values of the context according to FIPS 180-2:6.2.2 - step 4. */ - a += a_save; - b += b_save; - c += c_save; - d += d_save; - e += e_save; - f += f_save; - g += g_save; - h += h_save; - - /* Prepare for the next round. */ - nwords -= 16; - } - - /* Put checksum in context given as argument. */ - ctx->H[0] = a; - ctx->H[1] = b; - ctx->H[2] = c; - ctx->H[3] = d; - ctx->H[4] = e; - ctx->H[5] = f; - ctx->H[6] = g; - ctx->H[7] = h; -} - -uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]) { - struct sha256_ctx ctx; - sha256_init_ctx(&ctx); - sha256_process_bytes(buffer, sz, &ctx); - return sha256_finish_ctx(&ctx, result); -} diff --git a/src/fundamental/sha256.h b/src/fundamental/sha256.h deleted file mode 100644 index dbb08e3..0000000 --- a/src/fundamental/sha256.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include -#include - -#define SHA256_DIGEST_SIZE 32 - -struct sha256_ctx { - uint32_t H[8]; - - union { - uint64_t total64; -#define TOTAL64_low (1 - (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) -#define TOTAL64_high (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) - uint32_t total[2]; - }; - - uint32_t buflen; - - union { - uint8_t buffer[128]; /* NB: always correctly aligned for UINT32. */ - uint32_t buffer32[32]; - uint64_t buffer64[16]; - }; -}; - -void sha256_init_ctx(struct sha256_ctx *ctx); -uint8_t *sha256_finish_ctx(struct sha256_ctx *ctx, uint8_t resbuf[static SHA256_DIGEST_SIZE]); -void sha256_process_bytes(const void *buffer, size_t len, struct sha256_ctx *ctx); - -static inline void sha256_process_bytes_and_size(const void *buffer, size_t len, struct sha256_ctx *ctx) { - sha256_process_bytes(&len, sizeof(len), ctx); - sha256_process_bytes(buffer, len, ctx); -} - -uint8_t* sha256_direct(const void *buffer, size_t sz, uint8_t result[static SHA256_DIGEST_SIZE]); - -#define SHA256_DIRECT(buffer, sz) sha256_direct(buffer, sz, (uint8_t[SHA256_DIGEST_SIZE]) {}) diff --git a/src/fundamental/string-util-fundamental.c b/src/fundamental/string-util-fundamental.c index a5bafc6..a18b2bc 100644 --- a/src/fundamental/string-util-fundamental.c +++ b/src/fundamental/string-util-fundamental.c @@ -33,14 +33,14 @@ sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) { return (sd_char*) s + l; } -sd_char* endswith(const sd_char *s, const sd_char *postfix) { +sd_char* endswith(const sd_char *s, const sd_char *suffix) { size_t sl, pl; assert(s); - assert(postfix); + assert(suffix); sl = strlen(s); - pl = strlen(postfix); + pl = strlen(suffix); if (pl == 0) return (sd_char*) s + sl; @@ -48,20 +48,20 @@ sd_char* endswith(const sd_char *s, const sd_char *postfix) { if (sl < pl) return NULL; - if (strcmp(s + sl - pl, postfix) != 0) + if (!streq(s + sl - pl, suffix)) return NULL; return (sd_char*) s + sl - pl; } -sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) { +sd_char* endswith_no_case(const sd_char *s, const sd_char *suffix) { size_t sl, pl; assert(s); - assert(postfix); + assert(suffix); sl = strlen(s); - pl = strlen(postfix); + pl = strlen(suffix); if (pl == 0) return (sd_char*) s + sl; @@ -69,7 +69,7 @@ sd_char* endswith_no_case(const sd_char *s, const sd_char *postfix) { if (sl < pl) return NULL; - if (strcasecmp(s + sl - pl, postfix) != 0) + if (!strcaseeq(s + sl - pl, suffix)) return NULL; return (sd_char*) s + sl - pl; diff --git a/src/fundamental/string-util-fundamental.h b/src/fundamental/string-util-fundamental.h index b537b2e..419f1cc 100644 --- a/src/fundamental/string-util-fundamental.h +++ b/src/fundamental/string-util-fundamental.h @@ -59,8 +59,8 @@ static inline size_t strlen_ptr(const sd_char *s) { sd_char *startswith(const sd_char *s, const sd_char *prefix) _pure_; sd_char *startswith_no_case(const sd_char *s, const sd_char *prefix) _pure_; -sd_char *endswith(const sd_char *s, const sd_char *postfix) _pure_; -sd_char *endswith_no_case(const sd_char *s, const sd_char *postfix) _pure_; +sd_char *endswith(const sd_char *s, const sd_char *suffix) _pure_; +sd_char *endswith_no_case(const sd_char *s, const sd_char *suffix) _pure_; static inline bool isempty(const sd_char *a) { return !a || a[0] == '\0'; diff --git a/src/fundamental/uki.c b/src/fundamental/uki.c index b1fa044..3887bf5 100644 --- a/src/fundamental/uki.c +++ b/src/fundamental/uki.c @@ -13,6 +13,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { [UNIFIED_SECTION_OSREL] = ".osrel", [UNIFIED_SECTION_CMDLINE] = ".cmdline", [UNIFIED_SECTION_INITRD] = ".initrd", + [UNIFIED_SECTION_UCODE] = ".ucode", [UNIFIED_SECTION_SPLASH] = ".splash", [UNIFIED_SECTION_DTB] = ".dtb", [UNIFIED_SECTION_UNAME] = ".uname", diff --git a/src/fundamental/uki.h b/src/fundamental/uki.h index ffa960f..8ab742d 100644 --- a/src/fundamental/uki.h +++ b/src/fundamental/uki.h @@ -10,6 +10,7 @@ typedef enum UnifiedSection { UNIFIED_SECTION_OSREL, UNIFIED_SECTION_CMDLINE, UNIFIED_SECTION_INITRD, + UNIFIED_SECTION_UCODE, UNIFIED_SECTION_SPLASH, UNIFIED_SECTION_DTB, UNIFIED_SECTION_UNAME, diff --git a/src/fuzz/fuzz.h b/src/fuzz/fuzz.h index 698ba42..123e88e 100644 --- a/src/fuzz/fuzz.h +++ b/src/fuzz/fuzz.h @@ -31,9 +31,9 @@ static inline bool outside_size_range(size_t size, size_t lower, size_t upper) { static inline void fuzz_setup_logging(void) { /* We don't want to fill the logs and slow down stuff when running * in a fuzzing mode, so disable most of the logging. */ + log_set_assert_return_is_critical(true); log_set_max_level(LOG_CRIT); - log_parse_environment(); - log_open(); + log_setup(); } /* Force value to not be optimized away. */ diff --git a/src/getty-generator/getty-generator.c b/src/getty-generator/getty-generator.c index 7486118..acce71b 100644 --- a/src/getty-generator/getty-generator.c +++ b/src/getty-generator/getty-generator.c @@ -94,9 +94,8 @@ static int verify_tty(const char *name) { if (fd < 0) return -errno; - errno = 0; - if (isatty(fd) <= 0) - return errno_or_else(EIO); + if (!isatty_safe(fd)) + return -errno; return 0; } @@ -151,7 +150,7 @@ static int add_credential_gettys(void) { }; int r; - FOREACH_ARRAY(t, table, ELEMENTSOF(table)) { + FOREACH_ELEMENT(t, table) { _cleanup_free_ char *b = NULL; size_t sz = 0; diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 07531ec..d69a16a 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -162,7 +162,7 @@ static int add_cryptsetup( return 0; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Partition is encrypted, but systemd-gpt-auto-generator was compiled without libcryptsetup support"); + "Partition is encrypted, but systemd-gpt-auto-generator was compiled without libcryptsetup support."); #endif } @@ -280,7 +280,7 @@ static int path_is_busy(const char *where) { assert(where); /* already a mountpoint; generators run during reload */ - r = path_is_mount_point(where, NULL, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(where, /* root = */ NULL, AT_SYMLINK_FOLLOW); if (r > 0) return false; /* The directory will be created by the mount or automount unit when it is started. */ @@ -463,18 +463,6 @@ static int add_automount( return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit); } -static int slash_boot_in_fstab(void) { - static int cache = -1; - - if (cache >= 0) - return cache; - - cache = fstab_is_mount_point("/boot"); - if (cache < 0) - return log_error_errno(cache, "Failed to parse fstab: %m"); - return cache; -} - static int add_partition_xbootldr(DissectedPartition *p) { _cleanup_free_ char *options = NULL; int r; @@ -486,14 +474,6 @@ static int add_partition_xbootldr(DissectedPartition *p) { return 0; } - r = slash_boot_in_fstab(); - if (r < 0) - return r; - if (r > 0) { - log_debug("/boot/ specified in fstab, ignoring XBOOTLDR partition."); - return 0; - } - r = path_is_busy("/boot"); if (r < 0) return r; @@ -523,18 +503,6 @@ static int add_partition_xbootldr(DissectedPartition *p) { } #if ENABLE_EFI -static int slash_efi_in_fstab(void) { - static int cache = -1; - - if (cache >= 0) - return cache; - - cache = fstab_is_mount_point("/efi"); - if (cache < 0) - return log_error_errno(cache, "Failed to parse fstab: %m"); - return cache; -} - static bool slash_boot_exists(void) { static int cache = -1; @@ -565,8 +533,8 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) { /* Check if there's an existing fstab entry for ESP. If so, we just skip the gpt-auto logic. */ r = fstab_has_node(p->node); if (r < 0) - return log_error_errno(r, - "Failed to check if fstab entry for device '%s' exists: %m", p->node); + log_warning_errno(r, "Failed to check if fstab entry for device '%s' exists, ignoring: %m", + p->node); if (r > 0) return 0; @@ -574,27 +542,16 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) { * Otherwise, if /efi/ is unused and empty (or missing), we'll take that. * Otherwise, we do nothing. */ if (!has_xbootldr && slash_boot_exists()) { - r = slash_boot_in_fstab(); + r = path_is_busy("/boot"); if (r < 0) return r; if (r == 0) { - r = path_is_busy("/boot"); - if (r < 0) - return r; - if (r == 0) { - esp_path = "/boot"; - id = "boot"; - } + esp_path = "/boot"; + id = "boot"; } } if (!esp_path) { - r = slash_efi_in_fstab(); - if (r < 0) - return r; - if (r > 0) - return 0; - r = path_is_busy("/efi"); if (r < 0) return r; @@ -781,6 +738,18 @@ static int process_loader_partitions(DissectedPartition *esp, DissectedPartition assert(esp); assert(xbootldr); + /* If any paths in fstab look similar to our favorite paths for ESP or XBOOTLDR, we just exit + * early. We also don't bother with cases where one is configured explicitly and the other shall be + * mounted automatically. */ + + r = fstab_has_mount_point_prefix_strv(STRV_MAKE("/boot", "/efi")); + if (r > 0) { + log_debug("Found mount entries in the /boot/ or /efi/ hierarchies in fstab, not generating ESP or XBOOTLDR mounts."); + return 0; + } + if (r < 0) + log_debug_errno(r, "Failed to check fstab existing paths, ignoring: %m"); + if (!is_efi_boot()) { log_debug("Not an EFI boot, skipping loader partition UUID check."); goto mount; diff --git a/src/hibernate-resume/hibernate-resume-config.c b/src/hibernate-resume/hibernate-resume-config.c index e4be7ca..c7ed1bc 100644 --- a/src/hibernate-resume/hibernate-resume-config.c +++ b/src/hibernate-resume/hibernate-resume-config.c @@ -11,6 +11,12 @@ #include "proc-cmdline.h" #include "efivars.h" +typedef struct KernelHibernateLocation { + char *device; + uint64_t offset; + bool offset_set; +} KernelHibernateLocation; + static KernelHibernateLocation* kernel_hibernate_location_free(KernelHibernateLocation *k) { if (!k) return NULL; @@ -22,7 +28,7 @@ static KernelHibernateLocation* kernel_hibernate_location_free(KernelHibernateLo DEFINE_TRIVIAL_CLEANUP_FUNC(KernelHibernateLocation*, kernel_hibernate_location_free); -static EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e) { +EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e) { if (!e) return NULL; @@ -36,8 +42,6 @@ static EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e return mfree(e); } -DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free); - void hibernate_info_done(HibernateInfo *info) { assert(info); @@ -121,7 +125,7 @@ static bool validate_efi_hibernate_location(EFIHibernateLocation *e) { if (!streq_ptr(id, e->id) || !streq_ptr(image_id, e->image_id)) { - log_notice("HibernateLocation system identifier doesn't match currently running system, not resuming from it."); + log_notice("HibernateLocation system identifier doesn't match currently running system, would not resume from it."); return false; } @@ -133,9 +137,10 @@ static bool validate_efi_hibernate_location(EFIHibernateLocation *e) { return true; } +#endif -static int get_efi_hibernate_location(EFIHibernateLocation **ret) { - +int get_efi_hibernate_location(EFIHibernateLocation **ret) { +#if ENABLE_EFI static const JsonDispatch dispatch_table[] = { { "uuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(EFIHibernateLocation, uuid), JSON_MANDATORY }, { "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(EFIHibernateLocation, offset), JSON_MANDATORY }, @@ -152,8 +157,6 @@ static int get_efi_hibernate_location(EFIHibernateLocation **ret) { _cleanup_free_ char *location_str = NULL; int r; - assert(ret); - if (!is_efi_boot()) goto skip; @@ -173,7 +176,7 @@ static int get_efi_hibernate_location(EFIHibernateLocation **ret) { if (!e) return log_oom(); - r = json_dispatch(v, dispatch_table, JSON_LOG, e); + r = json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, e); if (r < 0) return r; @@ -192,15 +195,19 @@ static int get_efi_hibernate_location(EFIHibernateLocation **ret) { if (asprintf(&e->device, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(e->uuid)) < 0) return log_oom(); - *ret = TAKE_PTR(e); + if (ret) + *ret = TAKE_PTR(e); return 1; skip: - *ret = NULL; +#endif + if (ret) + *ret = NULL; return 0; } void compare_hibernate_location_and_warn(const HibernateInfo *info) { +#if ENABLE_EFI int r; assert(info); @@ -224,19 +231,8 @@ void compare_hibernate_location_and_warn(const HibernateInfo *info) { if (info->cmdline->offset != info->efi->offset) log_warning("resume_offset=%" PRIu64 " doesn't match with EFI HibernateLocation offset %" PRIu64 ", proceeding anyway with resume_offset=.", info->cmdline->offset, info->efi->offset); -} - -void clear_efi_hibernate_location(void) { - int r; - - if (!is_efi_boot()) - return; - - r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); - if (r < 0) - log_warning_errno(r, "Failed to clear EFI variable HibernateLocation, ignoring: %m"); -} #endif +} int acquire_hibernate_info(HibernateInfo *ret) { _cleanup_(hibernate_info_done) HibernateInfo i = {}; @@ -246,11 +242,9 @@ int acquire_hibernate_info(HibernateInfo *ret) { if (r < 0) return r; -#if ENABLE_EFI r = get_efi_hibernate_location(&i.efi); if (r < 0) return r; -#endif if (i.cmdline) { i.device = i.cmdline->device; diff --git a/src/hibernate-resume/hibernate-resume-config.h b/src/hibernate-resume/hibernate-resume-config.h index 365d9cc..68ef075 100644 --- a/src/hibernate-resume/hibernate-resume-config.h +++ b/src/hibernate-resume/hibernate-resume-config.h @@ -5,11 +5,9 @@ #include "sd-id128.h" -typedef struct KernelHibernateLocation { - char *device; - uint64_t offset; - bool offset_set; -} KernelHibernateLocation; +#include "macro.h" + +typedef struct KernelHibernateLocation KernelHibernateLocation; typedef struct EFIHibernateLocation { char *device; @@ -24,6 +22,11 @@ typedef struct EFIHibernateLocation { char *image_version; } EFIHibernateLocation; +EFIHibernateLocation* efi_hibernate_location_free(EFIHibernateLocation *e); +DEFINE_TRIVIAL_CLEANUP_FUNC(EFIHibernateLocation*, efi_hibernate_location_free); + +int get_efi_hibernate_location(EFIHibernateLocation **ret); + typedef struct HibernateInfo { const char *device; uint64_t offset; /* in memory pages */ @@ -36,20 +39,4 @@ void hibernate_info_done(HibernateInfo *info); int acquire_hibernate_info(HibernateInfo *ret); -#if ENABLE_EFI - void compare_hibernate_location_and_warn(const HibernateInfo *info); - -void clear_efi_hibernate_location(void); - -#else - -static inline void compare_hibernate_location_and_warn(const HibernateInfo *info) { - return; -} - -static inline void clear_efi_hibernate_location(void) { - return; -} - -#endif diff --git a/src/hibernate-resume/hibernate-resume.c b/src/hibernate-resume/hibernate-resume.c index 175a0bd..c6494b9 100644 --- a/src/hibernate-resume/hibernate-resume.c +++ b/src/hibernate-resume/hibernate-resume.c @@ -1,8 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include +#include "build.h" #include "devnum-util.h" #include "hibernate-resume-config.h" #include "hibernate-util.h" @@ -10,12 +12,84 @@ #include "log.h" #include "main-func.h" #include "parse-util.h" +#include "pretty-print.h" #include "static-destruct.h" +#include "terminal-util.h" static HibernateInfo arg_info = {}; +static bool arg_clear = false; STATIC_DESTRUCTOR_REGISTER(arg_info, hibernate_info_done); +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-hibernate-resume", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] [DEVICE [OFFSET]]\n" + "\n%sInitiate resume from hibernation.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --clear Clear hibernation storage information from EFI and exit\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + ansi_highlight(), + ansi_normal(), + link); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_CLEAR, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "clear", no_argument, NULL, ARG_CLEAR }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_CLEAR: + arg_clear = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (argc > optind && arg_clear) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Extraneous arguments specified with --clear, refusing."); + + return 1; +} + static int setup_hibernate_info_and_warn(void) { int r; @@ -32,42 +106,68 @@ static int setup_hibernate_info_and_warn(void) { return 1; } +static int action_clear(void) { + int r; + + assert(arg_clear); + + /* Let's insist that the system identifier is verified still. After all if things don't match, + * the resume wouldn't get triggered in the first place. We should not erase the var if booted + * from LiveCD/portable systems/... */ + r = get_efi_hibernate_location(/* ret = */ NULL); + if (r <= 0) + return r; + + r = clear_efi_hibernate_location_and_warn(); + if (r > 0) + log_notice("Successfully cleared HibernateLocation EFI variable."); + return r; +} + static int run(int argc, char *argv[]) { struct stat st; int r; log_setup(); - if (argc < 1 || argc > 3) + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (argc - optind > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects zero, one, or two arguments."); umask(0022); - if (!in_initrd()) - return 0; + if (arg_clear) + return action_clear(); - if (argc > 1) { - arg_info.device = argv[1]; + if (!in_initrd()) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Not running in initrd, refusing to initiate resume from hibernation."); - if (argc == 3) { - r = safe_atou64(argv[2], &arg_info.offset); - if (r < 0) - return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[2]); - } - } else { + if (argc <= optind) { r = setup_hibernate_info_and_warn(); if (r <= 0) return r; if (arg_info.efi) - clear_efi_hibernate_location(); + (void) clear_efi_hibernate_location_and_warn(); + } else { + arg_info.device = ASSERT_PTR(argv[optind]); + + if (argc - optind == 2) { + r = safe_atou64(argv[optind + 1], &arg_info.offset); + if (r < 0) + return log_error_errno(r, "Failed to parse resume offset %s: %m", argv[optind + 1]); + } } if (stat(arg_info.device, &st) < 0) return log_error_errno(errno, "Failed to stat resume device '%s': %m", arg_info.device); if (!S_ISBLK(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Resume device '%s' is not a block device.", arg_info.device); /* The write shall not return if a resume takes place. */ diff --git a/src/home/home-util.c b/src/home/home-util.c index c777d7b..9735236 100644 --- a/src/home/home-util.c +++ b/src/home/home-util.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "dns-domain.h" +#include "fd-util.h" #include "home-util.h" #include "libcrypt-util.h" #include "memory-util.h" @@ -9,6 +10,8 @@ #include "strv.h" #include "user-util.h" +DEFINE_HASH_OPS_FULL(blob_fd_hash_ops, char, path_hash_func, path_compare, free, void, close_fd_ptr); + bool suitable_user_name(const char *name) { /* Checks whether the specified name is suitable for management via homed. Note that client-side @@ -137,3 +140,7 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) { const char *home_record_dir(void) { return secure_getenv("SYSTEMD_HOME_RECORD_DIR") ?: "/var/lib/systemd/home/"; } + +const char *home_system_blob_dir(void) { + return secure_getenv("SYSTEMD_HOME_SYSTEM_BLOB_DIR") ?: "/var/cache/systemd/home/"; +} diff --git a/src/home/home-util.h b/src/home/home-util.h index 36b301d..42131b9 100644 --- a/src/home/home-util.h +++ b/src/home/home-util.h @@ -5,9 +5,17 @@ #include "sd-bus.h" +#include "hash-funcs.h" #include "time-util.h" #include "user-record.h" +/* Flags supported by UpdateEx() */ +#define SD_HOMED_UPDATE_OFFLINE (UINT64_C(1) << 0) +#define SD_HOMED_UPDATE_FLAGS_ALL (SD_HOMED_UPDATE_OFFLINE) + +/* Flags supported by CreateHomeEx() */ +#define SD_HOMED_CREATE_FLAGS_ALL (0) + /* Put some limits on disk sizes: not less than 5M, not more than 5T */ #define USER_DISK_SIZE_MIN (UINT64_C(5)*1024*1024) #define USER_DISK_SIZE_MAX (UINT64_C(5)*1024*1024*1024*1024) @@ -20,6 +28,8 @@ /* This should be 83% right now, i.e. 100 of (100 + 20). Let's protect us against accidental changes. */ assert_cc(USER_DISK_SIZE_DEFAULT_PERCENT == 83U); +extern const struct hash_ops blob_fd_hash_ops; + bool suitable_user_name(const char *name); int suitable_realm(const char *realm); int suitable_image_path(const char *path); @@ -35,3 +45,4 @@ int bus_message_append_secret(sd_bus_message *m, UserRecord *secret); #define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE) const char *home_record_dir(void); +const char *home_system_blob_dir(void); diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c index 3cbdf91..384461a 100644 --- a/src/home/homectl-fido2.c +++ b/src/home/homectl-fido2.c @@ -167,6 +167,7 @@ int identity_add_fido2_parameters( /* user_display_name= */ rn ? json_variant_string(rn) : NULL, /* user_icon_name= */ NULL, /* askpw_icon_name= */ "user-home", + /* askpw_credential= */ "home.token-pin", lock_with, cred_alg, &cid, &cid_size, diff --git a/src/home/homectl-pkcs11.c b/src/home/homectl-pkcs11.c index 2539af0..bb582d7 100644 --- a/src/home/homectl-pkcs11.c +++ b/src/home/homectl-pkcs11.c @@ -8,62 +8,55 @@ #include "memory-util.h" #include "openssl-util.h" #include "pkcs11-util.h" -#include "random-util.h" #include "strv.h" -static int add_pkcs11_encrypted_key( - JsonVariant **v, - const char *uri, - const void *encrypted_key, size_t encrypted_key_size, - const void *decrypted_key, size_t decrypted_key_size) { - - _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; - _cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; - ssize_t base64_encoded_size; +int identity_add_token_pin(JsonVariant **v, const char *pin) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL; + _cleanup_strv_free_erase_ char **pins = NULL; int r; assert(v); - assert(uri); - assert(encrypted_key); - assert(encrypted_key_size > 0); - assert(decrypted_key); - assert(decrypted_key_size > 0); - /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends - * expect a NUL terminated string, and we use a binary key */ - base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); - if (base64_encoded_size < 0) - return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + if (isempty(pin)) + return 0; - r = hash_password(base64_encoded, &hashed); + w = json_variant_ref(json_variant_by_key(*v, "secret")); + l = json_variant_ref(json_variant_by_key(w, "tokenPin")); + + r = json_variant_strv(l, &pins); if (r < 0) - return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); + return log_error_errno(r, "Failed to convert PIN array: %m"); - r = json_build(&e, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)), - JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)), - JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)))); + if (strv_contains(pins, pin)) + return 0; + + r = strv_extend(&pins, pin); if (r < 0) - return log_error_errno(r, "Failed to build encrypted JSON key object: %m"); + return log_oom(); - w = json_variant_ref(json_variant_by_key(*v, "privileged")); - l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey")); + strv_uniq(pins); - r = json_variant_append_array(&l, e); + l = json_variant_unref(l); + + r = json_variant_new_array_strv(&l, pins); if (r < 0) - return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m"); + return log_error_errno(r, "Failed to allocate new PIN array JSON: %m"); - r = json_variant_set_field(&w, "pkcs11EncryptedKey", l); + json_variant_sensitive(l); + + r = json_variant_set_field(&w, "tokenPin", l); if (r < 0) - return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m"); + return log_error_errno(r, "Failed to update PIN field: %m"); - r = json_variant_set_field(v, "privileged", w); + r = json_variant_set_field(v, "secret", w); if (r < 0) - return log_error_errno(r, "Failed to update privileged field: %m"); + return log_error_errno(r, "Failed to update secret object: %m"); - return 0; + return 1; } +#if HAVE_P11KIT + static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; _cleanup_strv_free_ char **l = NULL; @@ -98,100 +91,82 @@ static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) { return 0; } -int identity_add_token_pin(JsonVariant **v, const char *pin) { - _cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL; - _cleanup_strv_free_erase_ char **pins = NULL; +static int add_pkcs11_encrypted_key( + JsonVariant **v, + const char *uri, + const void *encrypted_key, size_t encrypted_key_size, + const void *decrypted_key, size_t decrypted_key_size) { + + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; + ssize_t base64_encoded_size; int r; assert(v); + assert(uri); + assert(encrypted_key); + assert(encrypted_key_size > 0); + assert(decrypted_key); + assert(decrypted_key_size > 0); - if (isempty(pin)) - return 0; - - w = json_variant_ref(json_variant_by_key(*v, "secret")); - l = json_variant_ref(json_variant_by_key(w, "tokenPin")); + /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends + * expect a NUL terminated string, and we use a binary key */ + base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); + if (base64_encoded_size < 0) + return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); - r = json_variant_strv(l, &pins); + r = hash_password(base64_encoded, &hashed); if (r < 0) - return log_error_errno(r, "Failed to convert PIN array: %m"); - - if (strv_contains(pins, pin)) - return 0; + return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %m"); - r = strv_extend(&pins, pin); + r = json_build(&e, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("uri", JSON_BUILD_STRING(uri)), + JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(encrypted_key, encrypted_key_size)), + JSON_BUILD_PAIR("hashedPassword", JSON_BUILD_STRING(hashed)))); if (r < 0) - return log_oom(); - - strv_uniq(pins); + return log_error_errno(r, "Failed to build encrypted JSON key object: %m"); - l = json_variant_unref(l); + w = json_variant_ref(json_variant_by_key(*v, "privileged")); + l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey")); - r = json_variant_new_array_strv(&l, pins); + r = json_variant_append_array(&l, e); if (r < 0) - return log_error_errno(r, "Failed to allocate new PIN array JSON: %m"); - - json_variant_sensitive(l); + return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m"); - r = json_variant_set_field(&w, "tokenPin", l); + r = json_variant_set_field(&w, "pkcs11EncryptedKey", l); if (r < 0) - return log_error_errno(r, "Failed to update PIN field: %m"); + return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m"); - r = json_variant_set_field(v, "secret", w); + r = json_variant_set_field(v, "privileged", w); if (r < 0) - return log_error_errno(r, "Failed to update secret object: %m"); - - return 1; -} + return log_error_errno(r, "Failed to update privileged field: %m"); -static int acquire_pkcs11_certificate( - const char *uri, - const char *askpw_friendly_name, - const char *askpw_icon_name, - X509 **ret_cert, - char **ret_pin_used) { -#if HAVE_P11KIT - return pkcs11_acquire_certificate(uri, askpw_friendly_name, askpw_icon_name, ret_cert, ret_pin_used); -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "PKCS#11 tokens not supported on this build."); -#endif + return 0; } int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) { - _cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_key = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL, *saved_key = NULL; _cleanup_(erase_and_freep) char *pin = NULL; - size_t decrypted_key_size, encrypted_key_size; - _cleanup_(X509_freep) X509 *cert = NULL; - EVP_PKEY *pkey; + size_t decrypted_key_size, saved_key_size; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; int r; assert(v); - r = acquire_pkcs11_certificate(uri, "home directory operation", "user-home", &cert, &pin); + r = pkcs11_acquire_public_key( + uri, + "home directory operation", + "user-home", + "home.token-pin", + /* askpw_flags= */ 0, + &pkey, + &pin); if (r < 0) return r; - pkey = X509_get0_pubkey(cert); - if (!pkey) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); - - r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + r = pkey_generate_volume_keys(pkey, &decrypted_key, &decrypted_key_size, &saved_key, &saved_key_size); if (r < 0) - return log_error_errno(r, "Failed to extract RSA key size from X509 certificate."); - - log_debug("Generating %zu bytes random key.", decrypted_key_size); - - decrypted_key = malloc(decrypted_key_size); - if (!decrypted_key) - return log_oom(); - - r = crypto_random_bytes(decrypted_key, decrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to generate random key: %m"); - - r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &encrypted_key, &encrypted_key_size); - if (r < 0) - return log_error_errno(r, "Failed to encrypt key: %m"); + return log_error_errno(r, "Failed to generate volume keys: %m"); /* Add the token URI to the public part of the record. */ r = add_pkcs11_token_uri(v, uri); @@ -202,7 +177,7 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) { r = add_pkcs11_encrypted_key( v, uri, - encrypted_key, encrypted_key_size, + saved_key, saved_key_size, decrypted_key, decrypted_key_size); if (r < 0) return r; @@ -216,3 +191,5 @@ int identity_add_pkcs11_key_data(JsonVariant **v, const char *uri) { return 0; } + +#endif diff --git a/src/home/homectl-pkcs11.h b/src/home/homectl-pkcs11.h index 5c30fee..424777f 100644 --- a/src/home/homectl-pkcs11.h +++ b/src/home/homectl-pkcs11.h @@ -5,7 +5,13 @@ int identity_add_token_pin(JsonVariant **v, const char *pin); +#if HAVE_P11KIT int identity_add_pkcs11_key_data(JsonVariant **v, const char *token_uri); +#else +static inline int identity_add_pkcs11_key_data(JsonVariant **v, const char *token_uri) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "PKCS#11 tokens not supported on this build."); +} +#endif int list_pkcs11_tokens(void); int find_pkcs11_token_auto(char **ret); diff --git a/src/home/homectl.c b/src/home/homectl.c index a6951c8..d9321a2 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -12,6 +12,9 @@ #include "cap-list.h" #include "capability-util.h" #include "cgroup-util.h" +#include "copy.h" +#include "creds-util.h" +#include "dirent-util.h" #include "dns-domain.h" #include "env-util.h" #include "fd-util.h" @@ -19,6 +22,7 @@ #include "format-table.h" #include "fs-util.h" #include "glyph-util.h" +#include "hashmap.h" #include "home-util.h" #include "homectl-fido2.h" #include "homectl-pkcs11.h" @@ -35,16 +39,21 @@ #include "percent-util.h" #include "pkcs11-util.h" #include "pretty-print.h" +#include "proc-cmdline.h" #include "process-util.h" +#include "recurse-dir.h" #include "rlimit-util.h" +#include "rm-rf.h" #include "spawn-polkit-agent.h" #include "terminal-util.h" -#include "uid-alloc-range.h" +#include "tmpfile-util.h" +#include "uid-classification.h" #include "user-record.h" #include "user-record-password-quality.h" #include "user-record-show.h" #include "user-record-util.h" #include "user-util.h" +#include "userdb.h" #include "verbs.h" static PagerFlags arg_pager_flags = 0; @@ -52,6 +61,7 @@ static bool arg_legend = true; static bool arg_ask_password = true; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; static const char *arg_host = NULL; +static bool arg_offline = false; static const char *arg_identity = NULL; static JsonVariant *arg_identity_extra = NULL; static JsonVariant *arg_identity_extra_privileged = NULL; @@ -80,6 +90,10 @@ static enum { } arg_export_format = EXPORT_FORMAT_FULL; static uint64_t arg_capability_bounding_set = UINT64_MAX; static uint64_t arg_capability_ambient_set = UINT64_MAX; +static bool arg_prompt_new_user = false; +static char *arg_blob_dir = NULL; +static bool arg_blob_clear = false; +static Hashmap *arg_blob_files = NULL; STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp); STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp); @@ -89,6 +103,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_identity_filter, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_identity_filter_rlimits, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_blob_dir, freep); +STATIC_DESTRUCTOR_REGISTER(arg_blob_files, hashmap_freep); static const BusLocator *bus_mgr; @@ -102,7 +118,10 @@ static bool identity_properties_specified(void) { !strv_isempty(arg_identity_filter) || !strv_isempty(arg_identity_filter_rlimits) || !strv_isempty(arg_pkcs11_token_uri) || - !strv_isempty(arg_fido2_device); + !strv_isempty(arg_fido2_device) || + arg_blob_dir || + arg_blob_clear || + !hashmap_isempty(arg_blob_files); } static int acquire_bus(sd_bus **bus) { @@ -184,7 +203,7 @@ static int list_homes(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (table_get_rows(table) > 1 || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + if (!table_isempty(table) || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { r = table_set_sort(table, (size_t) 0); if (r < 0) return table_log_sort_error(r); @@ -194,11 +213,11 @@ static int list_homes(int argc, char *argv[], void *userdata) { return r; } - if (arg_legend && (arg_json_format_flags & JSON_FORMAT_OFF)) { - if (table_get_rows(table) > 1) - printf("\n%zu home areas listed.\n", table_get_rows(table) - 1); - else + if (arg_legend && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + if (table_isempty(table)) printf("No home areas.\n"); + else + printf("\n%zu home areas listed.\n", table_get_rows(table) - 1); } return 0; @@ -243,14 +262,14 @@ static int acquire_existing_password( user_name) < 0) return log_oom(); - r = ask_password_auto(question, - /* icon= */ "user-home", - NULL, - /* key_name= */ "home-password", - /* credential_name= */ "home.password", - USEC_INFINITY, - flags, - &password); + AskPasswordRequest req = { + .message = question, + .icon = "user-home", + .keyring = "home-password", + .credential = "home.password", + }; + + r = ask_password_auto(&req, USEC_INFINITY, flags, &password); if (r == -EUNATCH) { /* EUNATCH is returned if no password was found and asking interactively was * disabled via the flags. Not an error for us. */ log_debug_errno(r, "No passwords acquired."); @@ -301,14 +320,14 @@ static int acquire_recovery_key( if (asprintf(&question, "Please enter recovery key for user %s:", user_name) < 0) return log_oom(); - r = ask_password_auto(question, - /* icon= */ "user-home", - NULL, - /* key_name= */ "home-recovery-key", - /* credential_name= */ "home.recovery-key", - USEC_INFINITY, - flags, - &recovery_key); + AskPasswordRequest req = { + .message = question, + .icon = "user-home", + .keyring = "home-recovery-key", + .credential = "home.recovery-key", + }; + + r = ask_password_auto(&req, USEC_INFINITY, flags, &recovery_key); if (r == -EUNATCH) { /* EUNATCH is returned if no recovery key was found and asking interactively was * disabled via the flags. Not an error for us. */ log_debug_errno(r, "No recovery keys acquired."); @@ -355,15 +374,14 @@ static int acquire_token_pin( if (asprintf(&question, "Please enter security token PIN for user %s:", user_name) < 0) return log_oom(); - r = ask_password_auto( - question, - /* icon= */ "user-home", - NULL, - /* key_name= */ "token-pin", - /* credential_name= */ "home.token-pin", - USEC_INFINITY, - flags, - &pin); + AskPasswordRequest req = { + .message = question, + .icon = "user-home", + .keyring = "token-pin", + .credential = "home.token-pin", + }; + + r = ask_password_auto(&req, USEC_INFINITY, flags, &pin); if (r == -EUNATCH) { /* EUNATCH is returned if no PIN was found and asking interactively was disabled * via the flags. Not an error for us. */ log_debug_errno(r, "No security token PINs acquired."); @@ -735,7 +753,6 @@ static int inspect_home(int argc, char *argv[], void *userdata) { r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", *i); } else r = bus_call_method(bus, bus_mgr, "GetUserRecordByUID", &error, &reply, "u", (uint32_t) uid); - if (r < 0) { log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r)); if (ret == 0) @@ -1092,7 +1109,7 @@ static int add_disposition(JsonVariant **v) { return 1; } -static int acquire_new_home_record(UserRecord **ret) { +static int acquire_new_home_record(JsonVariant *input, UserRecord **ret) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(user_record_unrefp) UserRecord *hr = NULL; int r; @@ -1102,12 +1119,16 @@ static int acquire_new_home_record(UserRecord **ret) { if (arg_identity) { unsigned line, column; + if (input) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Two identity records specified, refusing."); + r = json_parse_file( streq(arg_identity, "-") ? stdin : NULL, streq(arg_identity, "-") ? "" : arg_identity, JSON_PARSE_SENSITIVE, &v, &line, &column); if (r < 0) return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column); - } + } else + v = json_variant_ref(input); r = apply_identity_changes(&v); if (r < 0) @@ -1146,7 +1167,18 @@ static int acquire_new_home_record(UserRecord **ret) { if (!hr) return log_oom(); - r = user_record_load(hr, v, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_LOG|USER_RECORD_PERMISSIVE); + r = user_record_load( + hr, + v, + USER_RECORD_REQUIRE_REGULAR| + USER_RECORD_ALLOW_SECRET| + USER_RECORD_ALLOW_PRIVILEGED| + USER_RECORD_ALLOW_PER_MACHINE| + USER_RECORD_STRIP_BINDING| + USER_RECORD_STRIP_STATUS| + USER_RECORD_STRIP_SIGNATURE| + USER_RECORD_LOG| + USER_RECORD_PERMISSIVE); if (r < 0) return r; @@ -1191,19 +1223,22 @@ static int acquire_new_password( _cleanup_free_ char *question = NULL; if (--i == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up:"); + return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Too many attempts, giving up."); if (asprintf(&question, "Please enter new password for user %s:", user_name) < 0) return log_oom(); + AskPasswordRequest req = { + .message = question, + .icon = "user-home", + .keyring = "home-password", + .credential = "home.new-password", + }; + r = ask_password_auto( - question, - /* icon= */ "user-home", - NULL, - /* key_name= */ "home-password", - /* credential_name= */ "home.new-password", + &req, USEC_INFINITY, - 0, /* no caching, we want to collect a new password here after all */ + /* flags= */ 0, /* no caching, we want to collect a new password here after all */ &first); if (r < 0) return log_error_errno(r, "Failed to acquire password: %m"); @@ -1212,14 +1247,12 @@ static int acquire_new_password( if (asprintf(&question, "Please enter new password for user %s (repeat):", user_name) < 0) return log_oom(); + req.message = question; + r = ask_password_auto( - question, - /* icon= */ "user-home", - NULL, - /* key_name= */ "home-password", - /* credential_name= */ "home.new-password", + &req, USEC_INFINITY, - 0, /* no caching */ + /* flags= */ 0, /* no caching */ &second); if (r < 0) return log_error_errno(r, "Failed to acquire password: %m"); @@ -1247,47 +1280,145 @@ static int acquire_new_password( } } -static int create_home(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(user_record_unrefp) UserRecord *hr = NULL; +static int acquire_merged_blob_dir(UserRecord *hr, bool existing, Hashmap **ret) { + _cleanup_free_ char *sys_blob_path = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + _cleanup_closedir_ DIR *d = NULL; + const char *src_blob_path, *filename; + void *fd_ptr; int r; - r = acquire_bus(&bus); - if (r < 0) - return r; + assert(ret); - (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + HASHMAP_FOREACH_KEY(fd_ptr, filename, arg_blob_files) { + _cleanup_free_ char *filename_dup = NULL; + _cleanup_close_ int fd_dup = -EBADF; - if (argc >= 2) { - /* If a username was specified, use it */ + filename_dup = strdup(filename); + if (!filename_dup) + return log_oom(); - if (valid_user_group_name(argv[1], 0)) - r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]); + if (PTR_TO_FD(fd_ptr) != -EBADF) { + fd_dup = fcntl(PTR_TO_FD(fd_ptr), F_DUPFD_CLOEXEC, 3); + if (fd_dup < 0) + return log_error_errno(errno, "Failed to duplicate fd of %s: %m", filename); + } + + r = hashmap_ensure_put(&blobs, &blob_fd_hash_ops, filename_dup, FD_TO_PTR(fd_dup)); + if (r < 0) + return r; + TAKE_PTR(filename_dup); /* Ownership transferred to hashmap */ + TAKE_FD(fd_dup); + } + + if (arg_blob_dir) + src_blob_path = arg_blob_dir; + else if (existing && !arg_blob_clear) { + if (hr->blob_directory) + src_blob_path = hr->blob_directory; else { - _cleanup_free_ char *un = NULL, *rr = NULL; + /* This isn't technically a correct thing to do for generic user records, + * so anyone looking at this code for reference shouldn't replicate it. + * However, since homectl is tied to homed, this is OK. This adds robustness + * for situations where the user record is coming directly from the CLI and + * thus doesn't have a blobDirectory set */ + + sys_blob_path = path_join(home_system_blob_dir(), hr->user_name); + if (!sys_blob_path) + return log_oom(); - /* Before we consider the user name invalid, let's check if we can split it? */ - r = split_user_name_realm(argv[1], &un, &rr); - if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name '%s' is not valid: %m", argv[1]); + src_blob_path = sys_blob_path; + } + } else + goto nodir; /* Shortcut: no dir to merge with, so just return copy of arg_blob_files */ - if (rr) { - r = json_variant_set_field_string(&arg_identity_extra, "realm", rr); - if (r < 0) - return log_error_errno(r, "Failed to set realm field: %m"); - } + d = opendir(src_blob_path); + if (!d) + return log_error_errno(errno, "Failed to open %s: %m", src_blob_path); - r = json_variant_set_field_string(&arg_identity_extra, "userName", un); + FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read %s: %m", src_blob_path)) { + _cleanup_free_ char *name = NULL; + _cleanup_close_ int fd = -EBADF; + + if (dot_or_dot_dot(de->d_name)) + continue; + + if (hashmap_contains(blobs, de->d_name)) + continue; /* arg_blob_files should override the base dir */ + + if (!suitable_blob_filename(de->d_name)) { + log_warning("File %s in blob directory %s has an invalid filename. Skipping.", de->d_name, src_blob_path); + continue; } + + name = strdup(de->d_name); + if (!name) + return log_oom(); + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s in %s: %m", de->d_name, src_blob_path); + + r = fd_verify_regular(fd); + if (r < 0) { + log_warning_errno(r, "Entry %s in blob directory %s is not a regular file. Skipping.", de->d_name, src_blob_path); + continue; + } + + r = hashmap_ensure_put(&blobs, &blob_fd_hash_ops, name, FD_TO_PTR(fd)); if (r < 0) - return log_error_errno(r, "Failed to set userName field: %m"); - } else { - /* If neither a username nor an identity have been specified we cannot operate. */ - if (!arg_identity) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name required."); + return r; + TAKE_PTR(name); /* Ownership transferred to hashmap */ + TAKE_FD(fd); + } + +nodir: + *ret = TAKE_PTR(blobs); + return 0; +} + +static int bus_message_append_blobs(sd_bus_message *m, Hashmap *blobs) { + const char *filename; + void *fd_ptr; + int r; + + assert(m); + + r = sd_bus_message_open_container(m, 'a', "{sh}"); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(fd_ptr, filename, blobs) { + int fd = PTR_TO_FD(fd_ptr); + + if (fd == -EBADF) /* File marked for deletion */ + continue; + + r = sd_bus_message_append(m, "{sh}", filename, fd); + if (r < 0) + return r; } - r = acquire_new_home_record(&hr); + return sd_bus_message_close_container(m); +} + +static int create_home_common(JsonVariant *input) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + int r; + + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = acquire_new_home_record(input, &hr); + if (r < 0) + return r; + + r = acquire_merged_blob_dir(hr, false, &blobs); if (r < 0) return r; @@ -1337,7 +1468,7 @@ static int create_home(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to format user record: %m"); - r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "CreateHomeEx"); if (r < 0) return bus_log_create_error(r); @@ -1347,6 +1478,14 @@ static int create_home(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); + r = bus_message_append_blobs(m, blobs); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "t", UINT64_C(0)); + if (r < 0) + return bus_log_create_error(r); + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { if (sd_bus_error_has_name(&error, BUS_ERROR_LOW_PASSWORD_QUALITY)) { @@ -1374,6 +1513,41 @@ static int create_home(int argc, char *argv[], void *userdata) { return 0; } +static int create_home(int argc, char *argv[], void *userdata) { + int r; + + if (argc >= 2) { + /* If a username was specified, use it */ + + if (valid_user_group_name(argv[1], 0)) + r = json_variant_set_field_string(&arg_identity_extra, "userName", argv[1]); + else { + _cleanup_free_ char *un = NULL, *rr = NULL; + + /* Before we consider the user name invalid, let's check if we can split it? */ + r = split_user_name_realm(argv[1], &un, &rr); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name '%s' is not valid.", argv[1]); + + if (rr) { + r = json_variant_set_field_string(&arg_identity_extra, "realm", rr); + if (r < 0) + return log_error_errno(r, "Failed to set realm field: %m"); + } + + r = json_variant_set_field_string(&arg_identity_extra, "userName", un); + } + if (r < 0) + return log_error_errno(r, "Failed to set userName field: %m"); + } else { + /* If neither a username nor an identity have been specified we cannot operate. */ + if (!arg_identity) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name required."); + } + + return create_home_common(/* input= */ NULL); +} + static int remove_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r, ret = 0; @@ -1467,7 +1641,7 @@ static int acquire_updated_home_record( reply = sd_bus_message_unref(reply); - r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature")); + r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature", "blobManifest")); if (r < 0) return log_error_errno(r, "Failed to strip binding and status from record to update: %m"); } @@ -1537,7 +1711,9 @@ static int update_home(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *hr = NULL, *secret = NULL; _cleanup_free_ char *buffer = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; const char *username; + uint64_t flags = 0; int r; if (argc >= 2) @@ -1570,18 +1746,25 @@ static int update_home(int argc, char *argv[], void *userdata) { if (r < 0) return r; + r = acquire_merged_blob_dir(hr, true, &blobs); + if (r < 0) + return r; + /* If we do multiple operations, let's output things more verbosely, since otherwise the repeated * authentication might be confusing. */ if (arg_and_resize || arg_and_change_password) log_info("Updating home directory."); + if (arg_offline) + flags |= SD_HOMED_UPDATE_OFFLINE; + for (;;) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; _cleanup_free_ char *formatted = NULL; - r = bus_message_new_method_call(bus, &m, bus_mgr, "UpdateHome"); + r = bus_message_new_method_call(bus, &m, bus_mgr, "UpdateHomeEx"); if (r < 0) return bus_log_create_error(r); @@ -1595,6 +1778,14 @@ static int update_home(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_create_error(r); + r = bus_message_append_blobs(m, blobs); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "t", flags); + if (r < 0) + return bus_log_create_error(r); + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); if (r < 0) { if (arg_and_change_password && @@ -2131,6 +2322,190 @@ static int rebalance(int argc, char *argv[], void *userdata) { return 0; } +static int create_from_credentials(void) { + _cleanup_close_ int fd = -EBADF; + int ret = 0, n_created = 0, r; + + fd = open_credentials_dir(); + if (IN_SET(fd, -ENXIO, -ENOENT)) /* Credential env var not set, or dir doesn't exist. */ + return 0; + if (fd < 0) + return log_error_errno(fd, "Failed to open credentials directory: %m"); + + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + if (r < 0) + return log_error_errno(r, "Failed to enumerate credentials: %m"); + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + _cleanup_(json_variant_unrefp) JsonVariant *identity = NULL; + struct dirent *de = *i; + const char *e; + + if (de->d_type != DT_REG) + continue; + + e = startswith(de->d_name, "home.create."); + if (!e) + continue; + + if (!valid_user_group_name(e, 0)) { + log_notice("Skipping over credential with name that is not a suitable user name: %s", de->d_name); + continue; + } + + r = json_parse_file_at( + /* f= */ NULL, + fd, + de->d_name, + /* flags= */ 0, + &identity, + /* ret_line= */ NULL, + /* ret_column= */ NULL); + if (r < 0) { + log_warning_errno(r, "Failed to parse user record in credential '%s', ignoring: %m", de->d_name); + continue; + } + + JsonVariant *un; + un = json_variant_by_key(identity, "userName"); + if (un) { + if (!json_variant_is_string(un)) { + log_warning("User record from credential '%s' contains 'userName' field of invalid type, ignoring.", de->d_name); + continue; + } + + if (!streq(json_variant_string(un), e)) { + log_warning("User record from credential '%s' contains 'userName' field (%s) that doesn't match credential name (%s), ignoring.", de->d_name, json_variant_string(un), e); + continue; + } + } else { + r = json_variant_set_field_string(&identity, "userName", e); + if (r < 0) + return log_warning_errno(r, "Failed to set userName field: %m"); + } + + log_notice("Processing user '%s' from credentials.", e); + + r = create_home_common(identity); + if (r >= 0) + n_created++; + + RET_GATHER(ret, r); + } + + return ret < 0 ? ret : n_created; +} + +static int has_regular_user(void) { + _cleanup_(userdb_iterator_freep) UserDBIterator *iterator = NULL; + int r; + + r = userdb_all(USERDB_SUPPRESS_SHADOW, &iterator); + if (r < 0) + return log_error_errno(r, "Failed to create user enumerator: %m"); + + for (;;) { + _cleanup_(user_record_unrefp) UserRecord *ur = NULL; + + r = userdb_iterator_get(iterator, &ur); + if (r == -ESRCH) + break; + if (r < 0) + return log_error_errno(r, "Failed to enumerate users: %m"); + + if (user_record_disposition(ur) == USER_REGULAR) + return true; + } + + return false; +} + +static int create_interactively(void) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *username = NULL; + int r; + + if (!arg_prompt_new_user) { + log_debug("Prompting for user creation was not requested."); + return 0; + } + + r = acquire_bus(&bus); + if (r < 0) + return r; + + (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + (void) reset_terminal_fd(STDIN_FILENO, /* switch_to_text= */ false); + + for (;;) { + username = mfree(username); + + r = ask_string(&username, + "%s Please enter user name to create (empty to skip): ", + special_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET)); + if (r < 0) + return log_error_errno(r, "Failed to query user for username: %m"); + + if (isempty(username)) { + log_info("No data entered, skipping."); + return 0; + } + + if (!valid_user_group_name(username, /* flags= */ 0)) { + log_notice("Specified user name is not a valid UNIX user name, try again: %s", username); + continue; + } + + r = userdb_by_name(username, USERDB_SUPPRESS_SHADOW, /* ret= */ NULL); + if (r == -ESRCH) + break; + if (r < 0) + return log_error_errno(r, "Failed to check if specified user '%s' already exists: %m", username); + + log_notice("Specified user '%s' exists already, try again.", username); + } + + r = json_variant_set_field_string(&arg_identity_extra, "userName", username); + if (r < 0) + return log_error_errno(r, "Failed to set userName field: %m"); + + return create_home_common(/* input= */ NULL); +} + +static int verb_firstboot(int argc, char *argv[], void *userdata) { + int r; + + /* Let's honour the systemd.firstboot kernel command line option, just like the systemd-firstboot + * tool. */ + + bool enabled; + r = proc_cmdline_get_bool("systemd.firstboot", /* flags = */ 0, &enabled); + if (r < 0) + return log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %m"); + if (r > 0 && !enabled) { + log_debug("Found systemd.firstboot=no kernel command line argument, turning off all prompts."); + arg_prompt_new_user = false; + } + + r = create_from_credentials(); + if (r < 0) + return r; + if (r > 0) /* Already created users from credentials */ + return 0; + + r = has_regular_user(); + if (r < 0) + return r; + if (r > 0) { + log_info("Regular user already present in user database, skipping user creation."); + return 0; + } + + return create_interactively(); +} + static int drop_from_identity(const char *field) { int r; @@ -2187,12 +2562,14 @@ static int help(int argc, char *argv[], void *userdata) { " deactivate-all Deactivate all active home areas\n" " rebalance Rebalance free space between home areas\n" " with USER [COMMAND…] Run shell or command with access to a home area\n" + " firstboot Run first-boot home area creation wizard\n" "\n%4$sOptions:%5$s\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not show the headers and footers\n" " --no-ask-password Do not ask for system passwords\n" + " --offline Don't update record embedded in home directory\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" " --identity=PATH Read JSON identity from file\n" @@ -2205,6 +2582,8 @@ static int help(int argc, char *argv[], void *userdata) { " -E When specified once equals -j --export-format=\n" " stripped, when specified twice equals\n" " -j --export-format=minimal\n" + " --prompt-new-user firstboot: Query user interactively for user\n" + " to create\n" "\n%4$sGeneral User Record Properties:%5$s\n" " -c --real-name=REALNAME Real name for user\n" " --realm=REALM Realm to create user in\n" @@ -2222,7 +2601,7 @@ static int help(int argc, char *argv[], void *userdata) { " --shell=PATH Shell for account\n" " --setenv=VARIABLE[=VALUE] Set an environment variable at log-in\n" " --timezone=TIMEZONE Set a time-zone\n" - " --language=LOCALE Set preferred language\n" + " --language=LOCALE Set preferred languages\n" " --ssh-authorized-keys=KEYS\n" " Specify SSH public keys\n" " --pkcs11-token-uri=URI URI to PKCS#11 security token containing\n" @@ -2239,7 +2618,13 @@ static int help(int argc, char *argv[], void *userdata) { " Whether to require user verification to unlock\n" " the account\n" " --recovery-key=BOOL Add a recovery key\n" - "\n%4$sAccount Management User Record Properties:%5$s\n" + "\n%4$sBlob Directory User Record Properties:%5$s\n" + " -b --blob=[FILENAME=]PATH\n" + " Path to a replacement blob directory, or replace\n" + " an individual files in the blob directory.\n" + " --avatar=PATH Path to user avatar picture\n" + " --login-background=PATH Path to user login background picture\n" + "\n%4$sAccount Management User Record Properties:%5$s\n" " --locked=BOOL Set locked account state\n" " --not-before=TIMESTAMP Do not allow logins before\n" " --not-after=TIMESTAMP Do not allow logins after\n" @@ -2322,6 +2707,9 @@ static int help(int argc, char *argv[], void *userdata) { " --kill-processes=BOOL Whether to kill user processes when sessions\n" " terminate\n" " --auto-login=BOOL Try to log this user in automatically\n" + " --session-launcher=LAUNCHER\n" + " Preferred session launcher file\n" + " --session-type=TYPE Preferred session type\n" "\nSee the %6$s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -2334,12 +2722,14 @@ static int help(int argc, char *argv[], void *userdata) { } static int parse_argv(int argc, char *argv[]) { + _cleanup_strv_free_ char **arg_languages = NULL; enum { ARG_VERSION = 0x100, ARG_NO_PAGER, ARG_NO_LEGEND, ARG_NO_ASK_PASSWORD, + ARG_OFFLINE, ARG_REALM, ARG_EMAIL_ADDRESS, ARG_DISK_SIZE, @@ -2397,6 +2787,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_PASSWORD_CHANGE_INACTIVE, ARG_EXPORT_FORMAT, ARG_AUTO_LOGIN, + ARG_SESSION_LAUNCHER, + ARG_SESSION_TYPE, ARG_PKCS11_TOKEN_URI, ARG_FIDO2_DEVICE, ARG_FIDO2_WITH_PIN, @@ -2412,98 +2804,108 @@ static int parse_argv(int argc, char *argv[]) { ARG_FIDO2_CRED_ALG, ARG_CAPABILITY_BOUNDING_SET, ARG_CAPABILITY_AMBIENT_SET, + ARG_PROMPT_NEW_USER, + ARG_AVATAR, + ARG_LOGIN_BACKGROUND, }; 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 }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "identity", required_argument, NULL, 'I' }, - { "real-name", required_argument, NULL, 'c' }, - { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "realm", required_argument, NULL, ARG_REALM }, - { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, - { "location", required_argument, NULL, ARG_LOCATION }, - { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT }, - { "icon-name", required_argument, NULL, ARG_ICON_NAME }, - { "home-dir", required_argument, NULL, 'd' }, /* Compatible with useradd(8) */ - { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ - { "member-of", required_argument, NULL, 'G' }, - { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ - { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ - { "setenv", required_argument, NULL, ARG_SETENV }, - { "timezone", required_argument, NULL, ARG_TIMEZONE }, - { "language", required_argument, NULL, ARG_LANGUAGE }, - { "locked", required_argument, NULL, ARG_LOCKED }, - { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, - { "not-after", required_argument, NULL, ARG_NOT_AFTER }, - { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ - { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, - { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, - { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, - { "umask", required_argument, NULL, ARG_UMASK }, - { "nice", required_argument, NULL, ARG_NICE }, - { "rlimit", required_argument, NULL, ARG_RLIMIT }, - { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, - { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, - { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, - { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, - { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, - { "storage", required_argument, NULL, ARG_STORAGE }, - { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, - { "fs-type", required_argument, NULL, ARG_FS_TYPE }, - { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, - { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, - { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, - { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, - { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, - { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, - { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, - { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, - { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, - { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, - { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, - { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, - { "nosuid", required_argument, NULL, ARG_NOSUID }, - { "nodev", required_argument, NULL, ARG_NODEV }, - { "noexec", required_argument, NULL, ARG_NOEXEC }, - { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, - { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, - { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, - { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, - { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, - { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, - { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, - { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, - { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, - { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, - { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, - { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, - { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, - { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, - { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, - { "json", required_argument, NULL, ARG_JSON }, - { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, - { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, - { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, - { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, - { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, - { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, - { "fido2-with-user-verification",required_argument, NULL, ARG_FIDO2_WITH_UV }, - { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, - { "and-resize", required_argument, NULL, ARG_AND_RESIZE }, - { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD }, - { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, - { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, - { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, - { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, - { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, - { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, + { "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 }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "offline", no_argument, NULL, ARG_OFFLINE }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "identity", required_argument, NULL, 'I' }, + { "real-name", required_argument, NULL, 'c' }, + { "comment", required_argument, NULL, 'c' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "realm", required_argument, NULL, ARG_REALM }, + { "email-address", required_argument, NULL, ARG_EMAIL_ADDRESS }, + { "location", required_argument, NULL, ARG_LOCATION }, + { "password-hint", required_argument, NULL, ARG_PASSWORD_HINT }, + { "icon-name", required_argument, NULL, ARG_ICON_NAME }, + { "home-dir", required_argument, NULL, 'd' }, /* Compatible with useradd(8) */ + { "uid", required_argument, NULL, 'u' }, /* Compatible with useradd(8) */ + { "member-of", required_argument, NULL, 'G' }, + { "groups", required_argument, NULL, 'G' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "skel", required_argument, NULL, 'k' }, /* Compatible with useradd(8) */ + { "shell", required_argument, NULL, 's' }, /* Compatible with useradd(8) */ + { "setenv", required_argument, NULL, ARG_SETENV }, + { "timezone", required_argument, NULL, ARG_TIMEZONE }, + { "language", required_argument, NULL, ARG_LANGUAGE }, + { "locked", required_argument, NULL, ARG_LOCKED }, + { "not-before", required_argument, NULL, ARG_NOT_BEFORE }, + { "not-after", required_argument, NULL, ARG_NOT_AFTER }, + { "expiredate", required_argument, NULL, 'e' }, /* Compat alias to keep thing in sync with useradd(8) */ + { "ssh-authorized-keys", required_argument, NULL, ARG_SSH_AUTHORIZED_KEYS }, + { "disk-size", required_argument, NULL, ARG_DISK_SIZE }, + { "access-mode", required_argument, NULL, ARG_ACCESS_MODE }, + { "umask", required_argument, NULL, ARG_UMASK }, + { "nice", required_argument, NULL, ARG_NICE }, + { "rlimit", required_argument, NULL, ARG_RLIMIT }, + { "tasks-max", required_argument, NULL, ARG_TASKS_MAX }, + { "memory-high", required_argument, NULL, ARG_MEMORY_HIGH }, + { "memory-max", required_argument, NULL, ARG_MEMORY_MAX }, + { "cpu-weight", required_argument, NULL, ARG_CPU_WEIGHT }, + { "io-weight", required_argument, NULL, ARG_IO_WEIGHT }, + { "storage", required_argument, NULL, ARG_STORAGE }, + { "image-path", required_argument, NULL, ARG_IMAGE_PATH }, + { "fs-type", required_argument, NULL, ARG_FS_TYPE }, + { "luks-discard", required_argument, NULL, ARG_LUKS_DISCARD }, + { "luks-offline-discard", required_argument, NULL, ARG_LUKS_OFFLINE_DISCARD }, + { "luks-cipher", required_argument, NULL, ARG_LUKS_CIPHER }, + { "luks-cipher-mode", required_argument, NULL, ARG_LUKS_CIPHER_MODE }, + { "luks-volume-key-size", required_argument, NULL, ARG_LUKS_VOLUME_KEY_SIZE }, + { "luks-pbkdf-type", required_argument, NULL, ARG_LUKS_PBKDF_TYPE }, + { "luks-pbkdf-hash-algorithm", required_argument, NULL, ARG_LUKS_PBKDF_HASH_ALGORITHM }, + { "luks-pbkdf-force-iterations", required_argument, NULL, ARG_LUKS_PBKDF_FORCE_ITERATIONS }, + { "luks-pbkdf-time-cost", required_argument, NULL, ARG_LUKS_PBKDF_TIME_COST }, + { "luks-pbkdf-memory-cost", required_argument, NULL, ARG_LUKS_PBKDF_MEMORY_COST }, + { "luks-pbkdf-parallel-threads", required_argument, NULL, ARG_LUKS_PBKDF_PARALLEL_THREADS }, + { "luks-sector-size", required_argument, NULL, ARG_LUKS_SECTOR_SIZE }, + { "nosuid", required_argument, NULL, ARG_NOSUID }, + { "nodev", required_argument, NULL, ARG_NODEV }, + { "noexec", required_argument, NULL, ARG_NOEXEC }, + { "cifs-user-name", required_argument, NULL, ARG_CIFS_USER_NAME }, + { "cifs-domain", required_argument, NULL, ARG_CIFS_DOMAIN }, + { "cifs-service", required_argument, NULL, ARG_CIFS_SERVICE }, + { "cifs-extra-mount-options", required_argument, NULL, ARG_CIFS_EXTRA_MOUNT_OPTIONS }, + { "rate-limit-interval", required_argument, NULL, ARG_RATE_LIMIT_INTERVAL }, + { "rate-limit-burst", required_argument, NULL, ARG_RATE_LIMIT_BURST }, + { "stop-delay", required_argument, NULL, ARG_STOP_DELAY }, + { "kill-processes", required_argument, NULL, ARG_KILL_PROCESSES }, + { "enforce-password-policy", required_argument, NULL, ARG_ENFORCE_PASSWORD_POLICY }, + { "password-change-now", required_argument, NULL, ARG_PASSWORD_CHANGE_NOW }, + { "password-change-min", required_argument, NULL, ARG_PASSWORD_CHANGE_MIN }, + { "password-change-max", required_argument, NULL, ARG_PASSWORD_CHANGE_MAX }, + { "password-change-warn", required_argument, NULL, ARG_PASSWORD_CHANGE_WARN }, + { "password-change-inactive", required_argument, NULL, ARG_PASSWORD_CHANGE_INACTIVE }, + { "auto-login", required_argument, NULL, ARG_AUTO_LOGIN }, + { "session-launcher", required_argument, NULL, ARG_SESSION_LAUNCHER, }, + { "session-type", required_argument, NULL, ARG_SESSION_TYPE, }, + { "json", required_argument, NULL, ARG_JSON }, + { "export-format", required_argument, NULL, ARG_EXPORT_FORMAT }, + { "pkcs11-token-uri", required_argument, NULL, ARG_PKCS11_TOKEN_URI }, + { "fido2-credential-algorithm", required_argument, NULL, ARG_FIDO2_CRED_ALG }, + { "fido2-device", required_argument, NULL, ARG_FIDO2_DEVICE }, + { "fido2-with-client-pin", required_argument, NULL, ARG_FIDO2_WITH_PIN }, + { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, + { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, + { "recovery-key", required_argument, NULL, ARG_RECOVERY_KEY }, + { "and-resize", required_argument, NULL, ARG_AND_RESIZE }, + { "and-change-password", required_argument, NULL, ARG_AND_CHANGE_PASSWORD }, + { "drop-caches", required_argument, NULL, ARG_DROP_CACHES }, + { "luks-extra-mount-options", required_argument, NULL, ARG_LUKS_EXTRA_MOUNT_OPTIONS }, + { "auto-resize-mode", required_argument, NULL, ARG_AUTO_RESIZE_MODE }, + { "rebalance-weight", required_argument, NULL, ARG_REBALANCE_WEIGHT }, + { "capability-bounding-set", required_argument, NULL, ARG_CAPABILITY_BOUNDING_SET }, + { "capability-ambient-set", required_argument, NULL, ARG_CAPABILITY_AMBIENT_SET }, + { "prompt-new-user", no_argument, NULL, ARG_PROMPT_NEW_USER }, + { "blob", required_argument, NULL, 'b' }, + { "avatar", required_argument, NULL, ARG_AVATAR }, + { "login-background", required_argument, NULL, ARG_LOGIN_BACKGROUND }, {} }; @@ -2515,7 +2917,7 @@ static int parse_argv(int argc, char *argv[]) { for (;;) { int c; - c = getopt_long(argc, argv, "hH:M:I:c:d:u:k:s:e:G:jPE", options, NULL); + c = getopt_long(argc, argv, "hH:M:I:c:d:u:G:k:s:e:b:jPE", options, NULL); if (c < 0) break; @@ -2539,6 +2941,10 @@ static int parse_argv(int argc, char *argv[]) { arg_ask_password = false; break; + case ARG_OFFLINE: + arg_offline = true; + break; + case 'H': arg_transport = BUS_TRANSPORT_REMOTE; arg_host = optarg; @@ -2609,7 +3015,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to determine whether realm '%s' is a valid DNS domain: %m", optarg); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain: %m", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Realm '%s' is not a valid DNS domain.", optarg); r = json_variant_set_field_string(&arg_identity_extra, "realm", optarg); if (r < 0) @@ -2622,7 +3028,9 @@ static int parse_argv(int argc, char *argv[]) { case ARG_CIFS_USER_NAME: case ARG_CIFS_DOMAIN: case ARG_CIFS_EXTRA_MOUNT_OPTIONS: - case ARG_LUKS_EXTRA_MOUNT_OPTIONS: { + case ARG_LUKS_EXTRA_MOUNT_OPTIONS: + case ARG_SESSION_LAUNCHER: + case ARG_SESSION_TYPE: { const char *field = c == ARG_EMAIL_ADDRESS ? "emailAddress" : @@ -2632,6 +3040,8 @@ static int parse_argv(int argc, char *argv[]) { c == ARG_CIFS_DOMAIN ? "cifsDomain" : c == ARG_CIFS_EXTRA_MOUNT_OPTIONS ? "cifsExtraMountOptions" : c == ARG_LUKS_EXTRA_MOUNT_OPTIONS ? "luksExtraMountOptions" : + c == ARG_SESSION_LAUNCHER ? "preferredSessionLauncher" : + c == ARG_SESSION_TYPE ? "preferredSessionType" : NULL; assert(field); @@ -2754,15 +3164,15 @@ static int parse_argv(int argc, char *argv[]) { r = rlimit_parse(l, eq + 1, &rl); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse resource limit value: %s", eq + 1); + return log_error_errno(r, "Failed to parse resource limit value: %s", eq + 1); r = rl.rlim_cur == RLIM_INFINITY ? json_variant_new_null(&jcur) : json_variant_new_unsigned(&jcur, rl.rlim_cur); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to allocate current integer: %m"); + return log_error_errno(r, "Failed to allocate current integer: %m"); r = rl.rlim_max == RLIM_INFINITY ? json_variant_new_null(&jmax) : json_variant_new_unsigned(&jmax, rl.rlim_max); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to allocate maximum integer: %m"); + return log_error_errno(r, "Failed to allocate maximum integer: %m"); t = strjoin("RLIMIT_", rlimit_to_string(l)); if (!t) @@ -2906,26 +3316,46 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_LANGUAGE: - if (isempty(optarg)) { - r = drop_from_identity("language"); + case ARG_LANGUAGE: { + const char *p = optarg; + + if (isempty(p)) { + r = drop_from_identity("preferredLanguage"); + if (r < 0) + return r; + + r = drop_from_identity("additionalLanguages"); if (r < 0) return r; + arg_languages = strv_free(arg_languages); break; } - if (!locale_is_valid(optarg)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", optarg); + for (;;) { + _cleanup_free_ char *word = NULL; - if (locale_is_installed(optarg) <= 0) - log_warning("Locale '%s' is not installed, accepting anyway.", optarg); + r = extract_first_word(&p, &word, ",:", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse locale list: %m"); + if (r == 0) + break; - r = json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", optarg); - if (r < 0) - return log_error_errno(r, "Failed to set preferredLanguage field: %m"); + if (!locale_is_valid(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", word); + + if (locale_is_installed(word) <= 0) + log_warning("Locale '%s' is not installed, accepting anyway.", word); + + r = strv_consume(&arg_languages, TAKE_PTR(word)); + if (r < 0) + return log_oom(); + + strv_uniq(arg_languages); + } break; + } case ARG_NOSUID: case ARG_NODEV: @@ -3788,6 +4218,82 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_PROMPT_NEW_USER: + arg_prompt_new_user = true; + break; + + case 'b': + case ARG_AVATAR: + case ARG_LOGIN_BACKGROUND: { + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *path = NULL, *filename = NULL; + + if (c == 'b') { + char *eq; + + if (isempty(optarg)) { /* --blob= deletes everything, including existing blob dirs */ + hashmap_clear(arg_blob_files); + arg_blob_dir = mfree(arg_blob_dir); + arg_blob_clear = true; + break; + } + + eq = strrchr(optarg, '='); + if (!eq) { /* --blob=/some/path replaces the blob dir */ + r = parse_path_argument(optarg, false, &arg_blob_dir); + if (r < 0) + return log_error_errno(r, "Failed to parse path %s: %m", optarg); + break; + } + + /* --blob=filename=/some/path replaces the file "filename" with /some/path */ + filename = strndup(optarg, eq - optarg); + if (!filename) + return log_oom(); + + if (isempty(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse blob file assignment: %s", optarg); + if (!suitable_blob_filename(filename)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid blob filename: %s", filename); + + r = parse_path_argument(eq + 1, false, &path); + if (r < 0) + return log_error_errno(r, "Failed to parse path %s: %m", eq + 1); + } else { + const char *well_known_filename = + c == ARG_AVATAR ? "avatar" : + c == ARG_LOGIN_BACKGROUND ? "login-background" : + NULL; + assert(well_known_filename); + + filename = strdup(well_known_filename); + if (!filename) + return log_oom(); + + r = parse_path_argument(optarg, false, &path); + if (r < 0) + return log_error_errno(r, "Failed to parse path %s: %m", optarg); + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + + if (fd_verify_regular(fd) < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided blob is not a regular file: %s", path); + } else + fd = -EBADF; /* Delete the file */ + + r = hashmap_ensure_put(&arg_blob_files, &blob_fd_hash_ops, filename, FD_TO_PTR(fd)); + if (r < 0) + return log_error_errno(r, "Failed to map %s to %s in blob directory: %m", path, filename); + TAKE_PTR(filename); /* hashmap takes ownership */ + TAKE_FD(fd); + + break; + } + case '?': return -EINVAL; @@ -3802,6 +4308,25 @@ static int parse_argv(int argc, char *argv[]) { if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX) arg_and_resize = true; + if (!strv_isempty(arg_languages)) { + char **additional; + + r = json_variant_set_field_string(&arg_identity_extra, "preferredLanguage", arg_languages[0]); + if (r < 0) + return log_error_errno(r, "Failed to update preferred language: %m"); + + additional = strv_skip(arg_languages, 1); + if (!strv_isempty(additional)) { + r = json_variant_set_field_strv(&arg_identity_extra, "additionalLanguages", additional); + if (r < 0) + return log_error_errno(r, "Failed to update additional language list: %m"); + } else { + r = drop_from_identity("additionalLanguages"); + if (r < 0) + return r; + } + } + return 1; } @@ -3835,6 +4360,197 @@ static int redirect_bus_mgr(void) { return 0; } +static bool is_fallback_shell(const char *p) { + const char *q; + + if (!p) + return false; + + if (p[0] == '-') { + /* Skip over login shell dash */ + p++; + + if (streq(p, "ystemd-home-fallback-shell")) /* maybe the dash was used to override the binary name? */ + return true; + } + + q = strrchr(p, '/'); /* Skip over path */ + if (q) + p = q + 1; + + return streq(p, "systemd-home-fallback-shell"); +} + +static int fallback_shell(int argc, char *argv[]) { + _cleanup_(user_record_unrefp) UserRecord *secret = NULL, *hr = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *argv0 = NULL; + const char *json, *hd, *shell; + int r, incomplete; + + /* So here's the deal: if users log into a system via ssh, and their homed-managed home directory + * wasn't activated yet, SSH will permit the access but the home directory isn't actually available + * yet. SSH doesn't allow us to ask authentication questions from the PAM session stack, and doesn't + * run the PAM authentication stack (because it authenticates via its own key management, after + * all). So here's our way to support this: homectl can be invoked as a multi-call binary under the + * name "systemd-home-fallback-shell". If so, it will chainload a login shell, but first try to + * unlock the home directory of the user it is invoked as. systemd-homed will then override the shell + * listed in user records whose home directory is not activated yet with this pseudo-shell. Net + * effect: one SSH auth succeeds this pseudo shell gets invoked, which will unlock the homedir + * (possibly asking for a passphrase) and then chainload the regular shell. Once the login is + * complete the user record will look like any other. */ + + r = acquire_bus(&bus); + if (r < 0) + return r; + + for (unsigned n_tries = 0;; n_tries++) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (n_tries >= 5) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to activate home dir, even after %u tries.", n_tries); + + /* Let's start by checking if this all is even necessary, i.e. if the useFallback boolean field is actually set. */ + r = bus_call_method(bus, bus_mgr, "GetUserRecordByName", &error, &reply, "s", NULL); /* empty user string means: our calling user */ + if (r < 0) + return log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL); + if (r < 0) + return bus_log_parse_error(r); + + r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse JSON identity: %m"); + + hr = user_record_new(); + if (!hr) + return log_oom(); + + r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_LOG|USER_RECORD_PERMISSIVE); + if (r < 0) + return r; + + if (!hr->use_fallback) /* Nice! We are done, fallback logic not necessary */ + break; + + if (!secret) { + r = acquire_passed_secrets(hr->user_name, &secret); + if (r < 0) + return r; + } + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_mgr, "ActivateHomeIfReferenced"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", NULL); /* empty user string means: our calling user */ + if (r < 0) + return bus_log_create_error(r); + + r = bus_message_append_secret(m, secret); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_REFERENCED)) + return log_error_errno(r, "Called without reference on home taken, can't operate."); + + r = handle_generic_user_record_error(hr->user_name, secret, &error, r, false); + if (r < 0) + return r; + + sd_bus_error_free(&error); + } else + break; + } + + /* Try again */ + hr = user_record_unref(hr); + } + + incomplete = getenv_bool("XDG_SESSION_INCOMPLETE"); /* pam_systemd_home reports this state via an environment variable to us. */ + if (incomplete < 0 && incomplete != -ENXIO) + return log_error_errno(incomplete, "Failed to parse $XDG_SESSION_INCOMPLETE environment variable: %m"); + if (incomplete > 0) { + /* We are still in an "incomplete" session here. Now upgrade it to a full one. This will make logind + * start the user@.service instance for us. */ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1/session/self", + "org.freedesktop.login1.Session", + "SetClass", + &error, + /* ret_reply= */ NULL, + "s", + "user"); + if (r < 0) + return log_error_errno(r, "Failed to upgrade session: %s", bus_error_message(&error, r)); + + if (setenv("XDG_SESSION_CLASS", "user", /* overwrite= */ true) < 0) /* Update the XDG_SESSION_CLASS environment variable to match the above */ + return log_error_errno(errno, "Failed to set $XDG_SESSION_CLASS: %m"); + + if (unsetenv("XDG_SESSION_INCOMPLETE") < 0) /* Unset the 'incomplete' env var */ + return log_error_errno(errno, "Failed to unset $XDG_SESSION_INCOMPLETE: %m"); + } + + /* We are going to invoke execv() soon. Let's be extra accurate and flush/close our bus connection + * first, just to make sure anything queued is flushed out (though there shouldn't be anything) */ + bus = sd_bus_flush_close_unref(bus); + + assert(!hr->use_fallback); + assert_se(shell = user_record_shell(hr)); + assert_se(hd = user_record_home_directory(hr)); + + /* Extra protection: avoid loops */ + if (is_fallback_shell(shell)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Primary shell of '%s' is fallback shell, refusing loop.", hr->user_name); + + if (chdir(hd) < 0) + return log_error_errno(errno, "Failed to change directory to home directory '%s': %m", hd); + + if (setenv("SHELL", shell, /* overwrite= */ true) < 0) + return log_error_errno(errno, "Failed to set $SHELL: %m"); + + if (setenv("HOME", hd, /* overwrite= */ true) < 0) + return log_error_errno(errno, "Failed to set $HOME: %m"); + + /* Paranoia: in case the client passed some passwords to us to help us unlock, unlock things now */ + FOREACH_STRING(ue, "PASSWORD", "NEWPASSWORD", "PIN") + if (unsetenv(ue) < 0) + return log_error_errno(errno, "Failed to unset $%s: %m", ue); + + r = path_extract_filename(shell, &argv0); + if (r < 0) + return log_error_errno(r, "Unable to extract file name from '%s': %m", shell); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Shell '%s' is a path to a directory, refusing.", shell); + + /* Invoke this as login shell, by setting argv[0][0] to '-' (unless we ourselves weren't called as login shell) */ + if (!argv || isempty(argv[0]) || argv[0][0] == '-') + argv0[0] = '-'; + + l = strv_new(argv0); + if (!l) + return log_oom(); + + if (strv_extend_strv(&l, strv_skip(argv, 1), /* filter_duplicates= */ false) < 0) + return log_oom(); + + execv(shell, l); + return log_error_errno(errno, "Failed to execute shell '%s': %m", shell); +} + static int run(int argc, char *argv[]) { static const Verb verbs[] = { { "help", VERB_ANY, VERB_ANY, 0, help }, @@ -3854,6 +4570,7 @@ static int run(int argc, char *argv[]) { { "lock-all", VERB_ANY, 1, 0, lock_all_homes }, { "deactivate-all", VERB_ANY, 1, 0, deactivate_all_homes }, { "rebalance", VERB_ANY, 1, 0, rebalance }, + { "firstboot", VERB_ANY, 1, 0, verb_firstboot }, {} }; @@ -3865,6 +4582,9 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + if (is_fallback_shell(argv[0])) + return fallback_shell(argc, argv); + r = parse_argv(argc, argv); if (r <= 0) return r; diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c index 24b421a..a6f26fe 100644 --- a/src/home/homed-bus.c +++ b/src/home/homed-bus.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "fd-util.h" +#include "home-util.h" #include "homed-bus.h" +#include "stat-util.h" #include "strv.h" int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) { @@ -64,3 +67,70 @@ int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, U *ret = TAKE_PTR(hr); return 0; } + +int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error) { + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + int r; + + assert(m); + assert(ret); + + /* We want to differentiate between blobs being NULL (not passed at all) + * and empty (passed from dbus, but it was empty) */ + r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, 'a', "{sh}"); + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *filename = NULL; + _cleanup_close_ int fd = -EBADF; + const char *_filename = NULL; + int _fd; + + r = sd_bus_message_read(m, "{sh}", &_filename, &_fd); + if (r < 0) + return r; + if (r == 0) + break; + + filename = strdup(_filename); + if (!filename) + return -ENOMEM; + + fd = fcntl(_fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -errno; + + r = suitable_blob_filename(filename); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid blob directory filename: %s", filename); + + r = fd_verify_regular(fd); + if (r < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "FD for '%s' is not a regular file", filename); + + r = fd_verify_safe_flags(fd); + if (r == -EREMOTEIO) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "FD for '%s' has unexpected flags set", filename); + if (r < 0) + return r; + + r = hashmap_put(blobs, filename, FD_TO_PTR(fd)); + if (r < 0) + return r; + TAKE_PTR(filename); /* Ownership transferred to hashmap */ + TAKE_FD(fd); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + *ret = TAKE_PTR(blobs); + return 0; +} diff --git a/src/home/homed-bus.h b/src/home/homed-bus.h index 977679b..0660a59 100644 --- a/src/home/homed-bus.h +++ b/src/home/homed-bus.h @@ -3,8 +3,10 @@ #include "sd-bus.h" -#include "user-record.h" +#include "hashmap.h" #include "json.h" +#include "user-record.h" int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error); int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error); +int bus_message_read_blobs(sd_bus_message *m, Hashmap **ret, sd_bus_error *error); diff --git a/src/home/homed-conf.c b/src/home/homed-conf.c index ffa4bb3..3f74096 100644 --- a/src/home/homed-conf.c +++ b/src/home/homed-conf.c @@ -9,9 +9,12 @@ int manager_parse_config_file(Manager *m) { assert(m); - return config_parse_config_file("homed.conf", "Home\0", - config_item_perf_lookup, homed_gperf_lookup, - CONFIG_PARSE_WARN, m); + return config_parse_standard_file_with_dropins( + "systemd/homed.conf", + "Home\0", + config_item_perf_lookup, homed_gperf_lookup, + CONFIG_PARSE_WARN, + m); } DEFINE_CONFIG_PARSE_ENUM(config_parse_default_storage, user_storage, UserStorage, "Failed to parse default storage setting"); diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c index a47f4d8..23578fe 100644 --- a/src/home/homed-home-bus.c +++ b/src/home/homed-home-bus.c @@ -5,6 +5,8 @@ #include "bus-common-errors.h" #include "bus-polkit.h" #include "fd-util.h" +#include "format-util.h" +#include "home-util.h" #include "homed-bus.h" #include "homed-home-bus.h" #include "homed-home.h" @@ -74,6 +76,34 @@ int bus_home_client_is_trusted(Home *h, sd_bus_message *message) { return euid == 0 || h->uid == euid; } +static int home_verify_polkit_async( + Home *h, + sd_bus_message *message, + const char *action, + uid_t good_uid, + sd_bus_error *error) { + + assert(h); + assert(message); + assert(action); + assert(error); + + const char *details[] = { + "uid", FORMAT_UID(h->uid), + "username", h->user_name, + NULL + }; + + return bus_verify_polkit_async_full( + message, + action, + details, + good_uid, + /* flags= */ 0, + &h->manager->polkit_registry, + error); +} + int bus_home_get_record_json( Home *h, sd_bus_message *message, @@ -144,15 +174,31 @@ int bus_home_method_activate( _cleanup_(user_record_unrefp) UserRecord *secret = NULL; Home *h = ASSERT_PTR(userdata); + bool if_referenced; int r; assert(message); + if_referenced = endswith(sd_bus_message_get_member(message), "IfReferenced"); + + r = bus_verify_polkit_async_full( + message, + "org.freedesktop.home1.activate-home", + /* details= */ NULL, + h->uid, + /* flags= */ 0, + &h->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + r = bus_message_read_secret(message, &secret, error); if (r < 0) return r; - r = home_activate(h, secret, error); + r = home_activate(h, if_referenced, secret, error); if (r < 0) return r; @@ -201,14 +247,11 @@ int bus_home_method_unregister( assert(message); - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.remove-home", - NULL, - true, UID_INVALID, - &h->manager->polkit_registry, error); if (r < 0) return r; @@ -241,21 +284,18 @@ int bus_home_method_realize( if (r < 0) return r; - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.create-home", - NULL, - true, UID_INVALID, - &h->manager->polkit_registry, error); if (r < 0) return r; if (r == 0) return 1; /* Will call us back */ - r = home_create(h, secret, error); + r = home_create(h, secret, NULL, 0, error); if (r < 0) return r; @@ -281,14 +321,11 @@ int bus_home_method_remove( assert(message); - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.remove-home", - NULL, - true, UID_INVALID, - &h->manager->polkit_registry, error); if (r < 0) return r; @@ -354,14 +391,11 @@ int bus_home_method_authenticate( if (r < 0) return r; - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.authenticate-home", - NULL, - true, h->uid, - &h->manager->polkit_registry, error); if (r < 0) return r; @@ -382,7 +416,13 @@ int bus_home_method_authenticate( return 1; } -int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) { +int bus_home_update_record( + Home *h, + sd_bus_message *message, + UserRecord *hr, + Hashmap *blobs, + uint64_t flags, + sd_bus_error *error) { int r; assert(h); @@ -393,21 +433,21 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord * if (r < 0) return r; - r = bus_verify_polkit_async( + if ((flags & ~SD_HOMED_UPDATE_FLAGS_ALL) != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided."); + + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.update-home", - NULL, - true, UID_INVALID, - &h->manager->polkit_registry, error); if (r < 0) return r; if (r == 0) return 1; /* Will call us back */ - r = home_update(h, hr, error); + r = home_update(h, hr, blobs, flags, error); if (r < 0) return r; @@ -418,6 +458,8 @@ int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord * if (r < 0) return r; + h->current_operation->call_flags = flags; + return 1; } @@ -427,16 +469,28 @@ int bus_home_method_update( sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + uint64_t flags = 0; Home *h = ASSERT_PTR(userdata); int r; assert(message); - r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_REQUIRE_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error); + r = bus_message_read_home_record(message, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_PERMISSIVE, &hr, error); if (r < 0) return r; - return bus_home_method_update_record(h, message, hr, error); + if (endswith(sd_bus_message_get_member(message), "Ex")) { + r = bus_message_read_blobs(message, &blobs, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + } + + return bus_home_update_record(h, message, hr, blobs, flags, error); } int bus_home_method_resize( @@ -459,21 +513,18 @@ int bus_home_method_resize( if (r < 0) return r; - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.resize-home", - NULL, - true, UID_INVALID, - &h->manager->polkit_registry, error); if (r < 0) return r; if (r == 0) return 1; /* Will call us back */ - r = home_resize(h, sz, secret, /* automatic= */ false, error); + r = home_resize(h, sz, secret, error); if (r < 0) return r; @@ -506,14 +557,11 @@ int bus_home_method_change_password( if (r < 0) return r; - r = bus_verify_polkit_async( + r = home_verify_polkit_async( + h, message, - CAP_SYS_ADMIN, "org.freedesktop.home1.passwd-home", - NULL, - true, h->uid, - &h->manager->polkit_registry, error); if (r < 0) return r; @@ -637,30 +685,38 @@ int bus_home_method_ref( _cleanup_close_ int fd = -EBADF; Home *h = ASSERT_PTR(userdata); - HomeState state; int please_suspend, r; + bool unrestricted; assert(message); + /* In unrestricted mode we'll add a reference to the home even if it's not active */ + unrestricted = strstr(sd_bus_message_get_member(message), "Unrestricted"); + r = sd_bus_message_read(message, "b", &please_suspend); if (r < 0) return r; - state = home_get_state(h); - switch (state) { - case HOME_ABSENT: - return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); - case HOME_UNFIXATED: - case HOME_INACTIVE: - case HOME_DIRTY: - return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name); - case HOME_LOCKED: - return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); - default: - if (HOME_STATE_IS_ACTIVE(state)) - break; + if (!unrestricted) { + HomeState state; + + state = home_get_state(h); - return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); + switch (state) { + case HOME_ABSENT: + return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); + case HOME_UNFIXATED: + case HOME_INACTIVE: + case HOME_DIRTY: + return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s not active.", h->user_name); + case HOME_LOCKED: + return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); + default: + if (HOME_STATE_IS_ACTIVE(state)) + break; + + return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); + } } fd = home_create_fifo(h, please_suspend); @@ -784,7 +840,12 @@ const sd_bus_vtable home_vtable[] = { SD_BUS_ARGS("s", secret), SD_BUS_NO_RESULT, bus_home_method_activate, - SD_BUS_VTABLE_SENSITIVE), + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD_WITH_ARGS("ActivateIfReferenced", + SD_BUS_ARGS("s", secret), + SD_BUS_NO_RESULT, + bus_home_method_activate, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), SD_BUS_METHOD("Deactivate", NULL, NULL, bus_home_method_deactivate, 0), SD_BUS_METHOD("Unregister", NULL, NULL, bus_home_method_unregister, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("Realize", @@ -809,6 +870,11 @@ const sd_bus_vtable home_vtable[] = { SD_BUS_NO_RESULT, bus_home_method_update, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD_WITH_ARGS("UpdateEx", + SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags), + SD_BUS_NO_RESULT, + bus_home_method_update, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), SD_BUS_METHOD_WITH_ARGS("Resize", SD_BUS_ARGS("t", size, "s", secret), SD_BUS_NO_RESULT, @@ -835,6 +901,11 @@ const sd_bus_vtable home_vtable[] = { SD_BUS_RESULT("h", send_fd), bus_home_method_ref, 0), + SD_BUS_METHOD_WITH_ARGS("RefUnrestricted", + SD_BUS_ARGS("b", please_suspend), + SD_BUS_RESULT("h", send_fd), + bus_home_method_ref, + 0), SD_BUS_METHOD("Release", NULL, NULL, bus_home_method_release, 0), SD_BUS_VTABLE_END }; diff --git a/src/home/homed-home-bus.h b/src/home/homed-home-bus.h index 5522178..1644bc8 100644 --- a/src/home/homed-home-bus.h +++ b/src/home/homed-home-bus.h @@ -17,7 +17,6 @@ int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error int bus_home_method_fixate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_home_method_authenticate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_home_method_update(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_home_method_update_record(Home *home, sd_bus_message *message, UserRecord *hr, sd_bus_error *error); int bus_home_method_resize(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_home_method_change_password(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_home_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error); @@ -26,6 +25,8 @@ int bus_home_method_acquire(sd_bus_message *message, void *userdata, sd_bus_erro int bus_home_method_ref(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_home_method_release(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_home_update_record(Home *home, sd_bus_message *message, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error); + extern const BusObjectImplementation home_object; int bus_home_path(Home *h, char **ret); diff --git a/src/home/homed-home.c b/src/home/homed-home.c index 37b3270..757881c 100644 --- a/src/home/homed-home.c +++ b/src/home/homed-home.c @@ -10,6 +10,7 @@ #include "blockdev-util.h" #include "btrfs-util.h" +#include "build-path.h" #include "bus-common-errors.h" #include "bus-locator.h" #include "data-fd-util.h" @@ -33,12 +34,13 @@ #include "process-util.h" #include "quota-util.h" #include "resize-fs.h" +#include "rm-rf.h" #include "set.h" #include "signal-util.h" #include "stat-util.h" #include "string-table.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-record-password-quality.h" #include "user-record-sign.h" #include "user-record-util.h" @@ -54,7 +56,13 @@ assert_cc(HOME_UID_MIN <= HOME_UID_MAX); assert_cc(HOME_USERS_MAX <= (HOME_UID_MAX - HOME_UID_MIN + 1)); -static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret); +static int home_start_work( + Home *h, + const char *verb, + UserRecord *hr, + UserRecord *secret, + Hashmap *blobs, + uint64_t flags); DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref); @@ -96,7 +104,7 @@ static int suitable_home_record(UserRecord *hr) { int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { _cleanup_(home_freep) Home *home = NULL; - _cleanup_free_ char *nm = NULL, *ns = NULL; + _cleanup_free_ char *nm = NULL, *ns = NULL, *blob = NULL; int r; assert(m); @@ -162,6 +170,13 @@ int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret) { if (r < 0) return r; + blob = path_join(home_system_blob_dir(), hr->user_name); + if (!blob) + return -ENOMEM; + r = mkdir_safe(blob, 0755, 0, 0, MKDIR_IGNORE_EXISTING); + if (r < 0) + log_warning_errno(r, "Failed to create blob dir for user '%s': %m", home->user_name); + (void) bus_manager_emit_auto_login_changed(m); (void) bus_home_emit_change(home); (void) manager_schedule_rebalance(m, /* immediately= */ false); @@ -322,7 +337,9 @@ int home_save_record(Home *h) { } int home_unlink_record(Home *h) { + _cleanup_free_ char *blob = NULL; const char *fn; + int r; assert(h); @@ -334,6 +351,13 @@ int home_unlink_record(Home *h) { if (unlink(fn) < 0 && errno != ENOENT) return -errno; + blob = path_join(home_system_blob_dir(), h->user_name); + if (!blob) + return -ENOMEM; + r = rm_rf(blob, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK); + if (r < 0) + return r; + return 0; } @@ -402,11 +426,9 @@ static void home_maybe_stop_retry_deactivate(Home *h, HomeState state) { /* Free the deactivation retry event source if we won't need it anymore. Specifically, we'll free the * event source whenever the home directory is already deactivated (and we thus where successful) or * if we start executing an operation that indicates that the home directory is going to be used or - * operated on again. Also, if the home is referenced again stop the timer */ + * operated on again. Also, if the home is referenced again stop the timer. */ - if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) && - !h->ref_event_source_dont_suspend && - !h->ref_event_source_please_suspend) + if (HOME_STATE_MAY_RETRY_DEACTIVATE(state) && !home_is_referenced(h)) return; h->retry_deactivate_event_source = sd_event_source_disable_unref(h->retry_deactivate_event_source); @@ -454,7 +476,7 @@ static void home_start_retry_deactivate(Home *h) { return; /* If the home directory is being used now don't start the timer */ - if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend) + if (home_is_referenced(h)) return; r = sd_event_add_time_relative( @@ -650,11 +672,17 @@ static int convert_worker_errno(Home *h, int e, sd_bus_error *error) { return 0; } -static void home_count_bad_authentication(Home *h, bool save) { +static void home_count_bad_authentication(Home *h, int error, bool save) { int r; assert(h); + if (!IN_SET(error, + -ENOKEY, /* Password incorrect */ + -EBADSLT, /* Password incorrect and no token */ + -EREMOTEIO)) /* Recovery key incorrect */ + return; + r = user_record_bad_authentication(h->record); if (r < 0) { log_warning_errno(r, "Failed to increase bad authentication counter, ignoring: %m"); @@ -680,8 +708,7 @@ static void home_fixate_finish(Home *h, int ret, UserRecord *hr) { secret = TAKE_PTR(h->secret); /* Take possession */ if (ret < 0) { - if (ret == -ENOKEY) - (void) home_count_bad_authentication(h, false); + (void) home_count_bad_authentication(h, ret, /* save= */ false); (void) convert_worker_errno(h, ret, &error); r = log_error_errno(ret, "Fixation failed: %m"); @@ -717,7 +744,7 @@ static void home_fixate_finish(Home *h, int ret, UserRecord *hr) { if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) { - r = home_start_work(h, "activate", h->record, secret); + r = home_start_work(h, "activate", h->record, secret, NULL, 0); if (r < 0) { h->current_operation = operation_result_unref(h->current_operation, r, NULL); home_set_state(h, _HOME_STATE_INVALID); @@ -743,6 +770,27 @@ fail: home_set_state(h, HOME_UNFIXATED); } +static bool error_is_bad_password(int ret) { + /* Tests for the various cases of bad passwords. We generally don't want to log so loudly about + * these, since everyone types in a bad password now and then. Moreover we usually try to start out + * with an empty set of passwords, so the first authentication will frequently fail, if not token is + * inserted. */ + + return IN_SET(ret, + -ENOKEY, /* Bad password, or insufficient */ + -EBADSLT, /* Bad password, and no token */ + -EREMOTEIO, /* Bad recovery key */ + -ENOANO, /* PIN for security token needed */ + -ERFKILL, /* "Protected Authentication Path" for token needed */ + -EMEDIUMTYPE, /* Presence confirmation on token needed */ + -ENOCSI, /* User verification on token needed */ + -ENOSTR, /* Token action timeout */ + -EOWNERDEAD, /* PIN locked of security token */ + -ENOLCK, /* Bad PIN of security token */ + -ETOOMANYREFS, /* Bad PIN and few tries left */ + -EUCLEAN); /* Bad PIN and one try left */ +} + static void home_activate_finish(Home *h, int ret, UserRecord *hr) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -751,11 +799,11 @@ static void home_activate_finish(Home *h, int ret, UserRecord *hr) { assert(IN_SET(h->state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE)); if (ret < 0) { - if (ret == -ENOKEY) - home_count_bad_authentication(h, true); + (void) home_count_bad_authentication(h, ret, /* save= */ true); (void) convert_worker_errno(h, ret, &error); - r = log_error_errno(ret, "Activation failed: %m"); + r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR, + ret, "Activation failed: %s", bus_error_message(&error, ret)); goto finish; } @@ -907,31 +955,39 @@ static void home_create_finish(Home *h, int ret, UserRecord *hr) { static void home_change_finish(Home *h, int ret, UserRecord *hr) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + uint64_t flags; int r; assert(h); + flags = h->current_operation ? h->current_operation->call_flags : 0; + if (ret < 0) { - if (ret == -ENOKEY) - (void) home_count_bad_authentication(h, true); + (void) home_count_bad_authentication(h, ret, /* save= */ true); (void) convert_worker_errno(h, ret, &error); - r = log_error_errno(ret, "Change operation failed: %m"); + r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR, + ret, "Change operation failed: %s", bus_error_message(&error, ret)); goto finish; } if (hr) { - r = home_set_record(h, hr); - if (r < 0) - log_warning_errno(r, "Failed to update home record, ignoring: %m"); - else { + if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) { r = user_record_good_authentication(h->record); if (r < 0) log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m"); + } + r = home_set_record(h, hr); + if (r >= 0) r = home_save_record(h); - if (r < 0) - log_warning_errno(r, "Failed to write home record to disk, ignoring: %m"); + if (r < 0) { + if (FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) { + log_error_errno(r, "Failed to update home record and write it to disk: %m"); + sd_bus_error_set(&error, SD_BUS_ERROR_FAILED, "Failed to cache changes to home record"); + goto finish; + } else + log_warning_errno(r, "Failed to update home record, ignoring: %m"); } } @@ -982,11 +1038,11 @@ static void home_unlocking_finish(Home *h, int ret, UserRecord *hr) { assert(IN_SET(h->state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE)); if (ret < 0) { - if (ret == -ENOKEY) - (void) home_count_bad_authentication(h, true); + (void) home_count_bad_authentication(h, ret, /* save= */ true); (void) convert_worker_errno(h, ret, &error); - r = log_error_errno(ret, "Unlocking operation failed: %m"); + r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR, + ret, "Unlocking operation failed: %s", bus_error_message(&error, ret)); /* Revert to locked state */ home_set_state(h, HOME_LOCKED); @@ -1018,11 +1074,11 @@ static void home_authenticating_finish(Home *h, int ret, UserRecord *hr) { assert(IN_SET(h->state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE)); if (ret < 0) { - if (ret == -ENOKEY) - (void) home_count_bad_authentication(h, true); + (void) home_count_bad_authentication(h, ret, /* save= */ true); (void) convert_worker_errno(h, ret, &error); - r = log_error_errno(ret, "Authentication failed: %m"); + r = log_full_errno(error_is_bad_password(ret) ? LOG_NOTICE : LOG_ERR, + ret, "Authentication failed: %s", bus_error_message(&error, ret)); goto finish; } @@ -1136,10 +1192,17 @@ static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void return 0; } -static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; +static int home_start_work( + Home *h, + const char *verb, + UserRecord *hr, + UserRecord *secret, + Hashmap *blobs, + uint64_t flags) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *fdmap = NULL; _cleanup_(erase_and_freep) char *formatted = NULL; _cleanup_close_ int stdin_fd = -EBADF, stdout_fd = -EBADF; + _cleanup_free_ int *blob_fds = NULL; pid_t pid = 0; int r; @@ -1167,11 +1230,42 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord return r; } + if (blobs) { + const char *blob_filename = NULL; + void *fd_ptr; + size_t i = 0; + + blob_fds = new(int, hashmap_size(blobs)); + if (!blob_fds) + return -ENOMEM; + + /* homework needs to be able to tell the difference between blobs being null + * (the fdmap field is completely missing) and it being empty (the field is an + * empty object) */ + r = json_variant_new_object(&fdmap, NULL, 0); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(fd_ptr, blob_filename, blobs) { + blob_fds[i] = PTR_TO_FD(fd_ptr); + + r = json_variant_set_field_integer(&fdmap, blob_filename, i); + if (r < 0) + return r; + + i++; + } + + r = json_variant_set_field(&v, HOMEWORK_BLOB_FDMAP_FIELD, fdmap); + if (r < 0) + return r; + } + r = json_variant_format(v, 0, &formatted); if (r < 0) return r; - stdin_fd = acquire_data_fd(formatted, strlen(formatted), 0); + stdin_fd = acquire_data_fd(formatted); if (stdin_fd < 0) return stdin_fd; @@ -1183,13 +1277,14 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord r = safe_fork_full("(sd-homework)", (int[]) { stdin_fd, stdout_fd, STDERR_FILENO }, - NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid); + blob_fds, hashmap_size(blobs), + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_CLOEXEC_OFF|FORK_PACK_FDS|FORK_DEATHSIG_SIGTERM| + FORK_REARRANGE_STDIO|FORK_LOG|FORK_REOPEN_LOG, &pid); if (r < 0) return r; if (r == 0) { _cleanup_free_ char *joined = NULL; - const char *homework, *suffix, *unix_path; + const char *suffix, *unix_path; /* Child */ @@ -1225,16 +1320,21 @@ static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord _exit(EXIT_FAILURE); } + if (setenv("SYSTEMD_HOMEWORK_UPDATE_OFFLINE", one_zero(FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)), 1) < 0) { + log_error_errno(errno, "Failed to set $SYSTEMD_HOMEWORK_UPDATE_OFFLINE: %m"); + _exit(EXIT_FAILURE); + } + r = setenv_systemd_exec_pid(true); if (r < 0) log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m"); - /* Allow overriding the homework path via an environment variable, to make debugging - * easier. */ - homework = getenv("SYSTEMD_HOMEWORK_PATH") ?: SYSTEMD_HOMEWORK_PATH; + r = setenv_systemd_log_level(); + if (r < 0) + log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m"); - execl(homework, homework, verb, NULL); - log_error_errno(errno, "Failed to invoke %s: %m", homework); + r = invoke_callout_binary(SYSTEMD_HOMEWORK_PATH, STRV_MAKE(SYSTEMD_HOMEWORK_PATH, verb)); + log_error_errno(r, "Failed to invoke %s: %m", SYSTEMD_HOMEWORK_PATH); _exit(EXIT_FAILURE); } @@ -1298,9 +1398,10 @@ static int home_fixate_internal( int r; assert(h); + assert(secret); assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)); - r = home_start_work(h, "inspect", h->record, secret); + r = home_start_work(h, "inspect", h->record, secret, NULL, 0); if (r < 0) return r; @@ -1318,6 +1419,7 @@ int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) { int r; assert(h); + assert(secret); switch (home_get_state(h)) { case HOME_ABSENT: @@ -1345,9 +1447,10 @@ static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_sta int r; assert(h); + assert(secret); assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE)); - r = home_start_work(h, "activate", h->record, secret); + r = home_start_work(h, "activate", h->record, secret, NULL, 0); if (r < 0) return r; @@ -1355,10 +1458,14 @@ static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_sta return 0; } -int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) { +int home_activate(Home *h, bool if_referenced, UserRecord *secret, sd_bus_error *error) { int r; assert(h); + assert(secret); + + if (if_referenced && !home_is_referenced(h)) + return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_REFERENCED, "Home %s is currently not referenced.", h->user_name); switch (home_get_state(h)) { case HOME_UNFIXATED: @@ -1392,9 +1499,10 @@ static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for int r; assert(h); + assert(secret); assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE)); - r = home_start_work(h, "inspect", h->record, secret); + r = home_start_work(h, "inspect", h->record, secret, NULL, 0); if (r < 0) return r; @@ -1407,6 +1515,7 @@ int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) { int r; assert(h); + assert(secret); state = home_get_state(h); switch (state) { @@ -1438,7 +1547,7 @@ static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) { home_unpin(h); /* unpin so that we can deactivate */ - r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL); + r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL, NULL, 0); if (r < 0) /* Operation failed before it even started, reacquire pin fd, if state still dictates so */ home_update_pin_fd(h, _HOME_STATE_INVALID); @@ -1475,10 +1584,11 @@ int home_deactivate(Home *h, bool force, sd_bus_error *error) { return home_deactivate_internal(h, force, error); } -int home_create(Home *h, UserRecord *secret, sd_bus_error *error) { +int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error) { int r; assert(h); + assert(secret); switch (home_get_state(h)) { case HOME_INACTIVE: { @@ -1517,7 +1627,7 @@ int home_create(Home *h, UserRecord *secret, sd_bus_error *error) { return r; } - r = home_start_work(h, "create", h->record, secret); + r = home_start_work(h, "create", h->record, secret, blobs, flags); if (r < 0) return r; @@ -1547,7 +1657,7 @@ int home_remove(Home *h, sd_bus_error *error) { return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "Home %s is currently being used, or an operation on home %s is currently being executed.", h->user_name, h->user_name); } - r = home_start_work(h, "remove", h->record, NULL); + r = home_start_work(h, "remove", h->record, NULL, NULL, 0); if (r < 0) return r; @@ -1591,6 +1701,8 @@ static int home_update_internal( const char *verb, UserRecord *hr, UserRecord *secret, + Hashmap *blobs, + uint64_t flags, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL; @@ -1614,6 +1726,15 @@ static int home_update_internal( secret = saved_secret; } + if (blobs) { + const char *failed = NULL; + r = user_record_ensure_blob_manifest(hr, blobs, &failed); + if (r == -EINVAL) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed)); + } + r = manager_verify_user_record(h->manager, hr); switch (r) { @@ -1656,14 +1777,14 @@ static int home_update_internal( return sd_bus_error_set(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing."); } - r = home_start_work(h, verb, new_hr, secret); + r = home_start_work(h, verb, new_hr, secret, blobs, flags); if (r < 0) return r; return 0; } -int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { +int home_update(Home *h, UserRecord *hr, Hashmap *blobs, uint64_t flags, sd_bus_error *error) { HomeState state; int r; @@ -1675,7 +1796,9 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { case HOME_UNFIXATED: return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s has not been fixated yet.", h->user_name); case HOME_ABSENT: - return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); + if (!FLAGS_SET(flags, SD_HOMED_UPDATE_OFFLINE)) + return sd_bus_error_setf(error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); + break; /* offline updates are compatible w/ an absent home area */ case HOME_LOCKED: return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); case HOME_INACTIVE: @@ -1691,7 +1814,7 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { if (r < 0) return r; - r = home_update_internal(h, "update", hr, NULL, error); + r = home_update_internal(h, "update", hr, NULL, blobs, flags, error); if (r < 0) return r; @@ -1702,7 +1825,6 @@ int home_update(Home *h, UserRecord *hr, sd_bus_error *error) { int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, - bool automatic, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *c = NULL; @@ -1778,7 +1900,7 @@ int home_resize(Home *h, c = TAKE_PTR(signed_c); } - r = home_update_internal(h, automatic ? "resize-auto" : "resize", c, secret, error); + r = home_update_internal(h, "resize", c, secret, NULL, 0, error); if (r < 0) return r; @@ -1815,6 +1937,8 @@ int home_passwd(Home *h, int r; assert(h); + assert(new_secret); + assert(old_secret); if (h->signed_locally <= 0) /* Don't allow changing of records not signed only by us */ return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_SIGNED, "Home %s is signed and cannot be modified locally.", h->user_name); @@ -1892,7 +2016,7 @@ int home_passwd(Home *h, return r; } - r = home_update_internal(h, "passwd", signed_c, merged_secret, error); + r = home_update_internal(h, "passwd", signed_c, merged_secret, NULL, 0, error); if (r < 0) return r; @@ -1949,7 +2073,7 @@ int home_lock(Home *h, sd_bus_error *error) { return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name); } - r = home_start_work(h, "lock", h->record, NULL); + r = home_start_work(h, "lock", h->record, NULL, NULL, 0); if (r < 0) return r; @@ -1961,9 +2085,10 @@ static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state int r; assert(h); + assert(secret); assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE)); - r = home_start_work(h, "unlock", h->record, secret); + r = home_start_work(h, "unlock", h->record, secret, NULL, 0); if (r < 0) return r; @@ -1973,7 +2098,9 @@ static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) { int r; + assert(h); + assert(secret); r = home_ratelimit(h, error); if (r < 0) @@ -2085,23 +2212,11 @@ int home_killall(Home *h) { if (r < 0) return r; if (r == 0) { - gid_t gid; - /* Child */ - gid = user_record_gid(h->record); - if (setresgid(gid, gid, gid) < 0) { - log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - _exit(EXIT_FAILURE); - } - - if (setgroups(0, NULL) < 0) { - log_error_errno(errno, "Failed to reset auxiliary groups list: %m"); - _exit(EXIT_FAILURE); - } - - if (setresuid(h->uid, h->uid, h->uid) < 0) { - log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", h->uid); + r = fully_set_uid_gid(h->uid, user_record_gid(h->record), /* supplementary_gids= */ NULL, /* n_supplementary_gids= */ 0); + if (r < 0) { + log_error_errno(r, "Failed to change UID/GID to " UID_FMT "/" GID_FMT ": %m", h->uid, user_record_gid(h->record)); _exit(EXIT_FAILURE); } @@ -2542,6 +2657,9 @@ int home_augment_status( JSON_BUILD_OBJECT( JSON_BUILD_PAIR("state", JSON_BUILD_STRING(home_state_to_string(state))), JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.Home")), + JSON_BUILD_PAIR("useFallback", JSON_BUILD_BOOLEAN(!HOME_STATE_IS_ACTIVE(state))), + JSON_BUILD_PAIR("fallbackShell", JSON_BUILD_CONST_STRING(BINDIR "/systemd-home-fallback-shell")), + JSON_BUILD_PAIR("fallbackHomeDirectory", JSON_BUILD_CONST_STRING("/")), JSON_BUILD_PAIR_CONDITION(disk_size != UINT64_MAX, "diskSize", JSON_BUILD_UNSIGNED(disk_size)), JSON_BUILD_PAIR_CONDITION(disk_usage != UINT64_MAX, "diskUsage", JSON_BUILD_UNSIGNED(disk_usage)), JSON_BUILD_PAIR_CONDITION(disk_free != UINT64_MAX, "diskFree", JSON_BUILD_UNSIGNED(disk_free)), @@ -2602,7 +2720,7 @@ static int on_home_ref_eof(sd_event_source *s, int fd, uint32_t revents, void *u if (h->ref_event_source_dont_suspend == s) h->ref_event_source_dont_suspend = sd_event_source_disable_unref(h->ref_event_source_dont_suspend); - if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend) + if (home_is_referenced(h)) return 0; log_info("Got notification that all sessions of user %s ended, deactivating automatically.", h->user_name); @@ -2652,7 +2770,9 @@ int home_create_fifo(Home *h, bool please_suspend) { (void) sd_event_source_set_description(*ss, "acquire-ref"); - r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_IDLE-1); + /* We need to notice dropped refs before we process new bus requests (which + * might try to obtain new refs) */ + r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_NORMAL-10); if (r < 0) return r; @@ -2690,7 +2810,8 @@ static int home_dispatch_acquire(Home *h, Operation *o) { case HOME_ABSENT: r = sd_bus_error_setf(&error, BUS_ERROR_HOME_ABSENT, "Home %s is currently missing or not plugged in.", h->user_name); - goto check; + operation_result(o, r, &error); + return 1; case HOME_INACTIVE: case HOME_DIRTY: @@ -2721,7 +2842,6 @@ static int home_dispatch_acquire(Home *h, Operation *o) { if (r >= 0) r = call(h, o->secret, for_state, &error); - check: if (r != 0) /* failure or completed */ operation_result(o, r, &error); else /* ongoing */ @@ -2730,6 +2850,19 @@ static int home_dispatch_acquire(Home *h, Operation *o) { return 1; } +bool home_is_referenced(Home *h) { + assert(h); + + return h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend; +} + +bool home_shall_suspend(Home *h) { + assert(h); + + /* Suspend if there's at least one client referencing this home directory that wants a suspend and none who does not. */ + return h->ref_event_source_please_suspend && !h->ref_event_source_dont_suspend; +} + static int home_dispatch_release(Home *h, Operation *o) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; @@ -2738,33 +2871,35 @@ static int home_dispatch_release(Home *h, Operation *o) { assert(o); assert(o->type == OPERATION_RELEASE); - if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend) + if (home_is_referenced(h)) { /* If there's now a reference again, then let's abort the release attempt */ r = sd_bus_error_setf(&error, BUS_ERROR_HOME_BUSY, "Home %s is currently referenced.", h->user_name); - else { - switch (home_get_state(h)) { - - case HOME_UNFIXATED: - case HOME_ABSENT: - case HOME_INACTIVE: - case HOME_DIRTY: - r = 1; /* done */ - break; - - case HOME_LOCKED: - r = sd_bus_error_setf(&error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); - break; - - case HOME_ACTIVE: - case HOME_LINGERING: - r = home_deactivate_internal(h, false, &error); - break; - - default: - /* All other cases means we are currently executing an operation, which means the job remains - * pending. */ - return 0; - } + operation_result(o, r, &error); + return 1; + } + + switch (home_get_state(h)) { + + case HOME_UNFIXATED: + case HOME_ABSENT: + case HOME_INACTIVE: + case HOME_DIRTY: + r = 1; /* done */ + break; + + case HOME_LOCKED: + r = sd_bus_error_setf(&error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name); + break; + + case HOME_ACTIVE: + case HOME_LINGERING: + r = home_deactivate_internal(h, false, &error); + break; + + default: + /* All other cases means we are currently executing an operation, which means the job remains + * pending. */ + return 0; } assert(!h->current_operation); @@ -2875,7 +3010,7 @@ static int home_dispatch_pipe_eof(Home *h, Operation *o) { assert(o); assert(o->type == OPERATION_PIPE_EOF); - if (h->ref_event_source_please_suspend || h->ref_event_source_dont_suspend) + if (home_is_referenced(h)) return 1; /* Hmm, there's a reference again, let's cancel this */ switch (home_get_state(h)) { @@ -3028,7 +3163,6 @@ int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error) { static int home_get_image_path_seat(Home *h, char **ret) { _cleanup_(sd_device_unrefp) sd_device *d = NULL; - _cleanup_free_ char *c = NULL; const char *ip, *seat; struct stat st; int r; @@ -3061,12 +3195,7 @@ static int home_get_image_path_seat(Home *h, char **ret) { else if (r < 0) return r; - c = strdup(seat); - if (!c) - return -ENOMEM; - - *ret = TAKE_PTR(c); - return 0; + return strdup_to(ret, seat); } int home_auto_login(Home *h, char ***ret_seats) { diff --git a/src/home/homed-home.h b/src/home/homed-home.h index 0f314aa..7d466cd 100644 --- a/src/home/homed-home.h +++ b/src/home/homed-home.h @@ -3,6 +3,7 @@ typedef struct Home Home; +#include "hashmap.h" #include "homed-manager.h" #include "homed-operation.h" #include "list.h" @@ -185,21 +186,31 @@ int home_save_record(Home *h); int home_unlink_record(Home *h); int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error); -int home_activate(Home *h, UserRecord *secret, sd_bus_error *error); +int home_activate(Home *h, bool if_referenced, UserRecord *secret, sd_bus_error *error); int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error); int home_deactivate(Home *h, bool force, sd_bus_error *error); -int home_create(Home *h, UserRecord *secret, sd_bus_error *error); +int home_create(Home *h, UserRecord *secret, Hashmap *blobs, uint64_t flags, sd_bus_error *error); int home_remove(Home *h, sd_bus_error *error); -int home_update(Home *h, UserRecord *new_record, sd_bus_error *error); -int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, bool automatic, sd_bus_error *error); +int home_update(Home *h, UserRecord *new_record, Hashmap *blobs, uint64_t flags, sd_bus_error *error); +int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *error); int home_passwd(Home *h, UserRecord *new_secret, UserRecord *old_secret, sd_bus_error *error); int home_unregister(Home *h, sd_bus_error *error); int home_lock(Home *h, sd_bus_error *error); int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error); +bool home_is_referenced(Home *h); +bool home_shall_suspend(Home *h); HomeState home_get_state(Home *h); -int home_get_disk_status(Home *h, uint64_t *ret_disk_size,uint64_t *ret_disk_usage, uint64_t *ret_disk_free, uint64_t *ret_disk_ceiling, uint64_t *ret_disk_floor, statfs_f_type_t *ret_fstype, mode_t *ret_access_mode); +int home_get_disk_status( + Home *h, + uint64_t *ret_disk_size, + uint64_t *ret_disk_usage, + uint64_t *ret_disk_free, + uint64_t *ret_disk_ceiling, + uint64_t *ret_disk_floor, + statfs_f_type_t *ret_fstype, + mode_t *ret_access_mode); void home_process_notify(Home *h, char **l, int fd); diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c index 7cf5439..58cd037 100644 --- a/src/home/homed-manager-bus.c +++ b/src/home/homed-manager-bus.c @@ -6,6 +6,7 @@ #include "bus-common-errors.h" #include "bus-polkit.h" #include "format-util.h" +#include "home-util.h" #include "homed-bus.h" #include "homed-home-bus.h" #include "homed-manager-bus.h" @@ -61,6 +62,53 @@ static int property_get_auto_login( return sd_bus_message_close_container(reply); } +static int lookup_user_name( + Manager *m, + sd_bus_message *message, + const char *user_name, + sd_bus_error *error, + Home **ret) { + + Home *h; + int r; + + assert(m); + assert(message); + assert(user_name); + assert(ret); + + if (isempty(user_name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + uid_t uid; + + /* If an empty user name is specified, then identify caller's EUID and find home by that. */ + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + + h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid)); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "Client's UID " UID_FMT " not managed.", uid); + + } else { + + if (!valid_user_group_name(user_name, 0)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); + + h = hashmap_get(m->homes_by_name, user_name); + if (!h) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + } + + *ret = h; + return 0; +} + static int method_get_home_by_name( sd_bus_message *message, void *userdata, @@ -77,12 +125,10 @@ static int method_get_home_by_name( r = sd_bus_message_read(message, "s", &user_name); if (r < 0) return r; - if (!valid_user_group_name(user_name, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); - h = hashmap_get(m->homes_by_name, user_name); - if (!h) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + r = lookup_user_name(m, message, user_name, error, &h); + if (r < 0) + return r; r = bus_home_path(h, &path); if (r < 0) @@ -204,12 +250,10 @@ static int method_get_user_record_by_name( r = sd_bus_message_read(message, "s", &user_name); if (r < 0) return r; - if (!valid_user_group_name(user_name, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); - h = hashmap_get(m->homes_by_name, user_name); - if (!h) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + r = lookup_user_name(m, message, user_name, error, &h); + if (r < 0) + return r; r = bus_home_get_record_json(h, message, &json, &incomplete); if (r < 0) @@ -274,16 +318,17 @@ static int generic_home_method( Home *h; int r; + assert(m); + assert(message); + assert(handler); + r = sd_bus_message_read(message, "s", &user_name); if (r < 0) return r; - if (!valid_user_group_name(user_name, 0)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User name %s is not valid", user_name); - - h = hashmap_get(m->homes_by_name, user_name); - if (!h) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", user_name); + r = lookup_user_name(m, message, user_name, error, &h); + if (r < 0) + return r; return handler(message, h, error); } @@ -296,10 +341,8 @@ static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bu return generic_home_method(userdata, message, bus_home_method_deactivate, error); } -static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd_bus_error *error) { +static int validate_and_allocate_home(Manager *m, UserRecord *hr, Hashmap *blobs, Home **ret, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL; - struct passwd *pw; - struct group *gr; bool signed_locally; Home *other; int r; @@ -316,13 +359,26 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd if (other) return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists already, refusing.", hr->user_name); - pw = getpwnam(hr->user_name); - if (pw) + r = getpwnam_malloc(hr->user_name, /* ret= */ NULL); + if (r >= 0) return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s exists in the NSS user database, refusing.", hr->user_name); + if (r != -ESRCH) + return r; - gr = getgrnam(hr->user_name); - if (gr) + r = getgrnam_malloc(hr->user_name, /* ret= */ NULL); + if (r >= 0) return sd_bus_error_setf(error, BUS_ERROR_USER_NAME_EXISTS, "Specified user name %s conflicts with an NSS group by the same name, refusing.", hr->user_name); + if (r != -ESRCH) + return r; + + if (blobs) { + const char *failed = NULL; + r = user_record_ensure_blob_manifest(hr, blobs, &failed); + if (r == -EINVAL) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Provided blob files do not correspond to blob manifest."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to generate hash for blob %s: %m", strnull(failed)); + } r = manager_verify_user_record(m, hr); switch (r) { @@ -353,17 +409,24 @@ static int validate_and_allocate_home(Manager *m, UserRecord *hr, Home **ret, sd } if (uid_is_valid(hr->uid)) { + _cleanup_free_ struct passwd *pw = NULL; + _cleanup_free_ struct group *gr = NULL; + other = hashmap_get(m->homes_by_uid, UID_TO_PTR(hr->uid)); if (other) return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by home %s, refusing.", hr->uid, other->user_name); - pw = getpwuid(hr->uid); - if (pw) + r = getpwuid_malloc(hr->uid, &pw); + if (r >= 0) return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use by NSS user %s, refusing.", hr->uid, pw->pw_name); + if (r != -ESRCH) + return r; - gr = getgrgid(hr->uid); - if (gr) + r = getgrgid_malloc(hr->uid, &gr); + if (r >= 0) return sd_bus_error_setf(error, BUS_ERROR_UID_IN_USE, "Specified UID " UID_FMT " already in use as GID by NSS group %s, refusing.", hr->uid, gr->gr_name); + if (r != -ESRCH) + return r; } else { r = manager_augment_record_with_uid(m, hr); if (r < 0) @@ -396,11 +459,8 @@ static int method_register_home( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.home1.create-home", - NULL, - true, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -408,7 +468,7 @@ static int method_register_home( if (r == 0) return 1; /* Will call us back */ - r = validate_and_allocate_home(m, hr, &h, error); + r = validate_and_allocate_home(m, hr, NULL, &h, error); if (r < 0) return r; @@ -425,12 +485,11 @@ static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bu return generic_home_method(userdata, message, bus_home_method_unregister, error); } -static int method_create_home( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { +static int method_create_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + uint64_t flags = 0; Manager *m = ASSERT_PTR(userdata); Home *h; int r; @@ -441,13 +500,22 @@ static int method_create_home( if (r < 0) return r; + if (endswith(sd_bus_message_get_member(message), "Ex")) { + r = bus_message_read_blobs(message, &blobs, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + if ((flags & ~SD_HOMED_CREATE_FLAGS_ALL) != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags provided."); + } + r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.home1.create-home", - NULL, - true, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -455,11 +523,11 @@ static int method_create_home( if (r == 0) return 1; /* Will call us back */ - r = validate_and_allocate_home(m, hr, &h, error); + r = validate_and_allocate_home(m, hr, blobs, &h, error); if (r < 0) return r; - r = home_create(h, hr, error); + r = home_create(h, hr, blobs, flags, error); if (r < 0) goto fail; @@ -471,6 +539,8 @@ static int method_create_home( if (r < 0) return r; + h->current_operation->call_flags = flags; + return 1; fail: @@ -497,6 +567,8 @@ static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_ static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(user_record_unrefp) UserRecord *hr = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; + uint64_t flags = 0; Manager *m = ASSERT_PTR(userdata); Home *h; int r; @@ -507,13 +579,23 @@ static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_er if (r < 0) return r; + if (endswith(sd_bus_message_get_member(message), "Ex")) { + r = bus_message_read_blobs(message, &blobs, error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + } + assert(hr->user_name); h = hashmap_get(m->homes_by_name, hr->user_name); if (!h) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for user %s known", hr->user_name); - return bus_home_method_update_record(h, message, hr, error); + return bus_home_update_record(h, message, hr, blobs, flags, error); } static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -557,10 +639,7 @@ static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus HASHMAP_FOREACH(h, m->homes_by_name) { - /* Automatically suspend all homes that have at least one client referencing it that asked - * for "please suspend", and no client that asked for "please do not suspend". */ - if (h->ref_event_source_dont_suspend || - !h->ref_event_source_please_suspend) + if (!home_shall_suspend(h)) continue; if (!o) { @@ -689,7 +768,12 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_ARGS("s", user_name, "s", secret), SD_BUS_NO_RESULT, method_activate_home, - SD_BUS_VTABLE_SENSITIVE), + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD_WITH_ARGS("ActivateHomeIfReferenced", + SD_BUS_ARGS("s", user_name, "s", secret), + SD_BUS_NO_RESULT, + method_activate_home, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), SD_BUS_METHOD_WITH_ARGS("DeactivateHome", SD_BUS_ARGS("s", user_name), SD_BUS_NO_RESULT, @@ -716,6 +800,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_NO_RESULT, method_create_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD_WITH_ARGS("CreateHomeEx", + SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags), + SD_BUS_NO_RESULT, + method_create_home, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Create $HOME for already registered JSON entry */ SD_BUS_METHOD_WITH_ARGS("RealizeHome", @@ -751,6 +840,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_NO_RESULT, method_update_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), + SD_BUS_METHOD_WITH_ARGS("UpdateHomeEx", + SD_BUS_ARGS("s", user_record, "a{sh}", blobs, "t", flags), + SD_BUS_NO_RESULT, + method_update_home, + SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), SD_BUS_METHOD_WITH_ARGS("ResizeHome", SD_BUS_ARGS("s", user_name, "t", size, "s", secret), @@ -795,6 +889,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("h", send_fd), method_ref_home, 0), + SD_BUS_METHOD_WITH_ARGS("RefHomeUnrestricted", + SD_BUS_ARGS("s", user_name, "b", please_suspend), + SD_BUS_RESULT("h", send_fd), + method_ref_home, + 0), SD_BUS_METHOD_WITH_ARGS("ReleaseHome", SD_BUS_ARGS("s", user_name), SD_BUS_NO_RESULT, diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c index b8bef53..7669cbb 100644 --- a/src/home/homed-manager.c +++ b/src/home/homed-manager.c @@ -42,6 +42,7 @@ #include "quota-util.h" #include "random-util.h" #include "resize-fs.h" +#include "rm-rf.h" #include "socket-util.h" #include "sort-util.h" #include "stat-util.h" @@ -79,6 +80,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, pat static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata); static int manager_gc_images(Manager *m); +static int manager_gc_blob(Manager *m); static int manager_enumerate_images(Manager *m); static int manager_assess_image(Manager *m, int dir_fd, const char *dir_path, const char *dentry_name); static void manager_revalidate_image(Manager *m, Home *h); @@ -268,7 +270,7 @@ Manager* manager_free(Manager *m) { (void) home_wait_for_worker(h); m->bus = sd_bus_flush_close_unref(m->bus); - m->polkit_registry = bus_verify_polkit_async_registry_free(m->polkit_registry); + m->polkit_registry = hashmap_free(m->polkit_registry); m->device_monitor = sd_device_monitor_unref(m->device_monitor); @@ -588,8 +590,8 @@ static int manager_acquire_uid( assert(ret); for (;;) { - struct passwd *pw; - struct group *gr; + _cleanup_free_ struct passwd *pw = NULL; + _cleanup_free_ struct group *gr = NULL; uid_t candidate; Home *other; @@ -632,19 +634,27 @@ static int manager_acquire_uid( continue; } - pw = getpwuid(candidate); - if (pw) { + r = getpwuid_malloc(candidate, &pw); + if (r >= 0) { log_debug("Candidate UID " UID_FMT " already registered by another user in NSS (%s), let's try another.", candidate, pw->pw_name); continue; } + if (r != -ESRCH) { + log_debug_errno(r, "Failed to check if an NSS user is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate); + continue; + } - gr = getgrgid((gid_t) candidate); - if (gr) { + r = getgrgid_malloc((gid_t) candidate, &gr); + if (r >= 0) { log_debug("Candidate UID " UID_FMT " already registered by another group in NSS (%s), let's try another.", candidate, gr->gr_name); continue; } + if (r != -ESRCH) { + log_debug_errno(r, "Failed to check if an NSS group is already registered for candidate UID " UID_FMT ", assuming there might be: %m", candidate); + continue; + } r = search_ipc(candidate, (gid_t) candidate); if (r < 0) @@ -715,7 +725,7 @@ static int manager_add_home_by_image( } } else { /* Check NSS, in case there's another user or group by this name */ - if (getpwnam(user_name) || getgrnam(user_name)) { + if (getpwnam_malloc(user_name, /* ret= */ NULL) >= 0 || getgrnam_malloc(user_name, /* ret= */ NULL) >= 0) { log_debug("Found an existing user or group by name '%s', ignoring image '%s'.", user_name, image_path); return 0; } @@ -903,7 +913,7 @@ static int manager_assess_image( r = btrfs_is_subvol_fd(fd); if (r < 0) - return log_warning_errno(errno, "Failed to determine whether %s is a btrfs subvolume: %m", path); + return log_warning_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path); if (r > 0) storage = USER_SUBVOLUME; else { @@ -981,7 +991,7 @@ static int manager_connect_bus(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to request name: %m"); - r = sd_bus_attach_event(m->bus, m->event, 0); + r = sd_bus_attach_event(m->bus, m->event, SD_EVENT_PRIORITY_NORMAL); if (r < 0) return log_error_errno(r, "Failed to attach bus to event loop: %m"); @@ -998,7 +1008,7 @@ static int manager_bind_varlink(Manager *m) { assert(m); assert(!m->varlink_server); - r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA|VARLINK_SERVER_INPUT_SENSITIVE); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); @@ -1044,7 +1054,7 @@ static int manager_bind_varlink(Manager *m) { /* Avoid recursion */ if (setenv("SYSTEMD_BYPASS_USERDB", m->userdb_service, 1) < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set $SYSTEMD_BYPASS_USERDB: %m"); + return log_error_errno(errno, "Failed to set $SYSTEMD_BYPASS_USERDB: %m"); return 0; } @@ -1355,8 +1365,11 @@ static int manager_enumerate_devices(Manager *m) { if (r < 0) return r; - FOREACH_DEVICE(e, d) + FOREACH_DEVICE(e, d) { + if (device_is_processed(d) <= 0) + continue; (void) manager_add_device(m, d); + } return 0; } @@ -1428,7 +1441,7 @@ static int manager_generate_key_pair(Manager *m) { /* Write out public key (note that we only do that as a help to the user, we don't make use of this ever */ r = fopen_temporary("/var/lib/systemd/home/local.public", &fpublic, &temp_public); if (r < 0) - return log_error_errno(errno, "Failed to open key file for writing: %m"); + return log_error_errno(r, "Failed to open key file for writing: %m"); if (PEM_write_PUBKEY(fpublic, m->private_key) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write public key."); @@ -1442,7 +1455,7 @@ static int manager_generate_key_pair(Manager *m) { /* Write out the private key (this actually writes out both private and public, OpenSSL is confusing) */ r = fopen_temporary("/var/lib/systemd/home/local.private", &fprivate, &temp_private); if (r < 0) - return log_error_errno(errno, "Failed to open key file for writing: %m"); + return log_error_errno(r, "Failed to open key file for writing: %m"); if (PEM_write_PrivateKey(fprivate, m->private_key, NULL, NULL, 0, NULL, 0) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to write private key pair."); @@ -1619,6 +1632,9 @@ int manager_startup(Manager *m) { /* Let's clean up home directories whose devices got removed while we were not running */ (void) manager_enqueue_gc(m, NULL); + /* Let's clean up blob directories for home dirs that no longer exist */ + (void) manager_gc_blob(m); + return 0; } @@ -1707,6 +1723,29 @@ int manager_gc_images(Manager *m) { return 0; } +static int manager_gc_blob(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(m); + + d = opendir(home_system_blob_dir()); + if (!d) { + if (errno == ENOENT) + return 0; + return log_error_errno(errno, "Failed to open %s: %m", home_system_blob_dir()); + } + + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read system blob directory: %m")) + if (!hashmap_contains(m->homes_by_name, de->d_name)) { + r = rm_rf_at(dirfd(d), de->d_name, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + if (r < 0) + log_warning_errno(r, "Failed to delete blob dir for missing user '%s', ignoring: %m", de->d_name); + } + + return 0; +} + static int on_deferred_rescan(sd_event_source *s, void *userdata) { Manager *m = ASSERT_PTR(userdata); @@ -1989,7 +2028,7 @@ static int manager_rebalance_apply(Manager *m) { h->rebalance_pending = false; - r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, /* automatic= */ true, &error); + r = home_resize(h, h->rebalance_goal, /* secret= */ NULL, &error); if (r < 0) log_warning_errno(r, "Failed to resize home '%s' for rebalancing, ignoring: %s", h->user_name, bus_error_message(&error, r)); diff --git a/src/home/homed-operation.h b/src/home/homed-operation.h index 004246a..af165bb 100644 --- a/src/home/homed-operation.h +++ b/src/home/homed-operation.h @@ -39,6 +39,7 @@ typedef struct Operation { sd_bus_message *message; UserRecord *secret; + uint64_t call_flags; /* flags passed into UpdateEx() or CreateHomeEx() */ int send_fd; /* pipe fd for AcquireHome() which is taken already when we start the operation */ int result; /* < 0 if not completed yet, == 0 on failure, > 0 on success */ diff --git a/src/home/homed.c b/src/home/homed.c index 04d9b56..cfb498e 100644 --- a/src/home/homed.c +++ b/src/home/homed.c @@ -29,7 +29,7 @@ static int run(int argc, char *argv[]) { umask(0022); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) diff --git a/src/home/homework-blob.c b/src/home/homework-blob.c new file mode 100644 index 0000000..6b22ab6 --- /dev/null +++ b/src/home/homework-blob.c @@ -0,0 +1,301 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "copy.h" +#include "fileio.h" +#include "fd-util.h" +#include "format-util.h" +#include "fs-util.h" +#include "home-util.h" +#include "homework-blob.h" +#include "homework.h" +#include "install-file.h" +#include "macro.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "rm-rf.h" +#include "sha256.h" +#include "string-util.h" +#include "tmpfile-util.h" +#include "umask-util.h" +#include "utf8.h" + +static int copy_one_blob( + int src_fd, + int dest_dfd, + const char *name, + uint64_t *total_size, + uid_t uid, + Hashmap *manifest) { + _cleanup_(unlink_and_freep) char *dest_tmpname = NULL; + _cleanup_close_ int dest = -EBADF; + uint8_t hash[SHA256_DIGEST_SIZE], *known_hash; + off_t initial, size; + int r; + + assert(src_fd >= 0); + assert(dest_dfd >= 0); + assert(name); + assert(total_size); + assert(uid_is_valid(uid)); + assert(manifest); + + if (!suitable_blob_filename(name)) { + log_warning("Blob %s has invalid filename. Skipping.", name); + return 0; + } + + known_hash = hashmap_get(manifest, name); + if (!known_hash) { + log_warning("Blob %s is missing from manifest. Skipping.", name); + return 0; + } + + r = fd_verify_regular(src_fd); + if (r < 0) { + log_warning_errno(r, "Blob %s is not a regular file. Skipping.", name); + return 0; + } + + initial = lseek(src_fd, 0, SEEK_CUR); + if (initial < 0) + return log_debug_errno(errno, "Failed to get initial pos on fd for blob %s: %m", name); + if (initial > 0) + log_debug("Blob %s started offset %s into file", name, FORMAT_BYTES(initial)); + + /* Hashing is relatively cheaper compared to copying, especially since we're possibly copying across + * filesystems or even devices here. So first we check the hash and bail early if the file's contents + * don't match what's in the manifest. */ + + r = sha256_fd(src_fd, BLOB_DIR_MAX_SIZE, hash); + if (r == -EFBIG) + return log_warning_errno(r, "Blob %s is larger than blob directory size limit. Not copying any further.", name); + if (r < 0) + return log_debug_errno(r, "Failed to compute sha256 for blob %s: %m", name); + if (memcmp(hash, known_hash, SHA256_DIGEST_SIZE) != 0) { + log_warning("Blob %s has incorrect hash. Skipping.", name); + return 0; + } + + size = lseek(src_fd, 0, SEEK_CUR); + if (size < 0) + return log_debug_errno(errno, "Failed to get final pos on fd for blob %s: %m", name); + if (!DEC_SAFE(&size, initial)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid seek position on fd for %s. Couldn't get size.", name); + + if (!INC_SAFE(total_size, size)) + *total_size = UINT64_MAX; + log_debug("Blob %s size is %s, making the new total dir size %s", name, FORMAT_BYTES(size), + *total_size != UINT64_MAX ? FORMAT_BYTES(*total_size) : "overflow!"); + if (*total_size > BLOB_DIR_MAX_SIZE) + return log_warning_errno(SYNTHETIC_ERRNO(EFBIG), + "Blob %s will cause blob directory to exceed its size limit. Not copying any further.", name); + + /* Next we copy but don't yet link the file into the blob directory */ + + if (lseek(src_fd, initial, SEEK_SET) < 0) + return log_debug_errno(errno, "Failed to rewind fd for blob %s: %m", name); + + dest = open_tmpfile_linkable_at(dest_dfd, name, O_RDWR|O_CLOEXEC, &dest_tmpname); + if (dest < 0) + return log_debug_errno(dest, "Failed to create dest tmpfile for blob %s: %m", name); + + if (fchmod(dest, 0644) < 0) + return log_debug_errno(errno, "Failed to chmod blob %s: %m", name); + if (fchown(dest, uid, uid) < 0) + return log_debug_errno(errno, "Failed to chown blob %s: %m", name); + + r = copy_bytes(src_fd, dest, BLOB_DIR_MAX_SIZE, 0); + if (r < 0) + return log_debug_errno(r, "Failed to copy blob %s: %m", name); + + /* The source FD might have changed while we were busy copying, thus invalidating the hash. + * So, we re-hash the data we just copied to make sure that this didn't happen. */ + + if (lseek(dest, 0, SEEK_SET) < 0) + return log_debug_errno(errno, "Failed to rewind blob %s for rehash: %m", name); + + r = sha256_fd(dest, BLOB_DIR_MAX_SIZE, hash); + if (r < 0) + return log_debug_errno(r, "Failed to rehash blob %s: %m", name); + if (memcmp(hash, known_hash, SHA256_DIGEST_SIZE) != 0) { + log_warning("Blob %s has changed while we were copying it. Skipping.", name); + return 0; + } + + /* The file's contents still match the blob manifest, so it's safe to expose it in the directory */ + + r = link_tmpfile_at(dest, dest_dfd, dest_tmpname, name, 0); + if (r < 0) + return log_debug_errno(r, "Failed to link blob %s: %m", name); + dest_tmpname = mfree(dest_tmpname); + + return 0; +} + +static int replace_blob_at( + int src_base_dfd, + const char *src_name, + int dest_base_dfd, + const char *dest_name, + Hashmap *manifest, + mode_t mode, + uid_t uid) { + _cleanup_free_ char *fn = NULL; + _cleanup_close_ int src_dfd = -EBADF, dest_dfd = -EBADF; + _cleanup_free_ DirectoryEntries *de = NULL; + uint64_t total_size = 0; + int r; + + assert(src_base_dfd >= 0); + assert(src_name); + assert(dest_base_dfd >= 0); + assert(dest_name); + assert(uid_is_valid(uid)); + + src_dfd = openat(src_base_dfd, src_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (src_dfd < 0) { + if (errno == ENOENT) + return 0; + return log_debug_errno(errno, "Failed to open src blob dir: %m"); + } + + r = tempfn_random(dest_name, NULL, &fn); + if (r < 0) + return r; + + dest_dfd = open_mkdir_at(dest_base_dfd, fn, O_EXCL|O_CLOEXEC, mode); + if (dest_dfd < 0) + return log_debug_errno(dest_dfd, "Failed to create/open dest blob dir: %m"); + + r = readdir_all(src_dfd, RECURSE_DIR_SORT, &de); + if (r < 0) { + log_debug_errno(r, "Failed to read src blob dir: %m"); + goto fail; + } + for (size_t i = 0; i < de->n_entries; i++) { + const char *name = de->entries[i]->d_name; + _cleanup_close_ int src_fd = -EBADF; + + src_fd = openat(src_dfd, name, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (src_fd < 0) { + r = log_debug_errno(errno, "Failed to open %s in src blob dir: %m", name); + goto fail; + } + + r = copy_one_blob(src_fd, dest_dfd, name, &total_size, uid, manifest); + if (r == -EFBIG) + break; + if (r < 0) + goto fail; + } + + if (fchown(dest_dfd, uid, uid) < 0) { + r = log_debug_errno(errno, "Failed to chown dest blob dir: %m"); + goto fail; + } + + r = install_file(dest_base_dfd, fn, dest_base_dfd, dest_name, INSTALL_REPLACE); + if (r < 0) { + log_debug_errno(r, "Failed to move dest blob dir into place: %m"); + goto fail; + } + + return 0; + +fail: + (void) rm_rf_at(dest_base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK); + return r; +} + +int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled) { + _cleanup_close_ int sys_base_dfd = -EBADF; + int r; + + assert(h); + assert(root_fd >= 0); + assert(reconciled >= 0); + + if (reconciled == USER_RECONCILE_IDENTICAL) + return 0; + + sys_base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (sys_base_dfd < 0) + return log_error_errno(errno, "Failed to open system blob dir: %m"); + + if (reconciled == USER_RECONCILE_HOST_WON) { + r = replace_blob_at(sys_base_dfd, h->user_name, root_fd, ".identity-blob", + h->blob_manifest, 0700, h->uid); + if (r < 0) + return log_error_errno(r, "Failed to replace embedded blobs with system blobs: %m"); + + log_info("Replaced embedded blob dir with contents of system blob dir."); + } else { + assert(reconciled == USER_RECONCILE_EMBEDDED_WON); + + r = replace_blob_at(root_fd, ".identity-blob", sys_base_dfd, h->user_name, + h->blob_manifest, 0755, 0); + if (r < 0) + return log_error_errno(r, "Failed to replace system blobs with embedded blobs: %m"); + + log_info("Replaced system blob dir with contents of embedded blob dir."); + } + return 0; +} + +int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs) { + _cleanup_free_ char *fn = NULL; + _cleanup_close_ int base_dfd = -EBADF, dfd = -EBADF; + uint64_t total_size = 0; + const char *filename; + const void *v; + int r; + + assert(h); + + if (!blobs) /* Shortcut: If no blobs are passed from dbus, we have nothing to do. */ + return 0; + + base_dfd = open(home_system_blob_dir(), O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (base_dfd < 0) + return log_error_errno(errno, "Failed to open system blob base dir: %m"); + + if (hashmap_isempty(blobs)) { + /* Shortcut: If blobs was passed but empty, we can simply delete the contents + * of the directory. */ + r = rm_rf_at(base_dfd, h->user_name, REMOVE_PHYSICAL|REMOVE_MISSING_OK); + if (r < 0) + return log_error_errno(r, "Failed to empty out system blob dir: %m"); + return 0; + } + + r = tempfn_random(h->user_name, NULL, &fn); + if (r < 0) + return r; + + dfd = open_mkdir_at(base_dfd, fn, O_EXCL|O_CLOEXEC, 0755); + if (dfd < 0) + return log_error_errno(errno, "Failed to create system blob dir: %m"); + + HASHMAP_FOREACH_KEY(v, filename, blobs) { + r = copy_one_blob(PTR_TO_FD(v), dfd, filename, &total_size, 0, h->blob_manifest); + if (r == -EFBIG) + break; + if (r < 0) { + log_error_errno(r, "Failed to copy %s into system blob dir: %m", filename); + goto fail; + } + } + + r = install_file(base_dfd, fn, base_dfd, h->user_name, INSTALL_REPLACE); + if (r < 0) { + log_error_errno(r, "Failed to move system blob dir into place: %m"); + goto fail; + } + + log_info("Replaced system blob directory."); + return 0; + +fail: + (void) rm_rf_at(base_dfd, fn, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_MISSING_OK); + return r; +} diff --git a/src/home/homework-blob.h b/src/home/homework-blob.h new file mode 100644 index 0000000..fbe6c82 --- /dev/null +++ b/src/home/homework-blob.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#pragma once + +#include "user-record.h" + +int home_reconcile_blob_dirs(UserRecord *h, int root_fd, int reconciled); + +int home_apply_new_blob_dir(UserRecord *h, Hashmap *blobs); diff --git a/src/home/homework-cifs.c b/src/home/homework-cifs.c index 5d87131..eb87b37 100644 --- a/src/home/homework-cifs.c +++ b/src/home/homework-cifs.c @@ -76,7 +76,7 @@ int home_setup_cifs( pid_t mount_pid; int exit_status; - passwd_fd = acquire_data_fd(*pw, strlen(*pw), /* flags= */ 0); + passwd_fd = acquire_data_fd(*pw); if (passwd_fd < 0) return log_error_errno(passwd_fd, "Failed to create data FD for password: %m"); @@ -94,7 +94,7 @@ int home_setup_cifs( r = setenvf("PASSWD_FD", /* overwrite= */ true, "%d", passwd_fd); if (r < 0) { - log_error_errno(errno, "Failed to set $PASSWD_FD: %m"); + log_error_errno(r, "Failed to set $PASSWD_FD: %m"); _exit(EXIT_FAILURE); } diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c index 6870ae9..ff88367 100644 --- a/src/home/homework-directory.c +++ b/src/home/homework-directory.c @@ -4,6 +4,7 @@ #include "btrfs-util.h" #include "fd-util.h" +#include "homework-blob.h" #include "homework-directory.h" #include "homework-mount.h" #include "homework-quota.h" @@ -265,7 +266,7 @@ int home_resize_directory( UserRecord **ret_home) { _cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL; - int r; + int r, reconciled; assert(h); assert(setup); @@ -276,9 +277,9 @@ int home_resize_directory( if (r < 0) return r; - r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home); - if (r < 0) - return r; + reconciled = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home); + if (reconciled < 0) + return reconciled; r = home_maybe_shift_uid(h, flags, setup); if (r < 0) @@ -290,7 +291,11 @@ int home_resize_directory( if (r < 0) return r; - r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled); if (r < 0) return r; diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c index 6aae1d2..92ce5c3 100644 --- a/src/home/homework-fscrypt.c +++ b/src/home/homework-fscrypt.c @@ -12,6 +12,7 @@ #include "homework-fscrypt.h" #include "homework-mount.h" #include "homework-quota.h" +#include "keyring-util.h" #include "memory-util.h" #include "missing_keyctl.h" #include "missing_syscall.h" @@ -29,6 +30,98 @@ #include "user-util.h" #include "xattr-util.h" +static int fscrypt_unlink_key(UserRecord *h) { + _cleanup_free_ void *keyring = NULL; + size_t keyring_size = 0, n_keys = 0; + int r; + + assert(h); + assert(user_record_storage(h) == USER_FSCRYPT); + + r = fully_set_uid_gid( + h->uid, + user_record_gid(h), + /* supplementary_gids= */ NULL, + /* n_supplementary_gids= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to change UID/GID to " UID_FMT "/" GID_FMT ": %m", + h->uid, user_record_gid(h)); + + r = keyring_read(KEY_SPEC_USER_KEYRING, &keyring, &keyring_size); + if (r < 0) + return log_error_errno(r, "Failed to read the keyring of user " UID_FMT ": %m", h->uid); + + n_keys = keyring_size / sizeof(key_serial_t); + assert(keyring_size % sizeof(key_serial_t) == 0); + + /* Find any key with a description starting with 'fscrypt:' and unlink it. We need to iterate as we + * store the key with a description that uses the hash of the secret key, that we do not have when + * we are deactivating. */ + FOREACH_ARRAY(key, ((key_serial_t *) keyring), n_keys) { + _cleanup_free_ char *description = NULL; + char *d; + + r = keyring_describe(*key, &description); + if (r < 0) { + if (r == -ENOKEY) /* Something else deleted it already, that's ok. */ + continue; + + return log_error_errno(r, "Failed to describe key id %d: %m", *key); + } + + /* The description is the final element as per manpage. */ + d = strrchr(description, ';'); + if (!d) + return log_error_errno( + SYNTHETIC_ERRNO(EINVAL), + "Failed to parse description of key id %d: %s", + *key, + description); + + if (!startswith(d + 1, "fscrypt:")) + continue; + + r = keyctl(KEYCTL_UNLINK, *key, KEY_SPEC_USER_KEYRING, 0, 0); + if (r < 0) { + if (errno == ENOKEY) /* Something else deleted it already, that's ok. */ + continue; + + return log_error_errno( + errno, + "Failed to delete encryption key with id '%d' from the keyring of user " UID_FMT ": %m", + *key, + h->uid); + } + + log_debug("Deleted encryption key with id '%d' from the keyring of user " UID_FMT ".", *key, h->uid); + } + + return 0; +} + +int home_flush_keyring_fscrypt(UserRecord *h) { + int r; + + assert(h); + assert(user_record_storage(h) == USER_FSCRYPT); + + if (!uid_is_valid(h->uid)) + return 0; + + r = safe_fork("(sd-delkey)", + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|FORK_REOPEN_LOG, + NULL); + if (r < 0) + return r; + if (r == 0) { + if (fscrypt_unlink_key(h) < 0) + _exit(EXIT_FAILURE); + _exit(EXIT_SUCCESS); + } + + return 0; +} + static int fscrypt_upload_volume_key( const uint8_t key_descriptor[static FS_KEY_DESCRIPTOR_SIZE], const void *volume_key, @@ -131,7 +224,7 @@ static int fscrypt_slot_try_one( salt, salt_size, 0xFFFF, EVP_sha512(), sizeof(derived), derived) != 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed."); context = EVP_CIPHER_CTX_new(); if (!context) @@ -212,14 +305,13 @@ static int fscrypt_setup( r = flistxattr_malloc(setup->root_fd, &xattr_buf); if (r < 0) - return log_error_errno(errno, "Failed to retrieve xattr list: %m"); + return log_error_errno(r, "Failed to retrieve xattr list: %m"); NULSTR_FOREACH(xa, xattr_buf) { _cleanup_free_ void *salt = NULL, *encrypted = NULL; _cleanup_free_ char *value = NULL; size_t salt_size, encrypted_size; const char *nr, *e; - char **list; int n; /* Check if this xattr has the format 'trusted.fscrypt_slot' where '' is a 32-bit unsigned integer */ @@ -237,31 +329,30 @@ static int fscrypt_setup( e = memchr(value, ':', n); if (!e) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator: %m", xa); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator.", xa); - r = unbase64mem(value, e - value, &salt, &salt_size); + r = unbase64mem_full(value, e - value, /* secure = */ false, &salt, &salt_size); if (r < 0) return log_error_errno(r, "Failed to decode salt of %s: %m", xa); - r = unbase64mem(e+1, n - (e - value) - 1, &encrypted, &encrypted_size); + + r = unbase64mem_full(e + 1, n - (e - value) - 1, /* secure = */ false, &encrypted, &encrypted_size); if (r < 0) return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa); r = -ENOANO; - FOREACH_POINTER(list, cache->pkcs11_passwords, cache->fido2_passwords, password) { + char **list; + FOREACH_ARGUMENT(list, cache->pkcs11_passwords, cache->fido2_passwords, password) { r = fscrypt_slot_try_many( list, salt, salt_size, encrypted, encrypted_size, setup->fscrypt_key_descriptor, ret_volume_key, ret_volume_key_size); - if (r != -ENOANO) - break; - } - if (r < 0) { + if (r >= 0) + return 0; if (r != -ENOANO) return r; - } else - return 0; + } } return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to set up home directory with provided passwords."); @@ -319,23 +410,11 @@ int home_setup_fscrypt( if (r < 0) return log_error_errno(r, "Failed install encryption key in user's keyring: %m"); if (r == 0) { - gid_t gid; - /* Child */ - gid = user_record_gid(h); - if (setresgid(gid, gid, gid) < 0) { - log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - _exit(EXIT_FAILURE); - } - - if (setgroups(0, NULL) < 0) { - log_error_errno(errno, "Failed to reset auxiliary groups list: %m"); - _exit(EXIT_FAILURE); - } - - if (setresuid(h->uid, h->uid, h->uid) < 0) { - log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", h->uid); + r = fully_set_uid_gid(h->uid, user_record_gid(h), /* supplementary_gids= */ NULL, /* n_supplementary_gids= */ 0); + if (r < 0) { + log_error_errno(r, "Failed to change UID/GID to " UID_FMT "/" GID_FMT ": %m", h->uid, user_record_gid(h)); _exit(EXIT_FAILURE); } @@ -649,7 +728,7 @@ int home_passwd_fscrypt( r = flistxattr_malloc(setup->root_fd, &xattr_buf); if (r < 0) - return log_error_errno(errno, "Failed to retrieve xattr list: %m"); + return log_error_errno(r, "Failed to retrieve xattr list: %m"); NULSTR_FOREACH(xa, xattr_buf) { const char *nr; diff --git a/src/home/homework-fscrypt.h b/src/home/homework-fscrypt.h index 7c2d7aa..289e9d8 100644 --- a/src/home/homework-fscrypt.h +++ b/src/home/homework-fscrypt.h @@ -9,3 +9,5 @@ int home_setup_fscrypt(UserRecord *h, HomeSetup *setup, const PasswordCache *cac int home_create_fscrypt(UserRecord *h, HomeSetup *setup, char **effective_passwords, UserRecord **ret_home); int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, const PasswordCache *cache, char **effective_passwords); + +int home_flush_keyring_fscrypt(UserRecord *h); diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 5bd78a0..20ff4c3 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -33,6 +33,7 @@ #include "glyph-util.h" #include "gpt.h" #include "home-util.h" +#include "homework-blob.h" #include "homework-luks.h" #include "homework-mount.h" #include "io-util.h" @@ -125,7 +126,6 @@ static int probe_file_system_by_fd( sd_id128_t *ret_uuid) { _cleanup_(blkid_free_probep) blkid_probe b = NULL; - _cleanup_free_ char *s = NULL; const char *fstype = NULL, *uuid = NULL; sd_id128_t id; int r; @@ -167,13 +167,10 @@ static int probe_file_system_by_fd( if (r < 0) return r; - s = strdup(fstype); - if (!s) - return -ENOMEM; - - *ret_fstype = TAKE_PTR(s); + r = strdup_to(ret_fstype, fstype); + if (r < 0) + return r; *ret_uuid = id; - return 0; } @@ -199,7 +196,7 @@ static int block_get_size_by_fd(int fd, uint64_t *ret) { if (!S_ISBLK(st.st_mode)) return -ENOTBLK; - return RET_NERRNO(ioctl(fd, BLKGETSIZE64, ret)); + return blockdev_get_device_size(fd, ret); } static int block_get_size_by_path(const char *path, uint64_t *ret) { @@ -259,43 +256,30 @@ static int run_fsck(const char *node, const char *fstype) { DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(key_serial_t, keyring_unlink, -1); -static int upload_to_keyring( - UserRecord *h, - const char *password, - key_serial_t *ret_key_serial) { +static int upload_to_keyring(UserRecord *h, const void *vk, size_t vks, key_serial_t *ret) { _cleanup_free_ char *name = NULL; key_serial_t serial; assert(h); - assert(password); - - /* If auto-shrink-on-logout is turned on, we need to keep the key we used to unlock the LUKS volume - * around, since we'll need it when automatically resizing (since we can't ask the user there - * again). We do this by uploading it into the kernel keyring, specifically the "session" one. This - * is done under the assumption systemd-homed gets its private per-session keyring (i.e. default - * service behaviour, given that KeyringMode=private is the default). It will survive between our - * systemd-homework invocations that way. - * - * If auto-shrink-on-logout is disabled we'll skip this step, to be frugal with sensitive data. */ - - if (user_record_auto_resize_mode(h) != AUTO_RESIZE_SHRINK_AND_GROW) { /* Won't need it */ - if (ret_key_serial) - *ret_key_serial = -1; - return 0; - } + assert(vk); + assert(vks > 0); + + /* We upload the LUKS volume key into the kernel session keyring, under the assumption that + * systemd-homed gets its own private session keyring (i.e. the default service behavior, given + * that KeyringMode=private is the default). That way, the key will survive between invocations + * of systemd-homework. */ name = strjoin("homework-user-", h->user_name); if (!name) return -ENOMEM; - serial = add_key("user", name, password, strlen(password), KEY_SPEC_SESSION_KEYRING); + serial = add_key("user", name, vk, vks, KEY_SPEC_SESSION_KEYRING); if (serial == -1) return -errno; - if (ret_key_serial) - *ret_key_serial = serial; - + if (ret) + *ret = serial; return 1; } @@ -304,13 +288,14 @@ static int luks_try_passwords( struct crypt_device *cd, char **passwords, void *volume_key, - size_t *volume_key_size, - key_serial_t *ret_key_serial) { + size_t *volume_key_size) { int r; assert(h); assert(cd); + assert(volume_key); + assert(volume_key_size); STRV_FOREACH(pp, passwords) { size_t vks = *volume_key_size; @@ -323,16 +308,6 @@ static int luks_try_passwords( *pp, strlen(*pp)); if (r >= 0) { - if (ret_key_serial) { - /* If ret_key_serial is non-NULL, let's try to upload the password that - * worked, and return its serial. */ - r = upload_to_keyring(h, *pp, ret_key_serial); - if (r < 0) { - log_debug_errno(r, "Failed to upload LUKS password to kernel keyring, ignoring: %m"); - *ret_key_serial = -1; - } - } - *volume_key_size = vks; return 0; } @@ -343,6 +318,66 @@ static int luks_try_passwords( return -ENOKEY; } +static int luks_get_volume_key( + UserRecord *h, + struct crypt_device *cd, + const PasswordCache *cache, + void *volume_key, + size_t *volume_key_size, + key_serial_t *ret_key_serial) { + + char **list; + size_t vks; + int r; + + assert(h); + assert(cd); + assert(volume_key); + assert(volume_key_size); + + if (cache && cache->volume_key) { + /* Shortcut: If volume key was loaded from the keyring then just use it */ + if (cache->volume_key_size > *volume_key_size) + return log_error_errno(SYNTHETIC_ERRNO(ENOBUFS), + "LUKS volume key from kernel keyring too big for buffer (need %zu bytes, have %zu).", + cache->volume_key_size, *volume_key_size); + memcpy(volume_key, cache->volume_key, cache->volume_key_size); + *volume_key_size = cache->volume_key_size; + if (ret_key_serial) + *ret_key_serial = -1; /* Key came from keyring. No need to re-upload it */ + return 0; + } + + vks = *volume_key_size; + + FOREACH_ARGUMENT(list, + cache ? cache->pkcs11_passwords : NULL, + cache ? cache->fido2_passwords : NULL, + h->password) { + + r = luks_try_passwords(h, cd, list, volume_key, &vks); + if (r == -ENOKEY) + continue; + if (r < 0) + return r; + + /* We got a volume key! */ + + if (ret_key_serial) { + r = upload_to_keyring(h, volume_key, vks, ret_key_serial); + if (r < 0) { + log_warning_errno(r, "Failed to upload LUKS volume key to kernel keyring, ignoring: %m"); + *ret_key_serial = -1; + } + } + + *volume_key_size = vks; + return 0; + } + + return -ENOKEY; +} + static int luks_setup( UserRecord *h, const char *node, @@ -351,7 +386,6 @@ static int luks_setup( const char *cipher, const char *cipher_mode, uint64_t volume_key_size, - char **passwords, const PasswordCache *cache, bool discard, struct crypt_device **ret, @@ -365,7 +399,6 @@ static int luks_setup( _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; size_t vks; - char **list; int r; assert(h); @@ -385,7 +418,7 @@ static int luks_setup( r = sym_crypt_get_volume_key_size(cd); if (r <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size."); vks = (size_t) r; if (!sd_id128_is_null(uuid) || ret_found_uuid) { @@ -418,16 +451,7 @@ static int luks_setup( if (!vk) return log_oom(); - r = -ENOKEY; - FOREACH_POINTER(list, - cache ? cache->keyring_passswords : NULL, - cache ? cache->pkcs11_passwords : NULL, - cache ? cache->fido2_passwords : NULL, - passwords) { - r = luks_try_passwords(h, cd, list, vk, &vks, ret_key_serial ? &key_serial : NULL); - if (r != -ENOKEY) - break; - } + r = luks_get_volume_key(h, cd, cache, vk, &vks, ret_key_serial ? &key_serial : NULL); if (r == -ENOKEY) return log_error_errno(r, "No valid password for LUKS superblock."); if (r < 0) @@ -519,7 +543,6 @@ static int luks_open( _cleanup_(erase_and_freep) void *vk = NULL; sd_id128_t p; - char **list; size_t vks; int r; @@ -540,7 +563,7 @@ static int luks_open( r = sym_crypt_get_volume_key_size(setup->crypt_device); if (r <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size."); vks = (size_t) r; if (ret_found_uuid) { @@ -559,16 +582,7 @@ static int luks_open( if (!vk) return log_oom(); - r = -ENOKEY; - FOREACH_POINTER(list, - cache ? cache->keyring_passswords : NULL, - cache ? cache->pkcs11_passwords : NULL, - cache ? cache->fido2_passwords : NULL, - h->password) { - r = luks_try_passwords(h, setup->crypt_device, list, vk, &vks, NULL); - if (r != -ENOKEY) - break; - } + r = luks_get_volume_key(h, setup->crypt_device, cache, vk, &vks, NULL); if (r == -ENOKEY) return log_error_errno(r, "No valid password for LUKS superblock."); if (r < 0) @@ -979,7 +993,7 @@ static int format_luks_token_text( assert((size_t) encrypted_size_out1 <= encrypted_size); if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted + encrypted_size_out1, &encrypted_size_out2) != 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of JSON record. "); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of JSON record."); assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size); @@ -1295,9 +1309,6 @@ int home_setup_luks( if (!IN_SET(errno, ENOTTY, EINVAL)) return log_error_errno(errno, "Failed to get block device metrics of %s: %m", n); - if (ioctl(setup->loop->fd, BLKGETSIZE64, &size) < 0) - return log_error_errno(r, "Failed to read block device size of %s: %m", n); - if (fstat(setup->loop->fd, &st) < 0) return log_error_errno(r, "Failed to stat block device %s: %m", n); assert(S_ISBLK(st.st_mode)); @@ -1329,6 +1340,8 @@ int home_setup_luks( offset *= 512U; } + + size = setup->loop->device_size; } else { #if HAVE_VALGRIND_MEMCHECK_H VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info)); @@ -1403,7 +1416,6 @@ int home_setup_luks( h->luks_cipher, h->luks_cipher_mode, h->luks_volume_key_size, - h->password, cache, user_record_luks_discard(h) || user_record_luks_offline_discard(h), &setup->crypt_device, @@ -1698,12 +1710,13 @@ static struct crypt_pbkdf_type* build_minimal_pbkdf(struct crypt_pbkdf_type *buf assert(hr); /* For PKCS#11 derived keys (which are generated randomly and are of high quality already) we use a - * minimal PBKDF */ + * minimal PBKDF and CRYPT_PBKDF_NO_BENCHMARK flag to skip benchmark. */ *buffer = (struct crypt_pbkdf_type) { .hash = user_record_luks_pbkdf_hash_algorithm(hr), .type = CRYPT_KDF_PBKDF2, - .iterations = 1, - .time_ms = 1, + .iterations = 1000, /* recommended minimum count for pbkdf2 + * according to NIST SP 800-132, ch. 5.2 */ + .flags = CRYPT_PBKDF_NO_BENCHMARK }; return buffer; @@ -2227,8 +2240,9 @@ int home_create_luks( if (flock(setup->image_fd, LOCK_EX) < 0) /* make sure udev doesn't read from it while we operate on the device */ return log_error_errno(errno, "Failed to lock block device %s: %m", ip); - if (ioctl(setup->image_fd, BLKGETSIZE64, &block_device_size) < 0) - return log_error_errno(errno, "Failed to read block device size: %m"); + r = blockdev_get_device_size(setup->image_fd, &block_device_size); + if (r < 0) + return log_error_errno(r, "Failed to read block device size: %m"); if (h->disk_size == UINT64_MAX) { @@ -2539,7 +2553,7 @@ static int can_resize_fs(int fd, uint64_t old_size, uint64_t new_size) { /* btrfs can grow and shrink online */ - } else if (is_fs_type(&sfs, XFS_SB_MAGIC)) { + } else if (is_fs_type(&sfs, XFS_SUPER_MAGIC)) { if (new_size < XFS_MINIMAL_SIZE) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "New file system size too small for xfs (needs to be 14M at least)."); @@ -2602,7 +2616,7 @@ static int ext4_offline_resize_fs( return r; if (r == 0) { /* Child */ - execlp("e2fsck" ,"e2fsck", "-fp", setup->dm_node, NULL); + execlp("e2fsck", "e2fsck", "-fp", setup->dm_node, NULL); log_open(); log_error_errno(errno, "Failed to execute e2fsck: %m"); _exit(EXIT_FAILURE); @@ -2634,7 +2648,7 @@ static int ext4_offline_resize_fs( return r; if (r == 0) { /* Child */ - execlp("resize2fs" ,"resize2fs", setup->dm_node, size_str, NULL); + execlp("resize2fs", "resize2fs", setup->dm_node, size_str, NULL); log_open(); log_error_errno(errno, "Failed to execute resize2fs: %m"); _exit(EXIT_FAILURE); @@ -2720,7 +2734,7 @@ static int prepare_resize_partition( p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) continue; @@ -3126,7 +3140,7 @@ int home_resize_luks( struct fdisk_partition *partition = NULL; _cleanup_close_ int opened_image_fd = -EBADF; _cleanup_free_ char *whole_disk = NULL; - int r, resize_type, image_fd = -EBADF; + int r, resize_type, image_fd = -EBADF, reconciled = USER_RECONCILE_IDENTICAL; sd_id128_t disk_uuid; const char *ip, *ipo; struct statfs sfs; @@ -3183,8 +3197,9 @@ int home_resize_luks( } else log_info("Operating on whole block device %s.", ip); - if (ioctl(image_fd, BLKGETSIZE64, &old_image_size) < 0) - return log_error_errno(errno, "Failed to determine size of original block device: %m"); + r = blockdev_get_device_size(image_fd, &old_image_size); + if (r < 0) + return log_error_errno(r, "Failed to determine size of original block device: %m"); if (flock(image_fd, LOCK_EX) < 0) /* make sure udev doesn't read from it while we operate on the device */ return log_error_errno(errno, "Failed to lock block device %s: %m", ip); @@ -3231,9 +3246,9 @@ int home_resize_luks( return r; if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) { - r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home); - if (r < 0) - return r; + reconciled = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, cache, &embedded_home, &new_home); + if (reconciled < 0) + return reconciled; } r = home_maybe_shift_uid(h, flags, setup); @@ -3444,7 +3459,11 @@ int home_resize_luks( /* → Shrink */ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) { - r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled); if (r < 0) return r; } @@ -3532,7 +3551,11 @@ int home_resize_luks( } else { /* → Grow */ if (!FLAGS_SET(flags, HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES)) { - r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled); if (r < 0) return r; } @@ -3582,7 +3605,6 @@ int home_passwd_luks( _cleanup_(erase_and_freep) void *volume_key = NULL; struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf; const char *type; - char **list; int r; assert(h); @@ -3611,17 +3633,7 @@ int home_passwd_luks( if (!volume_key) return log_oom(); - r = -ENOKEY; - FOREACH_POINTER(list, - cache ? cache->keyring_passswords : NULL, - cache ? cache->pkcs11_passwords : NULL, - cache ? cache->fido2_passwords : NULL, - h->password) { - - r = luks_try_passwords(h, setup->crypt_device, list, volume_key, &volume_key_size, NULL); - if (r != -ENOKEY) - break; - } + r = luks_get_volume_key(h, setup->crypt_device, cache, volume_key, &volume_key_size, NULL); if (r == -ENOKEY) return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords."); if (r < 0) @@ -3664,11 +3676,6 @@ int home_passwd_luks( return log_error_errno(r, "Failed to set up LUKS password: %m"); log_info("Updated LUKS key slot %zu.", i); - - /* If we changed the password, then make sure to update the copy in the keyring, so that - * auto-rebalance continues to work. We only do this if we operate on an active home dir. */ - if (i == 0 && FLAGS_SET(flags, HOME_SETUP_ALREADY_ACTIVATED)) - upload_to_keyring(h, effective_passwords[i], NULL); } return 1; @@ -3706,36 +3713,10 @@ int home_lock_luks(UserRecord *h, HomeSetup *setup) { return 0; } -static int luks_try_resume( - struct crypt_device *cd, - const char *dm_name, - char **password) { - - int r; - - assert(cd); - assert(dm_name); - - STRV_FOREACH(pp, password) { - r = sym_crypt_resume_by_passphrase( - cd, - dm_name, - CRYPT_ANY_SLOT, - *pp, - strlen(*pp)); - if (r >= 0) { - log_info("Resumed LUKS device %s.", dm_name); - return 0; - } - - log_debug_errno(r, "Password %zu didn't work for resuming device: %m", (size_t) (pp - password)); - } - - return -ENOKEY; -} - int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache) { - char **list; + _cleanup_(keyring_unlinkp) key_serial_t key_serial = -1; + _cleanup_(erase_and_freep) void *vk = NULL; + size_t vks; int r; assert(h); @@ -3748,20 +3729,27 @@ int home_unlock_luks(UserRecord *h, HomeSetup *setup, const PasswordCache *cache log_info("Discovered used LUKS device %s.", setup->dm_node); - r = -ENOKEY; - FOREACH_POINTER(list, - cache ? cache->pkcs11_passwords : NULL, - cache ? cache->fido2_passwords : NULL, - h->password) { - r = luks_try_resume(setup->crypt_device, setup->dm_name, list); - if (r != -ENOKEY) - break; - } + r = sym_crypt_get_volume_key_size(setup->crypt_device); + if (r <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size."); + vks = (size_t) r; + + vk = malloc(vks); + if (!vk) + return log_oom(); + + r = luks_get_volume_key(h, setup->crypt_device, cache, vk, &vks, &key_serial); if (r == -ENOKEY) return log_error_errno(r, "No valid password for LUKS superblock."); + if (r < 0) + return log_error_errno(r, "Failed to unlock LUKS superblock: %m"); + + r = sym_crypt_resume_by_volume_key(setup->crypt_device, setup->dm_name, vk, vks); if (r < 0) return log_error_errno(r, "Failed to resume LUKS superblock: %m"); + TAKE_KEY_SERIAL(key_serial); /* Leave key in kernel keyring */ + log_info("LUKS device resumed."); return 0; } diff --git a/src/home/homework-password-cache.c b/src/home/homework-password-cache.c index 00a0f69..b8202ef 100644 --- a/src/home/homework-password-cache.c +++ b/src/home/homework-password-cache.c @@ -9,49 +9,41 @@ void password_cache_free(PasswordCache *cache) { if (!cache) return; + cache->volume_key = erase_and_free(cache->volume_key); cache->pkcs11_passwords = strv_free_erase(cache->pkcs11_passwords); cache->fido2_passwords = strv_free_erase(cache->fido2_passwords); - cache->keyring_passswords = strv_free_erase(cache->keyring_passswords); } void password_cache_load_keyring(UserRecord *h, PasswordCache *cache) { - _cleanup_(erase_and_freep) void *p = NULL; _cleanup_free_ char *name = NULL; - char **strv; + _cleanup_(erase_and_freep) void *vk = NULL; + size_t vks; key_serial_t serial; - size_t sz; int r; assert(h); assert(cache); - /* Loads the password we need to for automatic resizing from the kernel keyring */ - name = strjoin("homework-user-", h->user_name); if (!name) return (void) log_oom(); serial = request_key("user", name, NULL, 0); - if (serial == -1) - return (void) log_debug_errno(errno, "Failed to request key '%s', ignoring: %m", name); - - r = keyring_read(serial, &p, &sz); + if (serial == -1) { + if (errno == ENOKEY) { + log_info("Home volume key is not available in kernel keyring."); + return; + } + return (void) log_warning_errno(errno, "Failed to request key '%s', ignoring: %m", name); + } + + r = keyring_read(serial, &vk, &vks); if (r < 0) - return (void) log_debug_errno(r, "Failed to read keyring key '%s', ignoring: %m", name); - - if (memchr(p, 0, sz)) - return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cached password contains embedded NUL byte, ignoring."); - - strv = new(char*, 2); - if (!strv) - return (void) log_oom(); - - strv[0] = TAKE_PTR(p); /* Note that keyring_read() will NUL terminate implicitly, hence we don't have - * to NUL terminate manually here: it's a valid string. */ - strv[1] = NULL; + return (void) log_warning_errno(r, "Failed to read keyring key '%s', ignoring: %m", name); - strv_free_erase(cache->keyring_passswords); - cache->keyring_passswords = strv; + log_info("Successfully acquired home volume key from kernel keyring."); - log_debug("Successfully acquired home key from kernel keyring."); + erase_and_free(cache->volume_key); + cache->volume_key = TAKE_PTR(vk); + cache->volume_key_size = vks; } diff --git a/src/home/homework-password-cache.h b/src/home/homework-password-cache.h index fdfbcfe..e2d86eb 100644 --- a/src/home/homework-password-cache.h +++ b/src/home/homework-password-cache.h @@ -5,8 +5,9 @@ #include "user-record.h" typedef struct PasswordCache { - /* Passwords acquired from the kernel keyring */ - char **keyring_passswords; + /* The volume key from the kernel keyring */ + void *volume_key; + size_t volume_key_size; /* Decoding passwords from security tokens is expensive and typically requires user interaction, * hence cache any we already figured out. */ @@ -20,9 +21,12 @@ static inline bool password_cache_contains(const PasswordCache *cache, const cha if (!cache) return false; + /* Used to decide whether or not to set a minimal PBKDF, under the assumption that if + * the cache contains a password then the password came from a hardware token of some kind + * and is thus naturally high-entropy. */ + return strv_contains(cache->pkcs11_passwords, p) || - strv_contains(cache->fido2_passwords, p) || - strv_contains(cache->keyring_passswords, p); + strv_contains(cache->fido2_passwords, p); } void password_cache_load_keyring(UserRecord *h, PasswordCache *cache); diff --git a/src/home/homework-quota.c b/src/home/homework-quota.c index 508c0c0..c951682 100644 --- a/src/home/homework-quota.c +++ b/src/home/homework-quota.c @@ -99,7 +99,7 @@ int home_update_quota_auto(UserRecord *h, const char *path) { if (statfs(path, &sfs) < 0) return log_error_errno(errno, "Failed to statfs() file system: %m"); - if (is_fs_type(&sfs, XFS_SB_MAGIC) || + if (is_fs_type(&sfs, XFS_SUPER_MAGIC) || is_fs_type(&sfs, EXT4_SUPER_MAGIC)) return home_update_quota_classic(h, path); @@ -107,7 +107,7 @@ int home_update_quota_auto(UserRecord *h, const char *path) { r = btrfs_is_subvol(path); if (r < 0) - return log_error_errno(errno, "Failed to test if %s is a subvolume: %m", path); + return log_error_errno(r, "Failed to test if %s is a subvolume: %m", path); if (r == 0) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Directory %s is not a subvolume, cannot apply quota.", path); diff --git a/src/home/homework.c b/src/home/homework.c index 066483e..482db23 100644 --- a/src/home/homework.c +++ b/src/home/homework.c @@ -4,13 +4,17 @@ #include #include "blockdev-util.h" +#include "bus-unit-util.h" #include "chown-recursive.h" #include "copy.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "filesystems.h" +#include "format-util.h" #include "fs-util.h" #include "home-util.h" +#include "homework-blob.h" #include "homework-cifs.h" #include "homework-directory.h" #include "homework-fido2.h" @@ -24,6 +28,7 @@ #include "memory-util.h" #include "missing_magic.h" #include "mount-util.h" +#include "parse-util.h" #include "path-util.h" #include "recovery-key.h" #include "rm-rf.h" @@ -51,6 +56,7 @@ int user_record_authenticate( assert(h); assert(secret); + assert(cache); /* Tries to authenticate a user record with the supplied secrets. i.e. checks whether at least one * supplied plaintext passwords matches a hashed password field of the user record. Or if a @@ -61,9 +67,25 @@ int user_record_authenticate( * times over the course of an operation (think: on login we authenticate the host user record, the * record embedded in the LUKS record and the one embedded in $HOME). Hence we keep a list of * passwords we already decrypted, so that we don't have to do the (slow and potentially interactive) - * PKCS#11/FIDO2 dance for the relevant token again and again. */ + * PKCS#11/FIDO2 dance for the relevant token again and again. + * + * The 'cache' parameter might also contain the LUKS volume key, loaded from the kernel keyring. + * In this case, authentication becomes optional - if a secret section is provided it will be + * verified, but if missing then authentication is skipped entirely. Thus, callers should + * consider carefully whether it is safe to load the volume key into 'cache' before doing so. + * Note that most of the time this is safe, because the home area must be active for the key + * to exist in the keyring, and the user would have had to authenticate when activating their + * home area; however, for some methods (i.e. ChangePassword, Authenticate) it makes more sense + * to force re-authentication. */ + + /* First, let's see if we already have a volume key from the keyring */ + if (cache->volume_key && + json_variant_is_blank_object(json_variant_by_key(secret->json, "secret"))) { + log_info("LUKS volume key from keyring unlocks user record."); + return 1; + } - /* First, let's see if the supplied plain-text passwords work? */ + /* Next, let's see if the supplied plain-text passwords work? */ r = user_record_test_password(h, secret); if (r == -ENOKEY) need_password = true; @@ -96,10 +118,10 @@ int user_record_authenticate( else log_info("None of the supplied plaintext passwords unlock the user record's hashed recovery keys."); - /* Second, test cached PKCS#11 passwords */ - for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) + /* Next, test cached PKCS#11 passwords */ + FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key) STRV_FOREACH(pp, cache->pkcs11_passwords) { - r = test_password_one(h->pkcs11_encrypted_key[n].hashed_password, *pp); + r = test_password_one(i->hashed_password, *pp); if (r < 0) return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m"); if (r > 0) { @@ -108,11 +130,11 @@ int user_record_authenticate( } } - /* Third, test cached FIDO2 passwords */ - for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) + /* Next, test cached FIDO2 passwords */ + FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt) /* See if any of the previously calculated passwords work */ STRV_FOREACH(pp, cache->fido2_passwords) { - r = test_password_one(h->fido2_hmac_salt[n].hashed_password, *pp); + r = test_password_one(i->hashed_password, *pp); if (r < 0) return log_error_errno(r, "Failed to check supplied FIDO2 password: %m"); if (r > 0) { @@ -121,13 +143,13 @@ int user_record_authenticate( } } - /* Fourth, let's see if any of the PKCS#11 security tokens are plugged in and help us */ - for (size_t n = 0; n < h->n_pkcs11_encrypted_key; n++) { + /* Next, let's see if any of the PKCS#11 security tokens are plugged in and help us */ + FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key) { #if HAVE_P11KIT _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { .user_record = h, .secret = secret, - .encrypted_key = h->pkcs11_encrypted_key + n, + .encrypted_key = i, }; r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data); @@ -161,7 +183,9 @@ int user_record_authenticate( if (r < 0) return log_error_errno(r, "Failed to test PKCS#11 password: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured PKCS#11 security token %s does not decrypt encrypted key correctly.", data.encrypted_key->uri); + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Configured PKCS#11 security token %s does not decrypt encrypted key correctly.", + data.encrypted_key->uri); log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri); @@ -177,12 +201,12 @@ int user_record_authenticate( #endif } - /* Fifth, let's see if any of the FIDO2 security tokens are plugged in and help us */ - for (size_t n = 0; n < h->n_fido2_hmac_salt; n++) { + /* Next, let's see if any of the FIDO2 security tokens are plugged in and help us */ + FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt) { #if HAVE_LIBFIDO2 _cleanup_(erase_and_freep) char *decrypted_password = NULL; - r = fido2_use_token(h, secret, h->fido2_hmac_salt + n, &decrypted_password); + r = fido2_use_token(h, secret, i, &decrypted_password); switch (r) { case -EAGAIN: need_token = true; @@ -209,11 +233,12 @@ int user_record_authenticate( if (r < 0) return r; - r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password); + r = test_password_one(i->hashed_password, decrypted_password); if (r < 0) return log_error_errno(r, "Failed to test FIDO2 password: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Configured FIDO2 security token does not decrypt encrypted key correctly."); + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Configured FIDO2 security token does not decrypt encrypted key correctly."); log_info("Decrypted password from FIDO2 security token unlocks user record."); @@ -275,10 +300,10 @@ static void drop_caches_now(void) { int r; /* Drop file system caches now. See https://docs.kernel.org/admin-guide/sysctl/vm.html - * for details. We write "2" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but + * for details. We write "3" into /proc/sys/vm/drop_caches to ensure dentries/inodes are flushed, but * not more. */ - r = write_string_file("/proc/sys/vm/drop_caches", "2\n", WRITE_STRING_FILE_DISABLE_BUFFER); + r = write_string_file("/proc/sys/vm/drop_caches", "3\n", WRITE_STRING_FILE_DISABLE_BUFFER); if (r < 0) log_warning_errno(r, "Failed to drop caches, ignoring: %m"); else @@ -354,6 +379,9 @@ static int keyring_flush(UserRecord *h) { assert(h); + if (user_record_storage(h) == USER_FSCRYPT) + (void) home_flush_keyring_fscrypt(h); + name = strjoin("homework-user-", h->user_name); if (!name) return log_oom(); @@ -638,7 +666,7 @@ int home_load_embedded_identity( * * · The record we got passed from the host * · The record included in the LUKS header (only if LUKS is used) - * · The record in the home directory itself (~.identity) + * · The record in the home directory itself (~/.identity) * * Now we have to reconcile all three, and let the newest one win. */ @@ -695,16 +723,15 @@ int home_load_embedded_identity( if (ret_new_home) *ret_new_home = TAKE_PTR(new_home); - return 0; + return r; /* We pass along who won the reconciliation */ } -int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home) { +int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home) { _cleanup_(user_record_unrefp) UserRecord *embedded = NULL; int r; assert(h); assert(root_fd >= 0); - assert(uid_is_valid(uid)); r = user_record_clone(h, USER_RECORD_EXTRACT_EMBEDDED|USER_RECORD_PERMISSIVE, &embedded); if (r < 0) @@ -827,7 +854,7 @@ int home_refresh( UserRecord **ret_new_home) { _cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL; - int r; + int r, reconciled; assert(h); assert(setup); @@ -836,9 +863,9 @@ int home_refresh( /* When activating a home directory, does the identity work: loads the identity from the $HOME * directory, reconciles it with our idea, chown()s everything. */ - r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home); - if (r < 0) - return r; + reconciled = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_ANY, cache, &embedded_home, &new_home); + if (reconciled < 0) + return reconciled; r = home_maybe_shift_uid(h, flags, setup); if (r < 0) @@ -848,7 +875,11 @@ int home_refresh( if (r < 0) return r; - r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup->root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup->root_fd, reconciled); if (r < 0) return r; @@ -1031,12 +1062,13 @@ static int home_deactivate(UserRecord *h, bool force) { return 0; } -static int copy_skel(int root_fd, const char *skel) { +static int copy_skel(UserRecord *h, int root_fd, const char *skel) { int r; + assert(h); assert(root_fd >= 0); - r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE, NULL, NULL); + r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", h->uid, h->gid, COPY_MERGE|COPY_REPLACE, NULL, NULL); if (r == -ENOENT) { log_info("Skeleton directory %s missing, ignoring.", skel); return 0; @@ -1064,11 +1096,15 @@ int home_populate(UserRecord *h, int dir_fd) { assert(h); assert(dir_fd >= 0); - r = copy_skel(dir_fd, user_record_skeleton_directory(h)); + r = copy_skel(h, dir_fd, user_record_skeleton_directory(h)); + if (r < 0) + return r; + + r = home_store_embedded_identity(h, dir_fd, NULL); if (r < 0) return r; - r = home_store_embedded_identity(h, dir_fd, h->uid, NULL); + r = home_reconcile_blob_dirs(h, dir_fd, USER_RECONCILE_HOST_WON); if (r < 0) return r; @@ -1089,7 +1125,6 @@ static int user_record_compile_effective_passwords( char ***ret_effective_passwords) { _cleanup_strv_free_erase_ char **effective = NULL; - size_t n; int r; assert(h); @@ -1134,17 +1169,16 @@ static int user_record_compile_effective_passwords( return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Missing plaintext password for defined hashed password"); } - for (n = 0; n < h->n_recovery_key; n++) { + FOREACH_ARRAY(i, h->recovery_key, h->n_recovery_key) { bool found = false; - log_debug("Looking for plaintext recovery key for: %s", h->recovery_key[n].hashed_password); + log_debug("Looking for plaintext recovery key for: %s", i->hashed_password); STRV_FOREACH(j, h->password) { _cleanup_(erase_and_freep) char *mangled = NULL; const char *p; - if (streq(h->recovery_key[n].type, "modhex64")) { - + if (streq(i->type, "modhex64")) { r = normalize_recovery_key(*j, &mangled); if (r == -EINVAL) /* Not properly formatted, probably a regular password. */ continue; @@ -1155,7 +1189,7 @@ static int user_record_compile_effective_passwords( } else p = *j; - r = test_password_one(h->recovery_key[n].hashed_password, p); + r = test_password_one(i->hashed_password, p); if (r < 0) return log_error_errno(r, "Failed to test plaintext recovery key: %m"); if (r > 0) { @@ -1172,15 +1206,16 @@ static int user_record_compile_effective_passwords( } if (!found) - return log_error_errno(SYNTHETIC_ERRNO(EREMOTEIO), "Missing plaintext recovery key for defined recovery key"); + return log_error_errno(SYNTHETIC_ERRNO(EREMOTEIO), + "Missing plaintext recovery key for defined recovery key."); } - for (n = 0; n < h->n_pkcs11_encrypted_key; n++) { + FOREACH_ARRAY(i, h->pkcs11_encrypted_key, h->n_pkcs11_encrypted_key) { #if HAVE_P11KIT _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = { .user_record = h, .secret = h, - .encrypted_key = h->pkcs11_encrypted_key + n, + .encrypted_key = i, }; r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data); @@ -1209,19 +1244,20 @@ static int user_record_compile_effective_passwords( #endif } - for (n = 0; n < h->n_fido2_hmac_salt; n++) { + FOREACH_ARRAY(i, h->fido2_hmac_salt, h->n_fido2_hmac_salt) { #if HAVE_LIBFIDO2 _cleanup_(erase_and_freep) char *decrypted_password = NULL; - r = fido2_use_token(h, h, h->fido2_hmac_salt + n, &decrypted_password); + r = fido2_use_token(h, h, i, &decrypted_password); if (r < 0) return r; - r = test_password_one(h->fido2_hmac_salt[n].hashed_password, decrypted_password); + r = test_password_one(i->hashed_password, decrypted_password); if (r < 0) return log_error_errno(r, "Failed to test FIDO2 password: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Decrypted password from token is not correct, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EPERM), + "Decrypted password from token is not correct, refusing."); if (ret_effective_passwords) { r = strv_extend(&effective, decrypted_password); @@ -1306,7 +1342,7 @@ static int determine_default_storage(UserStorage *ret) { return 0; } -static int home_create(UserRecord *h, UserRecord **ret_home) { +static int home_create(UserRecord *h, Hashmap *blobs, UserRecord **ret_home) { _cleanup_strv_free_erase_ char **effective_passwords = NULL; _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; _cleanup_(user_record_unrefp) UserRecord *new_home = NULL; @@ -1368,6 +1404,10 @@ static int home_create(UserRecord *h, UserRecord **ret_home) { if (!IN_SET(r, USER_TEST_ABSENT, USER_TEST_UNDEFINED, USER_TEST_MAYBE)) return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Image path %s already exists, refusing.", user_record_image_path(h)); + r = home_apply_new_blob_dir(h, blobs); + if (r < 0) + return r; + switch (user_record_storage(h)) { case USER_LUKS: @@ -1519,20 +1559,32 @@ static int home_remove(UserRecord *h) { return 0; } -static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) { - bool has_mount = false; - int r; - +static int home_basic_validate_update(UserRecord *h) { assert(h); - assert(setup); if (!h->user_name) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, refusing."); + if (!uid_is_valid(h->uid)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing."); + if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS)) return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h))); + return 0; +} + +static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags *flags) { + bool has_mount = false; + int r; + + assert(h); + assert(setup); + + r = home_basic_validate_update(h); + if (r < 0) + return r; + r = user_record_test_home_directory_and_warn(h); if (r < 0) return r; @@ -1573,25 +1625,48 @@ static int home_validate_update(UserRecord *h, HomeSetup *setup, HomeSetupFlags return has_mount; /* return true if the home record is already active */ } -static int home_update(UserRecord *h, UserRecord **ret) { +static int home_update(UserRecord *h, Hashmap *blobs, UserRecord **ret) { _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL; _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; _cleanup_(password_cache_free) PasswordCache cache = {}; HomeSetupFlags flags = 0; + bool offline; int r; assert(h); assert(ret); - r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true); + offline = getenv_bool("SYSTEMD_HOMEWORK_UPDATE_OFFLINE") > 0; + + if (!offline) { + password_cache_load_keyring(h, &cache); + + r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true); + if (r < 0) + return r; + assert(r > 0); /* Insist that a password was verified */ + + r = home_validate_update(h, &setup, &flags); + } else { + /* In offline mode we skip all authentication, since we're + * not propagating anything into the home area. The new home + * records's authentication will still be checked when the user + * next logs in, so this is fine */ + + r = home_basic_validate_update(h); + } if (r < 0) return r; - assert(r > 0); /* Insist that a password was verified */ - r = home_validate_update(h, &setup, &flags); + r = home_apply_new_blob_dir(h, blobs); if (r < 0) return r; + if (offline) { + log_info("Offline update requested. Not touching embedded records."); + return user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_PERMISSIVE, ret); + } + r = home_setup(h, flags, &setup, &cache, &header_home); if (r < 0) return r; @@ -1608,7 +1683,11 @@ static int home_update(UserRecord *h, UserRecord **ret) { if (r < 0) return r; - r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup.root_fd, USER_RECONCILE_HOST_WON); if (r < 0) return r; @@ -1630,7 +1709,7 @@ static int home_update(UserRecord *h, UserRecord **ret) { return 0; } -static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) { +static int home_resize(UserRecord *h, UserRecord **ret) { _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; _cleanup_(password_cache_free) PasswordCache cache = {}; HomeSetupFlags flags = 0; @@ -1642,25 +1721,16 @@ static int home_resize(UserRecord *h, bool automatic, UserRecord **ret) { if (h->disk_size == UINT64_MAX) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing."); - if (automatic) - /* In automatic mode don't want to ask the user for the password, hence load it from the kernel keyring */ - password_cache_load_keyring(h, &cache); - else { - /* In manual mode let's ensure the user is fully authenticated */ - r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true); - if (r < 0) - return r; - assert(r > 0); /* Insist that a password was verified */ - } + password_cache_load_keyring(h, &cache); - r = home_validate_update(h, &setup, &flags); + r = user_record_authenticate(h, h, &cache, /* strict_verify= */ true); if (r < 0) return r; + assert(r > 0); /* Insist that a password was verified */ - /* In automatic mode let's skip syncing identities, because we can't validate them, since we can't - * ask the user for reauthentication */ - if (automatic) - flags |= HOME_SETUP_RESIZE_DONT_SYNC_IDENTITIES; + r = home_validate_update(h, &setup, &flags); + if (r < 0) + return r; switch (user_record_storage(h)) { @@ -1683,7 +1753,7 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) { _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; _cleanup_(password_cache_free) PasswordCache cache = {}; HomeSetupFlags flags = 0; - int r; + int r, reconciled; assert(h); assert(ret_home); @@ -1703,9 +1773,9 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) { if (r < 0) return r; - r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home); - if (r < 0) - return r; + reconciled = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &cache, &embedded_home, &new_home); + if (reconciled < 0) + return reconciled; r = home_maybe_shift_uid(h, flags, &setup); if (r < 0) @@ -1733,7 +1803,11 @@ static int home_passwd(UserRecord *h, UserRecord **ret_home) { if (r < 0) return r; - r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home); + r = home_store_embedded_identity(new_home, setup.root_fd, embedded_home); + if (r < 0) + return r; + + r = home_reconcile_blob_dirs(new_home, setup.root_fd, reconciled); if (r < 0) return r; @@ -1795,6 +1869,38 @@ static int home_inspect(UserRecord *h, UserRecord **ret_home) { return 1; } +static int user_session_freezer(uid_t uid, bool freeze_now, UnitFreezer **ret) { + _cleanup_free_ char *unit = NULL; + int r; + + assert(uid_is_valid(uid)); + assert(ret); + + r = getenv_bool("SYSTEMD_HOME_LOCK_FREEZE_SESSION"); + if (r < 0 && r != -ENXIO) + log_warning_errno(r, "Cannot parse value of $SYSTEMD_HOME_LOCK_FREEZE_SESSION, ignoring: %m"); + else if (r == 0) { + if (freeze_now) + log_notice("Session remains unfrozen on explicit request ($SYSTEMD_HOME_LOCK_FREEZE_SESSION=0).\n" + "This is not recommended, and might result in unexpected behavior including data loss!"); + + *ret = NULL; + return 0; + } + + if (asprintf(&unit, "user-" UID_FMT ".slice", uid) < 0) + return log_oom(); + + if (freeze_now) + r = unit_freezer_new_freeze(unit, ret); + else + r = unit_freezer_new(unit, ret); + if (r < 0) + return r; + + return 1; +} + static int home_lock(UserRecord *h) { _cleanup_(home_setup_done) HomeSetup setup = HOME_SETUP_INIT; int r; @@ -1812,10 +1918,23 @@ static int home_lock(UserRecord *h) { if (r != USER_TEST_MOUNTED) return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home directory of %s is not mounted, can't lock.", h->user_name); - r = home_lock_luks(h, &setup); + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + + r = user_session_freezer(h->uid, /* freeze_now= */ true, &f); if (r < 0) return r; + r = home_lock_luks(h, &setup); + if (r < 0) { + if (f) + (void) unit_freezer_thaw(f); + + return r; + } + + /* Explicitly flush any per-user key from the keyring */ + (void) keyring_flush(h); + log_info("Everything completed."); return 1; } @@ -1843,6 +1962,15 @@ static int home_unlock(UserRecord *h) { if (r < 0) return r; + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + + /* We want to thaw the session only after it's safe to access $HOME */ + r = user_session_freezer(h->uid, /* freeze_now= */ false, &f); + if (r > 0) + r = unit_freezer_thaw(f); + if (r < 0) + return r; + log_info("Everything completed."); return 1; } @@ -1851,10 +1979,12 @@ static int run(int argc, char *argv[]) { _cleanup_(user_record_unrefp) UserRecord *home = NULL, *new_home = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_fclose_ FILE *opened_file = NULL; + _cleanup_hashmap_free_ Hashmap *blobs = NULL; unsigned line = 0, column = 0; - const char *json_path = NULL; + const char *json_path = NULL, *blob_filename; FILE *json_file; usec_t start; + JsonVariant *fdmap, *blob_fd_variant; int r; start = now(CLOCK_MONOTONIC); @@ -1885,6 +2015,48 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column); + fdmap = json_variant_by_key(v, HOMEWORK_BLOB_FDMAP_FIELD); + if (fdmap) { + r = hashmap_ensure_allocated(&blobs, &blob_fd_hash_ops); + if (r < 0) + return log_oom(); + + JSON_VARIANT_OBJECT_FOREACH(blob_filename, blob_fd_variant, fdmap) { + _cleanup_free_ char *filename = NULL; + _cleanup_close_ int fd = -EBADF; + + assert(json_variant_is_integer(blob_fd_variant)); + assert(json_variant_integer(blob_fd_variant) >= 0); + assert(json_variant_integer(blob_fd_variant) <= INT_MAX - SD_LISTEN_FDS_START); + fd = SD_LISTEN_FDS_START + (int) json_variant_integer(blob_fd_variant); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *resolved = NULL; + r = fd_get_path(fd, &resolved); + log_debug("Got blob from daemon: %s (%d) → %s", + blob_filename, fd, resolved ?: STRERROR(r)); + } + + filename = strdup(blob_filename); + if (!filename) + return log_oom(); + + r = fd_cloexec(fd, true); + if (r < 0) + return log_error_errno(r, "Failed to enable O_CLOEXEC on blob %s: %m", filename); + + r = hashmap_put(blobs, filename, FD_TO_PTR(fd)); + if (r < 0) + return log_error_errno(r, "Failed to insert blob %s into map: %m", filename); + TAKE_PTR(filename); /* Ownership transfers to hashmap */ + TAKE_FD(fd); + } + + r = json_variant_filter(&v, STRV_MAKE(HOMEWORK_BLOB_FDMAP_FIELD)); + if (r < 0) + return log_error_errno(r, "Failed to strip internal fdmap from JSON: %m"); + } + home = user_record_new(); if (!home) return log_oom(); @@ -1928,15 +2100,13 @@ static int run(int argc, char *argv[]) { else if (streq(argv[1], "deactivate-force")) r = home_deactivate(home, true); else if (streq(argv[1], "create")) - r = home_create(home, &new_home); + r = home_create(home, blobs, &new_home); else if (streq(argv[1], "remove")) r = home_remove(home); else if (streq(argv[1], "update")) - r = home_update(home, &new_home); - else if (streq(argv[1], "resize")) /* Resize on user request */ - r = home_resize(home, false, &new_home); - else if (streq(argv[1], "resize-auto")) /* Automatic resize */ - r = home_resize(home, true, &new_home); + r = home_update(home, blobs, &new_home); + else if (streq(argv[1], "resize")) + r = home_resize(home, &new_home); else if (streq(argv[1], "passwd")) r = home_passwd(home, &new_home); else if (streq(argv[1], "inspect")) diff --git a/src/home/homework.h b/src/home/homework.h index cef3f4e..fb2b43e 100644 --- a/src/home/homework.h +++ b/src/home/homework.h @@ -87,7 +87,7 @@ int home_maybe_shift_uid(UserRecord *h, HomeSetupFlags flags, HomeSetup *setup); int home_populate(UserRecord *h, int dir_fd); int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, PasswordCache *cache, UserRecord **ret_embedded_home, UserRecord **ret_new_home); -int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, UserRecord *old_home); +int home_store_embedded_identity(UserRecord *h, int root_fd, UserRecord *old_home); int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup); int user_record_authenticate(UserRecord *h, UserRecord *secret, PasswordCache *cache, bool strict_verify); diff --git a/src/home/meson.build b/src/home/meson.build index 09831de..f573c5f 100644 --- a/src/home/meson.build +++ b/src/home/meson.build @@ -2,6 +2,7 @@ systemd_homework_sources = files( 'home-util.c', + 'homework-blob.c', 'homework-cifs.c', 'homework-directory.c', 'homework-fscrypt.c', @@ -105,6 +106,11 @@ executables += [ threads, ], }, + test_template + { + 'sources' : files('test-homed-regression-31896.c'), + 'conditions' : ['ENABLE_HOMED'], + 'type' : 'manual', + }, ] modules += [ @@ -137,4 +143,8 @@ if conf.get('ENABLE_HOMED') == 1 install_data('homed.conf', install_dir : pkgconfigfiledir) endif + + meson.add_install_script(sh, '-c', + ln_s.format(bindir / 'homectl', + bindir / 'systemd-home-fallback-shell')) endif diff --git a/src/home/org.freedesktop.home1.conf b/src/home/org.freedesktop.home1.conf index 5af1a68..b808592 100644 --- a/src/home/org.freedesktop.home1.conf +++ b/src/home/org.freedesktop.home1.conf @@ -57,6 +57,10 @@ send_interface="org.freedesktop.home1.Manager" send_member="ActivateHome"/> + + @@ -73,6 +77,10 @@ send_interface="org.freedesktop.home1.Manager" send_member="CreateHome"/> + + @@ -93,6 +101,10 @@ send_interface="org.freedesktop.home1.Manager" send_member="UpdateHome"/> + + @@ -117,6 +129,10 @@ send_interface="org.freedesktop.home1.Manager" send_member="RefHome"/> + + @@ -139,6 +155,10 @@ send_interface="org.freedesktop.home1.Home" send_member="Activate"/> + + @@ -167,6 +187,10 @@ send_interface="org.freedesktop.home1.Home" send_member="Update"/> + + @@ -191,6 +215,10 @@ send_interface="org.freedesktop.home1.Home" send_member="Ref"/> + + diff --git a/src/home/org.freedesktop.home1.policy b/src/home/org.freedesktop.home1.policy index a337b32..3b19ed3 100644 --- a/src/home/org.freedesktop.home1.policy +++ b/src/home/org.freedesktop.home1.policy @@ -69,4 +69,13 @@ + + Activate a home area + Authentication is required to activate a user's home area. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c index ba8d8f6..4616f08 100644 --- a/src/home/pam_systemd_home.c +++ b/src/home/pam_systemd_home.c @@ -20,10 +20,16 @@ #include "user-record.h" #include "user-util.h" +typedef enum AcquireHomeFlags { + ACQUIRE_MUST_AUTHENTICATE = 1 << 0, + ACQUIRE_PLEASE_SUSPEND = 1 << 1, + ACQUIRE_REF_ANYWAY = 1 << 2, +} AcquireHomeFlags; + static int parse_argv( pam_handle_t *handle, int argc, const char **argv, - bool *please_suspend, + AcquireHomeFlags *flags, bool *debug) { assert(argc >= 0); @@ -38,8 +44,8 @@ static int parse_argv( k = parse_boolean(v); if (k < 0) pam_syslog(handle, LOG_WARNING, "Failed to parse suspend= argument, ignoring: %s", v); - else if (please_suspend) - *please_suspend = k; + else if (flags) + SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, k); } else if (streq(argv[i], "debug")) { if (debug) @@ -62,7 +68,7 @@ static int parse_argv( static int parse_env( pam_handle_t *handle, - bool *please_suspend) { + AcquireHomeFlags *flags) { const char *v; int r; @@ -83,8 +89,8 @@ static int parse_env( r = parse_boolean(v); if (r < 0) pam_syslog(handle, LOG_WARNING, "Failed to parse $SYSTEMD_HOME_SUSPEND argument, ignoring: %s", v); - else if (please_suspend) - *please_suspend = r; + else if (flags) + SET_FLAG(*flags, ACQUIRE_PLEASE_SUSPEND, r); return 0; } @@ -99,7 +105,6 @@ static int acquire_user_record( _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; _cleanup_free_ char *homed_field = NULL; const char *json = NULL; int r; @@ -142,6 +147,7 @@ static int acquire_user_record( } else { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *generic_field = NULL, *json_copy = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); if (r != PAM_SUCCESS) @@ -275,21 +281,21 @@ static int handle_generic_user_record_error( const sd_bus_error *error, bool debug) { + int r; + assert(user_name); assert(error); - int r; - /* Logs about all errors, except for PAM_CONV_ERR, i.e. when requesting more info failed. */ if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently absent, please plug in the necessary storage device or backing file system."), user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_PERM_DENIED, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret)); } else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too frequent login attempts for user %s, try again later."), user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret)); @@ -301,10 +307,10 @@ static int handle_generic_user_record_error( /* This didn't work? Ask for an (additional?) password */ if (strv_isempty(secret->password)) - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Password: ")); else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient for authentication of user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, try again: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -326,10 +332,10 @@ static int handle_generic_user_record_error( /* Hmm, homed asks for recovery key (because no regular password is defined maybe)? Provide it. */ if (strv_isempty(secret->password)) - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Recovery key: ")); else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password/recovery key incorrect or not sufficient for authentication of user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, reenter recovery key: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -349,11 +355,11 @@ static int handle_generic_user_record_error( assert(secret); if (strv_isempty(secret->password)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token of user %s not inserted."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); } else { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password incorrect or not sufficient, and configured security token of user %s not inserted."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Try again with password: ")); } if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -363,7 +369,6 @@ static int handle_generic_user_record_error( return PAM_AUTHTOK_ERR; } - r = user_record_set_password(secret, STRV_MAKE(newp), true); if (r < 0) return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store password: %m"); @@ -373,7 +378,7 @@ static int handle_generic_user_record_error( assert(secret); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: ")); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -390,7 +395,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please authenticate physically on security token of user %s."), user_name); r = user_record_set_pkcs11_protected_authentication_path_permitted(secret, true); if (r < 0) @@ -401,7 +406,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please confirm presence on security token of user %s."), user_name); r = user_record_set_fido2_user_presence_permitted(secret, true); if (r < 0) @@ -412,7 +417,7 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Please verify user on security token of user %s."), user_name); r = user_record_set_fido2_user_verification_permitted(secret, true); if (r < 0) @@ -421,7 +426,7 @@ static int handle_generic_user_record_error( } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED)) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN is locked, please unlock it first. (Hint: Removal and re-insertion might suffice.)")); return PAM_SERVICE_ERR; } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) { @@ -429,8 +434,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN incorrect for user %s."), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -448,8 +453,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only a few tries left!)"), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -467,8 +472,8 @@ static int handle_generic_user_record_error( assert(secret); - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name); - r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Security token PIN of user %s incorrect (only one try left!)"), user_name); + r = pam_prompt_graceful(handle, PAM_PROMPT_ECHO_OFF, &newp, _("Sorry, retry security token PIN: ")); if (r != PAM_SUCCESS) return PAM_CONV_ERR; /* no logging here */ @@ -490,14 +495,12 @@ static int handle_generic_user_record_error( static int acquire_home( pam_handle_t *handle, - bool please_authenticate, - bool please_suspend, + AcquireHomeFlags flags, bool debug, PamBusData **bus_data) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *secret = NULL; - bool do_auth = please_authenticate, home_not_active = false, home_locked = false; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + bool do_auth = FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE), home_not_active = false, home_locked = false, unrestricted = false; _cleanup_close_ int acquired_fd = -EBADF; _cleanup_free_ char *fd_field = NULL; const void *home_fd_ptr = NULL; @@ -507,13 +510,27 @@ static int acquire_home( assert(handle); - /* This acquires a reference to a home directory in one of two ways: if please_authenticate is true, - * then we'll call AcquireHome() after asking the user for a password. Otherwise it tries to call - * RefHome() and if that fails queries the user for a password and uses AcquireHome(). + /* This acquires a reference to a home directory in the following ways: + * + * 1. If please_authenticate is false, it tries to call RefHome() first — which + * will get us a reference to the home without authentication (which will work for homes that are + * not encrypted, or that already are activated). If this works, we are done. Yay! * - * The idea is that the PAM authentication hook sets please_authenticate and thus always - * authenticates, while the other PAM hooks unset it so that they can a ref of their own without - * authentication if possible, but with authentication if necessary. */ + * 2. Otherwise, we'll call AcquireHome() — which will try to activate the home getting us a + * reference. If this works, we are done. Yay! + * + * 3. if ref_anyway, we'll call RefHomeUnrestricted() — which will give us a reference in any case + * (even if the activation failed!). + * + * The idea is that please_authenticate is set to false for the PAM session hooks (since for those + * authentication doesn't matter), and true for the PAM authentication hooks (since for those + * authentication is essential). And ref_anyway should be set if we are pretty sure that we can later + * activate the home directory via our fallback shell logic, and hence are OK if we can't activate + * things here. Usecase for that are SSH logins where SSH does the authentication and thus only the + * session hooks are called. But from the session hooks SSH doesn't allow asking questions, hence we + * simply allow the login attempt to continue but then invoke our fallback shell that will prompt the + * user for the missing unlock credentials, and then chainload the real shell. + */ r = pam_get_user(handle, &username, NULL); if (r != PAM_SUCCESS) @@ -534,25 +551,26 @@ static int acquire_home( if (r == PAM_SUCCESS && PTR_TO_FD(home_fd_ptr) >= 0) return PAM_SUCCESS; - r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); - if (r != PAM_SUCCESS) - return r; - r = acquire_user_record(handle, username, debug, &ur, bus_data); if (r != PAM_SUCCESS) return r; /* Implement our own retry loop here instead of relying on the PAM client's one. That's because it - * might happen that the record we stored on the host does not match the encryption password of - * the LUKS image in case the image was used in a different system where the password was - * changed. In that case it will happen that the LUKS password and the host password are - * different, and we handle that by collecting and passing multiple passwords in that case. Hence we - * treat bad passwords as a request to collect one more password and pass the new all all previously - * used passwords again. */ + * might happen that the record we stored on the host does not match the encryption password of the + * LUKS image in case the image was used in a different system where the password was changed. In + * that case it will happen that the LUKS password and the host password are different, and we handle + * that by collecting and passing multiple passwords in that case. Hence we treat bad passwords as a + * request to collect one more password and pass the new and all previously used passwords again. */ + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, bus_data); + if (r != PAM_SUCCESS) + return r; for (;;) { _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; + const char *method = NULL; if (do_auth && !secret) { const char *cached_password = NULL; @@ -576,7 +594,14 @@ static int acquire_home( } } - r = bus_message_new_method_call(bus, &m, bus_home_mgr, do_auth ? "AcquireHome" : "RefHome"); + if (do_auth) + method = "AcquireHome"; /* If we shall authenticate no matter what */ + else if (unrestricted) + method = "RefHomeUnrestricted"; /* If we shall get a ref no matter what */ + else + method = "RefHome"; /* If we shall get a ref (if possible) */ + + r = bus_message_new_method_call(bus, &m, bus_home_mgr, method); if (r < 0) return pam_bus_log_create_error(handle, r); @@ -590,21 +615,22 @@ static int acquire_home( return pam_bus_log_create_error(handle, r); } - r = sd_bus_message_append(m, "b", please_suspend); + r = sd_bus_message_append(m, "b", FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND)); if (r < 0) return pam_bus_log_create_error(handle, r); r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply); if (r < 0) { - - if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_NOT_ACTIVE)) + if (sd_bus_error_has_names(&error, BUS_ERROR_HOME_NOT_ACTIVE, BUS_ERROR_HOME_BUSY)) { /* Only on RefHome(): We can't access the home directory currently, unless * it's unlocked with a password. Hence, let's try this again, this time with * authentication. */ home_not_active = true; - else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) + do_auth = true; + } else if (sd_bus_error_has_name(&error, BUS_ERROR_HOME_LOCKED)) { home_locked = true; /* Similar */ - else { + do_auth = true; + } else { r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error, debug); if (r == PAM_CONV_ERR) { /* Password/PIN prompts will fail in certain environments, for example when @@ -612,20 +638,26 @@ static int acquire_home( * per-service PAM logic. In that case, print a friendly message and accept * failure. */ - if (home_not_active) - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name); - if (home_locked) - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); + if (!FLAGS_SET(flags, ACQUIRE_REF_ANYWAY)) { + if (home_not_active) + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently not active, please log in locally first."), ur->user_name); + if (home_locked) + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Home of user %s is currently locked, please unlock locally first."), ur->user_name); + + if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) || debug) + pam_syslog(handle, FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt."); - if (please_authenticate || debug) - pam_syslog(handle, please_authenticate ? LOG_ERR : LOG_DEBUG, "Failed to prompt for password/prompt."); + return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR; + } - return home_not_active || home_locked ? PAM_PERM_DENIED : PAM_CONV_ERR; - } - if (r != PAM_SUCCESS) + /* ref_anyway is true, hence let's now get a ref no matter what. */ + unrestricted = true; + do_auth = false; + } else if (r != PAM_SUCCESS) return r; + else + do_auth = true; /* The issue was dealt with, some more information was collected. Let's try to authenticate, again. */ } - } else { int fd; @@ -641,18 +673,15 @@ static int acquire_home( } if (++n_attempts >= 5) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many unsuccessful login attempts for user %s, refusing."), ur->user_name); return pam_syslog_pam_error(handle, LOG_ERR, PAM_MAXTRIES, "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r)); } - - /* Try again, this time with authentication if we didn't do that before. */ - do_auth = true; } /* Later PAM modules may need the auth token, but only during pam_authenticate. */ - if (please_authenticate && !strv_isempty(secret->password)) { + if (FLAGS_SET(flags, ACQUIRE_MUST_AUTHENTICATE) && !strv_isempty(secret->password)) { r = pam_set_item(handle, PAM_AUTHTOK, *secret->password); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM auth token: @PAMERR@"); @@ -672,7 +701,19 @@ static int acquire_home( return r; } - pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + /* If we didn't actually manage to unlock the home directory, then we rely on the fallback-shell to + * unlock it for us. But until that happens we don't want that logind spawns the per-user service + * manager for us (since it would see an inaccessible home directory). Hence set an environment + * variable that pam_systemd looks for). */ + if (unrestricted) { + r = pam_putenv(handle, "XDG_SESSION_INCOMPLETE=1"); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_WARNING, r, "Failed to set XDG_SESSION_INCOMPLETE= environment variable: @PAMERR@"); + + pam_syslog(handle, LOG_NOTICE, "Home for user %s acquired in incomplete mode, requires later activation.", ur->user_name); + } else + pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name); + return PAM_SUCCESS; } @@ -703,53 +744,99 @@ static int release_home_fd(pam_handle_t *handle, const char *username) { _public_ PAM_EXTERN int pam_sm_authenticate( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { - bool debug = false, suspend_please = false; + AcquireHomeFlags flags = 0; + bool debug = false; - if (parse_env(handle, &suspend_please) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_AUTH_ERR; if (parse_argv(handle, argc, argv, - &suspend_please, + &flags, &debug) < 0) return PAM_AUTH_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed authenticating"); - return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug, NULL); + return acquire_home(handle, ACQUIRE_MUST_AUTHENTICATE|flags, debug, /* bus_data= */ NULL); } -_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { +_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int sm_flags, int argc, const char **argv) { + return PAM_SUCCESS; +} + +static int fallback_shell_can_work( + pam_handle_t *handle, + AcquireHomeFlags *flags) { + + const char *tty = NULL, *display = NULL; + int r; + + assert(handle); + assert(flags); + + r = pam_get_item_many( + handle, + PAM_TTY, &tty, + PAM_XDISPLAY, &display); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); + + /* The fallback shell logic only works on TTY logins, hence only allow it if there's no X11 display + * set, and a TTY field is set that is neither "cron" (which is what crond sets, god knows why) not + * contains a colon (which is what various graphical X11 logins do). Note that ssh sets the tty to + * "ssh" here, which we allow (I mean, ssh is after all the primary reason we do all this). */ + if (isempty(display) && + tty && + !strchr(tty, ':') && + !streq(tty, "cron")) + *flags |= ACQUIRE_REF_ANYWAY; /* Allow login even if we can only ref, not activate */ + return PAM_SUCCESS; } _public_ PAM_EXTERN int pam_sm_open_session( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { /* Let's release the D-Bus connection once this function exits, 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. */ _cleanup_(pam_bus_data_disconnectp) PamBusData *d = NULL; - bool debug = false, suspend_please = false; + AcquireHomeFlags flags = 0; + bool debug = false; int r; - if (parse_env(handle, &suspend_please) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_SESSION_ERR; if (parse_argv(handle, argc, argv, - &suspend_please, + &flags, &debug) < 0) return PAM_SESSION_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed session start"); - r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug, &d); + r = fallback_shell_can_work(handle, &flags); + if (r != PAM_SUCCESS) + return r; + + /* Explicitly get saved PamBusData here. Otherwise, this function may succeed without setting 'd' + * even if there is an opened sd-bus connection, and it will be leaked. See issue #31375. */ + r = pam_get_bus_data(handle, "pam-systemd-home", &d); + if (r != PAM_SUCCESS) + return r; + + r = acquire_home(handle, flags, debug, &d); if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */ return PAM_SUCCESS; if (r != PAM_SUCCESS) @@ -760,7 +847,7 @@ _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: @PAMERR@"); - r = pam_putenv(handle, suspend_please ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); + r = pam_putenv(handle, FLAGS_SET(flags, ACQUIRE_PLEASE_SUSPEND) ? "SYSTEMD_HOME_SUSPEND=1" : "SYSTEMD_HOME_SUSPEND=0"); if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM environment variable $SYSTEMD_HOME_SUSPEND: @PAMERR@"); @@ -770,16 +857,17 @@ _public_ PAM_EXTERN int pam_sm_open_session( _public_ PAM_EXTERN int pam_sm_close_session( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; const char *username = NULL; bool debug = false; int r; + pam_log_setup(); + if (parse_argv(handle, argc, argv, NULL, @@ -803,6 +891,7 @@ _public_ PAM_EXTERN int pam_sm_close_session( if (r != PAM_SUCCESS) return r; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); if (r != PAM_SUCCESS) return r; @@ -829,27 +918,34 @@ _public_ PAM_EXTERN int pam_sm_close_session( _public_ PAM_EXTERN int pam_sm_acct_mgmt( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL; - bool debug = false, please_suspend = false; + AcquireHomeFlags flags = 0; + bool debug = false; usec_t t; int r; - if (parse_env(handle, &please_suspend) < 0) + pam_log_setup(); + + if (parse_env(handle, &flags) < 0) return PAM_AUTH_ERR; if (parse_argv(handle, argc, argv, - &please_suspend, + &flags, &debug) < 0) return PAM_AUTH_ERR; pam_debug_syslog(handle, debug, "pam-systemd-homed account management"); - r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug, NULL); + r = fallback_shell_can_work(handle, &flags); + if (r != PAM_SUCCESS) + return r; + + r = acquire_home(handle, flags, debug, /* bus_data= */ NULL); if (r != PAM_SUCCESS) return r; @@ -865,20 +961,20 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( break; case -ENOLCK: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is blocked, prohibiting access.")); return PAM_ACCT_EXPIRED; case -EL2HLT: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid yet, prohibiting access.")); return PAM_ACCT_EXPIRED; case -EL3HLT: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record is not valid anymore, prohibiting access.")); return PAM_ACCT_EXPIRED; default: if (r < 0) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); return PAM_ACCT_EXPIRED; } @@ -890,7 +986,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( usec_t n = now(CLOCK_REALTIME); if (t > n) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."), + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Too many logins, try again in %s."), FORMAT_TIMESPAN(t - n, USEC_PER_SEC)); return PAM_MAXTRIES; @@ -901,21 +997,21 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( switch (r) { case -EKEYREVOKED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password change required.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password change required.")); return PAM_NEW_AUTHTOK_REQD; case -EOWNERDEAD: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password expired, change required.")); return PAM_NEW_AUTHTOK_REQD; /* Strictly speaking this is only about password expiration, and we might want to allow * authentication via PKCS#11 or so, but let's ignore this fine distinction for now. */ case -EKEYREJECTED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password is expired, but can't change, refusing login.")); return PAM_AUTHTOK_EXPIRED; case -EKEYEXPIRED: - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("Password will expire soon, please change.")); break; case -ESTALE: @@ -929,7 +1025,7 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( default: if (r < 0) { - (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); + (void) pam_prompt_graceful(handle, PAM_ERROR_MSG, NULL, _("User record not valid, prohibiting access.")); return PAM_AUTHTOK_EXPIRED; } @@ -941,17 +1037,18 @@ _public_ PAM_EXTERN int pam_sm_acct_mgmt( _public_ PAM_EXTERN int pam_sm_chauthtok( pam_handle_t *handle, - int flags, + int sm_flags, int argc, const char **argv) { _cleanup_(user_record_unrefp) UserRecord *ur = NULL, *old_secret = NULL, *new_secret = NULL; const char *old_password = NULL, *new_password = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; unsigned n_attempts = 0; bool debug = false; int r; + pam_log_setup(); + if (parse_argv(handle, argc, argv, NULL, @@ -960,22 +1057,17 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( pam_debug_syslog(handle, debug, "pam-systemd-homed account management"); - r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); - if (r != PAM_SUCCESS) - return r; - r = acquire_user_record(handle, NULL, debug, &ur, NULL); if (r != PAM_SUCCESS) return r; /* Start with cached credentials */ - r = pam_get_item(handle, PAM_OLDAUTHTOK, (const void**) &old_password); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get old password: @PAMERR@"); - - r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached password: @PAMERR@"); + r = pam_get_item_many( + handle, + PAM_OLDAUTHTOK, &old_password, + PAM_AUTHTOK, &new_password); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get cached passwords: @PAMERR@"); if (isempty(new_password)) { /* No, it's not cached, then let's ask for the password and its verification, and cache @@ -1000,7 +1092,7 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( } /* Now everything is cached and checked, let's exit from the preliminary check */ - if (FLAGS_SET(flags, PAM_PRELIM_CHECK)) + if (FLAGS_SET(sm_flags, PAM_PRELIM_CHECK)) return PAM_SUCCESS; old_secret = user_record_new(); @@ -1021,6 +1113,11 @@ _public_ PAM_EXTERN int pam_sm_chauthtok( if (r < 0) return pam_syslog_errno(handle, LOG_ERR, r, "Failed to store new password: %m"); + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = pam_acquire_bus_connection(handle, "pam-systemd-home", &bus, NULL); + if (r != PAM_SUCCESS) + return r; + for (;;) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; diff --git a/src/home/test-homed-regression-31896.c b/src/home/test-homed-regression-31896.c new file mode 100644 index 0000000..1530a2f --- /dev/null +++ b/src/home/test-homed-regression-31896.c @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-locator.h" +#include "main-func.h" +#include "tests.h" + +static int run(int argc, char **argv) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *ref = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *username = NULL; + + /* This is a regression test for the following bug: + * https://github.com/systemd/systemd/pull/31896 + * It is run as part of TEST-46-HOMED + */ + + test_setup_logging(LOG_DEBUG); + assert_se(sd_bus_open_system(&bus) >= 0); + + assert_se(argc == 2); + username = argv[1]; + + assert_se(bus_call_method(bus, bus_home_mgr, "RefHomeUnrestricted", NULL, &ref, "sb", username, true) >= 0); + + assert_se(bus_call_method_async(bus, NULL, bus_home_mgr, "AuthenticateHome", NULL, NULL, "ss", username, "{}") >= 0); + assert_se(sd_bus_flush(bus) >= 0); + + (void) bus_call_method(bus, bus_home_mgr, "ReleaseHome", &error, NULL, "s", username); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY)); /* Make sure we didn't crash */ + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/home/user-record-sign.c b/src/home/user-record-sign.c index dd099a0..25618d0 100644 --- a/src/home/user-record-sign.c +++ b/src/home/user-record-sign.c @@ -136,11 +136,11 @@ int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) { return -EIO; if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) { - n_bad ++; + n_bad++; continue; } - n_good ++; + n_good++; } return n_good > 0 ? (n_bad == 0 ? USER_RECORD_SIGNED_EXCLUSIVE : USER_RECORD_SIGNED) : diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c index 089cbb1..3ae0883 100644 --- a/src/home/user-record-util.c +++ b/src/home/user-record-util.c @@ -3,6 +3,7 @@ #include #include "errno-util.h" +#include "fd-util.h" #include "home-util.h" #include "id128-util.h" #include "libcrypt-util.h" @@ -10,6 +11,7 @@ #include "recovery-key.h" #include "mountpoint-util.h" #include "path-util.h" +#include "sha256.h" #include "stat-util.h" #include "user-record-util.h" #include "user-util.h" @@ -282,7 +284,7 @@ int user_record_add_binding( gid_t gid) { _cleanup_(json_variant_unrefp) JsonVariant *new_binding_entry = NULL, *binding = NULL; - _cleanup_free_ char *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL; + _cleanup_free_ char *blob = NULL, *ip = NULL, *hd = NULL, *ip_auto = NULL, *lc = NULL, *lcm = NULL, *fst = NULL; sd_id128_t mid; int r; @@ -291,6 +293,10 @@ int user_record_add_binding( if (!h->json) return -EUNATCH; + blob = path_join(home_system_blob_dir(), h->user_name); + if (!blob) + return -ENOMEM; + r = sd_id128_get_machine(&mid); if (r < 0) return r; @@ -331,6 +337,7 @@ int user_record_add_binding( r = json_build(&new_binding_entry, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("blobDirectory", JSON_BUILD_STRING(blob)), JSON_BUILD_PAIR_CONDITION(!!image_path, "imagePath", JSON_BUILD_STRING(image_path)), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(partition_uuid), "partitionUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(partition_uuid))), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(luks_uuid), "luksUuid", JSON_BUILD_STRING(SD_ID128_TO_UUID_STRING(luks_uuid))), @@ -370,6 +377,8 @@ int user_record_add_binding( if (r < 0) return r; + free_and_replace(h->blob_directory, blob); + if (storage >= 0) h->storage = storage; @@ -428,7 +437,7 @@ int user_record_test_home_directory(UserRecord *h) { if (r == 0) return -ENOTDIR; - r = path_is_mount_point(hd, NULL, 0); + r = path_is_mount_point(hd); if (r < 0) return r; if (r > 0) @@ -1155,6 +1164,7 @@ int user_record_merge_secret(UserRecord *h, UserRecord *secret) { int r; assert(h); + assert(secret); /* Merges the secrets from 'secret' into 'h'. */ @@ -1382,6 +1392,15 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error) { if (hr->service && !streq(hr->service, "io.systemd.Home")) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Not accepted with service not matching io.systemd.Home."); + if (hr->blob_directory) { + /* This function is always called w/o binding section, so if hr->blob_dir is set then the caller set it themselves */ + assert((hr->mask & USER_RECORD_BINDING) == 0); + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage custom blob directories."); + } + + if (json_variant_by_key(hr->json, HOMEWORK_BLOB_FDMAP_FIELD)) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "User record contains unsafe internal fields."); + return 0; } @@ -1510,3 +1529,103 @@ int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight) { h->mask |= USER_RECORD_PER_MACHINE; return 0; } + +int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_hashmap_free_ Hashmap *manifest = NULL; + const char *filename; + void *key, *value; + uint64_t total_size = 0; + int r; + + assert(h); + assert(h->json); + assert(blobs); + assert(ret_failed); + + /* Ensures that blobManifest exists (possibly creating it using the + * contents of blobs), and that the set of keys in both hashmaps are + * exactly the same. If it fails to handle one blob file, the filename + * is put it ret_failed for nicer error reporting. ret_failed is a pointer + * to the same memory blobs uses to store its keys, so it is valid for + * as long as blobs is valid and the corresponding key isn't removed! */ + + if (h->blob_manifest) { + /* blobManifest already exists. In this case we verify + * that the sets of keys are equal and that's it */ + + HASHMAP_FOREACH_KEY(value, key, h->blob_manifest) + if (!hashmap_contains(blobs, key)) + return -EINVAL; + HASHMAP_FOREACH_KEY(value, key, blobs) + if (!hashmap_contains(h->blob_manifest, key)) + return -EINVAL; + + return 0; + } + + /* blobManifest doesn't exist, so we need to create it */ + + HASHMAP_FOREACH_KEY(value, filename, blobs) { + _cleanup_free_ char *filename_dup = NULL; + _cleanup_free_ uint8_t *hash = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *hash_json = NULL; + int fd = PTR_TO_FD(value); + off_t initial, size; + + *ret_failed = filename; + + filename_dup = strdup(filename); + if (!filename_dup) + return -ENOMEM; + + hash = malloc(SHA256_DIGEST_SIZE); + if (!hash) + return -ENOMEM; + + initial = lseek(fd, 0, SEEK_CUR); + if (initial < 0) + return -errno; + + r = sha256_fd(fd, BLOB_DIR_MAX_SIZE, hash); + if (r < 0) + return r; + + size = lseek(fd, 0, SEEK_CUR); + if (size < 0) + return -errno; + if (!DEC_SAFE(&size, initial)) + return -EOVERFLOW; + + if (!INC_SAFE(&total_size, size)) + total_size = UINT64_MAX; + if (total_size > BLOB_DIR_MAX_SIZE) + return -EFBIG; + + if (lseek(fd, initial, SEEK_SET) < 0) + return -errno; + + r = json_variant_new_hex(&hash_json, hash, SHA256_DIGEST_SIZE); + if (r < 0) + return r; + + r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename_dup, hash); + if (r < 0) + return r; + TAKE_PTR(filename_dup); /* Ownership transfers to hashmap */ + TAKE_PTR(hash); + + r = json_variant_set_field(&v, filename, hash_json); + if (r < 0) + return r; + + *ret_failed = NULL; + } + + r = json_variant_set_field_non_null(&h->json, "blobManifest", v); + if (r < 0) + return r; + + h->blob_manifest = TAKE_PTR(manifest); + return 0; +} diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h index 508e2bd..1295a8e 100644 --- a/src/home/user-record-util.h +++ b/src/home/user-record-util.h @@ -6,6 +6,11 @@ #include "user-record.h" #include "group-record.h" +/* We intentionally use snake_case instead of the usual camelCase here to further + * reduce the chance of collision with a field any legitimate user record may ever + * want to set. */ +#define HOMEWORK_BLOB_FDMAP_FIELD "__systemd_homework_internal_blob_fdmap" + int user_record_synthesize(UserRecord *h, const char *user_name, const char *realm, const char *image_path, UserStorage storage, uid_t uid, gid_t gid); int group_record_synthesize(GroupRecord *g, UserRecord *u); @@ -63,3 +68,5 @@ int user_record_is_supported(UserRecord *hr, sd_bus_error *error); bool user_record_shall_rebalance(UserRecord *h); int user_record_set_rebalance_weight(UserRecord *h, uint64_t weight); + +int user_record_ensure_blob_manifest(UserRecord *h, Hashmap *blobs, const char **ret_failed); diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index 14fc160..d1c4d47 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -24,6 +24,7 @@ #include "main-func.h" #include "parse-argument.h" #include "pretty-print.h" +#include "socket-util.h" #include "spawn-polkit-agent.h" #include "terminal-util.h" #include "verbs.h" @@ -58,6 +59,9 @@ typedef struct StatusInfo { usec_t firmware_date; sd_id128_t machine_id; sd_id128_t boot_id; + const char *hardware_serial; + sd_id128_t product_uuid; + uint32_t vsock_cid; } StatusInfo; static const char* chassis_string_to_glyph(const char *chassis) { @@ -191,6 +195,22 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } + if (!sd_id128_is_null(i->product_uuid)) { + r = table_add_many(table, + TABLE_FIELD, "Product UUID", + TABLE_UUID, i->product_uuid); + if (r < 0) + return table_log_add_error(r); + } + + if (i->vsock_cid != VMADDR_CID_ANY) { + r = table_add_many(table, + TABLE_FIELD, "AF_VSOCK CID", + TABLE_UINT32, i->vsock_cid); + if (r < 0) + return table_log_add_error(r); + } + if (!isempty(i->virtualization)) { r = table_add_many(table, TABLE_FIELD, "Virtualization", @@ -216,7 +236,7 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } - if (i->os_support_end != USEC_INFINITY) { + if (timestamp_is_set(i->os_support_end)) { usec_t n = now(CLOCK_REALTIME); r = table_add_many(table, @@ -264,6 +284,14 @@ static int print_status_info(StatusInfo *i) { return table_log_add_error(r); } + if (!isempty(i->hardware_serial)) { + r = table_add_many(table, + TABLE_FIELD, "Hardware Serial", + TABLE_STRING, i->hardware_serial); + if (r < 0) + return table_log_add_error(r); + } + if (!isempty(i->firmware_version)) { r = table_add_many(table, TABLE_FIELD, "Firmware Version", @@ -332,7 +360,11 @@ static int get_one_name(sd_bus *bus, const char* attr, char **ret) { } static int show_all_names(sd_bus *bus) { - StatusInfo info = {}; + StatusInfo info = { + .vsock_cid = VMADDR_CID_ANY, + .os_support_end = USEC_INFINITY, + .firmware_date = USEC_INFINITY, + }; static const struct bus_properties_map hostname_map[] = { { "Hostname", "s", NULL, offsetof(StatusInfo, hostname) }, @@ -354,6 +386,7 @@ static int show_all_names(sd_bus *bus) { { "FirmwareDate", "t", NULL, offsetof(StatusInfo, firmware_date) }, { "MachineID", "ay", bus_map_id128, offsetof(StatusInfo, machine_id) }, { "BootID", "ay", bus_map_id128, offsetof(StatusInfo, boot_id) }, + { "VSockCID", "u", NULL, offsetof(StatusInfo, vsock_cid) }, {} }, manager_map[] = { { "Virtualization", "s", NULL, offsetof(StatusInfo, virtualization) }, @@ -387,6 +420,49 @@ static int show_all_names(sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to query system properties: %s", bus_error_message(&error, r)); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *product_uuid_reply = NULL; + r = bus_call_method(bus, + bus_hostname, + "GetProductUUID", + &error, + &product_uuid_reply, + "b", + false); + if (r < 0) { + log_full_errno(sd_bus_error_has_names( + &error, + BUS_ERROR_NO_PRODUCT_UUID, + SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, + SD_BUS_ERROR_UNKNOWN_METHOD) ? LOG_DEBUG : LOG_WARNING, + r, "Failed to query product UUID, ignoring: %s", bus_error_message(&error, r)); + sd_bus_error_free(&error); + } else { + r = bus_message_read_id128(product_uuid_reply, &info.product_uuid); + if (r < 0) + return bus_log_parse_error(r); + } + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *hardware_serial_reply = NULL; + r = bus_call_method(bus, + bus_hostname, + "GetHardwareSerial", + &error, + &hardware_serial_reply, + NULL); + if (r < 0) + log_full_errno(sd_bus_error_has_names( + &error, + BUS_ERROR_NO_HARDWARE_SERIAL, + SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, + SD_BUS_ERROR_UNKNOWN_METHOD) || + ERRNO_IS_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING, /* old hostnamed used to send ENOENT/ENODEV back to client as is, handle that gracefully */ + r, "Failed to query hardware serial, ignoring: %s", bus_error_message(&error, r)); + else { + r = sd_bus_message_read_basic(hardware_serial_reply, 's', &info.hardware_serial); + if (r < 0) + return bus_log_parse_error(r); + } + /* For older version of hostnamed. */ if (!arg_host) { if (sd_id128_is_null(info.machine_id)) @@ -603,6 +679,7 @@ static int help(void) { " --pretty Only set pretty hostname\n" " --json=pretty|short|off\n" " Generate JSON output\n" + " -j Same as --json=pretty on tty, --json=short otherwise\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -645,7 +722,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hH:M:j", options, NULL)) >= 0) switch (c) { @@ -688,6 +765,10 @@ static int parse_argv(int argc, char *argv[]) { break; + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + break; + case '?': return -EINVAL; diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c index fc7a97f..82d0880 100644 --- a/src/hostname/hostnamed.c +++ b/src/hostname/hostnamed.c @@ -6,12 +6,15 @@ #include #include +#include "sd-device.h" + #include "alloc-util.h" #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-log-control-api.h" #include "bus-polkit.h" #include "constants.h" +#include "daemon-util.h" #include "env-file-label.h" #include "env-file.h" #include "env-util.h" @@ -28,14 +31,16 @@ #include "os-util.h" #include "parse-util.h" #include "path-util.h" -#include "sd-device.h" #include "selinux-util.h" #include "service-util.h" #include "signal-util.h" +#include "socket-util.h" #include "stat-util.h" #include "string-table.h" #include "strv.h" #include "user-util.h" +#include "utf8.h" +#include "varlink-io.systemd.Hostname.h" #include "virt.h" #define VALID_DEPLOYMENT_CHARS (DIGITS LETTERS "-.:") @@ -73,6 +78,9 @@ typedef struct Context { struct stat etc_os_release_stat; struct stat etc_machine_info_stat; + sd_event *event; + sd_bus *bus; + VarlinkServer *varlink_server; Hashmap *polkit_registry; } Context; @@ -91,7 +99,10 @@ static void context_destroy(Context *c) { assert(c); context_reset(c, UINT64_MAX); - bus_verify_polkit_async_registry_free(c->polkit_registry); + hashmap_free(c->polkit_registry); + sd_event_unref(c->event); + sd_bus_flush_close_unref(c->bus); + varlink_server_unref(c->varlink_server); } static void context_read_etc_hostname(Context *c) { @@ -200,7 +211,6 @@ static bool use_dmi_data(void) { static int get_dmi_data(const char *database_key, const char *regular_key, char **ret) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_free_ char *b = NULL; const char *s = NULL; int r; @@ -216,17 +226,7 @@ static int get_dmi_data(const char *database_key, const char *regular_key, char if (!s && regular_key) (void) sd_device_get_property_value(device, regular_key, &s); - if (!ret) - return !!s; - - if (s) { - b = strdup(s); - if (!b) - return -ENOMEM; - } - - *ret = TAKE_PTR(b); - return !!s; + return strdup_to_full(ret, s); } static int get_hardware_vendor(char **ret) { @@ -239,7 +239,6 @@ static int get_hardware_model(char **ret) { static int get_hardware_firmware_data(const char *sysattr, char **ret) { _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_free_ char *b = NULL; const char *s = NULL; int r; @@ -253,94 +252,105 @@ static int get_hardware_firmware_data(const char *sysattr, char **ret) { return log_debug_errno(r, "Failed to open /sys/class/dmi/id device, ignoring: %m"); (void) sd_device_get_sysattr_value(device, sysattr, &s); - if (!isempty(s)) { - b = strdup(s); - if (!b) - return -ENOMEM; - } - if (ret) - *ret = TAKE_PTR(b); - - return !isempty(s); + return strdup_to_full(ret, empty_to_null(s)); } static int get_hardware_serial(char **ret) { - int r; + _cleanup_free_ char *b = NULL; + int r = 0; + + FOREACH_STRING(attr, "product_serial", "board_serial") { + r = get_hardware_firmware_data(attr, &b); + if (r != 0 && !ERRNO_IS_NEG_DEVICE_ABSENT(r)) + break; + } + if (r < 0) + return r; + if (r == 0) + return -ENOENT; - r = get_hardware_firmware_data("product_serial", ret); - if (r <= 0) - return get_hardware_firmware_data("board_serial", ret); + /* Do some superficial validation: do not allow CCs and make sure D-Bus won't kick us off the bus + * because we send invalid UTF-8 data */ - return r; + if (string_has_cc(b, /* ok= */ NULL)) + return -ENOENT; + + if (!utf8_is_valid(b)) + return -ENOENT; + + if (ret) + *ret = TAKE_PTR(b); + + return 0; } static int get_firmware_version(char **ret) { - return get_hardware_firmware_data("bios_version", ret); + return get_hardware_firmware_data("bios_version", ret); } static int get_firmware_vendor(char **ret) { - return get_hardware_firmware_data("bios_vendor", ret); + return get_hardware_firmware_data("bios_vendor", ret); } static int get_firmware_date(usec_t *ret) { - _cleanup_free_ char *bios_date = NULL, *month = NULL, *day = NULL, *year = NULL; - int r; + _cleanup_free_ char *bios_date = NULL, *month = NULL, *day = NULL, *year = NULL; + int r; - assert(ret); + assert(ret); - r = get_hardware_firmware_data("bios_date", &bios_date); - if (r < 0) + r = get_hardware_firmware_data("bios_date", &bios_date); + if (r < 0) return r; - if (r == 0) { + if (r == 0) { *ret = USEC_INFINITY; return 0; - } + } - const char *p = bios_date; - r = extract_many_words(&p, "/", EXTRACT_DONT_COALESCE_SEPARATORS, &month, &day, &year, NULL); - if (r < 0) + const char *p = bios_date; + r = extract_many_words(&p, "/", EXTRACT_DONT_COALESCE_SEPARATORS, &month, &day, &year); + if (r < 0) return r; - if (r != 3) /* less than three args read? */ + if (r != 3) /* less than three args read? */ return -EINVAL; - if (!isempty(p)) /* more left in the string? */ + if (!isempty(p)) /* more left in the string? */ return -EINVAL; - unsigned m, d, y; - r = safe_atou_full(month, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &m); - if (r < 0) + unsigned m, d, y; + r = safe_atou_full(month, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &m); + if (r < 0) return r; - if (m < 1 || m > 12) + if (m < 1 || m > 12) return -EINVAL; - m -= 1; + m -= 1; - r = safe_atou_full(day, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &d); - if (r < 0) + r = safe_atou_full(day, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &d); + if (r < 0) return r; - if (d < 1 || d > 31) + if (d < 1 || d > 31) return -EINVAL; - r = safe_atou_full(year, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &y); - if (r < 0) + r = safe_atou_full(year, 10 | SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_WHITESPACE, &y); + if (r < 0) return r; - if (y < 1970 || y > (unsigned) INT_MAX) + if (y < 1970 || y > (unsigned) INT_MAX) return -EINVAL; - y -= 1900; + y -= 1900; - struct tm tm = { + struct tm tm = { .tm_mday = d, .tm_mon = m, .tm_year = y, - }; - time_t v = timegm(&tm); - if (v == (time_t) -1) + }; + time_t v = timegm(&tm); + if (v == (time_t) -1) return -errno; - if (tm.tm_mday != (int) d || tm.tm_mon != (int) m || tm.tm_year != (int) y) + if (tm.tm_mday != (int) d || tm.tm_mon != (int) m || tm.tm_year != (int) y) return -EINVAL; /* date was not normalized? (e.g. "30th of feb") */ - *ret = (usec_t) v * USEC_PER_SEC; + *ret = (usec_t) v * USEC_PER_SEC; - return 0; + return 0; } static const char* valid_chassis(const char *chassis) { @@ -1033,6 +1043,22 @@ static int property_get_boot_id( return bus_property_get_id128(bus, path, interface, property, reply, &id, error); } +static int property_get_vsock_cid( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + unsigned local_cid = VMADDR_CID_ANY; + + (void) vsock_get_local_cid(&local_cid); + + return sd_bus_message_append(reply, "u", (uint32_t) local_cid); +} + static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error *error) { Context *c = ASSERT_PTR(userdata); const char *name; @@ -1054,13 +1080,12 @@ static int method_set_hostname(sd_bus_message *m, void *userdata, sd_bus_error * context_read_etc_hostname(c); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.hostname1.set-hostname", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -1101,13 +1126,12 @@ static int method_set_static_hostname(sd_bus_message *m, void *userdata, sd_bus_ if (name && !hostname_is_valid(name, 0)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid static hostname '%s'", name); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.hostname1.set-static-hostname", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -1177,17 +1201,15 @@ static int set_machine_info(Context *c, sd_bus_message *m, int prop, sd_bus_mess return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid location '%s'", name); } - /* Since the pretty hostname should always be changed at the - * same time as the static one, use the same policy action for - * both... */ + /* Since the pretty hostname should always be changed at the same time as the static one, use the + * same policy action for both... */ - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, prop == PROP_PRETTY_HOSTNAME ? "org.freedesktop.hostname1.set-static-hostname" : "org.freedesktop.hostname1.set-machine-info", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -1259,13 +1281,12 @@ static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_err if (r < 0) return r; - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.hostname1.get-product-uuid", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -1297,7 +1318,6 @@ static int method_get_product_uuid(sd_bus_message *m, void *userdata, sd_bus_err } static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *serial = NULL; Context *c = ASSERT_PTR(userdata); int r; @@ -1306,11 +1326,8 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_ r = bus_verify_polkit_async( m, - CAP_SYS_ADMIN, "org.freedesktop.hostname1.get-hardware-serial", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &c->polkit_registry, error); if (r < 0) @@ -1320,49 +1337,26 @@ static int method_get_hardware_serial(sd_bus_message *m, void *userdata, sd_bus_ r = get_hardware_serial(&serial); if (r < 0) - return r; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; + return sd_bus_error_set(error, BUS_ERROR_NO_HARDWARE_SERIAL, + "Failed to read hardware serial from firmware."); - r = sd_bus_message_append(reply, "s", serial); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); + return sd_bus_reply_method_return(m, "s", serial); } -static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *text = NULL, +static int build_describe_response(Context *c, bool privileged, JsonVariant **ret) { + _cleanup_free_ char *hn = NULL, *dhn = NULL, *in = NULL, *chassis = NULL, *vendor = NULL, *model = NULL, *serial = NULL, *firmware_version = NULL, *firmware_vendor = NULL; + _cleanup_strv_free_ char **os_release_pairs = NULL, **machine_info_pairs = NULL; usec_t firmware_date = USEC_INFINITY, eol = USEC_INFINITY; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; sd_id128_t machine_id, boot_id, product_uuid = SD_ID128_NULL; - Context *c = ASSERT_PTR(userdata); - bool privileged; + unsigned local_cid = VMADDR_CID_ANY; struct utsname u; int r; - assert(m); - - r = bus_verify_polkit_async( - m, - CAP_SYS_ADMIN, - "org.freedesktop.hostname1.get-description", - NULL, - false, - UID_INVALID, - &c->polkit_registry, - error); - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* We ignore all authentication errors here, since most data is unprivileged, the one exception being - * the product ID which we'll check explicitly. */ - privileged = r > 0; + assert(c); + assert(ret); context_read_etc_hostname(c); context_read_machine_info(c); @@ -1415,6 +1409,11 @@ static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *erro if (r < 0) return log_error_errno(r, "Failed to get boot ID: %m"); + (void) vsock_get_local_cid(&local_cid); + + (void) load_os_release_pairs(/* root= */ NULL, &os_release_pairs); + (void) load_env_file_pairs(/* f=*/ NULL, "/etc/machine-info", &machine_info_pairs); + r = json_build(&v, JSON_BUILD_OBJECT( JSON_BUILD_PAIR("Hostname", JSON_BUILD_STRING(hn)), JSON_BUILD_PAIR("StaticHostname", JSON_BUILD_STRING(c->data[PROP_STATIC_HOSTNAME])), @@ -1432,6 +1431,8 @@ static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *erro JSON_BUILD_PAIR("OperatingSystemCPEName", JSON_BUILD_STRING(c->data[PROP_OS_CPE_NAME])), JSON_BUILD_PAIR("OperatingSystemHomeURL", JSON_BUILD_STRING(c->data[PROP_OS_HOME_URL])), JSON_BUILD_PAIR_FINITE_USEC("OperatingSystemSupportEnd", eol), + JSON_BUILD_PAIR("OperatingSystemReleaseData", JSON_BUILD_STRV_ENV_PAIR(os_release_pairs)), + JSON_BUILD_PAIR("MachineInformationData", JSON_BUILD_STRV_ENV_PAIR(machine_info_pairs)), JSON_BUILD_PAIR("HardwareVendor", JSON_BUILD_STRING(vendor ?: c->data[PROP_HARDWARE_VENDOR])), JSON_BUILD_PAIR("HardwareModel", JSON_BUILD_STRING(model ?: c->data[PROP_HARDWARE_MODEL])), JSON_BUILD_PAIR("HardwareSerial", JSON_BUILD_STRING(serial)), @@ -1441,24 +1442,47 @@ static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *erro JSON_BUILD_PAIR_ID128("MachineID", machine_id), JSON_BUILD_PAIR_ID128("BootID", boot_id), JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_ID128(product_uuid)), - JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL))); - + JSON_BUILD_PAIR_CONDITION(sd_id128_is_null(product_uuid), "ProductUUID", JSON_BUILD_NULL), + JSON_BUILD_PAIR_CONDITION(local_cid != VMADDR_CID_ANY, "VSockCID", JSON_BUILD_UNSIGNED(local_cid)), + JSON_BUILD_PAIR_CONDITION(local_cid == VMADDR_CID_ANY, "VSockCID", JSON_BUILD_NULL))); if (r < 0) return log_error_errno(r, "Failed to build JSON data: %m"); - r = json_variant_format(v, 0, &text); - if (r < 0) - return log_error_errno(r, "Failed to format JSON data: %m"); + *ret = TAKE_PTR(v); + return 0; +} - r = sd_bus_message_new_method_return(m, &reply); +static int method_describe(sd_bus_message *m, void *userdata, sd_bus_error *error) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + Context *c = ASSERT_PTR(userdata); + _cleanup_free_ char *text = NULL; + bool privileged; + int r; + + assert(m); + + r = bus_verify_polkit_async( + m, + "org.freedesktop.hostname1.get-description", + /* details= */ NULL, + &c->polkit_registry, + error); + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We ignore all authentication errors here, since most data is unprivileged, the one exception being + * the product ID which we'll check explicitly. */ + privileged = r > 0; + + r = build_describe_response(c, privileged, &v); if (r < 0) return r; - r = sd_bus_message_append(reply, "s", text); + r = json_variant_format(v, 0, &text); if (r < 0) - return r; + return log_error_errno(r, "Failed to format JSON data: %m"); - return sd_bus_send(NULL, reply, NULL); + return sd_bus_reply_method_return(m, "s", text); } static const sd_bus_vtable hostname_vtable[] = { @@ -1486,6 +1510,7 @@ static const sd_bus_vtable hostname_vtable[] = { SD_BUS_PROPERTY("FirmwareDate", "t", property_get_firmware_date, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MachineID", "ay", property_get_machine_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("BootID", "ay", property_get_boot_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("VSockCID", "u", property_get_vsock_cid, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_METHOD_WITH_ARGS("SetHostname", SD_BUS_ARGS("s", hostname, "b", interactive), @@ -1547,35 +1572,113 @@ static const BusObjectImplementation manager_object = { .vtables = BUS_VTABLES(hostname_vtable), }; -static int connect_bus(Context *c, sd_event *event, sd_bus **ret) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +static int connect_bus(Context *c) { int r; assert(c); - assert(event); - assert(ret); + assert(c->event); + assert(!c->bus); - r = sd_bus_default_system(&bus); + r = sd_bus_default_system(&c->bus); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); - r = bus_add_implementation(bus, &manager_object, c); + r = bus_add_implementation(c->bus, &manager_object, c); if (r < 0) return r; - r = bus_log_control_api_register(bus); + r = bus_log_control_api_register(c->bus); if (r < 0) return r; - r = sd_bus_request_name_async(bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL); + r = sd_bus_request_name_async(c->bus, NULL, "org.freedesktop.hostname1", 0, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to request name: %m"); - r = sd_bus_attach_event(bus, event, 0); + r = sd_bus_attach_event(c->bus, c->event, 0); if (r < 0) return log_error_errno(r, "Failed to attach bus to event loop: %m"); - *ret = TAKE_PTR(bus); + return 0; +} + +static int vl_method_describe(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + Context *c = ASSERT_PTR(userdata); + bool privileged; + int r; + + assert(link); + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, /* userdata= */ NULL); + if (r != 0) + return r; + + r = varlink_verify_polkit_async( + link, + c->bus, + "org.freedesktop.hostname1.get-hardware-serial", + /* details= */ NULL, + &c->polkit_registry); + if (r == 0) + return 0; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We ignore all authentication errors here, since most data is unprivileged, the one exception being + * the product ID which we'll check explicitly. */ + privileged = r > 0; + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + r = build_describe_response(c, privileged, &v); + if (r < 0) + return r; + + return varlink_reply(link, v); +} + +static int connect_varlink(Context *c) { + int r; + + assert(c); + assert(c->event); + assert(!c->varlink_server); + + r = varlink_server_new(&c->varlink_server, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + varlink_server_set_userdata(c->varlink_server, c); + + r = varlink_server_add_interface(c->varlink_server, &vl_interface_io_systemd_Hostname); + if (r < 0) + return log_error_errno(r, "Failed to add Hostname interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + c->varlink_server, + "io.systemd.Hostname.Describe", vl_method_describe); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method calls: %m"); + + r = varlink_server_attach_event(c->varlink_server, c->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach Varlink server to event loop: %m"); + + r = varlink_server_listen_auto(c->varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m"); + if (r == 0) { + r = varlink_server_listen_address(c->varlink_server, "/run/systemd/io.systemd.Hostname", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to Varlink socket: %m"); + } + return 0; } @@ -1583,8 +1686,6 @@ static int run(int argc, char *argv[]) { _cleanup_(context_destroy) Context context = { .hostname_source = _HOSTNAME_INVALID, /* appropriate value will be set later */ }; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; log_setup(); @@ -1603,27 +1704,35 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - r = sd_event_default(&event); + r = sd_event_default(&context.event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - (void) sd_event_set_watchdog(event, true); + (void) sd_event_set_watchdog(context.event, true); - r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + r = sd_event_set_signal_exit(context.event, true); if (r < 0) - return log_error_errno(r, "Failed to install SIGINT handler: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); - r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + r = connect_bus(&context); if (r < 0) - return log_error_errno(r, "Failed to install SIGTERM handler: %m"); + return r; - r = connect_bus(&context, event, &bus); + r = connect_varlink(&context); if (r < 0) return r; - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.hostname1", DEFAULT_EXIT_USEC, NULL, NULL); + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); + + r = bus_event_loop_with_idle( + context.event, + context.bus, + "org.freedesktop.hostname1", + DEFAULT_EXIT_USEC, + /* check_idle= */ NULL, + /* userdata= */ NULL); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); diff --git a/src/hwdb/hwdb.c b/src/hwdb/hwdb.c index 4287b1f..2ce8b2d 100644 --- a/src/hwdb/hwdb.c +++ b/src/hwdb/hwdb.c @@ -22,6 +22,9 @@ static int verb_query(int argc, char *argv[], void *userdata) { } static int verb_update(int argc, char *argv[], void *userdata) { + if (hwdb_bypass()) + return 0; + return hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, false); } @@ -117,8 +120,7 @@ static int hwdb_main(int argc, char *argv[]) { static int run(int argc, char *argv[]) { int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/id128/id128.c b/src/id128/id128.c index d726ab7..fa86cf6 100644 --- a/src/id128/id128.c +++ b/src/id128/id128.c @@ -5,18 +5,22 @@ #include "alloc-util.h" #include "build.h" +#include "format-table.h" #include "gpt.h" #include "id128-print.h" #include "main-func.h" +#include "parse-argument.h" #include "pretty-print.h" #include "strv.h" -#include "format-table.h" #include "terminal-util.h" #include "verbs.h" static Id128PrettyPrintMode arg_mode = ID128_PRINT_ID128; static sd_id128_t arg_app = {}; static bool arg_value = false; +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static int verb_new(int argc, char **argv, void *userdata) { return id128_print_new(arg_mode); @@ -150,9 +154,9 @@ static int verb_show(int argc, char **argv, void *userdata) { } if (table) { - r = table_print(table, NULL); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) - return table_log_print_error(r); + return r; } return 0; @@ -177,6 +181,12 @@ static int help(void) { " help Show this help\n" "\nOptions:\n" " -h --help Show this help\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --json=FORMAT Output inspection data in JSON (takes one of\n" + " pretty, short, off)\n" + " -j Equivalent to --json=pretty (on TTY) or\n" + " --json=short (otherwise)\n" " -p --pretty Generate samples of program code\n" " -P --value Only print the value\n" " -a --app-specific=ID Generate app-specific IDs\n" @@ -197,11 +207,17 @@ static int verb_help(int argc, char **argv, void *userdata) { static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_JSON, }; 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 }, + { "json", required_argument, NULL, ARG_JSON }, { "pretty", no_argument, NULL, 'p' }, { "value", no_argument, NULL, 'P' }, { "app-specific", required_argument, NULL, 'a' }, @@ -214,7 +230,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hpa:uP", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hpa:uPj", options, NULL)) >= 0) switch (c) { case 'h': @@ -223,6 +239,24 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + break; case 'p': arg_mode = ID128_PRINT_PRETTY; arg_value = false; diff --git a/src/import/curl-util.c b/src/import/curl-util.c index 94f718d..1628f83 100644 --- a/src/import/curl-util.c +++ b/src/import/curl-util.c @@ -10,20 +10,28 @@ #include "version.h" static void curl_glue_check_finished(CurlGlue *g) { - CURLMsg *msg; - int k = 0; + int r; assert(g); + /* sd_event_get_exit_code() returns -ENODATA if no exit was scheduled yet */ + r = sd_event_get_exit_code(g->event, /* ret_code= */ NULL); + if (r >= 0) + return; /* exit scheduled? Then don't process this anymore */ + if (r != -ENODATA) + log_debug_errno(r, "Unexpected error while checking for event loop exit code, ignoring: %m"); + + CURLMsg *msg; + int k = 0; msg = curl_multi_info_read(g->curl, &k); if (!msg) return; - if (msg->msg != CURLMSG_DONE) - return; - - if (g->on_finished) + if (msg->msg == CURLMSG_DONE && g->on_finished) g->on_finished(g, msg->easy_handle, msg->data.result); + + /* This is a queue, process another item soon, but do so in a later event loop iteration. */ + (void) sd_event_source_set_enabled(g->defer, SD_EVENT_ONESHOT); } static int curl_glue_on_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { @@ -126,6 +134,13 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata assert(curl); + /* Don't configure timer anymore when the event loop is dead already. */ + if (g->timer) { + sd_event *event_loop = sd_event_source_get_event(g->timer); + if (event_loop && sd_event_get_state(event_loop) == SD_EVENT_FINISHED) + return 0; + } + if (timeout_ms < 0) { if (g->timer) { if (sd_event_source_set_enabled(g->timer, SD_EVENT_OFF) < 0) @@ -153,6 +168,15 @@ static int curl_glue_timer_callback(CURLM *curl, long timeout_ms, void *userdata return 0; } +static int curl_glue_on_defer(sd_event_source *s, void *userdata) { + CurlGlue *g = ASSERT_PTR(userdata); + + assert(s); + + curl_glue_check_finished(g); + return 0; +} + CurlGlue *curl_glue_unref(CurlGlue *g) { sd_event_source *io; @@ -167,7 +191,8 @@ CurlGlue *curl_glue_unref(CurlGlue *g) { hashmap_free(g->ios); - sd_event_source_unref(g->timer); + sd_event_source_disable_unref(g->timer); + sd_event_source_disable_unref(g->defer); sd_event_unref(g->event); return mfree(g); } @@ -211,6 +236,12 @@ int curl_glue_new(CurlGlue **glue, sd_event *event) { if (curl_multi_setopt(g->curl, CURLMOPT_TIMERFUNCTION, curl_glue_timer_callback) != CURLM_OK) return -EINVAL; + r = sd_event_add_defer(g->event, &g->defer, curl_glue_on_defer, g); + if (r < 0) + return r; + + (void) sd_event_source_set_description(g->defer, "curl-defer"); + *glue = TAKE_PTR(g); return 0; diff --git a/src/import/curl-util.h b/src/import/curl-util.h index 6b4f992..cef0b26 100644 --- a/src/import/curl-util.h +++ b/src/import/curl-util.h @@ -16,6 +16,7 @@ struct CurlGlue { CURLM *curl; sd_event_source *timer; Hashmap *ios; + sd_event_source *defer; void (*on_finished)(CurlGlue *g, CURL *curl, CURLcode code); void *userdata; diff --git a/src/import/export.c b/src/import/export.c index 7e941a2..cdb1d62 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -14,6 +14,7 @@ #include "fd-util.h" #include "fs-util.h" #include "hostname-util.h" +#include "import-common.h" #include "import-util.h" #include "main-func.h" #include "signal-util.h" @@ -22,6 +23,7 @@ #include "verbs.h" static ImportCompressType arg_compress = IMPORT_COMPRESS_UNKNOWN; +static ImageClass arg_class = IMAGE_MACHINE; static void determine_compression_from_filename(const char *p) { @@ -43,12 +45,6 @@ static void determine_compression_from_filename(const char *p) { arg_compress = IMPORT_COMPRESS_UNCOMPRESSED; } -static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - log_notice("Transfer aborted."); - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - static void on_tar_finished(TarExport *export, int error, void *userdata) { sd_event *event = userdata; assert(export); @@ -67,12 +63,13 @@ static int export_tar(int argc, char *argv[], void *userdata) { _cleanup_close_ int open_fd = -EBADF; int r, fd; - if (hostname_is_valid(argv[1], 0)) { - r = image_find(IMAGE_MACHINE, argv[1], NULL, &image); + local = argv[1]; + if (image_name_is_valid(local)) { + r = image_find(arg_class, local, NULL, &image); if (r == -ENOENT) - return log_error_errno(r, "Machine image %s not found.", argv[1]); + return log_error_errno(r, "Image %s not found.", local); if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + return log_error_errno(r, "Failed to look for image %s: %m", local); local = image->path; } else @@ -101,13 +98,9 @@ static int export_tar(int argc, char *argv[], void *userdata) { log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); } - r = sd_event_default(&event); + r = import_allocate_event_with_signals(&event); if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + return r; r = tar_export_new(&export, event, on_tar_finished, event); if (r < 0) @@ -143,12 +136,13 @@ static int export_raw(int argc, char *argv[], void *userdata) { _cleanup_close_ int open_fd = -EBADF; int r, fd; - if (hostname_is_valid(argv[1], 0)) { - r = image_find(IMAGE_MACHINE, argv[1], NULL, &image); + local = argv[1]; + if (image_name_is_valid(local)) { + r = image_find(arg_class, local, NULL, &image); if (r == -ENOENT) - return log_error_errno(r, "Machine image %s not found.", argv[1]); + return log_error_errno(r, "Image %s not found.", local); if (r < 0) - return log_error_errno(r, "Failed to look for machine %s: %m", argv[1]); + return log_error_errno(r, "Failed to look for image %s: %m", local); local = image->path; } else @@ -177,13 +171,9 @@ static int export_raw(int argc, char *argv[], void *userdata) { log_info("Exporting '%s', saving to '%s' with compression '%s'.", local, strna(pretty), import_compress_type_to_string(arg_compress)); } - r = sd_event_default(&event); + r = import_allocate_event_with_signals(&event); if (r < 0) - return log_error_errno(r, "Failed to allocate event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + return r; r = raw_export_new(&export, event, on_raw_finished, event); if (r < 0) @@ -203,14 +193,16 @@ static int export_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sExport container or virtual machine images.%5$s\n" + "\n%4$sExport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar NAME [FILE] Export a TAR image\n" " raw NAME [FILE] Export a RAW image\n" "\n%2$sOptions:%3$s\n" " -h --help Show this help\n" " --version Show package version\n" - " --format=FORMAT Select format\n\n", + " --format=FORMAT Select format\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -225,12 +217,14 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_FORMAT, + ARG_CLASS, }; static const struct option options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "format", required_argument, NULL, ARG_FORMAT }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -263,6 +257,13 @@ static int parse_argv(int argc, char *argv[]) { "Unknown format: %s", optarg); break; + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -288,8 +289,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/import/import-common.c b/src/import/import-common.c index 319aa07..09faf16 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -276,7 +276,7 @@ bool import_validate_local(const char *name, ImportFlags flags) { if (FLAGS_SET(flags, IMPORT_DIRECT)) return path_is_valid(name); - return hostname_is_valid(name, 0); + return image_name_is_valid(name); } static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { @@ -295,9 +295,8 @@ int import_allocate_event_with_signals(sd_event **ret) { if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(event, NULL, SIGTERM, interrupt_signal_handler, NULL); - (void) sd_event_add_signal(event, NULL, SIGINT, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, interrupt_signal_handler, NULL); + (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, interrupt_signal_handler, NULL); *ret = TAKE_PTR(event); return 0; diff --git a/src/import/import-common.h b/src/import/import-common.h index 97fc16d..2198024 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -6,16 +6,33 @@ #include "sd-event.h" typedef enum ImportFlags { - IMPORT_FORCE = 1 << 0, /* replace existing image */ - IMPORT_READ_ONLY = 1 << 1, /* make generated image read-only */ - IMPORT_BTRFS_SUBVOL = 1 << 2, /* tar: preferably create images as btrfs subvols */ - IMPORT_BTRFS_QUOTA = 1 << 3, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ - IMPORT_CONVERT_QCOW2 = 1 << 4, /* raw: if we detect a qcow2 image, unpack it */ - IMPORT_DIRECT = 1 << 5, /* import without rename games */ - IMPORT_SYNC = 1 << 6, /* fsync() right before we are done */ - - IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, - IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, + /* Public Flags (i.e. accessible via D-Bus, must stay stable! */ + IMPORT_FORCE = 1 << 0, /* replace existing image */ + IMPORT_READ_ONLY = 1 << 1, /* make generated image read-only */ + IMPORT_PULL_KEEP_DOWNLOAD = 1 << 2, /* keep a pristine copy of the downloaded file around */ + + /* Private flags */ + IMPORT_BTRFS_SUBVOL = 1 << 3, /* tar: preferably create images as btrfs subvols */ + IMPORT_BTRFS_QUOTA = 1 << 4, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ + IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */ + IMPORT_DIRECT = 1 << 6, /* import without rename games */ + IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */ + + /* When pulling these flags are defined too */ + IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */ + IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */ + IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */ + IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */ + + /* The supported flags for the tar and the raw importing */ + IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, + IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, + + /* The supported flags for the tar and the raw pulling */ + IMPORT_PULL_FLAGS_MASK_TAR = IMPORT_FLAGS_MASK_TAR|IMPORT_PULL_KEEP_DOWNLOAD|IMPORT_PULL_SETTINGS, + IMPORT_PULL_FLAGS_MASK_RAW = IMPORT_FLAGS_MASK_RAW|IMPORT_PULL_KEEP_DOWNLOAD|IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY, + + _IMPORT_FLAGS_INVALID = -EINVAL, } ImportFlags; int import_fork_tar_c(const char *path, pid_t *ret); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index fd79c8f..44fc5be 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -31,7 +31,8 @@ static bool arg_btrfs_subvol = true; static bool arg_btrfs_quota = true; static bool arg_sync = true; static bool arg_direct = false; -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; +static ImageClass arg_class = IMAGE_MACHINE; typedef struct ProgressInfo { RateLimit limit; @@ -132,7 +133,7 @@ static int import_fs(int argc, char *argv[], void *userdata) { "Local path name '%s' is not valid.", final_path); } else { if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); } else @@ -143,7 +144,7 @@ static int import_fs(int argc, char *argv[], void *userdata) { return log_oom(); if (!arg_force) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -170,6 +171,8 @@ static int import_fs(int argc, char *argv[], void *userdata) { log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); } + log_info("Operating on image directory '%s'.", arg_image_root); + if (!arg_sync) log_info("File system synchronization on completion is off."); @@ -266,7 +269,9 @@ static int help(int argc, char *argv[], void *userdata) { " instead of a directory\n" " --btrfs-quota=BOOL Controls whether to set up quota for btrfs\n" " subvolume\n" - " --sync=BOOL Controls whether to sync() before completing\n", + " --sync=BOOL Controls whether to sync() before completing\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -287,6 +292,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_BTRFS_SUBVOL, ARG_BTRFS_QUOTA, ARG_SYNC, + ARG_CLASS, }; static const struct option options[] = { @@ -299,6 +305,7 @@ static int parse_argv(int argc, char *argv[]) { { "btrfs-subvol", required_argument, NULL, ARG_BTRFS_SUBVOL }, { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, { "sync", required_argument, NULL, ARG_SYNC }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -354,6 +361,13 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -361,6 +375,9 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + return 1; } @@ -379,8 +396,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/import/import-raw.c b/src/import/import-raw.c index f7ed163..ee9b297 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -95,8 +95,9 @@ int raw_import_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 9020270..39df11b 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -97,8 +97,9 @@ int tar_import_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; diff --git a/src/import/import.c b/src/import/import.c index a81617d..889cd63 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -25,9 +25,10 @@ #include "terminal-util.h" #include "verbs.h" -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; +static ImageClass arg_class = IMAGE_MACHINE; static int normalize_local(const char *local, char **ret) { _cleanup_free_ char *ll = NULL; @@ -53,7 +54,7 @@ static int normalize_local(const char *local, char **ret) { "Local path name '%s' is not valid.", local); } else { if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); @@ -61,7 +62,7 @@ static int normalize_local(const char *local, char **ret) { local = "imported"; if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -113,6 +114,12 @@ static int open_source(const char *path, const char *local, int *ret_open_fd) { log_info("Importing '%s', saving as '%s'.", strempty(pretty), local); } + if (!FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) + log_info("Operating on image directory '%s'.", arg_image_root); + + if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) + log_info("File system synchronization on completion is off."); + *ret_open_fd = TAKE_FD(open_fd); return retval; } @@ -160,15 +167,12 @@ static int import_tar(int argc, char *argv[], void *userdata) { fd = open_source(path, normalized, &open_fd); if (fd < 0) - return r; + return fd; r = import_allocate_event_with_signals(&event); if (r < 0) return r; - if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) - log_info("File system synchronization on completion is off."); - r = tar_import_new(&import, event, arg_image_root, on_tar_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate importer: %m"); @@ -238,9 +242,6 @@ static int import_raw(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) - log_info("File system synchronization on completion is off."); - r = raw_import_new(&import, event, arg_image_root, on_raw_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate importer: %m"); @@ -266,7 +267,7 @@ static int import_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sImport container or virtual machine images.%5$s\n" + "\n%4$sImport disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar FILE [NAME] Import a TAR image\n" " raw FILE [NAME] Import a RAW image\n" @@ -285,7 +286,9 @@ static int help(int argc, char *argv[], void *userdata) { " regular disk images\n" " --sync=BOOL Controls whether to sync() before completing\n" " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n", + " --size-max=BYTES Maximum number of bytes to write to destination\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -309,6 +312,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYNC, ARG_OFFSET, ARG_SIZE_MAX, + ARG_CLASS, }; static const struct option options[] = { @@ -324,6 +328,7 @@ static int parse_argv(int argc, char *argv[]) { { "sync", required_argument, NULL, ARG_SYNC }, { "offset", required_argument, NULL, ARG_OFFSET }, { "size-max", required_argument, NULL, ARG_SIZE_MAX }, + { "class", required_argument, NULL, ARG_CLASS }, {} }; @@ -416,6 +421,13 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + case '?': return -EINVAL; @@ -432,6 +444,9 @@ static int parse_argv(int argc, char *argv[]) { if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + return 1; } @@ -475,8 +490,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); parse_env(); diff --git a/src/import/importctl.c b/src/import/importctl.c new file mode 100644 index 0000000..f939d80 --- /dev/null +++ b/src/import/importctl.c @@ -0,0 +1,1245 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" + +#include "alloc-util.h" +#include "build.h" +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "discover-image.h" +#include "fd-util.h" +#include "format-table.h" +#include "hostname-util.h" +#include "import-common.h" +#include "import-util.h" +#include "locale-util.h" +#include "log.h" +#include "macro.h" +#include "main-func.h" +#include "os-util.h" +#include "pager.h" +#include "parse-argument.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "signal-util.h" +#include "sort-util.h" +#include "spawn-polkit-agent.h" +#include "string-table.h" +#include "verbs.h" +#include "web-util.h" + +static PagerFlags arg_pager_flags = 0; +static bool arg_legend = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static ImportFlags arg_import_flags = 0; +static ImportFlags arg_import_flags_mask = 0; /* Indicates which flags have been explicitly set to on or to off */ +static bool arg_quiet = false; +static bool arg_ask_password = true; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static const char* arg_format = NULL; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +static ImageClass arg_image_class = _IMAGE_CLASS_INVALID; + +#define PROGRESS_PREFIX "Total: " + +static int settle_image_class(void) { + + if (arg_image_class < 0) { + _cleanup_free_ char *j = NULL; + + for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) + if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)", + image_class_to_string(class), + image_root_to_string(class)) < 0) + return log_oom(); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No image class specified, retry with --class= set to one of: %s.", j); + } + + /* Keep the original pristine downloaded file as a copy only when dealing with machine images, + * because unlike sysext/confext/portable they are typically modified during runtime. */ + if (!FLAGS_SET(arg_import_flags_mask, IMPORT_PULL_KEEP_DOWNLOAD)) + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_image_class == IMAGE_MACHINE); + + return 0; +} + +typedef struct Context { + const char *object_path; + double progress; +} Context; + +static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + const char *line; + unsigned priority; + int r; + + assert(m); + + if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) + return 0; + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); + + log_full(priority, "%s", line); + + if (!arg_quiet) + draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); + + return 0; +} + +static int match_progress_update(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(m); + + if (!streq_ptr(c->object_path, sd_bus_message_get_path(m))) + return 0; + + r = sd_bus_message_read(m, "d", &c->progress); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!arg_quiet) + draw_progress_bar(PROGRESS_PREFIX, c->progress * 100); + + return 0; +} + +static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = ASSERT_PTR(userdata); + const char *path, *result; + uint32_t id; + int r; + + assert(m); + + if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(c->object_path, path)) + return 0; + + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); + return 0; +} + +static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + + if (!arg_quiet) + clear_progress_bar(PROGRESS_PREFIX); + + if (!arg_quiet) + log_info("Continuing download in the background. Use \"%s cancel-transfer %" PRIu32 "\" to abort transfer.", + program_invocation_short_name, + PTR_TO_UINT32(userdata)); + + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL, *slot_progress_update = 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_event_unrefp) sd_event* event = NULL; + Context c = {}; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = bus_match_signal_async( + bus, + &slot_job_removed, + bus_import_mgr, + "TransferRemoved", + match_transfer_removed, + /* add_callback= */ NULL, + &c); + if (r < 0) + return log_error_errno(r, "Failed to request match: %m"); + + r = sd_bus_match_signal_async( + bus, + &slot_log_message, + "org.freedesktop.import1", + /* object_path= */ NULL, + "org.freedesktop.import1.Transfer", + "LogMessage", + match_log_message, + /* add_callback= */ NULL, + &c); + if (r < 0) + return log_error_errno(r, "Failed to request match: %m"); + + r = sd_bus_match_signal_async( + bus, + &slot_progress_update, + "org.freedesktop.import1", + /* object_path= */ NULL, + "org.freedesktop.import1.Transfer", + "ProgressUpdate", + match_progress_update, + /* add_callback= */ NULL, + &c); + if (r < 0) + return log_error_errno(r, "Failed to request match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to transfer image: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "uo", &id, &c.object_path); + if (r < 0) + return bus_log_parse_error(r); + + if (!arg_quiet) { + clear_progress_bar(PROGRESS_PREFIX); + log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + draw_progress_bar(PROGRESS_PREFIX, c.progress); + } + + (void) sd_event_add_signal(event, NULL, SIGINT|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); + (void) sd_event_add_signal(event, NULL, SIGTERM|SD_EVENT_SIGNAL_PROCMASK, transfer_signal_handler, UINT32_TO_PTR(id)); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return -r; +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL, *fn = NULL; + const char *local = NULL, *path = NULL; + _cleanup_close_ int fd = -EBADF; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + if (argc >= 2) + path = empty_or_dash_to_null(argv[1]); + + if (argc >= 3) + local = empty_or_dash_to_null(argv[2]); + else if (path) { + r = path_extract_filename(path, &fn); + if (r < 0) + return log_error_errno(r, "Cannot extract container name from filename: %m"); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Path '%s' refers to directory, but we need a regular file: %m", path); + + local = fn; + } + if (!local) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Need either path or local name."); + + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + FLAGS_SET(arg_import_flags, IMPORT_FORCE), + FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTarEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsst", + fd >= 0 ? fd : STDIN_FILENO, + local, + image_class_to_string(arg_image_class), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL, *fn = NULL; + const char *local = NULL, *path = NULL; + _cleanup_close_ int fd = -EBADF; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + if (argc >= 2) + path = empty_or_dash_to_null(argv[1]); + + if (argc >= 3) + local = empty_or_dash_to_null(argv[2]); + else if (path) { + r = path_extract_filename(path, &fn); + if (r < 0) + return log_error_errno(r, "Cannot extract container name from filename: %m"); + if (r == O_DIRECTORY) + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Path '%s' refers to directory, but we need a regular file: %m", path); + + local = fn; + } + if (!local) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Need either path or local name."); + + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + FLAGS_SET(arg_import_flags, IMPORT_FORCE), + FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRawEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsst", + fd >= 0 ? fd : STDIN_FILENO, + local, + image_class_to_string(arg_image_class), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_fs(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + const char *local = NULL, *path = NULL; + _cleanup_free_ char *fn = NULL; + _cleanup_close_ int fd = -EBADF; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + if (argc >= 2) + path = empty_or_dash_to_null(argv[1]); + + if (argc >= 3) + local = empty_or_dash_to_null(argv[2]); + else if (path) { + r = path_extract_filename(path, &fn); + if (r < 0) + return log_error_errno(r, "Cannot extract container name from filename: %m"); + + local = fn; + } + if (!local) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Need either path or local name."); + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + + if (path) { + fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open directory '%s': %m", path); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY)) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystem"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + FLAGS_SET(arg_import_flags, IMPORT_FORCE), + FLAGS_SET(arg_import_flags, IMPORT_READ_ONLY)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystemEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsst", + fd >= 0 ? fd : STDIN_FILENO, + local, + image_class_to_string(arg_image_class), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY)); + } + if (r < 0) + return bus_log_create_error(r); + + + return transfer_image_common(bus, m); +} + +static void determine_compression_from_filename(const char *p) { + if (arg_format) + return; + + if (!p) + return; + + if (endswith(p, ".xz")) + arg_format = "xz"; + else if (endswith(p, ".gz")) + arg_format = "gzip"; + else if (endswith(p, ".bz2")) + arg_format = "bzip2"; +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -EBADF; + const char *local = NULL, *path = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + local = argv[1]; + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Image name %s is not valid.", local); + + if (argc >= 3) + path = argv[2]; + path = empty_or_dash_to_null(path); + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTarEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sshst", + local, + image_class_to_string(arg_image_class), + fd >= 0 ? fd : STDOUT_FILENO, + arg_format, + /* flags= */ UINT64_C(0)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -EBADF; + const char *local = NULL, *path = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + local = argv[1]; + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Image name %s is not valid.", local); + + if (argc >= 3) + path = argv[2]; + path = empty_or_dash_to_null(path); + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + if (arg_image_class == IMAGE_MACHINE && arg_import_flags == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRawEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sshst", + local, + image_class_to_string(arg_image_class), + fd >= 0 ? fd : STDOUT_FILENO, + arg_format, + /* flags= */ UINT64_C(0)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTarEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + r = settle_image_class(); + if (r < 0) + return r; + + remote = argv[1]; + if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "URL '%s' is not valid.", remote); + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + local = empty_or_dash_to_null(local); + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!image_name_is_valid(local)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Local name %s is not a suitable image name.", + local); + } + + if (arg_image_class == IMAGE_MACHINE && (arg_import_flags & ~IMPORT_FORCE) == 0) { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + FLAGS_SET(arg_import_flags, IMPORT_FORCE)); + } else { + r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRawEx"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssst", + remote, + local, + image_class_to_string(arg_image_class), + import_verify_to_string(arg_verify), + (uint64_t) arg_import_flags & (IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)); + } + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + _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 *t = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + pager_open(arg_pager_flags); + + bool ex; + r = bus_call_method(bus, bus_import_mgr, "ListTransfersEx", &error, &reply, "st", image_class_to_string(arg_image_class), UINT64_C(0)); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(&error); + + r = bus_call_method(bus, bus_import_mgr, "ListTransfers", &error, &reply, NULL); + } + if (r < 0) + return log_error_errno(r, "Could not get transfers: %s", bus_error_message(&error, r)); + + ex = false; + r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); + } else { + ex = true; + r = sd_bus_message_enter_container(reply, 'a', "(ussssdo)"); + } + if (r < 0) + return bus_log_parse_error(r); + + t = table_new("id", "progress", "type", "class", "local", "remote"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 4, (size_t) 0); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + for (;;) { + const char *type, *remote, *local, *class = "machine"; + double progress; + uint32_t id; + + if (ex) + r = sd_bus_message_read(reply, "(ussssdo)", &id, &type, &remote, &local, &class, &progress, NULL); + else + r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + /* Ideally we use server-side filtering. But if the server can't do it, we need to do it client side */ + if (arg_image_class >= 0 && image_class_from_string(class) != arg_image_class) + continue; + + r = table_add_many( + t, + TABLE_UINT32, id, + TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + + if (progress < 0) + r = table_add_many( + t, + TABLE_EMPTY, + TABLE_SET_ALIGN_PERCENT, 100); + else + r = table_add_many( + t, + TABLE_PERCENT, (int) (progress * 100), + TABLE_SET_ALIGN_PERCENT, 100); + if (r < 0) + return table_log_add_error(r); + r = table_add_many( + t, + TABLE_STRING, type, + TABLE_STRING, class, + TABLE_STRING, local, + TABLE_STRING, remote, + TABLE_SET_URL, remote); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + } + + if (arg_legend) { + if (!table_isempty(t)) + printf("\n%zu transfers listed.\n", table_get_rows(t) - 1); + else + printf("No transfers.\n"); + } + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + polkit_agent_open_if_enabled(arg_transport, arg_ask_password); + + for (int i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = bus_call_method(bus, bus_import_mgr, "CancelTransfer", &error, NULL, "u", id); + if (r < 0) + return log_error_errno(r, "Could not cancel transfer: %s", bus_error_message(&error, r)); + } + + return 0; +} + +static int list_images(int argc, char *argv[], void *userdata) { + _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 *t = NULL; + sd_bus *bus = ASSERT_PTR(userdata); + int r; + + pager_open(arg_pager_flags); + + r = bus_call_method(bus, bus_import_mgr, "ListImages", &error, &reply, "st", image_class_to_string(arg_image_class), UINT64_C(0)); + if (r < 0) + return log_error_errno(r, "Could not list images: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(ssssbtttttt)"); + if (r < 0) + return bus_log_parse_error(r); + + t = table_new("class", "name", "type", "path", "ro", "crtime", "mtime", "usage", "usage-exclusive", "limit", "limit-exclusive"); + if (!t) + return log_oom(); + + (void) table_set_sort(t, (size_t) 0, (size_t) 1); + table_set_ersatz_string(t, TABLE_ERSATZ_DASH); + + /* Hide the exclusive columns for now */ + (void) table_hide_column_from_display(t, 8); + (void) table_hide_column_from_display(t, 10); + + for (;;) { + uint64_t crtime, mtime, usage, usage_exclusive, limit, limit_exclusive; + const char *class, *name, *type, *path; + int read_only; + + r = sd_bus_message_read(reply, "(ssssbtttttt)", &class, &name, &type, &path, &read_only, &crtime, &mtime, &usage, &usage_exclusive, &limit, &limit_exclusive); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_many( + t, + TABLE_STRING, class, + TABLE_STRING, name, + TABLE_STRING, type, + TABLE_PATH, path); + if (r < 0) + return table_log_add_error(r); + + if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + r = table_add_many( + t, + TABLE_STRING, read_only ? "ro" : "rw", + TABLE_SET_COLOR, read_only ? ANSI_HIGHLIGHT_RED : ANSI_HIGHLIGHT_GREEN); + else + r = table_add_many( + t, + TABLE_BOOLEAN, read_only); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many( + t, + TABLE_TIMESTAMP, crtime, + TABLE_TIMESTAMP, mtime, + TABLE_SIZE, usage, + TABLE_SIZE, usage_exclusive, + TABLE_SIZE, limit, + TABLE_SIZE, limit_exclusive); + if (r < 0) + return table_log_add_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!table_isempty(t)) { + r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return log_error_errno(r, "Failed to output table: %m"); + } + + if (arg_legend) { + if (!table_isempty(t)) + printf("\n%zu images listed.\n", table_get_rows(t) - 1); + else + printf("No images.\n"); + } + + return 0; +} + +static int help(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *link = NULL; + int r; + + pager_open(arg_pager_flags); + + r = terminal_urlify_man("importctl", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] COMMAND ...\n\n" + "%5$sDownload, import or export disk images%6$s\n" + "\n%3$sCommands:%4$s\n" + " pull-tar URL [NAME] Download a TAR container image\n" + " pull-raw URL [NAME] Download a RAW container or VM image\n" + " import-tar FILE [NAME] Import a local TAR container image\n" + " import-raw FILE [NAME] Import a local RAW container or VM image\n" + " import-fs DIRECTORY [NAME] Import a local directory container image\n" + " export-tar NAME [FILE] Export a TAR container image locally\n" + " export-raw NAME [FILE] Export a RAW container or VM image locally\n" + " list-transfers Show list of transfers in progress\n" + " cancel-transfer [ID...] Cancel a transfer\n" + " list-images Show list of installed images\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --read-only Create read-only image\n" + " -q --quiet Suppress output\n" + " --json=pretty|short|off Generate JSON output\n" + " -j Equvilant to --json=pretty on TTY, --json=short\n" + " otherwise\n" + " --verify=MODE Verification mode for downloaded images (no,\n" + " checksum, signature)\n" + " --format=xz|gzip|bzip2 Desired output format for export\n" + " --force Install image even if already exists\n" + " -m --class=machine Install as machine image\n" + " -P --class=portable Install as portable service image\n" + " -S --class=sysext Install as system extension image\n" + " -C --class=confext Install as configuration extension image\n" + " --keep-download=BOOL Control whether to keep pristine copy of download\n" + " -N Shortcut for --keep-download=no\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_NO_ASK_PASSWORD, + ARG_READ_ONLY, + ARG_JSON, + ARG_VERIFY, + ARG_FORCE, + ARG_FORMAT, + ARG_CLASS, + ARG_KEEP_DOWNLOAD, + }; + + 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 }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "json", required_argument, NULL, ARG_JSON }, + { "quiet", no_argument, NULL, 'q' }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "format", required_argument, NULL, ARG_FORMAT }, + { "class", required_argument, NULL, ARG_CLASS }, + { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + for (;;) { + c = getopt_long(argc, argv, "hH:M:jqmPSCN", options, NULL); + if (c < 0) + break; + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_pager_flags |= PAGER_DISABLE; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_READ_ONLY: + arg_import_flags |= IMPORT_READ_ONLY; + arg_import_flags_mask |= IMPORT_READ_ONLY; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERIFY: + if (streq(optarg, "help")) { + DUMP_STRING_TABLE(import_verify, ImportVerify, _IMPORT_VERIFY_MAX); + return 0; + } + + r = import_verify_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --verify= setting: %s", optarg); + arg_verify = r; + break; + + case ARG_FORCE: + arg_import_flags |= IMPORT_FORCE; + arg_import_flags_mask |= IMPORT_FORCE; + break; + + case ARG_FORMAT: + if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unknown format: %s", optarg); + + arg_format = optarg; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + arg_legend = false; + break; + + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + arg_legend = false; + break; + + case ARG_CLASS: + arg_image_class = image_class_from_string(optarg); + if (arg_image_class < 0) + return log_error_errno(arg_image_class, "Failed to parse --class= parameter: %s", optarg); + break; + + case 'm': + arg_image_class = IMAGE_MACHINE; + break; + + case 'P': + arg_image_class = IMAGE_PORTABLE; + break; + + case 'S': + arg_image_class = IMAGE_SYSEXT; + break; + + case 'C': + arg_image_class = IMAGE_CONFEXT; + break; + + case ARG_KEEP_DOWNLOAD: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-download= value: %s", optarg); + + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); + arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; + break; + + case 'N': + arg_import_flags_mask &= ~IMPORT_PULL_KEEP_DOWNLOAD; + arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + return 1; +} + +static int importctl_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "import-tar", 2, 3, 0, import_tar }, + { "import-raw", 2, 3, 0, import_raw }, + { "import-fs", 2, 3, 0, import_fs }, + { "export-tar", 2, 3, 0, export_tar }, + { "export-raw", 2, 3, 0, export_raw }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "list-transfers", VERB_ANY, 1, VERB_DEFAULT, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, + { "list-images", VERB_ANY, 1, 0, list_images }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +static int run(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus); + if (r < 0) + return bus_log_connect_error(r, arg_transport); + + (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + + return importctl_main(argc, argv, bus); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/import/importd.c b/src/import/importd.c index e1a1ddc..3bfa3cd 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -6,21 +6,27 @@ #include "sd-bus.h" #include "alloc-util.h" +#include "build-path.h" #include "bus-common-errors.h" #include "bus-get-properties.h" #include "bus-log-control-api.h" #include "bus-polkit.h" #include "common-signal.h" #include "constants.h" +#include "daemon-util.h" +#include "discover-image.h" #include "env-util.h" +#include "event-util.h" #include "fd-util.h" #include "float.h" #include "hostname-util.h" +#include "import-common.h" #include "import-util.h" #include "machine-pool.h" #include "main-func.h" #include "missing_capability.h" #include "mkdir-label.h" +#include "os-util.h" #include "parse-util.h" #include "path-util.h" #include "percent-util.h" @@ -61,12 +67,11 @@ struct Transfer { char *remote; char *local; - bool force_local; - bool read_only; - + ImageClass class; + ImportFlags flags; char *format; - pid_t pid; + PidRef pidref; int log_fd; @@ -78,6 +83,7 @@ struct Transfer { unsigned n_canceled; unsigned progress_percent; + unsigned progress_percent_sent; int stdin_fd; int stdout_fd; @@ -105,11 +111,11 @@ struct Manager { static const char* const transfer_type_table[_TRANSFER_TYPE_MAX] = { [TRANSFER_IMPORT_TAR] = "import-tar", [TRANSFER_IMPORT_RAW] = "import-raw", - [TRANSFER_IMPORT_FS] = "import-fs", + [TRANSFER_IMPORT_FS] = "import-fs", [TRANSFER_EXPORT_TAR] = "export-tar", [TRANSFER_EXPORT_RAW] = "export-raw", - [TRANSFER_PULL_TAR] = "pull-tar", - [TRANSFER_PULL_RAW] = "pull-raw", + [TRANSFER_PULL_TAR] = "pull-tar", + [TRANSFER_PULL_RAW] = "pull-raw", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(transfer_type, TransferType); @@ -129,8 +135,7 @@ static Transfer *transfer_unref(Transfer *t) { free(t->format); free(t->object_path); - if (t->pid > 1) - sigkill_wait(t->pid); + pidref_done_sigkill_wait(&t->pidref); safe_close(t->log_fd); safe_close(t->stdin_fd); @@ -162,7 +167,8 @@ static int transfer_new(Manager *m, Transfer **ret) { .stdin_fd = -EBADF, .stdout_fd = -EBADF, .verify = _IMPORT_VERIFY_INVALID, - .progress_percent= UINT_MAX, + .progress_percent = UINT_MAX, + .progress_percent_sent = UINT_MAX, }; id = m->current_transfer_id + 1; @@ -213,7 +219,28 @@ static void transfer_send_log_line(Transfer *t, const char *line) { line); if (r < 0) log_warning_errno(r, "Cannot emit log message signal, ignoring: %m"); - } +} + +static void transfer_send_progress_update(Transfer *t) { + int r; + + assert(t); + + if (t->progress_percent_sent == t->progress_percent) + return; + + r = sd_bus_emit_signal( + t->manager->bus, + t->object_path, + "org.freedesktop.import1.Transfer", + "ProgressUpdate", + "d", + transfer_percent_as_double(t)); + if (r < 0) + log_warning_errno(r, "Cannot emit progress update signal, ignoring: %m"); + + t->progress_percent_sent = t->progress_percent; +} static void transfer_send_logs(Transfer *t, bool flush) { assert(t); @@ -300,7 +327,7 @@ static int transfer_cancel(Transfer *t) { assert(t); - r = kill_and_sigcont(t->pid, t->n_canceled < 3 ? SIGTERM : SIGKILL); + r = pidref_kill_and_sigcont(&t->pidref, t->n_canceled < 3 ? SIGTERM : SIGKILL); if (r < 0) return r; @@ -327,7 +354,7 @@ static int transfer_on_pid(sd_event_source *s, const siginfo_t *si, void *userda else log_error("Transfer process failed due to unknown reason."); - t->pid = 0; + pidref_done(&t->pidref); return transfer_finalize(t, success); } @@ -361,15 +388,17 @@ static int transfer_start(Transfer *t) { int r; assert(t); - assert(t->pid <= 0); + assert(!pidref_is_set(&t->pidref)); if (pipe2(pipefd, O_CLOEXEC) < 0) return -errno; - r = safe_fork_full("(sd-transfer)", - (int[]) { t->stdin_fd, t->stdout_fd < 0 ? pipefd[1] : t->stdout_fd, pipefd[1] }, - NULL, 0, - FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO, &t->pid); + r = pidref_safe_fork_full( + "(sd-transfer)", + (int[]) { t->stdin_fd, t->stdout_fd < 0 ? pipefd[1] : t->stdout_fd, pipefd[1] }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG, + &t->pidref); if (r < 0) return r; if (r == 0) { @@ -378,6 +407,9 @@ static int transfer_start(Transfer *t) { NULL, /* tar, raw */ NULL, /* --verify= */ NULL, /* verify argument */ + NULL, /* --class= */ + NULL, /* class argument */ + NULL, /* --keep-download= */ NULL, /* maybe --force */ NULL, /* maybe --read-only */ NULL, /* if so: the actual URL */ @@ -387,7 +419,7 @@ static int transfer_start(Transfer *t) { NULL, /* local */ NULL }; - unsigned k = 0; + size_t k = 0; /* Child */ @@ -397,6 +429,10 @@ static int transfer_start(Transfer *t) { _exit(EXIT_FAILURE); } + r = setenv_systemd_log_level(); + if (r < 0) + log_warning_errno(r, "Failed to update $SYSTEMD_LOG_LEVEL, ignoring: %m"); + r = setenv_systemd_exec_pid(true); if (r < 0) log_warning_errno(r, "Failed to update $SYSTEMD_EXEC_PID, ignoring: %m"); @@ -453,9 +489,18 @@ static int transfer_start(Transfer *t) { cmd[k++] = import_verify_to_string(t->verify); } - if (t->force_local) + if (t->class != IMAGE_MACHINE) { + cmd[k++] = "--class"; + cmd[k++] = image_class_to_string(t->class); + } + + if (IN_SET(t->type, TRANSFER_PULL_TAR, TRANSFER_PULL_RAW)) + cmd[k++] = FLAGS_SET(t->flags, IMPORT_PULL_KEEP_DOWNLOAD) ? + "--keep-download=yes" : "--keep-download=no"; + + if (FLAGS_SET(t->flags, IMPORT_FORCE)) cmd[k++] = "--force"; - if (t->read_only) + if (FLAGS_SET(t->flags, IMPORT_READ_ONLY)) cmd[k++] = "--read-only"; if (t->format) { @@ -474,8 +519,15 @@ static int transfer_start(Transfer *t) { cmd[k++] = t->local; cmd[k] = NULL; - execv(cmd[0], (char * const *) cmd); - log_error_errno(errno, "Failed to execute %s tool: %m", cmd[0]); + assert(k < ELEMENTSOF(cmd)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = strv_join((char**) cmd, " "); + log_debug("Calling: %s", strnull(joined)); + } + + r = invoke_callout_binary(cmd[0], (char * const *) cmd); + log_error_errno(r, "Failed to execute %s tool: %m", cmd[0]); _exit(EXIT_FAILURE); } @@ -484,8 +536,13 @@ static int transfer_start(Transfer *t) { t->stdin_fd = safe_close(t->stdin_fd); - r = sd_event_add_child(t->manager->event, &t->pid_event_source, - t->pid, WEXITED, transfer_on_pid, t); + r = event_add_child_pidref( + t->manager->event, + &t->pid_event_source, + &t->pidref, + WEXITED, + transfer_on_pid, + t); if (r < 0) return r; @@ -527,7 +584,7 @@ static Manager *manager_unref(Manager *m) { hashmap_free(m->transfers); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); m->bus = sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); @@ -580,7 +637,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void } HASHMAP_FOREACH(t, m->transfers) - if (ucred->pid == t->pid) + if (ucred->pid == t->pidref.pid) break; if (!t) { @@ -605,6 +662,8 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void t->progress_percent = (unsigned) r; log_debug("Got percentage from client: %u%%", t->progress_percent); + + transfer_send_progress_update(t); return 0; } @@ -631,17 +690,11 @@ static int manager_new(Manager **ret) { if (r < 0) return r; - (void) sd_event_set_watchdog(m->event, true); - - r = sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return r; - - r = sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + r = sd_event_set_signal_exit(m->event, true); if (r < 0) return r; - r = sd_event_add_signal(m->event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL); + r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); if (r < 0) return r; @@ -649,6 +702,10 @@ static int manager_new(Manager **ret) { if (r < 0) log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); + r = sd_event_set_watchdog(m->event, true); + if (r < 0) + log_debug_errno(r, "Failed to enable watchdog logic, ignoring: %m"); + r = sd_bus_default_system(&m->bus); if (r < 0) return r; @@ -693,22 +750,20 @@ static Transfer *manager_find(Manager *m, TransferType type, const char *remote) static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, force, read_only, r; - const char *local, *object; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); + const char *local; TransferType type; struct stat st; - uint32_t id; + uint64_t flags; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.import", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -716,7 +771,36 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "hsst", &fd, &local, &sclass, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_READ_ONLY|IMPORT_FORCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force, read_only; + + r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + SET_FLAG(flags, IMPORT_READ_ONLY, read_only); + } + + r = fd_verify_safe_flags(fd); if (r < 0) return r; @@ -726,15 +810,17 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) return -EINVAL; - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } - type = streq_ptr(sd_bus_message_get_member(msg), "ImportTar") ? + type = startswith(sd_bus_message_get_member(msg), "ImportTar") ? TRANSFER_IMPORT_TAR : TRANSFER_IMPORT_RAW; r = transfer_new(m, &t); @@ -742,8 +828,8 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ return r; t->type = type; - t->force_local = force; - t->read_only = read_only; + t->class = class; + t->flags = flags; t->local = strdup(local); if (!t->local) @@ -757,29 +843,28 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, force, read_only, r; - const char *local, *object; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); - uint32_t id; + const char *local; + uint64_t flags; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.import", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -787,7 +872,36 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "hsst", &fd, &local, &sclass, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_READ_ONLY|IMPORT_FORCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force, read_only; + + r = sd_bus_message_read(msg, "hsbb", &fd, &local, &force, &read_only); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + SET_FLAG(flags, IMPORT_READ_ONLY, read_only); + } + + r = fd_verify_safe_flags_full(fd, O_DIRECTORY); if (r < 0) return r; @@ -795,21 +909,23 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r < 0) return r; - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } r = transfer_new(m, &t); if (r < 0) return r; t->type = TRANSFER_IMPORT_FS; - t->force_local = force; - t->read_only = read_only; + t->class = class; + t->flags = flags; t->local = strdup(local); if (!t->local) @@ -823,31 +939,30 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - int fd, r; - const char *local, *object, *format; + ImageClass class = _IMAGE_CLASS_INVALID; Manager *m = ASSERT_PTR(userdata); + const char *local, *format; TransferType type; + uint64_t flags; struct stat st; - uint32_t id; + int fd, r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.export", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -855,13 +970,37 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "shs", &local, &fd, &format); - if (r < 0) - return r; + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "sshst", &local, &sclass, &fd, &format, &flags); + if (r < 0) + return r; - if (!hostname_is_valid(local, 0)) + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + r = sd_bus_message_read(msg, "shs", &local, &fd, &format); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + flags = 0; + } + + if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); + + r = fd_verify_safe_flags(fd); + if (r < 0) + return r; if (fstat(fd, &st) < 0) return -errno; @@ -869,7 +1008,7 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (!S_ISREG(st.st_mode) && !S_ISFIFO(st.st_mode)) return -EINVAL; - type = streq_ptr(sd_bus_message_get_member(msg), "ExportTar") ? + type = startswith(sd_bus_message_get_member(msg), "ExportTar") ? TRANSFER_EXPORT_TAR : TRANSFER_EXPORT_RAW; r = transfer_new(m, &t); @@ -877,6 +1016,8 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ return r; t->type = type; + t->class = class; + t->flags = flags; if (!isempty(format)) { t->format = strdup(format); @@ -896,31 +1037,30 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(transfer_unrefp) Transfer *t = NULL; - const char *remote, *local, *verify, *object; + ImageClass class = _IMAGE_CLASS_INVALID; + const char *remote, *local, *verify; Manager *m = ASSERT_PTR(userdata); - ImportVerify v; TransferType type; - int force, r; - uint32_t id; + uint64_t flags; + ImportVerify v; + int r; assert(msg); r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -928,9 +1068,33 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (r == 0) return 1; /* Will call us back */ - r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); - if (r < 0) - return r; + if (endswith(sd_bus_message_get_member(msg), "Ex")) { + const char *sclass; + + r = sd_bus_message_read(msg, "sssst", &remote, &local, &sclass, &verify, &flags); + if (r < 0) + return r; + + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + + if (flags & ~(IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_PULL_KEEP_DOWNLOAD)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } else { + int force; + + r = sd_bus_message_read(msg, "sssb", &remote, &local, &verify, &force); + if (r < 0) + return r; + + class = IMAGE_MACHINE; + + flags = 0; + SET_FLAG(flags, IMPORT_FORCE, force); + } if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, @@ -938,9 +1102,9 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (isempty(local)) local = NULL; - else if (!hostname_is_valid(local, 0)) + else if (!image_name_is_valid(local)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, - "Local name %s is invalid", local); + "Local image name %s is invalid", local); if (isempty(verify)) v = IMPORT_VERIFY_SIGNATURE; @@ -950,11 +1114,13 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown verification mode %s", verify); - r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); - if (r < 0) - return r; + if (class == IMAGE_MACHINE) { + r = setup_machine_directory(error, m->use_btrfs_subvol, m->use_btrfs_quota); + if (r < 0) + return r; + } - type = streq_ptr(sd_bus_message_get_member(msg), "PullTar") ? + type = startswith(sd_bus_message_get_member(msg), "PullTar") ? TRANSFER_PULL_TAR : TRANSFER_PULL_RAW; if (manager_find(m, type, remote)) @@ -967,7 +1133,8 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er t->type = type; t->verify = v; - t->force_local = force; + t->flags = flags; + t->class = class; t->remote = strdup(remote); if (!t->remote) @@ -983,40 +1150,81 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er if (r < 0) return r; - object = t->object_path; - id = t->id; - t = NULL; + r = sd_bus_reply_method_return(msg, "uo", t->id, t->object_path); + if (r < 0) + return r; - return sd_bus_reply_method_return(msg, "uo", id, object); + TAKE_PTR(t); + return 1; } static int method_list_transfers(sd_bus_message *msg, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = ASSERT_PTR(userdata); + ImageClass class = _IMAGE_CLASS_INVALID; Transfer *t; int r; assert(msg); + bool ex = endswith(sd_bus_message_get_member(msg), "Ex"); + if (ex) { + const char *sclass; + uint64_t flags; + + r = sd_bus_message_read(msg, "st", &sclass, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(sclass)) { + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + } + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + } + r = sd_bus_message_new_method_return(msg, &reply); if (r < 0) return r; - r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); + if (ex) + r = sd_bus_message_open_container(reply, 'a', "(ussssdo)"); + else + r = sd_bus_message_open_container(reply, 'a', "(usssdo)"); if (r < 0) return r; HASHMAP_FOREACH(t, m->transfers) { - r = sd_bus_message_append( - reply, - "(usssdo)", - t->id, - transfer_type_to_string(t->type), - t->remote, - t->local, - transfer_percent_as_double(t), - t->object_path); + if (class >= 0 && class != t->class) + continue; + + if (ex) + r = sd_bus_message_append( + reply, + "(ussssdo)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + image_class_to_string(t->class), + transfer_percent_as_double(t), + t->object_path); + else + r = sd_bus_message_append( + reply, + "(usssdo)", + t->id, + transfer_type_to_string(t->type), + t->remote, + t->local, + transfer_percent_as_double(t), + t->object_path); if (r < 0) return r; } @@ -1036,11 +1244,8 @@ static int method_cancel(sd_bus_message *msg, void *userdata, sd_bus_error *erro r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &t->manager->polkit_registry, error); if (r < 0) @@ -1065,11 +1270,8 @@ static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_er r = bus_verify_polkit_async( msg, - CAP_SYS_ADMIN, - "org.freedesktop.import1.pull", - NULL, - false, - UID_INVALID, + "org.freedesktop.import1.cancel", + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -1094,6 +1296,86 @@ static int method_cancel_transfer(sd_bus_message *msg, void *userdata, sd_bus_er return sd_bus_reply_method_return(msg, NULL); } +static int method_list_images(sd_bus_message *msg, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + ImageClass class = _IMAGE_CLASS_INVALID; + int r; + + assert(msg); + + const char *sclass; + uint64_t flags; + + r = sd_bus_message_read(msg, "st", &sclass, &flags); + if (r < 0) + return r; + + if (!isempty(sclass)) { + class = image_class_from_string(sclass); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Image class '%s' not known", sclass); + } + + if (flags != 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Flags 0x%" PRIx64 " invalid", flags); + + r = sd_bus_message_new_method_return(msg, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssssbtttttt)"); + if (r < 0) + return r; + + for (ImageClass c = class < 0 ? 0 : class; + class < 0 ? (c < _IMAGE_CLASS_MAX) : (c == class); + c++) { + + _cleanup_(hashmap_freep) Hashmap *h = NULL; + + h = hashmap_new(&image_hash_ops); + if (!h) + return -ENOMEM; + + r = image_discover(c, /* root= */ NULL, h); + if (r < 0) { + if (class >= 0) + return r; + + log_warning_errno(r, "Failed to discover images of type %s: %m", image_class_to_string(c)); + continue; + } + + Image *i; + HASHMAP_FOREACH(i, h) { + r = sd_bus_message_append( + reply, + "(ssssbtttttt)", + image_class_to_string(i->class), + i->name, + image_type_to_string(i->type), + i->path, + i->read_only, + i->crtime, + i->mtime, + i->usage, + i->usage_exclusive, + i->limit, + i->limit_exclusive); + if (r < 0) + return r; + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + static int property_get_progress( sd_bus *bus, const char *path, @@ -1196,6 +1478,10 @@ static const sd_bus_vtable transfer_vtable[] = { SD_BUS_PARAM(priority) SD_BUS_PARAM(line), 0), + SD_BUS_SIGNAL_WITH_NAMES("ProgressUpdate", + "d", + SD_BUS_PARAM(progress), + 0), SD_BUS_VTABLE_END, }; @@ -1221,6 +1507,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportTarEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ImportRaw", "hsbb", SD_BUS_PARAM(fd) @@ -1232,6 +1529,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportRawEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ImportFileSystem", "hsbb", SD_BUS_PARAM(fd) @@ -1243,6 +1551,17 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_import_fs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ImportFileSystemEx", + "hsst", + SD_BUS_PARAM(fd) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_import_fs, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ExportTar", "shs", SD_BUS_PARAM(local_name) @@ -1253,6 +1572,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ExportTarEx", + "sshst", + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(fd) + SD_BUS_PARAM(format) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_export_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ExportRaw", "shs", SD_BUS_PARAM(local_name) @@ -1263,6 +1594,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_export_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ExportRawEx", + "sshst", + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(fd) + SD_BUS_PARAM(format) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_export_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("PullTar", "sssb", SD_BUS_PARAM(url) @@ -1274,6 +1617,18 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("PullTarEx", + "sssst", + SD_BUS_PARAM(url) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(verify_mode) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_pull_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("PullRaw", "sssb", SD_BUS_PARAM(url) @@ -1285,18 +1640,46 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PARAM(transfer_path), method_pull_tar_or_raw, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("PullRawEx", + "sssst", + SD_BUS_PARAM(url) + SD_BUS_PARAM(local_name) + SD_BUS_PARAM(class) + SD_BUS_PARAM(verify_mode) + SD_BUS_PARAM(flags), + "uo", + SD_BUS_PARAM(transfer_id) + SD_BUS_PARAM(transfer_path), + method_pull_tar_or_raw, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("ListTransfers", NULL,, "a(usssdo)", SD_BUS_PARAM(transfers), method_list_transfers, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ListTransfersEx", + "st", + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "a(ussssdo)", + SD_BUS_PARAM(transfers), + method_list_transfers, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_NAMES("CancelTransfer", "u", SD_BUS_PARAM(transfer_id), NULL,, method_cancel_transfer, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_NAMES("ListImages", + "st", + SD_BUS_PARAM(class) + SD_BUS_PARAM(flags), + "a(ssssbtttttt)", + SD_BUS_PARAM(images), + method_list_images, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_SIGNAL_WITH_NAMES("TransferNew", "uo", @@ -1350,18 +1733,6 @@ static bool manager_check_idle(void *userdata) { return hashmap_isempty(m->transfers); } -static int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.import1", - DEFAULT_EXIT_USEC, - manager_check_idle, - m); -} - static void manager_parse_env(Manager *m) { int r; @@ -1400,7 +1771,7 @@ static int run(int argc, char *argv[]) { umask(0022); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); r = manager_new(&m); if (r < 0) @@ -1412,7 +1783,17 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - r = manager_run(m); + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); + + r = bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.import1", + DEFAULT_EXIT_USEC, + manager_check_idle, + m); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); diff --git a/src/import/meson.build b/src/import/meson.build index 3f0acf8..184dd7b 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -1,5 +1,9 @@ # SPDX-License-Identifier: LGPL-2.1-or-later +if conf.get('ENABLE_IMPORTD') != 1 + subdir_done() +endif + systemd_importd_sources = files( 'importd.c', ) @@ -100,6 +104,12 @@ executables += [ 'link_with' : common_libs, 'dependencies' : common_deps, }, + executable_template + { + 'name' : 'importctl', + 'public' : true, + 'conditions' : ['ENABLE_IMPORTD'], + 'sources' : files('importctl.c'), + }, test_template + { 'sources' : files( 'test-qcow2.c', @@ -111,15 +121,13 @@ executables += [ }, ] -if conf.get('ENABLE_IMPORTD') == 1 - install_data('org.freedesktop.import1.conf', - install_dir : dbuspolicydir) - install_data('org.freedesktop.import1.service', - install_dir : dbussystemservicedir) - install_data('org.freedesktop.import1.policy', - install_dir : polkitpolicydir) +install_data('org.freedesktop.import1.conf', + install_dir : dbuspolicydir) +install_data('org.freedesktop.import1.service', + install_dir : dbussystemservicedir) +install_data('org.freedesktop.import1.policy', + install_dir : polkitpolicydir) - install_data('import-pubring.gpg', - install_dir : libexecdir) - # TODO: shouldn't this be in pkgdatadir? -endif +install_data('import-pubring.gpg', + install_dir : libexecdir) +# TODO: shouldn't this be in pkgdatadir? diff --git a/src/import/org.freedesktop.import1.conf b/src/import/org.freedesktop.import1.conf index d252ff6..f99ec56 100644 --- a/src/import/org.freedesktop.import1.conf +++ b/src/import/org.freedesktop.import1.conf @@ -42,6 +42,10 @@ send_interface="org.freedesktop.import1.Manager" send_member="ListTransfers"/> + + @@ -50,34 +54,66 @@ send_interface="org.freedesktop.import1.Manager" send_member="ImportTar"/> + + + + + + + + + + + + + + + + diff --git a/src/import/org.freedesktop.import1.policy b/src/import/org.freedesktop.import1.policy index 88e436d..45c11de 100644 --- a/src/import/org.freedesktop.import1.policy +++ b/src/import/org.freedesktop.import1.policy @@ -19,8 +19,8 @@ https://systemd.io - Import a VM or container image - Authentication is required to import a VM or container image + Import a disk image + Authentication is required to import an image auth_admin auth_admin @@ -29,8 +29,8 @@ - Export a VM or container image - Authentication is required to export a VM or container image + Export a disk image + Authentication is required to export disk image auth_admin auth_admin @@ -39,8 +39,18 @@ - Download a VM or container image - Authentication is required to download a VM or container image + Download a disk image + Authentication is required to download a disk image + + auth_admin + auth_admin + auth_admin_keep + + + + + Cancel transfer of a disk image + Authentication is required to cancel the ongoing transfer of a disk image auth_admin auth_admin diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 5e1ea20..9a2ced0 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -7,6 +7,7 @@ #include "capability-util.h" #include "copy.h" #include "dirent-util.h" +#include "discover-image.h" #include "escape.h" #include "fd-util.h" #include "hostname-util.h" @@ -37,11 +38,9 @@ int pull_find_old_etags( int r; assert(url); + assert(image_root); assert(etags); - if (!image_root) - image_root = "/var/lib/machines"; - _cleanup_free_ char *escaped_url = xescape(url, FILENAME_ESCAPE); if (!escaped_url) return -ENOMEM; @@ -128,11 +127,9 @@ int pull_make_path(const char *url, const char *etag, const char *image_root, co char *path; assert(url); + assert(image_root); assert(ret); - if (!image_root) - image_root = "/var/lib/machines"; - escaped_url = xescape(url, FILENAME_ESCAPE); if (!escaped_url) return -ENOMEM; @@ -550,7 +547,6 @@ int pull_verify(ImportVerify verify, log_debug("Main download is a checksum file, can't validate its checksum with itself, skipping."); verify_job = main_job; } else { - PullJob *j; assert(main_job->calc_checksum); assert(main_job->checksum); assert(checksum_job); @@ -560,7 +556,8 @@ int pull_verify(ImportVerify verify, return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Checksum is empty, cannot verify."); - FOREACH_POINTER(j, main_job, settings_job, roothash_job, roothash_signature_job, verity_job) { + PullJob *j; + FOREACH_ARGUMENT(j, main_job, settings_job, roothash_job, roothash_signature_job, verity_job) { r = verify_one(checksum_job, j); if (r < 0) return r; @@ -643,12 +640,12 @@ int pull_job_restart_with_sha256sum(PullJob *j, char **ret) { return 1; } -bool pull_validate_local(const char *name, PullFlags flags) { +bool pull_validate_local(const char *name, ImportFlags flags) { - if (FLAGS_SET(flags, PULL_DIRECT)) + if (FLAGS_SET(flags, IMPORT_DIRECT)) return path_is_valid(name); - return hostname_is_valid(name, 0); + return image_name_is_valid(name); } int pull_url_needs_checksum(const char *url) { diff --git a/src/import/pull-common.h b/src/import/pull-common.h index 475613a..82e2526 100644 --- a/src/import/pull-common.h +++ b/src/import/pull-common.h @@ -3,27 +3,10 @@ #include +#include "import-common.h" #include "import-util.h" #include "pull-job.h" -typedef enum PullFlags { - PULL_FORCE = 1 << 0, /* replace existing image */ - PULL_READ_ONLY = 1 << 1, /* make generated image read-only */ - PULL_SETTINGS = 1 << 2, /* download .nspawn settings file */ - PULL_ROOTHASH = 1 << 3, /* only for raw: download .roothash file for verity */ - PULL_ROOTHASH_SIGNATURE = 1 << 4, /* only for raw: download .roothash.p7s file for verity */ - PULL_VERITY = 1 << 5, /* only for raw: download .verity file for verity */ - PULL_BTRFS_SUBVOL = 1 << 6, /* tar: preferably create images as btrfs subvols */ - PULL_BTRFS_QUOTA = 1 << 7, /* tar: set up btrfs quota for new subvolume as child of parent subvolume */ - PULL_CONVERT_QCOW2 = 1 << 8, /* raw: if we detect a qcow2 image, unpack it */ - PULL_DIRECT = 1 << 9, /* download without rename games */ - PULL_SYNC = 1 << 10, /* fsync() right before we are done */ - - /* The supported flags for the tar and the raw pulling */ - PULL_FLAGS_MASK_TAR = PULL_FORCE|PULL_READ_ONLY|PULL_SETTINGS|PULL_BTRFS_SUBVOL|PULL_BTRFS_QUOTA|PULL_DIRECT|PULL_SYNC, - PULL_FLAGS_MASK_RAW = PULL_FORCE|PULL_READ_ONLY|PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY|PULL_CONVERT_QCOW2|PULL_DIRECT|PULL_SYNC, -} PullFlags; - int pull_find_old_etags(const char *url, const char *root, int dt, const char *prefix, const char *suffix, char ***etags); int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); @@ -44,6 +27,6 @@ int verification_style_from_url(const char *url, VerificationStyle *style); int pull_job_restart_with_sha256sum(PullJob *job, char **ret); -bool pull_validate_local(const char *name, PullFlags flags); +bool pull_validate_local(const char *name, ImportFlags flags); int pull_url_needs_checksum(const char *url); diff --git a/src/import/pull-job.c b/src/import/pull-job.c index bed7e64..8482551 100644 --- a/src/import/pull-job.c +++ b/src/import/pull-job.c @@ -187,7 +187,7 @@ void pull_job_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) { } } - r = log_error_errno( + r = log_notice_errno( status == 404 ? SYNTHETIC_ERRNO(ENOMEDIUM) : SYNTHETIC_ERRNO(EIO), /* Make the most common error recognizable */ "HTTP request to %s failed with code %li.", j->url, status); goto finish; @@ -431,7 +431,9 @@ static int pull_job_open_disk(PullJob *j) { return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize hash context."); #else - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return log_error_errno(r, "Failed to load libgcrypt: %m"); if (gcry_md_open(&j->checksum_ctx, GCRY_MD_SHA256, 0) != 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 66c3f65..f3e6b3a 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -42,7 +42,7 @@ struct RawPull { sd_event *event; CurlGlue *glue; - PullFlags flags; + ImportFlags flags; ImportVerify verify; char *image_root; @@ -60,7 +60,7 @@ struct RawPull { void *userdata; char *local; /* In PULL_DIRECT mode the path we are supposed to place things in, otherwise the - * machine name of the final copy we make */ + * image name of the final copy we make */ char *final_path; char *temp_path; @@ -127,8 +127,9 @@ int raw_pull_new( int r; assert(ret); + assert(image_root); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; @@ -244,9 +245,9 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) { assert(i); assert(i->raw_job); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); - if (!FLAGS_SET(i->flags, PULL_CONVERT_QCOW2)) + if (!FLAGS_SET(i->flags, IMPORT_CONVERT_QCOW2)) return 0; assert(i->final_path); @@ -310,7 +311,7 @@ static int raw_pull_copy_auxiliary_file( const char *suffix, char **path /* input + output (!) */) { - const char *local; + _cleanup_free_ char *local = NULL; int r; assert(i); @@ -321,21 +322,29 @@ static int raw_pull_copy_auxiliary_file( if (r < 0) return r; - local = strjoina(i->image_root, "/", i->local, suffix); + local = strjoin(i->image_root, "/", i->local, suffix); + if (!local) + return log_oom(); - r = copy_file_atomic( - *path, - local, - 0644, - COPY_REFLINK | - (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) | - (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0)); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) + r = copy_file_atomic( + *path, + local, + 0644, + COPY_REFLINK | + (FLAGS_SET(i->flags, IMPORT_FORCE) ? COPY_REPLACE : 0) | + (FLAGS_SET(i->flags, IMPORT_SYNC) ? COPY_FSYNC_FULL : 0)); + else + r = install_file(AT_FDCWD, *path, + AT_FDCWD, local, + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r == -EEXIST) log_warning_errno(r, "File %s already exists, not replacing.", local); else if (r == -ENOENT) log_debug_errno(r, "Skipping creation of auxiliary file, since none was found."); else if (r < 0) - log_warning_errno(r, "Failed to copy file %s, ignoring: %m", local); + log_warning_errno(r, "Failed to install file %s, ignoring: %m", local); else log_info("Created new file %s.", local); @@ -344,14 +353,12 @@ static int raw_pull_copy_auxiliary_file( static int raw_pull_make_local_copy(RawPull *i) { _cleanup_(unlink_and_freep) char *tp = NULL; - _cleanup_free_ char *f = NULL; - _cleanup_close_ int dfd = -EBADF; - const char *p; + _cleanup_free_ char *p = NULL; int r; assert(i); assert(i->raw_job); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); if (!i->local) return 0; @@ -374,62 +381,73 @@ static int raw_pull_make_local_copy(RawPull *i) { return log_error_errno(errno, "Failed to seek to beginning of vendor image: %m"); } - p = strjoina(i->image_root, "/", i->local, ".raw"); - - r = tempfn_random(p, NULL, &f); - if (r < 0) + p = strjoin(i->image_root, "/", i->local, ".raw"); + if (!p) return log_oom(); - dfd = open(f, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (dfd < 0) - return log_error_errno(errno, "Failed to create writable copy of image: %m"); + const char *source; + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) { + _cleanup_close_ int dfd = -EBADF; + _cleanup_free_ char *f = NULL; - tp = TAKE_PTR(f); + r = tempfn_random(p, NULL, &f); + if (r < 0) + return log_oom(); - /* Turn off COW writing. This should greatly improve performance on COW file systems like btrfs, - * since it reduces fragmentation caused by not allowing in-place writes. */ - (void) import_set_nocow_and_log(dfd, tp); + dfd = open(f, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (dfd < 0) + return log_error_errno(errno, "Failed to create writable copy of image: %m"); - r = copy_bytes(i->raw_job->disk_fd, dfd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to make writable copy of image: %m"); + tp = TAKE_PTR(f); - (void) copy_times(i->raw_job->disk_fd, dfd, COPY_CRTIME); - (void) copy_xattr(i->raw_job->disk_fd, NULL, dfd, NULL, 0); + /* Turn off COW writing. This should greatly improve performance on COW file systems like btrfs, + * since it reduces fragmentation caused by not allowing in-place writes. */ + (void) import_set_nocow_and_log(dfd, tp); - dfd = safe_close(dfd); + r = copy_bytes(i->raw_job->disk_fd, dfd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to make writable copy of image: %m"); + + (void) copy_times(i->raw_job->disk_fd, dfd, COPY_CRTIME); + (void) copy_xattr(i->raw_job->disk_fd, NULL, dfd, NULL, 0); + + dfd = safe_close(dfd); + + source = tp; + } else + source = i->final_path; - r = install_file(AT_FDCWD, tp, + r = install_file(AT_FDCWD, source, AT_FDCWD, p, - (i->flags & PULL_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & PULL_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) - return log_error_errno(errno, "Failed to move local image into place '%s': %m", p); + return log_error_errno(r, "Failed to move local image into place '%s': %m", p); tp = mfree(tp); log_info("Created new local image '%s'.", i->local); - if (FLAGS_SET(i->flags, PULL_SETTINGS)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_SETTINGS)) { r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_ROOTHASH)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_ROOTHASH)) { r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_ROOTHASH_SIGNATURE)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_ROOTHASH_SIGNATURE)) { r = raw_pull_copy_auxiliary_file(i, ".roothash.p7s", &i->roothash_signature_path); if (r < 0) return r; } - if (FLAGS_SET(i->flags, PULL_VERITY)) { + if (FLAGS_SET(i->flags, IMPORT_PULL_VERITY)) { r = raw_pull_copy_auxiliary_file(i, ".verity", &i->verity_path); if (r < 0) return r; @@ -485,7 +503,7 @@ static int raw_pull_rename_auxiliary_file( AT_FDCWD, *temp_path, AT_FDCWD, *path, INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) return log_error_errno(r, "Failed to move '%s' into place: %m", *path); @@ -495,7 +513,6 @@ static int raw_pull_rename_auxiliary_file( static void raw_pull_job_on_finished(PullJob *j) { RawPull *i; - PullJob *jj; int r; assert(j); @@ -568,8 +585,9 @@ static void raw_pull_job_on_finished(PullJob *j) { } } + PullJob *jj; /* Let's close these auxiliary files now, we don't need access to them anymore. */ - FOREACH_POINTER(jj, i->settings_job, i->roothash_job, i->roothash_signature_job, i->verity_job) + FOREACH_ARGUMENT(jj, i->settings_job, i->roothash_job, i->roothash_signature_job, i->verity_job) pull_job_close_disk_fd(jj); if (!i->raw_job->etag_exists) { @@ -588,7 +606,7 @@ static void raw_pull_job_on_finished(PullJob *j) { goto finish; } - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { assert(!i->settings_job); assert(!i->roothash_job); assert(!i->roothash_signature_job); @@ -599,8 +617,8 @@ static void raw_pull_job_on_finished(PullJob *j) { if (i->local) { r = install_file(AT_FDCWD, i->local, AT_FDCWD, NULL, - ((i->flags & PULL_READ_ONLY) && i->offset == UINT64_MAX ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + ((i->flags & IMPORT_READ_ONLY) && i->offset == UINT64_MAX ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize raw file to '%s': %m", i->local); goto finish; @@ -627,8 +645,8 @@ static void raw_pull_job_on_finished(PullJob *j) { r = install_file(AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to move raw file to '%s': %m", i->final_path); goto finish; @@ -694,7 +712,7 @@ static int raw_pull_job_on_open_disk_generic( assert(extra); assert(temp_path); - assert(!FLAGS_SET(i->flags, PULL_DIRECT)); + assert(!FLAGS_SET(i->flags, IMPORT_DIRECT)); if (!*temp_path) { r = tempfn_random_child(i->image_root, extra, temp_path); @@ -722,7 +740,7 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) { assert(i->raw_job == j); assert(j->disk_fd < 0); - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { if (!i->local) { /* If no local name specified, the pull job will write its data to stdout */ j->disk_fd = STDOUT_FILENO; @@ -816,11 +834,10 @@ int raw_pull_start( const char *local, uint64_t offset, uint64_t size_max, - PullFlags flags, + ImportFlags flags, ImportVerify verify, const char *checksum) { - PullJob *j; int r; assert(i); @@ -828,10 +845,10 @@ int raw_pull_start( assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX); assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0); assert((verify < 0) || !checksum); - assert(!(flags & ~PULL_FLAGS_MASK_RAW)); - assert(offset == UINT64_MAX || FLAGS_SET(flags, PULL_DIRECT)); - assert(!(flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) || !(flags & PULL_DIRECT)); - assert(!(flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) || !checksum); + assert(!(flags & ~IMPORT_PULL_FLAGS_MASK_RAW)); + assert(offset == UINT64_MAX || FLAGS_SET(flags, IMPORT_DIRECT)); + assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !(flags & IMPORT_DIRECT)); + assert(!(flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) || !checksum); if (!http_url_is_valid(url) && !file_url_is_valid(url)) return -EINVAL; @@ -881,7 +898,7 @@ int raw_pull_start( if (offset != UINT64_MAX) i->raw_job->offset = i->offset = offset; - if (!FLAGS_SET(flags, PULL_DIRECT)) { + if (!FLAGS_SET(flags, IMPORT_DIRECT)) { r = pull_find_old_etags(url, i->image_root, DT_REG, ".raw-", ".raw", &i->raw_job->old_etags); if (r < 0) return r; @@ -899,7 +916,7 @@ int raw_pull_start( if (r < 0) return r; - if (FLAGS_SET(flags, PULL_SETTINGS)) { + if (FLAGS_SET(flags, IMPORT_PULL_SETTINGS)) { r = pull_make_auxiliary_job( &i->settings_job, url, @@ -914,7 +931,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_ROOTHASH)) { + if (FLAGS_SET(flags, IMPORT_PULL_ROOTHASH)) { r = pull_make_auxiliary_job( &i->roothash_job, url, @@ -929,7 +946,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_ROOTHASH_SIGNATURE)) { + if (FLAGS_SET(flags, IMPORT_PULL_ROOTHASH_SIGNATURE)) { r = pull_make_auxiliary_job( &i->roothash_signature_job, url, @@ -944,7 +961,7 @@ int raw_pull_start( return r; } - if (FLAGS_SET(flags, PULL_VERITY)) { + if (FLAGS_SET(flags, IMPORT_PULL_VERITY)) { r = pull_make_auxiliary_job( &i->verity_job, url, @@ -959,20 +976,21 @@ int raw_pull_start( return r; } - FOREACH_POINTER(j, - i->raw_job, - i->checksum_job, - i->signature_job, - i->settings_job, - i->roothash_job, - i->roothash_signature_job, - i->verity_job) { + PullJob *j; + FOREACH_ARGUMENT(j, + i->raw_job, + i->checksum_job, + i->signature_job, + i->settings_job, + i->roothash_job, + i->roothash_signature_job, + i->verity_job) { if (!j) continue; j->on_progress = raw_pull_job_on_progress; - j->sync = FLAGS_SET(flags, PULL_SYNC); + j->sync = FLAGS_SET(flags, IMPORT_SYNC); r = pull_job_begin(j); if (r < 0) diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index b39e4e2..e50ba32 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -16,4 +16,4 @@ RawPull* raw_pull_unref(RawPull *pull); DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); -int raw_pull_start(RawPull *pull, const char *url, const char *local, uint64_t offset, uint64_t size_max, PullFlags flags, ImportVerify verify, const char *checksum); +int raw_pull_start(RawPull *pull, const char *url, const char *local, uint64_t offset, uint64_t size_max, ImportFlags flags, ImportVerify verify, const char *checksum); diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index c32fc29..7fc71fe 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -41,7 +41,7 @@ struct TarPull { sd_event *event; CurlGlue *glue; - PullFlags flags; + ImportFlags flags; ImportVerify verify; char *image_root; @@ -106,9 +106,10 @@ int tar_pull_new( _cleanup_free_ char *root = NULL; int r; + assert(image_root); assert(ret); - root = strdup(image_root ?: "/var/lib/machines"); + root = strdup(image_root); if (!root) return -ENOMEM; @@ -219,7 +220,8 @@ static int tar_pull_determine_path( static int tar_pull_make_local_copy(TarPull *i) { _cleanup_(rm_rf_subvolume_and_freep) char *t = NULL; - const char *p; + _cleanup_free_ char *p = NULL; + const char *source; int r; assert(i); @@ -230,30 +232,37 @@ static int tar_pull_make_local_copy(TarPull *i) { assert(i->final_path); - p = prefix_roota(i->image_root, i->local); + p = path_join(i->image_root, i->local); + if (!p) + return log_oom(); - r = tempfn_random(p, NULL, &t); - if (r < 0) - return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); - - if (i->flags & PULL_BTRFS_SUBVOL) - r = btrfs_subvol_snapshot_at( - AT_FDCWD, i->final_path, - AT_FDCWD, t, - (i->flags & PULL_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| - BTRFS_SNAPSHOT_FALLBACK_COPY| - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| - BTRFS_SNAPSHOT_RECURSIVE); - else - r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) { + r = tempfn_random(p, NULL, &t); + if (r < 0) + return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); + + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_snapshot_at( + AT_FDCWD, i->final_path, + AT_FDCWD, t, + (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| + BTRFS_SNAPSHOT_FALLBACK_COPY| + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| + BTRFS_SNAPSHOT_RECURSIVE); + else + r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to create local image: %m"); - r = install_file(AT_FDCWD, t, + source = t; + } else + source = i->final_path; + + r = install_file(AT_FDCWD, source, AT_FDCWD, p, - (i->flags & PULL_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & PULL_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) return log_error_errno(r, "Failed to install local image '%s': %m", p); @@ -261,29 +270,37 @@ static int tar_pull_make_local_copy(TarPull *i) { log_info("Created new local image '%s'.", i->local); - if (FLAGS_SET(i->flags, PULL_SETTINGS)) { - const char *local_settings; + if (FLAGS_SET(i->flags, IMPORT_PULL_SETTINGS)) { + _cleanup_free_ char *local_settings = NULL; assert(i->settings_job); r = tar_pull_determine_path(i, ".nspawn", &i->settings_path); if (r < 0) return r; - local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); + local_settings = strjoin(i->image_root, "/", i->local, ".nspawn"); + if (!local_settings) + return log_oom(); - r = copy_file_atomic( - i->settings_path, - local_settings, - 0664, - COPY_REFLINK | - (FLAGS_SET(i->flags, PULL_FORCE) ? COPY_REPLACE : 0) | - (FLAGS_SET(i->flags, PULL_SYNC) ? COPY_FSYNC_FULL : 0)); + if (FLAGS_SET(i->flags, IMPORT_PULL_KEEP_DOWNLOAD)) + r = copy_file_atomic( + i->settings_path, + local_settings, + 0664, + COPY_REFLINK | + (FLAGS_SET(i->flags, IMPORT_FORCE) ? COPY_REPLACE : 0) | + (FLAGS_SET(i->flags, IMPORT_SYNC) ? COPY_FSYNC_FULL : 0)); + else + r = install_file(AT_FDCWD, i->settings_path, + AT_FDCWD, local_settings, + (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r == -EEXIST) log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); else if (r == -ENOENT) log_debug_errno(r, "Skipping creation of settings file, since none was found."); else if (r < 0) - log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + log_warning_errno(r, "Failed to install settings files %s, ignoring: %m", local_settings); else log_info("Created new settings file %s.", local_settings); } @@ -392,7 +409,7 @@ static void tar_pull_job_on_finished(PullJob *j) { goto finish; } - if (i->flags & PULL_DIRECT) { + if (i->flags & IMPORT_DIRECT) { assert(!i->settings_job); assert(i->local); assert(!i->temp_path); @@ -406,8 +423,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->local, AT_FDCWD, NULL, - (i->flags & PULL_READ_ONLY) ? INSTALL_READ_ONLY : 0 | - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY) ? INSTALL_READ_ONLY : 0 | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize '%s': %m", i->local); goto finish; @@ -432,8 +449,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); if (r < 0) { log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path); goto finish; @@ -460,7 +477,7 @@ static void tar_pull_job_on_finished(PullJob *j) { AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path, INSTALL_READ_ONLY| - (i->flags & PULL_SYNC ? INSTALL_FSYNC_FULL : 0)); + (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path); goto finish; @@ -498,7 +515,7 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { assert(i->tar_job == j); assert(i->tar_pid <= 0); - if (i->flags & PULL_DIRECT) + if (i->flags & IMPORT_DIRECT) where = i->local; else { if (!i->temp_path) { @@ -512,20 +529,20 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { (void) mkdir_parents_label(where, 0700); - if (FLAGS_SET(i->flags, PULL_DIRECT|PULL_FORCE)) + if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & PULL_BTRFS_SUBVOL) + if (i->flags & IMPORT_BTRFS_SUBVOL) r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); else r = RET_NERRNO(mkdir(where, 0755)); - if (r == -EEXIST && (i->flags & PULL_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, * because in that case our temporary path collided */ r = 0; if (r < 0) return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); - if (r > 0 && (i->flags & PULL_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & PULL_DIRECT)) + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) (void) import_assign_pool_quota_and_warn(i->image_root); (void) import_assign_pool_quota_and_warn(where); } @@ -577,20 +594,19 @@ int tar_pull_start( TarPull *i, const char *url, const char *local, - PullFlags flags, + ImportFlags flags, ImportVerify verify, const char *checksum) { - PullJob *j; int r; assert(i); assert(verify == _IMPORT_VERIFY_INVALID || verify < _IMPORT_VERIFY_MAX); assert(verify == _IMPORT_VERIFY_INVALID || verify >= 0); assert((verify < 0) || !checksum); - assert(!(flags & ~PULL_FLAGS_MASK_TAR)); - assert(!(flags & PULL_SETTINGS) || !(flags & PULL_DIRECT)); - assert(!(flags & PULL_SETTINGS) || !checksum); + assert(!(flags & ~IMPORT_PULL_FLAGS_MASK_TAR)); + assert(!(flags & IMPORT_PULL_SETTINGS) || !(flags & IMPORT_DIRECT)); + assert(!(flags & IMPORT_PULL_SETTINGS) || !checksum); if (!http_url_is_valid(url) && !file_url_is_valid(url)) return -EINVAL; @@ -621,7 +637,7 @@ int tar_pull_start( i->tar_job->on_open_disk = tar_pull_job_on_open_disk_tar; i->tar_job->calc_checksum = checksum || IN_SET(verify, IMPORT_VERIFY_CHECKSUM, IMPORT_VERIFY_SIGNATURE); - if (!FLAGS_SET(flags, PULL_DIRECT)) { + if (!FLAGS_SET(flags, IMPORT_DIRECT)) { r = pull_find_old_etags(url, i->image_root, DT_DIR, ".tar-", NULL, &i->tar_job->old_etags); if (r < 0) return r; @@ -641,7 +657,7 @@ int tar_pull_start( return r; /* Set up download job for the settings file (.nspawn) */ - if (FLAGS_SET(flags, PULL_SETTINGS)) { + if (FLAGS_SET(flags, IMPORT_PULL_SETTINGS)) { r = pull_make_auxiliary_job( &i->settings_job, url, @@ -656,17 +672,18 @@ int tar_pull_start( return r; } - FOREACH_POINTER(j, - i->tar_job, - i->checksum_job, - i->signature_job, - i->settings_job) { + PullJob *j; + FOREACH_ARGUMENT(j, + i->tar_job, + i->checksum_job, + i->signature_job, + i->settings_job) { if (!j) continue; j->on_progress = tar_pull_job_on_progress; - j->sync = FLAGS_SET(flags, PULL_SYNC); + j->sync = FLAGS_SET(flags, IMPORT_SYNC); r = pull_job_begin(j); if (r < 0) diff --git a/src/import/pull-tar.h b/src/import/pull-tar.h index e54c01c..1a5b740 100644 --- a/src/import/pull-tar.h +++ b/src/import/pull-tar.h @@ -16,4 +16,4 @@ TarPull* tar_pull_unref(TarPull *pull); DEFINE_TRIVIAL_CLEANUP_FUNC(TarPull*, tar_pull_unref); -int tar_pull_start(TarPull *pull, const char *url, const char *local, PullFlags flags, ImportVerify verify, const char *checksum); +int tar_pull_start(TarPull *pull, const char *url, const char *local, ImportFlags flags, ImportVerify verify, const char *checksum); diff --git a/src/import/pull.c b/src/import/pull.c index 38821b5..7c838a5 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -26,11 +26,12 @@ #include "verbs.h" #include "web-util.h" -static const char *arg_image_root = "/var/lib/machines"; +static const char *arg_image_root = NULL; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static PullFlags arg_pull_flags = PULL_SETTINGS | PULL_ROOTHASH | PULL_ROOTHASH_SIGNATURE | PULL_VERITY | PULL_BTRFS_SUBVOL | PULL_BTRFS_QUOTA | PULL_CONVERT_QCOW2 | PULL_SYNC; +static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; static char *arg_checksum = NULL; +static ImageClass arg_class = IMAGE_MACHINE; STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep); @@ -38,7 +39,7 @@ static int normalize_local(const char *local, const char *url, char **ret) { _cleanup_free_ char *ll = NULL; int r; - if (arg_pull_flags & PULL_DIRECT) { + if (arg_import_flags & IMPORT_DIRECT) { if (!local) log_debug("Writing downloaded data to STDOUT."); @@ -58,13 +59,13 @@ static int normalize_local(const char *local, const char *url, char **ret) { } else if (local) { - if (!hostname_is_valid(local, 0)) + if (!image_name_is_valid(local)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Local image name '%s' is not valid.", local); - if (!FLAGS_SET(arg_pull_flags, PULL_FORCE)) { - r = image_find(IMAGE_MACHINE, local, NULL, NULL); + if (!FLAGS_SET(arg_import_flags, IMPORT_FORCE)) { + r = image_find(arg_class, local, NULL, NULL); if (r < 0) { if (r != -ENOENT) return log_error_errno(r, "Failed to check whether image '%s' exists: %m", local); @@ -89,6 +90,12 @@ static int normalize_local(const char *local, const char *url, char **ret) { } else log_info("Pulling '%s'.", url); + if (!FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) + log_info("Operating on image directory '%s'.", arg_image_root); + + if (!FLAGS_SET(arg_import_flags, IMPORT_SYNC)) + log_info("File system synchronization on completion is off."); + *ret = TAKE_PTR(ll); return 0; } @@ -130,7 +137,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { local = ll; } - if (!local && FLAGS_SET(arg_pull_flags, PULL_DIRECT)) + if (!local && FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Pulling tar images to STDOUT is not supported."); r = normalize_local(local, url, &normalized); @@ -141,9 +148,6 @@ static int pull_tar(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_pull_flags, PULL_SYNC)) - log_info("File system synchronization on completion is off."); - r = tar_pull_new(&pull, event, arg_image_root, on_tar_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate puller: %m"); @@ -152,7 +156,7 @@ static int pull_tar(int argc, char *argv[], void *userdata) { pull, url, normalized, - arg_pull_flags & PULL_FLAGS_MASK_TAR, + arg_import_flags & IMPORT_PULL_FLAGS_MASK_TAR, arg_verify, arg_checksum); if (r < 0) @@ -211,9 +215,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_pull_flags, PULL_SYNC)) - log_info("File system synchronization on completion is off."); - r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); + r = raw_pull_new(&pull, event, arg_image_root, on_raw_finished, event); if (r < 0) return log_error_errno(r, "Failed to allocate puller: %m"); @@ -223,7 +225,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { normalized, arg_offset, arg_size_max, - arg_pull_flags & PULL_FLAGS_MASK_RAW, + arg_import_flags & IMPORT_PULL_FLAGS_MASK_RAW, arg_verify, arg_checksum); if (r < 0) @@ -240,7 +242,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { static int help(int argc, char *argv[], void *userdata) { printf("%1$s [OPTIONS...] {COMMAND} ...\n" - "\n%4$sDownload container or virtual machine images.%5$s\n" + "\n%4$sDownload disk images.%5$s\n" "\n%2$sCommands:%3$s\n" " tar URL [NAME] Download a TAR image\n" " raw URL [NAME] Download a RAW image\n" @@ -255,7 +257,7 @@ static int help(int argc, char *argv[], void *userdata) { " --roothash-signature=BOOL\n" " Download root hash signature file with image\n" " --verity=BOOL Download verity file with image\n" - " --image-root=PATH Image root directory\n\n" + " --image-root=PATH Image root directory\n" " --read-only Create a read-only image\n" " --direct Download directly to specified file\n" " --btrfs-subvol=BOOL Controls whether to create a btrfs subvolume\n" @@ -266,7 +268,11 @@ static int help(int argc, char *argv[], void *userdata) { " regular disk images\n" " --sync=BOOL Controls whether to sync() before completing\n" " --offset=BYTES Offset to seek to in destination\n" - " --size-max=BYTES Maximum number of bytes to write to destination\n", + " --size-max=BYTES Maximum number of bytes to write to destination\n" + " --class=CLASS Select image class (machine, sysext, confext,\n" + " portable)\n" + " --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n" + " around\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -295,6 +301,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_SYNC, ARG_OFFSET, ARG_SIZE_MAX, + ARG_CLASS, + ARG_KEEP_DOWNLOAD, }; static const struct option options[] = { @@ -315,10 +323,13 @@ static int parse_argv(int argc, char *argv[]) { { "sync", required_argument, NULL, ARG_SYNC }, { "offset", required_argument, NULL, ARG_OFFSET }, { "size-max", required_argument, NULL, ARG_SIZE_MAX }, + { "class", required_argument, NULL, ARG_CLASS }, + { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, {} }; int c, r; + bool auto_settings = true, auto_keep_download = true; assert(argc >= 0); assert(argv); @@ -334,7 +345,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); case ARG_FORCE: - arg_pull_flags |= PULL_FORCE; + arg_import_flags |= IMPORT_FORCE; break; case ARG_IMAGE_ROOT: @@ -353,7 +364,7 @@ static int parse_argv(int argc, char *argv[]) { /* If this is not a valid verification mode, maybe it's a literally specified * SHA256 hash? We can handle that too... */ - r = unhexmem(optarg, (size_t) -1, &h, &n); + r = unhexmem(optarg, &h, &n); if (r < 0 || n == 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid verification setting: %s", optarg); @@ -366,7 +377,7 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); free_and_replace(arg_checksum, hh); - arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY); + arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); arg_verify = _IMPORT_VERIFY_INVALID; } else arg_verify = v; @@ -379,7 +390,8 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_SETTINGS, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_SETTINGS, r); + auto_settings = false; break; case ARG_ROOTHASH: @@ -387,11 +399,11 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_ROOTHASH, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH, r); /* If we were asked to turn off the root hash, implicitly also turn off the root hash signature */ if (!r) - SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, false); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, false); break; case ARG_ROOTHASH_SIGNATURE: @@ -399,7 +411,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_ROOTHASH_SIGNATURE, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_ROOTHASH_SIGNATURE, r); break; case ARG_VERITY: @@ -407,16 +419,16 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_VERITY, r); + SET_FLAG(arg_import_flags, IMPORT_PULL_VERITY, r); break; case ARG_READ_ONLY: - arg_pull_flags |= PULL_READ_ONLY; + arg_import_flags |= IMPORT_READ_ONLY; break; case ARG_DIRECT: - arg_pull_flags |= PULL_DIRECT; - arg_pull_flags &= ~(PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY); + arg_import_flags |= IMPORT_DIRECT; + arg_import_flags &= ~(IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY); break; case ARG_BTRFS_SUBVOL: @@ -424,7 +436,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); break; case ARG_BTRFS_QUOTA: @@ -432,7 +444,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); break; case ARG_CONVERT_QCOW2: @@ -440,7 +452,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_CONVERT_QCOW2, r); + SET_FLAG(arg_import_flags, IMPORT_CONVERT_QCOW2, r); break; case ARG_SYNC: @@ -448,7 +460,7 @@ static int parse_argv(int argc, char *argv[]) { if (r < 0) return r; - SET_FLAG(arg_pull_flags, PULL_SYNC, r); + SET_FLAG(arg_import_flags, IMPORT_SYNC, r); break; case ARG_OFFSET: { @@ -477,6 +489,22 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_CLASS: + arg_class = image_class_from_string(optarg); + if (arg_class < 0) + return log_error_errno(arg_class, "Failed to parse --class= argument: %s", optarg); + + break; + + case ARG_KEEP_DOWNLOAD: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --keep-download= argument: %s", optarg); + + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, r); + auto_keep_download = false; + break; + case '?': return -EINVAL; @@ -490,12 +518,24 @@ static int parse_argv(int argc, char *argv[]) { !FILE_SIZE_VALID(arg_offset + arg_size_max))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset und maximum size out of range."); - if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_pull_flags, PULL_DIRECT)) + if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); - if (arg_checksum && (arg_pull_flags & (PULL_SETTINGS|PULL_ROOTHASH|PULL_ROOTHASH_SIGNATURE|PULL_VERITY)) != 0) + if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded."); + if (!arg_image_root) + arg_image_root = image_root_to_string(arg_class); + + /* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */ + if (auto_settings && arg_class != IMAGE_MACHINE) + arg_import_flags &= ~IMPORT_PULL_SETTINGS; + + /* Keep the original pristine downloaded file as a copy only when dealing with machine images, + * because unlike sysext/confext/portable they are typically modified during runtime. */ + if (auto_keep_download) + SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE); + return 1; } @@ -507,19 +547,19 @@ static void parse_env(void) { r = getenv_bool("SYSTEMD_IMPORT_BTRFS_SUBVOL"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_BTRFS_SUBVOL, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_SUBVOL, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_SUBVOL: %m"); r = getenv_bool("SYSTEMD_IMPORT_BTRFS_QUOTA"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_BTRFS_QUOTA, r); + SET_FLAG(arg_import_flags, IMPORT_BTRFS_QUOTA, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_BTRFS_QUOTA: %m"); r = getenv_bool("SYSTEMD_IMPORT_SYNC"); if (r >= 0) - SET_FLAG(arg_pull_flags, PULL_SYNC, r); + SET_FLAG(arg_import_flags, IMPORT_SYNC, r); else if (r != -ENXIO) log_warning_errno(r, "Failed to parse $SYSTEMD_IMPORT_SYNC: %m"); } @@ -539,8 +579,7 @@ static int run(int argc, char *argv[]) { int r; setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); + log_setup(); parse_env(); diff --git a/src/import/qcow2-util.c b/src/import/qcow2-util.c index c70656b..fc34f30 100644 --- a/src/import/qcow2-util.c +++ b/src/import/qcow2-util.c @@ -273,7 +273,7 @@ int qcow2_convert(int qcow2_fd, int raw_fd) { if ((uint64_t) l != sz) return -EIO; - for (i = 0; i < HEADER_L1_SIZE(&header); i ++) { + for (i = 0; i < HEADER_L1_SIZE(&header); i++) { uint64_t l2_begin, j; r = normalize_offset(&header, l1_table[i], &l2_begin, NULL, NULL); diff --git a/src/journal-remote/fuzz-journal-remote.c b/src/journal-remote/fuzz-journal-remote.c index 557100b..774389d 100644 --- a/src/journal-remote/fuzz-journal-remote.c +++ b/src/journal-remote/fuzz-journal-remote.c @@ -67,7 +67,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { /* Out */ - r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), 0); + r = sd_journal_open_files(&j, (const char**) STRV_MAKE(name), SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) { log_error_errno(r, "sd_journal_open_files([\"%s\"]) failed: %m", name); assert_se(IN_SET(r, -ENOMEM, -EMFILE, -ENFILE, -ENODATA)); diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index aaa52c0..dd91f22 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -22,6 +22,8 @@ #include "fileio.h" #include "glob-util.h" #include "hostname-util.h" +#include "journal-internal.h" +#include "journal-remote.h" #include "log.h" #include "logs-show.h" #include "main-func.h" @@ -32,6 +34,7 @@ #include "pretty-print.h" #include "sigbus.h" #include "signal-util.h" +#include "time-util.h" #include "tmpfile-util.h" #define JOURNAL_WAIT_TIMEOUT (10*USEC_PER_SEC) @@ -55,9 +58,10 @@ typedef struct RequestMeta { OutputMode mode; char *cursor; + usec_t since, until; int64_t n_skip; uint64_t n_entries; - bool n_entries_set; + bool n_entries_set, since_set, until_set; FILE *tmp; uint64_t delta, size; @@ -212,6 +216,17 @@ static ssize_t request_reader_entries( return MHD_CONTENT_READER_END_OF_STREAM; } + if (m->until_set) { + usec_t usec; + + r = sd_journal_get_realtime_usec(m->journal, &usec); + if (r < 0) { + log_error_errno(r, "Failed to determine timestamp: %m"); + return MHD_CONTENT_READER_END_WITH_ERROR; + } + if (usec > m->until) + return MHD_CONTENT_READER_END_OF_STREAM; + } pos -= m->size; m->delta += m->size; @@ -293,60 +308,59 @@ static int request_parse_accept( return 0; } -static int request_parse_range( +static int request_parse_range_skip_and_n_entries( RequestMeta *m, - struct MHD_Connection *connection) { + const char *colon) { - const char *range, *colon, *colon2; + const char *p, *colon2; int r; - assert(m); - assert(connection); + colon2 = strchr(colon + 1, ':'); + if (colon2) { + _cleanup_free_ char *t = NULL; - range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); - if (!range) - return 0; + t = strndup(colon + 1, colon2 - colon - 1); + if (!t) + return -ENOMEM; - if (!startswith(range, "entries=")) - return 0; + r = safe_atoi64(t, &m->n_skip); + if (r < 0) + return r; + } - range += 8; - range += strspn(range, WHITESPACE); + p = (colon2 ?: colon) + 1; + r = safe_atou64(p, &m->n_entries); + if (r < 0) + return r; - colon = strchr(range, ':'); - if (!colon) - m->cursor = strdup(range); - else { - const char *p; + if (m->n_entries <= 0) + return -EINVAL; - colon2 = strchr(colon + 1, ':'); - if (colon2) { - _cleanup_free_ char *t = NULL; + m->n_entries_set = true; - t = strndup(colon + 1, colon2 - colon - 1); - if (!t) - return -ENOMEM; + return 0; +} - r = safe_atoi64(t, &m->n_skip); - if (r < 0) - return r; - } +static int request_parse_range_entries( + RequestMeta *m, + const char *entries_request) { - p = (colon2 ?: colon) + 1; - if (*p) { - r = safe_atou64(p, &m->n_entries); - if (r < 0) - return r; + const char *colon; + int r; - if (m->n_entries <= 0) - return -EINVAL; + assert(m); + assert(entries_request); - m->n_entries_set = true; - } + colon = strchr(entries_request, ':'); + if (!colon) + m->cursor = strdup(entries_request); + else { + r = request_parse_range_skip_and_n_entries(m, colon); + if (r < 0) + return r; - m->cursor = strndup(range, colon - range); + m->cursor = strndup(entries_request, colon - entries_request); } - if (!m->cursor) return -ENOMEM; @@ -357,6 +371,93 @@ static int request_parse_range( return 0; } +static int request_parse_range_time( + RequestMeta *m, + const char *time_request) { + + _cleanup_free_ char *until = NULL; + const char *colon; + int r; + + assert(m); + assert(time_request); + + colon = strchr(time_request, ':'); + if (!colon) + return -EINVAL; + + if (colon - time_request > 0) { + _cleanup_free_ char *t = NULL; + + t = strndup(time_request, colon - time_request); + if (!t) + return -ENOMEM; + + r = parse_sec(t, &m->since); + if (r < 0) + return r; + + m->since_set = true; + } + + time_request = colon + 1; + colon = strchr(time_request, ':'); + if (!colon) + until = strdup(time_request); + else { + r = request_parse_range_skip_and_n_entries(m, colon); + if (r < 0) + return r; + + until = strndup(time_request, colon - time_request); + } + if (!until) + return -ENOMEM; + + if (!isempty(until)) { + r = parse_sec(until, &m->until); + if (r < 0) + return r; + + m->until_set = true; + if (m->until < m->since) + return -EINVAL; + } + + return 0; +} + +static int request_parse_range( + RequestMeta *m, + struct MHD_Connection *connection) { + + const char *range, *range_after_eq; + + assert(m); + assert(connection); + + range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); + if (!range) + return 0; + + /* Safety upper bound to make Coverity happy. Apache2 has a default limit of 8KB: + * https://httpd.apache.org/docs/2.2/mod/core.html#limitrequestfieldsize */ + if (strlen(range) > JOURNAL_SERVER_MEMORY_MAX) + return -EINVAL; + + m->n_skip = 0; + + range_after_eq = startswith(range, "entries="); + if (range_after_eq) + return request_parse_range_entries(m, skip_leading_chars(range_after_eq, /* bad = */ NULL)); + + range_after_eq = startswith(range, "realtime="); + if (range_after_eq) + return request_parse_range_time(m, skip_leading_chars(range_after_eq, /* bad = */ NULL)); + + return 0; +} + static mhd_result request_parse_arguments_iterator( void *cls, enum MHD_ValueKind kind, @@ -364,7 +465,6 @@ static mhd_result request_parse_arguments_iterator( const char *value) { RequestMeta *m = ASSERT_PTR(cls); - _cleanup_free_ char *p = NULL; int r; if (isempty(key)) { @@ -416,17 +516,7 @@ static mhd_result request_parse_arguments_iterator( } if (r) { - char match[9 + 32 + 1] = "_BOOT_ID="; - sd_id128_t bid; - - r = sd_id128_get_boot(&bid); - if (r < 0) { - log_error_errno(r, "Failed to get boot ID: %m"); - return MHD_NO; - } - - sd_id128_to_string(bid, match + 9); - r = sd_journal_add_match(m->journal, match, sizeof(match)-1); + r = add_match_boot_id(m->journal, SD_ID128_NULL); if (r < 0) { m->argument_parse_error = r; return MHD_NO; @@ -436,13 +526,7 @@ static mhd_result request_parse_arguments_iterator( return MHD_YES; } - p = strjoin(key, "=", strempty(value)); - if (!p) { - m->argument_parse_error = log_oom(); - return MHD_NO; - } - - r = sd_journal_add_match(m->journal, p, 0); + r = journal_add_match_pair(m->journal, key, strempty(value)); if (r < 0) { m->argument_parse_error = r; return MHD_NO; @@ -497,10 +581,15 @@ static int request_handler_entries( if (m->cursor) r = sd_journal_seek_cursor(m->journal, m->cursor); + else if (m->since_set) + r = sd_journal_seek_realtime_usec(m->journal, m->since); else if (m->n_skip >= 0) r = sd_journal_seek_head(m->journal); + else if (m->until_set && m->n_skip < 0) + r = sd_journal_seek_realtime_usec(m->journal, m->until); else if (m->n_skip < 0) r = sd_journal_seek_tail(m->journal); + if (r < 0) return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal."); @@ -778,7 +867,7 @@ static int request_handler_machine( SD_ID128_FORMAT_VAL(bid), hostname_cleanup(hostname), os_release_pretty_name(pretty_name, os_name), - v ? v : "bare", + v ?: "bare", usage, cutoff_from, cutoff_to); diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c index 294719b..34d4062 100644 --- a/src/journal-remote/journal-remote-main.c +++ b/src/journal-remote/journal-remote-main.c @@ -108,7 +108,7 @@ static int spawn_child(const char* child, char** argv) { r = fd_nonblock(fd[0], true); if (r < 0) - log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m"); + log_warning_errno(r, "Failed to set child pipe to non-blocking: %m"); return fd[0]; } @@ -374,7 +374,7 @@ static int setup_microhttpd_server(RemoteServer *s, { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger}, { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free}, { MHD_OPTION_LISTEN_SOCKET, fd}, - { MHD_OPTION_CONNECTION_MEMORY_LIMIT, 128*1024}, + { MHD_OPTION_CONNECTION_MEMORY_LIMIT, JOURNAL_SERVER_MEMORY_MAX}, { MHD_OPTION_END}, { MHD_OPTION_END}, { MHD_OPTION_END}, @@ -451,7 +451,7 @@ static int setup_microhttpd_server(RemoteServer *s, if (epoll_fd < 0) return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "μhttp epoll fd is invalid"); - r = sd_event_add_io(s->events, &d->io_event, + r = sd_event_add_io(s->event, &d->io_event, epoll_fd, EPOLLIN, dispatch_http_event, d); if (r < 0) @@ -461,7 +461,7 @@ static int setup_microhttpd_server(RemoteServer *s, if (r < 0) return log_error_errno(r, "Failed to set source name: %m"); - r = sd_event_add_time(s->events, &d->timer_event, + r = sd_event_add_time(s->event, &d->timer_event, CLOCK_MONOTONIC, UINT64_MAX, 0, null_timer_event_handler, d); if (r < 0) @@ -562,7 +562,7 @@ static int create_remoteserver( if (r < 0) return r; - r = sd_event_set_signal_exit(s->events, true); + r = sd_event_set_signal_exit(s->event, true); if (r < 0) return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); @@ -728,9 +728,12 @@ static int parse_config(void) { {} }; - return config_parse_config_file("journal-remote.conf", "Remote\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + return config_parse_standard_file_with_dropins( + "systemd/journal-remote.conf", + "Remote\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); } static int help(void) { @@ -1067,8 +1070,7 @@ static int run(int argc, char **argv) { _cleanup_free_ char *cert = NULL, *trust = NULL; int r; - log_show_color(true); - log_parse_environment(); + log_setup(); /* The journal merging logic potentially needs a lot of fds. */ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); @@ -1107,7 +1109,7 @@ static int run(int argc, char **argv) { if (r < 0) return r; - r = sd_event_set_watchdog(s.events, true); + r = sd_event_set_watchdog(s.event, true); if (r < 0) return log_error_errno(r, "Failed to enable watchdog: %m"); @@ -1119,13 +1121,13 @@ static int run(int argc, char **argv) { notify_message = notify_start(NOTIFY_READY, NOTIFY_STOPPING); while (s.active) { - r = sd_event_get_state(s.events); + r = sd_event_get_state(s.event); if (r < 0) return r; if (r == SD_EVENT_FINISHED) break; - r = sd_event_run(s.events, -1); + r = sd_event_run(s.event, -1); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); } diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 9db686d..92187b7 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -86,7 +86,6 @@ static int open_output(RemoteServer *s, Writer *w, const char* host) { UINT64_MAX, &w->metrics, w->mmap, - NULL, &w->journal); if (r < 0) return log_error_errno(r, "Failed to open output journal %s: %m", filename); @@ -258,19 +257,19 @@ int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name return r; } - r = sd_event_add_io(s->events, &source->event, + r = sd_event_add_io(s->event, &source->event, fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI, dispatch_raw_source_event, source); if (r == 0) { /* Add additional source for buffer processing. It will be * enabled later. */ - r = sd_event_add_defer(s->events, &source->buffer_event, + r = sd_event_add_defer(s->event, &source->buffer_event, dispatch_raw_source_until_block, source); if (r == 0) r = sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF); } else if (r == -EPERM) { log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name); - r = sd_event_add_defer(s->events, &source->event, + r = sd_event_add_defer(s->event, &source->event, dispatch_blocking_source_event, source); if (r == 0) r = sd_event_source_set_enabled(source->event, SD_EVENT_ON); @@ -302,7 +301,7 @@ int journal_remote_add_raw_socket(RemoteServer *s, int fd) { assert(s); assert(fd >= 0); - r = sd_event_add_io(s->events, &s->listen_event, + r = sd_event_add_io(s->event, &s->listen_event, fd, EPOLLIN, dispatch_raw_connection_event, s); if (r < 0) @@ -348,7 +347,7 @@ int journal_remote_server_init( else assert_not_reached(); - r = sd_event_default(&s->events); + r = sd_event_default(&s->event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); @@ -377,7 +376,7 @@ void journal_remote_server_destroy(RemoteServer *s) { hashmap_free(s->writers); sd_event_source_unref(s->listen_event); - sd_event_unref(s->events); + sd_event_unref(s->event); if (s == journal_remote_server_global) journal_remote_server_global = NULL; @@ -525,7 +524,7 @@ static int accept_connection( if (r < 0) return log_error_errno(r, "socket_address_print(): %m"); - r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b); + r = socknameinfo_pretty(&addr->sockaddr.sa, addr->size, &b); if (r < 0) return log_error_errno(r, "Resolving hostname failed: %m"); diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h index 3d64db0..f1ec5d9 100644 --- a/src/journal-remote/journal-remote.h +++ b/src/journal-remote/journal-remote.h @@ -29,7 +29,7 @@ struct RemoteServer { RemoteSource **sources; size_t active; - sd_event *events; + sd_event *event; sd_event_source *listen_event; Hashmap *writers; @@ -48,6 +48,9 @@ struct RemoteServer { }; extern RemoteServer *journal_remote_server_global; +/* Used for MHD_OPTION_CONNECTION_MEMORY_LIMIT and header parsing cap */ +#define JOURNAL_SERVER_MEMORY_MAX 128U * 1024U + int journal_remote_server_init( RemoteServer *s, const char *output, diff --git a/src/journal-remote/journal-upload-journal.c b/src/journal-remote/journal-upload-journal.c index 8206ca8..23ad3b2 100644 --- a/src/journal-remote/journal-upload-journal.c +++ b/src/journal-remote/journal-upload-journal.c @@ -388,7 +388,7 @@ int open_journal_for_upload(Uploader *u, else u->timeout = JOURNAL_UPLOAD_POLL_TIMEOUT; - r = sd_event_add_io(u->events, &u->input_event, + r = sd_event_add_io(u->event, &u->input_event, fd, events, dispatch_journal_input, u); if (r < 0) return log_error_errno(r, "Failed to register input event: %m"); diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index d70a049..f6b9351 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -366,7 +366,7 @@ static int open_file_for_upload(Uploader *u, const char *filename) { u->input = fd; if (arg_follow != 0) { - r = sd_event_add_io(u->events, &u->input_event, + r = sd_event_add_io(u->event, &u->input_event, fd, EPOLLIN, dispatch_fd_input, u); if (r < 0) { if (r != -EPERM || arg_follow > 0) @@ -415,11 +415,11 @@ static int setup_uploader(Uploader *u, const char *url, const char *state_file) u->state_file = state_file; - r = sd_event_default(&u->events); + r = sd_event_default(&u->event); if (r < 0) return log_error_errno(r, "sd_event_default failed: %m"); - r = sd_event_set_signal_exit(u->events, true); + r = sd_event_set_signal_exit(u->event, true); if (r < 0) return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); @@ -445,7 +445,7 @@ static void destroy_uploader(Uploader *u) { close_fd_input(u); close_journal_input(u); - sd_event_unref(u->events); + sd_event_unref(u->event); } static int perform_upload(Uploader *u) { @@ -499,9 +499,12 @@ static int parse_config(void) { {} }; - return config_parse_config_file("journal-upload.conf", "Upload\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + return config_parse_standard_file_with_dropins( + "systemd/journal-upload.conf", + "Upload\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); } static int help(void) { @@ -745,7 +748,7 @@ static int open_journal(sd_journal **j) { else if (arg_file) r = sd_journal_open_files(j, (const char**) arg_file, 0); else if (arg_machine) - r = journal_open_machine(j, arg_machine); + r = journal_open_machine(j, arg_machine, 0); else r = sd_journal_open_namespace(j, arg_namespace, (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | arg_namespace_flags | arg_journal_type); @@ -761,8 +764,7 @@ static int run(int argc, char **argv) { bool use_journal; int r; - log_show_color(true); - log_parse_environment(); + log_setup(); /* The journal merging logic potentially needs a lot of fds. */ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); @@ -781,7 +783,7 @@ static int run(int argc, char **argv) { if (r < 0) return r; - sd_event_set_watchdog(u.events, true); + sd_event_set_watchdog(u.event, true); r = check_cursor_updating(&u); if (r < 0) @@ -809,7 +811,7 @@ static int run(int argc, char **argv) { NOTIFY_STOPPING); for (;;) { - r = sd_event_get_state(u.events); + r = sd_event_get_state(u.event); if (r < 0) return r; if (r == SD_EVENT_FINISHED) @@ -836,7 +838,7 @@ static int run(int argc, char **argv) { return r; } - r = sd_event_run(u.events, u.timeout); + r = sd_event_run(u.event, u.timeout); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); } diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h index 2007864..5ba3c4f 100644 --- a/src/journal-remote/journal-upload.h +++ b/src/journal-remote/journal-upload.h @@ -24,7 +24,7 @@ typedef enum { } entry_state; typedef struct Uploader { - sd_event *events; + sd_event *event; char *url; CURL *easy; diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 964a251..10a8275 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -22,9 +22,9 @@ libsystemd_journal_remote = static_library( libsystemd_journal_remote_sources, include_directories : includes, dependencies : [libgnutls, - liblz4, + liblz4_cflags, libmicrohttpd, - libxz, + libxz_cflags, threads, userspace], build_by_default : false) @@ -38,9 +38,9 @@ systemd_journal_gatewayd_sources = files( common_deps = [ libgnutls, - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ] diff --git a/src/journal/bsod.c b/src/journal/bsod.c index a88cb66..b2889f0 100644 --- a/src/journal/bsod.c +++ b/src/journal/bsod.c @@ -17,6 +17,7 @@ #include "log.h" #include "logs-show.h" #include "main-func.h" +#include "parse-argument.h" #include "pretty-print.h" #include "qrcode-util.h" #include "sigbus.h" @@ -25,6 +26,9 @@ #include "terminal-util.h" static bool arg_continuous = false; +static char *arg_tty = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_tty, freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -34,19 +38,22 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s\n\n" - "%sFilter the journal to fetch the first message from the\n" - "current boot with an emergency log level and displays it\n" - "as a string and a QR code.\n\n%s" + printf("%1$s [OPTIONS...]\n\n" + "%5$sFilter the journal to fetch the first message from the current boot with an%6$s\n" + "%5$semergency log level and display it as a string and a QR code.%6$s\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" " -c --continuous Make systemd-bsod wait continuously\n" " for changes in the journal\n" - "\nSee the %s for details.\n", + " --tty=TTY Specify path to TTY to use\n" + "\nSee the %2$s for details.\n", program_invocation_short_name, - ansi_highlight(), + link, + ansi_underline(), ansi_normal(), - link); + ansi_highlight(), + ansi_normal()); return 0; } @@ -60,22 +67,22 @@ static int acquire_first_emergency_log_message(char **ret) { assert(ret); - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | (arg_continuous ? 0 : SD_JOURNAL_ASSUME_IMMUTABLE)); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); r = add_match_this_boot(j, NULL); if (r < 0) - return log_warning_errno(r, "Failed to add boot ID filter: %m"); + return log_error_errno(r, "Failed to add boot ID filter: %m"); - r = sd_journal_add_match(j, "_UID=0", 0); + r = sd_journal_add_match(j, "_UID=0", SIZE_MAX); if (r < 0) - return log_warning_errno(r, "Failed to add User ID filter: %m"); + return log_error_errno(r, "Failed to add User ID filter: %m"); assert_cc(0 == LOG_EMERG); - r = sd_journal_add_match(j, "PRIORITY=0", 0); + r = sd_journal_add_match(j, "PRIORITY=0", SIZE_MAX); if (r < 0) - return log_warning_errno(r, "Failed to add Emergency filter: %m"); + return log_error_errno(r, "Failed to add Emergency filter: %m"); r = sd_journal_seek_head(j); if (r < 0) @@ -103,7 +110,7 @@ static int acquire_first_emergency_log_message(char **ret) { if (r < 0) return log_error_errno(r, "Failed to read journal message: %m"); - message = memdup_suffix0((const char*)d + STRLEN("MESSAGE="), l - STRLEN("MESSAGE=")); + message = memdup_suffix0((const char*)d + strlen("MESSAGE="), l - strlen("MESSAGE=")); if (!message) return log_oom(); @@ -124,18 +131,18 @@ static int find_next_free_vt(int fd, int *ret_free_vt, int *ret_original_vt) { for (size_t i = 0; i < sizeof(terminal_status.v_state) * 8; i++) if ((terminal_status.v_state & (1 << i)) == 0) { - *ret_free_vt = i; + *ret_free_vt = i + 1; *ret_original_vt = terminal_status.v_active; return 0; } - return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "No free VT found: %m"); + return -ENOTTY; } static int display_emergency_message_fullscreen(const char *message) { - int r, ret = 0, free_vt = 0, original_vt = 0; + int r, free_vt = 0, original_vt = 0; unsigned qr_code_start_row = 1, qr_code_start_column = 1; - char tty[STRLEN("/dev/tty") + DECIMAL_STR_MAX(int) + 1]; + char ttybuf[STRLEN("/dev/tty") + DECIMAL_STR_MAX(int) + 1]; _cleanup_close_ int fd = -EBADF; _cleanup_fclose_ FILE *stream = NULL; char read_character_buffer = '\0'; @@ -143,30 +150,36 @@ static int display_emergency_message_fullscreen(const char *message) { .ws_col = 80, .ws_row = 25, }; + const char *tty; assert(message); - fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(fd, "Failed to open tty1: %m"); + if (arg_tty) + tty = arg_tty; + else { + fd = open_terminal("/dev/tty1", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(fd, "Failed to open /dev/tty1: %m"); - r = find_next_free_vt(fd, &free_vt, &original_vt); - if (r < 0) - return log_error_errno(r, "Failed to find a free VT: %m"); + r = find_next_free_vt(fd, &free_vt, &original_vt); + if (r < 0) + return log_error_errno(r, "Failed to find a free VT: %m"); - xsprintf(tty, "/dev/tty%d", free_vt + 1); + xsprintf(ttybuf, "/dev/tty%d", free_vt); + tty = ttybuf; - r = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (r < 0) - return log_error_errno(fd, "Failed to open tty: %m"); + fd = safe_close(fd); + } - close_and_replace(fd, r); + fd = open_terminal(tty, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(fd, "Failed to open %s: %m", tty); if (ioctl(fd, TIOCGWINSZ, &w) < 0) log_warning_errno(errno, "Failed to fetch tty size, ignoring: %m"); - if (ioctl(fd, VT_ACTIVATE, free_vt + 1) < 0) - return log_error_errno(errno, "Failed to activate tty: %m"); + if (free_vt > 0 && ioctl(fd, VT_ACTIVATE, free_vt) < 0) + return log_error_errno(errno, "Failed to activate /dev/tty%i, ignoring: %m", free_vt); r = loop_write(fd, ANSI_BACKGROUND_BLUE ANSI_HOME_CLEAR, SIZE_MAX); if (r < 0) @@ -178,7 +191,7 @@ static int display_emergency_message_fullscreen(const char *message) { r = loop_write(fd, "The current boot has failed!", SIZE_MAX); if (r < 0) { - ret = log_warning_errno(r, "Failed to write to terminal: %m"); + log_error_errno(r, "Failed to write to terminal: %m"); goto cleanup; } @@ -190,13 +203,13 @@ static int display_emergency_message_fullscreen(const char *message) { r = loop_write(fd, message, SIZE_MAX); if (r < 0) { - ret = log_warning_errno(r, "Failed to write emergency message to terminal: %m"); + log_error_errno(r, "Failed to write emergency message to terminal: %m"); goto cleanup; } r = fdopen_independent(fd, "r+", &stream); if (r < 0) { - ret = log_error_errno(errno, "Failed to open output file: %m"); + r = log_error_errno(errno, "Failed to open output file: %m"); goto cleanup; } @@ -208,37 +221,41 @@ static int display_emergency_message_fullscreen(const char *message) { if (r < 0) log_warning_errno(r, "Failed to move terminal cursor position, ignoring: %m"); - r = loop_write(fd, "Press any key to exit...", SIZE_MAX); + r = loop_write(fd, ANSI_BACKGROUND_BLUE "Press any key to exit...", SIZE_MAX); if (r < 0) { - ret = log_warning_errno(r, "Failed to write to terminal: %m"); + log_error_errno(r, "Failed to write to terminal: %m"); goto cleanup; } r = read_one_char(stream, &read_character_buffer, USEC_INFINITY, NULL); if (r < 0 && r != -EINTR) - ret = log_error_errno(r, "Failed to read character: %m"); + log_error_errno(r, "Failed to read character: %m"); + + r = 0; cleanup: - if (ioctl(fd, VT_ACTIVATE, original_vt) < 0) - return log_error_errno(errno, "Failed to switch back to original VT: %m"); + if (original_vt > 0 && ioctl(fd, VT_ACTIVATE, original_vt) < 0) + log_warning_errno(errno, "Failed to switch back to original VT /dev/tty%i: %m", original_vt); - return ret; + return r; } static int parse_argv(int argc, char * argv[]) { enum { ARG_VERSION = 0x100, + ARG_TTY, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "continuous", no_argument, NULL, 'c' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "continuous", no_argument, NULL, 'c' }, + { "tty", required_argument, NULL, ARG_TTY }, {} }; - int c; + int c, r; assert(argc >= 0); assert(argv); @@ -257,6 +274,13 @@ static int parse_argv(int argc, char * argv[]) { arg_continuous = true; break; + case ARG_TTY: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tty); + if (r < 0) + return r; + + break; + case '?': return -EINVAL; @@ -281,8 +305,7 @@ static int run(int argc, char *argv[]) { _cleanup_free_ char *message = NULL; int r; - log_open(); - log_parse_environment(); + log_setup(); sigbus_install(); @@ -292,7 +315,7 @@ static int run(int argc, char *argv[]) { r = acquire_first_emergency_log_message(&message); if (r < 0) - return log_error_errno(r, "Failed to acquire first emergency log message: %m"); + return r; if (!message) { log_debug("No emergency-level entries"); @@ -301,11 +324,7 @@ static int run(int argc, char *argv[]) { assert_se(sigaction_many(&nop_sigaction, SIGTERM, SIGINT) >= 0); - r = display_emergency_message_fullscreen((const char*) message); - if (r < 0) - return log_error_errno(r, "Failed to display emergency message on terminal: %m"); - - return 0; + return display_emergency_message_fullscreen(message); } DEFINE_MAIN_FUNCTION(run); diff --git a/src/journal/cat.c b/src/journal/cat.c index 0325add..90d4f53 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -24,6 +24,7 @@ #include "terminal-util.h" static const char *arg_identifier = NULL; +static const char *arg_namespace = NULL; static int arg_priority = LOG_INFO; static int arg_stderr_priority = -1; static bool arg_level_prefix = true; @@ -44,6 +45,7 @@ static int help(void) { " -p --priority=PRIORITY Set priority value (0..7)\n" " --stderr-priority=PRIORITY Set priority value (0..7) used for stderr\n" " --level-prefix=BOOL Control whether level prefix shall be parsed\n" + " --namespace=NAMESPACE Connect to specified journal namespace\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -58,7 +60,8 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_STDERR_PRIORITY, - ARG_LEVEL_PREFIX + ARG_LEVEL_PREFIX, + ARG_NAMESPACE, }; static const struct option options[] = { @@ -68,6 +71,7 @@ static int parse_argv(int argc, char *argv[]) { { "priority", required_argument, NULL, 'p' }, { "stderr-priority", required_argument, NULL, ARG_STDERR_PRIORITY }, { "level-prefix", required_argument, NULL, ARG_LEVEL_PREFIX }, + { "namespace", required_argument, NULL, ARG_NAMESPACE }, {} }; @@ -91,10 +95,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); case 't': - if (isempty(optarg)) - arg_identifier = NULL; - else - arg_identifier = optarg; + arg_identifier = empty_to_null(optarg); break; case 'p': @@ -117,6 +118,10 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_NAMESPACE: + arg_namespace = empty_to_null(optarg); + break; + case '?': return -EINVAL; @@ -137,12 +142,12 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - outfd = sd_journal_stream_fd(arg_identifier, arg_priority, arg_level_prefix); + outfd = sd_journal_stream_fd_with_namespace(arg_namespace, arg_identifier, arg_priority, arg_level_prefix); if (outfd < 0) return log_error_errno(outfd, "Failed to create stream fd: %m"); if (arg_stderr_priority >= 0 && arg_stderr_priority != arg_priority) { - errfd = sd_journal_stream_fd(arg_identifier, arg_stderr_priority, arg_level_prefix); + errfd = sd_journal_stream_fd_with_namespace(arg_namespace, arg_identifier, arg_stderr_priority, arg_level_prefix); if (errfd < 0) return log_error_errno(errfd, "Failed to create stream fd: %m"); } diff --git a/src/journal/fuzz-journald-audit.c b/src/journal/fuzz-journald-audit.c index 9bf7d01..3e08ce3 100644 --- a/src/journal/fuzz-journald-audit.c +++ b/src/journal/fuzz-journald-audit.c @@ -5,13 +5,13 @@ #include "journald-audit.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - Server s; + _cleanup_(server_freep) Server *s = NULL; fuzz_setup_logging(); - dummy_server_init(&s, data, size); - process_audit_string(&s, 0, s.buffer, size); - server_done(&s); + assert_se(server_new(&s) >= 0); + dummy_server_init(s, data, size); + process_audit_string(s, 0, s->buffer, size); return 0; } diff --git a/src/journal/fuzz-journald-kmsg.c b/src/journal/fuzz-journald-kmsg.c index 104a9b3..1a19f50 100644 --- a/src/journal/fuzz-journald-kmsg.c +++ b/src/journal/fuzz-journald-kmsg.c @@ -5,16 +5,16 @@ #include "journald-kmsg.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - Server s; + _cleanup_(server_freep) Server *s = NULL; if (size == 0) return 0; fuzz_setup_logging(); - dummy_server_init(&s, data, size); - dev_kmsg_record(&s, s.buffer, size); - server_done(&s); + assert_se(server_new(&s) >= 0); + dummy_server_init(s, data, size); + dev_kmsg_record(s, s->buffer, size); return 0; } diff --git a/src/journal/fuzz-journald-native-fd.c b/src/journal/fuzz-journald-native-fd.c index 110eb7f..07a8eb5 100644 --- a/src/journal/fuzz-journald-native-fd.c +++ b/src/journal/fuzz-journald-native-fd.c @@ -10,7 +10,7 @@ #include "tmpfile-util.h" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - Server s; + _cleanup_(server_freep) Server *s = NULL; _cleanup_close_ int sealed_fd = -EBADF, unsealed_fd = -EBADF; _cleanup_(unlink_tempfilep) char name[] = "/tmp/fuzz-journald-native-fd.XXXXXX"; char *label = NULL; @@ -20,7 +20,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { fuzz_setup_logging(); - dummy_server_init(&s, NULL, 0); + assert_se(server_new(&s) >= 0); + dummy_server_init(s, NULL, 0); sealed_fd = memfd_new_and_seal(NULL, data, size); assert_se(sealed_fd >= 0); @@ -29,15 +30,13 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { .uid = geteuid(), .gid = getegid(), }; - server_process_native_file(&s, sealed_fd, &ucred, tv, label, label_len); + (void) server_process_native_file(s, sealed_fd, &ucred, tv, label, label_len); unsealed_fd = mkostemp_safe(name); assert_se(unsealed_fd >= 0); assert_se(write(unsealed_fd, data, size) == (ssize_t) size); assert_se(lseek(unsealed_fd, 0, SEEK_SET) == 0); - server_process_native_file(&s, unsealed_fd, &ucred, tv, label, label_len); - - server_done(&s); + (void) server_process_native_file(s, unsealed_fd, &ucred, tv, label, label_len); return 0; } diff --git a/src/journal/fuzz-journald-stream.c b/src/journal/fuzz-journald-stream.c index 6b2055f..3ad9e20 100644 --- a/src/journal/fuzz-journald-stream.c +++ b/src/journal/fuzz-journald-stream.c @@ -9,12 +9,11 @@ #include "fuzz-journald.h" #include "journald-stream.h" -static int stream_fds[2] = EBADF_PAIR; - int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { - Server s; + _cleanup_close_pair_ int stream_fds[2] = EBADF_PAIR; + _cleanup_(server_freep) Server *s = NULL; StdoutStream *stream; - int v; + int v, fd0; if (outside_size_range(size, 1, 65536)) return 0; @@ -22,15 +21,18 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { fuzz_setup_logging(); assert_se(socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, stream_fds) >= 0); - dummy_server_init(&s, NULL, 0); - assert_se(stdout_stream_install(&s, stream_fds[0], &stream) >= 0); + assert_se(server_new(&s) >= 0); + dummy_server_init(s, NULL, 0); + + assert_se(stdout_stream_install(s, stream_fds[0], &stream) >= 0); + fd0 = TAKE_FD(stream_fds[0]); /* avoid double close */ + assert_se(write(stream_fds[1], data, size) == (ssize_t) size); - while (ioctl(stream_fds[0], SIOCINQ, &v) == 0 && v) - sd_event_run(s.event, UINT64_MAX); - if (s.n_stdout_streams) + while (ioctl(fd0, SIOCINQ, &v) == 0 && v) + sd_event_run(s->event, UINT64_MAX); + + if (s->n_stdout_streams > 0) stdout_stream_destroy(stream); - server_done(&s); - stream_fds[1] = safe_close(stream_fds[1]); return 0; } diff --git a/src/journal/fuzz-journald.c b/src/journal/fuzz-journald.c index c96fad5..8317783 100644 --- a/src/journal/fuzz-journald.c +++ b/src/journal/fuzz-journald.c @@ -6,17 +6,9 @@ #include "sd-event.h" void dummy_server_init(Server *s, const uint8_t *buffer, size_t size) { - *s = (Server) { - .syslog_fd = -EBADF, - .native_fd = -EBADF, - .stdout_fd = -EBADF, - .dev_kmsg_fd = -EBADF, - .audit_fd = -EBADF, - .hostname_fd = -EBADF, - .notify_fd = -EBADF, - .storage = STORAGE_NONE, - .line_max = 64, - }; + assert(s); + + s->storage = STORAGE_NONE; assert_se(sd_event_default(&s->event) >= 0); if (buffer) { @@ -30,7 +22,8 @@ void fuzz_journald_processing_function( size_t size, void (*f)(Server *s, const char *buf, size_t raw_len, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len) ) { - Server s; + + _cleanup_(server_freep) Server *s = NULL; char *label = NULL; size_t label_len = 0; struct ucred *ucred = NULL; @@ -39,7 +32,7 @@ void fuzz_journald_processing_function( if (size == 0) return; - dummy_server_init(&s, data, size); - (*f)(&s, s.buffer, size, ucred, tv, label, label_len); - server_done(&s); + assert_se(server_new(&s) >= 0); + dummy_server_init(s, data, size); + (*f)(s, s->buffer, size, ucred, tv, label, label_len); } diff --git a/src/journal/journalctl-authenticate.c b/src/journal/journalctl-authenticate.c new file mode 100644 index 0000000..10630f5 --- /dev/null +++ b/src/journal/journalctl-authenticate.c @@ -0,0 +1,207 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "chattr-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "fsprg.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-authenticate.h" +#include "journalctl.h" +#include "journalctl-authenticate.h" +#include "memstream-util.h" +#include "path-util.h" +#include "qrcode-util.h" +#include "random-util.h" +#include "stat-util.h" +#include "terminal-util.h" +#include "tmpfile-util.h" + +static int format_key( + const void *seed, + size_t seed_size, + uint64_t start, + uint64_t interval, + char **ret) { + + _cleanup_(memstream_done) MemStream m = {}; + FILE *f; + + assert(seed); + assert(seed_size > 0); + assert(ret); + + f = memstream_init(&m); + if (!f) + return -ENOMEM; + + for (size_t i = 0; i < seed_size; i++) { + if (i > 0 && i % 3 == 0) + fputc('-', f); + fprintf(f, "%02x", ((uint8_t*) seed)[i]); + } + + fprintf(f, "/%"PRIx64"-%"PRIx64, start, interval); + + return memstream_finalize(&m, ret, NULL); +} + +int action_setup_keys(void) { + _cleanup_(unlink_and_freep) char *tmpfile = NULL; + _cleanup_close_ int fd = -EBADF; + _cleanup_free_ char *path = NULL; + size_t mpk_size, seed_size, state_size; + uint8_t *mpk, *seed, *state; + sd_id128_t machine, boot; + uint64_t n; + int r; + + assert(arg_action == ACTION_SETUP_KEYS); + + r = is_dir("/var/log/journal/", /* follow = */ false); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), + "/var/log/journal is not a directory, must be using persistent logging for FSS."); + if (r == -ENOENT) + return log_error_errno(r, "Directory /var/log/journal/ does not exist, must be using persistent logging for FSS."); + if (r < 0) + return log_error_errno(r, "Failed to check if /var/log/journal/ is a directory: %m"); + + r = sd_id128_get_machine(&machine); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + r = sd_id128_get_boot(&boot); + if (r < 0) + return log_error_errno(r, "Failed to get boot ID: %m"); + + path = path_join("/var/log/journal/", SD_ID128_TO_STRING(machine), "/fss"); + if (!path) + return log_oom(); + + if (arg_force) { + if (unlink(path) < 0 && errno != ENOENT) + return log_error_errno(errno, "Failed to remove \"%s\": %m", path); + } else if (access(path, F_OK) >= 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Sealing key file %s exists already. Use --force to recreate.", path); + + mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR); + mpk = alloca_safe(mpk_size); + + seed_size = FSPRG_RECOMMENDED_SEEDLEN; + seed = alloca_safe(seed_size); + + state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); + state = alloca_safe(state_size); + + log_info("Generating seed..."); + r = crypto_random_bytes(seed, seed_size); + if (r < 0) + return log_error_errno(r, "Failed to acquire random seed: %m"); + + log_info("Generating key pair..."); + r = FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR); + if (r < 0) + return log_error_errno(r, "Failed to generate key pair: %m"); + + log_info("Generating sealing key..."); + r = FSPRG_GenState0(state, mpk, seed, seed_size); + if (r < 0) + return log_error_errno(r, "Failed to generate sealing key: %m"); + + assert(arg_interval > 0); + n = now(CLOCK_REALTIME); + n /= arg_interval; + + fd = open_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &tmpfile); + if (fd < 0) + return log_error_errno(fd, "Failed to open a temporary file for %s: %m", path); + + r = chattr_secret(fd, CHATTR_WARN_UNSUPPORTED_FLAGS); + if (r < 0) + log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, + r, "Failed to set file attributes on a temporary file for '%s', ignoring: %m", path); + + struct FSSHeader h = { + .signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }, + .machine_id = machine, + .boot_id = boot, + .header_size = htole64(sizeof(h)), + .start_usec = htole64(n * arg_interval), + .interval_usec = htole64(arg_interval), + .fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR), + .fsprg_state_size = htole64(state_size), + }; + + r = loop_write(fd, &h, sizeof(h)); + if (r < 0) + return log_error_errno(r, "Failed to write header: %m"); + + r = loop_write(fd, state, state_size); + if (r < 0) + return log_error_errno(r, "Failed to write state: %m"); + + r = link_tmpfile(fd, tmpfile, path, /* flags = */ 0); + if (r < 0) + return log_error_errno(r, "Failed to link file: %m"); + + tmpfile = mfree(tmpfile); + + _cleanup_free_ char *key = NULL; + r = format_key(seed, seed_size, n, arg_interval, &key); + if (r < 0) + return r; + + if (!on_tty()) { + /* If we are not on a TTY, show only the key. */ + puts(key); + return 0; + } + + _cleanup_free_ char *hn = NULL; + hn = gethostname_malloc(); + if (hn) + hostname_cleanup(hn); + + fprintf(stderr, + "\nNew keys have been generated for host %s%s" SD_ID128_FORMAT_STR ".\n" + "\n" + "The %ssecret sealing key%s has been written to the following local file.\n" + "This key file is automatically updated when the sealing key is advanced.\n" + "It should not be used on multiple hosts.\n" + "\n" + "\t%s\n" + "\n" + "The sealing key is automatically changed every %s.\n" + "\n" + "Please write down the following %ssecret verification key%s. It should be stored\n" + "in a safe location and should not be saved locally on disk.\n" + "\n\t%s", + strempty(hn), hn ? "/" : "", + SD_ID128_FORMAT_VAL(machine), + ansi_highlight(), ansi_normal(), + path, + FORMAT_TIMESPAN(arg_interval, 0), + ansi_highlight(), ansi_normal(), + ansi_highlight_red()); + fflush(stderr); + + puts(key); + + fputs(ansi_normal(), stderr); + +#if HAVE_QRENCODE + _cleanup_free_ char *url = NULL; + url = strjoin("fss://", key, "?machine=", SD_ID128_TO_STRING(machine), hn ? ";hostname=" : "", hn); + if (!url) + return log_oom(); + + (void) print_qrcode(stderr, + "To transfer the verification key to your phone scan the QR code below", + url); +#endif + + return 0; +} diff --git a/src/journal/journalctl-authenticate.h b/src/journal/journalctl-authenticate.h new file mode 100644 index 0000000..2a8ebd5 --- /dev/null +++ b/src/journal/journalctl-authenticate.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#if HAVE_GCRYPT + +int action_setup_keys(void); + +#else + +#include "log.h" + +static inline int action_setup_keys(void) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Forward-secure sealing not available."); +} + +#endif diff --git a/src/journal/journalctl-catalog.c b/src/journal/journalctl-catalog.c new file mode 100644 index 0000000..116e152 --- /dev/null +++ b/src/journal/journalctl-catalog.c @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "catalog.h" +#include "journalctl.h" +#include "journalctl-catalog.h" +#include "path-util.h" + +int action_update_catalog(void) { + _cleanup_free_ char *database = NULL; + const char *e; + int r; + + assert(arg_action == ACTION_UPDATE_CATALOG); + + database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE); + if (!database) + return log_oom(); + + e = secure_getenv("SYSTEMD_CATALOG_SOURCES"); + r = catalog_update(database, + arg_root, + e ? STRV_MAKE_CONST(e) : catalog_file_dirs); + if (r < 0) + return log_error_errno(r, "Failed to update catalog: %m"); + + return 0; +} + +int action_list_catalog(char **items) { + _cleanup_free_ char *database = NULL; + int r; + + assert(IN_SET(arg_action, ACTION_LIST_CATALOG, ACTION_DUMP_CATALOG)); + + database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE); + if (!database) + return log_oom(); + + bool oneline = arg_action == ACTION_LIST_CATALOG; + + pager_open(arg_pager_flags); + + if (items) + r = catalog_list_items(stdout, database, oneline, items); + else + r = catalog_list(stdout, database, oneline); + if (r < 0) + return log_error_errno(r, "Failed to list catalog: %m"); + + return 0; +} diff --git a/src/journal/journalctl-catalog.h b/src/journal/journalctl-catalog.h new file mode 100644 index 0000000..d52bdd4 --- /dev/null +++ b/src/journal/journalctl-catalog.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int action_update_catalog(void); +int action_list_catalog(char **items); diff --git a/src/journal/journalctl-filter.c b/src/journal/journalctl-filter.c new file mode 100644 index 0000000..f9eb9f8 --- /dev/null +++ b/src/journal/journalctl-filter.c @@ -0,0 +1,484 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-device.h" + +#include "chase.h" +#include "devnum-util.h" +#include "fileio.h" +#include "glob-util.h" +#include "journal-internal.h" +#include "journalctl.h" +#include "journalctl-filter.h" +#include "journalctl-util.h" +#include "logs-show.h" +#include "missing_sched.h" +#include "nulstr-util.h" +#include "path-util.h" +#include "unit-name.h" + +static int add_boot(sd_journal *j) { + int r; + + assert(j); + + if (!arg_boot) + return 0; + + assert(!sd_id128_is_null(arg_boot_id)); + + r = add_match_boot_id(j, arg_boot_id); + if (r < 0) + return r; + + return sd_journal_add_conjunction(j); +} + +static int add_dmesg(sd_journal *j) { + int r; + + assert(j); + + if (!arg_dmesg) + return 0; + + r = sd_journal_add_match(j, "_TRANSPORT=kernel", SIZE_MAX); + if (r < 0) + return r; + + return sd_journal_add_conjunction(j); +} + +static int get_possible_units( + sd_journal *j, + const char *fields, + char **patterns, + Set **ret) { + + _cleanup_set_free_ Set *found = NULL; + int r; + + assert(j); + assert(fields); + assert(ret); + + NULSTR_FOREACH(field, fields) { + const void *data; + size_t size; + + r = sd_journal_query_unique(j, field); + if (r < 0) + return r; + + SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { + _cleanup_free_ char *u = NULL; + char *eq; + + eq = memchr(data, '=', size); + if (eq) { + size -= eq - (char*) data + 1; + data = ++eq; + } + + u = strndup(data, size); + if (!u) + return -ENOMEM; + + size_t i; + if (!strv_fnmatch_full(patterns, u, FNM_NOESCAPE, &i)) + continue; + + log_debug("Matched %s with pattern %s=%s", u, field, patterns[i]); + r = set_ensure_consume(&found, &string_hash_ops_free, TAKE_PTR(u)); + if (r < 0) + return r; + } + } + + *ret = TAKE_PTR(found); + return 0; +} + +/* This list is supposed to return the superset of unit names + * possibly matched by rules added with add_matches_for_unit... */ +#define SYSTEM_UNITS \ + "_SYSTEMD_UNIT\0" \ + "COREDUMP_UNIT\0" \ + "UNIT\0" \ + "OBJECT_SYSTEMD_UNIT\0" \ + "_SYSTEMD_SLICE\0" + +/* ... and add_matches_for_user_unit */ +#define USER_UNITS \ + "_SYSTEMD_USER_UNIT\0" \ + "USER_UNIT\0" \ + "COREDUMP_USER_UNIT\0" \ + "OBJECT_SYSTEMD_USER_UNIT\0" \ + "_SYSTEMD_USER_SLICE\0" + +static int add_units(sd_journal *j) { + _cleanup_strv_free_ char **patterns = NULL; + bool added = false; + int r; + + assert(j); + + if (strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) + return 0; + + STRV_FOREACH(i, arg_system_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_consume(&patterns, TAKE_PTR(u)); + if (r < 0) + return r; + } else { + r = add_matches_for_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_ Set *units = NULL; + char *u; + + r = get_possible_units(j, SYSTEM_UNITS, patterns, &units); + if (r < 0) + return r; + + SET_FOREACH(u, units) { + r = add_matches_for_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + patterns = strv_free(patterns); + + STRV_FOREACH(i, arg_user_units) { + _cleanup_free_ char *u = NULL; + + r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); + if (r < 0) + return r; + + if (string_is_glob(u)) { + r = strv_consume(&patterns, TAKE_PTR(u)); + if (r < 0) + return r; + } else { + r = add_matches_for_user_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + if (!strv_isempty(patterns)) { + _cleanup_set_free_ Set *units = NULL; + char *u; + + r = get_possible_units(j, USER_UNITS, patterns, &units); + if (r < 0) + return r; + + SET_FOREACH(u, units) { + r = add_matches_for_user_unit(j, u); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + added = true; + } + } + + /* Complain if the user request matches but nothing whatsoever was found, since otherwise everything + * would be matched. */ + if (!added) + return -ENODATA; + + return sd_journal_add_conjunction(j); +} + +static int add_syslog_identifier(sd_journal *j) { + int r; + + assert(j); + + if (strv_isempty(arg_syslog_identifier)) + return 0; + + STRV_FOREACH(i, arg_syslog_identifier) { + r = journal_add_match_pair(j, "SYSLOG_IDENTIFIER", *i); + if (r < 0) + return r; + r = sd_journal_add_disjunction(j); + if (r < 0) + return r; + } + + return sd_journal_add_conjunction(j); +} + +static int add_exclude_identifier(sd_journal *j) { + _cleanup_set_free_ Set *excludes = NULL; + int r; + + assert(j); + + r = set_put_strdupv(&excludes, arg_exclude_identifier); + if (r < 0) + return r; + + return set_free_and_replace(j->exclude_syslog_identifiers, excludes); +} + +static int add_priorities(sd_journal *j) { + int r; + + assert(j); + + if (arg_priorities == 0) + return 0; + + for (int i = LOG_EMERG; i <= LOG_DEBUG; i++) + if (arg_priorities & (1 << i)) { + r = journal_add_matchf(j, "PRIORITY=%d", i); + if (r < 0) + return r; + } + + return sd_journal_add_conjunction(j); +} + +static int add_facilities(sd_journal *j) { + int r; + + assert(j); + + if (set_isempty(arg_facilities)) + return 0; + + void *p; + SET_FOREACH(p, arg_facilities) { + r = journal_add_matchf(j, "SYSLOG_FACILITY=%d", PTR_TO_INT(p)); + if (r < 0) + return r; + } + + return sd_journal_add_conjunction(j); +} + +static int add_matches_for_executable(sd_journal *j, const char *path) { + _cleanup_free_ char *interpreter = NULL; + int r; + + assert(j); + assert(path); + + if (executable_is_script(path, &interpreter) > 0) { + _cleanup_free_ char *comm = NULL; + + r = path_extract_filename(path, &comm); + if (r < 0) + return log_error_errno(r, "Failed to extract filename of '%s': %m", path); + + r = journal_add_match_pair(j, "_COMM", strshorten(comm, TASK_COMM_LEN-1)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + /* Append _EXE only if the interpreter is not a link. Otherwise, it might be outdated often. */ + path = is_symlink(interpreter) > 0 ? interpreter : NULL; + } + + if (path) { + r = journal_add_match_pair(j, "_EXE", path); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + return 0; +} + +static int add_matches_for_device(sd_journal *j, const char *devpath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert(j); + assert(devpath); + + r = sd_device_new_from_devname(&device, devpath); + if (r < 0) + return log_error_errno(r, "Failed to get device '%s': %m", devpath); + + for (sd_device *d = device; d; ) { + const char *subsys, *sysname; + + r = sd_device_get_subsystem(d, &subsys); + if (r < 0) + goto get_parent; + + r = sd_device_get_sysname(d, &sysname); + if (r < 0) + goto get_parent; + + r = journal_add_matchf(j, "_KERNEL_DEVICE=+%s:%s", subsys, sysname); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + dev_t devnum; + if (sd_device_get_devnum(d, &devnum) >= 0) { + r = journal_add_matchf(j, "_KERNEL_DEVICE=%c" DEVNUM_FORMAT_STR, + streq(subsys, "block") ? 'b' : 'c', + DEVNUM_FORMAT_VAL(devnum)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + +get_parent: + if (sd_device_get_parent(d, &d) < 0) + break; + } + + return add_match_boot_id(j, SD_ID128_NULL); +} + +static int add_matches_for_path(sd_journal *j, const char *path) { + _cleanup_free_ char *p = NULL; + struct stat st; + int r; + + assert(j); + assert(path); + + if (arg_root || arg_machine) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "An extra path in match filter is currently not supported with --root, --image, or -M/--machine."); + + r = chase_and_stat(path, NULL, 0, &p, &st); + if (r < 0) + return log_error_errno(r, "Couldn't canonicalize path '%s': %m", path); + + if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) + return add_matches_for_executable(j, p); + + if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) + return add_matches_for_device(j, p); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File is neither a device node nor executable: %s", p); +} + +static int add_matches(sd_journal *j, char **args) { + bool have_term = false; + int r; + + assert(j); + + if (strv_isempty(args)) + return 0; + + STRV_FOREACH(i, args) + if (streq(*i, "+")) { + if (!have_term) + break; + + r = sd_journal_add_disjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add disjunction: %m"); + + have_term = false; + + } else if (path_is_absolute(*i)) { + r = add_matches_for_path(j, *i); + if (r < 0) + return r; + have_term = true; + + } else { + r = sd_journal_add_match(j, *i, SIZE_MAX); + if (r < 0) + return log_error_errno(r, "Failed to add match '%s': %m", *i); + have_term = true; + } + + if (!have_term) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"+\" can only be used between terms."); + + return 0; +} + +int add_filters(sd_journal *j, char **matches) { + int r; + + assert(j); + + /* First, search boot ID, as that may set and flush matches and seek journal. */ + r = journal_acquire_boot(j); + if (r < 0) + return r; + + /* Clear unexpected matches for safety. */ + sd_journal_flush_matches(j); + + /* Then, add filters in the below. */ + r = add_boot(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for boot: %m"); + + r = add_dmesg(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for dmesg: %m"); + + r = add_units(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for units: %m"); + + r = add_syslog_identifier(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for syslog identifiers: %m"); + + r = add_exclude_identifier(j); + if (r < 0) + return log_error_errno(r, "Failed to add exclude filter for syslog identifiers: %m"); + + r = add_priorities(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for priorities: %m"); + + r = add_facilities(j); + if (r < 0) + return log_error_errno(r, "Failed to add filter for facilities: %m"); + + r = add_matches(j, matches); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + _cleanup_free_ char *filter = NULL; + + filter = journal_make_match_string(j); + if (!filter) + return log_oom(); + + log_debug("Journal filter: %s", filter); + } + + return 0; +} diff --git a/src/journal/journalctl-filter.h b/src/journal/journalctl-filter.h new file mode 100644 index 0000000..c752c0c --- /dev/null +++ b/src/journal/journalctl-filter.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-journal.h" + +int add_filters(sd_journal *j, char **matches); diff --git a/src/journal/journalctl-misc.c b/src/journal/journalctl-misc.c new file mode 100644 index 0000000..8ca6ea2 --- /dev/null +++ b/src/journal/journalctl-misc.c @@ -0,0 +1,298 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dirent-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "format-util.h" +#include "journal-internal.h" +#include "journal-verify.h" +#include "journalctl.h" +#include "journalctl-misc.h" +#include "journalctl-util.h" +#include "logs-show.h" +#include "syslog-util.h" + +int action_print_header(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(arg_action == ACTION_PRINT_HEADER); + + r = acquire_journal(&j); + if (r < 0) + return r; + + journal_print_header(j); + return 0; +} + +int action_verify(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(arg_action == ACTION_VERIFY); + + r = acquire_journal(&j); + if (r < 0) + return r; + + log_show_color(true); + + JournalFile *f; + ORDERED_HASHMAP_FOREACH(f, j->files) { + int k; + usec_t first = 0, validated = 0, last = 0; + +#if HAVE_GCRYPT + if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) + log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); +#endif + + k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, /* show_progress = */ !arg_quiet); + if (k == -EINVAL) + /* If the key was invalid give up right-away. */ + return k; + if (k < 0) + r = log_warning_errno(k, "FAIL: %s (%m)", f->path); + else { + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "PASS: %s", f->path); + + if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { + if (validated > 0) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> Validated from %s to %s, final %s entries not sealed.", + format_timestamp_maybe_utc(a, sizeof(a), first), + format_timestamp_maybe_utc(b, sizeof(b), validated), + FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0)); + } else if (last > 0) + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> No sealing yet, %s of entries not sealed.", + FORMAT_TIMESPAN(last - first, 0)); + else + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, + "=> No sealing yet, no entries in file."); + } + } + } + + return r; +} + +int action_disk_usage(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + uint64_t bytes = 0; + int r; + + assert(arg_action == ACTION_DISK_USAGE); + + r = acquire_journal(&j); + if (r < 0) + return r; + + r = sd_journal_get_usage(j, &bytes); + if (r < 0) + return log_error_errno(r, "Failed to get disk usage: %m"); + + printf("Archived and active journals take up %s in the file system.\n", FORMAT_BYTES(bytes)); + return 0; +} + +int action_list_boots(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ BootId *boots = NULL; + size_t n_boots; + int r; + + assert(arg_action == ACTION_LIST_BOOTS); + + r = acquire_journal(&j); + if (r < 0) + return r; + + r = journal_get_boots( + j, + /* advance_older = */ arg_lines_needs_seek_end(), + /* max_ids = */ arg_lines >= 0 ? (size_t) arg_lines : SIZE_MAX, + &boots, &n_boots); + if (r < 0) + return log_error_errno(r, "Failed to determine boots: %m"); + if (r == 0) + return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENODATA), + "No boot found."); + + table = table_new("idx", "boot id", "first entry", "last entry"); + if (!table) + return log_oom(); + + if (arg_full) + table_set_width(table, 0); + + r = table_set_json_field_name(table, 0, "index"); + if (r < 0) + return log_error_errno(r, "Failed to set JSON field name of column 0: %m"); + + (void) table_set_sort(table, (size_t) 0); + (void) table_set_reverse(table, 0, arg_reverse); + + for (int i = 0; i < (int) n_boots; i++) { + int index; + + if (arg_lines_needs_seek_end()) + /* With --lines=N, we only know the negative index, and the older ID is located earlier. */ + index = -i; + else if (arg_lines >= 0) + /* With --lines=+N, we only know the positive index, and the newer ID is located earlier. */ + index = i + 1; + else + /* Otherwise, show negative index. Note, in this case, newer ID is located earlier. */ + index = i + 1 - (int) n_boots; + + r = table_add_many(table, + TABLE_INT, index, + TABLE_SET_ALIGN_PERCENT, 100, + TABLE_ID128, boots[i].id, + TABLE_TIMESTAMP, boots[i].first_usec, + TABLE_TIMESTAMP, boots[i].last_usec); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet); + if (r < 0) + return table_log_print_error(r); + + return 0; +} + +int action_list_fields(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r, n_shown = 0; + + assert(arg_action == ACTION_LIST_FIELDS); + assert(arg_field); + + r = acquire_journal(&j); + if (r < 0) + return r; + + if (!journal_boot_has_effect(j)) + return 0; + + r = sd_journal_set_data_threshold(j, 0); + if (r < 0) + return log_error_errno(r, "Failed to unset data size threshold: %m"); + + r = sd_journal_query_unique(j, arg_field); + if (r < 0) + return log_error_errno(r, "Failed to query unique data objects: %m"); + + const void *data; + size_t size; + SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { + const void *eq; + + if (arg_lines >= 0 && n_shown >= arg_lines) + break; + + eq = memchr(data, '=', size); + if (eq) + printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1); + else + printf("%.*s\n", (int) size, (const char*) data); + + n_shown++; + } + + return 0; +} + +int action_list_field_names(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(arg_action == ACTION_LIST_FIELD_NAMES); + + r = acquire_journal(&j); + if (r < 0) + return r; + + const char *field; + SD_JOURNAL_FOREACH_FIELD(j, field) + printf("%s\n", field); + + return 0; +} + +int action_list_namespaces(void) { + _cleanup_(table_unrefp) Table *table = NULL; + sd_id128_t machine; + int r; + + assert(arg_action == ACTION_LIST_NAMESPACES); + + r = sd_id128_get_machine(&machine); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + table = table_new("namespace"); + if (!table) + return log_oom(); + + (void) table_set_sort(table, (size_t) 0); + + FOREACH_STRING(dir, "/var/log/journal", "/run/log/journal") { + _cleanup_free_ char *path = NULL; + _cleanup_closedir_ DIR *dirp = NULL; + + path = path_join(arg_root, dir); + if (!path) + return log_oom(); + + dirp = opendir(path); + if (!dirp) { + log_debug_errno(errno, "Failed to open directory %s, ignoring: %m", path); + continue; + } + + FOREACH_DIRENT(de, dirp, return log_error_errno(errno, "Failed to iterate through %s: %m", path)) { + + const char *e = strchr(de->d_name, '.'); + if (!e) + continue; + + _cleanup_free_ char *ids = strndup(de->d_name, e - de->d_name); + if (!ids) + return log_oom(); + + sd_id128_t id; + r = sd_id128_from_string(ids, &id); + if (r < 0) + continue; + + if (!sd_id128_equal(machine, id)) + continue; + + e++; + + if (!log_namespace_name_valid(e)) + continue; + + r = table_add_cell(table, NULL, TABLE_STRING, e); + if (r < 0) + return table_log_add_error(r); + } + } + + if (table_isempty(table) && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + if (!arg_quiet) + log_notice("No namespaces found."); + } else { + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/journal/journalctl-misc.h b/src/journal/journalctl-misc.h new file mode 100644 index 0000000..70f851b --- /dev/null +++ b/src/journal/journalctl-misc.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int action_print_header(void); +int action_verify(void); +int action_disk_usage(void); +int action_list_boots(void); +int action_list_fields(void); +int action_list_field_names(void); +int action_list_namespaces(void); diff --git a/src/journal/journalctl-show.c b/src/journal/journalctl-show.c new file mode 100644 index 0000000..e8ffc72 --- /dev/null +++ b/src/journal/journalctl-show.c @@ -0,0 +1,477 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-event.h" + +#include "fileio.h" +#include "journalctl.h" +#include "journalctl-filter.h" +#include "journalctl-show.h" +#include "journalctl-util.h" +#include "logs-show.h" +#include "terminal-util.h" + +#define PROCESS_INOTIFY_INTERVAL 1024 /* Every 1024 messages processed */ + +typedef struct Context { + sd_journal *journal; + bool has_cursor; + bool need_seek; + bool since_seeked; + bool ellipsized; + bool previous_boot_id_valid; + sd_id128_t previous_boot_id; + sd_id128_t previous_boot_id_output; + dual_timestamp previous_ts_output; +} Context; + +static void context_done(Context *c) { + assert(c); + + sd_journal_close(c->journal); +} + +static int seek_journal(Context *c) { + sd_journal *j = ASSERT_PTR(ASSERT_PTR(c)->journal); + _cleanup_free_ char *cursor_from_file = NULL; + const char *cursor = NULL; + bool after_cursor = false; + int r; + + if (arg_cursor || arg_after_cursor) { + assert(!!arg_cursor != !!arg_after_cursor); + + cursor = arg_cursor ?: arg_after_cursor; + after_cursor = arg_after_cursor; + + } else if (arg_cursor_file) { + r = read_one_line_file(arg_cursor_file, &cursor_from_file); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to read cursor file %s: %m", arg_cursor_file); + if (r > 0) { + cursor = cursor_from_file; + after_cursor = true; + } + } + + if (cursor) { + c->has_cursor = true; + + r = sd_journal_seek_cursor(j, cursor); + if (r < 0) + return log_error_errno(r, "Failed to seek to cursor: %m"); + + r = sd_journal_step_one(j, !arg_reverse); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + + if (after_cursor && r > 0) { + /* With --after-cursor=/--cursor-file= we want to skip the first entry only if it's + * the entry the cursor is pointing at, otherwise, if some journal filters are used, + * we might skip the first entry of the filter match, which leads to unexpectedly + * missing journal entries. */ + int k; + + k = sd_journal_test_cursor(j, cursor); + if (k < 0) + return log_error_errno(k, "Failed to test cursor against current entry: %m"); + if (k > 0) + /* Current entry matches the one our cursor is pointing at, so let's try + * to advance the next entry. */ + r = sd_journal_step_one(j, !arg_reverse); + } + + if (r == 0 && !arg_follow) + /* We couldn't find the next entry after the cursor. */ + arg_lines = 0; + + } else if (arg_reverse || arg_lines_needs_seek_end()) { + /* If --reverse and/or --lines=N are specified, things get a little tricky. First we seek to + * the place of --until if specified, otherwise seek to tail. Then, if --reverse is + * specified, we search backwards and let the output counter in show() handle --lines for us. + * If --reverse is unspecified, we just jump backwards arg_lines and search afterwards from + * there. */ + + if (arg_until_set) { + r = sd_journal_seek_realtime_usec(j, arg_until); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + } else { + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + } + + if (arg_reverse) + r = sd_journal_previous(j); + else /* arg_lines_needs_seek_end */ + r = sd_journal_previous_skip(j, arg_lines); + + } else if (arg_since_set) { + /* This is placed after arg_reverse and arg_lines. If --since is used without + * both, we seek to the place of --since and search afterwards from there. + * If used with --reverse or --lines, we seek to the tail first and check if + * the entry is within the range of --since later. */ + + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + c->since_seeked = true; + + r = sd_journal_next(j); + + } else { + r = sd_journal_seek_head(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to head: %m"); + + r = sd_journal_next(j); + } + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + if (r == 0) + c->need_seek = true; + + return 0; +} + +static int show(Context *c) { + sd_journal *j = ASSERT_PTR(ASSERT_PTR(c)->journal); + int r, n_shown = 0; + + OutputFlags flags = + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + arg_catalog * OUTPUT_CATALOG | + arg_utc * OUTPUT_UTC | + arg_truncate_newline * OUTPUT_TRUNCATE_NEWLINE | + arg_no_hostname * OUTPUT_NO_HOSTNAME; + + while (arg_lines < 0 || n_shown < arg_lines || arg_follow) { + size_t highlight[2] = {}; + + if (c->need_seek) { + r = sd_journal_step_one(j, !arg_reverse); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + if (r == 0) + break; + } + + if (arg_until_set && !arg_reverse && (arg_lines < 0 || arg_since_set || c->has_cursor)) { + /* If --lines= is set, we usually rely on the n_shown to tell us when to stop. + * However, if --since= or one of the cursor argument is set too, we may end up + * having less than --lines= to output. In this case let's also check if the entry + * is in range. */ + + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) + return log_error_errno(r, "Failed to determine timestamp: %m"); + if (usec > arg_until) + break; + } + + if (arg_since_set && (arg_reverse || !c->since_seeked)) { + usec_t usec; + + r = sd_journal_get_realtime_usec(j, &usec); + if (r < 0) + return log_error_errno(r, "Failed to determine timestamp: %m"); + + if (usec < arg_since) { + if (arg_reverse) + break; /* Reached the earliest entry */ + + /* arg_lines >= 0 (!since_seeked): + * We jumped arg_lines back and it seems to be too much */ + r = sd_journal_seek_realtime_usec(j, arg_since); + if (r < 0) + return log_error_errno(r, "Failed to seek to date: %m"); + c->since_seeked = true; + + c->need_seek = true; + continue; + } + c->since_seeked = true; /* We're surely within the range of --since now */ + } + + if (!arg_merge && !arg_quiet) { + sd_id128_t boot_id; + + r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); + if (r >= 0) { + if (c->previous_boot_id_valid && + !sd_id128_equal(boot_id, c->previous_boot_id)) + printf("%s-- Boot "SD_ID128_FORMAT_STR" --%s\n", + ansi_highlight(), SD_ID128_FORMAT_VAL(boot_id), ansi_normal()); + + c->previous_boot_id = boot_id; + c->previous_boot_id_valid = true; + } + } + + if (arg_compiled_pattern) { + const void *message; + size_t len; + + r = sd_journal_get_data(j, "MESSAGE", &message, &len); + if (r < 0) { + if (r == -ENOENT) { + c->need_seek = true; + continue; + } + + return log_error_errno(r, "Failed to get MESSAGE field: %m"); + } + + assert_se(message = startswith(message, "MESSAGE=")); + + r = pattern_matches_and_log(arg_compiled_pattern, message, + len - strlen("MESSAGE="), highlight); + if (r < 0) + return r; + if (r == 0) { + c->need_seek = true; + continue; + } + } + + r = show_journal_entry(stdout, j, arg_output, 0, flags, + arg_output_fields, highlight, &c->ellipsized, + &c->previous_ts_output, &c->previous_boot_id_output); + c->need_seek = true; + if (r == -EADDRNOTAVAIL) + break; + if (r < 0) + return r; + + n_shown++; + + /* If journalctl take a long time to process messages, and during that time journal file + * rotation occurs, a journalctl client will keep those rotated files open until it calls + * sd_journal_process(), which typically happens as a result of calling sd_journal_wait() below + * in the "following" case. By periodically calling sd_journal_process() during the processing + * loop we shrink the window of time a client instance has open file descriptors for rotated + * (deleted) journal files. */ + if ((n_shown % PROCESS_INOTIFY_INTERVAL) == 0) { + r = sd_journal_process(j); + if (r < 0) + return log_error_errno(r, "Failed to process inotify events: %m"); + } + } + + return n_shown; +} + +static int show_and_fflush(Context *c, sd_event_source *s) { + int r; + + assert(c); + assert(s); + + r = show(c); + if (r < 0) + return sd_event_exit(sd_event_source_get_event(s), r); + + fflush(stdout); + return 0; +} + +static int on_journal_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Context *c = ASSERT_PTR(userdata); + int r; + + assert(s); + + r = sd_journal_process(c->journal); + if (r < 0) { + log_error_errno(r, "Failed to process journal events: %m"); + return sd_event_exit(sd_event_source_get_event(s), r); + } + + return show_and_fflush(c, s); +} + +static int on_first_event(sd_event_source *s, void *userdata) { + return show_and_fflush(userdata, s); +} + +static int on_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + assert(IN_SET(si->ssi_signo, SIGTERM, SIGINT)); + + return sd_event_exit(sd_event_source_get_event(s), si->ssi_signo); +} + +static int setup_event(Context *c, int fd, sd_event **ret) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int r; + + assert(arg_follow); + assert(c); + assert(fd >= 0); + assert(ret); + + r = sd_event_default(&e); + if (r < 0) + return log_error_errno(r, "Failed to allocate sd_event object: %m"); + + (void) sd_event_add_signal(e, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); + (void) sd_event_add_signal(e, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); + + r = sd_event_add_io(e, NULL, fd, EPOLLIN, &on_journal_event, c); + if (r < 0) + return log_error_errno(r, "Failed to add io event source for journal: %m"); + + /* Also keeps an eye on STDOUT, and exits as soon as we see a POLLHUP on that, i.e. when it is closed. */ + r = sd_event_add_io(e, NULL, STDOUT_FILENO, EPOLLHUP|EPOLLERR, NULL, INT_TO_PTR(-ECANCELED)); + if (r == -EPERM) + /* Installing an epoll watch on a regular file doesn't work and fails with EPERM. Which is + * totally OK, handle it gracefully. epoll_ctl() documents EPERM as the error returned when + * the specified fd doesn't support epoll, hence it's safe to check for that. */ + log_debug_errno(r, "Unable to install EPOLLHUP watch on stderr, not watching for hangups."); + else if (r < 0) + return log_error_errno(r, "Failed to add io event source for stdout: %m"); + + if (arg_lines != 0 || arg_since_set) { + r = sd_event_add_defer(e, NULL, on_first_event, c); + if (r < 0) + return log_error_errno(r, "Failed to add defer event source: %m"); + } + + *ret = TAKE_PTR(e); + return 0; +} + +static int update_cursor(sd_journal *j) { + _cleanup_free_ char *cursor = NULL; + int r; + + assert(j); + + if (!arg_show_cursor && !arg_cursor_file) + return 0; + + r = sd_journal_get_cursor(j, &cursor); + if (r == -EADDRNOTAVAIL) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + if (arg_show_cursor) + printf("-- cursor: %s\n", cursor); + + if (arg_cursor_file) { + r = write_string_file(arg_cursor_file, cursor, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write new cursor to %s: %m", arg_cursor_file); + } + + return 0; +} + +int action_show(char **matches) { + _cleanup_(context_done) Context c = {}; + int n_shown, r, poll_fd = -EBADF; + + assert(arg_action == ACTION_SHOW); + + (void) signal(SIGWINCH, columns_lines_cache_reset); + + r = acquire_journal(&c.journal); + if (r < 0) + return r; + + if (!journal_boot_has_effect(c.journal)) + return arg_compiled_pattern ? -ENOENT : 0; + + r = add_filters(c.journal, matches); + if (r < 0) + return r; + + r = seek_journal(&c); + if (r < 0) + return r; + + /* Opening the fd now means the first sd_journal_wait() will actually wait */ + if (arg_follow) { + poll_fd = sd_journal_get_fd(c.journal); + if (poll_fd == -EMFILE) { + log_warning_errno(poll_fd, "Insufficient watch descriptors available. Reverting to -n."); + arg_follow = false; + } else if (poll_fd == -EMEDIUMTYPE) + return log_error_errno(poll_fd, "The --follow switch is not supported in conjunction with reading from STDIN."); + else if (poll_fd < 0) + return log_error_errno(poll_fd, "Failed to get journal fd: %m"); + } + + if (!arg_follow) + pager_open(arg_pager_flags); + + if (!arg_quiet && (arg_lines != 0 || arg_follow) && DEBUG_LOGGING) { + usec_t start, end; + char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; + + r = sd_journal_get_cutoff_realtime_usec(c.journal, &start, &end); + if (r < 0) + return log_error_errno(r, "Failed to get cutoff: %m"); + if (r > 0) { + if (arg_follow) + printf("-- Journal begins at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start)); + else + printf("-- Journal begins at %s, ends at %s. --\n", + format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start), + format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end)); + } + } + + if (arg_follow) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + int sig; + + assert(poll_fd >= 0); + + r = setup_event(&c, poll_fd, &e); + if (r < 0) + return r; + + r = sd_event_loop(e); + if (r < 0) + return r; + sig = r; + + r = update_cursor(c.journal); + if (r < 0) + return r; + + /* re-send the original signal. */ + return sig; + } + + r = show(&c); + if (r < 0) + return r; + n_shown = r; + + if (n_shown == 0 && !arg_quiet) + printf("-- No entries --\n"); + + r = update_cursor(c.journal); + if (r < 0) + return r; + + if (arg_compiled_pattern && n_shown == 0) + /* --grep was used, no error was thrown, but the pattern didn't + * match anything. Let's mimic grep's behavior here and return + * a non-zero exit code, so journalctl --grep can be used + * in scripts and such */ + return -ENOENT; + + return 0; +} diff --git a/src/journal/journalctl-show.h b/src/journal/journalctl-show.h new file mode 100644 index 0000000..83f3bdd --- /dev/null +++ b/src/journal/journalctl-show.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int action_show(char **matches); diff --git a/src/journal/journalctl-util.c b/src/journal/journalctl-util.c new file mode 100644 index 0000000..32cdaf8 --- /dev/null +++ b/src/journal/journalctl-util.c @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "id128-util.h" +#include "journal-util.h" +#include "journalctl.h" +#include "journalctl-util.h" +#include "logs-show.h" +#include "rlimit-util.h" +#include "sigbus.h" +#include "terminal-util.h" + +char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { + assert(buf); + + if (arg_utc) + return format_timestamp_style(buf, l, t, TIMESTAMP_UTC); + + return format_timestamp(buf, l, t); +} + +int acquire_journal(sd_journal **ret) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(ret); + + /* Increase max number of open files if we can, we might needs this when browsing journal files, which might be + * split up into many files. */ + (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); + + sigbus_install(); + + if (arg_directory) + r = sd_journal_open_directory(&j, arg_directory, arg_journal_type | arg_journal_additional_open_flags); + else if (arg_root) + r = sd_journal_open_directory(&j, arg_root, arg_journal_type | arg_journal_additional_open_flags | SD_JOURNAL_OS_ROOT); + else if (arg_file_stdin) + r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, arg_journal_additional_open_flags); + else if (arg_file) + r = sd_journal_open_files(&j, (const char**) arg_file, arg_journal_additional_open_flags); + else if (arg_machine) + r = journal_open_machine(&j, arg_machine, arg_journal_additional_open_flags); + else + r = sd_journal_open_namespace( + &j, + arg_namespace, + (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | + arg_namespace_flags | arg_journal_type | arg_journal_additional_open_flags); + if (r < 0) + return log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); + + r = journal_access_check_and_warn(j, arg_quiet, + !(arg_journal_type == SD_JOURNAL_CURRENT_USER || arg_user_units)); + if (r < 0) + return r; + + *ret = TAKE_PTR(j); + return 0; +} + +bool journal_boot_has_effect(sd_journal *j) { + assert(j); + + if (arg_boot_offset != 0 && + sd_journal_has_runtime_files(j) > 0 && + sd_journal_has_persistent_files(j) == 0) { + log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found."); + return false; + } + + return true; +} + +int journal_acquire_boot(sd_journal *j) { + int r; + + assert(j); + + if (!arg_boot) { + /* Clear relevant field for safety. */ + arg_boot_id = SD_ID128_NULL; + arg_boot_offset = 0; + return 0; + } + + /* Take a shortcut and use the current boot_id, which we can do very quickly. + * We can do this only when the logs are coming from the current machine, + * so take the slow path if log location is specified. */ + if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) && + !arg_directory && !arg_file && !arg_file_stdin && !arg_root) { + r = id128_get_boot_for_machine(arg_machine, &arg_boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot ID%s%s: %m", + isempty(arg_machine) ? "" : " of container ", strempty(arg_machine)); + } else { + sd_id128_t boot_id; + + r = journal_find_boot(j, arg_boot_id, arg_boot_offset, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to find journal entry from the specified boot (%s%+i): %m", + sd_id128_is_null(arg_boot_id) ? "" : SD_ID128_TO_STRING(arg_boot_id), + arg_boot_offset); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENODATA), + "No journal boot entry found from the specified boot (%s%+i).", + sd_id128_is_null(arg_boot_id) ? "" : SD_ID128_TO_STRING(arg_boot_id), + arg_boot_offset); + + log_debug("Found boot %s for %s%+i", + SD_ID128_TO_STRING(boot_id), + sd_id128_is_null(arg_boot_id) ? "" : SD_ID128_TO_STRING(arg_boot_id), + arg_boot_offset); + + arg_boot_id = boot_id; + } + + return 1; +} diff --git a/src/journal/journalctl-util.h b/src/journal/journalctl-util.h new file mode 100644 index 0000000..ea3e568 --- /dev/null +++ b/src/journal/journalctl-util.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-journal.h" + +#include "time-util.h" + +char* format_timestamp_maybe_utc(char *buf, size_t l, usec_t t); +int acquire_journal(sd_journal **ret); +bool journal_boot_has_effect(sd_journal *j); +int journal_acquire_boot(sd_journal *j); diff --git a/src/journal/journalctl-varlink.c b/src/journal/journalctl-varlink.c new file mode 100644 index 0000000..89aed05 --- /dev/null +++ b/src/journal/journalctl-varlink.c @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "errno-util.h" +#include "journal-internal.h" +#include "journal-vacuum.h" +#include "journalctl.h" +#include "journalctl-util.h" +#include "journalctl-varlink.h" +#include "varlink.h" + +static int varlink_connect_journal(Varlink **ret) { + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; + const char *address; + int r; + + assert(ret); + + address = arg_namespace ? + strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") : + "/run/systemd/journal/io.systemd.journal"; + + r = varlink_connect_address(&vl, address); + if (r < 0) + return r; + + (void) varlink_set_description(vl, "journal"); + (void) varlink_set_relative_timeout(vl, USEC_INFINITY); + + *ret = TAKE_PTR(vl); + return 0; +} + +int action_flush_to_var(void) { + _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; + int r; + + assert(arg_action == ACTION_FLUSH); + + if (arg_machine || arg_namespace) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--flush is not supported in conjunction with %s.", + arg_machine ? "--machine=" : "--namespace="); + + if (access("/run/systemd/journal/flushed", F_OK) >= 0) + return 0; /* Already flushed, no need to contact journald */ + if (errno != ENOENT) + return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m"); + + r = varlink_connect_journal(&link); + if (r < 0) + return log_error_errno(r, "Failed to connect to Varlink socket: %m"); + + return varlink_call_and_log(link, "io.systemd.Journal.FlushToVar", /* parameters= */ NULL, /* ret_parameters= */ NULL); +} + +int action_relinquish_var(void) { + _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; + int r; + + assert(arg_action == ACTION_RELINQUISH_VAR); + + if (arg_machine || arg_namespace) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--(smart-)relinquish-var is not supported in conjunction with %s.", + arg_machine ? "--machine=" : "--namespace="); + + r = varlink_connect_journal(&link); + if (r < 0) + return log_error_errno(r, "Failed to connect to Varlink socket: %m"); + + return varlink_call_and_log(link, "io.systemd.Journal.RelinquishVar", /* parameters= */ NULL, /* ret_parameters= */ NULL); +} + +int action_rotate(void) { + _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; + int r; + + assert(IN_SET(arg_action, ACTION_ROTATE, ACTION_ROTATE_AND_VACUUM)); + + if (arg_machine) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--rotate is not supported in conjunction with --machine=."); + + r = varlink_connect_journal(&link); + if (r < 0) + return log_error_errno(r, "Failed to connect to Varlink socket: %m"); + + return varlink_call_and_log(link, "io.systemd.Journal.Rotate", /* parameters= */ NULL, /* ret_parameters= */ NULL); +} + +int action_vacuum(void) { + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + Directory *d; + int r, ret = 0; + + assert(IN_SET(arg_action, ACTION_VACUUM, ACTION_ROTATE_AND_VACUUM)); + + r = acquire_journal(&j); + if (r < 0) + return r; + + HASHMAP_FOREACH(d, j->directories_by_path) { + r = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to vacuum %s: %m", d->path)); + } + + return ret; +} + +int action_rotate_and_vacuum(void) { + int r; + + assert(arg_action == ACTION_ROTATE_AND_VACUUM); + + r = action_rotate(); + if (r < 0) + return r; + + return action_vacuum(); +} + +int action_sync(void) { + _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; + int r; + + assert(arg_action == ACTION_SYNC); + + if (arg_machine) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "--sync is not supported in conjunction with --machine=."); + + r = varlink_connect_journal(&link); + if (ERRNO_IS_NEG_DISCONNECT(r) && arg_namespace) + /* If the namespaced sd-journald instance was shut down due to inactivity, it should already + * be synchronized */ + return 0; + if (r < 0) + return log_error_errno(r, "Failed to connect to Varlink socket: %m"); + + return varlink_call_and_log(link, "io.systemd.Journal.Synchronize", /* parameters= */ NULL, /* ret_parameters= */ NULL); +} diff --git a/src/journal/journalctl-varlink.h b/src/journal/journalctl-varlink.h new file mode 100644 index 0000000..e10983a --- /dev/null +++ b/src/journal/journalctl-varlink.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int action_flush_to_var(void); +int action_relinquish_var(void); +int action_rotate(void); +int action_vacuum(void); +int action_rotate_and_vacuum(void); +int action_sync(void); diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 45ecc96..45173a6 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -1,85 +1,29 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-device.h" + #include "sd-journal.h" -#include "acl-util.h" -#include "alloc-util.h" #include "build.h" -#include "bus-error.h" -#include "bus-locator.h" -#include "bus-util.h" -#include "catalog.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" -#include "format-table.h" -#include "format-util.h" -#include "fs-util.h" -#include "fsprg.h" #include "glob-util.h" -#include "hostname-util.h" #include "id128-print.h" -#include "io-util.h" -#include "journal-def.h" -#include "journal-internal.h" -#include "journal-util.h" -#include "journal-vacuum.h" -#include "journal-verify.h" +#include "journalctl.h" +#include "journalctl-authenticate.h" +#include "journalctl-catalog.h" +#include "journalctl-misc.h" +#include "journalctl-show.h" +#include "journalctl-varlink.h" #include "locale-util.h" -#include "log.h" -#include "logs-show.h" #include "main-func.h" -#include "memory-util.h" -#include "memstream-util.h" -#include "missing_sched.h" -#include "mkdir.h" #include "mount-util.h" #include "mountpoint-util.h" -#include "nulstr-util.h" -#include "pager.h" #include "parse-argument.h" -#include "parse-util.h" -#include "path-util.h" -#include "pcre2-util.h" #include "pretty-print.h" -#include "qrcode-util.h" -#include "random-util.h" -#include "rlimit-util.h" -#include "set.h" -#include "sigbus.h" -#include "signal-util.h" #include "static-destruct.h" -#include "stdio-util.h" #include "string-table.h" -#include "strv.h" #include "syslog-util.h" -#include "terminal-util.h" -#include "tmpfile-util.h" -#include "unit-name.h" -#include "user-util.h" -#include "varlink.h" #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE) -#define PROCESS_INOTIFY_INTERVAL 1024 /* Every 1,024 messages processed */ enum { /* Special values for arg_lines */ @@ -87,65 +31,71 @@ enum { ARG_LINES_ALL = -1, }; -static OutputMode arg_output = OUTPUT_SHORT; -static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; -static bool arg_utc = false; -static bool arg_follow = false; -static bool arg_full = true; -static bool arg_all = false; -static PagerFlags arg_pager_flags = 0; -static int arg_lines = ARG_LINES_DEFAULT; -static bool arg_lines_oldest = false; -static bool arg_no_tail = false; -static bool arg_truncate_newline = false; -static bool arg_quiet = false; -static bool arg_merge = false; -static bool arg_boot = false; -static sd_id128_t arg_boot_id = {}; -static int arg_boot_offset = 0; -static bool arg_dmesg = false; -static bool arg_no_hostname = false; -static const char *arg_cursor = NULL; -static const char *arg_cursor_file = NULL; -static const char *arg_after_cursor = NULL; -static bool arg_show_cursor = false; -static const char *arg_directory = NULL; -static char **arg_file = NULL; -static bool arg_file_stdin = false; -static int arg_priorities = 0xFF; -static Set *arg_facilities = NULL; -static char *arg_verify_key = NULL; +JournalctlAction arg_action = ACTION_SHOW; +OutputMode arg_output = OUTPUT_SHORT; +JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +PagerFlags arg_pager_flags = 0; +bool arg_utc = false; +bool arg_follow = false; +bool arg_full = true; +bool arg_all = false; +int arg_lines = ARG_LINES_DEFAULT; +bool arg_lines_oldest = false; +bool arg_no_tail = false; +bool arg_truncate_newline = false; +bool arg_quiet = false; +bool arg_merge = false; +bool arg_boot = false; +sd_id128_t arg_boot_id = {}; +int arg_boot_offset = 0; +bool arg_dmesg = false; +bool arg_no_hostname = false; +const char *arg_cursor = NULL; +const char *arg_cursor_file = NULL; +const char *arg_after_cursor = NULL; +bool arg_show_cursor = false; +const char *arg_directory = NULL; +char **arg_file = NULL; +bool arg_file_stdin = false; +int arg_priorities = 0; +Set *arg_facilities = NULL; +char *arg_verify_key = NULL; #if HAVE_GCRYPT -static usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; -static bool arg_force = false; +usec_t arg_interval = DEFAULT_FSS_INTERVAL_USEC; +bool arg_force = false; #endif -static usec_t arg_since = 0, arg_until = 0; -static bool arg_since_set = false, arg_until_set = false; -static char **arg_syslog_identifier = NULL; -static char **arg_system_units = NULL; -static char **arg_user_units = NULL; -static const char *arg_field = NULL; -static bool arg_catalog = false; -static bool arg_reverse = false; -static int arg_journal_type = 0; -static int arg_namespace_flags = 0; -static char *arg_root = NULL; -static char *arg_image = NULL; -static const char *arg_machine = NULL; -static const char *arg_namespace = NULL; -static uint64_t arg_vacuum_size = 0; -static uint64_t arg_vacuum_n_files = 0; -static usec_t arg_vacuum_time = 0; -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; +usec_t arg_since = 0; +usec_t arg_until = 0; +bool arg_since_set = false; +bool arg_until_set = false; +char **arg_syslog_identifier = NULL; +char **arg_exclude_identifier = NULL; +char **arg_system_units = NULL; +char **arg_user_units = NULL; +const char *arg_field = NULL; +bool arg_catalog = false; +bool arg_reverse = false; +int arg_journal_type = 0; +int arg_journal_additional_open_flags = 0; +int arg_namespace_flags = 0; +char *arg_root = NULL; +char *arg_image = NULL; +const char *arg_machine = NULL; +const char *arg_namespace = NULL; +uint64_t arg_vacuum_size = 0; +uint64_t arg_vacuum_n_files = 0; +usec_t arg_vacuum_time = 0; +Set *arg_output_fields = NULL; +const char *arg_pattern = NULL; +pcre2_code *arg_compiled_pattern = NULL; +PatternCompileCase arg_case = PATTERN_COMPILE_CASE_AUTO; +static ImagePolicy *arg_image_policy = NULL; STATIC_DESTRUCTOR_REGISTER(arg_file, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_facilities, set_freep); STATIC_DESTRUCTOR_REGISTER(arg_verify_key, freep); STATIC_DESTRUCTOR_REGISTER(arg_syslog_identifier, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_exclude_identifier, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_system_units, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_user_units, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -154,115 +104,21 @@ 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, - ACTION_NEW_ID128, - ACTION_PRINT_HEADER, - ACTION_SETUP_KEYS, - ACTION_VERIFY, - ACTION_DISK_USAGE, - ACTION_LIST_CATALOG, - ACTION_DUMP_CATALOG, - ACTION_UPDATE_CATALOG, - ACTION_LIST_BOOTS, - ACTION_FLUSH, - ACTION_RELINQUISH_VAR, - ACTION_SYNC, - ACTION_ROTATE, - ACTION_VACUUM, - ACTION_ROTATE_AND_VACUUM, - ACTION_LIST_FIELDS, - ACTION_LIST_FIELD_NAMES, -} arg_action = ACTION_SHOW; - -static int add_matches_for_device(sd_journal *j, const char *devpath) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - sd_device *d = NULL; - struct stat st; - int r; - - assert(j); - assert(devpath); - - if (!path_startswith(devpath, "/dev/")) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Devpath does not start with /dev/"); - - if (stat(devpath, &st) < 0) - return log_error_errno(errno, "Couldn't stat file: %m"); - - r = sd_device_new_from_stat_rdev(&device, &st); - if (r < 0) - 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; - const char *subsys, *sysname, *devnode; - sd_device *parent; - - r = sd_device_get_subsystem(d, &subsys); - if (r < 0) - goto get_parent; - - r = sd_device_get_sysname(d, &sysname); - if (r < 0) - goto get_parent; - - match = strjoin("_KERNEL_DEVICE=+", subsys, ":", sysname); - if (!match) - return log_oom(); - - r = sd_journal_add_match(j, match, 0); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - if (sd_device_get_devname(d, &devnode) >= 0) { - _cleanup_free_ char *match1 = NULL; - - r = stat(devnode, &st); - if (r < 0) - return log_error_errno(r, "Failed to stat() device node \"%s\": %m", devnode); - - 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(); - - r = sd_journal_add_match(j, match1, 0); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } - -get_parent: - if (sd_device_get_parent(d, &parent) < 0) - break; - - d = parent; - } - - r = add_match_this_boot(j, arg_machine); - if (r < 0) - return log_error_errno(r, "Failed to add match for the current boot: %m"); - - return 0; -} - -static char *format_timestamp_maybe_utc(char *buf, size_t l, usec_t t) { - - if (arg_utc) - return format_timestamp_style(buf, l, t, TIMESTAMP_UTC); - - return format_timestamp(buf, l, t); -} - -static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset) { +static int parse_id_descriptor(const char *x, sd_id128_t *ret_id, int *ret_offset) { sd_id128_t id = SD_ID128_NULL; int off = 0, r; + assert(x); + assert(ret_id); + assert(ret_offset); + if (streq(x, "all")) { - *boot_id = SD_ID128_NULL; - *offset = 0; + *ret_id = SD_ID128_NULL; + *ret_offset = 0; return 0; - } else if (strlen(x) >= SD_ID128_STRING_MAX - 1) { + } + + if (strlen(x) >= SD_ID128_STRING_MAX - 1) { char *t; t = strndupa_safe(x, SD_ID128_STRING_MAX - 1); @@ -284,12 +140,8 @@ static int parse_boot_descriptor(const char *x, sd_id128_t *boot_id, int *offset return r; } - if (boot_id) - *boot_id = id; - - if (offset) - *offset = off; - + *ret_id = id; + *ret_offset = off; return 1; } @@ -328,10 +180,6 @@ default_noarg: return 0; } -static bool arg_lines_needs_seek_end(void) { - return arg_lines >= 0 && !arg_lines_oldest; -} - static int help_facilities(void) { if (!arg_quiet) puts("Available facilities:"); @@ -339,7 +187,7 @@ static int help_facilities(void) { for (int i = 0; i < LOG_NFACILITIES; i++) { _cleanup_free_ char *t = NULL; - if (log_facility_unshifted_to_string_alloc(i, &t)) + if (log_facility_unshifted_to_string_alloc(i, &t) < 0) return log_oom(); puts(t); } @@ -365,7 +213,7 @@ static int help(void) { " -M --machine=CONTAINER Operate on local container\n" " -m --merge Show entries from all available journals\n" " -D --directory=PATH Show journal files from directory\n" - " --file=PATH Show journal file\n" + " -i --file=PATH Show journal file\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" @@ -380,6 +228,8 @@ static int help(void) { " -u --unit=UNIT Show logs from the specified unit\n" " --user-unit=UNIT Show logs from the specified user unit\n" " -t --identifier=STRING Show entries with the specified syslog identifier\n" + " -T --exclude-identifier=STRING\n" + " Hide entries with the specified syslog identifier\n" " -p --priority=RANGE Show entries within the specified priority range\n" " --facility=FACILITY... Show entries with the specified facilities\n" " -g --grep=PATTERN Show entries with MESSAGE matching PATTERN\n" @@ -417,6 +267,7 @@ static int help(void) { " -N --fields List all field names currently used\n" " -F --field=FIELD List all values that a specified field takes\n" " --list-boots Show terse information about recorded boots\n" + " --list-namespaces Show list of journal namespaces\n" " --disk-usage Show total disk usage of all journal files\n" " --vacuum-size=BYTES Reduce disk usage below specified size\n" " --vacuum-files=INT Leave only the specified number of journal files\n" @@ -461,7 +312,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_HEADER, ARG_FACILITY, ARG_SETUP_KEYS, - ARG_FILE, ARG_INTERVAL, ARG_VERIFY, ARG_VERIFY_KEY, @@ -488,6 +338,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_HOSTNAME, ARG_OUTPUT_FIELDS, ARG_NAMESPACE, + ARG_LIST_NAMESPACES, }; static const struct option options[] = { @@ -514,12 +365,13 @@ static int parse_argv(int argc, char *argv[]) { { "system", no_argument, NULL, ARG_SYSTEM }, { "user", no_argument, NULL, ARG_USER }, { "directory", required_argument, NULL, 'D' }, - { "file", required_argument, NULL, ARG_FILE }, + { "file", required_argument, NULL, 'i' }, { "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' }, + { "exclude-identifier", required_argument, NULL, 'T' }, { "priority", required_argument, NULL, 'p' }, { "facility", required_argument, NULL, ARG_FACILITY }, { "grep", required_argument, NULL, 'g' }, @@ -557,6 +409,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-hostname", no_argument, NULL, ARG_NO_HOSTNAME }, { "output-fields", required_argument, NULL, ARG_OUTPUT_FIELDS }, { "namespace", required_argument, NULL, ARG_NAMESPACE }, + { "list-namespaces", no_argument, NULL, ARG_LIST_NAMESPACES }, {} }; @@ -565,7 +418,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:u:NF:xrM:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hefo:aln::qmb::kD:p:g:c:S:U:t:T:u:NF:xrM:i:", options, NULL)) >= 0) switch (c) { @@ -666,19 +519,16 @@ static int parse_argv(int argc, char *argv[]) { arg_boot_offset = 0; if (optarg) { - r = parse_boot_descriptor(optarg, &arg_boot_id, &arg_boot_offset); + r = parse_id_descriptor(optarg, &arg_boot_id, &arg_boot_offset); if (r < 0) return log_error_errno(r, "Failed to parse boot descriptor '%s'", optarg); arg_boot = r; - /* Hmm, no argument? Maybe the next - * word on the command line is - * supposed to be the argument? Let's - * see if there is one and is parsable - * as a boot descriptor... */ } else if (optind < argc) { - r = parse_boot_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); + /* Hmm, no argument? Maybe the next word on the command line is supposed to be the + * argument? Let's see if there is one and is parsable as a boot descriptor... */ + r = parse_id_descriptor(argv[optind], &arg_boot_id, &arg_boot_offset); if (r >= 0) { arg_boot = r; optind++; @@ -723,11 +573,15 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_LIST_NAMESPACES: + arg_action = ACTION_LIST_NAMESPACES; + break; + case 'D': arg_directory = optarg; break; - case ARG_FILE: + case 'i': if (streq(optarg, "-")) /* An undocumented feature: we can read journal files from STDIN. We don't document * this though, since after all we only support this for mmap-able, seekable files, and @@ -959,6 +813,12 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + case 'T': + r = strv_extend(&arg_exclude_identifier, optarg); + if (r < 0) + return log_oom(); + break; + case 'u': r = strv_extend(&arg_system_units, optarg); if (r < 0) @@ -1081,7 +941,7 @@ static int parse_argv(int argc, char *argv[]) { arg_boot_offset = 0; } - if (!!arg_directory + !!arg_file + !!arg_machine + !!arg_root + !!arg_image > 1) + if (!!arg_directory + !!arg_file + arg_file_stdin + !!arg_machine + !!arg_root + !!arg_image > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify at most one of -D/--directory=, --file=, -M/--machine=, --root=, --image=."); @@ -1089,15 +949,15 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--since= must be before --until=."); - if (!!arg_cursor + !!arg_after_cursor + !!arg_since_set > 1) + if (!!arg_cursor + !!arg_after_cursor + !!arg_cursor_file + !!arg_since_set > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Please specify only one of --since=, --cursor=, and --after-cursor=."); + "Please specify only one of --since=, --cursor=, --cursor-file=, and --after-cursor=."); if (arg_follow && arg_reverse) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --reverse or --follow, not both."); - if (arg_lines >= 0 && arg_lines_oldest && (arg_reverse || arg_follow)) + if (arg_action == ACTION_SHOW && arg_lines >= 0 && arg_lines_oldest && (arg_reverse || arg_follow)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--lines=+N is unsupported when --reverse or --follow is specified."); @@ -1135,1499 +995,109 @@ static int parse_argv(int argc, char *argv[]) { arg_reverse = true; } - return 1; -} - -static int add_matches(sd_journal *j, char **args) { - bool have_term = false; - - assert(j); - - STRV_FOREACH(i, args) { - int r; - - if (streq(*i, "+")) { - if (!have_term) - break; - r = sd_journal_add_disjunction(j); - have_term = false; - - } else if (path_is_absolute(*i)) { - _cleanup_free_ char *p = NULL, *t = NULL, *t2 = NULL, *interpreter = NULL; - struct stat st; - - r = chase(*i, NULL, CHASE_TRAIL_SLASH, &p, NULL); - if (r < 0) - return log_error_errno(r, "Couldn't canonicalize path: %m"); - - if (lstat(p, &st) < 0) - return log_error_errno(errno, "Couldn't stat file: %m"); - - if (S_ISREG(st.st_mode) && (0111 & st.st_mode)) { - if (executable_is_script(p, &interpreter) > 0) { - _cleanup_free_ char *comm = NULL; - - r = path_extract_filename(p, &comm); - if (r < 0) - return log_error_errno(r, "Failed to extract filename of '%s': %m", p); - - t = strjoin("_COMM=", strshorten(comm, TASK_COMM_LEN-1)); - if (!t) - return log_oom(); - - /* Append _EXE only if the interpreter is not a link. - Otherwise, it might be outdated often. */ - if (lstat(interpreter, &st) == 0 && !S_ISLNK(st.st_mode)) { - t2 = strjoin("_EXE=", interpreter); - if (!t2) - return log_oom(); - } - } else { - t = strjoin("_EXE=", p); - if (!t) - return log_oom(); - } - - r = sd_journal_add_match(j, t, 0); - - if (r >=0 && t2) - r = sd_journal_add_match(j, t2, 0); - - } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { - r = add_matches_for_device(j, p); - if (r < 0) - return r; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "File is neither a device node, nor regular file, nor executable: %s", - *i); - - have_term = true; - } else { - r = sd_journal_add_match(j, *i, 0); - have_term = true; - } - - if (r < 0) - return log_error_errno(r, "Failed to add match '%s': %m", *i); - } - - if (!strv_isempty(args) && !have_term) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "\"+\" can only be used between terms"); - - return 0; -} - -static int list_boots(sd_journal *j) { - _cleanup_(table_unrefp) Table *table = NULL; - _cleanup_free_ BootId *boots = NULL; - size_t n_boots; - int r; - - assert(j); - - r = journal_get_boots(j, &boots, &n_boots); - if (r < 0) - return log_error_errno(r, "Failed to determine boots: %m"); - if (r == 0) - return 0; - - table = table_new("idx", "boot id", "first entry", "last entry"); - if (!table) - return log_oom(); - - if (arg_full) - table_set_width(table, 0); - - r = table_set_json_field_name(table, 0, "index"); - if (r < 0) - return log_error_errno(r, "Failed to set JSON field name of column 0: %m"); - - (void) table_set_sort(table, (size_t) 0); - (void) table_set_reverse(table, 0, arg_reverse); - - FOREACH_ARRAY(i, boots, n_boots) { - r = table_add_many(table, - TABLE_INT, (int)(i - boots) - (int) n_boots + 1, - TABLE_SET_ALIGN_PERCENT, 100, - TABLE_ID128, i->id, - TABLE_TIMESTAMP, i->first_usec, - TABLE_TIMESTAMP, i->last_usec); - if (r < 0) - return table_log_add_error(r); - } - - r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, !arg_quiet); - if (r < 0) - return table_log_print_error(r); - - return 0; -} - -static int add_boot(sd_journal *j) { - int r; - - assert(j); - - if (!arg_boot) - return 0; - - /* Take a shortcut and use the current boot_id, which we can do very quickly. - * We can do this only when we logs are coming from the current machine, - * so take the slow path if log location is specified. */ - if (arg_boot_offset == 0 && sd_id128_is_null(arg_boot_id) && - !arg_directory && !arg_file && !arg_root) - return add_match_this_boot(j, arg_machine); - - if (sd_id128_is_null(arg_boot_id)) { - r = journal_find_boot_by_offset(j, arg_boot_offset, &arg_boot_id); - if (r < 0) - return log_error_errno(r, "Failed to find journal entry from the specified boot offset (%+i): %m", - arg_boot_offset); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), - "No journal boot entry found from the specified boot offset (%+i).", - arg_boot_offset); - } else { - r = journal_find_boot_by_id(j, arg_boot_id); - if (r < 0) - return log_error_errno(r, "Failed to find journal entry from the specified boot ID (%s): %m", - SD_ID128_TO_STRING(arg_boot_id)); - if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENODATA), - "No journal boot entry found from the specified boot ID (%s).", - SD_ID128_TO_STRING(arg_boot_id)); - } - - r = add_match_boot_id(j, arg_boot_id); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - -static int add_dmesg(sd_journal *j) { - int r; - assert(j); - - if (!arg_dmesg) - return 0; - - r = sd_journal_add_match(j, "_TRANSPORT=kernel", - STRLEN("_TRANSPORT=kernel")); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); + if (!arg_follow) + arg_journal_additional_open_flags = SD_JOURNAL_ASSUME_IMMUTABLE; - return 0; + return 1; } -static int get_possible_units( - sd_journal *j, - const char *fields, - char **patterns, - Set **units) { - - _cleanup_set_free_free_ Set *found = NULL; +static int run(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_freep) char *mounted_dir = NULL; int r; - found = set_new(&string_hash_ops); - if (!found) - return -ENOMEM; - - NULSTR_FOREACH(field, fields) { - const void *data; - size_t size; - - r = sd_journal_query_unique(j, field); - if (r < 0) - return r; - - SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { - char *eq; - size_t prefix; - _cleanup_free_ char *u = NULL; - - eq = memchr(data, '=', size); - if (eq) - prefix = eq - (char*) data + 1; - else - prefix = 0; - - u = strndup((char*) data + prefix, size - prefix); - if (!u) - return -ENOMEM; - - STRV_FOREACH(pattern, patterns) - if (fnmatch(*pattern, u, FNM_NOESCAPE) == 0) { - log_debug("Matched %s with pattern %s=%s", u, field, *pattern); - - r = set_consume(found, u); - u = NULL; - if (r < 0 && r != -EEXIST) - return r; - - break; - } - } - } - - *units = TAKE_PTR(found); - - return 0; -} - -/* This list is supposed to return the superset of unit names - * possibly matched by rules added with add_matches_for_unit... */ -#define SYSTEM_UNITS \ - "_SYSTEMD_UNIT\0" \ - "COREDUMP_UNIT\0" \ - "UNIT\0" \ - "OBJECT_SYSTEMD_UNIT\0" \ - "_SYSTEMD_SLICE\0" - -/* ... and add_matches_for_user_unit */ -#define USER_UNITS \ - "_SYSTEMD_USER_UNIT\0" \ - "USER_UNIT\0" \ - "COREDUMP_USER_UNIT\0" \ - "OBJECT_SYSTEMD_USER_UNIT\0" \ - "_SYSTEMD_USER_SLICE\0" - -static int add_units(sd_journal *j) { - _cleanup_strv_free_ char **patterns = NULL; - int r, count = 0; - - assert(j); - - STRV_FOREACH(i, arg_system_units) { - _cleanup_free_ char *u = NULL; - - r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); - if (r < 0) - return r; - - if (string_is_glob(u)) { - r = strv_push(&patterns, u); - if (r < 0) - return r; - u = NULL; - } else { - r = add_matches_for_unit(j, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } - - if (!strv_isempty(patterns)) { - _cleanup_set_free_free_ Set *units = NULL; - char *u; - - r = get_possible_units(j, SYSTEM_UNITS, patterns, &units); - if (r < 0) - return r; + setlocale(LC_ALL, ""); + log_setup(); - SET_FOREACH(u, units) { - r = add_matches_for_unit(j, u); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } + r = parse_argv(argc, argv); + if (r <= 0) + return r; - patterns = strv_free(patterns); + char **args = strv_skip(argv, optind); - STRV_FOREACH(i, arg_user_units) { - _cleanup_free_ char *u = NULL; + if (arg_image) { + assert(!arg_root); - r = unit_name_mangle(*i, UNIT_NAME_MANGLE_GLOB | (arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN), &u); + r = mount_image_privately_interactively( + arg_image, + arg_image_policy, + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_RELAX_VAR_CHECK | + (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS : DISSECT_IMAGE_READ_ONLY) | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, + &mounted_dir, + /* ret_dir_fd= */ NULL, + &loop_device); if (r < 0) return r; - if (string_is_glob(u)) { - r = strv_push(&patterns, u); - if (r < 0) - return r; - u = NULL; - } else { - r = add_matches_for_user_unit(j, u, getuid()); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } + arg_root = strdup(mounted_dir); + if (!arg_root) + return log_oom(); } - if (!strv_isempty(patterns)) { - _cleanup_set_free_free_ Set *units = NULL; - char *u; - - r = get_possible_units(j, USER_UNITS, patterns, &units); - if (r < 0) - return r; + switch (arg_action) { - SET_FOREACH(u, units) { - r = add_matches_for_user_unit(j, u, getuid()); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; - count++; - } - } + case ACTION_SHOW: + return action_show(args); - /* Complain if the user request matches but nothing whatsoever was - * found, since otherwise everything would be matched. */ - if (!(strv_isempty(arg_system_units) && strv_isempty(arg_user_units)) && count == 0) - return -ENODATA; + case ACTION_NEW_ID128: + return id128_print_new(ID128_PRINT_PRETTY); - r = sd_journal_add_conjunction(j); - if (r < 0) - return r; + case ACTION_SETUP_KEYS: + return action_setup_keys(); - return 0; -} + case ACTION_LIST_CATALOG: + case ACTION_DUMP_CATALOG: + return action_list_catalog(args); -static int add_priorities(sd_journal *j) { - char match[] = "PRIORITY=0"; - int i, r; - assert(j); + case ACTION_UPDATE_CATALOG: + return action_update_catalog(); - if (arg_priorities == 0xFF) - return 0; + case ACTION_PRINT_HEADER: + return action_print_header(); - for (i = LOG_EMERG; i <= LOG_DEBUG; i++) - if (arg_priorities & (1 << i)) { - match[sizeof(match)-2] = '0' + i; + case ACTION_VERIFY: + return action_verify(); - r = sd_journal_add_match(j, match, strlen(match)); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } + case ACTION_DISK_USAGE: + return action_disk_usage(); - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); + case ACTION_LIST_BOOTS: + return action_list_boots(); - return 0; -} + case ACTION_LIST_FIELDS: + return action_list_fields(); -static int add_facilities(sd_journal *j) { - void *p; - int r; + case ACTION_LIST_FIELD_NAMES: + return action_list_field_names(); - SET_FOREACH(p, arg_facilities) { - char match[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; + case ACTION_LIST_NAMESPACES: + return action_list_namespaces(); - xsprintf(match, "SYSLOG_FACILITY=%d", PTR_TO_INT(p)); + case ACTION_FLUSH: + return action_flush_to_var(); - r = sd_journal_add_match(j, match, strlen(match)); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } + case ACTION_RELINQUISH_VAR: + return action_relinquish_var(); - return 0; -} + case ACTION_SYNC: + return action_sync(); -static int add_syslog_identifier(sd_journal *j) { - int r; + case ACTION_ROTATE: + return action_rotate(); - assert(j); + case ACTION_VACUUM: + return action_vacuum(); - STRV_FOREACH(i, arg_syslog_identifier) { - _cleanup_free_ char *u = NULL; + case ACTION_ROTATE_AND_VACUUM: + return action_rotate_and_vacuum(); - u = strjoin("SYSLOG_IDENTIFIER=", *i); - if (!u) - return -ENOMEM; - r = sd_journal_add_match(j, u, 0); - if (r < 0) - return r; - r = sd_journal_add_disjunction(j); - if (r < 0) - return r; + default: + assert_not_reached(); } - - r = sd_journal_add_conjunction(j); - if (r < 0) - return r; - - return 0; -} - -#if HAVE_GCRYPT -static int format_journal_url( - const void *seed, - size_t seed_size, - uint64_t start, - uint64_t interval, - const char *hn, - sd_id128_t machine, - bool full, - char **ret_url) { - - _cleanup_(memstream_done) MemStream m = {}; - FILE *f; - - assert(seed); - assert(seed_size > 0); - - f = memstream_init(&m); - if (!f) - return -ENOMEM; - - if (full) - fputs("fss://", f); - - for (size_t i = 0; i < seed_size; i++) { - if (i > 0 && i % 3 == 0) - fputc('-', f); - fprintf(f, "%02x", ((uint8_t*) seed)[i]); - } - - fprintf(f, "/%"PRIx64"-%"PRIx64, start, interval); - - if (full) { - fprintf(f, "?machine=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine)); - if (hn) - fprintf(f, ";hostname=%s", hn); - } - - return memstream_finalize(&m, ret_url, NULL); -} -#endif - -static int setup_keys(void) { -#if HAVE_GCRYPT - size_t mpk_size, seed_size, state_size; - _cleanup_(unlink_and_freep) char *k = NULL; - _cleanup_free_ char *p = NULL; - uint8_t *mpk, *seed, *state; - _cleanup_close_ int fd = -EBADF; - sd_id128_t machine, boot; - struct stat st; - uint64_t n; - int r; - - r = stat("/var/log/journal", &st); - if (r < 0 && !IN_SET(errno, ENOENT, ENOTDIR)) - return log_error_errno(errno, "stat(\"%s\") failed: %m", "/var/log/journal"); - - if (r < 0 || !S_ISDIR(st.st_mode)) { - log_error("%s is not a directory, must be using persistent logging for FSS.", - "/var/log/journal"); - return r < 0 ? -errno : -ENOTDIR; - } - - r = sd_id128_get_machine(&machine); - if (r < 0) - return log_error_errno(r, "Failed to get machine ID: %m"); - - r = sd_id128_get_boot(&boot); - if (r < 0) - return log_error_errno(r, "Failed to get boot ID: %m"); - - if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss", - SD_ID128_FORMAT_VAL(machine)) < 0) - return log_oom(); - - if (arg_force) { - r = unlink(p); - if (r < 0 && errno != ENOENT) - return log_error_errno(errno, "unlink(\"%s\") failed: %m", p); - } else if (access(p, F_OK) >= 0) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), - "Sealing key file %s exists already. Use --force to recreate.", p); - - if (asprintf(&k, "/var/log/journal/" SD_ID128_FORMAT_STR "/fss.tmp.XXXXXX", - SD_ID128_FORMAT_VAL(machine)) < 0) - return log_oom(); - - mpk_size = FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR); - mpk = alloca_safe(mpk_size); - - seed_size = FSPRG_RECOMMENDED_SEEDLEN; - seed = alloca_safe(seed_size); - - state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); - state = alloca_safe(state_size); - - log_info("Generating seed..."); - r = crypto_random_bytes(seed, seed_size); - if (r < 0) - return log_error_errno(r, "Failed to acquire random seed: %m"); - - log_info("Generating key pair..."); - FSPRG_GenMK(NULL, mpk, seed, seed_size, FSPRG_RECOMMENDED_SECPAR); - - log_info("Generating sealing key..."); - FSPRG_GenState0(state, mpk, seed, seed_size); - - assert(arg_interval > 0); - - n = now(CLOCK_REALTIME); - n /= arg_interval; - - safe_close(fd); - fd = mkostemp_safe(k); - if (fd < 0) - return log_error_errno(fd, "Failed to open %s: %m", k); - - r = chattr_secret(fd, CHATTR_WARN_UNSUPPORTED_FLAGS); - if (r < 0) - log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, - r, "Failed to set file attributes on '%s', ignoring: %m", k); - - struct FSSHeader h = { - .signature = { 'K', 'S', 'H', 'H', 'R', 'H', 'L', 'P' }, - .machine_id = machine, - .boot_id = boot, - .header_size = htole64(sizeof(h)), - .start_usec = htole64(n * arg_interval), - .interval_usec = htole64(arg_interval), - .fsprg_secpar = htole16(FSPRG_RECOMMENDED_SECPAR), - .fsprg_state_size = htole64(state_size), - }; - - r = loop_write(fd, &h, sizeof(h)); - if (r < 0) - return log_error_errno(r, "Failed to write header: %m"); - - r = loop_write(fd, state, state_size); - if (r < 0) - return log_error_errno(r, "Failed to write state: %m"); - - if (rename(k, p) < 0) - return log_error_errno(errno, "Failed to link file: %m"); - - k = mfree(k); - - _cleanup_free_ char *hn = NULL, *key = NULL; - - r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, false, &key); - if (r < 0) - return r; - - if (on_tty()) { - hn = gethostname_malloc(); - if (hn) - hostname_cleanup(hn); - - fprintf(stderr, - "\nNew keys have been generated for host %s%s" SD_ID128_FORMAT_STR ".\n" - "\n" - "The %ssecret sealing key%s has been written to the following local file.\n" - "This key file is automatically updated when the sealing key is advanced.\n" - "It should not be used on multiple hosts.\n" - "\n" - "\t%s\n" - "\n" - "The sealing key is automatically changed every %s.\n" - "\n" - "Please write down the following %ssecret verification key%s. It should be stored\n" - "in a safe location and should not be saved locally on disk.\n" - "\n\t%s", - strempty(hn), hn ? "/" : "", - SD_ID128_FORMAT_VAL(machine), - ansi_highlight(), ansi_normal(), - p, - FORMAT_TIMESPAN(arg_interval, 0), - ansi_highlight(), ansi_normal(), - ansi_highlight_red()); - fflush(stderr); - } - - puts(key); - - if (on_tty()) { - fprintf(stderr, "%s", ansi_normal()); -#if HAVE_QRENCODE - _cleanup_free_ char *url = NULL; - r = format_journal_url(seed, seed_size, n, arg_interval, hn, machine, true, &url); - if (r < 0) - return r; - - (void) print_qrcode(stderr, - "To transfer the verification key to your phone scan the QR code below", - url); -#endif - } - - return 0; -#else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Forward-secure sealing not available."); -#endif -} - -static int verify(sd_journal *j, bool verbose) { - int r = 0; - JournalFile *f; - - assert(j); - - log_show_color(true); - - ORDERED_HASHMAP_FOREACH(f, j->files) { - int k; - usec_t first = 0, validated = 0, last = 0; - -#if HAVE_GCRYPT - if (!arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) - log_notice("Journal file %s has sealing enabled but verification key has not been passed using --verify-key=.", f->path); -#endif - - k = journal_file_verify(f, arg_verify_key, &first, &validated, &last, verbose); - if (k == -EINVAL) - /* If the key was invalid give up right-away. */ - return k; - else if (k < 0) - r = log_warning_errno(k, "FAIL: %s (%m)", f->path); - else { - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; - log_full(verbose ? LOG_INFO : LOG_DEBUG, "PASS: %s", f->path); - - if (arg_verify_key && JOURNAL_HEADER_SEALED(f->header)) { - if (validated > 0) { - log_full(verbose ? LOG_INFO : LOG_DEBUG, - "=> Validated from %s to %s, final %s entries not sealed.", - format_timestamp_maybe_utc(a, sizeof(a), first), - format_timestamp_maybe_utc(b, sizeof(b), validated), - FORMAT_TIMESPAN(last > validated ? last - validated : 0, 0)); - } else if (last > 0) - log_full(verbose ? LOG_INFO : LOG_DEBUG, - "=> No sealing yet, %s of entries not sealed.", - FORMAT_TIMESPAN(last - first, 0)); - else - log_full(verbose ? LOG_INFO : LOG_DEBUG, - "=> No sealing yet, no entries in file."); - } - } - } - - return r; -} - -static int simple_varlink_call(const char *option, const char *method) { - _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL; - const char *error, *fn; - int r; - - if (arg_machine) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "%s is not supported in conjunction with --machine=.", option); - - fn = arg_namespace ? - strjoina("/run/systemd/journal.", arg_namespace, "/io.systemd.journal") : - "/run/systemd/journal/io.systemd.journal"; - - r = varlink_connect_address(&link, fn); - if (r < 0) - return log_error_errno(r, "Failed to connect to %s: %m", fn); - - (void) varlink_set_description(link, "journal"); - (void) varlink_set_relative_timeout(link, USEC_INFINITY); - - r = varlink_call(link, method, NULL, NULL, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to execute varlink call: %m"); - if (error) - return log_error_errno(SYNTHETIC_ERRNO(ENOANO), - "Failed to execute varlink call: %s", error); - - return 0; -} - -static int flush_to_var(void) { - if (access("/run/systemd/journal/flushed", F_OK) >= 0) - return 0; /* Already flushed, no need to contact journald */ - if (errno != ENOENT) - return log_error_errno(errno, "Unable to check for existence of /run/systemd/journal/flushed: %m"); - - return simple_varlink_call("--flush", "io.systemd.Journal.FlushToVar"); -} - -static int relinquish_var(void) { - return simple_varlink_call("--relinquish-var/--smart-relinquish-var", "io.systemd.Journal.RelinquishVar"); -} - -static int rotate(void) { - return simple_varlink_call("--rotate", "io.systemd.Journal.Rotate"); -} - -static int sync_journal(void) { - return simple_varlink_call("--sync", "io.systemd.Journal.Synchronize"); -} - -static int action_list_fields(sd_journal *j) { - const void *data; - size_t size; - int r, n_shown = 0; - - assert(arg_field); - - r = sd_journal_set_data_threshold(j, 0); - if (r < 0) - return log_error_errno(r, "Failed to unset data size threshold: %m"); - - r = sd_journal_query_unique(j, arg_field); - if (r < 0) - return log_error_errno(r, "Failed to query unique data objects: %m"); - - SD_JOURNAL_FOREACH_UNIQUE(j, data, size) { - const void *eq; - - if (arg_lines >= 0 && n_shown >= arg_lines) - break; - - eq = memchr(data, '=', size); - if (eq) - printf("%.*s\n", (int) (size - ((const uint8_t*) eq - (const uint8_t*) data + 1)), (const char*) eq + 1); - else - printf("%.*s\n", (int) size, (const char*) data); - - n_shown++; - } - - return 0; -} - -static int update_cursor(sd_journal *j) { - _cleanup_free_ char *cursor = NULL; - int r; - - assert(j); - - if (!arg_show_cursor && !arg_cursor_file) - return 0; - - r = sd_journal_get_cursor(j, &cursor); - if (r == -EADDRNOTAVAIL) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - if (arg_show_cursor) - printf("-- cursor: %s\n", cursor); - - if (arg_cursor_file) { - r = write_string_file(arg_cursor_file, cursor, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); - if (r < 0) - return log_error_errno(r, "Failed to write new cursor to %s: %m", arg_cursor_file); - } - - return 0; -} - -typedef struct Context { - sd_journal *journal; - bool has_cursor; - bool need_seek; - bool since_seeked; - bool ellipsized; - bool previous_boot_id_valid; - sd_id128_t previous_boot_id; - sd_id128_t previous_boot_id_output; - dual_timestamp previous_ts_output; -} Context; - -static int show(Context *c) { - sd_journal *j; - int r, n_shown = 0; - - assert(c); - - j = ASSERT_PTR(c->journal); - - while (arg_lines < 0 || n_shown < arg_lines || arg_follow) { - int flags; - size_t highlight[2] = {}; - - if (c->need_seek) { - r = sd_journal_step_one(j, !arg_reverse); - if (r < 0) - return log_error_errno(r, "Failed to iterate through journal: %m"); - if (r == 0) - break; - } - - if (arg_until_set && !arg_reverse && (arg_lines < 0 || arg_since_set || c->has_cursor)) { - /* If --lines= is set, we usually rely on the n_shown to tell us when to stop. - * However, if --since= or one of the cursor argument is set too, we may end up - * having less than --lines= to output. In this case let's also check if the entry - * is in range. */ - - usec_t usec; - - r = sd_journal_get_realtime_usec(j, &usec); - if (r < 0) - return log_error_errno(r, "Failed to determine timestamp: %m"); - if (usec > arg_until) - break; - } - - if (arg_since_set && (arg_reverse || !c->since_seeked)) { - usec_t usec; - - r = sd_journal_get_realtime_usec(j, &usec); - if (r < 0) - return log_error_errno(r, "Failed to determine timestamp: %m"); - - if (usec < arg_since) { - if (arg_reverse) - break; /* Reached the earliest entry */ - - /* arg_lines >= 0 (!since_seeked): - * We jumped arg_lines back and it seems to be too much */ - r = sd_journal_seek_realtime_usec(j, arg_since); - if (r < 0) - return log_error_errno(r, "Failed to seek to date: %m"); - c->since_seeked = true; - - c->need_seek = true; - continue; - } - c->since_seeked = true; /* We're surely within the range of --since now */ - } - - if (!arg_merge && !arg_quiet) { - sd_id128_t boot_id; - - r = sd_journal_get_monotonic_usec(j, NULL, &boot_id); - if (r >= 0) { - if (c->previous_boot_id_valid && - !sd_id128_equal(boot_id, c->previous_boot_id)) - printf("%s-- Boot "SD_ID128_FORMAT_STR" --%s\n", - ansi_highlight(), SD_ID128_FORMAT_VAL(boot_id), ansi_normal()); - - c->previous_boot_id = boot_id; - c->previous_boot_id_valid = true; - } - } - - if (arg_compiled_pattern) { - const void *message; - size_t len; - - r = sd_journal_get_data(j, "MESSAGE", &message, &len); - if (r < 0) { - if (r == -ENOENT) { - c->need_seek = true; - continue; - } - - return log_error_errno(r, "Failed to get MESSAGE field: %m"); - } - - assert_se(message = startswith(message, "MESSAGE=")); - - r = pattern_matches_and_log(arg_compiled_pattern, message, - len - strlen("MESSAGE="), highlight); - if (r < 0) - return r; - if (r == 0) { - c->need_seek = true; - continue; - } - } - - flags = - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - arg_catalog * OUTPUT_CATALOG | - arg_utc * OUTPUT_UTC | - arg_truncate_newline * OUTPUT_TRUNCATE_NEWLINE | - arg_no_hostname * OUTPUT_NO_HOSTNAME; - - r = show_journal_entry(stdout, j, arg_output, 0, flags, - arg_output_fields, highlight, &c->ellipsized, - &c->previous_ts_output, &c->previous_boot_id_output); - c->need_seek = true; - if (r == -EADDRNOTAVAIL) - break; - if (r < 0) - return r; - - n_shown++; - - /* If journalctl take a long time to process messages, and during that time journal file - * rotation occurs, a journalctl client will keep those rotated files open until it calls - * sd_journal_process(), which typically happens as a result of calling sd_journal_wait() below - * in the "following" case. By periodically calling sd_journal_process() during the processing - * loop we shrink the window of time a client instance has open file descriptors for rotated - * (deleted) journal files. */ - if ((n_shown % PROCESS_INOTIFY_INTERVAL) == 0) { - r = sd_journal_process(j); - if (r < 0) - return log_error_errno(r, "Failed to process inotify events: %m"); - } - } - - return n_shown; -} - -static int show_and_fflush(Context *c, sd_event_source *s) { - int r; - - assert(c); - assert(s); - - r = show(c); - if (r < 0) - return sd_event_exit(sd_event_source_get_event(s), r); - - fflush(stdout); - return 0; -} - -static int on_journal_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Context *c = ASSERT_PTR(userdata); - int r; - - assert(s); - - r = sd_journal_process(c->journal); - if (r < 0) { - log_error_errno(r, "Failed to process journal events: %m"); - return sd_event_exit(sd_event_source_get_event(s), r); - } - - return show_and_fflush(c, s); -} - -static int on_first_event(sd_event_source *s, void *userdata) { - return show_and_fflush(userdata, s); -} - -static int on_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - assert(si); - assert(IN_SET(si->ssi_signo, SIGTERM, SIGINT)); - - return sd_event_exit(sd_event_source_get_event(s), si->ssi_signo); -} - -static int setup_event(Context *c, int fd, sd_event **ret) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - int r; - - assert(arg_follow); - assert(c); - assert(fd >= 0); - assert(ret); - - r = sd_event_default(&e); - if (r < 0) - return log_error_errno(r, "Failed to allocate sd_event object: %m"); - - (void) sd_event_add_signal(e, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); - (void) sd_event_add_signal(e, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_signal, NULL); - - r = sd_event_add_io(e, NULL, fd, EPOLLIN, &on_journal_event, c); - if (r < 0) - return log_error_errno(r, "Failed to add io event source for journal: %m"); - - /* Also keeps an eye on STDOUT, and exits as soon as we see a POLLHUP on that, i.e. when it is closed. */ - r = sd_event_add_io(e, NULL, STDOUT_FILENO, EPOLLHUP|EPOLLERR, NULL, INT_TO_PTR(-ECANCELED)); - if (r == -EPERM) - /* Installing an epoll watch on a regular file doesn't work and fails with EPERM. Which is - * totally OK, handle it gracefully. epoll_ctl() documents EPERM as the error returned when - * the specified fd doesn't support epoll, hence it's safe to check for that. */ - log_debug_errno(r, "Unable to install EPOLLHUP watch on stderr, not watching for hangups."); - else if (r < 0) - return log_error_errno(r, "Failed to add io event source for stdout: %m"); - - if (arg_lines != 0 || arg_since_set) { - r = sd_event_add_defer(e, NULL, on_first_event, c); - if (r < 0) - return log_error_errno(r, "Failed to add defer event source: %m"); - } - - *ret = TAKE_PTR(e); - return 0; -} - -static int run(int argc, char *argv[]) { - bool need_seek = false, since_seeked = false, after_cursor = false; - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_freep) char *mounted_dir = NULL; - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - _cleanup_free_ char *cursor_from_file = NULL; - const char *cursor = NULL; - int n_shown, r, poll_fd = -EBADF; - - setlocale(LC_ALL, ""); - log_setup(); - - /* Increase max number of open files if we can, we might needs this when browsing journal files, which might be - * split up into many files. */ - (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); - - r = parse_argv(argc, argv); - if (r <= 0) - return r; - - if (arg_image) { - assert(!arg_root); - - r = mount_image_privately_interactively( - arg_image, - arg_image_policy, - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_VALIDATE_OS | - DISSECT_IMAGE_RELAX_VAR_CHECK | - (arg_action == ACTION_UPDATE_CATALOG ? DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS : DISSECT_IMAGE_READ_ONLY), - &mounted_dir, - /* ret_dir_fd= */ NULL, - &loop_device); - if (r < 0) - return r; - - arg_root = strdup(mounted_dir); - if (!arg_root) - return log_oom(); - } - - signal(SIGWINCH, columns_lines_cache_reset); - sigbus_install(); - - switch (arg_action) { - - case ACTION_NEW_ID128: - return id128_print_new(ID128_PRINT_PRETTY); - - case ACTION_SETUP_KEYS: - return setup_keys(); - - case ACTION_LIST_CATALOG: - case ACTION_DUMP_CATALOG: - case ACTION_UPDATE_CATALOG: { - _cleanup_free_ char *database = NULL; - - database = path_join(arg_root, secure_getenv("SYSTEMD_CATALOG") ?: CATALOG_DATABASE); - if (!database) - return log_oom(); - - if (arg_action == ACTION_UPDATE_CATALOG) { - const char *e; - - e = secure_getenv("SYSTEMD_CATALOG_SOURCES"); - - r = catalog_update( - database, - arg_root, - e ? (const char* const*) STRV_MAKE(e) : catalog_file_dirs); - if (r < 0) - return log_error_errno(r, "Failed to list catalog: %m"); - } else { - bool oneline = arg_action == ACTION_LIST_CATALOG; - - pager_open(arg_pager_flags); - - if (optind < argc) - r = catalog_list_items(stdout, database, oneline, argv + optind); - else - r = catalog_list(stdout, database, oneline); - if (r < 0) - return log_error_errno(r, "Failed to list catalog: %m"); - } - - return 0; - } - - case ACTION_FLUSH: - return flush_to_var(); - - case ACTION_RELINQUISH_VAR: - return relinquish_var(); - - case ACTION_SYNC: - return sync_journal(); - - case ACTION_ROTATE: - return rotate(); - - case ACTION_SHOW: - case ACTION_PRINT_HEADER: - case ACTION_VERIFY: - case ACTION_DISK_USAGE: - case ACTION_LIST_BOOTS: - case ACTION_VACUUM: - case ACTION_ROTATE_AND_VACUUM: - case ACTION_LIST_FIELDS: - case ACTION_LIST_FIELD_NAMES: - /* These ones require access to the journal files, continue below. */ - break; - - default: - assert_not_reached(); - } - - if (arg_directory) - r = sd_journal_open_directory(&j, arg_directory, arg_journal_type); - else if (arg_root) - r = sd_journal_open_directory(&j, arg_root, arg_journal_type | SD_JOURNAL_OS_ROOT); - else if (arg_file_stdin) - r = sd_journal_open_files_fd(&j, (int[]) { STDIN_FILENO }, 1, 0); - else if (arg_file) - r = sd_journal_open_files(&j, (const char**) arg_file, 0); - else if (arg_machine) - r = journal_open_machine(&j, arg_machine); - else - r = sd_journal_open_namespace( - &j, - arg_namespace, - (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) | - arg_namespace_flags | arg_journal_type); - if (r < 0) - return log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal"); - - r = journal_access_check_and_warn(j, arg_quiet, - !(arg_journal_type == SD_JOURNAL_CURRENT_USER || arg_user_units)); - if (r < 0) - return r; - - switch (arg_action) { - - case ACTION_NEW_ID128: - case ACTION_SETUP_KEYS: - case ACTION_LIST_CATALOG: - case ACTION_DUMP_CATALOG: - case ACTION_UPDATE_CATALOG: - case ACTION_FLUSH: - case ACTION_SYNC: - case ACTION_ROTATE: - assert_not_reached(); - - case ACTION_PRINT_HEADER: - journal_print_header(j); - return 0; - - case ACTION_VERIFY: - return verify(j, !arg_quiet); - - case ACTION_DISK_USAGE: { - uint64_t bytes = 0; - - r = sd_journal_get_usage(j, &bytes); - if (r < 0) - return r; - - printf("Archived and active journals take up %s in the file system.\n", - FORMAT_BYTES(bytes)); - - return 0; - } - - case ACTION_LIST_BOOTS: - return list_boots(j); - - case ACTION_ROTATE_AND_VACUUM: - - r = rotate(); - if (r < 0) - return r; - - _fallthrough_; - - case ACTION_VACUUM: { - Directory *d; - int ret = 0; - - HASHMAP_FOREACH(d, j->directories_by_path) { - r = journal_directory_vacuum(d->path, arg_vacuum_size, arg_vacuum_n_files, arg_vacuum_time, NULL, !arg_quiet); - if (r < 0) { - log_error_errno(r, "Failed to vacuum %s: %m", d->path); - if (ret >= 0) - ret = r; - } - } - - return ret; - } - - case ACTION_LIST_FIELD_NAMES: { - const char *field; - - SD_JOURNAL_FOREACH_FIELD(j, field) - printf("%s\n", field); - - return 0; - } - - case ACTION_SHOW: - case ACTION_LIST_FIELDS: - break; - - default: - assert_not_reached(); - } - - if (arg_boot_offset != 0 && - sd_journal_has_runtime_files(j) > 0 && - sd_journal_has_persistent_files(j) == 0) { - log_info("Specifying boot ID or boot offset has no effect, no persistent journal was found."); - - if (arg_action == ACTION_SHOW && arg_compiled_pattern) - return -ENOENT; - - return 0; - } - /* add_boot() must be called first! - * It may need to seek the journal to find parent boot IDs. */ - r = add_boot(j); - if (r < 0) - return r; - - r = add_dmesg(j); - if (r < 0) - return r; - - r = add_units(j); - if (r < 0) - return log_error_errno(r, "Failed to add filter for units: %m"); - - r = add_syslog_identifier(j); - if (r < 0) - return log_error_errno(r, "Failed to add filter for syslog identifiers: %m"); - - r = add_priorities(j); - if (r < 0) - return r; - - r = add_facilities(j); - if (r < 0) - return r; - - r = add_matches(j, argv + optind); - if (r < 0) - return r; - - if (DEBUG_LOGGING) { - _cleanup_free_ char *filter = NULL; - - filter = journal_make_match_string(j); - if (!filter) - return log_oom(); - - log_debug("Journal filter: %s", filter); - } - - if (arg_action == ACTION_LIST_FIELDS) - return action_list_fields(j); - - /* Opening the fd now means the first sd_journal_wait() will actually wait */ - if (arg_follow) { - poll_fd = sd_journal_get_fd(j); - if (poll_fd == -EMFILE) { - log_warning_errno(poll_fd, "Insufficient watch descriptors available. Reverting to -n."); - arg_follow = false; - } else if (poll_fd == -EMEDIUMTYPE) - return log_error_errno(poll_fd, "The --follow switch is not supported in conjunction with reading from STDIN."); - else if (poll_fd < 0) - return log_error_errno(poll_fd, "Failed to get journal fd: %m"); - } - - if (arg_cursor || arg_after_cursor || arg_cursor_file) { - cursor = arg_cursor ?: arg_after_cursor; - - if (arg_cursor_file) { - r = read_one_line_file(arg_cursor_file, &cursor_from_file); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to read cursor file %s: %m", arg_cursor_file); - - if (r > 0) { - cursor = cursor_from_file; - after_cursor = true; - } - } else - after_cursor = arg_after_cursor; - } - - if (cursor) { - r = sd_journal_seek_cursor(j, cursor); - if (r < 0) - return log_error_errno(r, "Failed to seek to cursor: %m"); - - r = sd_journal_step_one(j, !arg_reverse); - if (r < 0) - return log_error_errno(r, "Failed to iterate through journal: %m"); - - if (after_cursor && r > 0) { - /* With --after-cursor=/--cursor-file= we want to skip the first entry only if it's - * the entry the cursor is pointing at, otherwise, if some journal filters are used, - * we might skip the first entry of the filter match, which leads to unexpectedly - * missing journal entries. */ - int k; - - k = sd_journal_test_cursor(j, cursor); - if (k < 0) - return log_error_errno(k, "Failed to test cursor against current entry: %m"); - if (k > 0) - /* Current entry matches the one our cursor is pointing at, so let's try - * to advance the next entry. */ - r = sd_journal_step_one(j, !arg_reverse); - } - - if (r == 0) { - /* We couldn't find the next entry after the cursor. */ - if (arg_follow) - need_seek = true; - else - arg_lines = 0; - } - } else if (arg_until_set && (arg_reverse || arg_lines_needs_seek_end())) { - /* If both --until and any of --reverse and --lines=N is specified, things get - * a little tricky. We seek to the place of --until first. If only --reverse or - * --reverse and --lines is specified, we search backwards and let the output - * counter handle --lines for us. If only --lines is used, we just jump backwards - * arg_lines and search afterwards from there. */ - - r = sd_journal_seek_realtime_usec(j, arg_until); - if (r < 0) - return log_error_errno(r, "Failed to seek to date: %m"); - - if (arg_reverse) - r = sd_journal_previous(j); - else /* arg_lines_needs_seek_end */ - r = sd_journal_previous_skip(j, arg_lines); - - } else if (arg_reverse) { - r = sd_journal_seek_tail(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to tail: %m"); - - r = sd_journal_previous(j); - - } else if (arg_lines_needs_seek_end()) { - r = sd_journal_seek_tail(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to tail: %m"); - - r = sd_journal_previous_skip(j, arg_lines); - - } else if (arg_since_set) { - /* This is placed after arg_reverse and arg_lines. If --since is used without - * both, we seek to the place of --since and search afterwards from there. - * If used with --reverse or --lines, we seek to the tail first and check if - * the entry is within the range of --since later. */ - - r = sd_journal_seek_realtime_usec(j, arg_since); - if (r < 0) - return log_error_errno(r, "Failed to seek to date: %m"); - since_seeked = true; - - r = sd_journal_next(j); - - } else { - r = sd_journal_seek_head(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to head: %m"); - - r = sd_journal_next(j); - } - if (r < 0) - return log_error_errno(r, "Failed to iterate through journal: %m"); - if (r == 0) - need_seek = true; - - if (!arg_follow) - pager_open(arg_pager_flags); - - if (!arg_quiet && (arg_lines != 0 || arg_follow) && DEBUG_LOGGING) { - usec_t start, end; - char start_buf[FORMAT_TIMESTAMP_MAX], end_buf[FORMAT_TIMESTAMP_MAX]; - - r = sd_journal_get_cutoff_realtime_usec(j, &start, &end); - if (r < 0) - return log_error_errno(r, "Failed to get cutoff: %m"); - if (r > 0) { - if (arg_follow) - printf("-- Journal begins at %s. --\n", - format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start)); - else - printf("-- Journal begins at %s, ends at %s. --\n", - format_timestamp_maybe_utc(start_buf, sizeof(start_buf), start), - format_timestamp_maybe_utc(end_buf, sizeof(end_buf), end)); - } - } - - Context c = { - .journal = j, - .has_cursor = cursor, - .need_seek = need_seek, - .since_seeked = since_seeked, - }; - - if (arg_follow) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - int sig; - - assert(poll_fd >= 0); - - r = setup_event(&c, poll_fd, &e); - if (r < 0) - return r; - - r = sd_event_loop(e); - if (r < 0) - return r; - sig = r; - - /* unref signal event sources. */ - e = sd_event_unref(e); - - r = update_cursor(j); - if (r < 0) - return r; - - /* re-send the original signal. */ - assert(SIGNAL_VALID(sig)); - if (raise(sig) < 0) - log_error("Failed to raise the original signal SIG%s, ignoring: %m", signal_to_string(sig)); - - return 0; - } - - r = show(&c); - if (r < 0) - return r; - n_shown = r; - - if (n_shown == 0 && !arg_quiet) - printf("-- No entries --\n"); - - r = update_cursor(j); - if (r < 0) - return r; - - if (arg_compiled_pattern && n_shown == 0) - /* --grep was used, no error was thrown, but the pattern didn't - * match anything. Let's mimic grep's behavior here and return - * a non-zero exit code, so journalctl --grep can be used - * in scripts and such */ - return -ENOENT; - - return 0; } -DEFINE_MAIN_FUNCTION(run); +DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(run); diff --git a/src/journal/journalctl.h b/src/journal/journalctl.h new file mode 100644 index 0000000..c6993a2 --- /dev/null +++ b/src/journal/journalctl.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-id128.h" + +#include "json.h" +#include "output-mode.h" +#include "pager.h" +#include "pcre2-util.h" +#include "set.h" +#include "time-util.h" + +typedef enum JournalctlAction { + ACTION_SHOW, + ACTION_NEW_ID128, + ACTION_SETUP_KEYS, + ACTION_LIST_CATALOG, + ACTION_DUMP_CATALOG, + ACTION_UPDATE_CATALOG, + ACTION_PRINT_HEADER, + ACTION_VERIFY, + ACTION_DISK_USAGE, + ACTION_LIST_BOOTS, + ACTION_LIST_FIELDS, + ACTION_LIST_FIELD_NAMES, + ACTION_LIST_NAMESPACES, + ACTION_FLUSH, + ACTION_RELINQUISH_VAR, + ACTION_SYNC, + ACTION_ROTATE, + ACTION_VACUUM, + ACTION_ROTATE_AND_VACUUM, +} JournalctlAction; + +extern JournalctlAction arg_action; +extern OutputMode arg_output; +extern JsonFormatFlags arg_json_format_flags; +extern PagerFlags arg_pager_flags; +extern bool arg_utc; +extern bool arg_follow; +extern bool arg_full; +extern bool arg_all; +extern int arg_lines; +extern bool arg_lines_oldest; +extern bool arg_no_tail; +extern bool arg_truncate_newline; +extern bool arg_quiet; +extern bool arg_merge; +extern bool arg_boot; +extern sd_id128_t arg_boot_id; +extern int arg_boot_offset; +extern bool arg_dmesg; +extern bool arg_no_hostname; +extern const char *arg_cursor; +extern const char *arg_cursor_file; +extern const char *arg_after_cursor; +extern bool arg_show_cursor; +extern const char *arg_directory; +extern char **arg_file; +extern bool arg_file_stdin; +extern int arg_priorities; +extern Set *arg_facilities; +extern char *arg_verify_key; +#if HAVE_GCRYPT +extern usec_t arg_interval; +extern bool arg_force; +#endif +extern usec_t arg_since; +extern usec_t arg_until; +extern bool arg_since_set; +extern bool arg_until_set; +extern char **arg_syslog_identifier; +extern char **arg_exclude_identifier; +extern char **arg_system_units; +extern char **arg_user_units; +extern const char *arg_field; +extern bool arg_catalog; +extern bool arg_reverse; +extern int arg_journal_type; +extern int arg_journal_additional_open_flags; +extern int arg_namespace_flags; +extern char *arg_root; +extern char *arg_image; +extern const char *arg_machine; +extern const char *arg_namespace; +extern uint64_t arg_vacuum_size; +extern uint64_t arg_vacuum_n_files; +extern usec_t arg_vacuum_time; +extern Set *arg_output_fields; +extern const char *arg_pattern; +extern pcre2_code *arg_compiled_pattern; +extern PatternCompileCase arg_case; + +static inline bool arg_lines_needs_seek_end(void) { + return arg_lines >= 0 && !arg_lines_oldest; +} diff --git a/src/journal/journald-audit.c b/src/journal/journald-audit.c index bddfe76..d49283d 100644 --- a/src/journal/journald-audit.c +++ b/src/journal/journald-audit.c @@ -335,10 +335,9 @@ void process_audit_string(Server *s, int type, const char *data, size_t size) { size_t n = 0, z; uint64_t seconds, msec, id; const char *p, *type_name; - char id_field[sizeof("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)], - type_field[sizeof("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)], - source_time_field[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; - struct iovec iovec[N_IOVEC_META_FIELDS + 8 + N_IOVEC_AUDIT_FIELDS]; + char id_field[STRLEN("_AUDIT_ID=") + DECIMAL_STR_MAX(uint64_t)], + type_field[STRLEN("_AUDIT_TYPE=") + DECIMAL_STR_MAX(int)]; + struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_AUDIT_FIELDS]; char *m, *type_field_name; int k; @@ -375,14 +374,10 @@ void process_audit_string(Server *s, int type, const char *data, size_t size) { iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=audit"); - sprintf(source_time_field, "_SOURCE_REALTIME_TIMESTAMP=%" PRIu64, - (usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC); - iovec[n++] = IOVEC_MAKE_STRING(source_time_field); - - sprintf(type_field, "_AUDIT_TYPE=%i", type); + xsprintf(type_field, "_AUDIT_TYPE=%i", type); iovec[n++] = IOVEC_MAKE_STRING(type_field); - sprintf(id_field, "_AUDIT_ID=%" PRIu64, id); + xsprintf(id_field, "_AUDIT_ID=%" PRIu64, id); iovec[n++] = IOVEC_MAKE_STRING(id_field); assert_cc(4 == LOG_FAC(LOG_AUTH)); @@ -401,7 +396,9 @@ void process_audit_string(Server *s, int type, const char *data, size_t size) { map_all_fields(p, map_fields_kernel, "_AUDIT_FIELD_", true, iovec, &n, n + N_IOVEC_AUDIT_FIELDS); - server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, NULL, LOG_NOTICE, 0); + server_dispatch_message(s, iovec, n, ELEMENTSOF(iovec), NULL, + TIMEVAL_STORE((usec_t) seconds * USEC_PER_SEC + (usec_t) msec * USEC_PER_MSEC), + LOG_NOTICE, 0); /* free() all entries that map_all_fields() added. All others * are allocated on the stack or are constant. */ diff --git a/src/journal/journald-context.c b/src/journal/journald-context.c index f5f6ec5..b44f700 100644 --- a/src/journal/journald-context.c +++ b/src/journal/journald-context.c @@ -612,7 +612,7 @@ static void client_context_try_shrink_to(Server *s, size_t limit) { if (pid_is_unwaited(c->pid) == 0) client_context_free(s, c); else - idx ++; + idx++; } s->last_cache_pid_flush = t; @@ -650,8 +650,8 @@ void client_context_flush_all(Server *s) { client_context_flush_regular(s); - assert(prioq_size(s->client_contexts_lru) == 0); - assert(hashmap_size(s->client_contexts) == 0); + assert(prioq_isempty(s->client_contexts_lru)); + assert(hashmap_isempty(s->client_contexts)); s->client_contexts_lru = prioq_free(s->client_contexts_lru); s->client_contexts = hashmap_free(s->client_contexts); diff --git a/src/journal/journald-gperf.gperf b/src/journal/journald-gperf.gperf index 9076597..49987f5 100644 --- a/src/journal/journald-gperf.gperf +++ b/src/journal/journald-gperf.gperf @@ -19,35 +19,37 @@ struct ConfigPerfItem; %struct-type %includes %% -Journal.Storage, config_parse_storage, 0, offsetof(Server, storage) -Journal.Compress, config_parse_compress, 0, offsetof(Server, compress) -Journal.Seal, config_parse_bool, 0, offsetof(Server, seal) -Journal.ReadKMsg, config_parse_bool, 0, offsetof(Server, read_kmsg) -Journal.Audit, config_parse_tristate, 0, offsetof(Server, set_audit) -Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec) +Journal.Storage, config_parse_storage, 0, offsetof(Server, storage) +Journal.Compress, config_parse_compress, 0, offsetof(Server, compress) +Journal.Seal, config_parse_bool, 0, offsetof(Server, seal) +Journal.ReadKMsg, config_parse_bool, 0, offsetof(Server, read_kmsg) +Journal.Audit, config_parse_tristate, 0, offsetof(Server, set_audit) +Journal.SyncIntervalSec, config_parse_sec, 0, offsetof(Server, sync_interval_usec) # The following is a legacy name for compatibility -Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, ratelimit_interval) -Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, ratelimit_interval) -Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, ratelimit_burst) -Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_use) -Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_size) -Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.keep_free) -Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_storage.metrics.n_max_files) -Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_use) -Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_size) -Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.keep_free) -Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_storage.metrics.n_max_files) -Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec) -Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec) -Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) -Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg) -Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console) -Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall) -Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path) -Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store) -Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog) -Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg) -Journal.MaxLevelConsole, config_parse_log_level, 0, offsetof(Server, max_level_console) -Journal.MaxLevelWall, config_parse_log_level, 0, offsetof(Server, max_level_wall) -Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode) -Journal.LineMax, config_parse_line_max, 0, offsetof(Server, line_max) +Journal.RateLimitInterval, config_parse_sec, 0, offsetof(Server, ratelimit_interval) +Journal.RateLimitIntervalSec,config_parse_sec, 0, offsetof(Server, ratelimit_interval) +Journal.RateLimitBurst, config_parse_unsigned, 0, offsetof(Server, ratelimit_burst) +Journal.SystemMaxUse, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_use) +Journal.SystemMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.max_size) +Journal.SystemKeepFree, config_parse_iec_uint64, 0, offsetof(Server, system_storage.metrics.keep_free) +Journal.SystemMaxFiles, config_parse_uint64, 0, offsetof(Server, system_storage.metrics.n_max_files) +Journal.RuntimeMaxUse, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_use) +Journal.RuntimeMaxFileSize, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.max_size) +Journal.RuntimeKeepFree, config_parse_iec_uint64, 0, offsetof(Server, runtime_storage.metrics.keep_free) +Journal.RuntimeMaxFiles, config_parse_uint64, 0, offsetof(Server, runtime_storage.metrics.n_max_files) +Journal.MaxRetentionSec, config_parse_sec, 0, offsetof(Server, max_retention_usec) +Journal.MaxFileSec, config_parse_sec, 0, offsetof(Server, max_file_usec) +Journal.ForwardToSyslog, config_parse_bool, 0, offsetof(Server, forward_to_syslog) +Journal.ForwardToKMsg, config_parse_bool, 0, offsetof(Server, forward_to_kmsg) +Journal.ForwardToConsole, config_parse_bool, 0, offsetof(Server, forward_to_console) +Journal.ForwardToWall, config_parse_bool, 0, offsetof(Server, forward_to_wall) +Journal.ForwardToSocket, config_parse_forward_to_socket, 0, offsetof(Server, forward_to_socket) +Journal.TTYPath, config_parse_path, 0, offsetof(Server, tty_path) +Journal.MaxLevelStore, config_parse_log_level, 0, offsetof(Server, max_level_store) +Journal.MaxLevelSyslog, config_parse_log_level, 0, offsetof(Server, max_level_syslog) +Journal.MaxLevelKMsg, config_parse_log_level, 0, offsetof(Server, max_level_kmsg) +Journal.MaxLevelConsole, config_parse_log_level, 0, offsetof(Server, max_level_console) +Journal.MaxLevelWall, config_parse_log_level, 0, offsetof(Server, max_level_wall) +Journal.MaxLevelSocket, config_parse_log_level, 0, offsetof(Server, max_level_socket) +Journal.SplitMode, config_parse_split_mode, 0, offsetof(Server, split_mode) +Journal.LineMax, config_parse_line_max, 0, offsetof(Server, line_max) diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index 28d4880..78f6e1f 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -98,7 +98,7 @@ static bool is_us(const char *identifier, const char *pid) { void dev_kmsg_record(Server *s, char *p, size_t l) { - _cleanup_free_ char *message = NULL, *syslog_priority = NULL, *syslog_pid = NULL, *syslog_facility = NULL, *syslog_identifier = NULL, *source_time = NULL, *identifier = NULL, *pid = NULL; + _cleanup_free_ char *message = NULL, *syslog_pid = NULL, *syslog_identifier = NULL, *identifier = NULL, *pid = NULL; struct iovec iovec[N_IOVEC_META_FIELDS + 7 + N_IOVEC_KERNEL_FIELDS + 2 + N_IOVEC_UDEV_FIELDS]; char *kernel_device = NULL; unsigned long long usec; @@ -253,16 +253,19 @@ void dev_kmsg_record(Server *s, char *p, size_t l) { } } - if (asprintf(&source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec) >= 0) - iovec[n++] = IOVEC_MAKE_STRING(source_time); + char source_time[STRLEN("_SOURCE_MONOTONIC_TIMESTAMP=") + DECIMAL_STR_MAX(unsigned long long)]; + xsprintf(source_time, "_SOURCE_MONOTONIC_TIMESTAMP=%llu", usec); + iovec[n++] = IOVEC_MAKE_STRING(source_time); iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=kernel"); - if (asprintf(&syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK) >= 0) - iovec[n++] = IOVEC_MAKE_STRING(syslog_priority); + char syslog_priority[STRLEN("PRIORITY=") + DECIMAL_STR_MAX(int)]; + xsprintf(syslog_priority, "PRIORITY=%i", LOG_PRI(priority)); + iovec[n++] = IOVEC_MAKE_STRING(syslog_priority); - if (asprintf(&syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)) >= 0) - iovec[n++] = IOVEC_MAKE_STRING(syslog_facility); + char syslog_facility[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; + xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); + iovec[n++] = IOVEC_MAKE_STRING(syslog_facility); if (LOG_FAC(priority) == LOG_KERN) iovec[n++] = IOVEC_MAKE_STRING("SYSLOG_IDENTIFIER=kernel"); diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c index 315ec0b..0600102 100644 --- a/src/journal/journald-native.c +++ b/src/journal/journald-native.c @@ -22,7 +22,6 @@ #include "journald-wall.h" #include "memfd-util.h" #include "memory-util.h" -#include "missing_fcntl.h" #include "parse-util.h" #include "path-util.h" #include "process-util.h" @@ -54,13 +53,13 @@ static void server_process_entry_meta( else if (l == 17 && startswith(p, "SYSLOG_FACILITY=") && p[16] >= '0' && p[16] <= '9') - *priority = (*priority & LOG_PRIMASK) | ((p[16] - '0') << 3); + *priority = LOG_PRI(*priority) | ((p[16] - '0') << 3); else if (l == 18 && startswith(p, "SYSLOG_FACILITY=") && p[16] >= '0' && p[16] <= '9' && p[17] >= '0' && p[17] <= '9') - *priority = (*priority & LOG_PRIMASK) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); + *priority = LOG_PRI(*priority) | (((p[16] - '0')*10 + (p[17] - '0')) << 3); else if (l >= 19 && startswith(p, "SYSLOG_IDENTIFIER=")) { @@ -327,7 +326,7 @@ void server_process_native_message( } while (r == 0); } -void server_process_native_file( +int server_process_native_file( Server *s, int fd, const struct ucred *ucred, @@ -343,30 +342,25 @@ void server_process_native_file( assert(s); assert(fd >= 0); - if (fstat(fd, &st) < 0) { - log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, "Failed to stat passed file, ignoring: %m"); - return; - } + if (fstat(fd, &st) < 0) + return log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, + "Failed to stat passed file: %m"); r = stat_verify_regular(&st); - if (r < 0) { - log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, "File passed is not regular, ignoring: %m"); - return; - } + if (r < 0) + return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, + "File passed is not regular, ignoring message: %m"); if (st.st_size <= 0) - return; + return 0; - int flags = fcntl(fd, F_GETFL); - if (flags < 0) { - log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, "Failed to get flags of passed file, ignoring: %m"); - return; - } - - if ((flags & ~(O_ACCMODE|RAW_O_LARGEFILE)) != 0) { - log_ratelimit_error(JOURNAL_LOG_RATELIMIT, "Unexpected flags of passed memory fd, ignoring message: %m"); - return; - } + r = fd_verify_safe_flags(fd); + if (r == -EREMOTEIO) + return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, + "Unexpected flags of passed memory fd, ignoring message."); + if (r < 0) + return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, + "Failed to get flags of passed file: %m"); /* If it's a memfd, check if it is sealed. If so, we can just mmap it and use it, and do not need to * copy the data out. */ @@ -376,38 +370,30 @@ void server_process_native_file( _cleanup_free_ char *k = NULL; const char *e; - /* If this is not a sealed memfd, and the peer is unknown or - * unprivileged, then verify the path. */ + /* If this is not a sealed memfd, and the peer is unknown or unprivileged, then verify the + * path. */ r = fd_get_path(fd, &k); - if (r < 0) { - log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, - "readlink(/proc/self/fd/%i) failed: %m", fd); - return; - } + if (r < 0) + return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, + "Failed to get path of passed fd: %m"); e = PATH_STARTSWITH_SET(k, "/dev/shm/", "/tmp/", "/var/tmp/"); - if (!e) { - log_ratelimit_error(JOURNAL_LOG_RATELIMIT, - "Received file outside of allowed directories. Refusing."); - return; - } + if (!e) + return log_ratelimit_error_errno(SYNTHETIC_ERRNO(EPERM), JOURNAL_LOG_RATELIMIT, + "Received file outside of allowed directories, refusing."); - if (!filename_is_valid(e)) { - log_ratelimit_error(JOURNAL_LOG_RATELIMIT, - "Received file in subdirectory of allowed directories. Refusing."); - return; - } + if (!filename_is_valid(e)) + return log_ratelimit_error_errno(SYNTHETIC_ERRNO(EPERM), JOURNAL_LOG_RATELIMIT, + "Received file in subdirectory of allowed directories, refusing."); } /* When !sealed, set a lower memory limit. We have to read the file, effectively doubling memory * use. */ - if (st.st_size > ENTRY_SIZE_MAX / (sealed ? 1 : 2)) { - log_ratelimit_error(JOURNAL_LOG_RATELIMIT, - "File passed too large (%"PRIu64" bytes). Ignoring.", - (uint64_t) st.st_size); - return; - } + if (st.st_size > ENTRY_SIZE_MAX / (sealed ? 1 : 2)) + return log_ratelimit_error_errno(SYNTHETIC_ERRNO(EFBIG), JOURNAL_LOG_RATELIMIT, + "File passed too large (%"PRIu64" bytes), refusing.", + (uint64_t) st.st_size); if (sealed) { void *p; @@ -418,67 +404,54 @@ void server_process_native_file( ps = PAGE_ALIGN(st.st_size); assert(ps < SIZE_MAX); p = mmap(NULL, ps, PROT_READ, MAP_PRIVATE, fd, 0); - if (p == MAP_FAILED) { - log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, - "Failed to map memfd, ignoring: %m"); - return; - } + if (p == MAP_FAILED) + return log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, + "Failed to map memfd: %m"); server_process_native_message(s, p, st.st_size, ucred, tv, label, label_len); assert_se(munmap(p, ps) >= 0); - } else { - _cleanup_free_ void *p = NULL; - struct statvfs vfs; - ssize_t n; - - if (fstatvfs(fd, &vfs) < 0) { - log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, - "Failed to stat file system of passed file, not processing it: %m"); - return; - } - /* Refuse operating on file systems that have - * mandatory locking enabled, see: - * - * https://github.com/systemd/systemd/issues/1822 - */ - if (vfs.f_flag & ST_MANDLOCK) { - log_ratelimit_error(JOURNAL_LOG_RATELIMIT, - "Received file descriptor from file system with mandatory locking enabled, not processing it."); - return; - } + return 0; + } - /* Make the fd non-blocking. On regular files this has - * the effect of bypassing mandatory locking. Of - * course, this should normally not be necessary given - * the check above, but let's better be safe than - * sorry, after all NFS is pretty confusing regarding - * file system flags, and we better don't trust it, - * and so is SMB. */ - r = fd_nonblock(fd, true); - if (r < 0) { - log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, - "Failed to make fd non-blocking, not processing it: %m"); - return; - } + _cleanup_free_ void *p = NULL; + struct statvfs vfs; + ssize_t n; + + if (fstatvfs(fd, &vfs) < 0) + return log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, + "Failed to stat file system of passed file: %m"); + + /* Refuse operating on file systems that have mandatory locking enabled. + * See also: https://github.com/systemd/systemd/issues/1822 */ + if (FLAGS_SET(vfs.f_flag, ST_MANDLOCK)) + return log_ratelimit_error_errno(SYNTHETIC_ERRNO(EPERM), JOURNAL_LOG_RATELIMIT, + "Received file descriptor from file system with mandatory locking enabled, not processing it."); + + /* Make the fd non-blocking. On regular files this has the effect of bypassing mandatory + * locking. Of course, this should normally not be necessary given the check above, but let's + * better be safe than sorry, after all NFS is pretty confusing regarding file system flags, + * and we better don't trust it, and so is SMB. */ + r = fd_nonblock(fd, true); + if (r < 0) + return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, + "Failed to make fd non-blocking: %m"); - /* The file is not sealed, we can't map the file here, since - * clients might then truncate it and trigger a SIGBUS for - * us. So let's stupidly read it. */ + /* The file is not sealed, we can't map the file here, since clients might then truncate it + * and trigger a SIGBUS for us. So let's stupidly read it. */ - p = malloc(st.st_size); - if (!p) { - log_oom(); - return; - } + p = malloc(st.st_size); + if (!p) + return log_oom(); - n = pread(fd, p, st.st_size, 0); - if (n < 0) - log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, - "Failed to read file, ignoring: %m"); - else if (n > 0) - server_process_native_message(s, p, n, ucred, tv, label, label_len); - } + n = pread(fd, p, st.st_size, 0); + if (n < 0) + return log_ratelimit_error_errno(errno, JOURNAL_LOG_RATELIMIT, + "Failed to read file: %m"); + if (n > 0) + server_process_native_message(s, p, n, ucred, tv, label, label_len); + + return 0; } int server_open_native_socket(Server *s, const char *native_socket) { diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h index 7bbaaed..10db267 100644 --- a/src/journal/journald-native.h +++ b/src/journal/journald-native.h @@ -12,7 +12,7 @@ void server_process_native_message( const char *label, size_t label_len); -void server_process_native_file( +int server_process_native_file( Server *s, int fd, const struct ucred *ucred, diff --git a/src/journal/journald-rate-limit.c b/src/journal/journald-rate-limit.c index 1028e38..a1ae172 100644 --- a/src/journal/journald-rate-limit.c +++ b/src/journal/journald-rate-limit.c @@ -1,18 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include - #include "alloc-util.h" #include "hashmap.h" #include "journald-rate-limit.h" -#include "list.h" #include "logarithm.h" -#include "random-util.h" #include "string-util.h" #include "time-util.h" #define POOLS_MAX 5 -#define BUCKETS_MAX 127 #define GROUPS_MAX 2047 static const int priority_map[] = { @@ -23,20 +18,17 @@ static const int priority_map[] = { [LOG_WARNING] = 2, [LOG_NOTICE] = 3, [LOG_INFO] = 3, - [LOG_DEBUG] = 4 + [LOG_DEBUG] = 4, }; -typedef struct JournalRateLimitPool JournalRateLimitPool; -typedef struct JournalRateLimitGroup JournalRateLimitGroup; - -struct JournalRateLimitPool { +typedef struct JournalRateLimitPool { usec_t begin; unsigned num; unsigned suppressed; -}; +} JournalRateLimitPool; -struct JournalRateLimitGroup { - JournalRateLimit *parent; +typedef struct JournalRateLimitGroup { + OrderedHashmap *groups_by_id; char *id; @@ -44,116 +36,112 @@ struct JournalRateLimitGroup { usec_t interval; JournalRateLimitPool pools[POOLS_MAX]; - uint64_t hash; - - LIST_FIELDS(JournalRateLimitGroup, bucket); - LIST_FIELDS(JournalRateLimitGroup, lru); -}; - -struct JournalRateLimit { +} JournalRateLimitGroup; - JournalRateLimitGroup* buckets[BUCKETS_MAX]; - JournalRateLimitGroup *lru, *lru_tail; - - unsigned n_groups; - - uint8_t hash_key[16]; -}; - -JournalRateLimit *journal_ratelimit_new(void) { - JournalRateLimit *r; - - r = new0(JournalRateLimit, 1); - if (!r) +static JournalRateLimitGroup* journal_ratelimit_group_free(JournalRateLimitGroup *g) { + if (!g) return NULL; - random_bytes(r->hash_key, sizeof(r->hash_key)); - - return r; -} - -static void journal_ratelimit_group_free(JournalRateLimitGroup *g) { - assert(g); - - if (g->parent) { - assert(g->parent->n_groups > 0); - - if (g->parent->lru_tail == g) - g->parent->lru_tail = g->lru_prev; - - LIST_REMOVE(lru, g->parent->lru, g); - LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g); - - g->parent->n_groups--; - } + if (g->groups_by_id && g->id) + /* The group is already removed from the hashmap when this is called from the + * destructor of the hashmap. Hence, do not check the return value here. */ + ordered_hashmap_remove_value(g->groups_by_id, g->id, g); free(g->id); - free(g); + return mfree(g); } -void journal_ratelimit_free(JournalRateLimit *r) { - assert(r); +DEFINE_TRIVIAL_CLEANUP_FUNC(JournalRateLimitGroup*, journal_ratelimit_group_free); - while (r->lru) - journal_ratelimit_group_free(r->lru); - - free(r); -} +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + journal_ratelimit_group_hash_ops, + char, + string_hash_func, + string_compare_func, + JournalRateLimitGroup, + journal_ratelimit_group_free); static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) { - unsigned i; - assert(g); - for (i = 0; i < POOLS_MAX; i++) - if (g->pools[i].begin + g->interval >= ts) + FOREACH_ELEMENT(p, g->pools) + if (usec_add(p->begin, g->interval) >= ts) return false; return true; } -static void journal_ratelimit_vacuum(JournalRateLimit *r, usec_t ts) { - assert(r); +static void journal_ratelimit_vacuum(OrderedHashmap *groups_by_id, usec_t ts) { /* Makes room for at least one new item, but drop all expired items too. */ - while (r->n_groups >= GROUPS_MAX || - (r->lru_tail && journal_ratelimit_group_expired(r->lru_tail, ts))) - journal_ratelimit_group_free(r->lru_tail); -} + while (ordered_hashmap_size(groups_by_id) >= GROUPS_MAX) + journal_ratelimit_group_free(ordered_hashmap_first(groups_by_id)); -static JournalRateLimitGroup* journal_ratelimit_group_new(JournalRateLimit *r, const char *id, usec_t interval, usec_t ts) { JournalRateLimitGroup *g; + while ((g = ordered_hashmap_first(groups_by_id)) && journal_ratelimit_group_expired(g, ts)) + journal_ratelimit_group_free(g); +} + +static int journal_ratelimit_group_new( + OrderedHashmap **groups_by_id, + const char *id, + usec_t interval, + usec_t ts, + JournalRateLimitGroup **ret) { - assert(r); + _cleanup_(journal_ratelimit_group_freep) JournalRateLimitGroup *g = NULL; + int r; + + assert(groups_by_id); assert(id); + assert(ret); - g = new0(JournalRateLimitGroup, 1); + g = new(JournalRateLimitGroup, 1); if (!g) - return NULL; + return -ENOMEM; - g->id = strdup(id); + *g = (JournalRateLimitGroup) { + .id = strdup(id), + .interval = interval, + }; if (!g->id) - goto fail; + return -ENOMEM; - g->hash = siphash24_string(g->id, r->hash_key); + journal_ratelimit_vacuum(*groups_by_id, ts); - g->interval = interval; + r = ordered_hashmap_ensure_put(groups_by_id, &journal_ratelimit_group_hash_ops, g->id, g); + if (r < 0) + return r; + assert(r > 0); - journal_ratelimit_vacuum(r, ts); + g->groups_by_id = *groups_by_id; - LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g); - LIST_PREPEND(lru, r->lru, g); - if (!g->lru_next) - r->lru_tail = g; - r->n_groups++; + *ret = TAKE_PTR(g); + return 0; +} + +static int journal_ratelimit_group_acquire( + OrderedHashmap **groups_by_id, + const char *id, + usec_t interval, + usec_t ts, + JournalRateLimitGroup **ret) { - g->parent = r; - return g; + JournalRateLimitGroup *g; + + assert(groups_by_id); + assert(id); + assert(ret); -fail: - journal_ratelimit_group_free(g); - return NULL; + g = ordered_hashmap_get(*groups_by_id, id); + if (!g) + return journal_ratelimit_group_new(groups_by_id, id, interval, ts, ret); + + g->interval = interval; + + *ret = g; + return 0; } static unsigned burst_modulate(unsigned burst, uint64_t available) { @@ -184,13 +172,21 @@ static unsigned burst_modulate(unsigned burst, uint64_t available) { return burst; } -int journal_ratelimit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available) { - JournalRateLimitGroup *g, *found = NULL; +int journal_ratelimit_test( + OrderedHashmap **groups_by_id, + const char *id, + usec_t rl_interval, + unsigned rl_burst, + int priority, + uint64_t available) { + + JournalRateLimitGroup *g; JournalRateLimitPool *p; unsigned burst; - uint64_t h; usec_t ts; + int r; + assert(groups_by_id); assert(id); /* Returns: @@ -200,33 +196,18 @@ int journal_ratelimit_test(JournalRateLimit *r, const char *id, usec_t rl_interv * < 0 → error */ - if (!r) - return 1; - ts = now(CLOCK_MONOTONIC); - h = siphash24_string(id, r->hash_key); - g = r->buckets[h % BUCKETS_MAX]; - - LIST_FOREACH(bucket, i, g) - if (streq(i->id, id)) { - found = i; - break; - } - - if (!found) { - found = journal_ratelimit_group_new(r, id, rl_interval, ts); - if (!found) - return -ENOMEM; - } else - found->interval = rl_interval; + r = journal_ratelimit_group_acquire(groups_by_id, id, rl_interval, ts, &g); + if (r < 0) + return r; if (rl_interval == 0 || rl_burst == 0) return 1; burst = burst_modulate(rl_burst, available); - p = &found->pools[priority_map[priority]]; + p = &g->pools[priority_map[priority]]; if (p->begin <= 0) { p->suppressed = 0; @@ -235,7 +216,7 @@ int journal_ratelimit_test(JournalRateLimit *r, const char *id, usec_t rl_interv return 1; } - if (p->begin + rl_interval < ts) { + if (usec_add(p->begin, rl_interval) < ts) { unsigned s; s = p->suppressed; diff --git a/src/journal/journald-rate-limit.h b/src/journal/journald-rate-limit.h index 8def60f..566bba4 100644 --- a/src/journal/journald-rate-limit.h +++ b/src/journal/journald-rate-limit.h @@ -1,10 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "time-util.h" +#include -typedef struct JournalRateLimit JournalRateLimit; +#include "hashmap.h" +#include "time-util.h" -JournalRateLimit *journal_ratelimit_new(void); -void journal_ratelimit_free(JournalRateLimit *r); -int journal_ratelimit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available); +int journal_ratelimit_test( + OrderedHashmap **groups_by_id, + const char *id, + usec_t rl_interval, + unsigned rl_burst, + int priority, + uint64_t available); diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 1c3a2a0..717c8e4 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -18,7 +18,9 @@ #include "audit-util.h" #include "cgroup-util.h" #include "conf-parser.h" +#include "creds-util.h" #include "dirent-util.h" +#include "event-util.h" #include "extract-word.h" #include "fd-util.h" #include "fileio.h" @@ -39,9 +41,11 @@ #include "journald-native.h" #include "journald-rate-limit.h" #include "journald-server.h" +#include "journald-socket.h" #include "journald-stream.h" #include "journald-syslog.h" #include "log.h" +#include "memory-util.h" #include "missing_audit.h" #include "mkdir.h" #include "parse-util.h" @@ -51,12 +55,13 @@ #include "rm-rf.h" #include "selinux-util.h" #include "signal-util.h" +#include "socket-netlink.h" #include "socket-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "syslog-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" #include "varlink-io.systemd.Journal.h" @@ -87,6 +92,9 @@ #define FAILED_TO_WRITE_ENTRY_RATELIMIT ((const RateLimit) { .interval = 1 * USEC_PER_SEC, .burst = 1 }) +static int server_schedule_sync(Server *s, int priority); +static int server_refresh_idle_timer(Server *s); + static int server_determine_path_usage( Server *s, const char *path, @@ -289,11 +297,10 @@ static int server_open_journal( s->compress.threshold_bytes, metrics, s->mmap, - /* template= */ NULL, &f); else r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, fname, open_flags, file_flags, @@ -328,6 +335,20 @@ static bool server_flushed_flag_is_set(Server *s) { return access(fn, F_OK) >= 0; } +static void server_drop_flushed_flag(Server *s) { + const char *fn; + + assert(s); + + if (s->namespace) + return; + + fn = strjoina(s->runtime_directory, "/flushed"); + if (unlink(fn) < 0 && errno != ENOENT) + log_ratelimit_warning_errno(errno, JOURNAL_LOG_RATELIMIT, + "Failed to unlink %s, ignoring: %m", fn); +} + static int server_system_journal_open( Server *s, bool flush_requested, @@ -430,6 +451,7 @@ static int server_system_journal_open( server_add_acls(s->runtime_journal, 0); (void) cache_space_refresh(s, &s->runtime_storage); patch_min_use(&s->runtime_storage); + server_drop_flushed_flag(s); } } @@ -717,19 +739,46 @@ void server_rotate(Server *s) { server_process_deferred_closes(s); } -void server_sync(Server *s) { +static void server_rotate_journal(Server *s, JournalFile *f, uid_t uid) { + int r; + + assert(s); + assert(f); + + /* This is similar to server_rotate(), but rotates only specified journal file. + * + * 💣💣💣 This invalidate 'f', and the caller cannot reuse the passed JournalFile object. 💣💣💣 */ + + if (f == s->system_journal) + (void) server_do_rotate(s, &s->system_journal, "system", s->seal, /* uid= */ 0); + else if (f == s->runtime_journal) + (void) server_do_rotate(s, &s->runtime_journal, "runtime", /* seal= */ false, /* uid= */ 0); + else { + assert(ordered_hashmap_get(s->user_journals, UID_TO_PTR(uid)) == f); + r = server_do_rotate(s, &f, "user", s->seal, uid); + if (r >= 0) + ordered_hashmap_replace(s->user_journals, UID_TO_PTR(uid), f); + else if (!f) + /* Old file has been closed and deallocated */ + ordered_hashmap_remove(s->user_journals, UID_TO_PTR(uid)); + } + + server_process_deferred_closes(s); +} + +static void server_sync(Server *s, bool wait) { JournalFile *f; int r; if (s->system_journal) { - r = journal_file_set_offline(s->system_journal, false); + r = journal_file_set_offline(s->system_journal, wait); if (r < 0) log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT, "Failed to sync system journal, ignoring: %m"); } ORDERED_HASHMAP_FOREACH(f, s->user_journals) { - r = journal_file_set_offline(f, false); + r = journal_file_set_offline(f, wait); if (r < 0) log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT, "Failed to sync user journal, ignoring: %m"); @@ -899,47 +948,45 @@ static void server_write_to_journal( uid_t uid, const struct iovec *iovec, size_t n, + const dual_timestamp *ts, int priority) { - bool vacuumed = false, rotate = false; - struct dual_timestamp ts; + bool vacuumed = false; JournalFile *f; int r; assert(s); assert(iovec); assert(n > 0); + assert(ts); - /* Get the closest, linearized time we have for this log event from the event loop. (Note that we do not use - * the source time, and not even the time the event was originally seen, but instead simply the time we started - * processing it, as we want strictly linear ordering in what we write out.) */ - assert_se(sd_event_now(s->event, CLOCK_REALTIME, &ts.realtime) >= 0); - assert_se(sd_event_now(s->event, CLOCK_MONOTONIC, &ts.monotonic) >= 0); - - if (ts.realtime < s->last_realtime_clock) { + if (ts->realtime < s->last_realtime_clock) { /* When the time jumps backwards, let's immediately rotate. Of course, this should not happen during * regular operation. However, when it does happen, then we should make sure that we start fresh files * to ensure that the entries in the journal files are strictly ordered by time, in order to ensure * bisection works correctly. */ log_ratelimit_info(JOURNAL_LOG_RATELIMIT, "Time jumped backwards, rotating."); - rotate = true; - } else { + server_rotate(s); + server_vacuum(s, /* verbose = */ false); + vacuumed = true; + } - f = server_find_journal(s, uid); - if (!f) - return; + f = server_find_journal(s, uid); + if (!f) + return; - if (journal_file_rotate_suggested(f, s->max_file_usec, LOG_DEBUG)) { - log_debug("%s: Journal header limits reached or header out-of-date, rotating.", - f->path); - rotate = true; + if (journal_file_rotate_suggested(f, s->max_file_usec, LOG_DEBUG)) { + if (vacuumed) { + log_ratelimit_warning(JOURNAL_LOG_RATELIMIT, + "Suppressing rotation, as we already rotated immediately before write attempt. Giving up."); + return; } - } - if (rotate) { - server_rotate(s); - server_vacuum(s, false); + log_debug("%s: Journal header limits reached or header out-of-date, rotating.", f->path); + + server_rotate_journal(s, TAKE_PTR(f), uid); + server_vacuum(s, /* verbose = */ false); vacuumed = true; f = server_find_journal(s, uid); @@ -947,11 +994,11 @@ static void server_write_to_journal( return; } - s->last_realtime_clock = ts.realtime; + s->last_realtime_clock = ts->realtime; r = journal_file_append_entry( f, - &ts, + ts, /* boot_id= */ NULL, iovec, n, &s->seqnum->seqnum, @@ -973,8 +1020,8 @@ static void server_write_to_journal( return; } - server_rotate(s); - server_vacuum(s, false); + server_rotate_journal(s, TAKE_PTR(f), uid); + server_vacuum(s, /* verbose = */ false); f = server_find_journal(s, uid); if (!f) @@ -983,7 +1030,7 @@ static void server_write_to_journal( log_debug_errno(r, "Retrying write."); r = journal_file_append_entry( f, - &ts, + ts, /* boot_id= */ NULL, iovec, n, &s->seqnum->seqnum, @@ -1037,7 +1084,7 @@ static void server_dispatch_message_real( int priority, pid_t object_pid) { - char source_time[sizeof("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; + char source_time[STRLEN("_SOURCE_REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t)]; _unused_ _cleanup_free_ char *cmdline1 = NULL, *cmdline2 = NULL; uid_t journal_uid; ClientContext *o; @@ -1111,13 +1158,13 @@ static void server_dispatch_message_real( IOVEC_ADD_STRING_FIELD(iovec, n, o->slice, "OBJECT_SYSTEMD_SLICE"); IOVEC_ADD_STRING_FIELD(iovec, n, o->user_slice, "OBJECT_SYSTEMD_USER_SLICE"); - IOVEC_ADD_ID128_FIELD(iovec, n, o->invocation_id, "OBJECT_SYSTEMD_INVOCATION_ID="); + IOVEC_ADD_ID128_FIELD(iovec, n, o->invocation_id, "OBJECT_SYSTEMD_INVOCATION_ID"); } assert(n <= m); if (tv) { - sprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=" USEC_FMT, timeval_load(tv)); + xsprintf(source_time, "_SOURCE_REALTIME_TIMESTAMP=" USEC_FMT, timeval_load(tv)); iovec[n++] = IOVEC_MAKE_STRING(source_time); } @@ -1152,7 +1199,15 @@ static void server_dispatch_message_real( else journal_uid = 0; - server_write_to_journal(s, journal_uid, iovec, n, priority); + /* Get the closest, linearized time we have for this log event from the event loop. (Note that we do + * not use the source time, and not even the time the event was originally seen, but instead simply + * the time we started processing it, as we want strictly linear ordering in what we write out.) */ + struct dual_timestamp ts; + event_dual_timestamp_now(s->event, &ts); + + (void) server_forward_socket(s, iovec, n, &ts, priority); + + server_write_to_journal(s, journal_uid, iovec, n, &ts, priority); } void server_driver_message(Server *s, pid_t object_pid, const char *message_id, const char *format, ...) { @@ -1233,7 +1288,13 @@ void server_dispatch_message( if (c && c->unit) { (void) server_determine_space(s, &available, /* limit= */ NULL); - rl = journal_ratelimit_test(s->ratelimit, c->unit, c->log_ratelimit_interval, c->log_ratelimit_burst, priority & LOG_PRIMASK, available); + rl = journal_ratelimit_test( + &s->ratelimit_groups_by_id, + c->unit, + c->log_ratelimit_interval, + c->log_ratelimit_burst, + LOG_PRI(priority), + available); if (rl == 0) return; @@ -1275,11 +1336,19 @@ int server_flush_to_var(Server *s, bool require_flag_file) { if (!s->system_journal) return 0; + /* Offline and close the 'main' runtime journal file to allow the runtime journal to be opened with + * the SD_JOURNAL_ASSUME_IMMUTABLE flag in the below. */ + s->runtime_journal = journal_file_offline_close(s->runtime_journal); + + /* Reset current seqnum data to avoid unnecessary rotation when switching to system journal. + * See issue #30092. */ + zero(*s->seqnum); + log_debug("Flushing to %s...", s->system_storage.path); start = now(CLOCK_MONOTONIC); - r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_RUNTIME_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_ratelimit_error_errno(r, JOURNAL_LOG_RATELIMIT, "Failed to read runtime journal: %m"); @@ -1318,8 +1387,8 @@ int server_flush_to_var(Server *s, bool require_flag_file) { log_ratelimit_info(JOURNAL_LOG_RATELIMIT, "Rotating system journal."); - server_rotate(s); - server_vacuum(s, false); + server_rotate_journal(s, s->system_journal, /* uid = */ 0); + server_vacuum(s, /* verbose = */ false); if (!s->system_journal) { log_ratelimit_notice(JOURNAL_LOG_RATELIMIT, @@ -1348,13 +1417,36 @@ finish: if (s->system_journal) journal_file_post_change(s->system_journal); - s->runtime_journal = journal_file_offline_close(s->runtime_journal); - - if (r >= 0) - (void) rm_rf(s->runtime_storage.path, REMOVE_ROOT); + /* Save parent directories of runtime journals before closing runtime journals. */ + _cleanup_strv_free_ char **dirs = NULL; + (void) journal_get_directories(j, &dirs); + /* First, close all runtime journals opened in the above. */ sd_journal_close(j); + /* Remove the runtime directory if the all entries are successfully flushed to /var/. */ + if (r >= 0) { + r = rm_rf(s->runtime_storage.path, REMOVE_ROOT); + if (r < 0) + log_debug_errno(r, "Failed to remove runtime journal directory %s, ignoring: %m", s->runtime_storage.path); + else + log_debug("Removed runtime journal directory %s.", s->runtime_storage.path); + + /* The initrd may have a different machine ID from the host's one. Typically, that happens + * when our tests running on qemu, as the host's initrd is picked as is without updating + * the machine ID in the initrd with the one used in the image. Even in such the case, the + * runtime journals in the subdirectory named with the initrd's machine ID are flushed to + * the persistent journal. To make not the runtime journal flushed multiple times, let's + * also remove the runtime directories. */ + STRV_FOREACH(p, dirs) { + r = rm_rf(*p, REMOVE_ROOT); + if (r < 0) + log_debug_errno(r, "Failed to remove additional runtime journal directory %s, ignoring: %m", *p); + else + log_debug("Removed additional runtime journal directory %s.", *p); + } + } + server_driver_message(s, 0, NULL, LOG_MESSAGE("Time spent on flushing to %s is %s for %u entries.", s->system_storage.path, @@ -1373,7 +1465,6 @@ finish: } static int server_relinquish_var(Server *s) { - const char *fn; assert(s); if (s->storage == STORAGE_NONE) @@ -1393,11 +1484,6 @@ static int server_relinquish_var(Server *s) { ordered_hashmap_clear_with_destructor(s->user_journals, journal_file_offline_close); set_clear_with_destructor(s->deferred_closes, journal_file_offline_close); - fn = strjoina(s->runtime_directory, "/flushed"); - if (unlink(fn) < 0 && errno != ENOENT) - log_ratelimit_warning_errno(errno, JOURNAL_LOG_RATELIMIT, - "Failed to unlink %s, ignoring: %m", fn); - server_refresh_idle_timer(s); return 0; } @@ -1512,7 +1598,7 @@ int server_process_datagram( if (n > 0 && n_fds == 0) server_process_native_message(s, s->buffer, n, ucred, tv, label, label_len); else if (n == 0 && n_fds == 1) - server_process_native_file(s, fds[0], ucred, tv, label, label_len); + (void) server_process_native_file(s, fds[0], ucred, tv, label, label_len); else if (n_fds > 0) log_ratelimit_warning(JOURNAL_LOG_RATELIMIT, "Got too many file descriptors via native socket. Ignoring."); @@ -1537,7 +1623,7 @@ static void server_full_flush(Server *s) { assert(s); (void) server_flush_to_var(s, false); - server_sync(s); + server_sync(s, /* wait = */ false); server_vacuum(s, false); server_space_usage_message(s, NULL); @@ -1669,13 +1755,13 @@ fail: return 0; } -static void server_full_sync(Server *s) { +static void server_full_sync(Server *s, bool wait) { const char *fn; int r; assert(s); - server_sync(s); + server_sync(s, wait); /* Let clients know when the most recent sync happened. */ fn = strjoina(s->runtime_directory, "/synced"); @@ -1683,15 +1769,13 @@ static void server_full_sync(Server *s) { if (r < 0) log_ratelimit_warning_errno(r, JOURNAL_LOG_RATELIMIT, "Failed to write %s, ignoring: %m", fn); - - return; } static int dispatch_sigrtmin1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) { Server *s = ASSERT_PTR(userdata); log_debug("Received SIGRTMIN1 signal from PID %u, as request to sync.", si->ssi_pid); - server_full_sync(s); + server_full_sync(s, /* wait = */ false); return 0; } @@ -1701,7 +1785,7 @@ static int server_setup_signals(Server *s) { assert(s); - assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18) >= 0); r = sd_event_add_signal(s->event, &s->sigusr1_event_source, SIGUSR1, dispatch_sigusr1, s); if (r < 0) @@ -1839,6 +1923,17 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else s->max_level_wall = r; + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_socket")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = log_level_from_string(value); + if (r < 0) + log_warning("Failed to parse max level socket value \"%s\". Ignoring.", value); + else + s->max_level_socket = r; + } else if (startswith(key, "systemd.journald")) log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key); @@ -1847,39 +1942,44 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } static int server_parse_config_file(Server *s) { - const char *conf_file = "journald.conf"; + const char *conf_file; assert(s); if (s->namespace) - conf_file = strjoina("journald@", s->namespace, ".conf"); + conf_file = strjoina("systemd/journald@", s->namespace, ".conf"); + else + conf_file = "systemd/journald.conf"; - return config_parse_config_file(conf_file, "Journal\0", - config_item_perf_lookup, journald_gperf_lookup, - CONFIG_PARSE_WARN, s); + return config_parse_standard_file_with_dropins( + conf_file, + "Journal\0", + config_item_perf_lookup, journald_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ s); } static int server_dispatch_sync(sd_event_source *es, usec_t t, void *userdata) { Server *s = ASSERT_PTR(userdata); - server_sync(s); + server_sync(s, /* wait = */ false); return 0; } -int server_schedule_sync(Server *s, int priority) { +static int server_schedule_sync(Server *s, int priority) { int r; assert(s); if (priority <= LOG_CRIT) { /* Immediately sync to disk when this is of priority CRIT, ALERT, EMERG */ - server_sync(s); + server_sync(s, /* wait = */ false); return 0; } if (!s->event || sd_event_get_state(s->event) == SD_EVENT_FINISHED) { /* Shutting down the server? Let's sync immediately. */ - server_sync(s); + server_sync(s, /* wait = */ false); return 0; } @@ -2098,7 +2198,7 @@ static int synchronize_second_half(sd_event_source *event_source, void *userdata /* This is the "second half" of the Synchronize() varlink method. This function is called as deferred * event source at a low priority to ensure the synchronization completes after all queued log * messages are processed. */ - server_full_sync(s); + server_full_sync(s, /* wait = */ true); /* Let's get rid of the event source now, by marking it as non-floating again. It then has no ref * anymore and is immediately destroyed after we return from this function, i.e. from this event @@ -2303,7 +2403,7 @@ int server_map_seqnum_file( return 0; } -void server_unmap_seqnum_file(void *p, size_t size) { +static void server_unmap_seqnum_file(void *p, size_t size) { assert(size > 0); if (!p) @@ -2373,7 +2473,7 @@ int server_start_or_stop_idle_timer(Server *s) { return 1; } -int server_refresh_idle_timer(Server *s) { +static int server_refresh_idle_timer(Server *s) { int r; assert(s); @@ -2444,14 +2544,44 @@ static int server_setup_memory_pressure(Server *s) { return 0; } -int server_init(Server *s, const char *namespace) { - const char *native_socket, *syslog_socket, *stdout_socket, *varlink_socket, *e; - _cleanup_fdset_free_ FDSet *fds = NULL; - int n, r, fd, varlink_fd = -EBADF; - bool no_sockets; +static void server_load_credentials(Server *s) { + _cleanup_free_ void *data = NULL; + int r; assert(s); + r = read_credential("journal.forward_to_socket", &data, NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential journal.forward_to_socket, ignoring: %m"); + else { + r = socket_address_parse(&s->forward_to_socket, data); + if (r < 0) + log_debug_errno(r, "Failed to parse socket address '%s' from credential journal.forward_to_socket, ignoring: %m", (char *) data); + } + + data = mfree(data); + + r = read_credential("journal.storage", &data, NULL); + if (r < 0) + log_debug_errno(r, "Failed to read credential journal.storage, ignoring: %m"); + else { + r = storage_from_string(data); + if (r < 0) + log_debug_errno(r, "Failed to parse storage '%s' from credential journal.storage, ignoring: %m", (char *) data); + else + s->storage = r; + } +} + +int server_new(Server **ret) { + _cleanup_(server_freep) Server *s = NULL; + + assert(ret); + + s = new(Server, 1); + if (!s) + return -ENOMEM; + *s = (Server) { .syslog_fd = -EBADF, .native_fd = -EBADF, @@ -2460,6 +2590,7 @@ int server_init(Server *s, const char *namespace) { .audit_fd = -EBADF, .hostname_fd = -EBADF, .notify_fd = -EBADF, + .forward_socket_fd = -EBADF, .compress.enabled = true, .compress.threshold_bytes = UINT64_MAX, @@ -2476,6 +2607,7 @@ int server_init(Server *s, const char *namespace) { .ratelimit_burst = DEFAULT_RATE_LIMIT_BURST, .forward_to_wall = true, + .forward_to_socket = { .sockaddr.sa.sa_family = AF_UNSPEC }, .max_file_usec = DEFAULT_MAX_FILE_USEC, @@ -2484,6 +2616,7 @@ int server_init(Server *s, const char *namespace) { .max_level_kmsg = LOG_NOTICE, .max_level_console = LOG_INFO, .max_level_wall = LOG_EMERG, + .max_level_socket = LOG_DEBUG, .line_max = DEFAULT_LINE_MAX, @@ -2499,6 +2632,18 @@ int server_init(Server *s, const char *namespace) { .sigrtmin18_info.memory_pressure_userdata = s, }; + *ret = TAKE_PTR(s); + return 0; +} + +int server_init(Server *s, const char *namespace) { + const char *native_socket, *syslog_socket, *stdout_socket, *varlink_socket, *e; + _cleanup_fdset_free_ FDSet *fds = NULL; + int n, r, varlink_fd = -EBADF; + bool no_sockets; + + assert(s); + r = server_set_namespace(s, namespace); if (r < 0) return r; @@ -2510,6 +2655,7 @@ int server_init(Server *s, const char *namespace) { journal_reset_metrics(&s->system_storage.metrics); journal_reset_metrics(&s->runtime_storage.metrics); + server_load_credentials(s); server_parse_config_file(s); if (!s->namespace) { @@ -2562,7 +2708,7 @@ int server_init(Server *s, const char *namespace) { syslog_socket = strjoina(s->runtime_directory, "/dev-log"); varlink_socket = strjoina(s->runtime_directory, "/io.systemd.journal"); - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, native_socket, 0) > 0) { @@ -2615,12 +2761,11 @@ int server_init(Server *s, const char *namespace) { if (r < 0) return log_oom(); } - } /* Try to restore streams, but don't bother if this fails */ (void) server_restore_streams(s, fds); - if (fdset_size(fds) > 0) { + if (!fdset_isempty(fds)) { log_warning("%u unknown file descriptors passed, closing.", fdset_size(fds)); fds = fdset_free(fds); } @@ -2683,10 +2828,6 @@ int server_init(Server *s, const char *namespace) { if (r < 0) return r; - s->ratelimit = journal_ratelimit_new(); - if (!s->ratelimit) - return log_oom(); - r = cg_get_root_path(&s->cgroup_root); if (r < 0) return log_error_errno(r, "Failed to acquire cgroup root path: %m"); @@ -2721,6 +2862,7 @@ int server_init(Server *s, const char *namespace) { return r; server_start_or_stop_idle_timer(s); + return 0; } @@ -2739,8 +2881,9 @@ void server_maybe_append_tags(Server *s) { #endif } -void server_done(Server *s) { - assert(s); +Server* server_free(Server *s) { + if (!s) + return NULL; free(s->namespace); free(s->namespace_field); @@ -2783,9 +2926,9 @@ void server_done(Server *s) { safe_close(s->audit_fd); safe_close(s->hostname_fd); safe_close(s->notify_fd); + safe_close(s->forward_socket_fd); - if (s->ratelimit) - journal_ratelimit_free(s->ratelimit); + ordered_hashmap_free(s->ratelimit_groups_by_id); server_unmap_seqnum_file(s->seqnum, sizeof(*s->seqnum)); server_unmap_seqnum_file(s->kernel_seqnum, sizeof(*s->kernel_seqnum)); @@ -2799,6 +2942,8 @@ void server_done(Server *s) { free(s->runtime_directory); mmap_cache_unref(s->mmap); + + return mfree(s); } static const char* const storage_table[_STORAGE_MAX] = { @@ -2883,9 +3028,12 @@ int config_parse_compress( void *data, void *userdata) { - JournalCompressOptions* compress = data; + JournalCompressOptions* compress = ASSERT_PTR(data); int r; + assert(filename); + assert(rvalue); + if (isempty(rvalue)) { compress->enabled = true; compress->threshold_bytes = UINT64_MAX; @@ -2912,3 +3060,33 @@ int config_parse_compress( return 0; } + +int config_parse_forward_to_socket( + 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) { + + SocketAddress* addr = ASSERT_PTR(data); + int r; + + assert(filename); + assert(rvalue); + + if (isempty(rvalue)) + *addr = (SocketAddress) { .sockaddr.sa.sa_family = AF_UNSPEC }; + else { + r = socket_address_parse(addr, rvalue); + if (r < 0) + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse ForwardToSocket= value, ignoring: %s", rvalue); + } + + return 0; +} diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 2a17676..5138908 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -5,6 +5,7 @@ #include #include "sd-event.h" +#include "socket-util.h" typedef struct Server Server; @@ -13,7 +14,6 @@ typedef struct Server Server; #include "hashmap.h" #include "journal-file.h" #include "journald-context.h" -#include "journald-rate-limit.h" #include "journald-stream.h" #include "list.h" #include "prioq.h" @@ -78,6 +78,7 @@ struct Server { int audit_fd; int hostname_fd; int notify_fd; + int forward_socket_fd; sd_event *event; @@ -106,7 +107,7 @@ struct Server { char *buffer; - JournalRateLimit *ratelimit; + OrderedHashmap *ratelimit_groups_by_id; usec_t sync_interval_usec; usec_t ratelimit_interval; unsigned ratelimit_burst; @@ -123,6 +124,7 @@ struct Server { bool forward_to_syslog; bool forward_to_console; bool forward_to_wall; + SocketAddress forward_to_socket; unsigned n_forward_syslog_missed; usec_t last_warn_forward_syslog_missed; @@ -142,6 +144,7 @@ struct Server { int max_level_kmsg; int max_level_console; int max_level_wall; + int max_level_socket; Storage storage; SplitMode split_mode; @@ -158,8 +161,8 @@ struct Server { bool sent_notify_ready:1; bool sync_scheduled:1; - char machine_id_field[sizeof("_MACHINE_ID=") + 32]; - char boot_id_field[sizeof("_BOOT_ID=") + 32]; + char machine_id_field[STRLEN("_MACHINE_ID=") + SD_ID128_STRING_MAX]; + char boot_id_field[STRLEN("_BOOT_ID=") + SD_ID128_STRING_MAX]; char *hostname_field; char *namespace_field; char *runtime_directory; @@ -214,6 +217,7 @@ const struct ConfigPerfItem* journald_gperf_lookup(const char *key, GPERF_LEN_TY CONFIG_PARSER_PROTOTYPE(config_parse_storage); CONFIG_PARSER_PROTOTYPE(config_parse_line_max); CONFIG_PARSER_PROTOTYPE(config_parse_compress); +CONFIG_PARSER_PROTOTYPE(config_parse_forward_to_socket); const char *storage_to_string(Storage s) _const_; Storage storage_from_string(const char *s) _pure_; @@ -223,19 +227,17 @@ CONFIG_PARSER_PROTOTYPE(config_parse_split_mode); const char *split_mode_to_string(SplitMode s) _const_; SplitMode split_mode_from_string(const char *s) _pure_; +int server_new(Server **ret); int server_init(Server *s, const char *namespace); -void server_done(Server *s); -void server_sync(Server *s); +Server* server_free(Server *s); +DEFINE_TRIVIAL_CLEANUP_FUNC(Server*, server_free); void server_vacuum(Server *s, bool verbose); void server_rotate(Server *s); -int server_schedule_sync(Server *s, int priority); int server_flush_to_var(Server *s, bool require_flag_file); void server_maybe_append_tags(Server *s); int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); void server_space_usage_message(Server *s, JournalStorage *storage); int server_start_or_stop_idle_timer(Server *s); -int server_refresh_idle_timer(Server *s); int server_map_seqnum_file(Server *s, const char *fname, size_t size, void **ret); -void server_unmap_seqnum_file(void *p, size_t size); diff --git a/src/journal/journald-socket.c b/src/journal/journald-socket.c new file mode 100644 index 0000000..a079624 --- /dev/null +++ b/src/journal/journald-socket.c @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "fd-util.h" +#include "iovec-util.h" +#include "journald-socket.h" +#include "log.h" +#include "macro.h" +#include "process-util.h" +#include "socket-util.h" +#include "sparse-endian.h" + +static int server_open_forward_socket(Server *s) { + _cleanup_close_ int socket_fd = -EBADF; + const SocketAddress *addr; + int family; + + assert(s); + + /* Noop if there is nothing to do. */ + if (s->forward_to_socket.sockaddr.sa.sa_family == AF_UNSPEC || s->namespace) + return 0; + /* All ready, nothing to do. */ + if (s->forward_socket_fd >= 0) + return 1; + + addr = &s->forward_to_socket; + + family = socket_address_family(addr); + + if (!IN_SET(family, AF_UNIX, AF_INET, AF_INET6, AF_VSOCK)) + return log_debug_errno(SYNTHETIC_ERRNO(ESOCKTNOSUPPORT), + "Unsupported socket type for forward socket: %d", family); + + socket_fd = socket(family, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (socket_fd < 0) + return log_debug_errno(errno, "Failed to create forward socket, ignoring: %m"); + + if (connect(socket_fd, &addr->sockaddr.sa, addr->size) < 0) + return log_debug_errno(errno, "Failed to connect to remote address for forwarding, ignoring: %m"); + + s->forward_socket_fd = TAKE_FD(socket_fd); + log_debug("Successfully connected to remote address for forwarding."); + return 1; +} + +static inline bool must_serialize(struct iovec iov) { + /* checks an iovec of the form FIELD=VALUE to see if VALUE needs binary safe serialisation: + * See https://systemd.io/JOURNAL_EXPORT_FORMATS/#journal-export-format for more information + * on binary safe serialisation for the journal export format */ + + assert(iov.iov_len == 0 || iov.iov_base); + + const uint8_t *s = iov.iov_base; + bool before_value = true; + + FOREACH_ARRAY(c, s, iov.iov_len) + if (before_value) + before_value = *c != (uint8_t)'='; + else if (*c < (uint8_t)' ' && *c != (uint8_t)'\t') + return true; + + return false; +} + +int server_forward_socket( + Server *s, + const struct iovec *iovec, + size_t n_iovec, + const dual_timestamp *ts, + int priority) { + + _cleanup_free_ struct iovec *iov_alloc = NULL; + struct iovec *iov; + _cleanup_free_ le64_t *len_alloc = NULL; + le64_t *len; + int r; + + assert(s); + assert(iovec); + assert(n_iovec > 0); + assert(ts); + + if (LOG_PRI(priority) > s->max_level_socket) + return 0; + + r = server_open_forward_socket(s); + if (r <= 0) + return r; + + /* We need a newline after each iovec + 4 for each we have to serialize in a binary safe way + * + 2 for the final __REALTIME_TIMESTAMP and __MONOTONIC_TIMESTAMP metadata fields. */ + size_t n = n_iovec * 5 + 2; + + if (n < ALLOCA_MAX / (sizeof(struct iovec) + sizeof(le64_t)) / 2) { + iov = newa(struct iovec, n); + len = newa(le64_t, n_iovec); + } else { + iov_alloc = new(struct iovec, n); + if (!iov_alloc) + return log_oom(); + + iov = iov_alloc; + + len_alloc = new(le64_t, n_iovec); + if (!len_alloc) + return log_oom(); + + len = len_alloc; + } + + struct iovec nl = IOVEC_MAKE_STRING("\n"); + size_t iov_idx = 0, len_idx = 0; + FOREACH_ARRAY(i, iovec, n_iovec) { + if (must_serialize(*i)) { + const uint8_t *c; + c = memchr(i->iov_base, '=', i->iov_len); + + /* this should never happen */ + if (_unlikely_(!c || c == i->iov_base)) + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), + "Found invalid journal field, refusing to forward."); + + /* write the field name */ + iov[iov_idx++] = IOVEC_MAKE(i->iov_base, c - (uint8_t*) i->iov_base); + iov[iov_idx++] = nl; + + /* write the length of the value */ + len[len_idx] = htole64(i->iov_len - (c - (uint8_t*) i->iov_base) - 1); + iov[iov_idx++] = IOVEC_MAKE(&len[len_idx++], sizeof(le64_t)); + + /* write the raw binary value */ + iov[iov_idx++] = IOVEC_MAKE(c + 1, i->iov_len - (c - (uint8_t*) i->iov_base) - 1); + } else + /* if it doesn't need special treatment just write the value out */ + iov[iov_idx++] = *i; + + iov[iov_idx++] = nl; + } + + /* Synthesise __REALTIME_TIMESTAMP and __MONOTONIC_TIMESTAMP as the last arguments so + * systemd-journal-upload can receive these export messages. */ + char realtime_buf[STRLEN("__REALTIME_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t) + 1]; + xsprintf(realtime_buf, "__REALTIME_TIMESTAMP="USEC_FMT"\n", ts->realtime); + iov[iov_idx++] = IOVEC_MAKE_STRING(realtime_buf); + + char monotonic_buf[STRLEN("__MONOTONIC_TIMESTAMP=") + DECIMAL_STR_MAX(usec_t) + 2]; + xsprintf(monotonic_buf, "__MONOTONIC_TIMESTAMP="USEC_FMT"\n\n", ts->monotonic); + iov[iov_idx++] = IOVEC_MAKE_STRING(monotonic_buf); + + if (writev(s->forward_socket_fd, iov, iov_idx) < 0) { + log_debug_errno(errno, "Failed to forward log message over socket: %m"); + + /* If we failed to send once we will probably fail again so wait for a new connection to + * establish before attempting to forward again. */ + s->forward_socket_fd = safe_close(s->forward_socket_fd); + } + + return 0; +} diff --git a/src/journal/journald-socket.h b/src/journal/journald-socket.h new file mode 100644 index 0000000..2373953 --- /dev/null +++ b/src/journal/journald-socket.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "journald-server.h" +#include "socket-util.h" + +int server_forward_socket(Server *s, const struct iovec *iovec, size_t n, const dual_timestamp *ts, int priority); diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 81a0e68..e8437e3 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -310,7 +310,7 @@ static int stdout_stream_log( syslog_priority[STRLEN("PRIORITY=")] = '0' + LOG_PRI(priority); iovec[n++] = IOVEC_MAKE_STRING(syslog_priority); - if (priority & LOG_FACMASK) { + if (LOG_FAC(priority) != 0) { xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); iovec[n++] = IOVEC_MAKE_STRING(syslog_facility); } diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index f6accb5..4ad73ed 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -177,8 +177,8 @@ void server_forward_syslog(Server *s, int priority, const char *identifier, cons int syslog_fixup_facility(int priority) { - if ((priority & LOG_FACMASK) == 0) - return (priority & LOG_PRIMASK) | LOG_USER; + if (LOG_FAC(priority) == 0) + return LOG_PRI(priority) | LOG_USER; return priority; } @@ -314,8 +314,8 @@ void server_process_syslog_message( const char *label, size_t label_len) { - char *t, syslog_priority[sizeof("PRIORITY=") + DECIMAL_STR_MAX(int)], - syslog_facility[sizeof("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; + char *t, syslog_priority[STRLEN("PRIORITY=") + DECIMAL_STR_MAX(int)], + syslog_facility[STRLEN("SYSLOG_FACILITY=") + DECIMAL_STR_MAX(int)]; const char *msg, *syslog_ts, *a; _cleanup_free_ char *identifier = NULL, *pid = NULL, *dummy = NULL, *msg_msg = NULL, *msg_raw = NULL; @@ -403,10 +403,10 @@ void server_process_syslog_message( iovec[n++] = IOVEC_MAKE_STRING("_TRANSPORT=syslog"); - xsprintf(syslog_priority, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(syslog_priority, "PRIORITY=%i", LOG_PRI(priority)); iovec[n++] = IOVEC_MAKE_STRING(syslog_priority); - if (priority & LOG_FACMASK) { + if (LOG_FAC(priority) != 0) { xsprintf(syslog_facility, "SYSLOG_FACILITY=%i", LOG_FAC(priority)); iovec[n++] = IOVEC_MAKE_STRING(syslog_facility); } diff --git a/src/journal/journald.c b/src/journal/journald.c index 94aad05..2f013c2 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -10,19 +10,19 @@ #include "journald-kmsg.h" #include "journald-server.h" #include "journald-syslog.h" +#include "main-func.h" #include "process-util.h" #include "sigbus.h" +#include "terminal-util.h" -int main(int argc, char *argv[]) { +static int run(int argc, char *argv[]) { + _cleanup_(server_freep) Server *s = NULL; const char *namespace; LogTarget log_target; - Server server; int r; - if (argc > 2) { - log_error("This program takes one or no arguments."); - return EXIT_FAILURE; - } + if (argc > 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes one or no arguments."); namespace = argc > 1 ? empty_to_null(argv[1]) : NULL; @@ -36,7 +36,7 @@ int main(int argc, char *argv[]) { * daemon when it comes to logging hence LOG_TARGET_AUTO won't do the right thing for * us. Hence explicitly log to the console if we're started from a console or to kmsg * otherwise. */ - log_target = isatty(STDERR_FILENO) > 0 ? LOG_TARGET_CONSOLE : LOG_TARGET_KMSG; + log_target = isatty(STDERR_FILENO) ? LOG_TARGET_CONSOLE : LOG_TARGET_KMSG; log_set_prohibit_ipc(true); /* better safe than sorry */ log_set_target(log_target); @@ -48,20 +48,24 @@ int main(int argc, char *argv[]) { sigbus_install(); - r = server_init(&server, namespace); + r = server_new(&s); + if (r < 0) + return log_oom(); + + r = server_init(s, namespace); if (r < 0) - goto finish; + return r; - server_vacuum(&server, false); - server_flush_to_var(&server, true); - server_flush_dev_kmsg(&server); + server_vacuum(s, /* verbose = */ false); + server_flush_to_var(s, /* require_flag_file = */ true); + server_flush_dev_kmsg(s); - if (server.namespace) - log_debug("systemd-journald running as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace); + if (s->namespace) + log_debug("systemd-journald running as PID "PID_FMT" for namespace '%s'.", getpid_cached(), s->namespace); else log_debug("systemd-journald running as PID "PID_FMT" for the system.", getpid_cached()); - server_driver_message(&server, 0, + server_driver_message(s, 0, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_START_STR, LOG_MESSAGE("Journal started"), NULL); @@ -69,70 +73,63 @@ int main(int argc, char *argv[]) { /* Make sure to send the usage message *after* flushing the * journal so entries from the runtime journals are ordered * before this message. See #4190 for some details. */ - server_space_usage_message(&server, NULL); + server_space_usage_message(s, NULL); for (;;) { - usec_t t = USEC_INFINITY, n; + usec_t t, n; - r = sd_event_get_state(server.event); - if (r < 0) { - log_error_errno(r, "Failed to get event loop state: %m"); - goto finish; - } + r = sd_event_get_state(s->event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop state: %m"); if (r == SD_EVENT_FINISHED) break; - n = now(CLOCK_REALTIME); + r = sd_event_now(s->event, CLOCK_REALTIME, &n); + if (r < 0) + return log_error_errno(r, "Failed to get the current time: %m"); - if (server.max_retention_usec > 0 && server.oldest_file_usec > 0) { + if (s->max_retention_usec > 0 && s->oldest_file_usec > 0) { + /* Calculate when to rotate the next time */ + t = usec_sub_unsigned(usec_add(s->oldest_file_usec, s->max_retention_usec), n); /* The retention time is reached, so let's vacuum! */ - if (server.oldest_file_usec + server.max_retention_usec < n) { + if (t <= 0) { log_info("Retention time reached, rotating."); - server_rotate(&server); - server_vacuum(&server, false); + server_rotate(s); + server_vacuum(s, /* verbose = */ false); continue; } - - /* Calculate when to rotate the next time */ - t = server.oldest_file_usec + server.max_retention_usec - n; - } + } else + t = USEC_INFINITY; #if HAVE_GCRYPT - if (server.system_journal) { + if (s->system_journal) { usec_t u; - if (journal_file_next_evolve_usec(server.system_journal, &u)) { - if (n >= u) - t = 0; - else - t = MIN(t, u - n); - } + if (journal_file_next_evolve_usec(s->system_journal, &u)) + t = MIN(t, usec_sub_unsigned(u, n)); } #endif - r = sd_event_run(server.event, t); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } + r = sd_event_run(s->event, t); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); - server_maybe_append_tags(&server); - server_maybe_warn_forward_syslog_missed(&server); + server_maybe_append_tags(s); + server_maybe_warn_forward_syslog_missed(s); } - if (server.namespace) - log_debug("systemd-journald stopped as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace); + if (s->namespace) + log_debug("systemd-journald stopped as PID "PID_FMT" for namespace '%s'.", getpid_cached(), s->namespace); else log_debug("systemd-journald stopped as PID "PID_FMT" for the system.", getpid_cached()); - server_driver_message(&server, 0, + server_driver_message(s, 0, "MESSAGE_ID=" SD_MESSAGE_JOURNAL_STOP_STR, LOG_MESSAGE("Journal stopped"), NULL); -finish: - server_done(&server); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + return 0; } + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/journal/journald.conf b/src/journal/journald.conf index 7b9e232..13cdd63 100644 --- a/src/journal/journald.conf +++ b/src/journal/journald.conf @@ -44,6 +44,7 @@ #MaxLevelKMsg=notice #MaxLevelConsole=info #MaxLevelWall=emerg +#MaxLevelSocket=debug #LineMax=48K #ReadKMsg=yes #Audit=yes diff --git a/src/journal/meson.build b/src/journal/meson.build index 36600bf..9f0e699 100644 --- a/src/journal/meson.build +++ b/src/journal/meson.build @@ -12,6 +12,7 @@ sources = files( 'journald-stream.c', 'journald-syslog.c', 'journald-wall.c', + 'journald-socket.c', ) sources += custom_target( @@ -28,11 +29,24 @@ libjournal_core = static_library( userspace], build_by_default : false) +journalctl_sources = files( + 'journalctl.c', + 'journalctl-catalog.c', + 'journalctl-filter.c', + 'journalctl-misc.c', + 'journalctl-show.c', + 'journalctl-util.c', + 'journalctl-varlink.c', +) + +if conf.get('HAVE_GCRYPT') == 1 + journalctl_sources += files('journalctl-authenticate.c') +endif + if get_option('link-journalctl-shared') journalctl_link_with = [libshared] else journalctl_link_with = [ - libbasic_gcrypt, libshared_static, libsystemd_static, ] @@ -62,10 +76,10 @@ executables += [ libshared, ], 'dependencies' : [ - liblz4, + liblz4_cflags, libselinux, - libxz, - libzstd, + libxz_cflags, + libzstd_cflags, threads, ], }, @@ -90,30 +104,36 @@ executables += [ executable_template + { 'name' : 'journalctl', 'public' : true, - 'sources' : files('journalctl.c'), + 'sources' : journalctl_sources, 'link_with' : journalctl_link_with, 'dependencies' : [ libdl, - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ], }, journal_test_template + { 'sources' : files('test-journald-config.c'), 'dependencies' : [ - liblz4, + liblz4_cflags, libselinux, - libxz, + libxz_cflags, ], }, + test_template + { + 'sources' : files( + 'test-journald-rate-limit.c', + 'journald-rate-limit.c', + ), + }, journal_test_template + { 'sources' : files('test-journald-syslog.c'), 'dependencies' : [ - liblz4, + liblz4_cflags, libselinux, - libxz, + libxz_cflags, threads, ], }, diff --git a/src/journal/test-journald-config.c b/src/journal/test-journald-config.c index 1a6c531..900b1db 100644 --- a/src/journal/test-journald-config.c +++ b/src/journal/test-journald-config.c @@ -1,8 +1,15 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include +#include +#include #include "journald-server.h" +#include "log.h" +#include "path-util.h" +#include "socket-util.h" +#include "sparse-endian.h" #include "tests.h" #define _COMPRESS_PARSE_CHECK(str, enab, thresh, varname) \ @@ -47,4 +54,125 @@ TEST(config_compress) { COMPRESS_PARSE_CHECK("", true, UINT64_MAX); } +#define _FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, varname) \ + do { \ + SocketAddress varname = {}; \ + config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \ + &varname, NULL); \ + assert_se(socket_address_verify(&varname, true) < 0); \ + } while (0) + +#define FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str) \ + _FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(str, addr, conf##__COUNTER__) + +#define _FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, varname) \ + do { \ + SocketAddress varname = {}; \ + config_parse_forward_to_socket("", "", 0, "", 0, "", 0, str, \ + &varname, NULL); \ + buf = mfree(buf); \ + buf2 = mfree(buf2); \ + socket_address_print(&varname, &buf); \ + socket_address_print(&addr, &buf2); \ + log_info("\"%s\" parsed as \"%s\", should be \"%s\"", str, buf, buf2); \ + log_info("socket_address_verify(&addr, false) = %d", socket_address_verify(&addr, false)); \ + log_info("socket_address_verify(&varname, false) = %d", socket_address_verify(&varname, false)); \ + log_info("socket_address_family(&addr) = %d", socket_address_family(&addr)); \ + log_info("socket_address_family(&varname) = %d", socket_address_family(&varname)); \ + log_info("addr.size = %u", addr.size); \ + log_info("varname.size = %u", varname.size); \ + assert_se(socket_address_equal(&varname, &addr)); \ + } while (0) + +#define FORWARD_TO_SOCKET_PARSE_CHECK(str, addr) \ + _FORWARD_TO_SOCKET_PARSE_CHECK(str, addr, conf##__COUNTER__) + +TEST(config_forward_to_socket) { + SocketAddress addr; + _cleanup_free_ char *buf = NULL, *buf2 = NULL; + + /* Valid AF_UNIX */ + addr = (SocketAddress) { + .sockaddr.un = (struct sockaddr_un) { + .sun_family = AF_UNIX, + .sun_path = "/run/host/journal/socket", + }, + .size = offsetof(struct sockaddr_un, sun_path) + strlen("/run/host/journal/socket") + 1, + }; + FORWARD_TO_SOCKET_PARSE_CHECK("/run/host/journal/socket", addr); + + addr.size -= 1; + memcpy(addr.sockaddr.un.sun_path, "\0run/host/journal/socket", sizeof("\0run/host/journal/socket")); + FORWARD_TO_SOCKET_PARSE_CHECK("@run/host/journal/socket", addr); + + /* Valid AF_INET */ + addr = (SocketAddress) { + .sockaddr.in = (struct sockaddr_in) { + .sin_family = AF_INET, + .sin_addr = { htobe32(0xC0A80001) }, + .sin_port = htobe16(1234), + }, + .size = sizeof(struct sockaddr_in), + }; + FORWARD_TO_SOCKET_PARSE_CHECK("192.168.0.1:1234", addr); + + /* Valid AF_INET6 */ + addr = (SocketAddress) { + .sockaddr.in6 = (struct sockaddr_in6) { + .sin6_family = AF_INET6, + .sin6_addr = (struct in6_addr) { + .s6_addr16 = { + htobe16(0x2001), + htobe16(0xdb8), + htobe16(0x4006), + htobe16(0x812), + 0, 0, 0, + htobe16(0x200e) + } + }, + .sin6_port = htobe16(8080), + }, + .size = sizeof(struct sockaddr_in6), + }; + FORWARD_TO_SOCKET_PARSE_CHECK("[2001:db8:4006:812::200e]:8080", addr); + + /* Valid AF_VSOCK */ + addr = (SocketAddress) { + .sockaddr.vm = (struct sockaddr_vm) { + .svm_family = AF_VSOCK, + .svm_cid = 123456, + .svm_port = 654321, + }, + .size = sizeof(struct sockaddr_vm), + }; + FORWARD_TO_SOCKET_PARSE_CHECK("vsock:123456:654321", addr); + + /* Invalid IPv4 */ + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("256.123.45.12:1235"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:123500"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:0"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("252.123.45.12:-1"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("-1.123.45.12:22"); + + /* Invalid IPv6 */ + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:80800"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[1ffff:db8:4006:812::200e]:8080"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[-1:db8:4006:812::200e]:8080"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("[2001:db8:4006:812::200e]:-1"); + + /* Invalid UNIX */ + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("a/b/c"); + + /* Invalid VSock */ + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:4294967296:1234"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:4294967296"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:abcd:1234"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234:abcd"); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("vsock:1234"); + + /* Invalid Case */ + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS(""); + FORWARD_TO_SOCKET_PARSE_CHECK_FAILS("ahh yes sockets, mmh"); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/journal/test-journald-rate-limit.c b/src/journal/test-journald-rate-limit.c new file mode 100644 index 0000000..a08faba --- /dev/null +++ b/src/journal/test-journald-rate-limit.c @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "journald-rate-limit.h" +#include "tests.h" + +TEST(journal_ratelimit_test) { + _cleanup_ordered_hashmap_free_ OrderedHashmap *rl = NULL; + int r; + + for (unsigned i = 0; i < 20; i++) { + r = journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0); + assert_se(r == (i < 10 ? 1 : 0)); + r = journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0); + assert_se(r == (i < 10 ? 1 : 0)); + } + + /* Different priority group with the same ID is not ratelimited. */ + assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1); + assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1); + /* Still LOG_DEBUG is ratelimited. */ + assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0); + assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0); + /* Different ID is not ratelimited. */ + assert_se(journal_ratelimit_test(&rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1); + + usleep_safe(USEC_PER_SEC); + + /* The ratelimit is now expired (11 trials are suppressed, so the return value should be 12). */ + assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1 + 11); + + /* foo is still ratelimited. */ + assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_DEBUG, 0) == 0); + + /* Still other priority and/or other IDs are not ratelimited. */ + assert_se(journal_ratelimit_test(&rl, "hoge", USEC_PER_SEC, 10, LOG_INFO, 0) == 1); + assert_se(journal_ratelimit_test(&rl, "foo", 10 * USEC_PER_SEC, 10, LOG_INFO, 0) == 1); + assert_se(journal_ratelimit_test(&rl, "quux", USEC_PER_SEC, 10, LOG_DEBUG, 0) == 1); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/kernel-install/60-ukify.install.in b/src/kernel-install/60-ukify.install.in index 0f7a0db..54a5daf 100755 --- a/src/kernel-install/60-ukify.install.in +++ b/src/kernel-install/60-ukify.install.in @@ -21,13 +21,13 @@ import argparse import os -import runpy import shlex +import types from shutil import which from pathlib import Path from typing import Optional -__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})' +__version__ = '{{PROJECT_VERSION_FULL}} ({{VERSION_TAG}})' try: VERBOSE = int(os.environ['KERNEL_INSTALL_VERBOSE']) > 0 @@ -151,7 +151,10 @@ def input_file_location( def uki_conf_location() -> Optional[Path]: return input_file_location('uki.conf', - '/etc/kernel') + '/etc/kernel', + '/run/kernel', + '/usr/local/lib/kernel', + '/usr/lib/kernel') def devicetree_config_location() -> Optional[Path]: @@ -216,22 +219,20 @@ def initrd_list(opts) -> list[Path]: return [*microcode, *opts.initrd, *initrd] -def call_ukify(opts): - # Punish me harder. - # We want this: - # ukify = importlib.machinery.SourceFileLoader('ukify', UKIFY).load_module() - # but it throws a DeprecationWarning. - # https://stackoverflow.com/questions/67631/how-can-i-import-a-module-dynamically-given-the-full-path - # https://github.com/python/cpython/issues/65635 - # offer "explanations", but to actually load a python file without a .py extension, - # the "solution" is 4+ incomprehensible lines. - # The solution with runpy gives a dictionary, which isn't great, but will do. - ukify = runpy.run_path(UKIFY, run_name='ukify') +def load_module(name: str, path: str) -> types.ModuleType: + module = types.ModuleType(name) + text = open(path).read() + exec(compile(text, path, 'exec'), module.__dict__) + return module + + +def call_ukify(opts) -> None: + ukify = load_module('ukify', UKIFY) # Create "empty" namespace. We want to override just a few settings, so it # doesn't make sense to configure everything. We pretend to parse an empty # argument set to prepopulate the namespace with the defaults. - opts2 = ukify['create_parser']().parse_args(['build']) + opts2 = ukify.create_parser().parse_args(['build']) opts2.config = uki_conf_location() opts2.uname = opts.kernel_version @@ -247,12 +248,10 @@ def call_ukify(opts): if BOOT_STUB: opts2.stub = BOOT_STUB - # opts2.summary = True - - ukify['apply_config'](opts2) - ukify['finalize_options'](opts2) - ukify['check_inputs'](opts2) - ukify['make_uki'](opts2) + ukify.apply_config(opts2) + ukify.finalize_options(opts2) + ukify.check_inputs(opts2) + ukify.make_uki(opts2) log(f'{opts2.output} has been created') diff --git a/src/kernel-install/90-loaderentry.install.in b/src/kernel-install/90-loaderentry.install.in index a52dd81..766d321 100755 --- a/src/kernel-install/90-loaderentry.install.in +++ b/src/kernel-install/90-loaderentry.install.in @@ -181,7 +181,7 @@ mkdir -p "${LOADER_ENTRY%/*}" || { [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Creating $LOADER_ENTRY" { echo "# Boot Loader Specification type#1 entry" - echo "# File created by $0 (systemd {{GIT_VERSION}})" + echo "# File created by $0 (systemd {{VERSION_TAG}})" echo "title $PRETTY_NAME" echo "version $KERNEL_VERSION" if [ "$ENTRY_TOKEN" = "$MACHINE_ID" ]; then diff --git a/src/kernel-install/kernel-install.c b/src/kernel-install/kernel-install.c index 14ae1a8..5d559a9 100644 --- a/src/kernel-install/kernel-install.c +++ b/src/kernel-install/kernel-install.c @@ -19,6 +19,7 @@ #include "fs-util.h" #include "id128-util.h" #include "image-policy.h" +#include "kernel-config.h" #include "kernel-image.h" #include "main-func.h" #include "mkdir.h" @@ -49,6 +50,7 @@ static bool arg_legend = true; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); typedef enum Action { @@ -150,37 +152,37 @@ static int context_copy(const Context *source, Context *ret) { return copy.rfd; } - r = strdup_or_null(source->layout_other, ©.layout_other); + r = strdup_to(©.layout_other, source->layout_other); if (r < 0) return r; - r = strdup_or_null(source->conf_root, ©.conf_root); + r = strdup_to(©.conf_root, source->conf_root); if (r < 0) return r; - r = strdup_or_null(source->boot_root, ©.boot_root); + r = strdup_to(©.boot_root, source->boot_root); if (r < 0) return r; - r = strdup_or_null(source->entry_token, ©.entry_token); + r = strdup_to(©.entry_token, source->entry_token); if (r < 0) return r; - r = strdup_or_null(source->entry_dir, ©.entry_dir); + r = strdup_to(©.entry_dir, source->entry_dir); if (r < 0) return r; - r = strdup_or_null(source->version, ©.version); + r = strdup_to(©.version, source->version); if (r < 0) return r; - r = strdup_or_null(source->kernel, ©.kernel); + r = strdup_to(©.kernel, source->kernel); if (r < 0) return r; r = strv_copy_unless_empty(source->initrds, ©.initrds); if (r < 0) return r; - r = strdup_or_null(source->initrd_generator, ©.initrd_generator); + r = strdup_to(©.initrd_generator, source->initrd_generator); if (r < 0) return r; - r = strdup_or_null(source->uki_generator, ©.uki_generator); + r = strdup_to(©.uki_generator, source->uki_generator); if (r < 0) return r; - r = strdup_or_null(source->staging_area, ©.staging_area); + r = strdup_to(©.staging_area, source->staging_area); if (r < 0) return r; r = strv_copy_unless_empty(source->plugins, ©.plugins); @@ -430,79 +432,30 @@ static int context_load_environment(Context *c) { return 0; } -static int context_ensure_conf_root(Context *c) { - int r; - - assert(c); - - if (c->conf_root) - return 0; - - r = chaseat(c->rfd, "/etc/kernel", CHASE_AT_RESOLVE_IN_ROOT, &c->conf_root, /* ret_fd = */ NULL); - if (r < 0) - log_debug_errno(r, "Failed to chase /etc/kernel, ignoring: %m"); - - return 0; -} - -static int context_load_install_conf_one(Context *c, const char *path) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char - *conf = NULL, *machine_id = NULL, *boot_root = NULL, *layout = NULL, - *initrd_generator = NULL, *uki_generator = NULL; - int r; - - assert(c); - assert(path); - - conf = path_join(path, "install.conf"); - if (!conf) - return log_oom(); - - r = chase_and_fopenat_unlocked(c->rfd, conf, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to chase %s: %m", conf); - - log_debug("Loading %s…", conf); - - r = parse_env_file(f, conf, - "MACHINE_ID", &machine_id, - "BOOT_ROOT", &boot_root, - "layout", &layout, - "initrd_generator", &initrd_generator, - "uki_generator", &uki_generator); - if (r < 0) - return log_error_errno(r, "Failed to parse '%s': %m", conf); - - (void) context_set_machine_id(c, machine_id, conf); - (void) context_set_boot_root(c, boot_root, conf); - (void) context_set_layout(c, layout, conf); - (void) context_set_initrd_generator(c, initrd_generator, conf); - (void) context_set_uki_generator(c, uki_generator, conf); - - log_debug("Loaded %s.", conf); - return 1; -} - static int context_load_install_conf(Context *c) { + _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL, + *initrd_generator = NULL, *uki_generator = NULL; int r; assert(c); - if (c->conf_root) { - r = context_load_install_conf_one(c, c->conf_root); - if (r != 0) - return r; - } + r = load_kernel_install_conf(arg_root, + c->conf_root, + &machine_id, + &boot_root, + &layout, + &initrd_generator, + &uki_generator); + if (r <= 0) + return r; - STRV_FOREACH(p, STRV_MAKE("/etc/kernel", "/usr/lib/kernel")) { - r = context_load_install_conf_one(c, *p); - if (r != 0) - return r; - } + (void) context_set_machine_id(c, machine_id, "config"); + (void) context_set_boot_root(c, boot_root, "config"); + (void) context_set_layout(c, layout, "config"); + (void) context_set_initrd_generator(c, initrd_generator, "config"); + (void) context_set_uki_generator(c, uki_generator, "config"); + log_debug("Loaded config."); return 0; } @@ -525,7 +478,7 @@ static int context_load_machine_info(Context *c) { if (r < 0 && r != -ENXIO) log_warning_errno(r, "Failed to read $KERNEL_INSTALL_READ_MACHINE_INFO, assuming yes: %m"); if (r == 0) { - log_debug("Skipping to read /etc/machine-info."); + log_debug("Skipping reading of /etc/machine-info."); return 0; } @@ -732,10 +685,6 @@ static int context_init(Context *c) { if (r < 0) return r; - r = context_ensure_conf_root(c); - if (r < 0) - return r; - r = context_load_install_conf(c); if (r < 0) return r; @@ -819,7 +768,7 @@ static int context_ensure_layout(Context *c) { if (!entry_token_path) return log_oom(); - r = is_dir_full(c->rfd, entry_token_path, /* follow = */ false); + r = is_dir_at(c->rfd, entry_token_path, /* follow = */ false); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to check if '%s' is a directory: %m", entry_token_path); if (r > 0) { @@ -996,11 +945,10 @@ static int context_build_arguments(Context *c) { return log_oom(); } else if (c->action == ACTION_INSPECT) { - r = strv_extend(&a, c->kernel ?: "[KERNEL_IMAGE]"); - if (r < 0) - return log_oom(); - - r = strv_extend(&a, "[INITRD...]"); + r = strv_extend_many( + &a, + c->kernel ?: "[KERNEL_IMAGE]", + "[INITRD...]"); if (r < 0) return log_oom(); } @@ -1184,7 +1132,7 @@ static int verb_add(int argc, char *argv[], void *userdata) { assert(argv); if (arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root=."); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add' does not support --root= or --image=."); if (bypass()) return 0; @@ -1223,14 +1171,17 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { assert(argv); + if (arg_root) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'add-all' does not support --root= or --image=."); + if (bypass()) return 0; c->action = ACTION_ADD; - fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) - return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m"); + return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); _cleanup_free_ DirectoryEntries *de = NULL; r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); @@ -1238,15 +1189,10 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to numerate /usr/lib/modules/ contents: %m"); FOREACH_ARRAY(d, de->entries, de->n_entries) { - - _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name); - if (!j) - return log_oom(); - r = dirent_ensure_type(fd, *d); if (r < 0) { if (r != -ENOENT) /* don't log if just gone by now */ - log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j); + log_debug_errno(r, "Failed to check if '%s/usr/lib/modules/%s' is a directory, ignoring: %m", strempty(arg_root), (*d)->d_name); continue; } @@ -1259,7 +1205,7 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { if (errno != ENOENT) - log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name); + log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name); log_notice("Not adding version '%s', because kernel image not found.", (*d)->d_name); continue; @@ -1271,6 +1217,8 @@ static int verb_add_all(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to copy execution context: %m"); + /* do_add() will look up the path in the correct root directory so we don't need to prefix it + * with arg_root here. */ _cleanup_free_ char *full = path_join("/usr/lib/modules/", fn); if (!full) return log_oom(); @@ -1311,7 +1259,7 @@ static int verb_remove(int argc, char *argv[], void *userdata) { assert(argv); if (arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root=."); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "'remove' does not support --root= or --image=."); if (argc > 2) log_debug("Too many arguments specified. 'kernel-install remove' takes only kernel version. " @@ -1449,12 +1397,13 @@ static int verb_inspect(int argc, char *argv[], void *userdata) { } static int verb_list(int argc, char *argv[], void *userdata) { + Context *c = ASSERT_PTR(userdata); _cleanup_close_ int fd = -EBADF; int r; - fd = open("/usr/lib/modules", O_DIRECTORY|O_RDONLY|O_CLOEXEC); + fd = chase_and_openat(c->rfd, "/usr/lib/modules", CHASE_AT_RESOLVE_IN_ROOT, O_DIRECTORY|O_RDONLY|O_CLOEXEC, NULL); if (fd < 0) - return log_error_errno(fd, "Failed to open /usr/lib/modules/: %m"); + return log_error_errno(fd, "Failed to open %s/usr/lib/modules/: %m", strempty(arg_root)); _cleanup_free_ DirectoryEntries *de = NULL; r = readdir_all(fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de); @@ -1470,7 +1419,6 @@ static int verb_list(int argc, char *argv[], void *userdata) { table_set_align_percent(table, table_get_cell(table, 0, 1), 100); FOREACH_ARRAY(d, de->entries, de->n_entries) { - _cleanup_free_ char *j = path_join("/usr/lib/modules/", (*d)->d_name); if (!j) return log_oom(); @@ -1478,7 +1426,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { r = dirent_ensure_type(fd, *d); if (r < 0) { if (r != -ENOENT) /* don't log if just gone by now */ - log_debug_errno(r, "Failed to check if '%s' is a directory, ignoring: %m", j); + log_debug_errno(r, "Failed to check if '%s/%s' is a directory, ignoring: %m", strempty(arg_root), j); continue; } @@ -1492,7 +1440,7 @@ static int verb_list(int argc, char *argv[], void *userdata) { bool exists; if (faccessat(fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { if (errno != ENOENT) - log_debug_errno(errno, "Failed to check if '/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", (*d)->d_name); + log_debug_errno(errno, "Failed to check if '%s/usr/lib/modules/%s/vmlinuz' exists, ignoring: %m", strempty(arg_root), (*d)->d_name); exists = false; } else @@ -1718,7 +1666,8 @@ static int run(int argc, char* argv[]) { DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_VALIDATE_OS, + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/kernel-install/test-kernel-install.sh b/src/kernel-install/test-kernel-install.sh index 338d811..0e41979 100755 --- a/src/kernel-install/test-kernel-install.sh +++ b/src/kernel-install/test-kernel-install.sh @@ -125,7 +125,8 @@ grep -qE 'initrd' "$BOOT_ROOT/the-token/1.1.1/initrd" # Install UKI if [ -f "$ukify" ]; then - cat >>"$D/sources/install.conf" <>"$D/sources/install.conf.d/override.conf" <= MIN_CLIENT_ID_LEN && size <= MAX_CLIENT_ID_LEN; +} + +static inline bool client_id_data_size_is_valid(size_t size) { + return size >= MIN_CLIENT_ID_DATA_LEN && size <= MAX_CLIENT_ID_DATA_LEN; +} + +void client_id_hash_func(const sd_dhcp_client_id *client_id, struct siphash *state); +int client_id_compare_func(const sd_dhcp_client_id *a, const sd_dhcp_client_id *b); + +int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); diff --git a/src/libsystemd-network/dhcp-duid-internal.h b/src/libsystemd-network/dhcp-duid-internal.h new file mode 100644 index 0000000..f8bc15c --- /dev/null +++ b/src/libsystemd-network/dhcp-duid-internal.h @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-device.h" +#include "sd-dhcp-duid.h" +#include "sd-id128.h" + +#include "ether-addr-util.h" +#include "macro.h" +#include "sparse-endian.h" + +#define SYSTEMD_PEN 43793 + +typedef enum DUIDType { + DUID_TYPE_LLT = SD_DUID_TYPE_LLT, + DUID_TYPE_EN = SD_DUID_TYPE_EN, + DUID_TYPE_LL = SD_DUID_TYPE_LL, + DUID_TYPE_UUID = SD_DUID_TYPE_UUID, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -EINVAL, +} DUIDType; + +/* RFC 8415 section 11.1: + * A DUID consists of a 2-octet type code represented in network byte order, followed by a variable number of + * octets that make up the actual identifier. The length of the DUID (not including the type code) is at + * least 1 octet and at most 128 octets. */ +#define MIN_DUID_DATA_LEN 1 +#define MAX_DUID_DATA_LEN 128 +#define MIN_DUID_LEN (sizeof(be16_t) + MIN_DUID_DATA_LEN) +#define MAX_DUID_LEN (sizeof(be16_t) + MAX_DUID_DATA_LEN) + +/* https://tools.ietf.org/html/rfc3315#section-9.1 */ +struct duid { + be16_t type; + union { + struct { + /* DUID_TYPE_LLT */ + be16_t htype; + be32_t time; + uint8_t haddr[]; + } _packed_ llt; + struct { + /* DUID_TYPE_EN */ + be32_t pen; + uint8_t id[]; + } _packed_ en; + struct { + /* DUID_TYPE_LL */ + be16_t htype; + uint8_t haddr[]; + } _packed_ ll; + struct { + /* DUID_TYPE_UUID */ + sd_id128_t uuid; + } _packed_ uuid; + uint8_t data[MAX_DUID_DATA_LEN]; + }; +} _packed_; + +typedef struct sd_dhcp_duid { + size_t size; + union { + struct duid duid; + uint8_t raw[MAX_DUID_LEN]; + }; +} sd_dhcp_duid; + +static inline bool duid_size_is_valid(size_t size) { + return size >= MIN_DUID_LEN && size <= MAX_DUID_LEN; +} + +static inline bool duid_data_size_is_valid(size_t size) { + return size >= MIN_DUID_DATA_LEN && size <= MAX_DUID_DATA_LEN; +} + +const char *duid_type_to_string(DUIDType t) _const_; +int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret); + +int dhcp_identifier_set_iaid( + sd_device *dev, + const struct hw_addr_data *hw_addr, + bool legacy_unstable_byteorder, + void *ret); diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c deleted file mode 100644 index f65cdbe..0000000 --- a/src/libsystemd-network/dhcp-identifier.c +++ /dev/null @@ -1,209 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include -#include - -#include "dhcp-identifier.h" -#include "netif-util.h" -#include "network-common.h" -#include "siphash24.h" -#include "sparse-endian.h" -#include "string-table.h" - -#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) -#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) -#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ - -static const char * const duid_type_table[_DUID_TYPE_MAX] = { - [DUID_TYPE_LLT] = "DUID-LLT", - [DUID_TYPE_EN] = "DUID-EN/Vendor", - [DUID_TYPE_LL] = "DUID-LL", - [DUID_TYPE_UUID] = "UUID", -}; - -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType); - -int dhcp_identifier_set_duid_llt( - const struct hw_addr_data *hw_addr, - uint16_t arp_type, - usec_t t, - struct duid *ret_duid, - size_t *ret_len) { - - uint16_t time_from_2000y; - - assert(hw_addr); - assert(ret_duid); - assert(ret_len); - - if (hw_addr->length == 0) - return -EOPNOTSUPP; - - if (arp_type == ARPHRD_ETHER) - assert_return(hw_addr->length == ETH_ALEN, -EINVAL); - else if (arp_type == ARPHRD_INFINIBAND) - assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL); - else - return -EOPNOTSUPP; - - if (t < USEC_2000) - time_from_2000y = 0; - else - time_from_2000y = (uint16_t) (((t - USEC_2000) / USEC_PER_SEC) & 0xffffffff); - - unaligned_write_be16(&ret_duid->type, DUID_TYPE_LLT); - unaligned_write_be16(&ret_duid->llt.htype, arp_type); - unaligned_write_be32(&ret_duid->llt.time, time_from_2000y); - memcpy(ret_duid->llt.haddr, hw_addr->bytes, hw_addr->length); - - *ret_len = offsetof(struct duid, llt.haddr) + hw_addr->length; - - return 0; -} - -int dhcp_identifier_set_duid_ll( - const struct hw_addr_data *hw_addr, - uint16_t arp_type, - struct duid *ret_duid, - size_t *ret_len) { - - assert(hw_addr); - assert(ret_duid); - assert(ret_len); - - if (hw_addr->length == 0) - return -EOPNOTSUPP; - - if (arp_type == ARPHRD_ETHER) - assert_return(hw_addr->length == ETH_ALEN, -EINVAL); - else if (arp_type == ARPHRD_INFINIBAND) - assert_return(hw_addr->length == INFINIBAND_ALEN, -EINVAL); - else - return -EOPNOTSUPP; - - unaligned_write_be16(&ret_duid->type, DUID_TYPE_LL); - unaligned_write_be16(&ret_duid->ll.htype, arp_type); - memcpy(ret_duid->ll.haddr, hw_addr->bytes, hw_addr->length); - - *ret_len = offsetof(struct duid, ll.haddr) + hw_addr->length; - - return 0; -} - -int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len) { - sd_id128_t machine_id; - bool test_mode; - uint64_t hash; - int r; - - assert(ret_duid); - assert(ret_len); - - test_mode = network_test_mode_enabled(); - - if (!test_mode) { - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return r; - } else - /* For tests, especially for fuzzers, reproducibility is important. - * Hence, use a static and constant machine ID. - * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */ - machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10); - - unaligned_write_be16(&ret_duid->type, DUID_TYPE_EN); - unaligned_write_be32(&ret_duid->en.pen, SYSTEMD_PEN); - - /* a bit of snake-oil perhaps, but no need to expose the machine-id - * directly; duid->en.id might not be aligned, so we need to copy */ - hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); - memcpy(ret_duid->en.id, &hash, sizeof(hash)); - - *ret_len = offsetof(struct duid, en.id) + sizeof(hash); - - if (test_mode) - assert_se(memcmp(ret_duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, *ret_len) == 0); - - return 0; -} - -int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len) { - sd_id128_t machine_id; - int r; - - assert(ret_duid); - assert(ret_len); - - r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id); - if (r < 0) - return r; - - unaligned_write_be16(&ret_duid->type, DUID_TYPE_UUID); - memcpy(&ret_duid->uuid.uuid, &machine_id, sizeof(machine_id)); - - *ret_len = offsetof(struct duid, uuid.uuid) + sizeof(machine_id); - - return 0; -} - -int dhcp_identifier_set_duid_raw( - DUIDType duid_type, - const uint8_t *buf, - size_t buf_len, - struct duid *ret_duid, - size_t *ret_len) { - - assert(buf || buf_len == 0); - assert(ret_duid); - assert(ret_len); - - if (duid_type < 0 || duid_type > UINT16_MAX) - return -EINVAL; - - if (buf_len > MAX_DUID_DATA_LEN) - return -EINVAL; - - unaligned_write_be16(&ret_duid->type, duid_type); - memcpy_safe(ret_duid->raw.data, buf, buf_len); - - *ret_len = offsetof(struct duid, raw.data) + buf_len; - return 0; -} - -int dhcp_identifier_set_iaid( - sd_device *dev, - const struct hw_addr_data *hw_addr, - bool legacy_unstable_byteorder, - void *ret) { - - const char *name = NULL; - uint32_t id32; - uint64_t id; - - assert(hw_addr); - assert(ret); - - if (dev) - name = net_get_persistent_name(dev); - if (name) - id = siphash24(name, strlen(name), HASH_KEY.bytes); - else - /* fall back to MAC address if no predictable name available */ - id = siphash24(hw_addr->bytes, hw_addr->length, HASH_KEY.bytes); - - id32 = (id & 0xffffffff) ^ (id >> 32); - - if (legacy_unstable_byteorder) - /* for historical reasons (a bug), the bits were swapped and thus - * the result was endianness dependent. Preserve that behavior. */ - id32 = bswap_32(id32); - else - /* the fixed behavior returns a stable byte order. Since LE is expected - * to be more common, swap the bytes on LE to give the same as legacy - * behavior. */ - id32 = be32toh(id32); - - unaligned_write_ne32(ret, id32); - return 0; -} diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h deleted file mode 100644 index 96db588..0000000 --- a/src/libsystemd-network/dhcp-identifier.h +++ /dev/null @@ -1,87 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "sd-device.h" -#include "sd-id128.h" - -#include "ether-addr-util.h" -#include "macro.h" -#include "sparse-endian.h" -#include "time-util.h" -#include "unaligned.h" - -#define SYSTEMD_PEN 43793 - -typedef enum DUIDType { - DUID_TYPE_LLT = 1, - DUID_TYPE_EN = 2, - DUID_TYPE_LL = 3, - DUID_TYPE_UUID = 4, - _DUID_TYPE_MAX, - _DUID_TYPE_INVALID = -EINVAL, - _DUID_TYPE_FORCE_U16 = UINT16_MAX, -} DUIDType; - -/* RFC 8415 section 11.1: - * A DUID consists of a 2-octet type code represented in network byte order, followed by a variable number of - * octets that make up the actual identifier. The length of the DUID (not including the type code) is at - * least 1 octet and at most 128 octets. */ -#define MAX_DUID_DATA_LEN 128 -#define MAX_DUID_LEN (sizeof(be16_t) + MAX_DUID_DATA_LEN) - -/* https://tools.ietf.org/html/rfc3315#section-9.1 */ -struct duid { - be16_t type; - union { - struct { - /* DUID_TYPE_LLT */ - be16_t htype; - be32_t time; - uint8_t haddr[]; - } _packed_ llt; - struct { - /* DUID_TYPE_EN */ - be32_t pen; - uint8_t id[]; - } _packed_ en; - struct { - /* DUID_TYPE_LL */ - be16_t htype; - uint8_t haddr[]; - } _packed_ ll; - struct { - /* DUID_TYPE_UUID */ - sd_id128_t uuid; - } _packed_ uuid; - struct { - uint8_t data[MAX_DUID_DATA_LEN]; - } _packed_ raw; - }; -} _packed_; - -int dhcp_identifier_set_duid_llt( - const struct hw_addr_data *hw_addr, - uint16_t arp_type, - usec_t t, - struct duid *ret_duid, - size_t *ret_len); -int dhcp_identifier_set_duid_ll( - const struct hw_addr_data *hw_addr, - uint16_t arp_type, - struct duid *ret_duid, - size_t *ret_len); -int dhcp_identifier_set_duid_en(struct duid *ret_duid, size_t *ret_len); -int dhcp_identifier_set_duid_uuid(struct duid *ret_duid, size_t *ret_len); -int dhcp_identifier_set_duid_raw( - DUIDType duid_type, - const uint8_t *buf, - size_t buf_len, - struct duid *ret_duid, - size_t *ret_len); -int dhcp_identifier_set_iaid( - sd_device *dev, - const struct hw_addr_data *hw_addr, - bool legacy_unstable_byteorder, - void *ret); - -const char *duid_type_to_string(DUIDType t) _const_; diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index a3d8bb4..b7bc142 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -8,6 +8,7 @@ #include "sd-dhcp-client.h" #include "alloc-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-option.h" #include "list.h" #include "time-util.h" @@ -67,8 +68,7 @@ struct sd_dhcp_lease { char *root_path; char *captive_portal; - void *client_id; - size_t client_id_len; + sd_dhcp_client_id client_id; void *vendor_specific; size_t vendor_specific_len; @@ -92,7 +92,7 @@ int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const vo void dhcp_lease_set_timestamp(sd_dhcp_lease *lease, const triple_timestamp *timestamp); int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len); +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id); #define dhcp_lease_unref_and_replace(a, b) \ unref_and_replace_full(a, b, sd_dhcp_lease_ref, sd_dhcp_lease_unref) diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c index 1f4ad09..2e73983 100644 --- a/src/libsystemd-network/dhcp-network.c +++ b/src/libsystemd-network/dhcp-network.c @@ -3,15 +3,16 @@ Copyright © 2013 Intel Corporation. All rights reserved. ***/ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include +#include +#include +#include #include -#include #include #include #include -#include -#include -#include #include "dhcp-network.h" #include "dhcp-protocol.h" diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index 5679091..e4ba77a 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -285,7 +285,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo int r; while (offset < buflen) { - code = options[offset ++]; + code = options[offset++]; switch (code) { case SD_DHCP_OPTION_PAD: @@ -298,7 +298,7 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo if (buflen < offset + 1) return -ENOBUFS; - len = options[offset ++]; + len = options[offset++]; if (buflen < offset + len) return -EINVAL; diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index da9e56b..0b2fa96 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -8,6 +8,7 @@ #include "sd-dhcp-server.h" #include "sd-event.h" +#include "dhcp-client-id-internal.h" #include "dhcp-option.h" #include "network-common.h" #include "ordered-set.h" @@ -24,25 +25,6 @@ typedef enum DHCPRawOption { _DHCP_RAW_OPTION_DATA_INVALID, } DHCPRawOption; -typedef struct DHCPClientId { - size_t length; - uint8_t *data; -} DHCPClientId; - -typedef struct DHCPLease { - sd_dhcp_server *server; - - DHCPClientId client_id; - - uint8_t htype; /* e.g. ARPHRD_ETHER */ - uint8_t hlen; /* e.g. ETH_ALEN */ - be32_t address; - be32_t gateway; - uint8_t chaddr[16]; - usec_t expiration; - char *hostname; -} DHCPLease; - struct sd_dhcp_server { unsigned n_ref; @@ -93,6 +75,9 @@ struct sd_dhcp_server { char *agent_circuit_id; char *agent_remote_id; + + int lease_dir_fd; + char *lease_file; }; typedef struct DHCPRequest { @@ -100,7 +85,7 @@ typedef struct DHCPRequest { DHCPMessage *message; /* options */ - DHCPClientId client_id; + sd_dhcp_client_id client_id; size_t max_optlen; be32_t server_id; be32_t requested_ip; @@ -113,20 +98,12 @@ typedef struct DHCPRequest { triple_timestamp timestamp; } DHCPRequest; -extern const struct hash_ops dhcp_lease_hash_ops; - int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length, const triple_timestamp *timestamp); int dhcp_server_send_packet(sd_dhcp_server *server, DHCPRequest *req, DHCPPacket *packet, int type, size_t optoffset); -void client_id_hash_func(const DHCPClientId *p, struct siphash *state); -int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b); - -DHCPLease *dhcp_lease_free(DHCPLease *lease); -DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPLease*, dhcp_lease_free); - #define log_dhcp_server_errno(server, error, fmt, ...) \ log_interface_prefix_full_errno( \ "DHCPv4 server: ", \ diff --git a/src/libsystemd-network/dhcp-server-lease-internal.h b/src/libsystemd-network/dhcp-server-lease-internal.h new file mode 100644 index 0000000..7626552 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-lease-internal.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-dhcp-server-lease.h" + +#include "dhcp-client-id-internal.h" +#include "dhcp-server-internal.h" +#include "json.h" +#include "time-util.h" + +typedef struct sd_dhcp_server_lease { + unsigned n_ref; + + sd_dhcp_server *server; + + sd_dhcp_client_id client_id; + + uint8_t htype; /* e.g. ARPHRD_ETHER */ + uint8_t hlen; /* e.g. ETH_ALEN */ + be32_t address; + be32_t gateway; + uint8_t chaddr[16]; + usec_t expiration; + char *hostname; +} sd_dhcp_server_lease; + +extern const struct hash_ops dhcp_server_lease_hash_ops; + +int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static); + +int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration); +int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server); + +sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req); + +int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v); +int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v); + +int dhcp_server_save_leases(sd_dhcp_server *server); +int dhcp_server_load_leases(sd_dhcp_server *server); +int dhcp_server_leases_file_get_server_address( + int dir_fd, + const char *path, + struct in_addr *ret_address, + uint8_t *ret_prefixlen); diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index e5b3b13..ecd62ea 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -11,7 +11,7 @@ #include "sd-event.h" #include "sd-dhcp6-client.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "dhcp6-client-internal.h" #include "dhcp6-option.h" #include "dhcp6-protocol.h" @@ -64,8 +64,7 @@ struct sd_dhcp6_client { DHCP6IA ia_na; DHCP6IA ia_pd; DHCP6RequestIA request_ia; - struct duid duid; - size_t duid_len; + sd_dhcp_duid duid; be16_t *req_opts; size_t n_req_opts; char *fqdn; @@ -85,9 +84,8 @@ struct sd_dhcp6_client { bool send_release; }; -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *address); -int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, - const void *packet, size_t len); +int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *address); +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *address, const void *packet, size_t len); int dhcp6_client_send_message(sd_dhcp6_client *client); int dhcp6_client_set_transaction_id(sd_dhcp6_client *client, uint32_t transaction_id); diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c index a3e4e19..0aa8469 100644 --- a/src/libsystemd-network/dhcp6-network.c +++ b/src/libsystemd-network/dhcp6-network.c @@ -17,9 +17,10 @@ #include "fd-util.h" #include "socket-util.h" -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { +int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *local_address) { union sockaddr_union src = { .in6.sin6_family = AF_INET6, + .in6.sin6_addr = *ASSERT_PTR(local_address), .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), .in6.sin6_scope_id = ifindex, }; @@ -27,9 +28,6 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { int r; assert(ifindex > 0); - assert(local_address); - - src.in6.sin6_addr = *local_address; s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP); if (s < 0) @@ -58,20 +56,14 @@ int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *local_address) { return TAKE_FD(s); } -int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, - const void *packet, size_t len) { +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *server_address, const void *packet, size_t len) { union sockaddr_union dest = { .in6.sin6_family = AF_INET6, + .in6.sin6_addr = *ASSERT_PTR(server_address), .in6.sin6_port = htobe16(DHCP6_PORT_SERVER), }; - int r; - - assert(server_address); - memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr)); - - r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)); - if (r < 0) + if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)) < 0) return -errno; return 0; diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index c70f932..ab75bad 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -28,9 +28,11 @@ typedef struct DHCP6Message DHCP6Message; #define DHCP6_MIN_OPTIONS_SIZE \ 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) -#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } +#define IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS \ + ((const struct in6_addr) { { { \ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, \ + } } } ) enum { DHCP6_PORT_SERVER = 547, diff --git a/src/libsystemd-network/fuzz-dhcp-client.c b/src/libsystemd-network/fuzz-dhcp-client.c index 384972f..72787c4 100644 --- a/src/libsystemd-network/fuzz-dhcp-client.c +++ b/src/libsystemd-network/fuzz-dhcp-client.c @@ -7,8 +7,12 @@ #include "sd-dhcp-client.c" #include "alloc-util.h" +#include "dhcp-lease-internal.h" #include "dhcp-network.h" +#include "fs-util.h" #include "fuzz.h" +#include "network-internal.h" +#include "tmpfile-util.h" int dhcp_network_bind_raw_socket( int ifindex, @@ -52,6 +56,9 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { uint8_t bcast_addr[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_(unlink_tempfilep) char lease_file[] = "/tmp/fuzz-dhcp-client.XXXXXX"; + _cleanup_close_ int fd = -1; int res, r; assert_se(setenv("SYSTEMD_NETWORK_TEST_MODE", "1", 1) >= 0); @@ -75,8 +82,19 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { client->xid = 2; client->state = DHCP_STATE_SELECTING; - (void) client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL); + if (client_handle_offer_or_rapid_ack(client, (DHCPMessage*) data, size, NULL) < 0) + goto end; + fd = mkostemp_safe(lease_file); + assert_se(fd >= 0); + + r = dhcp_lease_save(client->lease, lease_file); + assert_se(r >= 0); + + r = dhcp_lease_load(&lease, lease_file); + assert_se(r >= 0); + +end: assert_se(sd_dhcp_client_stop(client) >= 0); return 0; diff --git a/src/libsystemd-network/fuzz-dhcp-server.c b/src/libsystemd-network/fuzz-dhcp-server.c index fddb3a5..c8b0378 100644 --- a/src/libsystemd-network/fuzz-dhcp-server.c +++ b/src/libsystemd-network/fuzz-dhcp-server.c @@ -7,6 +7,9 @@ #include "sd-dhcp-server.c" #include "fuzz.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tmpfile-util.h" /* stub out network so that the server doesn't send */ ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen) { @@ -18,40 +21,31 @@ ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags) { } static int add_lease(sd_dhcp_server *server, const struct in_addr *server_address, uint8_t i) { - _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL; + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; int r; assert(server); - lease = new(DHCPLease, 1); + lease = new(sd_dhcp_server_lease, 1); if (!lease) return -ENOMEM; - *lease = (DHCPLease) { + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, .address = htobe32(UINT32_C(10) << 24 | i), .chaddr = { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }, - .expiration = UINT64_MAX, + .expiration = usec_add(now(CLOCK_BOOTTIME), USEC_PER_DAY), .gateway = server_address->s_addr, .hlen = ETH_ALEN, .htype = ARPHRD_ETHER, - .client_id.length = 2, + .client_id.size = 2, }; - lease->client_id.data = new(uint8_t, lease->client_id.length); - if (!lease->client_id.data) - return -ENOMEM; - - lease->client_id.data[0] = 2; - lease->client_id.data[1] = i; - - lease->server = server; /* This must be set just before hashmap_put(). */ - - r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease); - if (r < 0) - return r; + lease->client_id.raw[0] = 2; + lease->client_id.raw[1] = i; - r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); + r = dhcp_server_put_lease(server, lease, /* is_static = */ false); if (r < 0) return r; @@ -71,9 +65,11 @@ static int add_static_lease(sd_dhcp_server *server, uint8_t i) { } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_(rm_rf_physical_and_freep) char *tmpdir = NULL; _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct in_addr address = { .s_addr = htobe32(UINT32_C(10) << 24 | UINT32_C(1))}; _cleanup_free_ uint8_t *duped = NULL; + _cleanup_close_ int dir_fd = -EBADF; if (size < sizeof(DHCPMessage)) return 0; @@ -82,8 +78,12 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(duped = memdup(data, size)); + dir_fd = mkdtemp_open(NULL, 0, &tmpdir); + assert_se(dir_fd >= 0); + assert_se(sd_dhcp_server_new(&server, 1) >= 0); assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); + assert_se(sd_dhcp_server_set_lease_file(server, dir_fd, "leases") >= 0); server->fd = open("/dev/null", O_RDWR|O_CLOEXEC|O_NOCTTY); assert_se(server->fd >= 0); assert_se(sd_dhcp_server_configure_pool(server, &address, 24, 0, 0) >= 0); @@ -98,5 +98,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { (void) dhcp_server_handle_message(server, (DHCPMessage*) duped, size, NULL); + assert_se(dhcp_server_save_leases(server) >= 0); + server->bound_leases_by_address = hashmap_free(server->bound_leases_by_address); + server->bound_leases_by_client_id = hashmap_free(server->bound_leases_by_client_id); + assert_se(dhcp_server_load_leases(server) >= 0); + return 0; } diff --git a/src/libsystemd-network/fuzz-dhcp6-client.c b/src/libsystemd-network/fuzz-dhcp6-client.c index 2d42844..2b6e335 100644 --- a/src/libsystemd-network/fuzz-dhcp6-client.c +++ b/src/libsystemd-network/fuzz-dhcp6-client.c @@ -12,11 +12,11 @@ static int test_dhcp_fd[2] = EBADF_PAIR; -int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, const void *packet, size_t len) { +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *server_address, const void *packet, size_t len) { return len; } -int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { +int dhcp6_network_bind_udp_socket(int index, const struct in6_addr *local_address) { assert_se(socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_dhcp_fd) >= 0); return TAKE_FD(test_dhcp_fd[0]); } diff --git a/src/libsystemd-network/fuzz-lldp-rx.c b/src/libsystemd-network/fuzz-lldp-rx.c index 844957c..dad1038 100644 --- a/src/libsystemd-network/fuzz-lldp-rx.c +++ b/src/libsystemd-network/fuzz-lldp-rx.c @@ -9,6 +9,8 @@ #include "fd-util.h" #include "fuzz.h" #include "lldp-network.h" +#include "lldp-rx-internal.h" +#include "memstream-util.h" static int test_fd[2] = EBADF_PAIR; @@ -22,6 +24,9 @@ int lldp_network_bind_raw_socket(int ifindex) { int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_lldp_rx_unrefp) sd_lldp_rx *lldp_rx = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(memstream_done) MemStream m = {}; + FILE *f; if (outside_size_range(size, 0, 2048)) return 0; @@ -37,6 +42,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(write(test_fd[1], data, size) == (ssize_t) size); assert_se(sd_event_run(e, 0) >= 0); + assert_se(lldp_rx_build_neighbors_json(lldp_rx, &v) >= 0); + assert_se(f = memstream_init(&m)); + (void) json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR, f, NULL); + assert_se(sd_lldp_rx_stop(lldp_rx) >= 0); assert_se(sd_lldp_rx_detach_event(lldp_rx) >= 0); test_fd[1] = safe_close(test_fd[1]); diff --git a/src/libsystemd-network/fuzz-ndisc-rs.c b/src/libsystemd-network/fuzz-ndisc-rs.c index a89e2b0..e6ee768 100644 --- a/src/libsystemd-network/fuzz-ndisc-rs.c +++ b/src/libsystemd-network/fuzz-ndisc-rs.c @@ -5,26 +5,24 @@ #include #include "sd-ndisc.h" +#include "sd-radv.h" #include "alloc-util.h" #include "fd-util.h" #include "fuzz.h" -#include "icmp6-util-unix.h" +#include "icmp6-packet.h" +#include "icmp6-test-util.h" #include "ndisc-internal.h" +#include "ndisc-option.h" #include "socket-util.h" -int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { +static void test_with_sd_ndisc(const uint8_t *data, size_t size) { struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} }; _cleanup_(sd_event_unrefp) sd_event *e = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - if (outside_size_range(size, 0, 2048)) - return 0; - - fuzz_setup_logging(); - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); @@ -34,7 +32,67 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(write(test_fd[1], data, size) == (ssize_t) size); (void) sd_event_run(e, UINT64_MAX); assert_se(sd_ndisc_stop(nd) >= 0); - close(test_fd[1]); + test_fd[1] = safe_close(test_fd[1]); + TAKE_FD(test_fd[0]); /* It should be already closed by sd_ndisc_stop(). */ +} + +static void test_with_sd_radv(const uint8_t *data, size_t size) { + struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} + }; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_radv_unrefp) sd_radv *ra = NULL; + + assert_se(socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) >= 0); + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_radv_new(&ra) >= 0); + assert_se(sd_radv_attach_event(ra, e, 0) >= 0); + assert_se(sd_radv_set_ifindex(ra, 42) >= 0); + assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0); + assert_se(sd_radv_start(ra) >= 0); + assert_se(write(test_fd[0], data, size) == (ssize_t) size); + (void) sd_event_run(e, UINT64_MAX); + assert_se(sd_radv_stop(ra) >= 0); + test_fd[0] = safe_close(test_fd[0]); + TAKE_FD(test_fd[1]); /* It should be already closed by sd_radv_stop(). */ +} + +static void test_with_icmp6_packet(const uint8_t *data, size_t size) { + _cleanup_close_pair_ int fd_pair[2] = EBADF_PAIR; + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + _cleanup_set_free_ Set *options = NULL; + + assert_se(socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, fd_pair) >= 0); + assert_se(write(fd_pair[1], data, size) == (ssize_t) size); + + if (icmp6_packet_receive(fd_pair[0], &packet) < 0) + return; + + if (ndisc_parse_options(packet, &options) < 0) + return; + + if (ndisc_send(fd_pair[1], &IN6_ADDR_ALL_ROUTERS_MULTICAST, + icmp6_packet_get_header(packet), options, /* timestamp = */ 0) < 0) + return; + + packet = icmp6_packet_unref(packet); + options = set_free(options); + + if (icmp6_packet_receive(fd_pair[0], &packet) < 0) + return; + + assert_se(ndisc_parse_options(packet, &options) >= 0); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + if (outside_size_range(size, 0, 2048)) + return 0; + + fuzz_setup_logging(); + test_with_sd_ndisc(data, size); + test_with_sd_radv(data, size); + test_with_icmp6_packet(data, size); return 0; } diff --git a/src/libsystemd-network/icmp6-packet.c b/src/libsystemd-network/icmp6-packet.c new file mode 100644 index 0000000..02865a4 --- /dev/null +++ b/src/libsystemd-network/icmp6-packet.c @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "icmp6-packet.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "network-common.h" +#include "socket-util.h" + +DEFINE_TRIVIAL_REF_UNREF_FUNC(ICMP6Packet, icmp6_packet, mfree); + +static ICMP6Packet* icmp6_packet_new(size_t size) { + ICMP6Packet *p; + + if (size > SIZE_MAX - offsetof(ICMP6Packet, raw_packet)) + return NULL; + + p = malloc0(offsetof(ICMP6Packet, raw_packet) + size); + if (!p) + return NULL; + + p->raw_size = size; + p->n_ref = 1; + + return p; +} + +int icmp6_packet_set_sender_address(ICMP6Packet *p, const struct in6_addr *addr) { + assert(p); + + if (addr) + p->sender_address = *addr; + else + p->sender_address = (const struct in6_addr) {}; + + return 0; +} + +int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret) { + assert(p); + + if (in6_addr_is_null(&p->sender_address)) + return -ENODATA; + + if (ret) + *ret = p->sender_address; + return 0; +} + +int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret) { + assert(p); + assert(ret); + + if (!TRIPLE_TIMESTAMP_HAS_CLOCK(clock) || !clock_supported(clock)) + return -EOPNOTSUPP; + + if (!triple_timestamp_is_set(&p->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&p->timestamp, clock); + return 0; +} + +const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p) { + assert(p); + + if (p->raw_size < sizeof(struct icmp6_hdr)) + return NULL; + + return (const struct icmp6_hdr*) p->raw_packet; +} + +int icmp6_packet_get_type(ICMP6Packet *p) { + const struct icmp6_hdr *hdr = icmp6_packet_get_header(p); + if (!hdr) + return -EBADMSG; + + return hdr->icmp6_type; +} + +static int icmp6_packet_verify(ICMP6Packet *p) { + const struct icmp6_hdr *hdr = icmp6_packet_get_header(p); + if (!hdr) + return -EBADMSG; + + if (hdr->icmp6_code != 0) + return -EBADMSG; + + /* Drop any overly large packets early. We are not interested in jumbograms, + * which could cause excessive processing. */ + if (p->raw_size > ICMP6_MAX_NORMAL_PAYLOAD_SIZE) + return -EMSGSIZE; + + return 0; +} + +int icmp6_packet_receive(int fd, ICMP6Packet **ret) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *p = NULL; + ssize_t len; + int r; + + assert(fd >= 0); + assert(ret); + + len = next_datagram_size_fd(fd); + if (len < 0) + return (int) len; + + p = icmp6_packet_new(len); + if (!p) + return -ENOMEM; + + r = icmp6_receive(fd, p->raw_packet, p->raw_size, &p->sender_address, &p->timestamp); + if (r == -EADDRNOTAVAIL) + return log_debug_errno(r, "ICMPv6: Received a packet from neither link-local nor null address."); + if (r == -EMULTIHOP) + return log_debug_errno(r, "ICMPv6: Received a packet with an invalid hop limit."); + if (r == -EPFNOSUPPORT) + return log_debug_errno(r, "ICMPv6: Received a packet with an invalid source address."); + if (r < 0) + return log_debug_errno(r, "ICMPv6: Unexpected error while receiving a packet: %m"); + + r = icmp6_packet_verify(p); + if (r < 0) + return r; + + *ret = TAKE_PTR(p); + return 0; +} diff --git a/src/libsystemd-network/icmp6-packet.h b/src/libsystemd-network/icmp6-packet.h new file mode 100644 index 0000000..b402255 --- /dev/null +++ b/src/libsystemd-network/icmp6-packet.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "macro.h" +#include "time-util.h" + +typedef struct ICMP6Pakcet { + unsigned n_ref; + + struct in6_addr sender_address; + struct triple_timestamp timestamp; + + size_t raw_size; + uint8_t raw_packet[]; +} ICMP6Packet; + +ICMP6Packet* icmp6_packet_ref(ICMP6Packet *p); +ICMP6Packet* icmp6_packet_unref(ICMP6Packet *p); +DEFINE_TRIVIAL_CLEANUP_FUNC(ICMP6Packet*, icmp6_packet_unref); + +/* IPv6 Header is 40 bytes and reserves 2 bytes to represent the Payload Length. Thus, the max payload size, + * including extension headers, is 65535 bytes (2^16 - 1). Jumbograms can be larger (2^32 - 1). */ +#define ICMP6_MAX_NORMAL_PAYLOAD_SIZE 65535 + +int icmp6_packet_set_sender_address(ICMP6Packet *p, const struct in6_addr *addr); +int icmp6_packet_get_sender_address(ICMP6Packet *p, struct in6_addr *ret); +int icmp6_packet_get_timestamp(ICMP6Packet *p, clockid_t clock, usec_t *ret); +const struct icmp6_hdr* icmp6_packet_get_header(ICMP6Packet *p); +int icmp6_packet_get_type(ICMP6Packet *p); + +int icmp6_packet_receive(int fd, ICMP6Packet **ret); diff --git a/src/libsystemd-network/icmp6-test-util.c b/src/libsystemd-network/icmp6-test-util.c new file mode 100644 index 0000000..3c78109 --- /dev/null +++ b/src/libsystemd-network/icmp6-test-util.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "fd-util.h" +#include "icmp6-test-util.h" + +int test_fd[2] = EBADF_PAIR; + +static struct in6_addr dummy_link_local = { + .s6_addr = { + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x12, 0x34, 0x56, 0xff, 0xfe, 0x78, 0x9a, 0xbc, + }, +}; + +int icmp6_bind(int ifindex, bool is_router) { + if (!is_router && socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; + + return test_fd[is_router]; +} + +int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov) { + return writev(fd, iov, n_iov); +} + +int icmp6_receive( + int fd, + void *iov_base, + size_t iov_len, + struct in6_addr *ret_sender, + triple_timestamp *ret_timestamp) { + + assert_se(read (fd, iov_base, iov_len) == (ssize_t) iov_len); + + if (ret_timestamp) + triple_timestamp_now(ret_timestamp); + + if (ret_sender) + *ret_sender = dummy_link_local; + + return 0; +} diff --git a/src/libsystemd-network/icmp6-test-util.h b/src/libsystemd-network/icmp6-test-util.h new file mode 100644 index 0000000..d7b0cc8 --- /dev/null +++ b/src/libsystemd-network/icmp6-test-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "icmp6-util.h" + +extern int test_fd[2]; diff --git a/src/libsystemd-network/icmp6-util-unix.c b/src/libsystemd-network/icmp6-util-unix.c deleted file mode 100644 index 01edb85..0000000 --- a/src/libsystemd-network/icmp6-util-unix.c +++ /dev/null @@ -1,53 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "fd-util.h" -#include "icmp6-util-unix.h" - -send_ra_t send_ra_function = NULL; -int test_fd[2] = EBADF_PAIR; - -static struct in6_addr dummy_link_local = { - .s6_addr = { - 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x34, 0x56, 0xff, 0xfe, 0x78, 0x9a, 0xbc, - }, -}; - -int icmp6_bind_router_solicitation(int ifindex) { - if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; - - return test_fd[0]; -} - -int icmp6_bind_router_advertisement(int ifindex) { - return test_fd[1]; -} - -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - if (!send_ra_function) - return 0; - - return send_ra_function(0); -} - -int icmp6_receive( - int fd, - void *iov_base, - size_t iov_len, - struct in6_addr *ret_sender, - triple_timestamp *ret_timestamp) { - - assert_se(read (fd, iov_base, iov_len) == (ssize_t) iov_len); - - if (ret_timestamp) - triple_timestamp_now(ret_timestamp); - - if (ret_sender) - *ret_sender = dummy_link_local; - - return 0; -} diff --git a/src/libsystemd-network/icmp6-util-unix.h b/src/libsystemd-network/icmp6-util-unix.h deleted file mode 100644 index a9cb05a..0000000 --- a/src/libsystemd-network/icmp6-util-unix.h +++ /dev/null @@ -1,9 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "icmp6-util.h" - -typedef int (*send_ra_t)(uint8_t flags); - -extern send_ra_t send_ra_function; -extern int test_fd[2]; diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c index 72c20ba..75a6489 100644 --- a/src/libsystemd-network/icmp6-util.c +++ b/src/libsystemd-network/icmp6-util.c @@ -3,7 +3,10 @@ Copyright © 2014 Intel Corporation. All rights reserved. ***/ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include +#include #include #include #include @@ -11,8 +14,6 @@ #include #include #include -#include -#include #include "fd-util.h" #include "icmp6-util.h" @@ -21,37 +22,44 @@ #include "network-common.h" #include "socket-util.h" -#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } - -#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } - -static int icmp6_bind_router_message(const struct icmp6_filter *filter, - const struct ipv6_mreq *mreq) { - int ifindex = mreq->ipv6mr_interface; +int icmp6_bind(int ifindex, bool is_router) { + struct icmp6_filter filter = {}; + struct ipv6_mreq mreq; _cleanup_close_ int s = -EBADF; int r; - assert(filter); - assert(mreq); + assert(ifindex > 0); + + ICMP6_FILTER_SETBLOCKALL(&filter); + if (is_router) { + mreq = (struct ipv6_mreq) { + .ipv6mr_multiaddr = IN6_ADDR_ALL_ROUTERS_MULTICAST, + .ipv6mr_interface = ifindex, + }; + ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); + } else { + mreq = (struct ipv6_mreq) { + .ipv6mr_multiaddr = IN6_ADDR_ALL_NODES_MULTICAST, + .ipv6mr_interface = ifindex, + }; + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter); + ICMP6_FILTER_SETPASS(ND_REDIRECT, &filter); + } s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); if (s < 0) return -errno; - if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, filter, sizeof(*filter)) < 0) + if (setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)) < 0) return -errno; - if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, mreq, sizeof(*mreq)) < 0) + if (setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) return -errno; - /* RFC 3315, section 6.7, bullet point 2 may indicate that an - IPV6_PKTINFO socket option also applies for ICMPv6 multicast. - Empirical experiments indicates otherwise and therefore an - IPV6_MULTICAST_IF socket option is used here instead */ + /* RFC 3315, section 6.7, bullet point 2 may indicate that an IPV6_PKTINFO socket option also applies + * for ICMPv6 multicast. Empirical experiments indicates otherwise and therefore an IPV6_MULTICAST_IF + * socket option is used here instead. */ r = setsockopt_int(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, ifindex); if (r < 0) return r; @@ -83,63 +91,21 @@ static int icmp6_bind_router_message(const struct icmp6_filter *filter, return TAKE_FD(s); } -int icmp6_bind_router_solicitation(int ifindex) { - struct icmp6_filter filter = {}; - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, - .ipv6mr_interface = ifindex, - }; - - ICMP6_FILTER_SETBLOCKALL(&filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); - - return icmp6_bind_router_message(&filter, &mreq); -} - -int icmp6_bind_router_advertisement(int ifindex) { - struct icmp6_filter filter = {}; - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, - .ipv6mr_interface = ifindex, - }; - - ICMP6_FILTER_SETBLOCKALL(&filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); - - return icmp6_bind_router_message(&filter, &mreq); -} - -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - struct sockaddr_in6 dst = { +int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov) { + struct sockaddr_in6 sa = { .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, - }; - struct { - struct nd_router_solicit rs; - struct nd_opt_hdr rs_opt; - struct ether_addr rs_opt_mac; - } _packed_ rs = { - .rs.nd_rs_type = ND_ROUTER_SOLICIT, - .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, - .rs_opt.nd_opt_len = 1, - }; - struct iovec iov = { - .iov_base = &rs, - .iov_len = sizeof(rs), + .sin6_addr = *ASSERT_PTR(dst), }; struct msghdr msg = { - .msg_name = &dst, - .msg_namelen = sizeof(dst), - .msg_iov = &iov, - .msg_iovlen = 1, + .msg_name = &sa, + .msg_namelen = sizeof(struct sockaddr_in6), + .msg_iov = (struct iovec*) iov, + .msg_iovlen = n_iov, }; - assert(s >= 0); - assert(ether_addr); - - rs.rs_opt_mac = *ether_addr; + assert(fd >= 0); - if (sendmsg(s, &msg, 0) < 0) + if (sendmsg(fd, &msg, 0) < 0) return -errno; return 0; @@ -155,7 +121,7 @@ int icmp6_receive( /* This needs to be initialized with zero. See #20741. */ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int)) + /* ttl */ CMSG_SPACE_TIMEVAL) control = {}; - struct iovec iov = {}; + struct iovec iov = { buffer, size }; union sockaddr_union sa = {}; struct msghdr msg = { .msg_name = &sa.sa, @@ -165,11 +131,8 @@ int icmp6_receive( .msg_control = &control, .msg_controllen = sizeof(control), }; - struct in6_addr addr = {}; ssize_t len; - iov = IOVEC_MAKE(buffer, size); - len = recvmsg_safe(fd, &msg, MSG_DONTWAIT); if (len < 0) return (int) len; @@ -177,17 +140,11 @@ int icmp6_receive( if ((size_t) len != size) return -EINVAL; - if (msg.msg_namelen == sizeof(struct sockaddr_in6) && - sa.in6.sin6_family == AF_INET6) { - - addr = sa.in6.sin6_addr; - if (!in6_addr_is_link_local(&addr) && !in6_addr_is_null(&addr)) - return -EADDRNOTAVAIL; - - } else if (msg.msg_namelen > 0) + if (msg.msg_namelen != sizeof(struct sockaddr_in6) || sa.in6.sin6_family != AF_INET6) return -EPFNOSUPPORT; - /* namelen == 0 only happens when running the test-suite over a socketpair */ + if (!in6_addr_is_link_local(&sa.in6.sin6_addr) && !in6_addr_is_null(&sa.in6.sin6_addr)) + return -EADDRNOTAVAIL; assert(!(msg.msg_flags & MSG_TRUNC)); @@ -198,6 +155,6 @@ int icmp6_receive( if (ret_timestamp) triple_timestamp_from_cmsg(ret_timestamp, &msg); if (ret_sender) - *ret_sender = addr; + *ret_sender = sa.in6.sin6_addr; return 0; } diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h index 0a9ecb4..9e5063f 100644 --- a/src/libsystemd-network/icmp6-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -6,20 +6,26 @@ ***/ #include +#include +#include +#include #include "time-util.h" -#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } +#define IN6_ADDR_ALL_ROUTERS_MULTICAST \ + ((const struct in6_addr) { { { \ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, \ + } } } ) -#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } +#define IN6_ADDR_ALL_NODES_MULTICAST \ + ((const struct in6_addr) { { { \ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, \ + } } } ) -int icmp6_bind_router_solicitation(int ifindex); -int icmp6_bind_router_advertisement(int ifindex); -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); +int icmp6_bind(int ifindex, bool is_router); +int icmp6_send(int fd, const struct in6_addr *dst, const struct iovec *iov, size_t n_iov); int icmp6_receive( int fd, void *buffer, diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c index af61c9b..a4384ac 100644 --- a/src/libsystemd-network/lldp-neighbor.c +++ b/src/libsystemd-network/lldp-neighbor.c @@ -14,10 +14,10 @@ static void lldp_neighbor_id_hash_func(const LLDPNeighborID *id, struct siphash assert(id); assert(state); - siphash24_compress(id->chassis_id, id->chassis_id_size, state); - siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); - siphash24_compress(id->port_id, id->port_id_size, state); - siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); + siphash24_compress_safe(id->chassis_id, id->chassis_id_size, state); + siphash24_compress_typesafe(id->chassis_id_size, state); + siphash24_compress_safe(id->port_id, id->port_id_size, state); + siphash24_compress_typesafe(id->port_id_size, state); } int lldp_neighbor_id_compare_func(const LLDPNeighborID *x, const LLDPNeighborID *y) { @@ -376,17 +376,6 @@ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_a return 0; } -int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - *ret = LLDP_NEIGHBOR_RAW(n); - *size = n->raw_size; - - return 0; -} - int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { assert_return(n, -EINVAL); assert_return(type, -EINVAL); @@ -640,28 +629,6 @@ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret return 0; } -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(raw || raw_size <= 0, -EINVAL); - - n = lldp_neighbor_new(raw_size); - if (!n) - return -ENOMEM; - - memcpy_safe(LLDP_NEIGHBOR_RAW(n), raw, raw_size); - - r = lldp_neighbor_parse(n); - if (r < 0) - return r; - - *ret = TAKE_PTR(n); - - return r; -} - int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { assert_return(n, -EINVAL); @@ -793,3 +760,31 @@ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_ *ret = triple_timestamp_by_clock(&n->timestamp, clock); return 0; } + +int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret) { + const char *chassis_id = NULL, *port_id = NULL, *port_description = NULL, + *system_name = NULL, *system_description = NULL; + uint16_t cc = 0; + bool valid_cc; + + assert(n); + assert(ret); + + (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id); + (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); + (void) sd_lldp_neighbor_get_port_description(n, &port_description); + (void) sd_lldp_neighbor_get_system_name(n, &system_name); + (void) sd_lldp_neighbor_get_system_description(n, &system_description); + + valid_cc = sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0; + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING_NON_EMPTY("ChassisID", chassis_id), + JSON_BUILD_PAIR_BYTE_ARRAY("RawChassisID", n->id.chassis_id, n->id.chassis_id_size), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PortID", port_id), + JSON_BUILD_PAIR_BYTE_ARRAY("RawPortID", n->id.port_id, n->id.port_id_size), + JSON_BUILD_PAIR_STRING_NON_EMPTY("PortDescription", port_description), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemName", system_name), + JSON_BUILD_PAIR_STRING_NON_EMPTY("SystemDescription", system_description), + JSON_BUILD_PAIR_CONDITION(valid_cc, "EnabledCapabilities", JSON_BUILD_UNSIGNED(cc)))); +} diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h index 016286b..06ba4c7 100644 --- a/src/libsystemd-network/lldp-neighbor.h +++ b/src/libsystemd-network/lldp-neighbor.h @@ -8,6 +8,7 @@ #include "sd-lldp-rx.h" #include "hash-funcs.h" +#include "json.h" #include "lldp-rx-internal.h" #include "time-util.h" @@ -90,3 +91,4 @@ sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); int lldp_neighbor_parse(sd_lldp_neighbor *n); void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); +int lldp_neighbor_build_json(sd_lldp_neighbor *n, JsonVariant **ret); diff --git a/src/libsystemd-network/lldp-rx-internal.h b/src/libsystemd-network/lldp-rx-internal.h index 83d0bc4..e914c6b 100644 --- a/src/libsystemd-network/lldp-rx-internal.h +++ b/src/libsystemd-network/lldp-rx-internal.h @@ -5,6 +5,7 @@ #include "sd-lldp-rx.h" #include "hashmap.h" +#include "json.h" #include "network-common.h" #include "prioq.h" @@ -36,6 +37,8 @@ struct sd_lldp_rx { const char* lldp_rx_event_to_string(sd_lldp_rx_event_t e) _const_; sd_lldp_rx_event_t lldp_rx_event_from_string(const char *s) _pure_; +int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret); + #define log_lldp_rx_errno(lldp_rx, error, fmt, ...) \ log_interface_prefix_full_errno( \ "LLDP Rx: ", \ diff --git a/src/libsystemd-network/meson.build b/src/libsystemd-network/meson.build index 93186e2..718495c 100644 --- a/src/libsystemd-network/meson.build +++ b/src/libsystemd-network/meson.build @@ -2,22 +2,24 @@ sources = files( 'arp-util.c', - 'dhcp-identifier.c', 'dhcp-network.c', 'dhcp-option.c', 'dhcp-packet.c', 'dhcp6-network.c', 'dhcp6-option.c', 'dhcp6-protocol.c', + 'icmp6-packet.c', 'icmp6-util.c', 'lldp-neighbor.c', 'lldp-network.c', - 'ndisc-protocol.c', - 'ndisc-router.c', + 'ndisc-option.c', 'network-common.c', 'network-internal.c', + 'sd-dhcp-client-id.c', 'sd-dhcp-client.c', + 'sd-dhcp-duid.c', 'sd-dhcp-lease.c', + 'sd-dhcp-server-lease.c', 'sd-dhcp-server.c', 'sd-dhcp6-client.c', 'sd-dhcp6-lease.c', @@ -26,6 +28,10 @@ sources = files( 'sd-lldp-rx.c', 'sd-lldp-tx.c', 'sd-ndisc.c', + 'sd-ndisc-neighbor.c', + 'sd-ndisc-redirect.c', + 'sd-ndisc-router.c', + 'sd-ndisc-router-solicit.c', 'sd-radv.c', ) @@ -85,15 +91,19 @@ executables += [ network_test_template + { 'sources' : files( 'test-ndisc-ra.c', - 'icmp6-util-unix.c', + 'icmp6-test-util.c', ), }, network_test_template + { 'sources' : files( 'test-ndisc-rs.c', - 'icmp6-util-unix.c', + 'icmp6-test-util.c', ), }, + network_test_template + { + 'sources' : files('test-ndisc-send.c'), + 'type' : 'manual', + }, network_test_template + { 'sources' : files('test-sd-dhcp-lease.c'), }, @@ -115,7 +125,7 @@ executables += [ network_fuzz_template + { 'sources' : files( 'fuzz-ndisc-rs.c', - 'icmp6-util-unix.c', + 'icmp6-test-util.c', ), }, ] diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h index 615de0d..4e82fd5 100644 --- a/src/libsystemd-network/ndisc-internal.h +++ b/src/libsystemd-network/ndisc-internal.h @@ -24,6 +24,7 @@ struct sd_ndisc { sd_event *event; int event_priority; + struct in6_addr link_local_addr; struct ether_addr mac_addr; sd_event_source *recv_event_source; diff --git a/src/libsystemd-network/ndisc-neighbor-internal.h b/src/libsystemd-network/ndisc-neighbor-internal.h new file mode 100644 index 0000000..aee6556 --- /dev/null +++ b/src/libsystemd-network/ndisc-neighbor-internal.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-ndisc.h" + +#include "icmp6-packet.h" +#include "set.h" + +struct sd_ndisc_neighbor { + unsigned n_ref; + + ICMP6Packet *packet; + + uint32_t flags; + struct in6_addr target_address; + + Set *options; +}; + +sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet); +int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na); diff --git a/src/libsystemd-network/ndisc-option.c b/src/libsystemd-network/ndisc-option.c new file mode 100644 index 0000000..901a3b3 --- /dev/null +++ b/src/libsystemd-network/ndisc-option.c @@ -0,0 +1,1502 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "dns-domain.h" +#include "ether-addr-util.h" +#include "hostname-util.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "iovec-util.h" +#include "missing_network.h" +#include "ndisc-option.h" +#include "network-common.h" +#include "strv.h" +#include "unaligned.h" + +/* RFC does not say anything about the maximum number of options, but let's limit the number of options for + * safety. Typically, the number of options in an ICMPv6 message should be only a few. */ +#define MAX_OPTIONS 128 + +int ndisc_option_parse( + ICMP6Packet *p, + size_t offset, + uint8_t *ret_type, + size_t *ret_len, + const uint8_t **ret_opt) { + + assert(p); + + if (offset == p->raw_size) + return -ESPIPE; /* end of the packet */ + + if (offset > p->raw_size) + return -EBADMSG; + + if (p->raw_size - offset < sizeof(struct nd_opt_hdr)) + return -EBADMSG; + + assert_cc(alignof(struct nd_opt_hdr) == 1); + const struct nd_opt_hdr *hdr = (const struct nd_opt_hdr*) (p->raw_packet + offset); + if (hdr->nd_opt_len == 0) + return -EBADMSG; + + size_t len = hdr->nd_opt_len * 8; + if (p->raw_size - offset < len) + return -EBADMSG; + + if (ret_type) + *ret_type = hdr->nd_opt_type; + if (ret_len) + *ret_len = len; + if (ret_opt) + *ret_opt = p->raw_packet + offset; + + return 0; +} + +static sd_ndisc_option* ndisc_option_new(uint8_t type, size_t offset) { + sd_ndisc_option *p = new0(sd_ndisc_option, 1); /* use new0() here to make the fuzzers silent. */ + if (!p) + return NULL; + + /* As the same reason in the above, do not use the structured initializer here. */ + p->type = type; + p->offset = offset; + + return p; +} + +static void ndisc_raw_done(sd_ndisc_raw *raw) { + if (!raw) + return; + + free(raw->bytes); +} + +static void ndisc_rdnss_done(sd_ndisc_rdnss *rdnss) { + if (!rdnss) + return; + + free(rdnss->addresses); +} + +static void ndisc_dnssl_done(sd_ndisc_dnssl *dnssl) { + if (!dnssl) + return; + + strv_free(dnssl->domains); +} + +sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option) { + if (!option) + return NULL; + + switch (option->type) { + case 0: + ndisc_raw_done(&option->raw); + break; + + case SD_NDISC_OPTION_RDNSS: + ndisc_rdnss_done(&option->rdnss); + break; + + case SD_NDISC_OPTION_DNSSL: + ndisc_dnssl_done(&option->dnssl); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + free(option->captive_portal); + break; + } + + return mfree(option); +} + +static int ndisc_option_compare_func(const sd_ndisc_option *x, const sd_ndisc_option *y) { + int r; + + assert(x); + assert(y); + + r = CMP(x->type, y->type); + if (r != 0) + return r; + + switch (x->type) { + case 0: + return memcmp_nn(x->raw.bytes, x->raw.length, y->raw.bytes, y->raw.length); + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + case SD_NDISC_OPTION_REDIRECTED_HEADER: + case SD_NDISC_OPTION_MTU: + case SD_NDISC_OPTION_HOME_AGENT: + case SD_NDISC_OPTION_FLAGS_EXTENSION: + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + /* These options cannot be specified multiple times. */ + return 0; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + /* Should not specify the same prefix multiple times. */ + r = CMP(x->prefix.prefixlen, y->prefix.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->prefix.address, &y->prefix.address, sizeof(struct in6_addr)); + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = CMP(x->route.prefixlen, y->route.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->route.address, &y->route.address, sizeof(struct in6_addr)); + + case SD_NDISC_OPTION_PREF64: + r = CMP(x->prefix64.prefixlen, y->prefix64.prefixlen); + if (r != 0) + return r; + + return memcmp(&x->prefix64.prefix, &y->prefix64.prefix, sizeof(struct in6_addr)); + + default: + /* DNSSL, RDNSS, and other unsupported options can be specified multiple times. */ + return trivial_compare_func(x, y); + } +} + +static void ndisc_option_hash_func(const sd_ndisc_option *option, struct siphash *state) { + assert(option); + assert(state); + + siphash24_compress_typesafe(option->type, state); + + switch (option->type) { + case 0: + siphash24_compress(option->raw.bytes, option->raw.length, state); + break; + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + case SD_NDISC_OPTION_REDIRECTED_HEADER: + case SD_NDISC_OPTION_MTU: + case SD_NDISC_OPTION_HOME_AGENT: + case SD_NDISC_OPTION_FLAGS_EXTENSION: + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + siphash24_compress_typesafe(option->prefix.prefixlen, state); + siphash24_compress_typesafe(option->prefix.address, state); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + siphash24_compress_typesafe(option->route.prefixlen, state); + siphash24_compress_typesafe(option->route.address, state); + break; + + case SD_NDISC_OPTION_PREF64: + siphash24_compress_typesafe(option->prefix64.prefixlen, state); + siphash24_compress_typesafe(option->prefix64.prefix, state); + break; + + default: + trivial_hash_func(option, state); + } +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_option_hash_ops, + sd_ndisc_option, + ndisc_option_hash_func, + ndisc_option_compare_func, + ndisc_option_free); + +static int ndisc_option_consume(Set **options, sd_ndisc_option *p) { + assert(options); + assert(p); + + if (set_size(*options) >= MAX_OPTIONS) { + ndisc_option_free(p); + return -ETOOMANYREFS; /* recognizable error code */ + } + + return set_ensure_consume(options, &ndisc_option_hash_ops, p); +} + +int ndisc_option_set_raw(Set **options, size_t length, const uint8_t *bytes) { + _cleanup_free_ uint8_t *copy = NULL; + + assert(options); + assert(bytes); + + if (length == 0) + return -EINVAL; + + copy = newdup(uint8_t, bytes, length); + if (!copy) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(/* type = */ 0, /* offset = */ 0); + if (!p) + return -ENOMEM; + + p->raw = (sd_ndisc_raw) { + .bytes = TAKE_PTR(copy), + .length = length, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_build_raw(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == 0); + assert(ret); + + _cleanup_free_ uint8_t *buf = newdup(uint8_t, option->raw.bytes, option->raw.length); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_link_layer_address(Set **options, uint8_t type, size_t offset, const struct ether_addr *mac) { + assert(options); + assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + + if (!mac || ether_addr_is_null(mac)) { + ndisc_option_remove_by_type(*options, type); + return 0; + } + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, type); + if (p) { + /* offset == 0 means that we are now building a packet to be sent, and in that case we allow + * to override the option we previously set. + * offset != 0 means that we are now parsing a received packet, and we refuse to override + * conflicting options. */ + if (offset != 0) + return -EEXIST; + + p->mac = *mac; + return 0; + } + + p = ndisc_option_new(type, offset); + if (!p) + return -ENOMEM; + + p->mac = *mac; + + return set_ensure_consume(options, &ndisc_option_hash_ops, p); +} + +static int ndisc_option_parse_link_layer_address(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len != sizeof(struct ether_addr) + 2) + return -EBADMSG; + + if (!IN_SET(opt[0], SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)) + return -EBADMSG; + + struct ether_addr mac; + memcpy(&mac, opt + 2, sizeof(struct ether_addr)); + + if (ether_addr_is_null(&mac)) + return -EBADMSG; + + return ndisc_option_add_link_layer_address(options, opt[0], offset, &mac); +} + +static int ndisc_option_build_link_layer_address(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(IN_SET(option->type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + assert(ret); + + assert_cc(2 + sizeof(struct ether_addr) == 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, 2 + sizeof(struct ether_addr)); + if (!buf) + return -ENOMEM; + + buf[0] = option->type; + buf[1] = 1; + memcpy(buf + 2, &option->mac, sizeof(struct ether_addr)); + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_prefix_internal( + Set **options, + size_t offset, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime, + usec_t valid_until, + usec_t preferred_until) { + + assert(options); + assert(address); + + if (prefixlen > 128) + return -EINVAL; + + struct in6_addr addr = *address; + in6_addr_mask(&addr, prefixlen); + + /* RFC 4861 and 4862 only state that link-local prefix should be ignored. + * But here we also ignore null and multicast addresses. */ + if (in6_addr_is_link_local(&addr) || in6_addr_is_null(&addr) || in6_addr_is_multicast(&addr)) + return -EINVAL; + + if (preferred_lifetime > valid_lifetime) + return -EINVAL; + + if (preferred_until > valid_until) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_get( + *options, + &(const sd_ndisc_option) { + .type = SD_NDISC_OPTION_PREFIX_INFORMATION, + .prefix.prefixlen = prefixlen, + .prefix.address = addr, + }); + if (p) { + if (offset != 0) + return -EEXIST; + + p->prefix.flags = flags; + p->prefix.valid_lifetime = valid_lifetime; + p->prefix.preferred_lifetime = preferred_lifetime; + p->prefix.valid_until = valid_until; + p->prefix.preferred_until = preferred_until; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_PREFIX_INFORMATION, offset); + if (!p) + return -ENOMEM; + + p->prefix = (sd_ndisc_prefix) { + .flags = flags, + .prefixlen = prefixlen, + .address = addr, + .valid_lifetime = valid_lifetime, + .preferred_lifetime = preferred_lifetime, + .valid_until = valid_until, + .preferred_until = preferred_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_prefix(Set **options, size_t offset, size_t len, const uint8_t *opt) { + const struct nd_opt_prefix_info *pi = (const struct nd_opt_prefix_info*) ASSERT_PTR(opt); + + assert(options); + + if (len != sizeof(struct nd_opt_prefix_info)) + return -EBADMSG; + + if (pi->nd_opt_pi_type != SD_NDISC_OPTION_PREFIX_INFORMATION) + return -EBADMSG; + + usec_t valid = be32_sec_to_usec(pi->nd_opt_pi_valid_time, /* max_as_infinity = */ true); + usec_t pref = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true); + + /* We only support 64 bits interface identifier for addrconf. */ + uint8_t flags = pi->nd_opt_pi_flags_reserved; + if (FLAGS_SET(flags, ND_OPT_PI_FLAG_AUTO) && pi->nd_opt_pi_prefix_len != 64) + flags &= ~ND_OPT_PI_FLAG_AUTO; + + return ndisc_option_add_prefix(options, offset, flags, + pi->nd_opt_pi_prefix_len, &pi->nd_opt_pi_prefix, + valid, pref); +} + +static int ndisc_option_build_prefix(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_PREFIX_INFORMATION); + assert(ret); + + assert_cc(sizeof(struct nd_opt_prefix_info) % 8 == 0); + + _cleanup_free_ struct nd_opt_prefix_info *buf = new(struct nd_opt_prefix_info, 1); + if (!buf) + return -ENOMEM; + + usec_t valid = MIN(option->prefix.valid_lifetime, + usec_sub_unsigned(option->prefix.valid_until, timestamp)); + usec_t pref = MIN3(valid, + option->prefix.preferred_lifetime, + usec_sub_unsigned(option->prefix.preferred_until, timestamp)); + + *buf = (struct nd_opt_prefix_info) { + .nd_opt_pi_type = SD_NDISC_OPTION_PREFIX_INFORMATION, + .nd_opt_pi_len = sizeof(struct nd_opt_prefix_info) / 8, + .nd_opt_pi_prefix_len = option->prefix.prefixlen, + .nd_opt_pi_flags_reserved = option->prefix.flags, + .nd_opt_pi_valid_time = usec_to_be32_sec(valid), + .nd_opt_pi_preferred_time = usec_to_be32_sec(pref), + .nd_opt_pi_prefix = option->prefix.address, + }; + + *ret = (uint8_t*) TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_redirected_header(Set **options, size_t offset, const struct ip6_hdr *hdr) { + assert(options); + + if (!hdr) { + ndisc_option_remove_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER); + return 0; + } + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_REDIRECTED_HEADER); + if (p) { + if (offset != 0) + return -EEXIST; + + memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr)); + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_REDIRECTED_HEADER, offset); + if (!p) + return -ENOMEM; + + /* For safety, here we copy only IPv6 header. */ + memcpy(&p->hdr, hdr, sizeof(struct ip6_hdr)); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_redirected_header(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_REDIRECTED_HEADER) + return -EBADMSG; + + return ndisc_option_add_redirected_header(options, offset, (const struct ip6_hdr*) (opt + sizeof(struct nd_opt_rd_hdr))); +} + +static int ndisc_option_build_redirected_header(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_REDIRECTED_HEADER); + assert(ret); + + assert_cc((sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr)) % 8 == 0); + + size_t len = DIV_ROUND_UP(sizeof(struct nd_opt_rd_hdr) + sizeof(struct ip6_hdr), 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + uint8_t *p; + p = mempcpy(buf, + &(const struct nd_opt_rd_hdr) { + .nd_opt_rh_type = SD_NDISC_OPTION_REDIRECTED_HEADER, + .nd_opt_rh_len = len, + }, + sizeof(struct nd_opt_rd_hdr)); + memcpy(p, &option->hdr, sizeof(struct ip6_hdr)); + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_mtu(Set **options, size_t offset, uint32_t mtu) { + assert(options); + + if (mtu < IPV6_MIN_MTU) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_MTU); + if (p) { + if (offset != 0) + return -EEXIST; + + p->mtu = mtu; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_MTU, offset); + if (!p) + return -ENOMEM; + + p->mtu = mtu; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_mtu(Set **options, size_t offset, size_t len, const uint8_t *opt) { + const struct nd_opt_mtu *pm = (const struct nd_opt_mtu*) ASSERT_PTR(opt); + + assert(options); + + if (len != sizeof(struct nd_opt_mtu)) + return -EBADMSG; + + if (pm->nd_opt_mtu_type != SD_NDISC_OPTION_MTU) + return -EBADMSG; + + return ndisc_option_add_mtu(options, offset, be32toh(pm->nd_opt_mtu_mtu)); +} + +static int ndisc_option_build_mtu(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_MTU); + assert(ret); + + assert_cc(sizeof(struct nd_opt_mtu) % 8 == 0); + + _cleanup_free_ struct nd_opt_mtu *buf = new(struct nd_opt_mtu, 1); + if (!buf) + return -ENOMEM; + + *buf = (struct nd_opt_mtu) { + .nd_opt_mtu_type = SD_NDISC_OPTION_MTU, + .nd_opt_mtu_len = sizeof(struct nd_opt_mtu) / 8, + .nd_opt_mtu_mtu = htobe32(option->mtu), + }; + + *ret = (uint8_t*) TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_home_agent_internal( + Set **options, + size_t offset, + uint16_t preference, + usec_t lifetime, + usec_t valid_until) { + + assert(options); + + if (lifetime > UINT16_MAX * USEC_PER_SEC) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_HOME_AGENT); + if (p) { + if (offset != 0) + return -EEXIST; + + p->home_agent = (sd_ndisc_home_agent) { + .preference = preference, + .lifetime = lifetime, + .valid_until = valid_until, + }; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_HOME_AGENT, offset); + if (!p) + return -ENOMEM; + + p->home_agent = (sd_ndisc_home_agent) { + .preference = preference, + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_home_agent(Set **options, size_t offset, size_t len, const uint8_t *opt) { + const struct nd_opt_home_agent_info *p = (const struct nd_opt_home_agent_info*) ASSERT_PTR(opt); + + assert(options); + + if (len != sizeof(struct nd_opt_home_agent_info)) + return -EBADMSG; + + if (p->nd_opt_home_agent_info_type != SD_NDISC_OPTION_HOME_AGENT) + return -EBADMSG; + + return ndisc_option_add_home_agent( + options, offset, + be16toh(p->nd_opt_home_agent_info_preference), + be16_sec_to_usec(p->nd_opt_home_agent_info_lifetime, /* max_as_infinity = */ false)); +} + +static int ndisc_option_build_home_agent(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_HOME_AGENT); + assert(ret); + + assert_cc(sizeof(struct nd_opt_home_agent_info) % 8 == 0); + + usec_t lifetime = MIN(option->home_agent.lifetime, + usec_sub_unsigned(option->home_agent.valid_until, timestamp)); + + _cleanup_free_ struct nd_opt_home_agent_info *buf = new(struct nd_opt_home_agent_info, 1); + if (!buf) + return -ENOMEM; + + *buf = (struct nd_opt_home_agent_info) { + .nd_opt_home_agent_info_type = SD_NDISC_OPTION_HOME_AGENT, + .nd_opt_home_agent_info_len = sizeof(struct nd_opt_home_agent_info) / 8, + .nd_opt_home_agent_info_preference = htobe16(option->home_agent.preference), + .nd_opt_home_agent_info_lifetime = usec_to_be16_sec(lifetime), + }; + + *ret = (uint8_t*) TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_route_internal( + Set **options, + size_t offset, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until) { + + assert(options); + assert(prefix); + + if (prefixlen > 128) + return -EINVAL; + + /* RFC 4191 section 2.3 + * Prf (Route Preference) + * 2-bit signed integer. The Route Preference indicates whether to prefer the router associated with + * this prefix over others, when multiple identical prefixes (for different routers) have been + * received. If the Reserved (10) value is received, the Route Information Option MUST be ignored. */ + if (!IN_SET(preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH)) + return -EINVAL; + + struct in6_addr addr = *prefix; + in6_addr_mask(&addr, prefixlen); + + sd_ndisc_option *p = ndisc_option_get( + *options, + &(const sd_ndisc_option) { + .type = SD_NDISC_OPTION_ROUTE_INFORMATION, + .route.prefixlen = prefixlen, + .route.address = addr, + }); + if (p) { + if (offset != 0) + return -EEXIST; + + p->route.preference = preference; + p->route.lifetime = lifetime; + p->route.valid_until = valid_until; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_ROUTE_INFORMATION, offset); + if (!p) + return -ENOMEM; + + p->route = (sd_ndisc_route) { + .preference = preference, + .prefixlen = prefixlen, + .address = addr, + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_route(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (!IN_SET(len, 1*8, 2*8, 3*8)) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_ROUTE_INFORMATION) + return -EBADMSG; + + uint8_t prefixlen = opt[2]; + if (prefixlen > 128) + return -EBADMSG; + + if (len < (size_t) (DIV_ROUND_UP(prefixlen, 64) + 1) * 8) + return -EBADMSG; + + uint8_t preference = (opt[3] >> 3) & 0x03; + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + + struct in6_addr prefix; + memcpy(&prefix, opt + 8, len - 8); + in6_addr_mask(&prefix, prefixlen); + + return ndisc_option_add_route(options, offset, preference, prefixlen, &prefix, lifetime); +} + +static int ndisc_option_build_route(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_ROUTE_INFORMATION); + assert(option->route.prefixlen <= 128); + assert(ret); + + size_t len = 1 + DIV_ROUND_UP(option->route.prefixlen, 64); + be32_t lifetime = usec_to_be32_sec(MIN(option->route.lifetime, + usec_sub_unsigned(option->route.valid_until, timestamp))); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_ROUTE_INFORMATION; + buf[1] = len; + buf[2] = option->route.prefixlen; + buf[3] = option->route.preference << 3; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + memcpy_safe(buf + 8, &option->route.address, (len - 1) * 8); + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_rdnss_internal( + Set **options, + size_t offset, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime, + usec_t valid_until) { + + assert(options); + assert(addresses); + + if (n_addresses == 0) + return -EINVAL; + + _cleanup_free_ struct in6_addr *addrs = newdup(struct in6_addr, addresses, n_addresses); + if (!addrs) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_RDNSS, offset); + if (!p) + return -ENOMEM; + + p->rdnss = (sd_ndisc_rdnss) { + .n_addresses = n_addresses, + .addresses = TAKE_PTR(addrs), + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_rdnss(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < 8 + sizeof(struct in6_addr) || (len % sizeof(struct in6_addr)) != 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_RDNSS) + return -EBADMSG; + + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + size_t n_addrs = len / sizeof(struct in6_addr); + + return ndisc_option_add_rdnss(options, offset, n_addrs, (const struct in6_addr*) (opt + 8), lifetime); +} + +static int ndisc_option_build_rdnss(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_RDNSS); + assert(ret); + + size_t len = option->rdnss.n_addresses * 2 + 1; + be32_t lifetime = usec_to_be32_sec(MIN(option->rdnss.lifetime, + usec_sub_unsigned(option->rdnss.valid_until, timestamp))); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_RDNSS; + buf[1] = len; + buf[2] = 0; + buf[3] = 0; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + memcpy(buf + 8, option->rdnss.addresses, sizeof(struct in6_addr) * option->rdnss.n_addresses); + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_flags_extension(Set **options, size_t offset, uint64_t flags) { + assert(options); + + if ((flags & UINT64_C(0x00ffffffffffff00)) != flags) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_FLAGS_EXTENSION); + if (p) { + if (offset != 0) + return -EEXIST; + + p->extended_flags = flags; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_FLAGS_EXTENSION, offset); + if (!p) + return -ENOMEM; + + p->extended_flags = flags; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_flags_extension(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len != 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_FLAGS_EXTENSION) + return -EBADMSG; + + uint64_t flags = (unaligned_read_be64(opt) & UINT64_C(0xffffffffffff0000)) >> 8; + return ndisc_option_add_flags_extension(options, offset, flags); +} + +static int ndisc_option_build_flags_extension(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_FLAGS_EXTENSION); + assert(ret); + + _cleanup_free_ uint8_t *buf = new(uint8_t, 8); + if (!buf) + return 0; + + unaligned_write_be64(buf, (option->extended_flags & UINT64_C(0x00ffffffffffff00)) << 8); + buf[0] = SD_NDISC_OPTION_FLAGS_EXTENSION; + buf[1] = 1; + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_dnssl_internal( + Set **options, + size_t offset, char * + const *domains, + usec_t lifetime, + usec_t valid_until) { + + int r; + + assert(options); + + if (strv_isempty(domains)) + return -EINVAL; + + STRV_FOREACH(s, domains) { + r = dns_name_is_valid(*s); + if (r < 0) + return r; + + if (is_localhost(*s) || dns_name_is_root(*s)) + return -EINVAL; + } + + _cleanup_strv_free_ char **copy = strv_copy(domains); + if (!copy) + return -ENOMEM; + + sd_ndisc_option *p = ndisc_option_new(SD_NDISC_OPTION_DNSSL, offset); + if (!p) + return -ENOMEM; + + p->dnssl = (sd_ndisc_dnssl) { + .domains = TAKE_PTR(copy), + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_dnssl(Set **options, size_t offset, size_t len, const uint8_t *opt) { + int r; + + assert(options); + assert(opt); + + if (len < 2*8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_DNSSL) + return -EBADMSG; + + usec_t lifetime = unaligned_be32_sec_to_usec(opt + 4, /* max_as_infinity = */ true); + + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *e = NULL; + size_t n = 0; + for (size_t c, pos = 8; pos < len; pos += c) { + + c = opt[pos]; + pos++; + + if (c == 0) { + /* Found NUL termination */ + + if (n > 0) { + _cleanup_free_ char *normalized = NULL; + + e[n] = 0; + r = dns_name_normalize(e, 0, &normalized); + if (r < 0) + return r; + + /* Ignore the root domain name or "localhost" and friends */ + if (!is_localhost(normalized) && !dns_name_is_root(normalized)) { + r = strv_consume(&l, TAKE_PTR(normalized)); + if (r < 0) + return r; + } + } + + n = 0; + continue; + } + + /* Check for compression (which is not allowed) */ + if (c > 63) + return -EBADMSG; + + if (pos + c >= len) + return -EBADMSG; + + if (!GREEDY_REALLOC(e, n + (n != 0) + DNS_LABEL_ESCAPED_MAX + 1U)) + return -ENOMEM; + + if (n != 0) + e[n++] = '.'; + + r = dns_label_escape((const char*) (opt + pos), c, e + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + } + + if (n > 0) /* Not properly NUL terminated */ + return -EBADMSG; + + return ndisc_option_add_dnssl(options, offset, l, lifetime); +} + + static int ndisc_option_build_dnssl(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + int r; + + assert(option); + assert(option->type == SD_NDISC_OPTION_DNSSL); + assert(ret); + + size_t len = 8; + STRV_FOREACH(s, option->dnssl.domains) + len += strlen(*s) + 2; + len = DIV_ROUND_UP(len, 8); + + be32_t lifetime = usec_to_be32_sec(MIN(option->dnssl.lifetime, + usec_sub_unsigned(option->dnssl.valid_until, timestamp))); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_DNSSL; + buf[1] = len; + buf[2] = 0; + buf[3] = 0; + memcpy(buf + 4, &lifetime, sizeof(be32_t)); + + size_t remaining = len * 8 - 8; + uint8_t *p = buf + 8; + + STRV_FOREACH(s, option->dnssl.domains) { + r = dns_name_to_wire_format(*s, p, remaining, /* canonical = */ false); + if (r < 0) + return r; + + assert(remaining >= (size_t) r); + p += r; + remaining -= r; + } + + memzero(p, remaining); + + *ret = TAKE_PTR(buf); + return 0; +} + +int ndisc_option_add_captive_portal(Set **options, size_t offset, const char *portal) { + assert(options); + + if (isempty(portal)) + return -EINVAL; + + if (!in_charset(portal, URI_VALID)) + return -EINVAL; + + sd_ndisc_option *p = ndisc_option_get_by_type(*options, SD_NDISC_OPTION_CAPTIVE_PORTAL); + if (p) { + if (offset != 0) + return -EEXIST; + + return free_and_strdup(&p->captive_portal, portal); + } + + _cleanup_free_ char *copy = strdup(portal); + if (!copy) + return -ENOMEM; + + p = ndisc_option_new(SD_NDISC_OPTION_CAPTIVE_PORTAL, offset); + if (!p) + return -ENOMEM; + + p->captive_portal = TAKE_PTR(copy); + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_captive_portal(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + + if (len < 8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_CAPTIVE_PORTAL) + return -EBADMSG; + + _cleanup_free_ char *portal = memdup_suffix0(opt + 2, len - 2); + if (!portal) + return -ENOMEM; + + size_t size = strlen(portal); + if (size == 0) + return -EBADMSG; + + /* Check that the message is not truncated by an embedded NUL. + * NUL padding to a multiple of 8 is expected. */ + if (DIV_ROUND_UP(size + 2, 8) * 8 != len && DIV_ROUND_UP(size + 3, 8) * 8 != len) + return -EBADMSG; + + return ndisc_option_add_captive_portal(options, offset, portal); +} + +static int ndisc_option_build_captive_portal(const sd_ndisc_option *option, uint8_t **ret) { + assert(option); + assert(option->type == SD_NDISC_OPTION_CAPTIVE_PORTAL); + assert(ret); + + size_t len_portal = strlen(option->captive_portal); + size_t len = DIV_ROUND_UP(len_portal + 1 + 2, 8); + + _cleanup_free_ uint8_t *buf = new(uint8_t, len * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_CAPTIVE_PORTAL; + buf[1] = len; + + uint8_t *p = mempcpy(buf + 2, option->captive_portal, len_portal); + size_t remaining = len * 8 - 2 - len_portal; + + memzero(p, remaining); + + *ret = TAKE_PTR(buf); + return 0; +} + +static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { + [PREFIX_LENGTH_CODE_96] = 96, + [PREFIX_LENGTH_CODE_64] = 64, + [PREFIX_LENGTH_CODE_56] = 56, + [PREFIX_LENGTH_CODE_48] = 48, + [PREFIX_LENGTH_CODE_40] = 40, + [PREFIX_LENGTH_CODE_32] = 32, +}; + +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { + for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) + if (prefix_length_code_to_prefix_length[i] == prefixlen) { + if (ret) + *ret = i; + return 0; + } + + return -EINVAL; +} + +static int pref64_lifetime_and_plc_parse(uint16_t lifetime_and_plc, uint8_t *ret_prefixlen, usec_t *ret_lifetime) { + uint16_t plc = lifetime_and_plc & PREF64_PLC_MASK; + if (plc >= _PREFIX_LENGTH_CODE_MAX) + return -EINVAL; + + if (ret_prefixlen) + *ret_prefixlen = prefix_length_code_to_prefix_length[plc]; + if (ret_lifetime) + *ret_lifetime = (lifetime_and_plc & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC; + return 0; +} + +int ndisc_option_add_prefix64_internal( + Set **options, + size_t offset, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until) { + + int r; + + assert(options); + assert(prefix); + + r = pref64_prefix_length_to_plc(prefixlen, NULL); + if (r < 0) + return r; + + if (lifetime > PREF64_MAX_LIFETIME_USEC) + return -EINVAL; + + struct in6_addr addr = *prefix; + in6_addr_mask(&addr, prefixlen); + + sd_ndisc_option *p = ndisc_option_get( + *options, + &(const sd_ndisc_option) { + .type = SD_NDISC_OPTION_PREF64, + .prefix64.prefixlen = prefixlen, + .prefix64.prefix = addr, + }); + if (p) { + if (offset != 0) + return -EEXIST; + + p->prefix64.lifetime = lifetime; + p->prefix64.valid_until = valid_until; + return 0; + } + + p = ndisc_option_new(SD_NDISC_OPTION_PREF64, offset); + if (!p) + return -ENOMEM; + + p->prefix64 = (sd_ndisc_prefix64) { + .prefixlen = prefixlen, + .prefix = addr, + .lifetime = lifetime, + .valid_until = valid_until, + }; + + return ndisc_option_consume(options, p); +} + +static int ndisc_option_parse_prefix64(Set **options, size_t offset, size_t len, const uint8_t *opt) { + int r; + + assert(options); + assert(opt); + + if (len != 2*8) + return -EBADMSG; + + if (opt[0] != SD_NDISC_OPTION_PREF64) + return -EBADMSG; + + uint8_t prefixlen; + usec_t lifetime; + r = pref64_lifetime_and_plc_parse(unaligned_read_be16(opt + 2), &prefixlen, &lifetime); + if (r < 0) + return r; + + struct in6_addr prefix; + memcpy(&prefix, opt + 4, len - 4); + in6_addr_mask(&prefix, prefixlen); + + return ndisc_option_add_prefix64(options, offset, prefixlen, &prefix, lifetime); +} + +static int ndisc_option_build_prefix64(const sd_ndisc_option *option, usec_t timestamp, uint8_t **ret) { + int r; + + assert(option); + assert(option->type == SD_NDISC_OPTION_PREF64); + assert(ret); + + uint8_t code; + r = pref64_prefix_length_to_plc(option->prefix64.prefixlen, &code); + if (r < 0) + return r; + + uint16_t lifetime = (uint16_t) DIV_ROUND_UP(MIN(option->prefix64.lifetime, + usec_sub_unsigned(option->prefix64.valid_until, timestamp)), + USEC_PER_SEC) & PREF64_SCALED_LIFETIME_MASK; + + _cleanup_free_ uint8_t *buf = new(uint8_t, 2 * 8); + if (!buf) + return -ENOMEM; + + buf[0] = SD_NDISC_OPTION_PREF64; + buf[1] = 2; + unaligned_write_be16(buf + 2, lifetime | code); + memcpy(buf + 4, &option->prefix64.prefix, 12); + + *ret = TAKE_PTR(buf); + return 0; +} + +static int ndisc_option_parse_default(Set **options, size_t offset, size_t len, const uint8_t *opt) { + assert(options); + assert(opt); + assert(len > 0); + + sd_ndisc_option *p = ndisc_option_new(opt[0], offset); + if (!p) + return -ENOMEM; + + return ndisc_option_consume(options, p); +} + +static int ndisc_header_size(uint8_t icmp6_type) { + switch (icmp6_type) { + case ND_ROUTER_SOLICIT: + return sizeof(struct nd_router_solicit); + case ND_ROUTER_ADVERT: + return sizeof(struct nd_router_advert); + case ND_NEIGHBOR_SOLICIT: + return sizeof(struct nd_neighbor_solicit); + case ND_NEIGHBOR_ADVERT: + return sizeof(struct nd_neighbor_advert); + case ND_REDIRECT: + return sizeof(struct nd_redirect); + default: + return -EINVAL; + } +} + +int ndisc_parse_options(ICMP6Packet *packet, Set **ret_options) { + _cleanup_set_free_ Set *options = NULL; + int r; + + assert(packet); + assert(ret_options); + + r = icmp6_packet_get_type(packet); + if (r < 0) + return r; + + r = ndisc_header_size(r); + if (r < 0) + return -EBADMSG; + size_t header_size = r; + + if (packet->raw_size < header_size) + return -EBADMSG; + + for (size_t length, offset = header_size; offset < packet->raw_size; offset += length) { + uint8_t type; + const uint8_t *opt; + + r = ndisc_option_parse(packet, offset, &type, &length, &opt); + if (r < 0) + return log_debug_errno(r, "Failed to parse NDisc option header: %m"); + + switch (type) { + case 0: + r = -EBADMSG; + break; + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + r = ndisc_option_parse_link_layer_address(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + r = ndisc_option_parse_prefix(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_REDIRECTED_HEADER: + r = ndisc_option_parse_redirected_header(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_MTU: + r = ndisc_option_parse_mtu(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_HOME_AGENT: + r = ndisc_option_parse_home_agent(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = ndisc_option_parse_route(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_RDNSS: + r = ndisc_option_parse_rdnss(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + r = ndisc_option_parse_flags_extension(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_DNSSL: + r = ndisc_option_parse_dnssl(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + r = ndisc_option_parse_captive_portal(&options, offset, length, opt); + break; + + case SD_NDISC_OPTION_PREF64: + r = ndisc_option_parse_prefix64(&options, offset, length, opt); + break; + + default: + r = ndisc_option_parse_default(&options, offset, length, opt); + } + if (r == -ENOMEM) + return log_oom_debug(); + if (r < 0) + log_debug_errno(r, "Failed to parse NDisc option %u, ignoring: %m", type); + } + + *ret_options = TAKE_PTR(options); + return 0; +} + +int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret) { + assert(IN_SET(type, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, SD_NDISC_OPTION_TARGET_LL_ADDRESS)); + + sd_ndisc_option *p = ndisc_option_get_by_type(options, type); + if (!p) + return -ENODATA; + + if (ret) + *ret = p->mac; + return 0; +} + +int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp) { + int r; + + assert(fd >= 0); + assert(dst); + assert(hdr); + + size_t n; + _cleanup_free_ sd_ndisc_option **list = NULL; + r = set_dump_sorted(options, (void***) &list, &n); + if (r < 0) + return r; + + struct iovec *iov = NULL; + size_t n_iov = 0; + CLEANUP_ARRAY(iov, n_iov, iovec_array_free); + + iov = new(struct iovec, 1 + n); + if (!iov) + return -ENOMEM; + + r = ndisc_header_size(hdr->icmp6_type); + if (r < 0) + return r; + size_t hdr_size = r; + + _cleanup_free_ uint8_t *copy = newdup(uint8_t, hdr, hdr_size); + if (!copy) + return -ENOMEM; + + iov[n_iov++] = IOVEC_MAKE(TAKE_PTR(copy), hdr_size); + + FOREACH_ARRAY(p, list, n) { + _cleanup_free_ uint8_t *buf = NULL; + sd_ndisc_option *option = *p; + + switch (option->type) { + case 0: + r = ndisc_option_build_raw(option, &buf); + break; + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: + r = ndisc_option_build_link_layer_address(option, &buf); + break; + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + r = ndisc_option_build_prefix(option, timestamp, &buf); + break; + + case SD_NDISC_OPTION_REDIRECTED_HEADER: + r = ndisc_option_build_redirected_header(option, &buf); + break; + + case SD_NDISC_OPTION_MTU: + r = ndisc_option_build_mtu(option, &buf); + break; + + case SD_NDISC_OPTION_HOME_AGENT: + r = ndisc_option_build_home_agent(option, timestamp, &buf); + break; + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + r = ndisc_option_build_route(option, timestamp, &buf); + break; + + case SD_NDISC_OPTION_RDNSS: + r = ndisc_option_build_rdnss(option, timestamp, &buf); + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + r = ndisc_option_build_flags_extension(option, &buf); + break; + + case SD_NDISC_OPTION_DNSSL: + r = ndisc_option_build_dnssl(option, timestamp, &buf); + break; + + case SD_NDISC_OPTION_CAPTIVE_PORTAL: + r = ndisc_option_build_captive_portal(option, &buf); + break; + + case SD_NDISC_OPTION_PREF64: + r = ndisc_option_build_prefix64(option, timestamp, &buf); + break; + + default: + continue; + } + if (r == -ENOMEM) + return log_oom_debug(); + if (r < 0) + log_debug_errno(r, "Failed to build NDisc option %u, ignoring: %m", option->type); + + iov[n_iov++] = IOVEC_MAKE(buf, buf[1] * 8); + TAKE_PTR(buf); + } + + return icmp6_send(fd, dst, iov, n_iov); +} diff --git a/src/libsystemd-network/ndisc-option.h b/src/libsystemd-network/ndisc-option.h new file mode 100644 index 0000000..d7bd861 --- /dev/null +++ b/src/libsystemd-network/ndisc-option.h @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "sd-ndisc-protocol.h" + +#include "icmp6-packet.h" +#include "macro.h" +#include "set.h" +#include "time-util.h" + +typedef struct sd_ndisc_raw { + uint8_t *bytes; + size_t length; +} sd_ndisc_raw; + +/* Mostly equivalent to struct nd_opt_prefix_info, but using usec_t. */ +typedef struct sd_ndisc_prefix { + uint8_t flags; + uint8_t prefixlen; + struct in6_addr address; + usec_t valid_lifetime; + usec_t preferred_lifetime; + /* timestamp in CLOCK_BOOTTIME, used when sending option for adjusting lifetime. */ + usec_t valid_until; + usec_t preferred_until; +} sd_ndisc_prefix; + +typedef struct sd_ndisc_home_agent { + uint16_t preference; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_home_agent; + +typedef struct sd_ndisc_route { + uint8_t preference; + uint8_t prefixlen; + struct in6_addr address; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_route; + +typedef struct sd_ndisc_rdnss { + size_t n_addresses; + struct in6_addr *addresses; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_rdnss; + +typedef struct sd_ndisc_dnssl { + char **domains; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_dnssl; + +typedef struct sd_ndisc_prefix64 { + uint8_t prefixlen; + struct in6_addr prefix; + usec_t lifetime; + usec_t valid_until; +} sd_ndisc_prefix64; + +typedef struct sd_ndisc_option { + uint8_t type; + size_t offset; + + union { + sd_ndisc_raw raw; /* for testing or unsupported options */ + struct ether_addr mac; /* SD_NDISC_OPTION_SOURCE_LL_ADDRESS or SD_NDISC_OPTION_TARGET_LL_ADDRESS */ + sd_ndisc_prefix prefix; /* SD_NDISC_OPTION_PREFIX_INFORMATION */ + struct ip6_hdr hdr; /* SD_NDISC_OPTION_REDIRECTED_HEADER */ + uint32_t mtu; /* SD_NDISC_OPTION_MTU */ + sd_ndisc_home_agent home_agent; /* SD_NDISC_OPTION_HOME_AGENT */ + sd_ndisc_route route; /* SD_NDISC_OPTION_ROUTE_INFORMATION */ + sd_ndisc_rdnss rdnss; /* SD_NDISC_OPTION_RDNSS */ + uint64_t extended_flags; /* SD_NDISC_OPTION_FLAGS_EXTENSION */ + sd_ndisc_dnssl dnssl; /* SD_NDISC_OPTION_DNSSL */ + char *captive_portal; /* SD_NDISC_OPTION_CAPTIVE_PORTAL */ + sd_ndisc_prefix64 prefix64; /* SD_NDISC_OPTION_PREF64 */ + }; +} sd_ndisc_option; + +/* RFC 8781: PREF64 or (NAT64 prefix) */ +#define PREF64_SCALED_LIFETIME_MASK 0xfff8 +#define PREF64_PLC_MASK 0x0007 +#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC) + +typedef enum PrefixLengthCode { + PREFIX_LENGTH_CODE_96, + PREFIX_LENGTH_CODE_64, + PREFIX_LENGTH_CODE_56, + PREFIX_LENGTH_CODE_48, + PREFIX_LENGTH_CODE_40, + PREFIX_LENGTH_CODE_32, + _PREFIX_LENGTH_CODE_MAX, + _PREFIX_LENGTH_CODE_INVALID = -EINVAL, +} PrefixLengthCode; + +/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */ +struct nd_opt_prefix64_info { + uint8_t type; + uint8_t length; + uint16_t lifetime_and_plc; + uint8_t prefix[12]; +} _packed_; + +int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret); + +sd_ndisc_option* ndisc_option_free(sd_ndisc_option *option); + +int ndisc_option_parse( + ICMP6Packet *p, + size_t offset, + uint8_t *ret_type, + size_t *ret_len, + const uint8_t **ret_opt); + +int ndisc_parse_options(ICMP6Packet *p, Set **ret_options); + +static inline sd_ndisc_option* ndisc_option_get(Set *options, const sd_ndisc_option *p) { + return set_get(options, ASSERT_PTR(p)); +} +static inline sd_ndisc_option* ndisc_option_get_by_type(Set *options, uint8_t type) { + return ndisc_option_get(options, &(const sd_ndisc_option) { .type = type }); +} +int ndisc_option_get_mac(Set *options, uint8_t type, struct ether_addr *ret); + +static inline void ndisc_option_remove(Set *options, const sd_ndisc_option *p) { + ndisc_option_free(set_remove(options, ASSERT_PTR(p))); +} +static inline void ndisc_option_remove_by_type(Set *options, uint8_t type) { + ndisc_option_remove(options, &(const sd_ndisc_option) { .type = type }); +} + +int ndisc_option_set_raw( + Set **options, + size_t length, + const uint8_t *bytes); +int ndisc_option_add_link_layer_address( + Set **options, + uint8_t type, + size_t offset, + const struct ether_addr *mac); +static inline int ndisc_option_set_link_layer_address( + Set **options, + uint8_t type, + const struct ether_addr *mac) { + return ndisc_option_add_link_layer_address(options, type, 0, mac); +} +int ndisc_option_add_prefix_internal( + Set **options, + size_t offset, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime, + usec_t valid_until, + usec_t preferred_until); +static inline int ndisc_option_add_prefix( + Set **options, + size_t offset, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime) { + return ndisc_option_add_prefix_internal(options, offset, flags, prefixlen, address, + valid_lifetime, preferred_lifetime, + USEC_INFINITY, USEC_INFINITY); +} +static inline int ndisc_option_set_prefix( + Set **options, + uint8_t flags, + uint8_t prefixlen, + const struct in6_addr *address, + usec_t valid_lifetime, + usec_t preferred_lifetime, + usec_t valid_until, + usec_t preferred_until) { + return ndisc_option_add_prefix_internal(options, 0, flags, prefixlen, address, + valid_lifetime, preferred_lifetime, + valid_until, preferred_until); +} +int ndisc_option_add_redirected_header( + Set **options, + size_t offset, + const struct ip6_hdr *hdr); +int ndisc_option_add_mtu( + Set **options, + size_t offset, + uint32_t mtu); +static inline int ndisc_option_set_mtu( + Set **options, + uint32_t mtu) { + return ndisc_option_add_mtu(options, 0, mtu); +} +int ndisc_option_add_home_agent_internal( + Set **options, + size_t offset, + uint16_t preference, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_home_agent( + Set **options, + size_t offset, + uint16_t preference, + usec_t lifetime) { + return ndisc_option_add_home_agent_internal(options, offset, preference, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_home_agent( + Set **options, + uint16_t preference, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_home_agent_internal(options, 0, preference, lifetime, valid_until); +} +int ndisc_option_add_route_internal( + Set **options, + size_t offset, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_route( + Set **options, + size_t offset, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime) { + return ndisc_option_add_route_internal(options, offset, preference, prefixlen, prefix, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_route( + Set **options, + uint8_t preference, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_route_internal(options, 0, preference, prefixlen, prefix, lifetime, valid_until); +} +int ndisc_option_add_rdnss_internal( + Set **options, + size_t offset, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_rdnss( + Set **options, + size_t offset, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime) { + return ndisc_option_add_rdnss_internal(options, offset, n_addresses, addresses, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_rdnss( + Set **options, + size_t n_addresses, + const struct in6_addr *addresses, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_rdnss_internal(options, 0, n_addresses, addresses, lifetime, valid_until); +} +int ndisc_option_add_flags_extension( + Set **options, + size_t offset, + uint64_t flags); +int ndisc_option_add_dnssl_internal( + Set **options, + size_t offset, + char * const *domains, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_dnssl( + Set **options, + size_t offset, + char * const *domains, + usec_t lifetime) { + return ndisc_option_add_dnssl_internal(options, offset, domains, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_dnssl( + Set **options, + char * const *domains, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_dnssl_internal(options, 0, domains, lifetime, valid_until); +} +int ndisc_option_add_captive_portal( + Set **options, + size_t offset, + const char *portal); +static inline int ndisc_option_set_captive_portal( + Set **options, + const char *portal) { + return ndisc_option_add_captive_portal(options, 0, portal); +} +int ndisc_option_add_prefix64_internal( + Set **options, + size_t offset, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until); +static inline int ndisc_option_add_prefix64( + Set **options, + size_t offset, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime) { + return ndisc_option_add_prefix64_internal(options, offset, prefixlen, prefix, lifetime, USEC_INFINITY); +} +static inline int ndisc_option_set_prefix64( + Set **options, + uint8_t prefixlen, + const struct in6_addr *prefix, + usec_t lifetime, + usec_t valid_until) { + return ndisc_option_add_prefix64_internal(options, 0, prefixlen, prefix, lifetime, valid_until); +} + +int ndisc_send(int fd, const struct in6_addr *dst, const struct icmp6_hdr *hdr, Set *options, usec_t timestamp); diff --git a/src/libsystemd-network/ndisc-protocol.c b/src/libsystemd-network/ndisc-protocol.c deleted file mode 100644 index fae4a58..0000000 --- a/src/libsystemd-network/ndisc-protocol.c +++ /dev/null @@ -1,34 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "ndisc-protocol.h" - -static const uint8_t prefix_length_code_to_prefix_length[_PREFIX_LENGTH_CODE_MAX] = { - [PREFIX_LENGTH_CODE_96] = 96, - [PREFIX_LENGTH_CODE_64] = 64, - [PREFIX_LENGTH_CODE_56] = 56, - [PREFIX_LENGTH_CODE_48] = 48, - [PREFIX_LENGTH_CODE_40] = 40, - [PREFIX_LENGTH_CODE_32] = 32, -}; - -int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret) { - plc &= PREF64_PLC_MASK; - if (plc >= _PREFIX_LENGTH_CODE_MAX) - return -EINVAL; - - if (ret) - *ret = prefix_length_code_to_prefix_length[plc]; - return 0; -} - -int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret) { - assert(ret); - - for (size_t i = 0; i < ELEMENTSOF(prefix_length_code_to_prefix_length); i++) - if (prefix_length_code_to_prefix_length[i] == prefixlen) { - *ret = i; - return 0; - } - - return -EINVAL; -} diff --git a/src/libsystemd-network/ndisc-protocol.h b/src/libsystemd-network/ndisc-protocol.h deleted file mode 100644 index 8e403e3..0000000 --- a/src/libsystemd-network/ndisc-protocol.h +++ /dev/null @@ -1,31 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include "time-util.h" - -/* RFC 8781: PREF64 or (NAT64 prefix) */ -#define PREF64_SCALED_LIFETIME_MASK 0xfff8 -#define PREF64_PLC_MASK 0x0007 -#define PREF64_MAX_LIFETIME_USEC (65528 * USEC_PER_SEC) - -typedef enum PrefixLengthCode { - PREFIX_LENGTH_CODE_96, - PREFIX_LENGTH_CODE_64, - PREFIX_LENGTH_CODE_56, - PREFIX_LENGTH_CODE_48, - PREFIX_LENGTH_CODE_40, - PREFIX_LENGTH_CODE_32, - _PREFIX_LENGTH_CODE_MAX, - _PREFIX_LENGTH_CODE_INVALID = -EINVAL, -} PrefixLengthCode; - -/* rfc8781: section 4 - Scaled Lifetime: 13-bit unsigned integer. PREFIX_LEN (Prefix Length Code): 3-bit unsigned integer */ -struct nd_opt_prefix64_info { - uint8_t type; - uint8_t length; - uint16_t lifetime_and_plc; - uint8_t prefix[12]; -} __attribute__((__packed__)); - -int pref64_plc_to_prefix_length(uint16_t plc, uint8_t *ret); -int pref64_prefix_length_to_plc(uint8_t prefixlen, uint8_t *ret); diff --git a/src/libsystemd-network/ndisc-redirect-internal.h b/src/libsystemd-network/ndisc-redirect-internal.h new file mode 100644 index 0000000..e35263a --- /dev/null +++ b/src/libsystemd-network/ndisc-redirect-internal.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-ndisc.h" + +#include "icmp6-packet.h" +#include "set.h" + +struct sd_ndisc_redirect { + unsigned n_ref; + + ICMP6Packet *packet; + + struct in6_addr target_address; + struct in6_addr destination_address; + + Set *options; +}; + +sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet); +int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd); diff --git a/src/libsystemd-network/ndisc-router-internal.h b/src/libsystemd-network/ndisc-router-internal.h new file mode 100644 index 0000000..6df72fd --- /dev/null +++ b/src/libsystemd-network/ndisc-router-internal.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include "sd-ndisc.h" + +#include "icmp6-packet.h" +#include "ndisc-option.h" +#include "time-util.h" + +struct sd_ndisc_router { + unsigned n_ref; + + ICMP6Packet *packet; + + /* From RA header */ + uint8_t hop_limit; + uint8_t flags; + uint8_t preference; + usec_t lifetime_usec; + usec_t reachable_time_usec; + usec_t retransmission_time_usec; + + /* Options */ + Set *options; + Iterator iterator; + sd_ndisc_option *current_option; +}; + +sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet); +int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt); + +int ndisc_router_flags_to_string(uint64_t flags, char **ret); +const char* ndisc_router_preference_to_string(int s) _const_; diff --git a/src/libsystemd-network/ndisc-router-solicit-internal.h b/src/libsystemd-network/ndisc-router-solicit-internal.h new file mode 100644 index 0000000..6f0b0af --- /dev/null +++ b/src/libsystemd-network/ndisc-router-solicit-internal.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-radv.h" + +#include "icmp6-packet.h" +#include "set.h" + +struct sd_ndisc_router_solicit { + unsigned n_ref; + + ICMP6Packet *packet; + + Set *options; +}; + +sd_ndisc_router_solicit* ndisc_router_solicit_new(ICMP6Packet *packet); +int ndisc_router_solicit_parse(sd_radv *ra, sd_ndisc_router_solicit *rs); diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c deleted file mode 100644 index 5162df7..0000000 --- a/src/libsystemd-network/ndisc-router.c +++ /dev/null @@ -1,913 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -/*** - Copyright © 2014 Intel Corporation. All rights reserved. -***/ - -#include - -#include "sd-ndisc.h" - -#include "alloc-util.h" -#include "dns-domain.h" -#include "hostname-util.h" -#include "memory-util.h" -#include "missing_network.h" -#include "ndisc-internal.h" -#include "ndisc-protocol.h" -#include "ndisc-router.h" -#include "strv.h" - -DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, mfree); - -sd_ndisc_router *ndisc_router_new(size_t raw_size) { - sd_ndisc_router *rt; - - if (raw_size > SIZE_MAX - ALIGN(sizeof(sd_ndisc_router))) - return NULL; - - rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size); - if (!rt) - return NULL; - - rt->raw_size = raw_size; - rt->n_ref = 1; - - return rt; -} - -int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - if (in6_addr_is_null(&rt->address)) - return -ENODATA; - - *ret = rt->address; - return 0; -} - -int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { - assert_return(rt, -EINVAL); - assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); - assert_return(clock_supported(clock), -EOPNOTSUPP); - assert_return(ret, -EINVAL); - - if (!triple_timestamp_is_set(&rt->timestamp)) - return -ENODATA; - - *ret = triple_timestamp_by_clock(&rt->timestamp, clock); - return 0; -} - -#define DEFINE_GET_TIMESTAMP(name) \ - int sd_ndisc_router_##name##_timestamp( \ - sd_ndisc_router *rt, \ - clockid_t clock, \ - uint64_t *ret) { \ - \ - usec_t s, t; \ - int r; \ - \ - assert_return(rt, -EINVAL); \ - assert_return(ret, -EINVAL); \ - \ - r = sd_ndisc_router_##name(rt, &s); \ - if (r < 0) \ - return r; \ - \ - r = sd_ndisc_router_get_timestamp(rt, clock, &t); \ - if (r < 0) \ - return r; \ - \ - *ret = time_span_to_stamp(s, t); \ - return 0; \ - } - -DEFINE_GET_TIMESTAMP(get_lifetime); -DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime); -DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime); -DEFINE_GET_TIMESTAMP(route_get_lifetime); -DEFINE_GET_TIMESTAMP(rdnss_get_lifetime); -DEFINE_GET_TIMESTAMP(dnssl_get_lifetime); -DEFINE_GET_TIMESTAMP(prefix64_get_lifetime); - -int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(ret_size, -EINVAL); - - *ret = NDISC_ROUTER_RAW(rt); - *ret_size = rt->raw_size; - - return 0; -} - -static bool pref64_option_verify(const struct nd_opt_prefix64_info *p, size_t length) { - uint16_t lifetime_and_plc; - - assert(p); - - if (length != sizeof(struct nd_opt_prefix64_info)) - return false; - - lifetime_and_plc = be16toh(p->lifetime_and_plc); - if (pref64_plc_to_prefix_length(lifetime_and_plc, NULL) < 0) - return false; - - return true; -} - -int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { - struct nd_router_advert *a; - const uint8_t *p; - bool has_mtu = false, has_flag_extension = false; - size_t left; - - assert(rt); - - if (rt->raw_size < sizeof(struct nd_router_advert)) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Too small to be a router advertisement, ignoring."); - - /* Router advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */ - a = NDISC_ROUTER_RAW(rt); - - if (a->nd_ra_type != ND_ROUTER_ADVERT) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Received ND packet that is not a router advertisement, ignoring."); - - if (a->nd_ra_code != 0) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Received ND packet with wrong RA code, ignoring."); - - rt->hop_limit = a->nd_ra_curhoplimit; - rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */ - rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false); - rt->icmp6_ratelimit_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); - - rt->preference = (rt->flags >> 3) & 3; - if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) - rt->preference = SD_NDISC_PREFERENCE_MEDIUM; - - p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert); - left = rt->raw_size - sizeof(struct nd_router_advert); - - for (;;) { - uint8_t type; - size_t length; - - if (left == 0) - break; - - if (left < 2) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Option lacks header, ignoring datagram."); - - type = p[0]; - length = p[1] * 8; - - if (length == 0) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Zero-length option, ignoring datagram."); - if (left < length) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Option truncated, ignoring datagram."); - - switch (type) { - - case SD_NDISC_OPTION_PREFIX_INFORMATION: - - if (length != 4*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Prefix option of invalid size, ignoring datagram."); - - if (p[2] > 128) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Bad prefix length, ignoring datagram."); - - break; - - case SD_NDISC_OPTION_MTU: { - uint32_t m; - - if (has_mtu) { - log_ndisc(nd, "MTU option specified twice, ignoring."); - break; - } - - if (length != 8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "MTU option of invalid size, ignoring datagram."); - - m = be32toh(*(uint32_t*) (p + 4)); - if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */ - rt->mtu = m; - - has_mtu = true; - break; - } - - case SD_NDISC_OPTION_ROUTE_INFORMATION: - if (length < 1*8 || length > 3*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Route information option of invalid size, ignoring datagram."); - - if (p[2] > 128) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Bad route prefix length, ignoring datagram."); - - break; - - case SD_NDISC_OPTION_RDNSS: - if (length < 3*8 || (length % (2*8)) != 1*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), "RDNSS option has invalid size."); - - break; - - case SD_NDISC_OPTION_FLAGS_EXTENSION: - - if (has_flag_extension) { - log_ndisc(nd, "Flags extension option specified twice, ignoring."); - break; - } - - if (length < 1*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "Flags extension option has invalid size."); - - /* Add in the additional flags bits */ - rt->flags |= - ((uint64_t) p[2] << 8) | - ((uint64_t) p[3] << 16) | - ((uint64_t) p[4] << 24) | - ((uint64_t) p[5] << 32) | - ((uint64_t) p[6] << 40) | - ((uint64_t) p[7] << 48); - - has_flag_extension = true; - break; - - case SD_NDISC_OPTION_DNSSL: - if (length < 2*8) - return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "DNSSL option has invalid size."); - - break; - case SD_NDISC_OPTION_PREF64: { - if (!pref64_option_verify((struct nd_opt_prefix64_info *) p, length)) - log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), - "PREF64 prefix has invalid prefix length."); - break; - }} - - p += length, left -= length; - } - - rt->rindex = sizeof(struct nd_router_advert); - return 0; -} - -int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->hop_limit; - return 0; -} - -int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->icmp6_ratelimit_usec; - return 0; -} - -int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->flags; - return 0; -} - -int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->lifetime_usec; - return 0; -} - -int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->preference; - return 0; -} - -int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - if (rt->mtu <= 0) - return -ENODATA; - - *ret = rt->mtu; - return 0; -} - -int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { - assert_return(rt, -EINVAL); - - assert(rt->raw_size >= sizeof(struct nd_router_advert)); - rt->rindex = sizeof(struct nd_router_advert); - - return rt->rindex < rt->raw_size; -} - -int sd_ndisc_router_option_next(sd_ndisc_router *rt) { - size_t length; - - assert_return(rt, -EINVAL); - - if (rt->rindex == rt->raw_size) /* EOF */ - return -ESPIPE; - - if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ - return -EBADMSG; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (rt->rindex + length > rt->raw_size) - return -EBADMSG; - - rt->rindex += length; - return rt->rindex < rt->raw_size; -} - -int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - if (rt->rindex == rt->raw_size) /* EOF */ - return -ESPIPE; - - if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ - return -EBADMSG; - - *ret = NDISC_ROUTER_OPTION_TYPE(rt); - return 0; -} - -int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { - uint8_t k; - int r; - - assert_return(rt, -EINVAL); - - r = sd_ndisc_router_option_get_type(rt, &k); - if (r < 0) - return r; - - return type == k; -} - -int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size) { - size_t length; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(ret_size, -EINVAL); - - /* Note that this returns the full option, including the option header */ - - if (rt->rindex + 2 > rt->raw_size) - return -EBADMSG; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (rt->rindex + length > rt->raw_size) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - *ret_size = length; - - return 0; -} - -static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) { - struct nd_opt_prefix_info *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length != sizeof(struct nd_opt_prefix_info)) - return -EBADMSG; - - ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); - if (ri->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix_info *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &ri); - if (r < 0) - return r; - - *ret = be32_sec_to_usec(ri->nd_opt_pi_valid_time, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = be32_sec_to_usec(pi->nd_opt_pi_preferred_time, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) { - struct nd_opt_prefix_info *pi; - uint8_t flags; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - flags = pi->nd_opt_pi_flags_reserved; - - if ((flags & ND_OPT_PI_FLAG_AUTO) && (pi->nd_opt_pi_prefix_len != 64)) { - log_ndisc(NULL, "Invalid prefix length, ignoring prefix for stateless autoconfiguration."); - flags &= ~ND_OPT_PI_FLAG_AUTO; - } - - *ret = flags; - return 0; -} - -int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = pi->nd_opt_pi_prefix; - return 0; -} - -int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - if (pi->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = pi->nd_opt_pi_prefix_len; - return 0; -} - -static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) { - uint8_t *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 1*8 || length > 3*8) - return -EBADMSG; - - ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - - if (ri[2] > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - zero(*ret); - memcpy(ret, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8); - - return 0; -} - -int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = ri[2]; - return 0; -} - -int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - if (!IN_SET((ri[3] >> 3) & 3, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH)) - return -EOPNOTSUPP; - - *ret = (ri[3] >> 3) & 3; - return 0; -} - -static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 3*8 || (length % (2*8)) != 1*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; -} - -int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = (const struct in6_addr*) (ri + 8); - return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16; -} - -int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 2*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; -} - -int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - _cleanup_free_ char *e = NULL; - size_t n = 0, left; - uint8_t *ri, *p; - bool first = true; - int r; - unsigned k = 0; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - p = ri + 8; - left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8; - - for (;;) { - if (left == 0) { - - if (n > 0) /* Not properly NUL terminated */ - return -EBADMSG; - - break; - } - - if (*p == 0) { - /* Found NUL termination */ - - if (n > 0) { - _cleanup_free_ char *normalized = NULL; - - e[n] = 0; - r = dns_name_normalize(e, 0, &normalized); - if (r < 0) - return r; - - /* Ignore the root domain name or "localhost" and friends */ - if (!is_localhost(normalized) && - !dns_name_is_root(normalized)) { - - if (strv_push(&l, normalized) < 0) - return -ENOMEM; - - normalized = NULL; - k++; - } - } - - n = 0; - first = true; - p++, left--; - continue; - } - - /* Check for compression (which is not allowed) */ - if (*p > 63) - return -EBADMSG; - - if (1U + *p + 1U > left) - return -EBADMSG; - - if (!GREEDY_REALLOC(e, n + !first + DNS_LABEL_ESCAPED_MAX + 1U)) - return -ENOMEM; - - if (first) - first = false; - else - e[n++] = '.'; - - r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - - left -= 1 + *p; - p += 1 + *p; - } - - if (strv_isempty(l)) { - *ret = NULL; - return 0; - } - - *ret = TAKE_PTR(l); - - return k; -} - -int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - *ret = unaligned_be32_sec_to_usec(ri + 4, /* max_as_infinity = */ true); - return 0; -} - -int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size) { - int r; - const char *nd_opt_captive_portal; - size_t length; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(ret_size, -EINVAL); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_CAPTIVE_PORTAL); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - r = sd_ndisc_router_option_get_raw(rt, (void *)&nd_opt_captive_portal, &length); - if (r < 0) - return r; - - /* The length field has units of 8 octets */ - assert(length % 8 == 0); - if (length == 0) - return -EBADMSG; - - /* Check that the message is not truncated by an embedded NUL. - * NUL padding to a multiple of 8 is expected. */ - size_t size = strnlen(nd_opt_captive_portal + 2, length - 2); - if (DIV_ROUND_UP(size + 2, 8) != length / 8) - return -EBADMSG; - - /* Let's not return an empty buffer */ - if (size == 0) { - *ret = NULL; - *ret_size = 0; - return 0; - } - - *ret = nd_opt_captive_portal + 2; - *ret_size = size; - - return 0; -} - -static int get_pref64_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix64_info **ret) { - struct nd_opt_prefix64_info *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREF64); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length != sizeof(struct nd_opt_prefix64_info)) - return -EBADMSG; - - ri = (struct nd_opt_prefix64_info *) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); - if (!pref64_option_verify(ri, length)) - return -EBADMSG; - - *ret = ri; - return 0; -} - -int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret) { - struct nd_opt_prefix64_info *pi; - struct in6_addr a = {}; - unsigned prefixlen; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - r = sd_ndisc_router_prefix64_get_prefixlen(rt, &prefixlen); - if (r < 0) - return r; - - memcpy(&a, pi->prefix, sizeof(pi->prefix)); - in6_addr_mask(&a, prefixlen); - /* extra safety check for refusing malformed prefix. */ - if (memcmp(&a, pi->prefix, sizeof(pi->prefix)) != 0) - return -EBADMSG; - - *ret = a; - return 0; -} - -int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - struct nd_opt_prefix64_info *pi; - uint16_t lifetime_prefix_len; - uint8_t prefix_len; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - lifetime_prefix_len = be16toh(pi->lifetime_and_plc); - pref64_plc_to_prefix_length(lifetime_prefix_len, &prefix_len); - - *ret = prefix_len; - return 0; -} - -int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { - struct nd_opt_prefix64_info *pi; - uint16_t lifetime_prefix_len; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_pref64_prefix_info(rt, &pi); - if (r < 0) - return r; - - lifetime_prefix_len = be16toh(pi->lifetime_and_plc); - - *ret = (lifetime_prefix_len & PREF64_SCALED_LIFETIME_MASK) * USEC_PER_SEC; - return 0; -} diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h deleted file mode 100644 index 0a55e1a..0000000 --- a/src/libsystemd-network/ndisc-router.h +++ /dev/null @@ -1,49 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -/*** - Copyright © 2014 Intel Corporation. All rights reserved. -***/ - -#include "sd-ndisc.h" - -#include "time-util.h" - -struct sd_ndisc_router { - unsigned n_ref; - - triple_timestamp timestamp; - struct in6_addr address; - - /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */ - size_t raw_size; - - /* The current read index for the iterative option interface */ - size_t rindex; - - uint64_t flags; - unsigned preference; - uint64_t lifetime_usec; - - uint8_t hop_limit; - uint32_t mtu; - uint64_t icmp6_ratelimit_usec; -}; - -static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) { - return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router)); -} - -static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex; -} - -static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0]; -} -static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8; -} - -sd_ndisc_router *ndisc_router_new(size_t raw_size); -int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt); diff --git a/src/libsystemd-network/radv-internal.h b/src/libsystemd-network/radv-internal.h index d6cec90..0a535dd 100644 --- a/src/libsystemd-network/radv-internal.h +++ b/src/libsystemd-network/radv-internal.h @@ -10,7 +10,7 @@ #include "sd-radv.h" #include "list.h" -#include "ndisc-protocol.h" +#include "ndisc-option.h" #include "network-common.h" #include "sparse-endian.h" #include "time-util.h" @@ -43,9 +43,11 @@ #define RADV_MAX_ROUTER_LIFETIME_USEC (9000 * USEC_PER_SEC) #define RADV_DEFAULT_ROUTER_LIFETIME_USEC (3 * RADV_DEFAULT_MAX_TIMEOUT_USEC) /* RFC 4861 section 4.2. - * Retrans Timer + * Reachable Time and Retrans Timer * 32-bit unsigned integer. The time, in milliseconds. */ -#define RADV_MAX_RETRANSMIT_USEC (UINT32_MAX * USEC_PER_MSEC) +#define RADV_MAX_UINT32_MSEC_USEC (UINT32_MAX * USEC_PER_MSEC) +#define RADV_MAX_REACHABLE_TIME_USEC RADV_MAX_UINT32_MSEC_USEC +#define RADV_MAX_RETRANSMIT_USEC RADV_MAX_UINT32_MSEC_USEC /* draft-ietf-6man-slaac-renum-02 section 4.1.1. * AdvPreferredLifetime: max(AdvDefaultLifetime, 3 * MaxRtrAdvInterval) * AdvValidLifetime: 2 * AdvPreferredLifetime */ @@ -79,11 +81,10 @@ /* Pref64 option type (RFC8781, section 4) */ #define RADV_OPT_PREF64 38 -enum RAdvState { +typedef enum RAdvState { RADV_STATE_IDLE = 0, RADV_STATE_ADVERTISING = 1, -}; -typedef enum RAdvState RAdvState; +} RAdvState; struct sd_radv_opt_dns { uint8_t type; @@ -98,6 +99,7 @@ struct sd_radv { int ifindex; char *ifname; + struct in6_addr ipv6ll; sd_event *event; int event_priority; @@ -105,7 +107,9 @@ struct sd_radv { struct ether_addr mac_addr; uint8_t hop_limit; uint8_t flags; + uint8_t preference; uint32_t mtu; + usec_t reachable_usec; usec_t retransmit_usec; usec_t lifetime_usec; /* timespan */ diff --git a/src/libsystemd-network/sd-dhcp-client-id.c b/src/libsystemd-network/sd-dhcp-client-id.c new file mode 100644 index 0000000..cab04f0 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-client-id.c @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "dhcp-client-id-internal.h" +#include "iovec-util.h" +#include "unaligned.h" +#include "utf8.h" + +int sd_dhcp_client_id_clear(sd_dhcp_client_id *client_id) { + assert_return(client_id, -EINVAL); + + *client_id = (sd_dhcp_client_id) {}; + return 0; +} + +int sd_dhcp_client_id_is_set(const sd_dhcp_client_id *client_id) { + if (!client_id) + return false; + + return client_id_size_is_valid(client_id->size); +} + +int sd_dhcp_client_id_get(const sd_dhcp_client_id *client_id, uint8_t *ret_type, const void **ret_data, size_t *ret_size) { + assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL); + assert_return(ret_type, -EINVAL); + assert_return(ret_data, -EINVAL); + assert_return(ret_size, -EINVAL); + + *ret_type = client_id->id.type; + *ret_data = client_id->id.data; + *ret_size = client_id->size - offsetof(typeof(client_id->id), data); + return 0; +} + +int sd_dhcp_client_id_get_raw(const sd_dhcp_client_id *client_id, const void **ret_data, size_t *ret_size) { + assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL); + assert_return(ret_data, -EINVAL); + assert_return(ret_size, -EINVAL); + + /* Unlike sd_dhcp_client_id_get(), this returns whole client ID including its type. */ + + *ret_data = client_id->raw; + *ret_size = client_id->size; + return 0; +} + +int sd_dhcp_client_id_set( + sd_dhcp_client_id *client_id, + uint8_t type, + const void *data, + size_t data_size) { + + assert_return(client_id, -EINVAL); + assert_return(data, -EINVAL); + + if (!client_id_data_size_is_valid(data_size)) + return -EINVAL; + + client_id->id.type = type; + memcpy(client_id->id.data, data, data_size); + + client_id->size = offsetof(typeof(client_id->id), data) + data_size; + return 0; +} + +int sd_dhcp_client_id_set_raw( + sd_dhcp_client_id *client_id, + const void *data, + size_t data_size) { + + assert_return(client_id, -EINVAL); + assert_return(data, -EINVAL); + + /* Unlike sd_dhcp_client_id_set(), this takes whole client ID including its type. */ + + if (!client_id_size_is_valid(data_size)) + return -EINVAL; + + memcpy(client_id->raw, data, data_size); + + client_id->size = data_size; + return 0; +} + +int sd_dhcp_client_id_set_iaid_duid( + sd_dhcp_client_id *client_id, + uint32_t iaid, + sd_dhcp_duid *duid) { + + assert_return(client_id, -EINVAL); + assert_return(duid, -EINVAL); + assert_return(sd_dhcp_duid_is_set(duid), -ESTALE); + + client_id->id.type = 255; + unaligned_write_be32(&client_id->id.ns.iaid, iaid); + memcpy(&client_id->id.ns.duid, &duid->duid, duid->size); + + client_id->size = offsetof(typeof(client_id->id), ns.duid) + duid->size; + return 0; +} + +int sd_dhcp_client_id_to_string(const sd_dhcp_client_id *client_id, char **ret) { + _cleanup_free_ char *t = NULL; + size_t len; + int r; + + assert_return(sd_dhcp_client_id_is_set(client_id), -EINVAL); + assert_return(ret, -EINVAL); + + len = client_id->size - offsetof(typeof(client_id->id), data); + + switch (client_id->id.type) { + case 0: + if (utf8_is_printable((char *) client_id->id.gen.data, len)) + r = asprintf(&t, "%.*s", (int) len, client_id->id.gen.data); + else + r = asprintf(&t, "DATA"); + break; + case 1: + if (len == sizeof_field(sd_dhcp_client_id, id.eth)) + r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x", + client_id->id.eth.haddr[0], + client_id->id.eth.haddr[1], + client_id->id.eth.haddr[2], + client_id->id.eth.haddr[3], + client_id->id.eth.haddr[4], + client_id->id.eth.haddr[5]); + else + r = asprintf(&t, "ETHER"); + break; + case 2 ... 254: + r = asprintf(&t, "ARP/LL"); + break; + case 255: + if (len < sizeof(uint32_t)) + r = asprintf(&t, "IAID/DUID"); + else { + uint32_t iaid = be32toh(client_id->id.ns.iaid); + /* TODO: check and stringify DUID */ + r = asprintf(&t, "IAID:0x%x/DUID", iaid); + } + break; + default: + assert_not_reached(); + } + if (r < 0) + return -ENOMEM; + + *ret = TAKE_PTR(t); + return 0; +} + +int sd_dhcp_client_id_to_string_from_raw(const void *data, size_t data_size, char **ret) { + sd_dhcp_client_id client_id; + int r; + + assert_return(data, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_dhcp_client_id_set_raw(&client_id, data, data_size); + if (r < 0) + return r; + + return sd_dhcp_client_id_to_string(&client_id, ret); +} + +void client_id_hash_func(const sd_dhcp_client_id *client_id, struct siphash *state) { + assert(sd_dhcp_client_id_is_set(client_id)); + assert(state); + + siphash24_compress_typesafe(client_id->size, state); + siphash24_compress(client_id->raw, client_id->size, state); +} + +int client_id_compare_func(const sd_dhcp_client_id *a, const sd_dhcp_client_id *b) { + assert(sd_dhcp_client_id_is_set(a)); + assert(sd_dhcp_client_id_is_set(b)); + + return memcmp_nn(a->raw, a->size, b->raw, b->size); +} + +int json_dispatch_client_id(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + sd_dhcp_client_id *client_id = ASSERT_PTR(userdata); + _cleanup_(iovec_done) struct iovec iov = {}; + int r; + + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); + if (r < 0) + return r; + + r = sd_dhcp_client_id_set_raw(client_id, iov.iov_base, iov.iov_len); + if (r < 0) + return json_log(variant, flags, r, "Failed to set DHCP client ID from JSON field '%s': %m", strna(name)); + + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 24bcd74..1eb8509 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -15,8 +15,8 @@ #include "alloc-util.h" #include "device-util.h" +#include "dhcp-client-id-internal.h" #include "dhcp-client-internal.h" -#include "dhcp-identifier.h" #include "dhcp-lease-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" @@ -39,7 +39,6 @@ #include "utf8.h" #include "web-util.h" -#define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) #define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) @@ -48,32 +47,6 @@ #define TRANSIENT_FAILURE_ATTEMPTS 3 /* Arbitrary limit: how many attempts are considered enough to report * transient failure. */ -typedef struct sd_dhcp_client_id { - uint8_t type; - union { - struct { - /* 0: Generic (non-LL) (RFC 2132) */ - uint8_t data[MAX_CLIENT_ID_LEN]; - } _packed_ gen; - struct { - /* 1: Ethernet Link-Layer (RFC 2132) */ - uint8_t haddr[ETH_ALEN]; - } _packed_ eth; - struct { - /* 2 - 254: ARP/Link-Layer (RFC 2132) */ - uint8_t haddr[0]; - } _packed_ ll; - struct { - /* 255: Node-specific (RFC 4361) */ - be32_t iaid; - struct duid duid; - } _packed_ ns; - struct { - uint8_t data[MAX_CLIENT_ID_LEN]; - } _packed_ raw; - }; -} _packed_ sd_dhcp_client_id; - struct sd_dhcp_client { unsigned n_ref; @@ -89,6 +62,7 @@ struct sd_dhcp_client { int fd; uint16_t port; + uint16_t server_port; union sockaddr_union link; sd_event_source *receive_message; bool request_broadcast; @@ -100,7 +74,6 @@ struct sd_dhcp_client { struct hw_addr_data bcast_addr; uint16_t arp_type; sd_dhcp_client_id client_id; - size_t client_id_len; char *hostname; char *vendor_class_identifier; char *mudurl; @@ -181,58 +154,6 @@ static int client_receive_message_udp( static void client_stop(sd_dhcp_client *client, int error); static int client_restart(sd_dhcp_client *client); -int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret) { - const sd_dhcp_client_id *client_id = data; - _cleanup_free_ char *t = NULL; - int r = 0; - - assert_return(data, -EINVAL); - assert_return(len >= 1, -EINVAL); - assert_return(ret, -EINVAL); - - len -= 1; - if (len > MAX_CLIENT_ID_LEN) - return -EINVAL; - - switch (client_id->type) { - case 0: - if (utf8_is_printable((char *) client_id->gen.data, len)) - r = asprintf(&t, "%.*s", (int) len, client_id->gen.data); - else - r = asprintf(&t, "DATA"); - break; - case 1: - if (len == sizeof_field(sd_dhcp_client_id, eth)) - r = asprintf(&t, "%02x:%02x:%02x:%02x:%02x:%02x", - client_id->eth.haddr[0], - client_id->eth.haddr[1], - client_id->eth.haddr[2], - client_id->eth.haddr[3], - client_id->eth.haddr[4], - client_id->eth.haddr[5]); - else - r = asprintf(&t, "ETHER"); - break; - case 2 ... 254: - r = asprintf(&t, "ARP/LL"); - break; - case 255: - if (len < sizeof(uint32_t)) - r = asprintf(&t, "IAID/DUID"); - else { - uint32_t iaid = be32toh(client_id->ns.iaid); - /* TODO: check and stringify DUID */ - r = asprintf(&t, "IAID:0x%x/DUID", iaid); - } - break; - } - if (r < 0) - return -ENOMEM; - - *ret = TAKE_PTR(t); - return 0; -} - int dhcp_client_set_state_callback( sd_dhcp_client *client, sd_dhcp_client_callback_t cb, @@ -363,34 +284,14 @@ int sd_dhcp_client_set_mac( return 0; } -int sd_dhcp_client_get_client_id( - sd_dhcp_client *client, - uint8_t *ret_type, - const uint8_t **ret_data, - size_t *ret_data_len) { - +int sd_dhcp_client_get_client_id(sd_dhcp_client *client, const sd_dhcp_client_id **ret) { assert_return(client, -EINVAL); + assert_return(ret, -EINVAL); - if (client->client_id_len > 0) { - if (client->client_id_len <= offsetof(sd_dhcp_client_id, raw.data)) - return -EINVAL; - - if (ret_type) - *ret_type = client->client_id.type; - if (ret_data) - *ret_data = client->client_id.raw.data; - if (ret_data_len) - *ret_data_len = client->client_id_len - offsetof(sd_dhcp_client_id, raw.data); - return 1; - } - - if (ret_type) - *ret_type = 0; - if (ret_data) - *ret_data = NULL; - if (ret_data_len) - *ret_data_len = 0; + if (!sd_dhcp_client_id_is_set(&client->client_id)) + return -ENODATA; + *ret = &client->client_id; return 0; } @@ -403,7 +304,7 @@ int sd_dhcp_client_set_client_id( assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); assert_return(data, -EINVAL); - assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL); + assert_return(client_id_data_size_is_valid(data_len), -EINVAL); /* For hardware types, log debug message about unexpected data length. * @@ -416,42 +317,28 @@ int sd_dhcp_client_set_client_id( "Changing client ID to hardware type %u with unexpected address length %zu", type, data_len); - client->client_id.type = type; - memcpy(&client->client_id.raw.data, data, data_len); - client->client_id_len = data_len + sizeof (client->client_id.type); - - return 0; + return sd_dhcp_client_id_set(&client->client_id, type, data, data_len); } -/** - * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid - * without further modification. Otherwise, if duid_type is supported, DUID - * is set based on that type. Otherwise, an error is returned. - */ -static int dhcp_client_set_iaid( +static int dhcp_client_set_iaid_duid( sd_dhcp_client *client, bool iaid_set, - uint32_t iaid) { + uint32_t iaid, + sd_dhcp_duid *duid) { int r; - assert_return(client, -EINVAL); - assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - - zero(client->client_id); - client->client_id.type = 255; - - if (iaid_set) - client->client_id.ns.iaid = htobe32(iaid); - else { + if (!iaid_set) { r = dhcp_identifier_set_iaid(client->dev, &client->hw_addr, /* legacy_unstable_byteorder = */ true, - &client->client_id.ns.iaid); + &iaid); if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set IAID: %m"); + return r; + + iaid = be32toh(iaid); } - return 0; + return sd_dhcp_client_id_set_iaid_duid(&client->client_id, iaid, duid); } int sd_dhcp_client_set_iaid_duid_llt( @@ -460,23 +347,17 @@ int sd_dhcp_client_set_iaid_duid_llt( uint32_t iaid, usec_t llt_time) { - size_t len; + sd_dhcp_duid duid; int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - r = dhcp_client_set_iaid(client, iaid_set, iaid); + r = sd_dhcp_duid_set_llt(&duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type, llt_time); if (r < 0) return r; - r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-LLT: %m"); - - client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len; - - return 0; + return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid); } int sd_dhcp_client_set_iaid_duid_ll( @@ -484,23 +365,17 @@ int sd_dhcp_client_set_iaid_duid_ll( bool iaid_set, uint32_t iaid) { - size_t len; + sd_dhcp_duid duid; int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - r = dhcp_client_set_iaid(client, iaid_set, iaid); + r = sd_dhcp_duid_set_ll(&duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type); if (r < 0) return r; - r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-LL: %m"); - - client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len; - - return 0; + return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid); } int sd_dhcp_client_set_iaid_duid_en( @@ -508,23 +383,17 @@ int sd_dhcp_client_set_iaid_duid_en( bool iaid_set, uint32_t iaid) { - size_t len; + sd_dhcp_duid duid; int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - r = dhcp_client_set_iaid(client, iaid_set, iaid); + r = sd_dhcp_duid_set_en(&duid); if (r < 0) return r; - r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-EN: %m"); - - client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len; - - return 0; + return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid); } int sd_dhcp_client_set_iaid_duid_uuid( @@ -532,23 +401,17 @@ int sd_dhcp_client_set_iaid_duid_uuid( bool iaid_set, uint32_t iaid) { - size_t len; + sd_dhcp_duid duid; int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - r = dhcp_client_set_iaid(client, iaid_set, iaid); + r = sd_dhcp_duid_set_uuid(&duid); if (r < 0) return r; - r = dhcp_identifier_set_duid_uuid(&client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID-UUID: %m"); - - client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len; - - return 0; + return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid); } int sd_dhcp_client_set_iaid_duid_raw( @@ -556,27 +419,21 @@ int sd_dhcp_client_set_iaid_duid_raw( bool iaid_set, uint32_t iaid, uint16_t duid_type, - const uint8_t *duid, - size_t duid_len) { + const uint8_t *duid_data, + size_t duid_data_len) { - size_t len; + sd_dhcp_duid duid; int r; assert_return(client, -EINVAL); assert_return(!sd_dhcp_client_is_running(client), -EBUSY); - assert_return(duid || duid_len == 0, -EINVAL); + assert_return(duid_data || duid_data_len == 0, -EINVAL); - r = dhcp_client_set_iaid(client, iaid_set, iaid); + r = sd_dhcp_duid_set(&duid, duid_type, duid_data, duid_data_len); if (r < 0) return r; - r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->client_id.ns.duid, &len); - if (r < 0) - return log_dhcp_client_errno(client, r, "Failed to set DUID: %m"); - - client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + len; - - return 0; + return dhcp_client_set_iaid_duid(client, iaid_set, iaid, &duid); } int sd_dhcp_client_set_rapid_commit(sd_dhcp_client *client, bool rapid_commit) { @@ -660,6 +517,18 @@ int sd_dhcp_client_set_client_port( return 0; } +int sd_dhcp_client_set_port( + sd_dhcp_client *client, + uint16_t port) { + + assert_return(client, -EINVAL); + assert_return(!sd_dhcp_client_is_running(client), -EBUSY); + + client->server_port = port; + + return 0; +} + int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { assert_return(client, -EINVAL); assert_return(mtu >= DHCP_MIN_PACKET_SIZE, -ERANGE); @@ -925,8 +794,8 @@ static int client_message_init( Identifier option is not set */ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, SD_DHCP_OPTION_CLIENT_IDENTIFIER, - client->client_id_len, - &client->client_id); + client->client_id.size, + client->client_id.raw); if (r < 0) return r; @@ -1035,7 +904,7 @@ static int dhcp_client_send_raw( size_t len) { dhcp_packet_append_ip_headers(packet, INADDR_ANY, client->port, - INADDR_BROADCAST, DHCP_PORT_SERVER, len, client->ip_service_type); + INADDR_BROADCAST, client->server_port, len, client->ip_service_type); return dhcp_network_send_raw_socket(client->fd, &client->link, packet, len); @@ -1257,7 +1126,7 @@ static int client_send_request(sd_dhcp_client *client) { if (client->state == DHCP_STATE_RENEWING) r = dhcp_network_send_udp_socket(client->fd, client->lease->server_address, - DHCP_PORT_SERVER, + client->server_port, &request->dhcp, sizeof(DHCPMessage) + optoffset); else @@ -1599,10 +1468,8 @@ static int client_parse_message( if (r < 0) return r; - if (client->client_id_len > 0) { - r = dhcp_lease_set_client_id(lease, - (uint8_t *) &client->client_id, - client->client_id_len); + if (sd_dhcp_client_id_is_set(&client->client_id)) { + r = dhcp_lease_set_client_id(lease, &client->client_id); if (r < 0) return r; } @@ -2302,7 +2169,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { return r; /* If no client identifier exists, construct an RFC 4361-compliant one */ - if (client->client_id_len == 0) { + if (!sd_dhcp_client_id_is_set(&client->client_id)) { r = sd_dhcp_client_set_iaid_duid_en(client, /* iaid_set = */ false, /* iaid = */ 0); if (r < 0) return r; @@ -2349,7 +2216,7 @@ int sd_dhcp_client_send_release(sd_dhcp_client *client) { r = dhcp_network_send_udp_socket(client->fd, client->lease->server_address, - DHCP_PORT_SERVER, + client->server_port, &release->dhcp, sizeof(DHCPMessage) + optoffset); if (r < 0) @@ -2383,7 +2250,7 @@ int sd_dhcp_client_send_decline(sd_dhcp_client *client) { r = dhcp_network_send_udp_socket(client->fd, client->lease->server_address, - DHCP_PORT_SERVER, + client->server_port, &release->dhcp, sizeof(DHCPMessage) + optoffset); if (r < 0) @@ -2528,6 +2395,7 @@ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { .fd = -EBADF, .mtu = DHCP_MIN_PACKET_SIZE, .port = DHCP_PORT_CLIENT, + .server_port = DHCP_PORT_SERVER, .anonymize = !!anonymize, .max_discover_attempts = UINT64_MAX, .max_request_attempts = 5, diff --git a/src/libsystemd-network/sd-dhcp-duid.c b/src/libsystemd-network/sd-dhcp-duid.c new file mode 100644 index 0000000..4782ec6 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-duid.c @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "dhcp-duid-internal.h" +#include "hexdecoct.h" +#include "netif-util.h" +#include "network-common.h" +#include "siphash24.h" +#include "string-table.h" +#include "unaligned.h" + +#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) +#define APPLICATION_ID SD_ID128_MAKE(a5,0a,d1,12,bf,60,45,77,a2,fb,74,1a,b1,95,5b,03) +#define USEC_2000 ((usec_t) 946684800000000) /* 2000-01-01 00:00:00 UTC */ + +static const char * const duid_type_table[_DUID_TYPE_MAX] = { + [DUID_TYPE_LLT] = "DUID-LLT", + [DUID_TYPE_EN] = "DUID-EN/Vendor", + [DUID_TYPE_LL] = "DUID-LL", + [DUID_TYPE_UUID] = "UUID", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(duid_type, DUIDType); + +int sd_dhcp_duid_clear(sd_dhcp_duid *duid) { + assert_return(duid, -EINVAL); + + *duid = (sd_dhcp_duid) {}; + return 0; +} + +int sd_dhcp_duid_is_set(const sd_dhcp_duid *duid) { + if (!duid) + return false; + + return duid_size_is_valid(duid->size); +} + +int sd_dhcp_duid_get(const sd_dhcp_duid *duid, uint16_t *ret_type, const void **ret_data, size_t *ret_size) { + assert_return(sd_dhcp_duid_is_set(duid), -EINVAL); + assert_return(ret_type, -EINVAL); + assert_return(ret_data, -EINVAL); + assert_return(ret_size, -EINVAL); + + *ret_type = be16toh(duid->duid.type); + *ret_data = duid->duid.data; + *ret_size = duid->size - offsetof(struct duid, data); + return 0; +} + +int sd_dhcp_duid_get_raw(const sd_dhcp_duid *duid, const void **ret_data, size_t *ret_size) { + assert_return(sd_dhcp_duid_is_set(duid), -EINVAL); + assert_return(ret_data, -EINVAL); + assert_return(ret_size, -EINVAL); + + /* Unlike sd_dhcp_duid_get(), this returns whole DUID including its type. */ + + *ret_data = duid->raw; + *ret_size = duid->size; + return 0; +} + +int sd_dhcp_duid_set( + sd_dhcp_duid *duid, + uint16_t duid_type, + const void *data, + size_t data_size) { + + assert_return(duid, -EINVAL); + assert_return(data, -EINVAL); + + if (!duid_data_size_is_valid(data_size)) + return -EINVAL; + + unaligned_write_be16(&duid->duid.type, duid_type); + memcpy(duid->duid.data, data, data_size); + + duid->size = offsetof(struct duid, data) + data_size; + return 0; +} + +int sd_dhcp_duid_set_raw( + sd_dhcp_duid *duid, + const void *data, + size_t data_size) { + + assert_return(duid, -EINVAL); + assert_return(data, -EINVAL); + + /* Unlike sd_dhcp_duid_set(), this takes whole DUID including its type. */ + + if (!duid_size_is_valid(data_size)) + return -EINVAL; + + memcpy(duid->raw, data, data_size); + + duid->size = data_size; + return 0; +} + +int sd_dhcp_duid_set_llt( + sd_dhcp_duid *duid, + const void *hw_addr, + size_t hw_addr_size, + uint16_t arp_type, + uint64_t usec) { + + uint16_t time_from_2000y; + + assert_return(duid, -EINVAL); + assert_return(hw_addr, -EINVAL); + + if (arp_type == ARPHRD_ETHER) + assert_return(hw_addr_size == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL); + else + return -EOPNOTSUPP; + + time_from_2000y = (uint16_t) ((usec_sub_unsigned(usec, USEC_2000) / USEC_PER_SEC) & 0xffffffff); + + unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LLT); + unaligned_write_be16(&duid->duid.llt.htype, arp_type); + unaligned_write_be32(&duid->duid.llt.time, time_from_2000y); + memcpy(duid->duid.llt.haddr, hw_addr, hw_addr_size); + + duid->size = offsetof(struct duid, llt.haddr) + hw_addr_size; + return 0; +} + +int sd_dhcp_duid_set_ll( + sd_dhcp_duid *duid, + const void *hw_addr, + size_t hw_addr_size, + uint16_t arp_type) { + + assert_return(duid, -EINVAL); + assert_return(hw_addr, -EINVAL); + + if (arp_type == ARPHRD_ETHER) + assert_return(hw_addr_size == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(hw_addr_size == INFINIBAND_ALEN, -EINVAL); + else + return -EOPNOTSUPP; + + unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_LL); + unaligned_write_be16(&duid->duid.ll.htype, arp_type); + memcpy(duid->duid.ll.haddr, hw_addr, hw_addr_size); + + duid->size = offsetof(struct duid, ll.haddr) + hw_addr_size; + return 0; +} + +int sd_dhcp_duid_set_en(sd_dhcp_duid *duid) { + sd_id128_t machine_id; + bool test_mode; + uint64_t hash; + int r; + + assert_return(duid, -EINVAL); + + test_mode = network_test_mode_enabled(); + + if (!test_mode) { + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + } else + /* For tests, especially for fuzzers, reproducibility is important. + * Hence, use a static and constant machine ID. + * See 9216fddc5a8ac2742e6cfa7660f95c20ca4f2193. */ + machine_id = SD_ID128_MAKE(01, 02, 03, 04, 05, 06, 07, 08, 09, 0a, 0b, 0c, 0d, 0e, 0f, 10); + + unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_EN); + unaligned_write_be32(&duid->duid.en.pen, SYSTEMD_PEN); + + /* a bit of snake-oil perhaps, but no need to expose the machine-id + * directly; duid->en.id might not be aligned, so we need to copy */ + hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); + memcpy(duid->duid.en.id, &hash, sizeof(hash)); + + duid->size = offsetof(struct duid, en.id) + sizeof(hash); + + if (test_mode) + assert_se(memcmp(&duid->duid, (const uint8_t[]) { 0x00, 0x02, 0x00, 0x00, 0xab, 0x11, 0x61, 0x77, 0x40, 0xde, 0x13, 0x42, 0xc3, 0xa2 }, duid->size) == 0); + + return 0; +} + +int sd_dhcp_duid_set_uuid(sd_dhcp_duid *duid) { + sd_id128_t machine_id; + int r; + + assert_return(duid, -EINVAL); + + r = sd_id128_get_machine_app_specific(APPLICATION_ID, &machine_id); + if (r < 0) + return r; + + unaligned_write_be16(&duid->duid.type, SD_DUID_TYPE_UUID); + memcpy(&duid->duid.uuid.uuid, &machine_id, sizeof(machine_id)); + + duid->size = offsetof(struct duid, uuid.uuid) + sizeof(machine_id); + return 0; +} + +int dhcp_duid_to_string_internal(uint16_t type, const void *data, size_t data_size, char **ret) { + _cleanup_free_ char *p = NULL, *x = NULL; + const char *t; + + assert(data); + assert(ret); + + if (!duid_data_size_is_valid(data_size)) + return -EINVAL; + + x = hexmem(data, data_size); + if (!x) + return -ENOMEM; + + t = duid_type_to_string(type); + if (!t) + return asprintf(ret, "%04x:%s", htobe16(type), x); + + p = strjoin(t, ":", x); + if (!p) + return -ENOMEM; + + *ret = TAKE_PTR(p); + return 0; +} + +int sd_dhcp_duid_to_string(const sd_dhcp_duid *duid, char **ret) { + uint16_t type; + const void *data; + size_t data_size; + int r; + + assert_return(sd_dhcp_duid_is_set(duid), -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_dhcp_duid_get(duid, &type, &data, &data_size); + if (r < 0) + return r; + + return dhcp_duid_to_string_internal(type, data, data_size, ret); +} + +int dhcp_identifier_set_iaid( + sd_device *dev, + const struct hw_addr_data *hw_addr, + bool legacy_unstable_byteorder, + void *ret) { + + const char *name = NULL; + uint32_t id32; + uint64_t id; + + assert(hw_addr); + assert(ret); + + if (dev) + name = net_get_persistent_name(dev); + if (name) + id = siphash24(name, strlen(name), HASH_KEY.bytes); + else + /* fall back to MAC address if no predictable name available */ + id = siphash24(hw_addr->bytes, hw_addr->length, HASH_KEY.bytes); + + id32 = (id & 0xffffffff) ^ (id >> 32); + + if (legacy_unstable_byteorder) + /* for historical reasons (a bug), the bits were swapped and thus + * the result was endianness dependent. Preserve that behavior. */ + id32 = bswap_32(id32); + else + /* the fixed behavior returns a stable byte order. Since LE is expected + * to be more common, swap the bytes on LE to give the same as legacy + * behavior. */ + id32 = be32toh(id32); + + unaligned_write_ne32(ret, id32); + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 202d75f..37f4b3b 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -420,7 +420,6 @@ static sd_dhcp_lease *dhcp_lease_free(sd_dhcp_lease *lease) { free(lease->static_routes); free(lease->classless_routes); - free(lease->client_id); free(lease->vendor_specific); strv_free(lease->search_domains); free(lease->sixrd_br_addresses); @@ -1070,8 +1069,8 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { _cleanup_fclose_ FILE *f = NULL; struct in_addr address; const struct in_addr *addresses; - const void *client_id, *data; - size_t client_id_len, data_len; + const void *data; + size_t data_len; const char *string; uint16_t mtu; _cleanup_free_ sd_dhcp_route **routes = NULL; @@ -1187,11 +1186,10 @@ int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { if (r >= 0) fprintf(f, "TIMEZONE=%s\n", string); - r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); - if (r >= 0) { + if (sd_dhcp_client_id_is_set(&lease->client_id)) { _cleanup_free_ char *client_id_hex = NULL; - client_id_hex = hexmem(client_id, client_id_len); + client_id_hex = hexmem(lease->client_id.raw, lease->client_id.size); if (!client_id_hex) return -ENOMEM; fprintf(f, "CLIENTID=%s\n", client_id_hex); @@ -1482,13 +1480,20 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { } if (client_id_hex) { - r = unhexmem(client_id_hex, SIZE_MAX, &lease->client_id, &lease->client_id_len); + _cleanup_free_ void *data = NULL; + size_t data_size; + + r = unhexmem(client_id_hex, &data, &data_size); if (r < 0) log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); + + r = sd_dhcp_client_id_set_raw(&lease->client_id, data, data_size); + if (r < 0) + log_debug_errno(r, "Failed to assign client ID, ignoring: %m"); } if (vendor_specific_hex) { - r = unhexmem(vendor_specific_hex, SIZE_MAX, &lease->vendor_specific, &lease->vendor_specific_len); + r = unhexmem(vendor_specific_hex, &lease->vendor_specific, &lease->vendor_specific_len); if (r < 0) log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); } @@ -1500,7 +1505,7 @@ int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { if (!options[i]) continue; - r = unhexmem(options[i], SIZE_MAX, &data, &len); + r = unhexmem(options[i], &data, &len); if (r < 0) { log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); continue; @@ -1541,36 +1546,25 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { return 0; } -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) { +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret) { assert_return(lease, -EINVAL); - assert_return(client_id, -EINVAL); - assert_return(client_id_len, -EINVAL); + assert_return(ret, -EINVAL); - if (!lease->client_id) + if (!sd_dhcp_client_id_is_set(&lease->client_id)) return -ENODATA; - *client_id = lease->client_id; - *client_id_len = lease->client_id_len; + *ret = &lease->client_id; return 0; } -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) { +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id *client_id) { assert_return(lease, -EINVAL); - assert_return(client_id || client_id_len <= 0, -EINVAL); - - if (client_id_len <= 0) - lease->client_id = mfree(lease->client_id); - else { - void *p; - p = memdup(client_id, client_id_len); - if (!p) - return -ENOMEM; + if (!sd_dhcp_client_id_is_set(client_id)) + return sd_dhcp_client_id_clear(&lease->client_id); - free_and_replace(lease->client_id, p); - lease->client_id_len = client_id_len; - } + lease->client_id = *client_id; return 0; } diff --git a/src/libsystemd-network/sd-dhcp-server-lease.c b/src/libsystemd-network/sd-dhcp-server-lease.c new file mode 100644 index 0000000..2f84d51 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-server-lease.c @@ -0,0 +1,513 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dhcp-server-lease-internal.h" +#include "fd-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "tmpfile-util.h" + +static sd_dhcp_server_lease* dhcp_server_lease_free(sd_dhcp_server_lease *lease) { + if (!lease) + return NULL; + + if (lease->server) { + hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease); + hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease); + hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease); + hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease); + } + + free(lease->hostname); + return mfree(lease); +} + +DEFINE_TRIVIAL_REF_UNREF_FUNC(sd_dhcp_server_lease, sd_dhcp_server_lease, dhcp_server_lease_free); + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + dhcp_server_lease_hash_ops, + sd_dhcp_client_id, + client_id_hash_func, + client_id_compare_func, + sd_dhcp_server_lease, + sd_dhcp_server_lease_unref); + +int dhcp_server_put_lease(sd_dhcp_server *server, sd_dhcp_server_lease *lease, bool is_static) { + int r; + + assert(server); + assert(lease); + + lease->server = server; /* This must be set before hashmap_put(). */ + + r = hashmap_ensure_put(is_static ? &server->static_leases_by_client_id : &server->bound_leases_by_client_id, + &dhcp_server_lease_hash_ops, &lease->client_id, lease); + if (r < 0) + return r; + + r = hashmap_ensure_put(is_static ? &server->static_leases_by_address : &server->bound_leases_by_address, + NULL, UINT32_TO_PTR(lease->address), lease); + if (r < 0) + return r; + + return 0; +} + +int dhcp_server_set_lease(sd_dhcp_server *server, be32_t address, DHCPRequest *req, usec_t expiration) { + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; + int r; + + assert(server); + assert(address != 0); + assert(req); + assert(expiration != 0); + + /* If a lease for the host already exists, update it. */ + lease = hashmap_get(server->bound_leases_by_client_id, &req->client_id); + if (lease) { + if (lease->address != address) { + hashmap_remove_value(server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease); + lease->address = address; + + r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); + if (r < 0) + return r; + } + + lease->expiration = expiration; + + TAKE_PTR(lease); + return 0; + } + + /* Otherwise, add a new lease. */ + + lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + .address = address, + .client_id = req->client_id, + .htype = req->message->htype, + .hlen = req->message->hlen, + .gateway = req->message->giaddr, + .expiration = expiration, + }; + + memcpy(lease->chaddr, req->message->chaddr, req->message->hlen); + + if (req->hostname) { + lease->hostname = strdup(req->hostname); + if (!lease->hostname) + return -ENOMEM; + } + + r = dhcp_server_put_lease(server, lease, /* is_static = */ false); + if (r < 0) + return r; + + TAKE_PTR(lease); + return 0; +} + +int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { + sd_dhcp_server_lease *lease; + usec_t time_now; + int r; + + assert(server); + + r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now); + if (r < 0) + return r; + + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) + if (lease->expiration < time_now) { + log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address)); + sd_dhcp_server_lease_unref(lease); + } + + return 0; +} + +sd_dhcp_server_lease* dhcp_server_get_static_lease(sd_dhcp_server *server, const DHCPRequest *req) { + sd_dhcp_server_lease *static_lease; + sd_dhcp_client_id client_id; + + assert(server); + assert(req); + + static_lease = hashmap_get(server->static_leases_by_client_id, &req->client_id); + if (static_lease) + goto verify; + + /* when no lease is found based on the client id fall back to chaddr */ + if (!client_id_data_size_is_valid(req->message->hlen)) + return NULL; + + if (sd_dhcp_client_id_set(&client_id, /* type = */ 1, req->message->chaddr, req->message->hlen) < 0) + return NULL; + + static_lease = hashmap_get(server->static_leases_by_client_id, &client_id); + if (!static_lease) + return NULL; + +verify: + /* Check if the address is in the same subnet. */ + if ((static_lease->address & server->netmask) != server->subnet) + return NULL; + + /* Check if the address is different from the server address. */ + if (static_lease->address == server->address) + return NULL; + + return static_lease; +} + +int sd_dhcp_server_set_static_lease( + sd_dhcp_server *server, + const struct in_addr *address, + uint8_t *client_id_raw, + size_t client_id_size) { + + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; + sd_dhcp_client_id client_id; + int r; + + assert_return(server, -EINVAL); + assert_return(client_id_raw, -EINVAL); + assert_return(client_id_size_is_valid(client_id_size), -EINVAL); + assert_return(!sd_dhcp_server_is_running(server), -EBUSY); + + r = sd_dhcp_client_id_set_raw(&client_id, client_id_raw, client_id_size); + if (r < 0) + return r; + + /* Static lease with an empty or omitted address is a valid entry, + * the server removes any static lease with the specified mac address. */ + if (!address || address->s_addr == 0) { + sd_dhcp_server_lease_unref(hashmap_get(server->static_leases_by_client_id, &client_id)); + return 0; + } + + lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + .address = address->s_addr, + .client_id = client_id, + }; + + r = dhcp_server_put_lease(server, lease, /* is_static = */ true); + if (r < 0) + return r; + + TAKE_PTR(lease); + return 0; +} + +static int dhcp_server_lease_append_json(sd_dhcp_server_lease *lease, JsonVariant **ret) { + assert(lease); + assert(ret); + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_BYTE_ARRAY("ClientId", lease->client_id.raw, lease->client_id.size), + JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &(struct in_addr) { .s_addr = lease->address }), + JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname))); +} + +int dhcp_server_bound_leases_append_json(sd_dhcp_server *server, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + sd_dhcp_server_lease *lease; + usec_t now_b, now_r; + int r; + + assert(server); + assert(v); + + r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); + if (r < 0) + return r; + + r = sd_event_now(server->event, CLOCK_REALTIME, &now_r); + if (r < 0) + return r; + + HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = dhcp_server_lease_append_json(lease, &w); + if (r < 0) + return r; + + usec_t exp_r = map_clock_usec_raw(lease->expiration, now_b, now_r); + + r = json_variant_merge_objectb(&w, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("ExpirationUSec", lease->expiration), + JSON_BUILD_PAIR_UNSIGNED("ExpirationRealtimeUSec", exp_r))); + if (r < 0) + return r; + + r = json_variant_append_array(&array, w); + if (r < 0) + return r; + } + + return json_variant_set_field_non_null(v, "Leases", array); +} + +int dhcp_server_static_leases_append_json(sd_dhcp_server *server, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + sd_dhcp_server_lease *lease; + int r; + + assert(server); + assert(v); + + HASHMAP_FOREACH(lease, server->static_leases_by_client_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = dhcp_server_lease_append_json(lease, &w); + if (r < 0) + return r; + + r = json_variant_append_array(&array, w); + if (r < 0) + return r; + } + + return json_variant_set_field_non_null(v, "StaticLeases", array); +} + +int dhcp_server_save_leases(sd_dhcp_server *server) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + sd_id128_t boot_id; + int r; + + assert(server); + + if (!server->lease_file) + return 0; + + if (hashmap_isempty(server->bound_leases_by_client_id)) { + if (unlink(server->lease_file) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + r = json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_ID128("BootID", boot_id), + JSON_BUILD_PAIR_IN4_ADDR("Address", &(struct in_addr) { .s_addr = server->address }), + JSON_BUILD_PAIR_UNSIGNED("PrefixLength", + in4_addr_netmask_to_prefixlen(&(struct in_addr) { .s_addr = server->netmask })))); + if (r < 0) + return r; + + r = dhcp_server_bound_leases_append_json(server, &v); + if (r < 0) + return r; + + r = mkdirat_parents(server->lease_dir_fd, server->lease_file, 0755); + if (r < 0) + return r; + + r = fopen_temporary_at(server->lease_dir_fd, server->lease_file, &f, &temp_path); + if (r < 0) + return r; + + (void) fchmod(fileno(f), 0644); + + r = json_variant_dump(v, JSON_FORMAT_NEWLINE | JSON_FORMAT_FLUSH, f, /* prefix = */ NULL); + if (r < 0) + goto failure; + + r = conservative_renameat(server->lease_dir_fd, temp_path, server->lease_dir_fd, server->lease_file); + if (r < 0) + goto failure; + + return 0; + +failure: + (void) unlinkat(server->lease_dir_fd, temp_path, /* flags = */ 0); + return r; +} + +static int json_dispatch_dhcp_lease(sd_dhcp_server *server, JsonVariant *v, bool use_boottime) { + static const JsonDispatch dispatch_table_boottime[] = { + { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY }, + { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY }, + { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, + { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY }, + {} + }, dispatch_table_realtime[] = { + { "ClientId", JSON_VARIANT_ARRAY, json_dispatch_client_id, offsetof(sd_dhcp_server_lease, client_id), JSON_MANDATORY }, + { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(sd_dhcp_server_lease, address), JSON_MANDATORY }, + { "Hostname", JSON_VARIANT_STRING, json_dispatch_string, offsetof(sd_dhcp_server_lease, hostname), 0 }, + { "ExpirationUSec", _JSON_VARIANT_TYPE_INVALID, NULL, 0, JSON_MANDATORY }, + { "ExpirationRealtimeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(sd_dhcp_server_lease, expiration), JSON_MANDATORY }, + {} + }; + + _cleanup_(sd_dhcp_server_lease_unrefp) sd_dhcp_server_lease *lease = NULL; + usec_t now_b; + int r; + + assert(server); + assert(v); + + lease = new(sd_dhcp_server_lease, 1); + if (!lease) + return -ENOMEM; + + *lease = (sd_dhcp_server_lease) { + .n_ref = 1, + }; + + r = json_dispatch(v, use_boottime ? dispatch_table_boottime : dispatch_table_realtime, JSON_ALLOW_EXTENSIONS, lease); + if (r < 0) + return r; + + r = sd_event_now(server->event, CLOCK_BOOTTIME, &now_b); + if (r < 0) + return r; + + if (use_boottime) { + if (lease->expiration < now_b) + return 0; /* already expired */ + } else { + usec_t now_r; + + r = sd_event_now(server->event, CLOCK_REALTIME, &now_r); + if (r < 0) + return r; + + if (lease->expiration < now_r) + return 0; /* already expired */ + + lease->expiration = map_clock_usec_raw(lease->expiration, now_r, now_b); + } + + r = dhcp_server_put_lease(server, lease, /* is_static = */ false); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(lease); + return 0; +} + +typedef struct SavedInfo { + sd_id128_t boot_id; + struct in_addr address; + uint8_t prefixlen; + JsonVariant *leases; +} SavedInfo; + +static void saved_info_done(SavedInfo *info) { + if (!info) + return; + + json_variant_unref(info->leases); +} + +static int load_leases_file(int dir_fd, const char *path, SavedInfo *ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(path); + assert(ret); + + r = json_parse_file_at( + /* f = */ NULL, + dir_fd, + path, + /* flags = */ 0, + &v, + /* ret_line = */ NULL, + /* ret_column = */ NULL); + if (r < 0) + return r; + + static const JsonDispatch dispatch_lease_file_table[] = { + { "BootID", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(SavedInfo, boot_id), JSON_MANDATORY }, + { "Address", JSON_VARIANT_ARRAY, json_dispatch_in_addr, offsetof(SavedInfo, address), JSON_MANDATORY }, + { "PrefixLength", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint8, offsetof(SavedInfo, prefixlen), JSON_MANDATORY }, + { "Leases", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(SavedInfo, leases), JSON_MANDATORY }, + {} + }; + + return json_dispatch(v, dispatch_lease_file_table, JSON_ALLOW_EXTENSIONS, ret); +} + +int dhcp_server_load_leases(sd_dhcp_server *server) { + _cleanup_(saved_info_done) SavedInfo info = {}; + sd_id128_t boot_id; + size_t n, m; + int r; + + assert(server); + assert(server->event); + + if (!server->lease_file) + return 0; + + r = load_leases_file(server->lease_dir_fd, server->lease_file, &info); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + n = hashmap_size(server->bound_leases_by_client_id); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, info.leases) + RET_GATHER(r, json_dispatch_dhcp_lease(server, i, /* use_boottime = */ sd_id128_equal(info.boot_id, boot_id))); + + m = hashmap_size(server->bound_leases_by_client_id); + assert(m >= n); + log_dhcp_server(server, "Loaded %zu lease(s) from %s.", m - n, server->lease_file); + + return r; +} + +int dhcp_server_leases_file_get_server_address( + int dir_fd, + const char *path, + struct in_addr *ret_address, + uint8_t *ret_prefixlen) { + + _cleanup_(saved_info_done) SavedInfo info = {}; + int r; + + if (!ret_address && !ret_prefixlen) + return 0; + + r = load_leases_file(dir_fd, path, &info); + if (r < 0) + return r; + + if (ret_address) + *ret_address = info.address; + if (ret_prefixlen) + *ret_prefixlen = info.prefixlen; + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index b87e4d6..c3b0f82 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -14,6 +14,7 @@ #include "dhcp-option.h" #include "dhcp-packet.h" #include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" #include "dns-domain.h" #include "fd-util.h" #include "in-addr-util.h" @@ -21,6 +22,7 @@ #include "memory-util.h" #include "network-common.h" #include "ordered-set.h" +#include "path-util.h" #include "siphash24.h" #include "string-util.h" #include "unaligned.h" @@ -29,20 +31,17 @@ #define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR #define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12) -DHCPLease *dhcp_lease_free(DHCPLease *lease) { - if (!lease) - return NULL; +static void server_on_lease_change(sd_dhcp_server *server) { + int r; - if (lease->server) { - hashmap_remove_value(lease->server->bound_leases_by_address, UINT32_TO_PTR(lease->address), lease); - hashmap_remove_value(lease->server->bound_leases_by_client_id, &lease->client_id, lease); - hashmap_remove_value(lease->server->static_leases_by_address, UINT32_TO_PTR(lease->address), lease); - hashmap_remove_value(lease->server->static_leases_by_client_id, &lease->client_id, lease); - } + assert(server); - free(lease->client_id.data); - free(lease->hostname); - return mfree(lease); + r = dhcp_server_save_leases(server); + if (r < 0) + log_dhcp_server_errno(server, r, "Failed to save leases, ignoring: %m"); + + if (server->callback) + server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); } /* configures the server's address and subnet, and optionally the pool's size and offset into the subnet @@ -102,13 +101,6 @@ int sd_dhcp_server_configure_pool( server->address = address->s_addr; server->netmask = netmask; server->subnet = address->s_addr & netmask; - - /* Drop any leases associated with the old address range */ - hashmap_clear(server->bound_leases_by_address); - hashmap_clear(server->bound_leases_by_client_id); - - if (server->callback) - server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); } return 0; @@ -127,38 +119,6 @@ int sd_dhcp_server_is_in_relay_mode(sd_dhcp_server *server) { return in4_addr_is_set(&server->relay_target); } -void client_id_hash_func(const DHCPClientId *id, struct siphash *state) { - assert(id); - assert(id->length > 0); - assert(id->data); - - siphash24_compress(&id->length, sizeof(id->length), state); - siphash24_compress(id->data, id->length, state); -} - -int client_id_compare_func(const DHCPClientId *a, const DHCPClientId *b) { - int r; - - assert(a->length > 0); - assert(a->data); - assert(b->length > 0); - assert(b->data); - - r = CMP(a->length, b->length); - if (r != 0) - return r; - - return memcmp(a->data, b->data, a->length); -} - -DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( - dhcp_lease_hash_ops, - DHCPClientId, - client_id_hash_func, - client_id_compare_func, - DHCPLease, - dhcp_lease_free); - static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { assert(server); @@ -184,6 +144,9 @@ static sd_dhcp_server *dhcp_server_free(sd_dhcp_server *server) { free(server->agent_circuit_id); free(server->agent_remote_id); + safe_close(server->lease_dir_fd); + free(server->lease_file); + free(server->ifname); return mfree(server); } @@ -212,6 +175,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { .default_lease_time = DHCP_DEFAULT_LEASE_TIME_USEC, .max_lease_time = DHCP_MAX_LEASE_TIME_USEC, .rapid_commit = true, + .lease_dir_fd = -EBADF, }; *ret = TAKE_PTR(server); @@ -786,16 +750,8 @@ static int parse_request(uint8_t code, uint8_t len, const void *option, void *us break; case SD_DHCP_OPTION_CLIENT_IDENTIFIER: - if (len >= 2) { - uint8_t *data; - - data = memdup(option, len); - if (!data) - return -ENOMEM; - - free_and_replace(req->client_id.data, data); - req->client_id.length = len; - } + if (client_id_size_is_valid(len)) + (void) sd_dhcp_client_id_set_raw(&req->client_id, option, len); break; case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: @@ -835,7 +791,6 @@ static DHCPRequest* dhcp_request_free(DHCPRequest *req) { if (!req) return NULL; - free(req->client_id.data); free(req->hostname); return mfree(req); } @@ -843,6 +798,8 @@ static DHCPRequest* dhcp_request_free(DHCPRequest *req) { DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { + int r; + assert(req); assert(message); @@ -852,39 +809,39 @@ static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMes return -EBADMSG; /* set client id based on MAC address if client did not send an explicit one */ - if (!req->client_id.data) { - uint8_t *data; - - if (message->hlen == 0) + if (!sd_dhcp_client_id_is_set(&req->client_id)) { + if (!client_id_data_size_is_valid(message->hlen)) return -EBADMSG; - data = new0(uint8_t, message->hlen + 1); - if (!data) - return -ENOMEM; - - data[0] = 0x01; - memcpy(data + 1, message->chaddr, message->hlen); - - req->client_id.length = message->hlen + 1; - req->client_id.data = data; + r = sd_dhcp_client_id_set(&req->client_id, /* type = */ 1, message->chaddr, message->hlen); + if (r < 0) + return r; } if (message->hlen == 0 || memeqzero(message->chaddr, message->hlen)) { + uint8_t type; + const void *data; + size_t size; + /* See RFC2131 section 4.1.1. * hlen and chaddr may not be set for non-ethernet interface. * Let's try to retrieve it from the client ID. */ - if (!req->client_id.data) + if (!sd_dhcp_client_id_is_set(&req->client_id)) return -EBADMSG; - if (req->client_id.length <= 1 || req->client_id.length > sizeof(message->chaddr) + 1) + r = sd_dhcp_client_id_get(&req->client_id, &type, &data, &size); + if (r < 0) + return r; + + if (type != 1) return -EBADMSG; - if (req->client_id.data[0] != 0x01) + if (size > sizeof(message->chaddr)) return -EBADMSG; - message->hlen = req->client_id.length - 1; - memcpy(message->chaddr, req->client_id.data + 1, message->hlen); + memcpy(message->chaddr, data, size); + message->hlen = size; } if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) @@ -1020,44 +977,7 @@ static int dhcp_server_relay_message(sd_dhcp_server *server, DHCPMessage *messag return -EBADMSG; } -static int prepare_new_lease(DHCPLease **ret_lease, be32_t address, DHCPRequest *req, usec_t expiration) { - _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL; - - assert(ret_lease); - assert(address != 0); - assert(req); - assert(expiration != 0); - - lease = new(DHCPLease, 1); - if (!lease) - return -ENOMEM; - - *lease = (DHCPLease) { - .address = address, - .client_id.length = req->client_id.length, - .htype = req->message->htype, - .hlen = req->message->hlen, - .gateway = req->message->giaddr, - .expiration = expiration, - }; - lease->client_id.data = memdup(req->client_id.data, req->client_id.length); - if (!lease->client_id.data) - return -ENOMEM; - - memcpy(lease->chaddr, req->message->chaddr, req->message->hlen); - - if (req->hostname) { - lease->hostname = strdup(req->hostname); - if (!lease->hostname) - return -ENOMEM; - } - - *ret_lease = TAKE_PTR(lease); - - return 0; -} - -static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLease *existing_lease, be32_t address) { +static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, be32_t address) { usec_t expiration; int r; @@ -1069,30 +989,9 @@ static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLeas if (r < 0) return r; - if (existing_lease) { - assert(existing_lease->server); - assert(existing_lease->address == address); - existing_lease->expiration = expiration; - - } else { - _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL; - - r = prepare_new_lease(&lease, address, req, expiration); - if (r < 0) - return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); - - lease->server = server; /* This must be set just before hashmap_put(). */ - - r = hashmap_ensure_put(&server->bound_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not save lease: %m"); - - r = hashmap_ensure_put(&server->bound_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); - if (r < 0) - return log_dhcp_server_errno(server, r, "Could not save lease: %m"); - - TAKE_PTR(lease); - } + r = dhcp_server_set_lease(server, address, req, expiration); + if (r < 0) + return log_dhcp_server_errno(server, r, "Failed to create new lease: %m"); r = server_send_offer_or_ack(server, req, address, DHCP_ACK); if (r < 0) @@ -1100,32 +999,11 @@ static int server_ack_request(sd_dhcp_server *server, DHCPRequest *req, DHCPLeas log_dhcp_server(server, "ACK (0x%x)", be32toh(req->message->xid)); - if (server->callback) - server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); + server_on_lease_change(server); return DHCP_ACK; } -static int dhcp_server_cleanup_expired_leases(sd_dhcp_server *server) { - DHCPLease *lease; - usec_t time_now; - int r; - - assert(server); - - r = sd_event_now(server->event, CLOCK_BOOTTIME, &time_now); - if (r < 0) - return r; - - HASHMAP_FOREACH(lease, server->bound_leases_by_client_id) - if (lease->expiration < time_now) { - log_dhcp_server(server, "CLEAN (0x%x)", be32toh(lease->address)); - dhcp_lease_free(lease); - } - - return 0; -} - static bool address_available(sd_dhcp_server *server, be32_t address) { assert(server); @@ -1137,46 +1015,12 @@ 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, const triple_timestamp *timestamp) { _cleanup_(dhcp_request_freep) DHCPRequest *req = NULL; _cleanup_free_ char *error_message = NULL; - DHCPLease *existing_lease, *static_lease; + sd_dhcp_server_lease *existing_lease, *static_lease; int type, r; assert(server); @@ -1204,9 +1048,7 @@ 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); - r = server_get_static_lease(server, req, &static_lease); - if (r < 0) - return r; + static_lease = dhcp_server_get_static_lease(server, req); switch (type) { @@ -1220,10 +1062,19 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz return 0; /* for now pick a random free address from the pool */ - if (static_lease) + if (static_lease) { + if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(static_lease->address))) + /* The address is already assigned to another host. Refusing. */ + return 0; + + /* Found a matching static lease. */ address = static_lease->address; - else if (existing_lease) + + } else if (existing_lease && address_is_in_pool(server, existing_lease->address)) + + /* If we previously assigned an address to the host, then reuse it. */ address = existing_lease->address; + else { struct siphash state; uint64_t hash; @@ -1252,7 +1103,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz return 0; if (server->rapid_commit && req->rapid_commit) - return server_ack_request(server, req, existing_lease, address); + return server_ack_request(server, req, address); r = server_send_offer_or_ack(server, req, address, DHCP_OFFER); if (r < 0) @@ -1321,30 +1172,24 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz /* Silently ignore Rapid Commit option in REQUEST message. */ req->rapid_commit = false; - /* disallow our own address */ - if (address == server->address) - return 0; - if (static_lease) { - /* Found a static lease for the client ID. */ - if (static_lease->address != address) - /* The client requested an address which is different from the static lease. Refuse. */ + /* The client requested an address which is different from the static lease. Refusing. */ return server_send_nak_or_ignore(server, init_reboot, req); - return server_ack_request(server, req, existing_lease, address); - } - - if (address_is_in_pool(server, address)) { - /* The requested address is in the pool. */ - - if (existing_lease && existing_lease->address != address) - /* We previously assigned an address, but the client requested another one. Refuse. */ + if (existing_lease != hashmap_get(server->bound_leases_by_address, UINT32_TO_PTR(address))) + /* The requested address is already assigned to another host. Refusing. */ return server_send_nak_or_ignore(server, init_reboot, req); - return server_ack_request(server, req, existing_lease, address); + /* Found a static lease for the client ID. */ + return server_ack_request(server, req, address); } + if (address_is_in_pool(server, address)) + /* The requested address is in the pool. */ + return server_ack_request(server, req, address); + + /* Refuse otherwise. */ return server_send_nak_or_ignore(server, init_reboot, req); } @@ -1358,10 +1203,9 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, siz if (existing_lease->address != req->message->ciaddr) return 0; - dhcp_lease_free(existing_lease); + sd_dhcp_server_lease_unref(existing_lease); - if (server->callback) - server->callback(server, SD_DHCP_SERVER_EVENT_LEASE_CHANGED, server->callback_userdata); + server_on_lease_change(server); return 0; }} @@ -1515,6 +1359,10 @@ int sd_dhcp_server_start(sd_dhcp_server *server) { goto on_error; } + r = dhcp_server_load_leases(server); + if (r < 0) + log_dhcp_server_errno(server, r, "Failed to load lease file %s, ignoring: %m", strna(server->lease_file)); + log_dhcp_server(server, "STARTED"); return 0; @@ -1525,7 +1373,7 @@ on_error: } int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { - DHCPLease *lease; + sd_dhcp_server_lease *lease; int r = 0; assert_return(server, -EINVAL); @@ -1740,55 +1588,32 @@ int sd_dhcp_server_set_relay_agent_information( return 0; } -int sd_dhcp_server_set_static_lease( - sd_dhcp_server *server, - const struct in_addr *address, - uint8_t *client_id, - size_t client_id_size) { - - _cleanup_(dhcp_lease_freep) DHCPLease *lease = NULL; +int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path) { int r; assert_return(server, -EINVAL); - assert_return(client_id, -EINVAL); - assert_return(client_id_size > 0, -EINVAL); + assert_return(!path || (dir_fd >= 0 || dir_fd == AT_FDCWD), -EBADF); assert_return(!sd_dhcp_server_is_running(server), -EBUSY); - /* Static lease with an empty or omitted address is a valid entry, - * the server removes any static lease with the specified mac address. */ - if (!address || address->s_addr == 0) { - DHCPClientId c; - - c = (DHCPClientId) { - .length = client_id_size, - .data = client_id, - }; - - dhcp_lease_free(hashmap_get(server->static_leases_by_client_id, &c)); + if (!path) { + /* When NULL, clear the previous assignment. */ + server->lease_file = mfree(server->lease_file); + server->lease_dir_fd = safe_close(server->lease_dir_fd); return 0; } - lease = new(DHCPLease, 1); - if (!lease) - return -ENOMEM; - - *lease = (DHCPLease) { - .address = address->s_addr, - .client_id.length = client_id_size, - }; - lease->client_id.data = memdup(client_id, client_id_size); - if (!lease->client_id.data) - return -ENOMEM; + if (!path_is_safe(path)) + return -EINVAL; - lease->server = server; /* This must be set just before hashmap_put(). */ + _cleanup_close_ int fd = fd_reopen(dir_fd, O_CLOEXEC | O_DIRECTORY | O_PATH); + if (fd < 0) + return fd; - r = hashmap_ensure_put(&server->static_leases_by_client_id, &dhcp_lease_hash_ops, &lease->client_id, lease); - if (r < 0) - return r; - r = hashmap_ensure_put(&server->static_leases_by_address, NULL, UINT32_TO_PTR(lease->address), lease); + r = free_and_strdup(&server->lease_file, path); if (r < 0) return r; - TAKE_PTR(lease); + close_and_replace(server->lease_dir_fd, fd); + return 0; } diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index c20367d..3e992d7 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -12,13 +12,12 @@ #include "alloc-util.h" #include "device-util.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dns-domain.h" #include "event-util.h" #include "fd-util.h" -#include "hexdecoct.h" #include "hostname-util.h" #include "in-addr-util.h" #include "iovec-util.h" @@ -191,10 +190,10 @@ int sd_dhcp6_client_add_vendor_option(sd_dhcp6_client *client, sd_dhcp6_option * static int client_ensure_duid(sd_dhcp6_client *client) { assert(client); - if (client->duid_len != 0) + if (sd_dhcp_duid_is_set(&client->duid)) return 0; - return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + return sd_dhcp6_client_set_duid_en(client); } /** @@ -208,7 +207,7 @@ int sd_dhcp6_client_set_duid_llt(sd_dhcp6_client *client, uint64_t llt_time) { assert_return(client, -EINVAL); assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - r = dhcp_identifier_set_duid_llt(&client->hw_addr, client->arp_type, llt_time, &client->duid, &client->duid_len); + r = sd_dhcp_duid_set_llt(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type, llt_time); if (r < 0) return log_dhcp6_client_errno(client, r, "Failed to set DUID-LLT: %m"); @@ -221,7 +220,7 @@ int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client) { assert_return(client, -EINVAL); assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - r = dhcp_identifier_set_duid_ll(&client->hw_addr, client->arp_type, &client->duid, &client->duid_len); + r = sd_dhcp_duid_set_ll(&client->duid, client->hw_addr.bytes, client->hw_addr.length, client->arp_type); if (r < 0) return log_dhcp6_client_errno(client, r, "Failed to set DUID-LL: %m"); @@ -234,7 +233,7 @@ int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client) { assert_return(client, -EINVAL); assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + r = sd_dhcp_duid_set_en(&client->duid); if (r < 0) return log_dhcp6_client_errno(client, r, "Failed to set DUID-EN: %m"); @@ -247,7 +246,7 @@ int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client) { assert_return(client, -EINVAL); assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); - r = dhcp_identifier_set_duid_uuid(&client->duid, &client->duid_len); + r = sd_dhcp_duid_set_uuid(&client->duid); if (r < 0) return log_dhcp6_client_errno(client, r, "Failed to set DUID-UUID: %m"); @@ -261,46 +260,41 @@ int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, co assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); assert_return(duid || duid_len == 0, -EINVAL); - r = dhcp_identifier_set_duid_raw(duid_type, duid, duid_len, &client->duid, &client->duid_len); + r = sd_dhcp_duid_set(&client->duid, duid_type, duid, duid_len); if (r < 0) return log_dhcp6_client_errno(client, r, "Failed to set DUID: %m"); return 0; } -int sd_dhcp6_client_duid_as_string( - sd_dhcp6_client *client, - char **duid) { - _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; - const char *v; - int r; +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, const sd_dhcp_duid *duid) { + assert_return(client, -EINVAL); + assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); + assert_return(sd_dhcp_duid_is_set(duid), -EINVAL); + + client->duid = *duid; + return 0; +} +int sd_dhcp6_client_get_duid(sd_dhcp6_client *client, const sd_dhcp_duid **ret) { assert_return(client, -EINVAL); - assert_return(client->duid_len > offsetof(struct duid, raw.data), -ENODATA); - assert_return(duid, -EINVAL); + assert_return(ret, -EINVAL); - v = duid_type_to_string(be16toh(client->duid.type)); - if (v) { - s = strdup(v); - if (!s) - return -ENOMEM; - } else { - r = asprintf(&s, "%0x", client->duid.type); - if (r < 0) - return -ENOMEM; - } + if (!sd_dhcp_duid_is_set(&client->duid)) + return -ENODATA; - t = hexmem(client->duid.raw.data, client->duid_len - offsetof(struct duid, raw.data)); - if (!t) - return -ENOMEM; + *ret = &client->duid; + return 0; +} - p = strjoin(s, ":", t); - if (!p) - return -ENOMEM; +int sd_dhcp6_client_get_duid_as_string(sd_dhcp6_client *client, char **ret) { + assert_return(client, -EINVAL); + assert_return(ret, -EINVAL); - *duid = TAKE_PTR(p); + if (!sd_dhcp_duid_is_set(&client->duid)) + return -ENODATA; - return 0; + return sd_dhcp_duid_to_string(&client->duid, ret); } int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { @@ -517,7 +511,6 @@ int sd_dhcp6_client_set_rapid_commit(sd_dhcp6_client *client, int enable) { int sd_dhcp6_client_set_send_release(sd_dhcp6_client *client, int enable) { assert_return(client, -EINVAL); - assert_return(!sd_dhcp6_client_is_running(client), -EBUSY); client->send_release = enable; return 0; @@ -750,8 +743,6 @@ static int client_append_mudurl(sd_dhcp6_client *client, uint8_t **buf, size_t * int dhcp6_client_send_message(sd_dhcp6_client *client) { _cleanup_free_ uint8_t *buf = NULL; - struct in6_addr all_servers = - IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; struct sd_dhcp6_option *j; usec_t elapsed_usec, time_now; be16_t elapsed_time; @@ -825,9 +816,9 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) { if (r < 0) return r; - assert(client->duid_len > 0); + assert(sd_dhcp_duid_is_set(&client->duid)); r = dhcp6_option_append(&buf, &offset, SD_DHCP6_OPTION_CLIENTID, - client->duid_len, &client->duid); + client->duid.size, &client->duid.duid); if (r < 0) return r; @@ -846,7 +837,7 @@ int dhcp6_client_send_message(sd_dhcp6_client *client) { if (r < 0) return r; - r = dhcp6_network_send_udp_socket(client->fd, &all_servers, buf, offset); + r = dhcp6_network_send_udp_socket(client->fd, &IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS, buf, offset); if (r < 0) return r; @@ -1337,7 +1328,7 @@ static int client_receive_message( return 0; } if ((size_t) len < sizeof(DHCP6Message)) { - log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); + log_dhcp6_client(client, "Too small to be DHCPv6 message: ignoring"); return 0; } @@ -1413,7 +1404,7 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) { r = client_send_release(client); if (r < 0) log_dhcp6_client_errno(client, r, - "Failed to send DHCP6 release message, ignoring: %m"); + "Failed to send DHCPv6 release message, ignoring: %m"); client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); @@ -1424,7 +1415,8 @@ int sd_dhcp6_client_stop(sd_dhcp6_client *client) { } int sd_dhcp6_client_is_running(sd_dhcp6_client *client) { - assert_return(client, -EINVAL); + if (!client) + return false; return client->state != DHCP6_STATE_STOPPED; } diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 674248b..e5d6547 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -10,6 +10,7 @@ #include "dhcp6-lease-internal.h" #include "network-common.h" #include "strv.h" +#include "unaligned.h" #define IRT_DEFAULT (1 * USEC_PER_DAY) #define IRT_MINIMUM (600 * USEC_PER_SEC) @@ -866,7 +867,7 @@ static int dhcp6_lease_parse_message( "%s message does not contain client ID. Ignoring.", dhcp6_message_type_to_string(message->type)); - if (memcmp_nn(clientid, clientid_len, &client->duid, client->duid_len) != 0) + if (memcmp_nn(clientid, clientid_len, &client->duid.duid, client->duid.size) != 0) return log_dhcp6_client_errno(client, SYNTHETIC_ERRNO(EINVAL), "The client ID in %s message does not match. Ignoring.", dhcp6_message_type_to_string(message->type)); diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index 0cc37a6..51d2b22 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -564,7 +564,8 @@ int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address) { } int sd_ipv4acd_is_running(sd_ipv4acd *acd) { - assert_return(acd, false); + if (!acd) + return false; return acd->state != IPV4ACD_STATE_INIT; } @@ -585,6 +586,12 @@ int sd_ipv4acd_start(sd_ipv4acd *acd, bool reset_conflicts) { assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + r = sd_event_get_state(acd->event); + if (r < 0) + return r; + if (r == SD_EVENT_FINISHED) + return -ESTALE; + r = arp_network_bind_raw_socket(acd->ifindex, &acd->address, &acd->mac_addr); if (r < 0) return r; diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index a29279e..5bf9833 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -206,7 +206,8 @@ int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) { } int sd_ipv4ll_is_running(sd_ipv4ll *ll) { - assert_return(ll, false); + if (!ll) + return false; return sd_ipv4acd_is_running(ll->acd); } diff --git a/src/libsystemd-network/sd-lldp-rx.c b/src/libsystemd-network/sd-lldp-rx.c index 2fc9a55..74000ff 100644 --- a/src/libsystemd-network/sd-lldp-rx.c +++ b/src/libsystemd-network/sd-lldp-rx.c @@ -10,6 +10,7 @@ #include "ether-addr-util.h" #include "event-util.h" #include "fd-util.h" +#include "json.h" #include "lldp-neighbor.h" #include "lldp-network.h" #include "lldp-rx-internal.h" @@ -490,6 +491,30 @@ int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***ret) { return k; } +int lldp_rx_build_neighbors_json(sd_lldp_rx *lldp_rx, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + assert(lldp_rx); + assert(ret); + + sd_lldp_neighbor *n; + HASHMAP_FOREACH(n, lldp_rx->neighbor_by_id) { + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + + r = lldp_neighbor_build_json(n, &w); + if (r < 0) + return r; + + r = json_variant_append_array(&v, w); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(v); + return 0; +} + int sd_lldp_rx_set_neighbors_max(sd_lldp_rx *lldp_rx, uint64_t m) { assert_return(lldp_rx, -EINVAL); assert_return(m > 0, -EINVAL); diff --git a/src/libsystemd-network/sd-ndisc-neighbor.c b/src/libsystemd-network/sd-ndisc-neighbor.c new file mode 100644 index 0000000..1bb6ebf --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-neighbor.c @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "ndisc-internal.h" +#include "ndisc-neighbor-internal.h" +#include "ndisc-option.h" + +static sd_ndisc_neighbor* ndisc_neighbor_free(sd_ndisc_neighbor *na) { + if (!na) + return NULL; + + icmp6_packet_unref(na->packet); + set_free(na->options); + return mfree(na); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor, ndisc_neighbor_free); + +sd_ndisc_neighbor* ndisc_neighbor_new(ICMP6Packet *packet) { + sd_ndisc_neighbor *na; + + assert(packet); + + na = new(sd_ndisc_neighbor, 1); + if (!na) + return NULL; + + *na = (sd_ndisc_neighbor) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + }; + + return na; +} + +int ndisc_neighbor_parse(sd_ndisc *nd, sd_ndisc_neighbor *na) { + int r; + + assert(na); + + if (na->packet->raw_size < sizeof(struct nd_neighbor_advert)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a neighbor advertisement, ignoring datagram."); + + /* Neighbor advertisement packets are neatly aligned to 64-bit boundaries, hence we can access them directly */ + const struct nd_neighbor_advert *a = (const struct nd_neighbor_advert*) na->packet->raw_packet; + assert(a->nd_na_type == ND_NEIGHBOR_ADVERT); + assert(a->nd_na_code == 0); + + na->flags = a->nd_na_flags_reserved; /* the first 3 bits */ + na->target_address = a->nd_na_target; + + /* RFC 4861 section 4.4: + * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that + * prompted this advertisement. For an unsolicited advertisement, the address whose link-layer + * address has changed. The Target Address MUST NOT be a multicast address. + * + * Here, we only check if the target address is a link-layer address (or a null address, for safety) + * when the message is an unsolicited neighbor advertisement. */ + if (!FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED)) + if (!in6_addr_is_link_local(&na->target_address) && !in6_addr_is_null(&na->target_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received ND packet with an invalid target address (%s), ignoring datagram.", + IN6_ADDR_TO_STRING(&na->target_address)); + + r = ndisc_parse_options(na->packet, &na->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in neighbor advertisement message, ignoring: %m"); + + return 0; +} + +int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret) { + assert_return(na, -EINVAL); + + return icmp6_packet_get_sender_address(na->packet, ret); +} + +int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret) { + assert_return(na, -EINVAL); + + if (in6_addr_is_null(&na->target_address)) + /* fall back to the sender address, for safety. */ + return sd_ndisc_neighbor_get_sender_address(na, ret); + + if (ret) + *ret = na->target_address; + return 0; +} + +int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret) { + assert_return(na, -EINVAL); + + return ndisc_option_get_mac(na->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret); +} + +int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret) { + assert_return(na, -EINVAL); + + if (ret) + *ret = na->flags; + return 0; +} + +int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_ROUTER); +} + +int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_SOLICITED); +} + +int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na) { + assert_return(na, -EINVAL); + + return FLAGS_SET(na->flags, ND_NA_FLAG_OVERRIDE); +} diff --git a/src/libsystemd-network/sd-ndisc-redirect.c b/src/libsystemd-network/sd-ndisc-redirect.c new file mode 100644 index 0000000..a1fceb2 --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-redirect.c @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "ndisc-internal.h" +#include "ndisc-option.h" +#include "ndisc-redirect-internal.h" + +static sd_ndisc_redirect* ndisc_redirect_free(sd_ndisc_redirect *rd) { + if (!rd) + return NULL; + + icmp6_packet_unref(rd->packet); + set_free(rd->options); + return mfree(rd); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_redirect, sd_ndisc_redirect, ndisc_redirect_free); + +sd_ndisc_redirect* ndisc_redirect_new(ICMP6Packet *packet) { + sd_ndisc_redirect *rd; + + assert(packet); + + rd = new(sd_ndisc_redirect, 1); + if (!rd) + return NULL; + + *rd = (sd_ndisc_redirect) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + }; + + return rd; +} + +int ndisc_redirect_parse(sd_ndisc *nd, sd_ndisc_redirect *rd) { + int r; + + assert(rd); + assert(rd->packet); + + if (rd->packet->raw_size < sizeof(struct nd_redirect)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a redirect message, ignoring."); + + const struct nd_redirect *a = (const struct nd_redirect*) rd->packet->raw_packet; + assert(a->nd_rd_type == ND_REDIRECT); + assert(a->nd_rd_code == 0); + + rd->target_address = a->nd_rd_target; + rd->destination_address = a->nd_rd_dst; + + /* RFC 4861 section 8.1 + * The ICMP Destination Address field in the redirect message does not contain a multicast address. */ + if (in6_addr_is_null(&rd->destination_address) || in6_addr_is_multicast(&rd->destination_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Redirect message with an invalid destination address, ignoring datagram: %m"); + + /* RFC 4861 section 8.1 + * The ICMP Target Address is either a link-local address (when redirected to a router) or the same + * as the ICMP Destination Address (when redirected to the on-link destination). */ + if (!in6_addr_is_link_local(&rd->target_address) && !in6_addr_equal(&rd->target_address, &rd->destination_address)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Received Redirect message with an invalid target address, ignoring datagram: %m"); + + r = ndisc_parse_options(rd->packet, &rd->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in Redirect message, ignoring datagram: %m"); + + return 0; +} + +int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr) { + assert_return(rd, -EINVAL); + + return icmp6_packet_set_sender_address(rd->packet, addr); +} + +int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + return icmp6_packet_get_sender_address(rd->packet, ret); +} + +int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + if (in6_addr_is_null(&rd->target_address)) + return -ENODATA; + + if (ret) + *ret = rd->target_address; + return 0; +} + +int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *rd, struct in6_addr *ret) { + assert_return(rd, -EINVAL); + + if (in6_addr_is_null(&rd->destination_address)) + return -ENODATA; + + if (ret) + *ret = rd->destination_address; + return 0; +} + +int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *rd, struct ether_addr *ret) { + assert_return(rd, -EINVAL); + + return ndisc_option_get_mac(rd->options, SD_NDISC_OPTION_TARGET_LL_ADDRESS, ret); +} + +int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *rd, struct ip6_hdr *ret) { + assert_return(rd, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get_by_type(rd->options, SD_NDISC_OPTION_REDIRECTED_HEADER); + if (!p) + return -ENODATA; + + if (ret) + *ret = p->hdr; + return 0; +} diff --git a/src/libsystemd-network/sd-ndisc-router-solicit.c b/src/libsystemd-network/sd-ndisc-router-solicit.c new file mode 100644 index 0000000..04e7c26 --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-router-solicit.c @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-radv.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "ndisc-option.h" +#include "ndisc-router-solicit-internal.h" +#include "radv-internal.h" + +static sd_ndisc_router_solicit* ndisc_router_solicit_free(sd_ndisc_router_solicit *rs) { + if (!rs) + return NULL; + + icmp6_packet_unref(rs->packet); + set_free(rs->options); + return mfree(rs); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router_solicit, sd_ndisc_router_solicit, ndisc_router_solicit_free); + +sd_ndisc_router_solicit* ndisc_router_solicit_new(ICMP6Packet *packet) { + sd_ndisc_router_solicit *rs; + + assert(packet); + + rs = new(sd_ndisc_router_solicit, 1); + if (!rs) + return NULL; + + *rs = (sd_ndisc_router_solicit) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + }; + + return rs; +} + +int ndisc_router_solicit_parse(sd_radv *ra, sd_ndisc_router_solicit *rs) { + int r; + + assert(rs); + assert(rs->packet); + + if (rs->packet->raw_size < sizeof(struct nd_router_solicit)) + return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a router solicit, ignoring."); + + const struct nd_router_solicit *a = (const struct nd_router_solicit*) rs->packet->raw_packet; + assert(a); + assert(a->nd_rs_type == ND_ROUTER_SOLICIT); + assert(a->nd_rs_code == 0); + + r = ndisc_parse_options(rs->packet, &rs->options); + if (r < 0) + return log_radv_errno(ra, r, "Failed to parse NDisc options in router solicit, ignoring datagram: %m"); + + /* RFC 4861 section 4.1. + * Source link-layer address: + * The link-layer address of the sender, if known. MUST NOT be included if the Source + * Address is the unspecified address. Otherwise, it SHOULD be included on link + * layers that have addresses. */ + if (ndisc_option_get_mac(rs->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, NULL) >= 0&& + sd_ndisc_router_solicit_get_sender_address(rs, NULL) == -ENODATA) + return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG), + "Router Solicitation message from null address unexpectedly contains source link-layer address option, ignoring datagaram."); + + return 0; +} + +int sd_ndisc_router_solicit_get_sender_address(sd_ndisc_router_solicit *rs, struct in6_addr *ret) { + assert_return(rs, -EINVAL); + + return icmp6_packet_get_sender_address(rs->packet, ret); +} + +int sd_ndisc_router_solicit_get_sender_mac(sd_ndisc_router_solicit *rs, struct ether_addr *ret) { + assert_return(rs, -EINVAL); + + return ndisc_option_get_mac(rs->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret); +} diff --git a/src/libsystemd-network/sd-ndisc-router.c b/src/libsystemd-network/sd-ndisc-router.c new file mode 100644 index 0000000..9ca737d --- /dev/null +++ b/src/libsystemd-network/sd-ndisc-router.c @@ -0,0 +1,344 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2014 Intel Corporation. All rights reserved. +***/ + +#include + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "ndisc-internal.h" +#include "ndisc-router-internal.h" +#include "string-table.h" + +static sd_ndisc_router* ndisc_router_free(sd_ndisc_router *rt) { + if (!rt) + return NULL; + + icmp6_packet_unref(rt->packet); + set_free(rt->options); + return mfree(rt); +} + +DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_ndisc_router, sd_ndisc_router, ndisc_router_free); + +sd_ndisc_router* ndisc_router_new(ICMP6Packet *packet) { + sd_ndisc_router *rt; + + assert(packet); + + rt = new(sd_ndisc_router, 1); + if (!rt) + return NULL; + + *rt = (sd_ndisc_router) { + .n_ref = 1, + .packet = icmp6_packet_ref(packet), + .iterator = ITERATOR_FIRST, + }; + + return rt; +} + +int sd_ndisc_router_set_sender_address(sd_ndisc_router *rt, const struct in6_addr *addr) { + assert_return(rt, -EINVAL); + + return icmp6_packet_set_sender_address(rt->packet, addr); +} + +int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret) { + assert_return(rt, -EINVAL); + + return icmp6_packet_get_sender_address(rt->packet, ret); +} + +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + return icmp6_packet_get_timestamp(rt->packet, clock, ret); +} + +#define DEFINE_GET_TIMESTAMP(name) \ + int sd_ndisc_router_##name##_timestamp( \ + sd_ndisc_router *rt, \ + clockid_t clock, \ + uint64_t *ret) { \ + \ + usec_t s, t; \ + int r; \ + \ + assert_return(rt, -EINVAL); \ + assert_return(ret, -EINVAL); \ + \ + r = sd_ndisc_router_##name(rt, &s); \ + if (r < 0) \ + return r; \ + \ + r = sd_ndisc_router_get_timestamp(rt, clock, &t); \ + if (r < 0) \ + return r; \ + \ + *ret = time_span_to_stamp(s, t); \ + return 0; \ + } + +DEFINE_GET_TIMESTAMP(get_lifetime); +DEFINE_GET_TIMESTAMP(prefix_get_valid_lifetime); +DEFINE_GET_TIMESTAMP(prefix_get_preferred_lifetime); +DEFINE_GET_TIMESTAMP(route_get_lifetime); +DEFINE_GET_TIMESTAMP(rdnss_get_lifetime); +DEFINE_GET_TIMESTAMP(dnssl_get_lifetime); +DEFINE_GET_TIMESTAMP(prefix64_get_lifetime); + +int ndisc_router_parse(sd_ndisc *nd, sd_ndisc_router *rt) { + const struct nd_router_advert *a; + int r; + + assert(rt); + assert(rt->packet); + + if (rt->packet->raw_size < sizeof(struct nd_router_advert)) + return log_ndisc_errno(nd, SYNTHETIC_ERRNO(EBADMSG), + "Too small to be a router advertisement, ignoring."); + + a = (const struct nd_router_advert*) rt->packet->raw_packet; + assert(a->nd_ra_type == ND_ROUTER_ADVERT); + assert(a->nd_ra_code == 0); + + rt->hop_limit = a->nd_ra_curhoplimit; + rt->flags = a->nd_ra_flags_reserved; /* the first 8 bits */ + rt->lifetime_usec = be16_sec_to_usec(a->nd_ra_router_lifetime, /* max_as_infinity = */ false); + rt->reachable_time_usec = be32_msec_to_usec(a->nd_ra_reachable, /* mas_as_infinity = */ false); + rt->retransmission_time_usec = be32_msec_to_usec(a->nd_ra_retransmit, /* max_as_infinity = */ false); + + /* RFC 4191 section 2.2 + * Prf (Default Router Preference) + * 2-bit signed integer. Indicates whether to prefer this router over other default routers. If the + * Router Lifetime is zero, the preference value MUST be set to (00) by the sender and MUST be + * ignored by the receiver. If the Reserved (10) value is received, the receiver MUST treat the value + * as if it were (00). */ + rt->preference = (rt->flags >> 3) & 3; + if (rt->preference == SD_NDISC_PREFERENCE_RESERVED) + rt->preference = SD_NDISC_PREFERENCE_MEDIUM; + + r = ndisc_parse_options(rt->packet, &rt->options); + if (r < 0) + return log_ndisc_errno(nd, r, "Failed to parse NDisc options in router advertisement message, ignoring: %m"); + + return 0; +} + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->hop_limit; + return 0; +} + +int sd_ndisc_router_get_reachable_time(sd_ndisc_router *rt, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->reachable_time_usec; + return 0; +} + +int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->retransmission_time_usec; + return 0; +} + +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_FLAGS_EXTENSION); + + *ret = rt->flags | (p ? p->extended_flags : 0); + return 0; +} + +int ndisc_router_flags_to_string(uint64_t flags, char **ret) { + _cleanup_free_ char *s = NULL; + + assert(ret); + + if (FLAGS_SET(flags, ND_RA_FLAG_MANAGED) && + !strextend_with_separator(&s, ", ", "managed")) + return -ENOMEM; + + if (FLAGS_SET(flags, ND_RA_FLAG_OTHER) && + !strextend_with_separator(&s, ", ", "other")) + return -ENOMEM; + + if (FLAGS_SET(flags, ND_RA_FLAG_HOME_AGENT) && + !strextend_with_separator(&s, ", ", "home-agent")) + return -ENOMEM; + + *ret = TAKE_PTR(s); + return 0; +} + +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret) { + assert_return(rt, -EINVAL); + + if (ret) + *ret = rt->lifetime_usec; + + return rt->lifetime_usec > 0; /* Indicate if the router is still valid or not. */ +} + +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->preference; + return 0; +} + +static const char* const ndisc_router_preference_table[] = { + [SD_NDISC_PREFERENCE_LOW] = "low", + [SD_NDISC_PREFERENCE_MEDIUM] = "medium", + [SD_NDISC_PREFERENCE_HIGH] = "high", + [SD_NDISC_PREFERENCE_RESERVED] = "reserved", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(ndisc_router_preference, int); + +int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret) { + assert_return(rt, -EINVAL); + + return ndisc_option_get_mac(rt->options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, ret); +} + +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_MTU); + if (!p) + return -ENODATA; + + *ret = p->mtu; + return 0; +} + +int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + sd_ndisc_option *p = ndisc_option_get_by_type(rt->options, SD_NDISC_OPTION_CAPTIVE_PORTAL); + if (!p) + return -ENODATA; + + *ret = p->captive_portal; + return 0; +} + +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { + assert_return(rt, -EINVAL); + + rt->iterator = ITERATOR_FIRST; + return sd_ndisc_router_option_next(rt); +} + +int sd_ndisc_router_option_next(sd_ndisc_router *rt) { + assert_return(rt, -EINVAL); + + return set_iterate(rt->options, &rt->iterator, (void**) &rt->current_option); +} + +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + if (!rt->current_option) + return -ENODATA; + + *ret = rt->current_option->type; + return 0; +} + +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { + uint8_t t; + int r; + + assert_return(rt, -EINVAL); + + r = sd_ndisc_router_option_get_type(rt, &t); + if (r < 0) + return r; + + return t == type; +} + +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size) { + assert_return(rt, -EINVAL); + + if (!rt->current_option) + return -ENODATA; + + return ndisc_option_parse(rt->packet, rt->current_option->offset, NULL, ret_size, ret); +} + +#define DEFINE_GETTER(name, type, element, element_type) \ + int sd_ndisc_router_##name##_get_##element( \ + sd_ndisc_router *rt, \ + element_type *ret) { \ + \ + int r; \ + \ + assert_return(rt, -EINVAL); \ + assert_return(ret, -EINVAL); \ + \ + r = sd_ndisc_router_option_is_type(rt, type); \ + if (r < 0) \ + return r; \ + if (r == 0) \ + return -EMEDIUMTYPE; \ + \ + *ret = rt->current_option->name.element; \ + return 0; \ + } + +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, flags, uint8_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, prefixlen, uint8_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, address, struct in6_addr); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, valid_lifetime, uint64_t); +DEFINE_GETTER(prefix, SD_NDISC_OPTION_PREFIX_INFORMATION, preferred_lifetime, uint64_t); + +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, preference, uint8_t); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, prefixlen, uint8_t); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, address, struct in6_addr); +DEFINE_GETTER(route, SD_NDISC_OPTION_ROUTE_INFORMATION, lifetime, uint64_t); + +DEFINE_GETTER(rdnss, SD_NDISC_OPTION_RDNSS, lifetime, uint64_t); + +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + *ret = rt->current_option->rdnss.addresses; + return (int) rt->current_option->rdnss.n_addresses; +} + +DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, domains, char**); +DEFINE_GETTER(dnssl, SD_NDISC_OPTION_DNSSL, lifetime, uint64_t); + +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefixlen, uint8_t); +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, prefix, struct in6_addr); +DEFINE_GETTER(prefix64, SD_NDISC_OPTION_PREF64, lifetime, uint64_t); diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index 1beed5d..ca15f94 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -9,13 +9,16 @@ #include "sd-ndisc.h" #include "alloc-util.h" +#include "ether-addr-util.h" #include "event-util.h" #include "fd-util.h" #include "icmp6-util.h" #include "in-addr-util.h" #include "memory-util.h" #include "ndisc-internal.h" -#include "ndisc-router.h" +#include "ndisc-neighbor-internal.h" +#include "ndisc-redirect-internal.h" +#include "ndisc-router-internal.h" #include "network-common.h" #include "random-util.h" #include "socket-util.h" @@ -25,13 +28,15 @@ #define NDISC_TIMEOUT_NO_RA_USEC (NDISC_ROUTER_SOLICITATION_INTERVAL * NDISC_MAX_ROUTER_SOLICITATIONS) static const char * const ndisc_event_table[_SD_NDISC_EVENT_MAX] = { - [SD_NDISC_EVENT_TIMEOUT] = "timeout", - [SD_NDISC_EVENT_ROUTER] = "router", + [SD_NDISC_EVENT_TIMEOUT] = "timeout", + [SD_NDISC_EVENT_ROUTER] = "router", + [SD_NDISC_EVENT_NEIGHBOR] = "neighbor", + [SD_NDISC_EVENT_REDIRECT] = "redirect", }; DEFINE_STRING_TABLE_LOOKUP(ndisc_event, sd_ndisc_event_t); -static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_router *rt) { +static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, void *message) { assert(ndisc); assert(event >= 0 && event < _SD_NDISC_EVENT_MAX); @@ -39,7 +44,14 @@ static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event_t event, sd_ndisc_rou return (void) log_ndisc(ndisc, "Received '%s' event.", ndisc_event_to_string(event)); log_ndisc(ndisc, "Invoking callback for '%s' event.", ndisc_event_to_string(event)); - ndisc->callback(ndisc, event, rt, ndisc->userdata); + ndisc->callback(ndisc, event, message, ndisc->userdata); +} + +int sd_ndisc_is_running(sd_ndisc *nd) { + if (!nd) + return false; + + return sd_event_source_get_enabled(nd->recv_event_source, NULL) > 0; } int sd_ndisc_set_callback( @@ -58,7 +70,7 @@ int sd_ndisc_set_callback( int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { assert_return(nd, -EINVAL); assert_return(ifindex > 0, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); + assert_return(!sd_ndisc_is_running(nd), -EBUSY); nd->ifindex = ifindex; return 0; @@ -89,6 +101,18 @@ int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret) { return 0; } +int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr) { + assert_return(nd, -EINVAL); + assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL); + + if (addr) + nd->link_local_addr = *addr; + else + zero(nd->link_local_addr); + + return 0; +} + int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { assert_return(nd, -EINVAL); @@ -104,7 +128,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { int r; assert_return(nd, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); + assert_return(!sd_ndisc_is_running(nd), -EBUSY); assert_return(!nd->event, -EBUSY); if (event) @@ -123,7 +147,7 @@ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { int sd_ndisc_detach_event(sd_ndisc *nd) { assert_return(nd, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); + assert_return(!sd_ndisc_is_running(nd), -EBUSY); nd->event = sd_event_unref(nd->event); return 0; @@ -179,78 +203,207 @@ int sd_ndisc_new(sd_ndisc **ret) { return 0; } -static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { +static int ndisc_handle_router(sd_ndisc *nd, ICMP6Packet *packet) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; int r; assert(nd); - assert(rt); + assert(packet); + + rt = ndisc_router_new(packet); + if (!rt) + return -ENOMEM; r = ndisc_router_parse(nd, rt); if (r < 0) return r; - log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %s", - rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", - rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", - FORMAT_TIMESPAN(rt->lifetime_usec, USEC_PER_SEC)); + (void) event_source_disable(nd->timeout_event_source); + (void) event_source_disable(nd->timeout_no_ra); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *s = NULL; + struct in6_addr a; + uint64_t flags; + uint8_t pref; + usec_t lifetime; + + r = sd_ndisc_router_get_sender_address(rt, &a); + if (r < 0) + return r; + + r = sd_ndisc_router_get_flags(rt, &flags); + if (r < 0) + return r; + + r = ndisc_router_flags_to_string(flags, &s); + if (r < 0) + return r; + + r = sd_ndisc_router_get_preference(rt, &pref); + if (r < 0) + return r; + + r = sd_ndisc_router_get_lifetime(rt, &lifetime); + if (r < 0) + return r; + + log_ndisc(nd, "Received Router Advertisement from %s: flags=0x%0*"PRIx64"(%s), preference=%s, lifetime=%s", + IN6_ADDR_TO_STRING(&a), + flags & UINT64_C(0x00ffffffffffff00) ? 14 : 2, flags, /* suppress too many zeros if no extension */ + s ?: "none", + ndisc_router_preference_to_string(pref), + FORMAT_TIMESPAN(lifetime, USEC_PER_SEC)); + } ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); return 0; } +static int ndisc_handle_neighbor(sd_ndisc *nd, ICMP6Packet *packet) { + _cleanup_(sd_ndisc_neighbor_unrefp) sd_ndisc_neighbor *na = NULL; + int r; + + assert(nd); + assert(packet); + + na = ndisc_neighbor_new(packet); + if (!na) + return -ENOMEM; + + r = ndisc_neighbor_parse(nd, na); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + struct in6_addr a; + + r = sd_ndisc_neighbor_get_sender_address(na, &a); + if (r < 0) + return r; + + log_ndisc(nd, "Received Neighbor Advertisement from %s: Router=%s, Solicited=%s, Override=%s", + IN6_ADDR_TO_STRING(&a), + yes_no(sd_ndisc_neighbor_is_router(na) > 0), + yes_no(sd_ndisc_neighbor_is_solicited(na) > 0), + yes_no(sd_ndisc_neighbor_is_override(na) > 0)); + } + + ndisc_callback(nd, SD_NDISC_EVENT_NEIGHBOR, na); + return 0; +} + +static int ndisc_handle_redirect(sd_ndisc *nd, ICMP6Packet *packet) { + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *rd = NULL; + int r; + + assert(nd); + assert(packet); + + rd = ndisc_redirect_new(packet); + if (!rd) + return -ENOMEM; + + r = ndisc_redirect_parse(nd, rd); + if (r < 0) + return r; + + if (DEBUG_LOGGING) { + struct in6_addr sender, target, dest; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_target_address(rd, &target); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(rd, &dest); + if (r < 0) + return r; + + log_ndisc(nd, "Received Redirect message from %s: Target=%s, Destination=%s", + IN6_ADDR_TO_STRING(&sender), + IN6_ADDR_TO_STRING(&target), + IN6_ADDR_TO_STRING(&dest)); + } + + ndisc_callback(nd, SD_NDISC_EVENT_REDIRECT, rd); + return 0; +} + static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; sd_ndisc *nd = ASSERT_PTR(userdata); - ssize_t buflen; int r; assert(s); assert(nd->event); - buflen = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) + r = icmp6_packet_receive(fd, &packet); + if (r < 0) { + log_ndisc_errno(nd, r, "Failed to receive ICMPv6 packet, ignoring: %m"); return 0; - if (buflen < 0) { - log_ndisc_errno(nd, buflen, "Failed to determine datagram size to read, ignoring: %m"); + } + + /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states + * that hosts MUST discard messages with the null source address. */ + if (in6_addr_is_null(&packet->sender_address)) { + log_ndisc(nd, "Received an ICMPv6 packet from null address, ignoring."); return 0; } - rt = ndisc_router_new(buflen); - if (!rt) - return -ENOMEM; + if (in6_addr_equal(&packet->sender_address, &nd->link_local_addr)) { + log_ndisc(nd, "Received an ICMPv6 packet sent by the same interface, ignoring."); + return 0; + } - r = icmp6_receive(fd, NDISC_ROUTER_RAW(rt), rt->raw_size, &rt->address, &rt->timestamp); - if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r)) + r = icmp6_packet_get_type(packet); + if (r < 0) { + log_ndisc_errno(nd, r, "Received an invalid ICMPv6 packet, ignoring: %m"); return 0; - if (r < 0) - switch (r) { - case -EADDRNOTAVAIL: - log_ndisc(nd, "Received RA from neither link-local nor null address. Ignoring."); - return 0; + } - case -EMULTIHOP: - log_ndisc(nd, "Received RA with invalid hop limit. Ignoring."); - return 0; + switch (r) { + case ND_ROUTER_ADVERT: + (void) ndisc_handle_router(nd, packet); + break; - case -EPFNOSUPPORT: - log_ndisc(nd, "Received invalid source address from ICMPv6 socket. Ignoring."); - return 0; + case ND_NEIGHBOR_ADVERT: + (void) ndisc_handle_neighbor(nd, packet); + break; - default: - log_ndisc_errno(nd, r, "Unexpected error while reading from ICMPv6, ignoring: %m"); - return 0; - } + case ND_REDIRECT: + (void) ndisc_handle_redirect(nd, packet); + break; - /* The function icmp6_receive() accepts the null source address, but RFC 4861 Section 6.1.2 states - * that hosts MUST discard messages with the null source address. */ - if (in6_addr_is_null(&rt->address)) - log_ndisc(nd, "Received RA from null address. Ignoring."); + default: + log_ndisc(nd, "Received an ICMPv6 packet with unexpected type %i, ignoring.", r); + } - (void) event_source_disable(nd->timeout_event_source); - (void) ndisc_handle_datagram(nd, rt); return 0; } +static int ndisc_send_router_solicitation(sd_ndisc *nd) { + static const struct nd_router_solicit header = { + .nd_rs_type = ND_ROUTER_SOLICIT, + }; + + _cleanup_set_free_ Set *options = NULL; + int r; + + assert(nd); + + if (!ether_addr_is_null(&nd->mac_addr)) { + r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &nd->mac_addr); + if (r < 0) + return r; + } + + return ndisc_send(nd->fd, &IN6_ADDR_ALL_ROUTERS_MULTICAST, &header.nd_rs_hdr, options, USEC_INFINITY); +} + static usec_t ndisc_timeout_compute_random(usec_t val) { /* compute a time that is random within ±10% of the given value */ return val - val / 10 + @@ -284,7 +437,7 @@ static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { if (r < 0) goto fail; - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); + r = ndisc_send_router_solicitation(nd); if (r < 0) log_ndisc_errno(nd, r, "Failed to send Router Solicitation, next solicitation in %s, ignoring: %m", FORMAT_TIMESPAN(nd->retransmit_time, USEC_PER_SEC)); @@ -316,7 +469,7 @@ int sd_ndisc_stop(sd_ndisc *nd) { if (!nd) return 0; - if (nd->fd < 0) + if (!sd_ndisc_is_running(nd)) return 0; log_ndisc(nd, "Stopping IPv6 Router Solicitation client"); @@ -325,50 +478,74 @@ int sd_ndisc_stop(sd_ndisc *nd) { return 1; } -int sd_ndisc_start(sd_ndisc *nd) { +static int ndisc_setup_recv_event(sd_ndisc *nd) { int r; - usec_t time_now; - assert_return(nd, -EINVAL); - assert_return(nd->event, -EINVAL); - assert_return(nd->ifindex > 0, -EINVAL); + assert(nd); + assert(nd->event); + assert(nd->ifindex > 0); - if (nd->fd >= 0) - return 0; + _cleanup_close_ int fd = -EBADF; + fd = icmp6_bind(nd->ifindex, /* is_router = */ false); + if (fd < 0) + return fd; - assert(!nd->recv_event_source); + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(nd->event, &s, fd, EPOLLIN, ndisc_recv, nd); + if (r < 0) + return r; - r = sd_event_now(nd->event, CLOCK_BOOTTIME, &time_now); + r = sd_event_source_set_priority(s, nd->event_priority); if (r < 0) - goto fail; + return r; + + (void) sd_event_source_set_description(s, "ndisc-receive-router-message"); - nd->fd = icmp6_bind_router_solicitation(nd->ifindex); - if (nd->fd < 0) - return nd->fd; + nd->fd = TAKE_FD(fd); + nd->recv_event_source = TAKE_PTR(s); + return 1; +} - r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); +static int ndisc_setup_timer(sd_ndisc *nd) { + int r; + + assert(nd); + assert(nd->event); + + r = event_reset_time_relative(nd->event, &nd->timeout_event_source, + CLOCK_BOOTTIME, + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */ + ndisc_timeout, nd, + nd->event_priority, "ndisc-timeout", true); if (r < 0) - goto fail; + return r; - r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); + r = event_reset_time_relative(nd->event, &nd->timeout_no_ra, + CLOCK_BOOTTIME, + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC, + ndisc_timeout_no_ra, nd, + nd->event_priority, "ndisc-timeout-no-ra", true); if (r < 0) - goto fail; + return r; - (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); + return 0; +} - r = event_reset_time(nd->event, &nd->timeout_event_source, - CLOCK_BOOTTIME, - time_now + USEC_PER_SEC / 2, 1 * USEC_PER_SEC, /* See RFC 8415 sec. 18.2.1 */ - ndisc_timeout, nd, - nd->event_priority, "ndisc-timeout", true); +int sd_ndisc_start(sd_ndisc *nd) { + int r; + + assert_return(nd, -EINVAL); + assert_return(nd->event, -EINVAL); + assert_return(nd->ifindex > 0, -EINVAL); + + if (sd_ndisc_is_running(nd)) + return 0; + + r = ndisc_setup_recv_event(nd); if (r < 0) goto fail; - r = event_reset_time(nd->event, &nd->timeout_no_ra, - CLOCK_BOOTTIME, - time_now + NDISC_TIMEOUT_NO_RA_USEC, 10 * USEC_PER_MSEC, - ndisc_timeout_no_ra, nd, - nd->event_priority, "ndisc-timeout-no-ra", true); + r = ndisc_setup_timer(nd); if (r < 0) goto fail; diff --git a/src/libsystemd-network/sd-radv.c b/src/libsystemd-network/sd-radv.c index 97d306c..c384d4e 100644 --- a/src/libsystemd-network/sd-radv.c +++ b/src/libsystemd-network/sd-radv.c @@ -19,6 +19,7 @@ #include "iovec-util.h" #include "macro.h" #include "memory-util.h" +#include "ndisc-router-solicit-internal.h" #include "network-common.h" #include "radv-internal.h" #include "random-util.h" @@ -81,7 +82,8 @@ sd_event *sd_radv_get_event(sd_radv *ra) { } int sd_radv_is_running(sd_radv *ra) { - assert_return(ra, false); + if (!ra) + return false; return ra->state != RADV_STATE_IDLE; } @@ -121,30 +123,63 @@ static sd_radv *radv_free(sd_radv *ra) { DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_radv, sd_radv, radv_free); static bool router_lifetime_is_valid(usec_t lifetime_usec) { + assert_cc(RADV_MAX_ROUTER_LIFETIME_USEC <= UINT16_MAX * USEC_PER_SEC); return lifetime_usec == 0 || (lifetime_usec >= RADV_MIN_ROUTER_LIFETIME_USEC && lifetime_usec <= RADV_MAX_ROUTER_LIFETIME_USEC); } -static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_usec) { +static int radv_send_router_on_stop(sd_radv *ra) { + static const struct nd_router_advert adv = { + .nd_ra_type = ND_ROUTER_ADVERT, + }; + + _cleanup_set_free_ Set *options = NULL; + usec_t time_now; + int r; + + assert(ra); + + r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now); + if (r < 0) + return r; + + if (!ether_addr_is_null(&ra->mac_addr)) { + r = ndisc_option_set_link_layer_address(&options, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &ra->mac_addr); + if (r < 0) + return r; + } + + return ndisc_send(ra->fd, &IN6_ADDR_ALL_NODES_MULTICAST, &adv.nd_ra_hdr, options, time_now); +} + +static int radv_send_router(sd_radv *ra, const struct in6_addr *dst) { + assert(ra); + struct sockaddr_in6 dst_addr = { .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + .sin6_addr = IN6_ADDR_ALL_NODES_MULTICAST, + }; + struct nd_router_advert adv = { + .nd_ra_type = ND_ROUTER_ADVERT, + .nd_ra_router_lifetime = usec_to_be16_sec(ra->lifetime_usec), + .nd_ra_reachable = usec_to_be32_msec(ra->reachable_usec), + .nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec), }; - struct nd_router_advert adv = {}; struct { struct nd_opt_hdr opthdr; struct ether_addr slladdr; } _packed_ opt_mac = { .opthdr = { .nd_opt_type = ND_OPT_SOURCE_LINKADDR, - .nd_opt_len = (sizeof(struct nd_opt_hdr) + - sizeof(struct ether_addr) - 1) /8 + 1, + .nd_opt_len = DIV_ROUND_UP(sizeof(struct nd_opt_hdr) + sizeof(struct ether_addr), 8), }, + .slladdr = ra->mac_addr, }; struct nd_opt_mtu opt_mtu = { .nd_opt_mtu_type = ND_OPT_MTU, .nd_opt_mtu_len = 1, + .nd_opt_mtu_mtu = htobe32(ra->mtu), }; /* Reserve iov space for RA header, linkaddr, MTU, N prefixes, N routes, N pref64 prefixes, RDNSS, * DNSSL, and home agent. */ @@ -157,9 +192,6 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us usec_t time_now; int r; - assert(ra); - assert(router_lifetime_is_valid(lifetime_usec)); - r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now); if (r < 0) return r; @@ -167,25 +199,21 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us if (dst && in6_addr_is_set(dst)) dst_addr.sin6_addr = *dst; - adv.nd_ra_type = ND_ROUTER_ADVERT; + /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime + * simultaneously in the structured initializer in the above. */ adv.nd_ra_curhoplimit = ra->hop_limit; - adv.nd_ra_retransmit = usec_to_be32_msec(ra->retransmit_usec); - adv.nd_ra_flags_reserved = ra->flags; - assert_cc(RADV_MAX_ROUTER_LIFETIME_USEC <= UINT16_MAX * USEC_PER_SEC); - adv.nd_ra_router_lifetime = usec_to_be16_sec(lifetime_usec); + /* RFC 4191, Section 2.2, + * "...If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender..." */ + adv.nd_ra_flags_reserved = ra->flags | (ra->lifetime_usec > 0 ? (ra->preference << 3) : 0); iov[msg.msg_iovlen++] = IOVEC_MAKE(&adv, sizeof(adv)); - /* MAC address is optional, either because the link does not use L2 - addresses or load sharing is desired. See RFC 4861, Section 4.2 */ - if (!ether_addr_is_null(&ra->mac_addr)) { - opt_mac.slladdr = ra->mac_addr; + /* MAC address is optional, either because the link does not use L2 addresses or load sharing is + * desired. See RFC 4861, Section 4.2. */ + if (!ether_addr_is_null(&ra->mac_addr)) iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mac, sizeof(opt_mac)); - } - if (ra->mtu > 0) { - opt_mtu.nd_opt_mtu_mtu = htobe32(ra->mtu); + if (ra->mtu > 0) iov[msg.msg_iovlen++] = IOVEC_MAKE(&opt_mtu, sizeof(opt_mtu)); - } LIST_FOREACH(prefix, p, ra->prefixes) { usec_t lifetime_valid_usec, lifetime_preferred_usec; @@ -236,87 +264,90 @@ static int radv_send(sd_radv *ra, const struct in6_addr *dst, usec_t lifetime_us return 0; } -static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_radv *ra = ASSERT_PTR(userdata); - struct in6_addr src; - triple_timestamp timestamp; +static int radv_process_packet(sd_radv *ra, ICMP6Packet *packet) { int r; - assert(s); - assert(ra->event); + assert(ra); + assert(packet); - ssize_t buflen = next_datagram_size_fd(fd); - if (ERRNO_IS_NEG_TRANSIENT(buflen) || ERRNO_IS_NEG_DISCONNECT(buflen)) - return 0; - if (buflen < 0) { - log_radv_errno(ra, buflen, "Failed to determine datagram size to read, ignoring: %m"); - return 0; - } + if (icmp6_packet_get_type(packet) != ND_ROUTER_SOLICIT) + return log_radv_errno(ra, SYNTHETIC_ERRNO(EBADMSG), "Received ICMP6 packet with unexpected type, ignoring."); - _cleanup_free_ char *buf = new0(char, buflen); - if (!buf) - return -ENOMEM; + _cleanup_(sd_ndisc_router_solicit_unrefp) sd_ndisc_router_solicit *rs = NULL; + rs = ndisc_router_solicit_new(packet); + if (!rs) + return log_oom_debug(); - r = icmp6_receive(fd, buf, buflen, &src, ×tamp); - if (ERRNO_IS_NEG_TRANSIENT(r) || ERRNO_IS_NEG_DISCONNECT(r)) - return 0; + r = ndisc_router_solicit_parse(ra, rs); if (r < 0) - switch (r) { - case -EADDRNOTAVAIL: - log_radv(ra, "Received RS from neither link-local nor null address. Ignoring"); - return 0; + return r; - case -EMULTIHOP: - log_radv(ra, "Received RS with invalid hop limit. Ignoring."); - return 0; + struct in6_addr src; + r = sd_ndisc_router_solicit_get_sender_address(rs, &src); + if (r == -ENODATA) /* null address is allowed */ + return sd_radv_send(ra); /* When an unsolicited RA, we need to also update timer. */ + if (r < 0) + return log_radv_errno(ra, r, "Failed to get sender address of RS, ignoring: %m"); + if (in6_addr_equal(&src, &ra->ipv6ll)) + /* This should be definitely caused by a misconfiguration. If we send RA to ourself, the + * kernel complains about that. Let's ignore the packet. */ + return log_radv_errno(ra, SYNTHETIC_ERRNO(EADDRINUSE), "Received RS from the same interface, ignoring."); - case -EPFNOSUPPORT: - log_radv(ra, "Received invalid source address from ICMPv6 socket. Ignoring."); - return 0; + r = radv_send_router(ra, &src); + if (r < 0) + return log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", IN6_ADDR_TO_STRING(&src)); - default: - log_radv_errno(ra, r, "Unexpected error receiving from ICMPv6 socket, ignoring: %m"); - return 0; - } + log_radv(ra, "Sent solicited Router Advertisement to %s.", IN6_ADDR_TO_STRING(&src)); + return 0; +} - if ((size_t) buflen < sizeof(struct nd_router_solicit)) { - log_radv(ra, "Too short packet received, ignoring"); +static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + sd_radv *ra = ASSERT_PTR(userdata); + int r; + + assert(fd >= 0); + + r = icmp6_packet_receive(fd, &packet); + if (r < 0) { + log_radv_errno(ra, r, "Failed to receive ICMPv6 packet, ignoring: %m"); return 0; } - /* TODO: if the sender address is null, check that the message does not have the source link-layer - * address option. See RFC 4861 Section 6.1.1. */ + (void) radv_process_packet(ra, packet); + return 0; +} - const char *addr = IN6_ADDR_TO_STRING(&src); +static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_radv *ra = ASSERT_PTR(userdata); - r = radv_send(ra, &src, ra->lifetime_usec); - if (r < 0) - log_radv_errno(ra, r, "Unable to send solicited Router Advertisement to %s, ignoring: %m", addr); - else - log_radv(ra, "Sent solicited Router Advertisement to %s", addr); + if (sd_radv_send(ra) < 0) + (void) sd_radv_stop(ra); return 0; } -static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) { +int sd_radv_send(sd_radv *ra) { usec_t min_timeout, max_timeout, time_now, timeout; - sd_radv *ra = ASSERT_PTR(userdata); int r; - assert(s); - assert(ra->event); + assert_return(ra, -EINVAL); + assert_return(ra->event, -EINVAL); + assert_return(sd_radv_is_running(ra), -EINVAL); assert(router_lifetime_is_valid(ra->lifetime_usec)); r = sd_event_now(ra->event, CLOCK_BOOTTIME, &time_now); if (r < 0) - goto fail; + return r; - r = radv_send(ra, NULL, ra->lifetime_usec); + r = radv_send_router(ra, NULL); if (r < 0) - log_radv_errno(ra, r, "Unable to send Router Advertisement, ignoring: %m"); + return log_radv_errno(ra, r, "Unable to send Router Advertisement: %m"); + + ra->ra_sent++; /* RFC 4861, Section 6.2.4, sending initial Router Advertisements */ - if (ra->ra_sent < RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) + if (ra->ra_sent <= RADV_MAX_INITIAL_RTR_ADVERTISEMENTS) max_timeout = RADV_MAX_INITIAL_RTR_ADVERT_INTERVAL_USEC; else max_timeout = RADV_DEFAULT_MAX_TIMEOUT_USEC; @@ -340,47 +371,64 @@ static int radv_timeout(sd_event_source *s, uint64_t usec, void *userdata) { assert(min_timeout <= max_timeout * 3 / 4); timeout = min_timeout + random_u64_range(max_timeout - min_timeout); - log_radv(ra, "Next Router Advertisement in %s", FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + log_radv(ra, "Sent unsolicited Router Advertisement. Next advertisement will be in %s.", + FORMAT_TIMESPAN(timeout, USEC_PER_SEC)); + + return event_reset_time( + ra->event, &ra->timeout_event_source, + CLOCK_BOOTTIME, + usec_add(time_now, timeout), MSEC_PER_SEC, + radv_timeout, ra, + ra->event_priority, "radv-timeout", true); +} - r = event_reset_time(ra->event, &ra->timeout_event_source, - CLOCK_BOOTTIME, - usec_add(time_now, timeout), MSEC_PER_SEC, - radv_timeout, ra, - ra->event_priority, "radv-timeout", true); - if (r < 0) - goto fail; +int sd_radv_stop(sd_radv *ra) { + int r; - ra->ra_sent++; + if (!sd_radv_is_running(ra)) + return 0; /* Already stopped. */ - return 0; + log_radv(ra, "Stopping IPv6 Router Advertisement daemon"); -fail: - sd_radv_stop(ra); + /* RFC 4861, Section 6.2.5: + * the router SHOULD transmit one or more (but not more than MAX_FINAL_RTR_ADVERTISEMENTS) final + * multicast Router Advertisements on the interface with a Router Lifetime field of zero. */ + r = radv_send_router_on_stop(ra); + if (r < 0) + log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m"); + + radv_reset(ra); + ra->fd = safe_close(ra->fd); + ra->state = RADV_STATE_IDLE; return 0; } -int sd_radv_stop(sd_radv *ra) { +static int radv_setup_recv_event(sd_radv *ra) { int r; - if (!ra) - return 0; + assert(ra); + assert(ra->event); + assert(ra->ifindex > 0); - if (ra->state == RADV_STATE_IDLE) - return 0; + _cleanup_close_ int fd = -EBADF; + fd = icmp6_bind(ra->ifindex, /* is_router = */ true); + if (fd < 0) + return fd; - log_radv(ra, "Stopping IPv6 Router Advertisement daemon"); + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + r = sd_event_add_io(ra->event, &s, fd, EPOLLIN, radv_recv, ra); + if (r < 0) + return r; - /* RFC 4861, Section 6.2.5, send at least one Router Advertisement - with zero lifetime */ - r = radv_send(ra, NULL, 0); + r = sd_event_source_set_priority(s, ra->event_priority); if (r < 0) - log_radv_errno(ra, r, "Unable to send last Router Advertisement with router lifetime set to zero, ignoring: %m"); + return r; - radv_reset(ra); - ra->fd = safe_close(ra->fd); - ra->state = RADV_STATE_IDLE; + (void) sd_event_source_set_description(s, "radv-receive-message"); + ra->fd = TAKE_FD(fd); + ra->recv_event_source = TAKE_PTR(s); return 0; } @@ -391,8 +439,12 @@ int sd_radv_start(sd_radv *ra) { assert_return(ra->event, -EINVAL); assert_return(ra->ifindex > 0, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return 0; + if (sd_radv_is_running(ra)) + return 0; /* Already started. */ + + r = radv_setup_recv_event(ra); + if (r < 0) + goto fail; r = event_reset_time(ra->event, &ra->timeout_event_source, CLOCK_BOOTTIME, @@ -402,22 +454,6 @@ int sd_radv_start(sd_radv *ra) { if (r < 0) goto fail; - r = icmp6_bind_router_advertisement(ra->ifindex); - if (r < 0) - goto fail; - - ra->fd = r; - - r = sd_event_add_io(ra->event, &ra->recv_event_source, ra->fd, EPOLLIN, radv_recv, ra); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(ra->recv_event_source, ra->event_priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(ra->recv_event_source, "radv-receive-message"); - ra->state = RADV_STATE_ADVERTISING; log_radv(ra, "Started IPv6 Router Advertisement daemon"); @@ -432,13 +468,10 @@ int sd_radv_start(sd_radv *ra) { int sd_radv_set_ifindex(sd_radv *ra, int ifindex) { assert_return(ra, -EINVAL); + assert_return(!sd_radv_is_running(ra), -EBUSY); assert_return(ifindex > 0, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - ra->ifindex = ifindex; - return 0; } @@ -467,11 +500,20 @@ int sd_radv_get_ifname(sd_radv *ra, const char **ret) { return 0; } -int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) { +int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr) { assert_return(ra, -EINVAL); + assert_return(!addr || in6_addr_is_link_local(addr), -EINVAL); + + if (addr) + ra->ipv6ll = *addr; + else + zero(ra->ipv6ll); + + return 0; +} - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; +int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr) { + assert_return(ra, -EINVAL); if (mac_addr) ra->mac_addr = *mac_addr; @@ -493,22 +535,19 @@ int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu) { int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - ra->hop_limit = hop_limit; - return 0; } -int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) { +int sd_radv_set_reachable_time(sd_radv *ra, uint64_t usec) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; + ra->reachable_usec = usec; + return 0; +} - if (usec > RADV_MAX_RETRANSMIT_USEC) - return -EINVAL; +int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) { + assert_return(ra, -EINVAL); ra->retransmit_usec = usec; return 0; @@ -517,89 +556,55 @@ int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec) { int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - if (!router_lifetime_is_valid(usec)) return -EINVAL; - /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set - * to (00) by the sender..." */ - if (usec == 0 && - (ra->flags & (0x3 << 3)) != (SD_NDISC_PREFERENCE_MEDIUM << 3)) - return -EINVAL; - ra->lifetime_usec = usec; return 0; } -int sd_radv_set_managed_information(sd_radv *ra, int managed) { +int sd_radv_set_managed_information(sd_radv *ra, int b) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - - SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, managed); - + SET_FLAG(ra->flags, ND_RA_FLAG_MANAGED, b); return 0; } -int sd_radv_set_other_information(sd_radv *ra, int other) { +int sd_radv_set_other_information(sd_radv *ra, int b) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - - SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, other); - + SET_FLAG(ra->flags, ND_RA_FLAG_OTHER, b); return 0; } -int sd_radv_set_preference(sd_radv *ra, unsigned preference) { +int sd_radv_set_preference(sd_radv *ra, uint8_t preference) { assert_return(ra, -EINVAL); assert_return(IN_SET(preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH), -EINVAL); - /* RFC 4191, Section 2.2, "...If the Router Lifetime is zero, the preference value MUST be set - * to (00) by the sender..." */ - if (ra->lifetime_usec == 0 && preference != SD_NDISC_PREFERENCE_MEDIUM) - return -EINVAL; - - ra->flags = (ra->flags & ~(0x3 << 3)) | (preference << 3); - + ra->preference = preference; return 0; } int sd_radv_set_home_agent_information(sd_radv *ra, int home_agent) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - SET_FLAG(ra->flags, ND_RA_FLAG_HOME_AGENT, home_agent); - return 0; } int sd_radv_set_home_agent_preference(sd_radv *ra, uint16_t preference) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - ra->home_agent.nd_opt_home_agent_info_preference = htobe16(preference); - return 0; } int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) { assert_return(ra, -EINVAL); - if (ra->state != RADV_STATE_IDLE) - return -EBUSY; - if (lifetime_usec > RADV_HOME_AGENT_MAX_LIFETIME_USEC) return -EINVAL; @@ -609,7 +614,6 @@ int sd_radv_set_home_agent_lifetime(sd_radv *ra, uint64_t lifetime_usec) { int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { sd_radv_prefix *found = NULL; - int r; assert_return(ra, -EINVAL); assert_return(p, -EINVAL); @@ -621,19 +625,13 @@ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen); LIST_FOREACH(prefix, cur, ra->prefixes) { - r = in_addr_prefix_intersect(AF_INET6, - (const union in_addr_union*) &cur->opt.in6_addr, - cur->opt.prefixlen, - (const union in_addr_union*) &p->opt.in6_addr, - p->opt.prefixlen); - if (r < 0) - return r; - if (r == 0) - continue; + if (!in6_addr_prefix_intersect(&cur->opt.in6_addr, cur->opt.prefixlen, + &p->opt.in6_addr, p->opt.prefixlen)) + continue; /* no intersection */ if (cur->opt.prefixlen == p->opt.prefixlen) { found = cur; - break; + break; /* same prefix */ } return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST), @@ -667,19 +665,6 @@ int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p) { log_radv(ra, "Added prefix %s", addr_p); } - if (ra->state == RADV_STATE_IDLE) - return 0; - - if (ra->ra_sent == 0) - return 0; - - /* If RAs have already been sent, send an RA immediately to announce the newly-added prefix */ - r = radv_send(ra, NULL, ra->lifetime_usec); - if (r < 0) - log_radv_errno(ra, r, "Unable to send Router Advertisement for added prefix %s, ignoring: %m", addr_p); - else - log_radv(ra, "Sent Router Advertisement for added/updated prefix %s.", addr_p); - return 0; } @@ -710,35 +695,17 @@ void sd_radv_remove_prefix( int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) { sd_radv_route_prefix *found = NULL; - int r; assert_return(ra, -EINVAL); assert_return(p, -EINVAL); - const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen); - - LIST_FOREACH(prefix, cur, ra->route_prefixes) { - r = in_addr_prefix_intersect(AF_INET6, - (const union in_addr_union*) &cur->opt.in6_addr, - cur->opt.prefixlen, - (const union in_addr_union*) &p->opt.in6_addr, - p->opt.prefixlen); - if (r < 0) - return r; - if (r == 0) - continue; - - if (cur->opt.prefixlen == p->opt.prefixlen) { + LIST_FOREACH(prefix, cur, ra->route_prefixes) + if (cur->opt.prefixlen == p->opt.prefixlen && + in6_addr_equal(&cur->opt.in6_addr, &p->opt.in6_addr)) { found = cur; break; } - return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST), - "IPv6 route prefix %s conflicts with %s, ignoring.", - addr_p, - IN6_ADDR_PREFIX_TO_STRING(&cur->opt.in6_addr, cur->opt.prefixlen)); - } - if (found) { /* p and cur may be equivalent. First increment the reference counter. */ sd_radv_route_prefix_ref(p); @@ -751,7 +718,7 @@ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) { LIST_APPEND(prefix, ra->route_prefixes, p); log_radv(ra, "Updated/replaced IPv6 route prefix %s (lifetime: %s)", - strna(addr_p), + IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen), FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC)); } else { /* The route prefix is new. Let's simply add it. */ @@ -760,57 +727,26 @@ int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p) { LIST_APPEND(prefix, ra->route_prefixes, p); ra->n_route_prefixes++; - log_radv(ra, "Added route prefix %s", strna(addr_p)); + log_radv(ra, "Added route prefix %s", + IN6_ADDR_PREFIX_TO_STRING(&p->opt.in6_addr, p->opt.prefixlen)); } - if (ra->state == RADV_STATE_IDLE) - return 0; - - if (ra->ra_sent == 0) - return 0; - - /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */ - r = radv_send(ra, NULL, ra->lifetime_usec); - if (r < 0) - log_radv_errno(ra, r, "Unable to send Router Advertisement for added route prefix %s, ignoring: %m", - strna(addr_p)); - else - log_radv(ra, "Sent Router Advertisement for added route prefix %s.", strna(addr_p)); - return 0; } int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) { sd_radv_pref64_prefix *found = NULL; - int r; assert_return(ra, -EINVAL); assert_return(p, -EINVAL); - const char *addr_p = IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen); - - LIST_FOREACH(prefix, cur, ra->pref64_prefixes) { - r = in_addr_prefix_intersect(AF_INET6, - (const union in_addr_union*) &cur->in6_addr, - cur->prefixlen, - (const union in_addr_union*) &p->in6_addr, - p->prefixlen); - if (r < 0) - return r; - if (r == 0) - continue; - - if (cur->prefixlen == p->prefixlen) { + LIST_FOREACH(prefix, cur, ra->pref64_prefixes) + if (cur->prefixlen == p->prefixlen && + in6_addr_equal(&cur->in6_addr, &p->in6_addr)) { found = cur; break; } - return log_radv_errno(ra, SYNTHETIC_ERRNO(EEXIST), - "IPv6 PREF64 prefix %s conflicts with %s, ignoring.", - addr_p, - IN6_ADDR_PREFIX_TO_STRING(&cur->in6_addr, cur->prefixlen)); - } - if (found) { /* p and cur may be equivalent. First increment the reference counter. */ sd_radv_pref64_prefix_ref(p); @@ -823,7 +759,7 @@ int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) { LIST_APPEND(prefix, ra->pref64_prefixes, p); log_radv(ra, "Updated/replaced IPv6 PREF64 prefix %s (lifetime: %s)", - strna(addr_p), + IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen), FORMAT_TIMESPAN(p->lifetime_usec, USEC_PER_SEC)); } else { /* The route prefix is new. Let's simply add it. */ @@ -832,23 +768,10 @@ int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p) { LIST_APPEND(prefix, ra->pref64_prefixes, p); ra->n_pref64_prefixes++; - log_radv(ra, "Added PREF64 prefix %s", strna(addr_p)); + log_radv(ra, "Added PREF64 prefix %s", + IN6_ADDR_PREFIX_TO_STRING(&p->in6_addr, p->prefixlen)); } - if (ra->state == RADV_STATE_IDLE) - return 0; - - if (ra->ra_sent == 0) - return 0; - - /* If RAs have already been sent, send an RA immediately to announce the newly-added route prefix */ - r = radv_send(ra, NULL, ra->lifetime_usec); - if (r < 0) - log_radv_errno(ra, r, "Unable to send Router Advertisement for added PREF64 prefix %s, ignoring: %m", - strna(addr_p)); - else - log_radv(ra, "Sent Router Advertisement for added PREF64 prefix %s.", strna(addr_p)); - return 0; } diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c index 4b5ad70..6ed7260 100644 --- a/src/libsystemd-network/test-acd.c +++ b/src/libsystemd-network/test-acd.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include +#include #include #include -#include -#include #include "sd-event.h" #include "sd-ipv4acd.h" diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index e3f148d..5b4ce3e 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -17,7 +17,7 @@ #include "sd-event.h" #include "alloc-util.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "dhcp-network.h" #include "dhcp-option.h" #include "dhcp-packet.h" @@ -57,14 +57,14 @@ static void test_request_basic(sd_event *e) { r = sd_dhcp_client_attach_event(client, e, 0); assert_se(r >= 0); - assert_se(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL); assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); - assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); assert_se(sd_dhcp_client_set_hostname(client, "host") == 1); @@ -165,19 +165,18 @@ static int check_options(uint8_t code, uint8_t len, const void *option, void *us switch (code) { case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { + sd_dhcp_duid duid; uint32_t iaid; - struct duid duid; - size_t duid_len; - assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0); + assert_se(sd_dhcp_duid_set_en(&duid) >= 0); assert_se(dhcp_identifier_set_iaid(NULL, &hw_addr, /* legacy = */ true, &iaid) >= 0); - assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len); + assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid.size); assert_se(len == 19); assert_se(((uint8_t*) option)[0] == 0xff); assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); - assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0); + assert_se(memcmp((uint8_t*) option + 5, &duid.duid, duid.size) == 0); break; } @@ -515,7 +514,7 @@ static void test_addr_acq(sd_event *e) { callback_recv = test_addr_acq_recv_discover; assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); res = sd_dhcp_client_start(client); diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index b2e6034..ecd030e 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -17,7 +17,7 @@ static void test_pool(struct in_addr *address, unsigned size, int ret) { assert_se(sd_dhcp_server_new(&server, 1) >= 0); - assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret); + ASSERT_RETURN_IS_CRITICAL(ret >= 0, assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret)); } static int test_basic(bool bind_to_interface) { @@ -41,20 +41,20 @@ static int test_basic(bool bind_to_interface) { server->bind_to_interface = bind_to_interface; assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0); - assert_se(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY); assert_se(sd_dhcp_server_get_event(server) == event); assert_se(sd_dhcp_server_detach_event(server) >= 0); assert_se(!sd_dhcp_server_get_event(server)); assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); - assert_se(sd_dhcp_server_attach_event(server, NULL, 0) == -EBUSY); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_server_attach_event(server, NULL, 0) == -EBUSY); assert_se(sd_dhcp_server_ref(server) == server); assert_se(!sd_dhcp_server_unref(server)); - assert_se(sd_dhcp_server_start(server) == -EUNATCH); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_server_start(server) == -EUNATCH); - assert_se(sd_dhcp_server_configure_pool(server, &address_any, 28, 0, 0) == -EINVAL); - assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0) == -ERANGE); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_server_configure_pool(server, &address_any, 28, 0, 0) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0) == -ERANGE); assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0); assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0); @@ -62,7 +62,9 @@ static int test_basic(bool bind_to_interface) { test_pool(&address_lo, 1, 0); r = sd_dhcp_server_start(server); - if (r == -EPERM) + /* skip test if running in an environment with no full networking support, CONFIG_PACKET not + * compiled in kernel, nor af_packet module available. */ + if (r == -EPERM || r == -EAFNOSUPPORT) return r; assert_se(r >= 0); @@ -184,7 +186,7 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0); test.option_server_id.address = htobe32(INADDR_LOOPBACK); test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4); - assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK); test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK); @@ -200,7 +202,7 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK); test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30); - assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == 0); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK); /* request address reserved for static lease (unmatching client ID) */ test.option_client_id.id[6] = 'H'; @@ -223,7 +225,7 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test), NULL) == DHCP_ACK); } -static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZE]) { +static uint64_t client_id_hash_helper(sd_dhcp_client_id *id, uint8_t key[HASH_KEY_SIZE]) { struct siphash state; siphash24_init(&state, key); @@ -233,10 +235,10 @@ static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZ } static void test_client_id_hash(void) { - DHCPClientId a = { - .length = 4, + sd_dhcp_client_id a = { + .size = 4, }, b = { - .length = 4, + .size = 4, }; uint8_t hash_key[HASH_KEY_SIZE] = { '0', '1', '2', '3', '4', '5', '6', '7', @@ -245,29 +247,25 @@ static void test_client_id_hash(void) { log_debug("/* %s */", __func__); - a.data = (uint8_t*)strdup("abcd"); - b.data = (uint8_t*)strdup("abcd"); + memcpy(a.raw, "abcd", 4); + memcpy(b.raw, "abcd", 4); assert_se(client_id_compare_func(&a, &b) == 0); assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key)); - a.length = 3; + a.size = 3; assert_se(client_id_compare_func(&a, &b) != 0); - a.length = 4; + a.size = 4; assert_se(client_id_compare_func(&a, &b) == 0); assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key)); - b.length = 3; + b.size = 3; assert_se(client_id_compare_func(&a, &b) != 0); - b.length = 4; + b.size = 4; assert_se(client_id_compare_func(&a, &b) == 0); assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key)); - free(b.data); - b.data = (uint8_t*)strdup("abce"); + memcpy(b.raw, "abce", 4); assert_se(client_id_compare_func(&a, &b) != 0); - - free(a.data); - free(b.data); } static void test_static_lease(void) { diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index ae3cdb8..7afd464 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -13,7 +13,7 @@ #include "sd-dhcp6-client.h" #include "sd-event.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dhcp6-protocol.h" @@ -25,6 +25,7 @@ #include "strv.h" #include "tests.h" #include "time-util.h" +#include "unaligned.h" #define DHCP6_CLIENT_EVENT_TEST_ADVERTISED 77 #define IA_ID_BYTES \ @@ -58,8 +59,7 @@ static const struct in6_addr local_address = { { { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, } } }; -static const struct in6_addr mcast_address = - IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; +static const struct in6_addr mcast_address = IN6_ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS; static const struct in6_addr ia_na_address1 = { { { IA_NA_ADDRESS1_BYTES } } }; static const struct in6_addr ia_na_address2 = { { { IA_NA_ADDRESS2_BYTES } } }; static const struct in6_addr ia_pd_prefix1 = { { { IA_PD_PREFIX1_BYTES } } }; @@ -1027,7 +1027,7 @@ static void test_client_callback(sd_dhcp6_client *client, int event, void *userd } } -int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet, size_t len) { +int dhcp6_network_send_udp_socket(int s, const struct in6_addr *a, const void *packet, size_t len) { log_debug("/* %s(count=%u) */", __func__, test_client_sent_message_count); assert_se(a); @@ -1071,7 +1071,7 @@ int dhcp6_network_send_udp_socket(int s, struct in6_addr *a, const void *packet, return len; } -int dhcp6_network_bind_udp_socket(int ifindex, struct in6_addr *a) { +int dhcp6_network_bind_udp_socket(int ifindex, const struct in6_addr *a) { assert_se(ifindex == test_ifindex); assert_se(a); assert_se(in6_addr_equal(a, &local_address)); @@ -1086,7 +1086,7 @@ TEST(dhcp6_client) { assert_se(sd_event_new(&e) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); assert_se(sd_dhcp6_client_new(&client) >= 0); diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c index 5dc6b10..b4e6d24 100644 --- a/src/libsystemd-network/test-ipv4ll-manual.c +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +/* Make sure the net/if.h header is included before any linux/ one */ #include +#include +#include #include #include -#include #include "sd-event.h" #include "sd-ipv4ll.h" diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index bb42930..e7dbd7f 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -85,33 +85,34 @@ static void test_public_api_setters(sd_event *e) { assert_se(sd_ipv4ll_new(&ll) == 0); assert_se(ll); - assert_se(sd_ipv4ll_attach_event(NULL, NULL, 0) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_attach_event(NULL, NULL, 0) == -EINVAL); assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0); - assert_se(sd_ipv4ll_attach_event(ll, e, 0) == -EBUSY); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_attach_event(ll, e, 0) == -EBUSY); - assert_se(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_callback(ll, NULL, NULL) == 0); - assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL); address.s_addr |= htobe32(169U << 24 | 254U << 16); - assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL); address.s_addr |= htobe32(0x00FF); - assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL); address.s_addr |= htobe32(0xF000); assert_se(sd_ipv4ll_set_address(ll, &address) == 0); address.s_addr |= htobe32(0x0F00); - assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address(ll, &address) == -EINVAL); - assert_se(sd_ipv4ll_set_address_seed(NULL, seed) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_address_seed(NULL, seed) == -EINVAL); assert_se(sd_ipv4ll_set_address_seed(ll, seed) == 0); - assert_se(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL); - assert_se(sd_ipv4ll_set_mac(ll, NULL) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL); + + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_mac(ll, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0); - assert_se(sd_ipv4ll_set_ifindex(NULL, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(NULL, -1) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL); assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); assert_se(sd_ipv4ll_ref(ll) == ll); @@ -134,17 +135,17 @@ static void test_basic_request(sd_event *e, const struct in_addr *start_address) assert_se(sd_ipv4ll_new(&ll) == 0); if (in4_addr_is_set(start_address)) assert_se(sd_ipv4ll_set_address(ll, start_address) >= 0); - assert_se(sd_ipv4ll_start(ll) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL); assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0); - assert_se(sd_ipv4ll_start(ll) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL); assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0); - assert_se(sd_ipv4ll_start(ll) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL); assert_se(sd_ipv4ll_set_callback(ll, basic_request_handler, basic_request_handler_userdata) == 0); - assert_se(sd_ipv4ll_start(ll) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_ipv4ll_start(ll) == -EINVAL); assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); assert_se(sd_ipv4ll_start(ll) == 1); diff --git a/src/libsystemd-network/test-ndisc-ra.c b/src/libsystemd-network/test-ndisc-ra.c index 23abe78..b2ea8f5 100644 --- a/src/libsystemd-network/test-ndisc-ra.c +++ b/src/libsystemd-network/test-ndisc-ra.c @@ -11,7 +11,7 @@ #include "alloc-util.h" #include "hexdecoct.h" -#include "icmp6-util-unix.h" +#include "icmp6-test-util.h" #include "socket-util.h" #include "strv.h" #include "tests.h" @@ -20,37 +20,6 @@ static struct ether_addr mac_addr = { .ether_addr_octet = { 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 } }; -static uint8_t advertisement[] = { - /* ICMPv6 Router Advertisement, no checksum */ - 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Source Link Layer Address Option */ - 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, - /* Prefix Information Option */ - 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Prefix Information Option */ - 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, - 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Prefix Information Option */ - 0x03, 0x04, 0x30, 0xc0, 0x00, 0x00, 0x0e, 0x10, - 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - /* Recursive DNS Server Option */ - 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - /* DNS Search List Option */ - 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -}; - static bool test_stopped; static struct { struct in6_addr address; @@ -103,26 +72,26 @@ TEST(radv_prefix) { assert_se(sd_radv_prefix_new(&p) >= 0); - assert_se(sd_radv_prefix_set_onlink(NULL, true) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_onlink(NULL, true) < 0); assert_se(sd_radv_prefix_set_onlink(p, true) >= 0); assert_se(sd_radv_prefix_set_onlink(p, false) >= 0); - assert_se(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_address_autoconfiguration(NULL, true) < 0); assert_se(sd_radv_prefix_set_address_autoconfiguration(p, true) >= 0); assert_se(sd_radv_prefix_set_address_autoconfiguration(p, false) >= 0); - assert_se(sd_radv_prefix_set_valid_lifetime(NULL, 1, 1) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_valid_lifetime(NULL, 1, 1) < 0); assert_se(sd_radv_prefix_set_valid_lifetime(p, 0, 0) >= 0); assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0); assert_se(sd_radv_prefix_set_valid_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0); - assert_se(sd_radv_prefix_set_preferred_lifetime(NULL, 1, 1) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_preferred_lifetime(NULL, 1, 1) < 0); assert_se(sd_radv_prefix_set_preferred_lifetime(p, 0, 0) >= 0); assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0); assert_se(sd_radv_prefix_set_preferred_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0); - assert_se(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0); - assert_se(sd_radv_prefix_set_prefix(p, NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(NULL, NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(p, NULL, 0) < 0); assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 64) >= 0); assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 0) < 0); @@ -131,8 +100,8 @@ TEST(radv_prefix) { assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 3) >= 0); assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 125) >= 0); assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 128) >= 0); - assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0); - assert_se(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(p, &prefix[0].address, 129) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_prefix_set_prefix(p, &prefix[0].address, 255) < 0); assert_se(!sd_radv_prefix_unref(p)); } @@ -142,13 +111,13 @@ TEST(radv_route_prefix) { assert_se(sd_radv_route_prefix_new(&p) >= 0); - assert_se(sd_radv_route_prefix_set_lifetime(NULL, 1, 1) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_lifetime(NULL, 1, 1) < 0); assert_se(sd_radv_route_prefix_set_lifetime(p, 0, 0) >= 0); assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_INFINITY) >= 0); assert_se(sd_radv_route_prefix_set_lifetime(p, 300 * USEC_PER_SEC, USEC_PER_YEAR) >= 0); - assert_se(sd_radv_route_prefix_set_prefix(NULL, NULL, 0) < 0); - assert_se(sd_radv_route_prefix_set_prefix(p, NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(NULL, NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, NULL, 0) < 0); assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 64) >= 0); assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 0) >= 0); @@ -157,8 +126,8 @@ TEST(radv_route_prefix) { assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 3) >= 0); assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 125) >= 0); assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 128) >= 0); - assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 129) < 0); - assert_se(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 255) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 129) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_route_prefix_set_prefix(p, &prefix[0].address, 255) < 0); assert_se(!sd_radv_route_prefix_unref(p)); } @@ -168,8 +137,8 @@ TEST(radv_pref64_prefix) { assert_se(sd_radv_pref64_prefix_new(&p) >= 0); - assert_se(sd_radv_pref64_prefix_set_prefix(NULL, NULL, 0, 0) < 0); - assert_se(sd_radv_pref64_prefix_set_prefix(p, NULL, 0, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_pref64_prefix_set_prefix(NULL, NULL, 0, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_pref64_prefix_set_prefix(p, NULL, 0, 0) < 0); assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 32, 300 * USEC_PER_SEC) >= 0); assert_se(sd_radv_pref64_prefix_set_prefix(p, &prefix[0].address, 40, 300 * USEC_PER_SEC) >= 0); @@ -190,60 +159,59 @@ TEST(radv) { assert_se(sd_radv_new(&ra) >= 0); assert_se(ra); - assert_se(sd_radv_set_ifindex(NULL, 0) < 0); - assert_se(sd_radv_set_ifindex(ra, 0) < 0); - assert_se(sd_radv_set_ifindex(ra, -1) < 0); - assert_se(sd_radv_set_ifindex(ra, -2) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(ra, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(ra, -1) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_ifindex(ra, -2) < 0); assert_se(sd_radv_set_ifindex(ra, 42) >= 0); - assert_se(sd_radv_set_mac(NULL, NULL) < 0); - assert_se(sd_radv_set_mac(ra, NULL) >= 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mac(NULL, NULL) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mac(ra, NULL) >= 0); assert_se(sd_radv_set_mac(ra, &mac_addr) >= 0); - assert_se(sd_radv_set_mtu(NULL, 0) < 0); - assert_se(sd_radv_set_mtu(ra, 0) < 0); - assert_se(sd_radv_set_mtu(ra, 1279) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mtu(NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mtu(ra, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_mtu(ra, 1279) < 0); assert_se(sd_radv_set_mtu(ra, 1280) >= 0); assert_se(sd_radv_set_mtu(ra, ~0) >= 0); - assert_se(sd_radv_set_hop_limit(NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_hop_limit(NULL, 0) < 0); assert_se(sd_radv_set_hop_limit(ra, 0) >= 0); assert_se(sd_radv_set_hop_limit(ra, ~0) >= 0); - assert_se(sd_radv_set_router_lifetime(NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_router_lifetime(NULL, 0) < 0); assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0); assert_se(sd_radv_set_router_lifetime(ra, USEC_INFINITY) < 0); assert_se(sd_radv_set_router_lifetime(ra, USEC_PER_YEAR) < 0); assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0); - assert_se(sd_radv_set_preference(NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_preference(NULL, 0) < 0); assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_LOW) >= 0); assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0); assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0); - assert_se(sd_radv_set_preference(ra, ~0) < 0); - - assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_HIGH) >= 0); - assert_se(sd_radv_set_router_lifetime(ra, 300 * USEC_PER_SEC) >= 0); - assert_se(sd_radv_set_router_lifetime(ra, 0) < 0); - assert_se(sd_radv_set_preference(ra, SD_NDISC_PREFERENCE_MEDIUM) >= 0); - assert_se(sd_radv_set_router_lifetime(ra, 0) >= 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_preference(ra, ~0) < 0); - assert_se(sd_radv_set_managed_information(NULL, true) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_managed_information(NULL, true) < 0); assert_se(sd_radv_set_managed_information(ra, true) >= 0); assert_se(sd_radv_set_managed_information(ra, false) >= 0); - assert_se(sd_radv_set_other_information(NULL, true) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_other_information(NULL, true) < 0); assert_se(sd_radv_set_other_information(ra, true) >= 0); assert_se(sd_radv_set_other_information(ra, false) >= 0); - assert_se(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_reachable_time(NULL, 10 * USEC_PER_MSEC) < 0); + assert_se(sd_radv_set_reachable_time(ra, 10 * USEC_PER_MSEC) >= 0); + assert_se(sd_radv_set_reachable_time(ra, 0) >= 0); + assert_se(sd_radv_set_reachable_time(ra, USEC_INFINITY) >= 0); + + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_retransmit(NULL, 10 * USEC_PER_MSEC) < 0); assert_se(sd_radv_set_retransmit(ra, 10 * USEC_PER_MSEC) >= 0); assert_se(sd_radv_set_retransmit(ra, 0) >= 0); - assert_se(sd_radv_set_retransmit(ra, usec_add(UINT32_MAX * USEC_PER_MSEC, USEC_PER_MSEC)) < 0); + assert_se(sd_radv_set_retransmit(ra, USEC_INFINITY) >= 0); - assert_se(sd_radv_set_rdnss(NULL, 0, NULL, 0) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_rdnss(NULL, 0, NULL, 0) < 0); assert_se(sd_radv_set_rdnss(ra, 0, NULL, 0) >= 0); - assert_se(sd_radv_set_rdnss(ra, 0, NULL, 128) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_rdnss(ra, 0, NULL, 128) < 0); assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 0) >= 0); assert_se(sd_radv_set_rdnss(ra, 600 * USEC_PER_SEC, &test_rdnss, 1) >= 0); assert_se(sd_radv_set_rdnss(ra, 0, &test_rdnss, 1) >= 0); @@ -254,15 +222,15 @@ TEST(radv) { assert_se(sd_radv_set_dnssl(ra, 0, (char **)test_dnssl) >= 0); assert_se(sd_radv_set_dnssl(ra, 600 * USEC_PER_SEC, (char **)test_dnssl) >= 0); - assert_se(sd_radv_set_home_agent_information(NULL, true) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_information(NULL, true) < 0); assert_se(sd_radv_set_home_agent_information(ra, true) >= 0); assert_se(sd_radv_set_home_agent_information(ra, false) >= 0); - assert_se(sd_radv_set_home_agent_preference(NULL, 10) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_preference(NULL, 10) < 0); assert_se(sd_radv_set_home_agent_preference(ra, 10) >= 0); assert_se(sd_radv_set_home_agent_preference(ra, 0) >= 0); - assert_se(sd_radv_set_home_agent_lifetime(NULL, 300 * USEC_PER_SEC) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_radv_set_home_agent_lifetime(NULL, 300 * USEC_PER_SEC) < 0); assert_se(sd_radv_set_home_agent_lifetime(ra, 300 * USEC_PER_SEC) >= 0); assert_se(sd_radv_set_home_agent_lifetime(ra, 0) >= 0); assert_se(sd_radv_set_home_agent_lifetime(ra, USEC_PER_DAY) < 0); @@ -271,49 +239,96 @@ TEST(radv) { assert_se(!ra); } -static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_radv *ra = userdata; - unsigned char buf[168]; - size_t i; +static void dump_message(const uint8_t *buf, size_t len) { + assert(len >= sizeof(struct nd_router_advert)); - assert_se(read(test_fd[0], &buf, sizeof(buf)) == sizeof(buf)); + printf("Received Router Advertisement with lifetime %i sec\n", + (buf[6] << 8) + buf[7]); - /* router lifetime must be zero when test is stopped */ - if (test_stopped) { - advertisement[6] = 0x00; - advertisement[7] = 0x00; - } - - printf ("Received Router Advertisement with lifetime %i\n", - (advertisement[6] << 8) + advertisement[7]); - - /* test only up to buf size, rest is not yet implemented */ - for (i = 0; i < sizeof(buf); i++) { + for (size_t i = 0; i < len; i++) { if (!(i % 8)) printf("%3zu: ", i); printf("0x%02x", buf[i]); - assert_se(buf[i] == advertisement[i]); - if ((i + 1) % 8) printf(", "); else printf("\n"); } +} - if (test_stopped) { - sd_event *e; +static void verify_message(const uint8_t *buf, size_t len) { + static const uint8_t advertisement[] = { + /* ICMPv6 Router Advertisement, no checksum */ + 0x86, 0x00, 0x00, 0x00, 0x40, 0xc0, 0x00, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Source Link Layer Address Option */ + 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, + /* Prefix Information Option */ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Prefix Information Option */ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Prefix Information Option */ + 0x03, 0x04, 0x30, 0xc0, 0x00, 0x00, 0x0e, 0x10, + 0x00, 0x00, 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xc0, 0x01, 0x0d, 0xad, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* Recursive DNS Server Option */ + 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* DNS Search List Option */ + 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + /* verify only up to known options, rest is not yet implemented */ + for (size_t i = 0, m = MIN(len, sizeof(advertisement)); i < m; i++) { + if (test_stopped) + /* on stop, many header fields are zero */ + switch (i) { + case 4: /* hop limit */ + case 5: /* flags */ + case 6 ... 7: /* router lifetime */ + case 8 ... 11: /* reachable time */ + case 12 ... 15: /* retrans timer */ + assert_se(buf[i] == 0); + continue; + } - e = sd_radv_get_event(ra); - sd_event_exit(e, 0); + assert_se(buf[i] == advertisement[i]); + } +} +static int radv_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_radv *ra = ASSERT_PTR(userdata); + _cleanup_free_ uint8_t *buf = NULL; + ssize_t buflen; + + buflen = next_datagram_size_fd(fd); + assert_se(buflen >= 0); + assert_se(buf = new0(uint8_t, buflen)); + + assert_se(read(fd, buf, buflen) == buflen); + + dump_message(buf, buflen); + verify_message(buf, buflen); + + if (test_stopped) { + assert_se(sd_event_exit(sd_radv_get_event(ra), 0) >= 0); return 0; } assert_se(sd_radv_stop(ra) >= 0); test_stopped = true; - return 0; } @@ -365,7 +380,7 @@ TEST(ra) { assert_se(sd_event_source_set_io_fd_own(recv_router_advertisement, true) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, - 2 * USEC_PER_SEC, 0, + 30 * USEC_PER_SEC, 0, NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); assert_se(sd_radv_start(ra) >= 0); diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index d94cc1c..66aad26 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -12,7 +12,8 @@ #include "alloc-util.h" #include "fd-util.h" #include "hexdecoct.h" -#include "icmp6-util-unix.h" +#include "icmp6-packet.h" +#include "icmp6-test-util.h" #include "socket-util.h" #include "strv.h" #include "ndisc-internal.h" @@ -23,21 +24,20 @@ static struct ether_addr mac_addr = { }; static bool verbose = false; -static sd_ndisc *test_timeout_nd; static void router_dump(sd_ndisc_router *rt) { struct in6_addr addr; uint8_t hop_limit; - usec_t t, lifetime; + usec_t t, lifetime, retrans_time; uint64_t flags; uint32_t mtu; - unsigned preference; + uint8_t preference; int r; assert_se(rt); log_info("--"); - assert_se(sd_ndisc_router_get_address(rt, &addr) >= 0); + assert_se(sd_ndisc_router_get_sender_address(rt, &addr) >= 0); log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr)); assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0); @@ -65,6 +65,9 @@ static void router_dump(sd_ndisc_router *rt) { assert_se(sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0); log_info("Lifetime: %s (%s)", FORMAT_TIMESPAN(lifetime, USEC_PER_SEC), FORMAT_TIMESTAMP(t)); + assert_se(sd_ndisc_router_get_retransmission_time(rt, &retrans_time) >= 0); + log_info("Retransmission Time: %s", FORMAT_TIMESPAN(retrans_time, USEC_PER_SEC)); + if (sd_ndisc_router_get_mtu(rt, &mtu) < 0) log_info("No MTU set"); else @@ -88,20 +91,19 @@ static void router_dump(sd_ndisc_router *rt) { case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: case SD_NDISC_OPTION_TARGET_LL_ADDRESS: { _cleanup_free_ char *c = NULL; - const void *p; + const uint8_t *p; size_t n; assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0); assert_se(n > 2); - assert_se(c = hexmem((uint8_t*) p + 2, n - 2)); + assert_se(c = hexmem(p + 2, n - 2)); log_info("Address: %s", c); break; } case SD_NDISC_OPTION_PREFIX_INFORMATION: { - unsigned prefix_len; - uint8_t pfl; + uint8_t prefix_len, pfl; struct in6_addr a; assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime) >= 0); @@ -143,14 +145,12 @@ static void router_dump(sd_ndisc_router *rt) { } case SD_NDISC_OPTION_DNSSL: { - _cleanup_strv_free_ char **l = NULL; - int n, i; + char **l; - n = sd_ndisc_router_dnssl_get_domains(rt, &l); - assert_se(n > 0); + assert_se(sd_ndisc_router_dnssl_get_domains(rt, &l) >= 0); - for (i = 0; i < n; i++) - log_info("Domain: %s", l[i]); + STRV_FOREACH(s, l) + log_info("Domain: %s", *s); assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, &lifetime) >= 0); assert_se(sd_ndisc_router_dnssl_get_lifetime_timestamp(rt, CLOCK_REALTIME, &t) >= 0); @@ -164,18 +164,23 @@ static void router_dump(sd_ndisc_router *rt) { static int send_ra(uint8_t flags) { uint8_t advertisement[] = { + /* struct nd_router_advert */ 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */ 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */ 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* type = 0x1f (SD_NDISC_OPTION_DNSSL), length = 24 */ 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), length = 8 */ 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, }; @@ -190,7 +195,7 @@ static int send_ra(uint8_t flags) { return 0; } -static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) { +static void test_callback_ra(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { sd_event *e = userdata; static unsigned idx = 0; uint64_t flags_array[] = { @@ -207,6 +212,8 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router if (event != SD_NDISC_EVENT_ROUTER) return; + sd_ndisc_router *rt = ASSERT_PTR(message); + router_dump(rt); assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); @@ -221,15 +228,22 @@ static void test_callback(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router return; } + idx = 0; sd_event_exit(e, 0); } +static int on_recv_rs(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + assert_se(icmp6_packet_receive(fd, &packet) >= 0); + + return send_ra(0); +} + TEST(rs) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - send_ra_function = send_ra; - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); @@ -239,7 +253,7 @@ TEST(rs) { assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_ra, e) >= 0); assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, 30 * USEC_PER_SEC, 0, @@ -253,19 +267,219 @@ TEST(rs) { assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_loop(e) >= 0); - test_fd[1] = safe_close(test_fd[1]); + test_fd[1] = -EBADF; } -static int test_timeout_value(uint8_t flags) { +static int send_ra_invalid_domain(uint8_t flags) { + uint8_t advertisement[] = { + /* struct nd_router_advert */ + 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* type = 0x03 (SD_NDISC_OPTION_PREFIX_INFORMATION), length = 32 */ + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* type = 0x19 (SD_NDISC_OPTION_RDNSS), length = 24 */ + 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* type = 0x1f (SD_NDISC_OPTION_DNSSL), length = 112 */ + 0x1f, 0x0e, 0xee, 0x68, 0xb0, 0xf4, 0x36, 0x39, + 0x2c, 0xbc, 0x0b, 0xbc, 0xa9, 0x97, 0x71, 0x37, + 0xad, 0x86, 0x80, 0x14, 0x2e, 0x58, 0xaa, 0x8a, + 0xb7, 0xa1, 0xbe, 0x91, 0x59, 0x00, 0xc4, 0xe8, + 0xdd, 0xd8, 0x6d, 0xe5, 0x4a, 0x7a, 0x71, 0x42, + 0x74, 0x45, 0x9e, 0x2e, 0xfd, 0x9d, 0x71, 0x1d, + 0xd0, 0xc0, 0x54, 0x0c, 0x4d, 0x1f, 0xbf, 0x90, + 0xd9, 0x79, 0x58, 0xc0, 0x1d, 0xa3, 0x39, 0xcf, + 0xb8, 0xec, 0xd2, 0xe4, 0xcd, 0xb6, 0x13, 0x2f, + 0xc0, 0x46, 0xe8, 0x07, 0x3f, 0xaa, 0x28, 0xa5, + 0x23, 0xf1, 0xf0, 0xca, 0xd3, 0x19, 0x3f, 0xfa, + 0x6c, 0x7c, 0xec, 0x1b, 0xcf, 0x71, 0xeb, 0xba, + 0x68, 0x1b, 0x8e, 0x7d, 0x93, 0x7e, 0x0b, 0x9f, + 0xdb, 0x12, 0x9c, 0x75, 0x22, 0x5f, 0x12, 0x00, + /* type = 0x01 (SD_NDISC_OPTION_SOURCE_LL_ADDRESS), length = 8 */ + 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, + }; + + advertisement[5] = flags; + + printf("sizeof(nd_router_advert)=%zu\n", sizeof(struct nd_router_advert)); + + assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == + sizeof(advertisement)); + + if (verbose) + printf(" sent RA with flag 0x%02x\n", flags); + + return 0; +} + +static int on_recv_rs_invalid_domain(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + assert_se(icmp6_packet_receive(fd, &packet) >= 0); + + return send_ra_invalid_domain(0); +} + +TEST(invalid_domain) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_ra, e) >= 0); + + assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, + 30 * USEC_PER_SEC, 0, + NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_invalid_domain, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + test_fd[1] = -EBADF; +} + +static void neighbor_dump(sd_ndisc_neighbor *na) { + struct in6_addr addr; + uint32_t flags; + + assert_se(na); + + log_info("--"); + assert_se(sd_ndisc_neighbor_get_sender_address(na, &addr) >= 0); + log_info("Sender: %s", IN6_ADDR_TO_STRING(&addr)); + + assert_se(sd_ndisc_neighbor_get_flags(na, &flags) >= 0); + log_info("Flags: Router:%s, Solicited:%s, Override: %s", + yes_no(flags & ND_NA_FLAG_ROUTER), + yes_no(flags & ND_NA_FLAG_SOLICITED), + yes_no(flags & ND_NA_FLAG_OVERRIDE)); + + assert_se(sd_ndisc_neighbor_is_router(na) == FLAGS_SET(flags, ND_NA_FLAG_ROUTER)); + assert_se(sd_ndisc_neighbor_is_solicited(na) == FLAGS_SET(flags, ND_NA_FLAG_SOLICITED)); + assert_se(sd_ndisc_neighbor_is_override(na) == FLAGS_SET(flags, ND_NA_FLAG_OVERRIDE)); +} + +static int send_na(uint32_t flags) { + uint8_t advertisement[] = { + /* struct nd_neighbor_advert */ + 0x88, 0x00, 0xde, 0x83, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + /* type = 0x02 (SD_NDISC_OPTION_TARGET_LL_ADDRESS), length = 8 */ + 0x01, 0x01, 'A', 'B', 'C', '1', '2', '3', + }; + + ((struct nd_neighbor_advert*) advertisement)->nd_na_flags_reserved = flags; + + assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == sizeof(advertisement)); + if (verbose) + printf(" sent NA with flag 0x%02x\n", flags); + + return 0; +} + +static void test_callback_na(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { + sd_event *e = userdata; + static unsigned idx = 0; + uint32_t flags_array[] = { + 0, + 0, + ND_NA_FLAG_ROUTER, + ND_NA_FLAG_SOLICITED, + ND_NA_FLAG_SOLICITED | ND_NA_FLAG_OVERRIDE, + }; + uint32_t flags; + + assert_se(nd); + + if (event != SD_NDISC_EVENT_NEIGHBOR) + return; + + sd_ndisc_neighbor *rt = ASSERT_PTR(message); + + neighbor_dump(rt); + + assert_se(sd_ndisc_neighbor_get_flags(rt, &flags) >= 0); + assert_se(flags == flags_array[idx]); + idx++; + + if (verbose) + printf(" got event 0x%02" PRIx32 "\n", flags); + + if (idx < ELEMENTSOF(flags_array)) { + send_na(flags_array[idx]); + return; + } + + idx = 0; + sd_event_exit(e, 0); +} + +static int on_recv_rs_na(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + assert_se(icmp6_packet_receive(fd, &packet) >= 0); + + return send_na(0); +} + +TEST(na) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback_na, e) >= 0); + + assert_se(sd_event_add_time_relative(e, NULL, CLOCK_BOOTTIME, + 30 * USEC_PER_SEC, 0, + NULL, INT_TO_PTR(-ETIMEDOUT)) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_na, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + test_fd[1] = -EBADF; +} + +static int on_recv_rs_timeout(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(icmp6_packet_unrefp) ICMP6Packet *packet = NULL; + sd_ndisc *nd = ASSERT_PTR(userdata); static int count = 0; static usec_t last = 0; - sd_ndisc *nd = test_timeout_nd; usec_t min, max; - assert_se(nd); - assert_se(nd->event); + assert_se(icmp6_packet_receive(fd, &packet) >= 0); if (++count >= 20) sd_event_exit(nd->event, 0); @@ -309,17 +523,14 @@ static int test_timeout_value(uint8_t flags) { TEST(timeout) { _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL; _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - send_ra_function = test_timeout_value; - assert_se(sd_event_new(&e) >= 0); assert_se(sd_ndisc_new(&nd) >= 0); assert_se(nd); - test_timeout_nd = nd; - assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); @@ -331,9 +542,12 @@ TEST(timeout) { assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_event_add_io(e, &s, test_fd[1], EPOLLIN, on_recv_rs_timeout, nd) >= 0); + assert_se(sd_event_source_set_io_fd_own(s, true) >= 0); + assert_se(sd_event_loop(e) >= 0); - test_fd[1] = safe_close(test_fd[1]); + test_fd[1] = -EBADF; } DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd-network/test-ndisc-send.c b/src/libsystemd-network/test-ndisc-send.c new file mode 100644 index 0000000..1b1b27d --- /dev/null +++ b/src/libsystemd-network/test-ndisc-send.c @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "build.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "main-func.h" +#include "ndisc-option.h" +#include "netlink-util.h" +#include "network-common.h" +#include "parse-util.h" +#include "socket-util.h" +#include "strv.h" +#include "time-util.h" + +static int arg_ifindex = 0; +static int arg_icmp6_type = 0; +static union in_addr_union arg_dest = IN_ADDR_NULL; +static uint8_t arg_hop_limit = 0; +static uint8_t arg_ra_flags = 0; +static uint8_t arg_preference = false; +static usec_t arg_lifetime = 0; +static usec_t arg_reachable = 0; +static usec_t arg_retransmit = 0; +static uint32_t arg_na_flags = 0; +static union in_addr_union arg_target_address = IN_ADDR_NULL; +static union in_addr_union arg_redirect_destination = IN_ADDR_NULL; +static bool arg_set_source_mac = false; +static struct ether_addr arg_source_mac = {}; +static bool arg_set_target_mac = false; +static struct ether_addr arg_target_mac = {}; +static struct ip6_hdr *arg_redirected_header = NULL; +static bool arg_set_mtu = false; +static uint32_t arg_mtu = 0; + +STATIC_DESTRUCTOR_REGISTER(arg_redirected_header, freep); + +static int parse_icmp6_type(const char *str) { + if (STR_IN_SET(str, "router-solicit", "rs", "RS")) + return ND_ROUTER_SOLICIT; + if (STR_IN_SET(str, "router-advertisement", "ra", "RA")) + return ND_ROUTER_ADVERT; + if (STR_IN_SET(str, "neighbor-solicit", "ns", "NS")) + return ND_NEIGHBOR_SOLICIT; + if (STR_IN_SET(str, "neighbor-advertisement", "na", "NA")) + return ND_NEIGHBOR_ADVERT; + if (STR_IN_SET(str, "redirect", "rd", "RD")) + return ND_REDIRECT; + return -EINVAL; +} + +static int parse_preference(const char *str) { + if (streq(str, "low")) + return SD_NDISC_PREFERENCE_LOW; + if (streq(str, "medium")) + return SD_NDISC_PREFERENCE_MEDIUM; + if (streq(str, "high")) + return SD_NDISC_PREFERENCE_HIGH; + if (streq(str, "reserved")) + return SD_NDISC_PREFERENCE_RESERVED; + return -EINVAL; +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_RA_HOP_LIMIT, + ARG_RA_MANAGED, + ARG_RA_OTHER, + ARG_RA_HOME_AGENT, + ARG_RA_PREFERENCE, + ARG_RA_LIFETIME, + ARG_RA_REACHABLE, + ARG_RA_RETRANSMIT, + ARG_NA_ROUTER, + ARG_NA_SOLICITED, + ARG_NA_OVERRIDE, + ARG_TARGET_ADDRESS, + ARG_REDIRECT_DESTINATION, + ARG_OPTION_SOURCE_LL, + ARG_OPTION_TARGET_LL, + ARG_OPTION_REDIRECTED_HEADER, + ARG_OPTION_MTU, + }; + + static const struct option options[] = { + { "version", no_argument, NULL, ARG_VERSION }, + { "interface", required_argument, NULL, 'i' }, + { "type", required_argument, NULL, 't' }, + { "dest", required_argument, NULL, 'd' }, + /* For Router Advertisement */ + { "hop-limit", required_argument, NULL, ARG_RA_HOP_LIMIT }, + { "managed", required_argument, NULL, ARG_RA_MANAGED }, + { "other", required_argument, NULL, ARG_RA_OTHER }, + { "home-agent", required_argument, NULL, ARG_RA_HOME_AGENT }, + { "preference", required_argument, NULL, ARG_RA_PREFERENCE }, + { "lifetime", required_argument, NULL, ARG_RA_LIFETIME }, + { "reachable-time", required_argument, NULL, ARG_RA_REACHABLE }, + { "retransmit-timer", required_argument, NULL, ARG_RA_RETRANSMIT }, + /* For Neighbor Advertisement */ + { "is-router", required_argument, NULL, ARG_NA_ROUTER }, + { "is-solicited", required_argument, NULL, ARG_NA_SOLICITED }, + { "is-override", required_argument, NULL, ARG_NA_OVERRIDE }, + /* For Neighbor Solicit, Neighbor Advertisement, and Redirect */ + { "target-address", required_argument, NULL, ARG_TARGET_ADDRESS }, + /* For Redirect */ + { "redirect-destination", required_argument, NULL, ARG_REDIRECT_DESTINATION }, + /* Options */ + { "source-ll-address", required_argument, NULL, ARG_OPTION_SOURCE_LL }, + { "target-ll-address", required_argument, NULL, ARG_OPTION_TARGET_LL }, + { "redirected-header", required_argument, NULL, ARG_OPTION_REDIRECTED_HEADER }, + { "mtu", required_argument, NULL, ARG_OPTION_MTU }, + {} + }; + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "i:t:d:", options, NULL)) >= 0) { + + switch (c) { + + case ARG_VERSION: + return version(); + + case 'i': + r = rtnl_resolve_interface_or_warn(&rtnl, optarg); + if (r < 0) + return r; + arg_ifindex = r; + break; + + case 't': + r = parse_icmp6_type(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse message type: %m"); + arg_icmp6_type = r; + break; + + case 'd': + r = in_addr_from_string(AF_INET6, optarg, &arg_dest); + if (r < 0) + return log_error_errno(r, "Failed to parse destination address: %m"); + if (!in6_addr_is_link_local(&arg_dest.in6)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The destination address %s is not a link-local address.", optarg); + break; + + case ARG_RA_HOP_LIMIT: + r = safe_atou8(optarg, &arg_hop_limit); + if (r < 0) + return log_error_errno(r, "Failed to parse hop limit: %m"); + break; + + case ARG_RA_MANAGED: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse managed flag: %m"); + SET_FLAG(arg_ra_flags, ND_RA_FLAG_MANAGED, r); + break; + + case ARG_RA_OTHER: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse other flag: %m"); + SET_FLAG(arg_ra_flags, ND_RA_FLAG_OTHER, r); + break; + + case ARG_RA_HOME_AGENT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse home-agent flag: %m"); + SET_FLAG(arg_ra_flags, ND_RA_FLAG_HOME_AGENT, r); + break; + + case ARG_RA_PREFERENCE: + r = parse_preference(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse preference: %m"); + arg_preference = r; + break; + + case ARG_RA_LIFETIME: + r = parse_sec(optarg, &arg_lifetime); + if (r < 0) + return log_error_errno(r, "Failed to parse lifetime: %m"); + break; + + case ARG_RA_REACHABLE: + r = parse_sec(optarg, &arg_reachable); + if (r < 0) + return log_error_errno(r, "Failed to parse reachable time: %m"); + break; + + case ARG_RA_RETRANSMIT: + r = parse_sec(optarg, &arg_retransmit); + if (r < 0) + return log_error_errno(r, "Failed to parse retransmit timer: %m"); + break; + + case ARG_NA_ROUTER: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse is-router flag: %m"); + SET_FLAG(arg_na_flags, ND_NA_FLAG_ROUTER, r); + break; + + case ARG_NA_SOLICITED: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse is-solicited flag: %m"); + SET_FLAG(arg_na_flags, ND_NA_FLAG_SOLICITED, r); + break; + + case ARG_NA_OVERRIDE: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse is-override flag: %m"); + SET_FLAG(arg_na_flags, ND_NA_FLAG_OVERRIDE, r); + break; + + case ARG_TARGET_ADDRESS: + r = in_addr_from_string(AF_INET6, optarg, &arg_target_address); + if (r < 0) + return log_error_errno(r, "Failed to parse target address: %m"); + break; + + case ARG_REDIRECT_DESTINATION: + r = in_addr_from_string(AF_INET6, optarg, &arg_redirect_destination); + if (r < 0) + return log_error_errno(r, "Failed to parse destination address: %m"); + break; + + case ARG_OPTION_SOURCE_LL: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse source LL address option: %m"); + arg_set_source_mac = r; + break; + + case ARG_OPTION_TARGET_LL: + r = parse_ether_addr(optarg, &arg_target_mac); + if (r < 0) + return log_error_errno(r, "Failed to parse target LL address option: %m"); + arg_set_target_mac = true; + break; + + case ARG_OPTION_REDIRECTED_HEADER: { + _cleanup_free_ void *p = NULL; + size_t len; + + r = unbase64mem(optarg, &p, &len); + if (r < 0) + return log_error_errno(r, "Failed to parse redirected header: %m"); + + if (len < sizeof(struct ip6_hdr)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid redirected header."); + + arg_redirected_header = TAKE_PTR(p); + break; + } + case ARG_OPTION_MTU: + r = safe_atou32(optarg, &arg_mtu); + if (r < 0) + return log_error_errno(r, "Failed to parse MTU: %m"); + arg_set_mtu = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + if (arg_ifindex <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--interface/-i option is mandatory."); + + if (arg_icmp6_type <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--type/-t option is mandatory."); + + if (in6_addr_is_null(&arg_dest.in6)) { + if (IN_SET(arg_icmp6_type, ND_ROUTER_ADVERT, ND_NEIGHBOR_ADVERT, ND_REDIRECT)) + arg_dest.in6 = IN6_ADDR_ALL_NODES_MULTICAST; + else + arg_dest.in6 = IN6_ADDR_ALL_ROUTERS_MULTICAST; + } + + if (arg_set_source_mac) { + struct hw_addr_data hw_addr; + + r = rtnl_get_link_info(&rtnl, arg_ifindex, + /* ret_iftype = */ NULL, + /* ret_flags = */ NULL, + /* ret_kind = */ NULL, + &hw_addr, + /* ret_permanent_hw_addr = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to get the source link-layer address: %m"); + + if (hw_addr.length != sizeof(struct ether_addr)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported hardware address length %zu: %m", + hw_addr.length); + + arg_source_mac = hw_addr.ether; + } + + return 1; +} + +static int send_icmp6(int fd, const struct icmp6_hdr *hdr) { + _cleanup_set_free_ Set *options = NULL; + int r; + + assert(fd >= 0); + assert(hdr); + + if (arg_set_source_mac) { + r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_SOURCE_LL_ADDRESS, &arg_source_mac); + if (r < 0) + return r; + } + + if (arg_set_target_mac) { + r = ndisc_option_add_link_layer_address(&options, 0, SD_NDISC_OPTION_TARGET_LL_ADDRESS, &arg_target_mac); + if (r < 0) + return r; + } + + if (arg_redirected_header) { + r = ndisc_option_add_redirected_header(&options, 0, arg_redirected_header); + if (r < 0) + return r; + } + + if (arg_set_mtu) { + r = ndisc_option_add_mtu(&options, 0, arg_mtu); + if (r < 0) + return r; + } + + return ndisc_send(fd, &arg_dest.in6, hdr, options, now(CLOCK_BOOTTIME)); +} + +static int send_router_solicit(int fd) { + struct nd_router_solicit hdr = { + .nd_rs_type = ND_ROUTER_SOLICIT, + }; + + assert(fd >= 0); + + return send_icmp6(fd, &hdr.nd_rs_hdr); +} + +static int send_router_advertisement(int fd) { + struct nd_router_advert hdr = { + .nd_ra_type = ND_ROUTER_ADVERT, + .nd_ra_router_lifetime = usec_to_be16_sec(arg_lifetime), + .nd_ra_reachable = usec_to_be32_msec(arg_reachable), + .nd_ra_retransmit = usec_to_be32_msec(arg_retransmit), + }; + + assert(fd >= 0); + + /* The nd_ra_curhoplimit and nd_ra_flags_reserved fields cannot specified with nd_ra_router_lifetime + * simultaneously in the structured initializer in the above. */ + hdr.nd_ra_curhoplimit = arg_hop_limit; + hdr.nd_ra_flags_reserved = arg_ra_flags; + + return send_icmp6(fd, &hdr.nd_ra_hdr); +} + +static int send_neighbor_solicit(int fd) { + struct nd_neighbor_solicit hdr = { + .nd_ns_type = ND_NEIGHBOR_SOLICIT, + .nd_ns_target = arg_target_address.in6, + }; + + assert(fd >= 0); + + return send_icmp6(fd, &hdr.nd_ns_hdr); +} + +static int send_neighbor_advertisement(int fd) { + struct nd_neighbor_advert hdr = { + .nd_na_type = ND_NEIGHBOR_ADVERT, + .nd_na_flags_reserved = arg_na_flags, + .nd_na_target = arg_target_address.in6, + }; + + assert(fd >= 0); + + return send_icmp6(fd, &hdr.nd_na_hdr); +} + +static int send_redirect(int fd) { + struct nd_redirect hdr = { + .nd_rd_type = ND_REDIRECT, + .nd_rd_target = arg_target_address.in6, + .nd_rd_dst = arg_redirect_destination.in6, + }; + + assert(fd >= 0); + + return send_icmp6(fd, &hdr.nd_rd_hdr); +} + +static int run(int argc, char *argv[]) { + _cleanup_close_ int fd = -EBADF; + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + fd = icmp6_bind(arg_ifindex, /* is_router = */ false); + if (fd < 0) + return log_error_errno(fd, "Failed to bind socket to interface: %m"); + + switch (arg_icmp6_type) { + case ND_ROUTER_SOLICIT: + return send_router_solicit(fd); + case ND_ROUTER_ADVERT: + return send_router_advertisement(fd); + case ND_NEIGHBOR_SOLICIT: + return send_neighbor_solicit(fd); + case ND_NEIGHBOR_ADVERT: + return send_neighbor_advertisement(fd); + case ND_REDIRECT: + return send_redirect(fd); + default: + assert_not_reached(); + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym index 4113920..78b4453 100644 --- a/src/libsystemd/libsystemd.sym +++ b/src/libsystemd/libsystemd.sym @@ -834,3 +834,12 @@ global: sd_id128_get_app_specific; sd_device_enumerator_add_match_property_required; } LIBSYSTEMD_254; + +LIBSYSTEMD_256 { +global: + sd_bus_creds_get_pidfd_dup; + sd_bus_creds_new_from_pidfd; + sd_id128_get_invocation_app_specific; + sd_journal_stream_fd_with_namespace; + sd_event_source_get_inotify_path; +} LIBSYSTEMD_255; diff --git a/src/libsystemd/meson.build b/src/libsystemd/meson.build index 5d18f97..6d4337d 100644 --- a/src/libsystemd/meson.build +++ b/src/libsystemd/meson.build @@ -118,8 +118,7 @@ libsystemd_static = static_library( libsystemd_sources, include_directories : libsystemd_includes, c_args : libsystemd_c_args, - link_with : [libbasic, - libbasic_compress], + link_with : [libbasic], dependencies : [threads, librt, userspace], @@ -159,6 +158,10 @@ libsystemd_tests += [ 'sources' : files('sd-journal/test-journal-enum.c'), 'timeout' : 360, }, + { + 'sources' : files('sd-event/test-event.c'), + 'timeout' : 120, + } ] ############################################################ @@ -171,7 +174,6 @@ simple_tests += files( 'sd-device/test-device-util.c', 'sd-device/test-sd-device-monitor.c', 'sd-device/test-sd-device.c', - 'sd-event/test-event.c', 'sd-journal/test-journal-flush.c', 'sd-journal/test-journal-interleaving.c', 'sd-journal/test-journal-stream.c', diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c index df26fd7..de12ec5 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ b/src/libsystemd/sd-bus/bus-common-errors.c @@ -105,11 +105,13 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRODUCT_UUID, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_HARDWARE_SERIAL, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_FILE_IS_PROTECTED, EACCES), SD_BUS_ERROR_MAP(BUS_ERROR_READ_ONLY_FILESYSTEM, EROFS), SD_BUS_ERROR_MAP(BUS_ERROR_SPEED_METER_INACTIVE, EOPNOTSUPP), SD_BUS_ERROR_MAP(BUS_ERROR_UNMANAGED_INTERFACE, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_ALREADY_RELOADING, EBUSY), SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_HOME, EEXIST), SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE, EEXIST), @@ -146,6 +148,7 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { SD_BUS_ERROR_MAP(BUS_ERROR_HOME_CANT_AUTHENTICATE, EKEYREVOKED), SD_BUS_ERROR_MAP(BUS_ERROR_HOME_IN_USE, EADDRINUSE), SD_BUS_ERROR_MAP(BUS_ERROR_REBALANCE_NOT_NEEDED, EALREADY), + SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_REFERENCED, EBADR), SD_BUS_ERROR_MAP_END }; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h index 3a0eef4..94dc85d 100644 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ b/src/libsystemd/sd-bus/bus-common-errors.h @@ -106,11 +106,13 @@ #define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" #define BUS_ERROR_NO_PRODUCT_UUID "org.freedesktop.hostname1.NoProductUUID" +#define BUS_ERROR_NO_HARDWARE_SERIAL "org.freedesktop.hostname1.NoHardwareSerial" #define BUS_ERROR_FILE_IS_PROTECTED "org.freedesktop.hostname1.FileIsProtected" #define BUS_ERROR_READ_ONLY_FILESYSTEM "org.freedesktop.hostname1.ReadOnlyFilesystem" #define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive" #define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface" +#define BUS_ERROR_NETWORK_ALREADY_RELOADING "org.freedesktop.network1.AlreadyReloading" #define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome" #define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse" @@ -151,5 +153,6 @@ #define BUS_ERROR_HOME_CANT_AUTHENTICATE "org.freedesktop.home1.HomeCantAuthenticate" #define BUS_ERROR_HOME_IN_USE "org.freedesktop.home1.HomeInUse" #define BUS_ERROR_REBALANCE_NOT_NEEDED "org.freedesktop.home1.RebalanceNotNeeded" +#define BUS_ERROR_HOME_NOT_REFERENCED "org.freedesktop.home1.HomeNotReferenced" BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c index 4146a6e..2eca82b 100644 --- a/src/libsystemd/sd-bus/bus-container.c +++ b/src/libsystemd/sd-bus/bus-container.c @@ -34,7 +34,7 @@ int bus_container_connect_socket(sd_bus *b) { log_debug("sd-bus: connecting bus%s%s to namespace of PID "PID_FMT"...", b->description ? " " : "", strempty(b->description), b->nspid); - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, &usernsfd, &rootfd); if (r < 0) return log_debug_errno(r, "Failed to open namespace of PID "PID_FMT": %m", b->nspid); diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c index 1355e41..c420584 100644 --- a/src/libsystemd/sd-bus/bus-control.c +++ b/src/libsystemd/sd-bus/bus-control.c @@ -14,6 +14,7 @@ #include "bus-internal.h" #include "bus-message.h" #include "capability-util.h" +#include "fd-util.h" #include "process-util.h" #include "stdio-util.h" #include "string-util.h" @@ -430,7 +431,6 @@ _public_ int sd_bus_get_name_creds( _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; const char *unique; - pid_t pid = 0; int r; assert_return(bus, -EINVAL); @@ -483,8 +483,9 @@ _public_ int sd_bus_get_name_creds( } if (mask != 0) { + bool need_pid, need_uid, need_gids, need_selinux, need_separate_calls, need_pidfd, need_augment; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool need_pid, need_uid, need_selinux, need_separate_calls; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; c = bus_creds_new(); if (!c) @@ -498,20 +499,25 @@ _public_ int sd_bus_get_name_creds( c->mask |= SD_BUS_CREDS_UNIQUE_NAME; } - need_pid = (mask & SD_BUS_CREDS_PID) || - ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))); + need_augment = + (mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID| + SD_BUS_CREDS_PIDFD)); + + need_pid = (mask & SD_BUS_CREDS_PID) || need_augment; need_uid = mask & SD_BUS_CREDS_EUID; + need_gids = mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS; need_selinux = mask & SD_BUS_CREDS_SELINUX_CONTEXT; + need_pidfd = (mask & SD_BUS_CREDS_PIDFD) || need_augment; - if (need_pid + need_uid + need_selinux > 1) { + if (need_pid + need_uid + need_selinux + need_pidfd + need_gids > 1) { /* If we need more than one of the credentials, then use GetConnectionCredentials() */ @@ -572,7 +578,9 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - pid = p; + if (!pidref_is_set(&pidref)) + pidref = PIDREF_MAKE_FROM_PID(p); + if (mask & SD_BUS_CREDS_PID) { c->pid = p; c->mask |= SD_BUS_CREDS_PID; @@ -599,6 +607,69 @@ _public_ int sd_bus_get_name_creds( r = sd_bus_message_exit_container(reply); if (r < 0) return r; + } else if (need_pidfd && streq(m, "ProcessFD")) { + int fd; + + r = sd_bus_message_read(reply, "v", "h", &fd); + if (r < 0) + return r; + + pidref_done(&pidref); + r = pidref_set_pidfd(&pidref, fd); + if (r < 0) + return r; + + if (mask & SD_BUS_CREDS_PIDFD) { + fd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (fd < 0) + return -errno; + + close_and_replace(c->pidfd, fd); + c->mask |= SD_BUS_CREDS_PIDFD; + } + } else if (need_gids && streq(m, "UnixGroupIDs")) { + + /* Note that D-Bus actually only gives us a combined list of + * primary gid and supplementary gids. And we don't know + * which one the primary one is. We'll take the whole shebang + * hence and use it as the supplementary group list, and not + * initialize the primary gid field. This is slightly + * incorrect of course, but only slightly, as in effect if + * the primary gid is also listed in the supplementary gid + * it has zero effect. */ + + r = sd_bus_message_enter_container(reply, 'v', "au"); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "u"); + if (r < 0) + return r; + + for (;;) { + uint32_t u; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(c->supplementary_gids, c->n_supplementary_gids+1)) + return -ENOMEM; + + c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) u; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; } else { r = sd_bus_message_skip(reply, "v"); if (r < 0) @@ -614,7 +685,7 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - if (need_pid && pid == 0) + if (need_pid && !pidref_is_set(&pidref)) return -EPROTO; } @@ -642,7 +713,9 @@ _public_ int sd_bus_get_name_creds( if (r < 0) return r; - pid = u; + if (!pidref_is_set(&pidref)) + pidref = PIDREF_MAKE_FROM_PID(u); + if (mask & SD_BUS_CREDS_PID) { c->pid = u; c->mask |= SD_BUS_CREDS_PID; @@ -710,9 +783,11 @@ _public_ int sd_bus_get_name_creds( } } - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0 && r != -ESRCH) /* Return the error, but ignore ESRCH which just means the process is already gone */ - return r; + if (pidref_is_set(&pidref)) { + r = bus_creds_add_more(c, mask, &pidref, 0); + if (r < 0 && r != -ESRCH) /* Return the error, but ignore ESRCH which just means the process is already gone */ + return r; + } } if (creds) @@ -765,8 +840,8 @@ not_found: _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - bool do_label, do_groups, do_sockaddr_peer; - pid_t pid = 0; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + bool do_label, do_groups, do_sockaddr_peer, do_pidfd; int r; assert_return(bus, -EINVAL); @@ -786,9 +861,10 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r do_sockaddr_peer = bus->sockaddr_size_peer >= offsetof(struct sockaddr_un, sun_path) + 1 && bus->sockaddr_peer.sa.sa_family == AF_UNIX && bus->sockaddr_peer.un.sun_path[0] == 0; + do_pidfd = bus->pidfd >= 0 && (mask & SD_BUS_CREDS_PIDFD); /* Avoid allocating anything if we have no chance of returning useful data */ - if (!bus->ucred_valid && !do_label && !do_groups && !do_sockaddr_peer) + if (!bus->ucred_valid && !do_label && !do_groups && !do_sockaddr_peer && !do_pidfd) return -ENODATA; c = bus_creds_new(); @@ -797,8 +873,10 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r if (bus->ucred_valid) { if (pid_is_valid(bus->ucred.pid)) { - pid = c->pid = bus->ucred.pid; + c->pid = bus->ucred.pid; c->mask |= SD_BUS_CREDS_PID & mask; + + pidref = PIDREF_MAKE_FROM_PID(c->pid); } if (uid_is_valid(bus->ucred.uid)) { @@ -859,7 +937,20 @@ _public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **r } } - r = bus_creds_add_more(c, mask, pid, 0); + if (do_pidfd) { + c->pidfd = fcntl(bus->pidfd, F_DUPFD_CLOEXEC, 3); + if (c->pidfd < 0) + return -errno; + + pidref_done(&pidref); + r = pidref_set_pidfd(&pidref, bus->pidfd); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_PIDFD; + } + + r = bus_creds_add_more(c, mask, &pidref, 0); if (r < 0 && r != -ESRCH) /* If the process vanished, then don't complain, just return what we got */ return r; diff --git a/src/libsystemd/sd-bus/bus-convenience.c b/src/libsystemd/sd-bus/bus-convenience.c index 989e577..14d8073 100644 --- a/src/libsystemd/sd-bus/bus-convenience.c +++ b/src/libsystemd/sd-bus/bus-convenience.c @@ -640,8 +640,8 @@ _public_ int sd_bus_set_property( } _public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **ret) { + uint64_t missing; sd_bus_creds *c; - int r; assert_return(call, -EINVAL); assert_return(call->sealed, -EPERM); @@ -653,36 +653,22 @@ _public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_b return -ENOTCONN; c = sd_bus_message_get_creds(call); - - /* All data we need? */ - if (c && (mask & ~SD_BUS_CREDS_AUGMENT & ~c->mask) == 0) { + if (c) + missing = mask & ~SD_BUS_CREDS_AUGMENT & ~c->mask; + else + missing = mask & ~SD_BUS_CREDS_AUGMENT; + if (missing == 0) { /* All data we need? */ *ret = sd_bus_creds_ref(c); return 0; } - /* No data passed? Or not enough data passed to retrieve the missing bits? */ - if (!c || !(c->mask & SD_BUS_CREDS_PID)) { - /* We couldn't read anything from the call, let's try - * to get it from the sender or peer. */ - - if (call->sender) - /* There's a sender, but the creds are missing. */ - return sd_bus_get_name_creds(call->bus, call->sender, mask, ret); - else - /* There's no sender. For direct connections - * the credentials of the AF_UNIX peer matter, - * which may be queried via sd_bus_get_owner_creds(). */ - return sd_bus_get_owner_creds(call->bus, mask, ret); - } - - r = bus_creds_extend_by_pid(c, mask, ret); - if (r == -ESRCH) { - /* Process doesn't exist anymore? propagate the few things we have */ - *ret = sd_bus_creds_ref(c); - return 0; - } + /* There's a sender, use that */ + if (call->sender && call->bus->bus_client) + return sd_bus_get_name_creds(call->bus, call->sender, mask, ret); - return r; + /* There's no sender. For direct connections the credentials of the AF_UNIX peer matter, which may be + * queried via sd_bus_get_owner_creds(). */ + return sd_bus_get_owner_creds(call->bus, mask, ret); } _public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c index c6d8caa..adbdeaa 100644 --- a/src/libsystemd/sd-bus/bus-creds.c +++ b/src/libsystemd/sd-bus/bus-creds.c @@ -53,6 +53,8 @@ void bus_creds_done(sd_bus_creds *c) { * below. */ strv_free(c->cmdline_array); + + safe_close(c->pidfd); } _public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { @@ -129,46 +131,72 @@ _public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { sd_bus_creds* bus_creds_new(void) { sd_bus_creds *c; - c = new0(sd_bus_creds, 1); + c = new(sd_bus_creds, 1); if (!c) return NULL; - c->allocated = true; - c->n_ref = 1; + *c = (sd_bus_creds) { + .allocated = true, + .n_ref = 1, + SD_BUS_CREDS_INIT_FIELDS, + }; + return c; } -_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { +static int bus_creds_new_from_pidref(sd_bus_creds **ret, PidRef *pidref, uint64_t mask) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; int r; - assert_return(pid >= 0, -EINVAL); assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); assert_return(ret, -EINVAL); - if (pid == 0) - pid = getpid_cached(); - c = bus_creds_new(); if (!c) return -ENOMEM; - r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); + r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pidref, 0); if (r < 0) return r; - /* Check if the process existed at all, in case we haven't - * figured that out already */ - r = pid_is_alive(pid); + r = pidref_verify(pidref); if (r < 0) return r; - if (r == 0) - return -ESRCH; *ret = TAKE_PTR(c); return 0; } +_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + r = pidref_set_pid(&pidref, pid); + if (r < 0) + return r; + + return bus_creds_new_from_pidref(ret, &pidref, mask); +} + +_public_ int sd_bus_creds_new_from_pidfd(sd_bus_creds **ret, int pidfd, uint64_t mask) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + assert_return(pidfd >= 0, -EBADF); + + r = pidref_set_pidfd(&pidref, pidfd); + if (r < 0) + return r; + + return bus_creds_new_from_pidref(ret, &pidref, mask); +} + _public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { assert_return(c, -EINVAL); assert_return(uid, -EINVAL); @@ -280,6 +308,23 @@ _public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { return 0; } +_public_ int sd_bus_creds_get_pidfd_dup(sd_bus_creds *c, int *ret_fd) { + _cleanup_close_ int copy = -EBADF; + + assert_return(c, -EINVAL); + assert_return(ret_fd, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PIDFD)) + return -ENODATA; + + copy = fcntl(c->pidfd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + *ret_fd = TAKE_FD(copy); + return 0; +} + _public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { assert_return(c, -EINVAL); assert_return(ppid, -EINVAL); @@ -731,7 +776,7 @@ static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { return -ENOMEM; } - for (i = 0; i < sz; i ++) { + for (i = 0; i < sz; i++) { uint32_t v = 0; for (j = 0; j < 8; ++j) { @@ -750,7 +795,8 @@ static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { return 0; } -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, PidRef *pidref, pid_t tid) { + _cleanup_(pidref_done) PidRef pidref_buf = PIDREF_NULL; uint64_t missing; int r; @@ -761,12 +807,26 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { return 0; /* Try to retrieve PID from creds if it wasn't passed to us */ - if (pid > 0) { - c->pid = pid; + if (pidref_is_set(pidref)) { + if ((c->mask & SD_BUS_CREDS_PID) && c->pid != pidref->pid) /* Insist that things match if already set */ + return -EBUSY; + + c->pid = pidref->pid; c->mask |= SD_BUS_CREDS_PID; - } else if (c->mask & SD_BUS_CREDS_PID) - pid = c->pid; - else + } else if (c->mask & SD_BUS_CREDS_PIDFD) { + r = pidref_set_pidfd(&pidref_buf, c->pidfd); + if (r < 0) + return r; + + pidref = &pidref_buf; + + } else if (c->mask & SD_BUS_CREDS_PID) { + r = pidref_set_pid(&pidref_buf, c->pid); + if (r < 0) + return r; + + pidref = &pidref_buf; + } else /* Without pid we cannot do much... */ return 0; @@ -784,6 +844,14 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { c->mask |= SD_BUS_CREDS_TID; } + if ((missing & SD_BUS_CREDS_PIDFD) && pidref->fd >= 0) { + c->pidfd = fcntl(pidref->fd, F_DUPFD_CLOEXEC, 3); + if (c->pidfd < 0) + return -errno; + + c->mask |= SD_BUS_CREDS_PIDFD; + } + if (missing & (SD_BUS_CREDS_PPID | SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | @@ -794,13 +862,13 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { _cleanup_fclose_ FILE *f = NULL; const char *p; - p = procfs_file_alloca(pid, "status"); + p = procfs_file_alloca(pidref->pid, "status"); f = fopen(p, "re"); if (!f) { if (errno == ENOENT) return -ESRCH; - else if (!ERRNO_IS_PRIVILEGE(errno)) + if (!ERRNO_IS_PRIVILEGE(errno)) return -errno; } else { @@ -958,7 +1026,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { const char *p; - p = procfs_file_alloca(pid, "attr/current"); + p = procfs_file_alloca(pidref->pid, "attr/current"); r = read_one_line_file(p, &c->label); if (r < 0) { if (!IN_SET(r, -ENOENT, -EINVAL, -EPERM, -EACCES)) @@ -968,7 +1036,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_COMM) { - r = pid_get_comm(pid, &c->comm); + r = pid_get_comm(pidref->pid, &c->comm); if (r < 0) { if (!ERRNO_IS_PRIVILEGE(r)) return r; @@ -977,7 +1045,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_EXE) { - r = get_process_exe(pid, &c->exe); + r = get_process_exe(pidref->pid, &c->exe); if (r == -ESRCH) { /* Unfortunately we cannot really distinguish * the case here where the process does not @@ -998,7 +1066,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & SD_BUS_CREDS_CMDLINE) { const char *p; - p = procfs_file_alloca(pid, "cmdline"); + p = procfs_file_alloca(pidref->pid, "cmdline"); r = read_full_virtual_file(p, &c->cmdline, &c->cmdline_size); if (r == -ENOENT) return -ESRCH; @@ -1016,7 +1084,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { _cleanup_free_ char *p = NULL; - if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) + if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pidref->pid, tid) < 0) return -ENOMEM; r = read_one_line_file(p, &c->tid_comm); @@ -1032,7 +1100,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { if (!c->cgroup) { - r = cg_pid_get_path(NULL, pid, &c->cgroup); + r = cg_pid_get_path(NULL, pidref->pid, &c->cgroup); if (r < 0) { if (!ERRNO_IS_PRIVILEGE(r)) return r; @@ -1050,7 +1118,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { - r = audit_session_from_pid(pid, &c->audit_session_id); + r = audit_session_from_pid(pidref->pid, &c->audit_session_id); if (r == -ENODATA) { /* ENODATA means: no audit session id assigned */ c->audit_session_id = AUDIT_SESSION_INVALID; @@ -1063,7 +1131,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - r = audit_loginuid_from_pid(pid, &c->audit_login_uid); + r = audit_loginuid_from_pid(pidref->pid, &c->audit_login_uid); if (r == -ENODATA) { /* ENODATA means: no audit login uid assigned */ c->audit_login_uid = UID_INVALID; @@ -1076,7 +1144,7 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { } if (missing & SD_BUS_CREDS_TTY) { - r = get_ctty(pid, NULL, &c->tty); + r = get_ctty(pidref->pid, NULL, &c->tty); if (r == -ENXIO) { /* ENXIO means: process has no controlling TTY */ c->tty = NULL; @@ -1088,16 +1156,12 @@ int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { c->mask |= SD_BUS_CREDS_TTY; } - /* In case only the exe path was to be read we cannot distinguish the case where the exe path was - * unreadable because the process was a kernel thread, or when the process didn't exist at - * all. Hence, let's do a final check, to be sure. */ - r = pid_is_alive(pid); + r = pidref_verify(pidref); if (r < 0) return r; - if (r == 0) - return -ESRCH; - if (tid > 0 && tid != pid && pid_is_unwaited(tid) == 0) + /* Validate tid is still valid, too */ + if (tid > 0 && tid != pidref->pid && pid_is_unwaited(tid) == 0) return -ESRCH; c->augmented = missing & c->mask; @@ -1131,6 +1195,13 @@ int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) n->mask |= SD_BUS_CREDS_PID; } + if (c->mask & mask & SD_BUS_CREDS_PIDFD) { + n->pidfd = fcntl(c->pidfd, F_DUPFD_CLOEXEC, 3); + if (n->pidfd < 0) + return -errno; + n->mask |= SD_BUS_CREDS_PIDFD; + } + if (c->mask & mask & SD_BUS_CREDS_TID) { n->tid = c->tid; n->mask |= SD_BUS_CREDS_TID; diff --git a/src/libsystemd/sd-bus/bus-creds.h b/src/libsystemd/sd-bus/bus-creds.h index 7806d9e..f45de1c 100644 --- a/src/libsystemd/sd-bus/bus-creds.h +++ b/src/libsystemd/sd-bus/bus-creds.h @@ -5,6 +5,9 @@ #include "sd-bus.h" +#include "pidref.h" +#include "user-util.h" + struct sd_bus_creds { bool allocated; unsigned n_ref; @@ -27,6 +30,7 @@ struct sd_bus_creds { pid_t ppid; pid_t pid; pid_t tid; + int pidfd; char *comm; char *tid_comm; @@ -63,10 +67,22 @@ struct sd_bus_creds { char *description, *unescaped_description; }; +#define SD_BUS_CREDS_INIT_FIELDS \ + .uid = UID_INVALID, \ + .euid = UID_INVALID, \ + .suid = UID_INVALID, \ + .fsuid = UID_INVALID, \ + .gid = GID_INVALID, \ + .egid = GID_INVALID, \ + .sgid = GID_INVALID, \ + .fsgid = GID_INVALID, \ + .pidfd = -EBADF, \ + .audit_login_uid = UID_INVALID + sd_bus_creds* bus_creds_new(void); void bus_creds_done(sd_bus_creds *c); -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, PidRef *pidref, pid_t tid); int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c index 6d24f3b..aa46fec 100644 --- a/src/libsystemd/sd-bus/bus-dump.c +++ b/src/libsystemd/sd-bus/bus-dump.c @@ -355,6 +355,8 @@ int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { if (c->mask & SD_BUS_CREDS_PID) fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); + if (c->mask & SD_BUS_CREDS_PIDFD) + fprintf(f, "%sPIDFD=%syes%s", prefix, color, suffix); if (c->mask & SD_BUS_CREDS_TID) fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); if (c->mask & SD_BUS_CREDS_PPID) { diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h index 098a518..e0f4746 100644 --- a/src/libsystemd/sd-bus/bus-internal.h +++ b/src/libsystemd/sd-bus/bus-internal.h @@ -254,6 +254,9 @@ struct sd_bus { char *address; unsigned address_index; + uid_t connect_as_uid; + gid_t connect_as_gid; + int last_connect_error; enum bus_auth auth; @@ -269,6 +272,7 @@ struct sd_bus { size_t n_groups; union sockaddr_union sockaddr_peer; socklen_t sockaddr_size_peer; + int pidfd; uint64_t creds_mask; diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c index ab8b068..296f450 100644 --- a/src/libsystemd/sd-bus/bus-message.c +++ b/src/libsystemd/sd-bus/bus-message.c @@ -373,6 +373,7 @@ static int message_from_header( if (!m) return -ENOMEM; + m->creds = (sd_bus_creds) { SD_BUS_CREDS_INIT_FIELDS }; m->sealed = true; m->header = buffer; @@ -469,6 +470,7 @@ _public_ int sd_bus_message_new( return -ENOMEM; t->n_ref = 1; + t->creds = (sd_bus_creds) { SD_BUS_CREDS_INIT_FIELDS }; t->bus = sd_bus_ref(bus); t->header = (struct bus_header*) ((uint8_t*) t + ALIGN(sizeof(struct sd_bus_message))); t->header->endian = BUS_NATIVE_ENDIAN; @@ -627,7 +629,7 @@ static int message_new_reply( return r; } - t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); + t->dont_send = FLAGS_SET(call->header->flags, BUS_MESSAGE_NO_REPLY_EXPECTED); t->enforced_reply_signature = call->enforced_reply_signature; /* let's copy the sensitive flag over. Let's do that as a safety precaution to keep a transaction diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c index c25c40f..e528987 100644 --- a/src/libsystemd/sd-bus/bus-objects.c +++ b/src/libsystemd/sd-bus/bus-objects.c @@ -1701,7 +1701,7 @@ static bool names_are_valid(const char *signature, const char **names, names_fla if ((*flags & NAMES_FIRST_PART || *flags & NAMES_SINGLE_PART) && **names != '\0') *flags |= NAMES_PRESENT; - for (;*flags & NAMES_PRESENT;) { + while (*flags & NAMES_PRESENT) { size_t l; if (!*signature) diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c index 5ade8e9..07179e0 100644 --- a/src/libsystemd/sd-bus/bus-socket.c +++ b/src/libsystemd/sd-bus/bus-socket.c @@ -217,7 +217,7 @@ static int bus_socket_auth_verify_client(sd_bus *b) { /* And possibly check the third line, too */ if (b->accept_fd) { l = lines[i++]; - b->can_fds = !!memory_startswith(l, lines[i] - l, "AGREE_UNIX_FD"); + b->can_fds = memory_startswith(l, lines[i] - l, "AGREE_UNIX_FD"); } assert(i == n); @@ -266,7 +266,7 @@ static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - r = unhexmem(p, l, (void **) &token, &len); + r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len); if (r < 0) return 0; @@ -298,7 +298,7 @@ static int verify_external_token(sd_bus *b, const char *p, size_t l) { if (l % 2 != 0) return 0; - r = unhexmem(p, l, (void**) &token, &len); + r = unhexmem_full(p, l, /* secure = */ false, (void**) &token, &len); if (r < 0) return 0; @@ -503,11 +503,38 @@ static int bus_socket_write_auth(sd_bus *b) { if (b->prefer_writev) k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); else { + CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control = {}; + struct msghdr mh = { .msg_iov = b->auth_iovec + b->auth_index, .msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index, }; + if (uid_is_valid(b->connect_as_uid) || gid_is_valid(b->connect_as_gid)) { + + /* If we shall connect under some specific UID/GID, then synthesize an + * SCM_CREDENTIALS record accordingly. After all we want to adopt this UID/GID both + * for SO_PEERCRED (where we have to fork()) and SCM_CREDENTIALS (where we can just + * fake it via sendmsg()) */ + + struct ucred ucred = { + .pid = getpid_cached(), + .uid = uid_is_valid(b->connect_as_uid) ? b->connect_as_uid : getuid(), + .gid = gid_is_valid(b->connect_as_gid) ? b->connect_as_gid : getgid(), + }; + + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh); + *cmsg = (struct cmsghdr) { + .cmsg_level = SOL_SOCKET, + .cmsg_type = SCM_CREDENTIALS, + .cmsg_len = CMSG_LEN(sizeof(struct ucred)), + }; + + memcpy(CMSG_DATA(cmsg), &ucred, sizeof(struct ucred)); + } + k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); if (k < 0 && errno == ENOTSOCK) { b->prefer_writev = true; @@ -643,14 +670,20 @@ static void bus_get_peercred(sd_bus *b) { /* Get the SELinux context of the peer */ r = getpeersec(b->input_fd, &b->label); if (r < 0 && !IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)) - log_debug_errno(r, "Failed to determine peer security context: %m"); + log_debug_errno(r, "Failed to determine peer security context, ignoring: %m"); /* Get the list of auxiliary groups of the peer */ r = getpeergroups(b->input_fd, &b->groups); if (r >= 0) b->n_groups = (size_t) r; else if (!IN_SET(r, -EOPNOTSUPP, -ENOPROTOOPT)) - log_debug_errno(r, "Failed to determine peer's group list: %m"); + log_debug_errno(r, "Failed to determine peer's group list, ignoring: %m"); + + r = getpeerpidfd(b->input_fd); + if (r < 0) + log_debug_errno(r, "Failed to determine peer pidfd, ignoring: %m"); + else + close_and_replace(b->pidfd, r); /* Let's query the peers socket address, it might carry information such as the peer's comm or * description string */ @@ -943,6 +976,66 @@ static int bind_description(sd_bus *b, int fd, int family) { return 0; } +static int connect_as(int fd, const struct sockaddr *sa, socklen_t salen, uid_t uid, gid_t gid) { + _cleanup_(close_pairp) int pfd[2] = EBADF_PAIR; + ssize_t n; + int r; + + /* Shortcut if we are not supposed to drop privileges */ + if (!uid_is_valid(uid) && !gid_is_valid(gid)) + return RET_NERRNO(connect(fd, sa, salen)); + + /* This changes identity to the specified uid/gid and issues connect() as that. This is useful to + * make sure SO_PEERCRED reports the selected UID/GID rather than the usual one of the caller. */ + + if (pipe2(pfd, O_CLOEXEC) < 0) + return -errno; + + r = safe_fork("(sd-setresuid)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_WAIT, /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* child */ + + pfd[0] = safe_close(pfd[0]); + + r = RET_NERRNO(setgroups(0, NULL)); + if (r < 0) + goto child_finish; + + if (gid_is_valid(gid)) { + r = RET_NERRNO(setresgid(gid, gid, gid)); + if (r < 0) + goto child_finish; + } + + if (uid_is_valid(uid)) { + r = RET_NERRNO(setresuid(uid, uid, uid)); + if (r < 0) + goto child_finish; + } + + r = RET_NERRNO(connect(fd, sa, salen)); + if (r < 0) + goto child_finish; + + r = 0; + + child_finish: + n = write(pfd[1], &r, sizeof(r)); + if (n != sizeof(r)) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + n = read(pfd[0], &r, sizeof(r)); + if (n != sizeof(r)) + return -EIO; + + return r; +} + int bus_socket_connect(sd_bus *b) { bool inotify_done = false; int r; @@ -974,8 +1067,9 @@ int bus_socket_connect(sd_bus *b) { b->output_fd = b->input_fd; bus_socket_setup(b); - if (connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size) < 0) { - if (errno == EINPROGRESS) { + r = connect_as(b->input_fd, &b->sockaddr.sa, b->sockaddr_size, b->connect_as_uid, b->connect_as_gid); + if (r < 0) { + if (r == -EINPROGRESS) { /* If we have any inotify watches open, close them now, we don't need them anymore, as * we have successfully initiated a connection */ @@ -988,7 +1082,7 @@ int bus_socket_connect(sd_bus *b) { return 1; } - if (IN_SET(errno, ENOENT, ECONNREFUSED) && /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */ + if (IN_SET(r, -ENOENT, -ECONNREFUSED) && /* ENOENT → unix socket doesn't exist at all; ECONNREFUSED → unix socket stale */ b->watch_bind && b->sockaddr.sa.sa_family == AF_UNIX && b->sockaddr.un.sun_path[0] != 0) { @@ -1016,7 +1110,7 @@ int bus_socket_connect(sd_bus *b) { inotify_done = true; } else - return -errno; + return r; } else break; } diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c index f9c59a1..6f6fa2d 100644 --- a/src/libsystemd/sd-bus/bus-track.c +++ b/src/libsystemd/sd-bus/bus-track.c @@ -69,7 +69,7 @@ static void bus_track_add_to_queue(sd_bus_track *track) { return; /* still referenced? */ - if (hashmap_size(track->names) > 0) + if (!hashmap_isempty(track->names)) return; /* Nothing to call? */ diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c index 8befc97..1a642cb 100644 --- a/src/libsystemd/sd-bus/sd-bus.c +++ b/src/libsystemd/sd-bus/sd-bus.c @@ -30,6 +30,7 @@ #include "constants.h" #include "errno-util.h" #include "fd-util.h" +#include "format-util.h" #include "glyph-util.h" #include "hexdecoct.h" #include "hostname-util.h" @@ -151,6 +152,14 @@ void bus_close_inotify_fd(sd_bus *b) { b->n_inotify_watches = 0; } +static void bus_close_fds(sd_bus *b) { + assert(b); + + bus_close_io_fds(b); + bus_close_inotify_fd(b); + b->pidfd = safe_close(b->pidfd); +} + static void bus_reset_queues(sd_bus *b) { assert(b); @@ -191,8 +200,7 @@ static sd_bus* bus_free(sd_bus *b) { if (b->default_bus_ptr) *b->default_bus_ptr = NULL; - bus_close_io_fds(b); - bus_close_inotify_fd(b); + bus_close_fds(b); free(b->label); free(b->groups); @@ -256,7 +264,10 @@ _public_ int sd_bus_new(sd_bus **ret) { .n_groups = SIZE_MAX, .close_on_exit = true, .ucred = UCRED_INVALID, + .pidfd = -EBADF, .runtime_scope = _RUNTIME_SCOPE_INVALID, + .connect_as_uid = UID_INVALID, + .connect_as_gid = GID_INVALID, }; /* We guarantee that wqueue always has space for at least one entry */ @@ -321,7 +332,7 @@ _public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { assert_return(!bus->patch_sender, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->bus_client = !!b; + bus->bus_client = b; return 0; } @@ -331,7 +342,7 @@ _public_ int sd_bus_set_monitor(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->is_monitor = !!b; + bus->is_monitor = b; return 0; } @@ -341,7 +352,7 @@ _public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->accept_fd = !!b; + bus->accept_fd = b; return 0; } @@ -353,7 +364,7 @@ _public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { /* 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 */ - bus->attach_timestamp = !!b; + bus->attach_timestamp = b; return 0; } @@ -380,7 +391,7 @@ _public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->is_server = !!b; + bus->is_server = b; bus->server_id = server_id; return 0; } @@ -391,7 +402,7 @@ _public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->anonymous_auth = !!b; + bus->anonymous_auth = b; return 0; } @@ -401,7 +412,7 @@ _public_ int sd_bus_set_trusted(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->trusted = !!b; + bus->trusted = b; return 0; } @@ -419,7 +430,7 @@ _public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { assert_return(bus = bus_resolve(bus), -ENOPKG); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->allow_interactive_authorization = !!b; + bus->allow_interactive_authorization = b; return 0; } @@ -437,7 +448,7 @@ _public_ int sd_bus_set_watch_bind(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->watch_bind = !!b; + bus->watch_bind = b; return 0; } @@ -455,7 +466,7 @@ _public_ int sd_bus_set_connected_signal(sd_bus *bus, int b) { assert_return(bus->state == BUS_UNSET, -EPERM); assert_return(!bus_origin_changed(bus), -ECHILD); - bus->connected_signal = !!b; + bus->connected_signal = b; return 0; } @@ -640,7 +651,7 @@ int bus_start_running(sd_bus *bus) { static int parse_address_key(const char **p, const char *key, char **value) { _cleanup_free_ char *r = NULL; - size_t l, n = 0; + size_t n = 0; const char *a; assert(p); @@ -648,17 +659,14 @@ static int parse_address_key(const char **p, const char *key, char **value) { assert(value); if (key) { - l = strlen(key); - if (strncmp(*p, key, l) != 0) - return 0; - - if ((*p)[l] != '=') + a = startswith(*p, key); + if (!a || *a != '=') return 0; if (*value) return -EINVAL; - a = *p + l + 1; + a++; } else a = *p; @@ -717,7 +725,7 @@ static void skip_address_key(const char **p) { } static int parse_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL, *abstract = NULL; + _cleanup_free_ char *path = NULL, *abstract = NULL, *uids = NULL, *gids = NULL; size_t l; int r; @@ -745,6 +753,18 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) { else if (r > 0) continue; + r = parse_address_key(p, "uid", &uids); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "gid", &gids); + if (r < 0) + return r; + else if (r > 0) + continue; + skip_address_key(p); } @@ -781,6 +801,17 @@ static int parse_unix_address(sd_bus *b, const char **p, char **guid) { b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; } + if (uids) { + r = parse_uid(uids, &b->connect_as_uid); + if (r < 0) + return r; + } + if (gids) { + r = parse_gid(gids, &b->connect_as_gid); + if (r < 0) + return r; + } + b->is_local = true; return 0; @@ -1102,8 +1133,7 @@ static int bus_start_address(sd_bus *b) { assert(b); for (;;) { - bus_close_io_fds(b); - bus_close_inotify_fd(b); + bus_close_fds(b); bus_kill_exec(b); @@ -1486,9 +1516,15 @@ interpret_port_as_machine_old_syntax: return -ENOMEM; } - a = strjoin("unixexec:path=ssh,argv1=-xT", p ? ",argv2=-p,argv3=" : "", strempty(p), - ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e, - ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c); + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + _cleanup_free_ char *ssh_escaped = bus_address_escape(ssh); + if (!ssh_escaped) + return -ENOMEM; + + a = strjoin("unixexec:path=", ssh_escaped, ",argv1=-xT", + p ? ",argv2=-p,argv3=" : "", strempty(p), + ",argv", p ? "4" : "2", "=--,argv", p ? "5" : "3", "=", e, + ",argv", p ? "6" : "4", "=systemd-stdio-bridge", c); if (!a) return -ENOMEM; @@ -1668,10 +1704,7 @@ static int user_and_machine_equivalent(const char *user_and_machine) { return true; /* Otherwise, we have to figure out our user id and name, and compare things with that. */ - char buf[DECIMAL_STR_MAX(uid_t)]; - xsprintf(buf, UID_FMT, uid); - - f = startswith(user_and_machine, buf); + f = startswith(user_and_machine, FORMAT_UID(uid)); if (!f) { un = getusername_malloc(); if (!un) @@ -1775,8 +1808,7 @@ _public_ void sd_bus_close(sd_bus *bus) { * the bus object and the bus may be freed */ bus_reset_queues(bus); - bus_close_io_fds(bus); - bus_close_inotify_fd(bus); + bus_close_fds(bus); } _public_ sd_bus *sd_bus_close_unref(sd_bus *bus) { @@ -4123,13 +4155,13 @@ _public_ int sd_bus_path_decode_many(const char *path, const char *path_template for (template_pos = path_template; *template_pos; ) { const char *sep; - size_t length; + size_t length, path_length; char *label; /* verify everything until the next '%' matches verbatim */ sep = strchrnul(template_pos, '%'); length = sep - template_pos; - if (strncmp(path_pos, template_pos, length)) + if (!strneq(path_pos, template_pos, length)) return 0; path_pos += length; @@ -4150,8 +4182,8 @@ _public_ int sd_bus_path_decode_many(const char *path, const char *path_template /* verify the suffixes match */ sep = strchrnul(path_pos, '/'); - if (sep - path_pos < (ssize_t)length || - strncmp(sep - length, template_pos, length)) + path_length = sep - path_pos; + if (length > path_length || !strneq(sep - length, template_pos, length)) return 0; template_pos += length; /* skip over matched label */ diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c index da1340f..d06853b 100644 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ b/src/libsystemd/sd-bus/test-bus-chat.c @@ -432,8 +432,7 @@ static void* client2(void *p) { if (r < 0) log_debug("Failed to issue method call: %s", bus_error_message(&error, r)); else { - log_error("Slow call unexpectedly succeed."); - r = -ENOANO; + r = log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Slow call unexpectedly succeeded."); goto finish; } diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c index 3e14627..b569986 100644 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ b/src/libsystemd/sd-bus/test-bus-cleanup.c @@ -30,7 +30,7 @@ static void test_bus_fork(void) { 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_RETURN_EXPECTED_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); diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c index 13801be..7eb7a38 100644 --- a/src/libsystemd/sd-bus/test-bus-creds.c +++ b/src/libsystemd/sd-bus/test-bus-creds.c @@ -4,6 +4,7 @@ #include "bus-dump.h" #include "cgroup-util.h" +#include "errno-util.h" #include "tests.h" int main(int argc, char *argv[]) { @@ -12,7 +13,7 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - if (cg_unified() == -ENOMEDIUM) + if (IN_SET(cg_unified(), -ENOMEDIUM, -ENOENT)) return log_tests_skipped("/sys/fs/cgroup/ not available"); r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); @@ -24,11 +25,30 @@ int main(int argc, char *argv[]) { creds = sd_bus_creds_unref(creds); r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); - if (r != -EACCES) { + if (!ERRNO_IS_NEG_PRIVILEGE(r)) { assert_se(r >= 0); putchar('\n'); bus_creds_dump(creds, NULL, true); } + creds = sd_bus_creds_unref(creds); + + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + r = sd_bus_default_system(&bus); + if (r < 0) + log_warning_errno(r, "Unable to connect to system bus, skipping rest of test."); + else { + const char *unique; + + assert_se(sd_bus_get_unique_name(bus, &unique) >= 0); + + r = sd_bus_get_name_creds(bus, unique, _SD_BUS_CREDS_ALL, &creds); + log_full_errno(r < 0 ? LOG_ERR : LOG_DEBUG, r, "sd_bus_get_name_creds: %m"); + assert_se(r >= 0); + + putchar('\n'); + bus_creds_dump(creds, NULL, true); + } + return 0; } diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c index a55f3f9..91045c0 100644 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ b/src/libsystemd/sd-bus/test-bus-error.c @@ -213,8 +213,8 @@ TEST(errno_mapping_custom) { assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); - assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); - assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); } TEST(sd_bus_error_set_errnof) { diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c index 0044d33..92da627 100644 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ b/src/libsystemd/sd-bus/test-bus-marshal.c @@ -42,8 +42,8 @@ static void test_bus_path_encode(void) { assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); - assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); - assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_bus_path_encode("xxxx", "waldo", &c) < 0); + ASSERT_RETURN_EXPECTED_SE(sd_bus_path_encode("/foo/", "waldo", &c) < 0); assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c index ccdd0d5..2847ba8 100644 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ b/src/libsystemd/sd-bus/test-bus-objects.c @@ -494,10 +494,9 @@ static int client(struct context *c) { } assert_se(sd_bus_message_exit_container(reply) >= 0); - if (streq(path, "/value/a")) { + if (streq(path, "/value/a")) /* ObjectManager must be here */ assert_se(found_object_manager_interface); - } } else assert_se(sd_bus_message_skip(reply, "a{sa{sv}}") >= 0); diff --git a/src/libsystemd/sd-bus/test-bus-peersockaddr.c b/src/libsystemd/sd-bus/test-bus-peersockaddr.c index 79556e8..a7bba17 100644 --- a/src/libsystemd/sd-bus/test-bus-peersockaddr.c +++ b/src/libsystemd/sd-bus/test-bus-peersockaddr.c @@ -5,10 +5,39 @@ #include "sd-bus.h" +#include "bus-dump.h" +#include "bus-util.h" #include "fd-util.h" #include "process-util.h" #include "socket-util.h" +#include "sort-util.h" #include "tests.h" +#include "user-util.h" + +static bool gid_list_contained(const gid_t *a, size_t n, const gid_t *b, size_t m) { + assert_se(a || n == 0); + assert_se(b || m == 0); + + /* Checks if every entry in a[] is also in b[] */ + + for (size_t i = 0; i < n; i++) { + size_t j; + + for (j = 0; j < m; j++) + if (a[i] == b[j]) + break; + + if (j >= m) + return false; + } + + return true; +} + +static bool gid_list_same(const gid_t *a, size_t n, const gid_t *b, size_t m) { + return gid_list_contained(a, n, b, m) && + gid_list_contained(b, m, a, n); +} static void *server(void *p) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; @@ -27,11 +56,13 @@ static void *server(void *p) { assert_se(sd_bus_set_fd(bus, fd, fd) >= 0); TAKE_FD(fd); assert_se(sd_bus_set_server(bus, true, id) >= 0); - assert_se(sd_bus_negotiate_creds(bus, 1, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION) >= 0); + assert_se(sd_bus_negotiate_creds(bus, 1, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_SUPPLEMENTARY_GIDS) >= 0); assert_se(sd_bus_start(bus) >= 0); - assert_se(sd_bus_get_owner_creds(bus, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION, &c) >= 0); + assert_se(sd_bus_get_owner_creds(bus, SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_PIDFD|SD_BUS_CREDS_SUPPLEMENTARY_GIDS, &c) >= 0); + + bus_creds_dump(c, /* f= */ NULL, /* terse= */ false); uid_t u; assert_se(sd_bus_creds_get_euid(c, &u) >= 0); @@ -45,6 +76,26 @@ static void *server(void *p) { assert_se(sd_bus_creds_get_pid(c, &pid) >= 0); assert_se(pid == getpid_cached()); + int pidfd = -EBADF; + if (sd_bus_creds_get_pidfd_dup(c, &pidfd) >= 0) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_set_pidfd_take(&pidref, pidfd) >= 0); + assert_se(pidref.pid == getpid_cached()); + } + + const gid_t *gl = NULL; + int n; + n = sd_bus_creds_get_supplementary_gids(c, &gl); + + if (n >= 0) { + _cleanup_free_ gid_t *gg = NULL; + r = getgroups_alloc(&gg); + assert_se(r >= 0); + + assert_se(gid_list_same(gl, n, gg, r)); + } + const char *comm; assert_se(sd_bus_creds_get_comm(c, &comm) >= 0); assert_se(pid_get_comm(0, &our_comm) >= 0); diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c index 6a60cde..4945d82 100644 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ b/src/libsystemd/sd-daemon/sd-daemon.c @@ -76,7 +76,7 @@ _public_ int sd_listen_fds(int unset_environment) { goto finish; } - for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { r = fd_cloexec(fd, true); if (r < 0) goto finish; @@ -456,6 +456,7 @@ static int pid_notify_with_fds_internal( const char *state, const int *fds, unsigned n_fds) { + SocketAddress address; struct iovec iovec; struct msghdr msghdr = { @@ -464,19 +465,12 @@ static int pid_notify_with_fds_internal( .msg_name = &address.sockaddr, }; _cleanup_close_ int fd = -EBADF; - struct cmsghdr *cmsg = NULL; - const char *e; - bool send_ucred; - ssize_t n; int type, r; - if (!state) - return -EINVAL; - - if (n_fds > 0 && !fds) - return -EINVAL; + assert_return(state, -EINVAL); + assert_return(fds || n_fds == 0, -EINVAL); - e = getenv("NOTIFY_SOCKET"); + const char *e = getenv("NOTIFY_SOCKET"); if (!e) return 0; @@ -530,12 +524,14 @@ static int pid_notify_with_fds_internal( iovec = IOVEC_MAKE_STRING(state); - send_ucred = + bool send_ucred = (pid != 0 && pid != getpid_cached()) || getuid() != geteuid() || getgid() != getegid(); if (n_fds > 0 || send_ucred) { + struct cmsghdr *cmsg; + /* CMSG_SPACE(0) may return value different than zero, which results in miscalculated controllen. */ msghdr.msg_controllen = (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + @@ -569,6 +565,8 @@ static int pid_notify_with_fds_internal( } } + ssize_t n; + do { /* First try with fake ucred data, as requested */ n = sendmsg(fd, &msghdr, MSG_NOSIGNAL); @@ -594,6 +592,19 @@ static int pid_notify_with_fds_internal( } } while (!iovec_increment(msghdr.msg_iov, msghdr.msg_iovlen, n)); + if (address.sockaddr.sa.sa_family == AF_VSOCK && IN_SET(type, SOCK_STREAM, SOCK_SEQPACKET)) { + /* For AF_VSOCK, we need to close the socket to signal the end of the message. */ + if (shutdown(fd, SHUT_WR) < 0) + return log_debug_errno(errno, "Failed to shutdown notify socket: %m"); + + char c; + n = recv(fd, &c, sizeof(c), MSG_NOSIGNAL); + if (n < 0) + return log_debug_errno(errno, "Failed to wait for EOF on notify socket: %m"); + if (n > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpectedly received data on notify socket."); + } + return 1; } @@ -650,7 +661,7 @@ _public_ int sd_notify(int unset_environment, const char *state) { _public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; if (format) { va_list ap; @@ -659,16 +670,20 @@ _public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify(pid, unset_environment, p); + k = sd_pid_notify(pid, unset_environment, p); + return r < 0 ? r : k; } _public_ int sd_notifyf(int unset_environment, const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; if (format) { va_list ap; @@ -677,11 +692,15 @@ _public_ int sd_notifyf(int unset_environment, const char *format, ...) { r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify(0, unset_environment, p); + k = sd_pid_notify(0, unset_environment, p); + return r < 0 ? r : k; } _public_ int sd_pid_notifyf_with_fds( @@ -691,27 +710,31 @@ _public_ int sd_pid_notifyf_with_fds( const char *format, ...) { _cleanup_free_ char *p = NULL; - int r; + int r = 0, k; /* 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; + r = -E2BIG; - if (format) { + else if (format) { va_list ap; va_start(ap, format); r = vasprintf(&p, format, ap); va_end(ap); - if (r < 0 || !p) - return -ENOMEM; + if (r < 0 || !p) { + r = -ENOMEM; + p = mfree(p); /* If vasprintf failed, do not use the string, + * even if something was returned. */ + } } - return sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds); + k = sd_pid_notify_with_fds(pid, unset_environment, p, fds, n_fds); + return r < 0 ? r : k; } _public_ int sd_booted(void) { diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c index 15c5c42..71ab3d8 100644 --- a/src/libsystemd/sd-device/device-enumerator.c +++ b/src/libsystemd/sd-device/device-enumerator.c @@ -370,12 +370,8 @@ static int enumerator_sort_devices(sd_device_enumerator *enumerator) { HASHMAP_FOREACH_KEY(device, syspath, enumerator->devices_by_syspath) { _cleanup_free_ char *p = NULL; - const char *subsys; - if (sd_device_get_subsystem(device, &subsys) < 0) - continue; - - if (!streq(subsys, *prioritized_subsystem)) + if (!device_in_subsystem(device, *prioritized_subsystem)) continue; devices[n++] = sd_device_ref(device); @@ -662,10 +658,8 @@ static int enumerator_add_parent_devices( continue; r = device_enumerator_add_device(enumerator, device); - if (r < 0) + if (r <= 0) /* r == 0 means the device already exists, then no need to go further up. */ return r; - if (r == 0) /* Exists already? Then no need to go further up. */ - return 0; } } diff --git a/src/libsystemd/sd-device/device-monitor.c b/src/libsystemd/sd-device/device-monitor.c index bb4f9bd..a7ef03a 100644 --- a/src/libsystemd/sd-device/device-monitor.c +++ b/src/libsystemd/sd-device/device-monitor.c @@ -47,7 +47,7 @@ struct sd_device_monitor { union sockaddr_union snl_trusted_sender; bool bound; - UidRange *mapped_userns_uid_range; + UIDRange *mapped_userns_uid_range; Hashmap *subsystem_filter; Set *tag_filter; @@ -402,8 +402,7 @@ static sd_device_monitor *device_monitor_free(sd_device_monitor *m) { DEFINE_PUBLIC_TRIVIAL_REF_UNREF_FUNC(sd_device_monitor, sd_device_monitor, device_monitor_free); static int check_subsystem_filter(sd_device_monitor *m, sd_device *device) { - const char *s, *subsystem, *d, *devtype = NULL; - int r; + const char *s, *d; assert(m); assert(device); @@ -411,20 +410,14 @@ static int check_subsystem_filter(sd_device_monitor *m, sd_device *device) { if (hashmap_isempty(m->subsystem_filter)) return true; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - - r = sd_device_get_devtype(device, &devtype); - if (r < 0 && r != -ENOENT) - return r; - HASHMAP_FOREACH_KEY(d, s, m->subsystem_filter) { - if (!streq(s, subsystem)) + if (!device_in_subsystem(device, s)) continue; - if (!d || streq_ptr(d, devtype)) - return true; + if (d && !device_is_devtype(device, d)) + continue; + + return true; } return false; @@ -480,7 +473,7 @@ static bool check_sender_uid(sd_device_monitor *m, uid_t uid) { return true; if (!m->mapped_userns_uid_range) { - r = uid_range_load_userns(&m->mapped_userns_uid_range, NULL); + r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &m->mapped_userns_uid_range); if (r < 0) log_monitor_errno(m, r, "Failed to load UID ranges mapped to the current user namespace, ignoring: %m"); } diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c index 0edabfb..cd85ec9 100644 --- a/src/libsystemd/sd-device/device-private.c +++ b/src/libsystemd/sd-device/device-private.c @@ -768,55 +768,96 @@ static bool device_has_info(sd_device *device) { return false; } +bool device_should_have_db(sd_device *device) { + assert(device); + + if (device_has_info(device)) + return true; + + if (major(device->devnum) != 0) + return true; + + if (device->ifindex != 0) + return true; + + return false; +} + void device_set_db_persist(sd_device *device) { assert(device); device->db_persist = true; } -int device_update_db(sd_device *device) { +static int device_get_db_path(sd_device *device, char **ret) { const char *id; char *path; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_(unlink_and_freep) char *path_tmp = NULL; - bool has_info; int r; assert(device); - - has_info = device_has_info(device); + assert(ret); r = device_get_device_id(device, &id); if (r < 0) return r; - path = strjoina("/run/udev/data/", id); + path = path_join("/run/udev/data/", id); + if (!path) + return -ENOMEM; + + *ret = path; + return 0; +} + +int device_has_db(sd_device *device) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return access(path, F_OK) >= 0; +} + +int device_update_db(sd_device *device) { + _cleanup_(unlink_and_freep) char *path = NULL, *path_tmp = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(device); /* do not store anything for otherwise empty devices */ - if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { - if (unlink(path) < 0 && errno != ENOENT) - return -errno; + if (!device_should_have_db(device)) + return device_delete_db(device); - return 0; - } + r = device_get_db_path(device, &path); + if (r < 0) + return r; /* write a database file */ r = mkdir_parents(path, 0755); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to create parent directories of '%s': %m", + path); r = fopen_temporary(path, &f, &path_tmp); if (r < 0) - return r; + return log_device_debug_errno(device, r, + "sd-device: Failed to create temporary file for database file '%s': %m", + path); /* set 'sticky' bit to indicate that we should not clean the database when we transition from initrd * to the real root */ - if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) { - r = -errno; - goto fail; - } + if (fchmod(fileno(f), device->db_persist ? 01644 : 0644) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to chmod temporary database file '%s': %m", + path_tmp); - if (has_info) { + if (device_has_info(device)) { const char *property, *value, *ct; if (major(device->devnum) > 0) { @@ -846,45 +887,55 @@ int device_update_db(sd_device *device) { r = fflush_and_check(f); if (r < 0) - goto fail; + return log_device_debug_errno(device, r, + "sd-device: Failed to flush temporary database file '%s': %m", + path_tmp); - if (rename(path_tmp, path) < 0) { - r = -errno; - goto fail; - } + if (rename(path_tmp, path) < 0) + return log_device_debug_errno(device, errno, + "sd-device: Failed to rename temporary database file '%s' to '%s': %m", + path_tmp, path); - path_tmp = mfree(path_tmp); + log_device_debug(device, "sd-device: Created database file '%s' for '%s'.", path, device->devpath); - log_device_debug(device, "sd-device: Created %s file '%s' for '%s'", has_info ? "db" : "empty", - path, device->devpath); + path_tmp = mfree(path_tmp); + path = mfree(path); return 0; - -fail: - (void) unlink(path); - - return log_device_debug_errno(device, r, "sd-device: Failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); } int device_delete_db(sd_device *device) { - const char *id; - char *path; + _cleanup_free_ char *path = NULL; int r; assert(device); - r = device_get_device_id(device, &id); + r = device_get_db_path(device, &path); if (r < 0) return r; - path = strjoina("/run/udev/data/", id); - if (unlink(path) < 0 && errno != ENOENT) return -errno; return 0; } +int device_read_db_internal(sd_device *device, bool force) { + _cleanup_free_ char *path = NULL; + int r; + + assert(device); + + if (device->db_loaded || (!force && device->sealed)) + return 0; + + r = device_get_db_path(device, &path); + if (r < 0) + return r; + + return device_read_db_internal_filename(device, path); +} + static const char* const device_action_table[_SD_DEVICE_ACTION_MAX] = { [SD_DEVICE_ADD] = "add", [SD_DEVICE_REMOVE] = "remove", diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h index e8a6d52..85e8609 100644 --- a/src/libsystemd/sd-device/device-private.h +++ b/src/libsystemd/sd-device/device-private.h @@ -24,6 +24,7 @@ int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, uns static inline int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value) { return device_get_sysattr_unsigned_full(device, sysattr, 0, ret_value); } +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value); int device_get_sysattr_bool(sd_device *device, const char *sysattr); int device_get_device_id(sd_device *device, const char **ret); int device_get_devlink_priority(sd_device *device, int *ret); @@ -61,6 +62,8 @@ int device_get_properties_strv(sd_device *device, char ***ret); int device_clone_with_db(sd_device *device, sd_device **ret); int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +bool device_should_have_db(sd_device *device); +int device_has_db(sd_device *device); int device_update_db(sd_device *device); int device_delete_db(sd_device *device); int device_read_db_internal_filename(sd_device *device, const char *filename); /* For fuzzer */ diff --git a/src/libsystemd/sd-device/device-util.c b/src/libsystemd/sd-device/device-util.c index 529eff2..123629c 100644 --- a/src/libsystemd/sd-device/device-util.c +++ b/src/libsystemd/sd-device/device-util.c @@ -9,7 +9,6 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - _cleanup_free_ char *s = NULL; const char *devname; int r; @@ -26,15 +25,10 @@ int devname_from_devnum(mode_t mode, dev_t devnum, char **ret) { if (r < 0) return r; - s = strdup(devname); - if (!s) - return -ENOMEM; - - *ret = TAKE_PTR(s); - return 0; + return strdup_to(ret, devname); } -int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret) { +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; _cleanup_close_ int fd = -EBADF; int r; @@ -47,19 +41,16 @@ int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret) { if (fd < 0) return fd; - if (ret) { + if (ret_devname) { const char *devname; - char *s; r = sd_device_get_devname(dev, &devname); if (r < 0) return r; - s = strdup(devname); - if (!s) - return -ENOMEM; - - *ret = s; + r = strdup_to(ret_devname, devname); + if (r < 0) + return r; } return TAKE_FD(fd); @@ -139,3 +130,21 @@ char** device_make_log_fields(sd_device *device) { return TAKE_PTR(strv); } + +bool device_in_subsystem(sd_device *device, const char *subsystem) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_subsystem(device, &s); + return streq_ptr(s, subsystem); +} + +bool device_is_devtype(sd_device *device, const char *devtype) { + const char *s = NULL; + + assert(device); + + (void) sd_device_get_devtype(device, &s); + return streq_ptr(s, devtype); +} diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h index bf86ddc..b17993d 100644 --- a/src/libsystemd/sd-device/device-util.h +++ b/src/libsystemd/sd-device/device-util.h @@ -10,6 +10,7 @@ #include "alloc-util.h" #include "log.h" #include "macro.h" +#include "strv.h" #define device_unref_and_replace(a, b) \ unref_and_replace_full(a, b, sd_device_ref, sd_device_unref) @@ -99,6 +100,16 @@ static inline int devname_from_stat_rdev(const struct stat *st, char **ret) { assert(st); return devname_from_devnum(st->st_mode, st->st_rdev, ret); } -int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret); +int device_open_from_devnum(mode_t mode, dev_t devnum, int flags, char **ret_devname); char** device_make_log_fields(sd_device *device); + +bool device_in_subsystem(sd_device *device, const char *subsystem); +bool device_is_devtype(sd_device *device, const char *devtype); + +static inline bool device_property_can_set(const char *property) { + return property && + !STR_IN_SET(property, + "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER", + "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS"); +} diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c index 01e66b4..d8d1518 100644 --- a/src/libsystemd/sd-device/sd-device.c +++ b/src/libsystemd/sd-device/sd-device.c @@ -214,7 +214,7 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { /* Only operate on sysfs, i.e. refuse going down into /sys/fs/cgroup/ or similar places where * things are not arranged as kobjects in kernel, and hence don't necessarily have * kobject/attribute structure. */ - r = getenv_bool_secure("SYSTEMD_DEVICE_VERIFY_SYSFS"); + r = secure_getenv_bool("SYSTEMD_DEVICE_VERIFY_SYSFS"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DEVICE_VERIFY_SYSFS value: %m"); if (r != 0) { @@ -283,7 +283,7 @@ _public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; _cleanup_free_ char *syspath = NULL; - const char *t, *subsystem = NULL; + const char *t; dev_t n; int r; @@ -314,10 +314,7 @@ int device_new_from_mode_and_devnum(sd_device **ret, mode_t mode, dev_t devnum) if (n != devnum) return -ENXIO; - r = sd_device_get_subsystem(dev, &subsystem); - if (r < 0 && r != -ENOENT) - return r; - if (streq_ptr(subsystem, "block") != !!S_ISBLK(mode)) + if (device_in_subsystem(dev, "block") != !!S_ISBLK(mode)) return -ENXIO; *ret = TAKE_PTR(dev); @@ -348,17 +345,11 @@ _public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { assert_return(ret, -EINVAL); assert_return(ifname, -EINVAL); - r = parse_ifindex(ifname); - if (r > 0) - return sd_device_new_from_ifindex(ret, r); - - if (ifname_valid(ifname)) { - r = device_new_from_main_ifname(ret, ifname); - if (r >= 0) - return r; - } + r = device_new_from_main_ifname(ret, ifname); + if (r >= 0) + return r; - r = rtnl_resolve_link_alternative_name(NULL, ifname, &main_name); + r = rtnl_resolve_ifname_full(NULL, RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, ifname, &main_name, NULL); if (r < 0) return r; @@ -367,14 +358,15 @@ _public_ int sd_device_new_from_ifname(sd_device **ret, const char *ifname) { _public_ int sd_device_new_from_ifindex(sd_device **ret, int ifindex) { _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - char ifname[IF_NAMESIZE]; + _cleanup_free_ char *ifname = NULL; int r, i; assert_return(ret, -EINVAL); assert_return(ifindex > 0, -EINVAL); - if (format_ifname(ifindex, ifname) < 0) - return -ENODEV; + r = rtnl_get_ifname_full(NULL, ifindex, &ifname, NULL); + if (r < 0) + return r; r = device_new_from_main_ifname(&dev, ifname); if (r < 0) @@ -1222,37 +1214,27 @@ _public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { return !!device->devtype; } -_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { - sd_device *parent = NULL; +_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *device, const char *subsystem, const char *devtype, sd_device **ret) { int r; - assert_return(child, -EINVAL); + assert_return(device, -EINVAL); assert_return(subsystem, -EINVAL); - r = sd_device_get_parent(child, &parent); - while (r >= 0) { - const char *parent_subsystem = NULL; + for (;;) { + r = sd_device_get_parent(device, &device); + if (r < 0) + return r; - (void) sd_device_get_subsystem(parent, &parent_subsystem); - if (streq_ptr(parent_subsystem, subsystem)) { - const char *parent_devtype = NULL; + if (!device_in_subsystem(device, subsystem)) + continue; - if (!devtype) - break; + if (devtype && !device_is_devtype(device, devtype)) + continue; - (void) sd_device_get_devtype(parent, &parent_devtype); - if (streq_ptr(parent_devtype, devtype)) - break; - } - r = sd_device_get_parent(parent, &parent); + if (ret) + *ret = device; + return 0; } - - if (r < 0) - return r; - - if (ret) - *ret = parent; - return 0; } _public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { @@ -1778,24 +1760,6 @@ int device_read_db_internal_filename(sd_device *device, const char *filename) { return 0; } -int device_read_db_internal(sd_device *device, bool force) { - const char *id, *path; - int r; - - assert(device); - - if (device->db_loaded || (!force && device->sealed)) - return 0; - - r = device_get_device_id(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - return device_read_db_internal_filename(device, path); -} - _public_ int sd_device_get_is_initialized(sd_device *device) { int r; @@ -2454,6 +2418,25 @@ int device_get_sysattr_unsigned_full(sd_device *device, const char *sysattr, uns return v > 0; } +int device_get_sysattr_u32(sd_device *device, const char *sysattr, uint32_t *ret_value) { + const char *value; + int r; + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return r; + + uint32_t v; + r = safe_atou32(value, &v); + if (r < 0) + return log_device_debug_errno(device, r, "Failed to parse '%s' attribute: %m", sysattr); + + if (ret_value) + *ret_value = v; + /* We return "true" if the value is positive. */ + return v > 0; +} + int device_get_sysattr_bool(sd_device *device, const char *sysattr) { const char *value; int r; @@ -2506,7 +2489,7 @@ _public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, /* drop trailing newlines */ while (len > 0 && strchr(NEWLINE, _value[len - 1])) - len --; + len--; /* value length is limited to 4k */ if (len > 4096) @@ -2609,7 +2592,7 @@ _public_ int sd_device_trigger_with_uuid( _public_ int sd_device_open(sd_device *device, int flags) { _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; - const char *devname, *subsystem = NULL; + const char *devname; uint64_t q, diskseq = 0; struct stat st; dev_t devnum; @@ -2630,10 +2613,6 @@ _public_ int sd_device_open(sd_device *device, int flags) { if (r < 0) return r; - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0 && r != -ENOENT) - return r; - fd = open(devname, FLAGS_SET(flags, O_PATH) ? flags : O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd < 0) return -errno; @@ -2644,7 +2623,7 @@ _public_ int sd_device_open(sd_device *device, int flags) { if (st.st_rdev != devnum) return -ENXIO; - if (streq_ptr(subsystem, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) + if (device_in_subsystem(device, "block") ? !S_ISBLK(st.st_mode) : !S_ISCHR(st.st_mode)) return -ENXIO; /* If flags has O_PATH, then we cannot check diskseq. Let's return earlier. */ diff --git a/src/libsystemd/sd-device/test-device-util.c b/src/libsystemd/sd-device/test-device-util.c index bc8ab66..f7c9deb 100644 --- a/src/libsystemd/sd-device/test-device-util.c +++ b/src/libsystemd/sd-device/test-device-util.c @@ -1,23 +1,91 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "device-util.h" +#include "mountpoint-util.h" #include "tests.h" TEST(log_device_full) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; int r; + (void) sd_device_new_from_subsystem_sysname(&dev, "net", "lo"); + for (int level = LOG_ERR; level <= LOG_DEBUG; level++) { - log_device_full(NULL, level, "test level=%d: %m", level); + log_device_full(dev, level, "test level=%d: %m", level); - r = log_device_full_errno(NULL, level, EUCLEAN, "test level=%d errno=EUCLEAN: %m", level); + r = log_device_full_errno(dev, level, EUCLEAN, "test level=%d errno=EUCLEAN: %m", level); assert_se(r == -EUCLEAN); - r = log_device_full_errno(NULL, level, 0, "test level=%d errno=0: %m", level); + r = log_device_full_errno(dev, level, 0, "test level=%d errno=0: %m", level); assert_se(r == 0); - r = log_device_full_errno(NULL, level, SYNTHETIC_ERRNO(ENODATA), "test level=%d errno=S(ENODATA): %m", level); + r = log_device_full_errno(dev, level, SYNTHETIC_ERRNO(ENODATA), "test level=%d errno=S(ENODATA).", level); assert_se(r == -ENODATA); } } -DEFINE_TEST_MAIN(LOG_INFO); +TEST(device_in_subsystem) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + int r; + + r = sd_device_new_from_subsystem_sysname(&dev, "net", "lo"); + if (r == -ENODEV) + return (void) log_tests_skipped("net/lo does not exist"); + assert_se(r >= 0); + + assert_se(device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(!device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(!device_in_subsystem(dev, NULL)); + + dev = sd_device_unref(dev); + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net") >= 0); + assert_se(!device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(!device_in_subsystem(dev, NULL)); + + dev = sd_device_unref(dev); + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class") >= 0); + assert_se(!device_in_subsystem(dev, "net")); + assert_se(!device_in_subsystem(dev, "disk")); + assert_se(!device_in_subsystem(dev, "subsystem")); + assert_se(!device_in_subsystem(dev, "")); + assert_se(device_in_subsystem(dev, NULL)); +} + +TEST(device_is_devtype) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + + assert_se(sd_device_enumerator_new(&e) >= 0); + assert_se(sd_device_enumerator_add_match_subsystem(e, "disk", true) >= 0); + + FOREACH_DEVICE(e, d) { + const char *t; + + assert_se(sd_device_get_devtype(d, &t) >= 0); + assert_se(device_is_devtype(d, t)); + assert_se(!device_is_devtype(d, "hoge")); + assert_se(!device_is_devtype(d, "")); + assert_se(!device_is_devtype(d, NULL)); + } + + assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net") >= 0); + assert_se(!device_is_devtype(dev, "hoge")); + assert_se(!device_is_devtype(dev, "")); + assert_se(device_is_devtype(dev, NULL)); +} + +static int intro(void) { + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/libsystemd/sd-device/test-sd-device-monitor.c b/src/libsystemd/sd-device/test-sd-device-monitor.c index e124e00..3dbf987 100644 --- a/src/libsystemd/sd-device/test-sd-device-monitor.c +++ b/src/libsystemd/sd-device/test-sd-device-monitor.c @@ -10,6 +10,7 @@ #include "device-private.h" #include "device-util.h" #include "macro.h" +#include "mountpoint-util.h" #include "path-util.h" #include "stat-util.h" #include "string-util.h" @@ -298,6 +299,9 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return log_tests_skipped("not root"); + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + if (path_is_read_only_fs("/sys") > 0) return log_tests_skipped("Running in container"); diff --git a/src/libsystemd/sd-device/test-sd-device-thread.c b/src/libsystemd/sd-device/test-sd-device-thread.c index c99d179..539dabd 100644 --- a/src/libsystemd/sd-device/test-sd-device-thread.c +++ b/src/libsystemd/sd-device/test-sd-device-thread.c @@ -8,6 +8,7 @@ #include "sd-device.h" #include "device-util.h" +#include "tests.h" #define handle_error_errno(error, msg) \ ({ \ @@ -30,6 +31,8 @@ int main(int argc, char *argv[]) { int r; r = sd_device_new_from_syspath(&loopback, "/sys/class/net/lo"); + if (r == -ENODEV) + return log_tests_skipped("Loopback device not found"); if (r < 0) return handle_error_errno(r, "Failed to create loopback device object"); diff --git a/src/libsystemd/sd-device/test-sd-device.c b/src/libsystemd/sd-device/test-sd-device.c index bce99b5..9fde1a0 100644 --- a/src/libsystemd/sd-device/test-sd-device.c +++ b/src/libsystemd/sd-device/test-sd-device.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "fd-util.h" #include "hashmap.h" +#include "mountpoint-util.h" #include "nulstr-util.h" #include "path-util.h" #include "rm-rf.h" @@ -675,4 +676,11 @@ TEST(devname_from_devnum) { } } -DEFINE_TEST_MAIN(LOG_INFO); +static int intro(void) { + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_INFO, intro); diff --git a/src/libsystemd/sd-event/event-source.h b/src/libsystemd/sd-event/event-source.h index f4e38d7..d05bcf0 100644 --- a/src/libsystemd/sd-event/event-source.h +++ b/src/libsystemd/sd-event/event-source.h @@ -189,6 +189,9 @@ struct inode_data { * iteration. */ int fd; + /* The path that the fd points to. The field is optional. */ + char *path; + /* The inotify "watch descriptor" */ int wd; diff --git a/src/libsystemd/sd-event/event-util.c b/src/libsystemd/sd-event/event-util.c index a310122..862455e 100644 --- a/src/libsystemd/sd-event/event-util.c +++ b/src/libsystemd/sd-event/event-util.c @@ -151,3 +151,29 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle return 0; } + +int event_add_child_pidref( + sd_event *e, + sd_event_source **s, + const PidRef *pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + if (!pidref_is_set(pid)) + return -ESRCH; + + if (pid->fd >= 0) + return sd_event_add_child_pidfd(e, s, pid->fd, options, callback, userdata); + + return sd_event_add_child(e, s, pid->pid, options, callback, userdata); +} + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) { + assert(e); + assert(ts); + + assert_se(sd_event_now(e, CLOCK_REALTIME, &ts->realtime) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &ts->monotonic) >= 0); + return ts; +} diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h index c185584..7002ca3 100644 --- a/src/libsystemd/sd-event/event-util.h +++ b/src/libsystemd/sd-event/event-util.h @@ -5,6 +5,8 @@ #include "sd-event.h" +#include "pidref.h" + int event_reset_time( sd_event *e, sd_event_source **s, @@ -32,3 +34,7 @@ static inline int event_source_disable(sd_event_source *s) { } int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handler_t callback, void *userdata); + +int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata); + +dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts); diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index b6899df..a1305ef 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#if HAVE_PIDFD_OPEN +#include +#endif #include #include @@ -1165,10 +1168,10 @@ static int source_set_pending(sd_event_source *s, bool b) { assert(s->inotify.inode_data->inotify_data); if (b) - s->inotify.inode_data->inotify_data->n_pending ++; + s->inotify.inode_data->inotify_data->n_pending++; else { assert(s->inotify.inode_data->inotify_data->n_pending > 0); - s->inotify.inode_data->inotify_data->n_pending --; + s->inotify.inode_data->inotify_data->n_pending--; } } @@ -1574,7 +1577,7 @@ static int child_exit_callback(sd_event_source *s, const siginfo_t *si, void *us static bool shall_use_pidfd(void) { /* Mostly relevant for debugging, i.e. this is used in test-event.c to test the event loop once with and once without pidfd */ - return getenv_bool_secure("SYSTEMD_PIDFD") != 0; + return secure_getenv_bool("SYSTEMD_PIDFD") != 0; } _public_ int sd_event_add_child( @@ -1976,7 +1979,7 @@ _public_ int sd_event_add_memory_pressure( env = secure_getenv("MEMORY_PRESSURE_WRITE"); if (env) { - r = unbase64mem(env, SIZE_MAX, &write_buffer, &write_buffer_size); + r = unbase64mem(env, &write_buffer, &write_buffer_size); if (r < 0) return r; } @@ -2231,8 +2234,8 @@ static int inode_data_compare(const struct inode_data *x, const struct inode_dat static void inode_data_hash_func(const struct inode_data *d, struct siphash *state) { assert(d); - siphash24_compress(&d->dev, sizeof(d->dev), state); - siphash24_compress(&d->ino, sizeof(d->ino), state); + siphash24_compress_typesafe(d->dev, state); + siphash24_compress_typesafe(d->ino, state); } DEFINE_PRIVATE_HASH_OPS(inode_data_hash_ops, struct inode_data, inode_data_hash_func, inode_data_compare); @@ -2272,6 +2275,7 @@ static void event_free_inode_data( assert_se(hashmap_remove(d->inotify_data->inodes, d) == d); } + free(d->path); free(d); } @@ -2512,6 +2516,15 @@ static int event_add_inotify_fd_internal( } LIST_PREPEND(to_close, e->inode_data_to_close_list, inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) { /* The path is optional, hence ignore -ENOSYS. */ + event_gc_inode_data(e, inode_data); + return r; + } + + free_and_replace(inode_data->path, path); } /* Link our event source to the inode data object */ @@ -2797,6 +2810,13 @@ _public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) } LIST_PREPEND(to_close, s->event->inode_data_to_close_list, new_inode_data); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(new_inode_data->fd, &path); + if (r < 0 && r != -ENOSYS) + goto fail; + + free_and_replace(new_inode_data->path, path); } /* Move the event source to the new inode data structure */ @@ -3281,13 +3301,29 @@ _public_ int sd_event_source_set_child_process_own(sd_event_source *s, int own) return 0; } -_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *mask) { +_public_ int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret) { assert_return(s, -EINVAL); - assert_return(mask, -EINVAL); + assert_return(ret, -EINVAL); assert_return(s->type == SOURCE_INOTIFY, -EDOM); assert_return(!event_origin_changed(s->event), -ECHILD); - *mask = s->inotify.mask; + *ret = s->inotify.mask; + return 0; +} + +_public_ int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret) { + assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(s->type == SOURCE_INOTIFY, -EDOM); + assert_return(!event_origin_changed(s->event), -ECHILD); + + if (!s->inotify.inode_data) + return -ESTALE; /* already disconnected. */ + + if (!s->inotify.inode_data->path) + return -ENOSYS; /* /proc was not mounted? */ + + *ret = s->inotify.inode_data->path; return 0; } @@ -3999,7 +4035,7 @@ static int process_inotify(sd_event *e) { if (r < 0) return r; if (r > 0) - done ++; + done++; } return done; @@ -4914,13 +4950,13 @@ _public_ int sd_event_get_state(sd_event *e) { _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_origin_changed(e), -ECHILD); if (!e->exit_requested) return -ENODATA; - *code = e->exit_code; + if (code) + *code = e->exit_code; return 0; } @@ -5037,7 +5073,7 @@ _public_ int sd_event_set_watchdog(sd_event *e, int b) { } } - e->watchdog = !!b; + e->watchdog = b; return e->watchdog; fail: diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c index cc3d84e..57dee39 100644 --- a/src/libsystemd/sd-event/test-event.c +++ b/src/libsystemd/sd-event/test-event.c @@ -1,5 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#if HAVE_PIDFD_OPEN +#include +#endif #include #include @@ -92,7 +95,7 @@ static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, assert_se(userdata == INT_TO_PTR('e')); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGUSR2, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGUSR2) >= 0); pid = fork(); assert_se(pid >= 0); @@ -142,7 +145,7 @@ static int defer_handler(sd_event_source *s, void *userdata) { assert_se(userdata == INT_TO_PTR('d')); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1) >= 0); assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); @@ -254,7 +257,7 @@ static void test_basic_one(bool with_pidfd) { assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); /* Test for floating event sources */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1) >= 0); assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); assert_se(write(a[1], &ch, 1) >= 0); @@ -346,7 +349,7 @@ TEST(rtqueue) { assert_se(sd_event_default(&e) >= 0); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2) >= 0); assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); @@ -396,6 +399,7 @@ struct inotify_context { unsigned create_called[CREATE_EVENTS_MAX]; unsigned create_overflow; unsigned n_create_events; + const char *path; }; static void maybe_exit(sd_event_source *s, struct inotify_context *c) { @@ -422,10 +426,12 @@ static void maybe_exit(sd_event_source *s, struct inotify_context *c) { } static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) { - struct inotify_context *c = userdata; - const char *description; + struct inotify_context *c = ASSERT_PTR(userdata); + const char *path, *description; unsigned bit, n; + assert_se(sd_event_source_get_inotify_path(s, &path) >= 0); + assert_se(sd_event_source_get_description(s, &description) >= 0); assert_se(safe_atou(description, &n) >= 0); @@ -433,11 +439,12 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v bit = 1U << n; if (ev->mask & IN_Q_OVERFLOW) { - log_info("inotify-handler <%s>: overflow", description); + log_info("inotify-handler for %s <%s>: overflow", path, description); c->create_overflow |= bit; } else if (ev->mask & IN_CREATE) { + assert_se(path_equal_or_inode_same(path, c->path, 0)); if (streq(ev->name, "sub")) - log_debug("inotify-handler <%s>: create on %s", description, ev->name); + log_debug("inotify-handler for %s <%s>: create on %s", path, description, ev->name); else { unsigned i; @@ -446,7 +453,7 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v c->create_called[i] |= bit; } } else if (ev->mask & IN_DELETE) { - log_info("inotify-handler <%s>: delete of %s", description, ev->name); + log_info("inotify-handler for %s <%s>: delete of %s", path, description, ev->name); assert_se(streq(ev->name, "sub")); } else assert_not_reached(); @@ -456,16 +463,19 @@ static int inotify_handler(sd_event_source *s, const struct inotify_event *ev, v } static int delete_self_handler(sd_event_source *s, const struct inotify_event *ev, void *userdata) { - struct inotify_context *c = userdata; + struct inotify_context *c = ASSERT_PTR(userdata); + const char *path; + + assert_se(sd_event_source_get_inotify_path(s, &path) >= 0); if (ev->mask & IN_Q_OVERFLOW) { - log_info("delete-self-handler: overflow"); + log_info("delete-self-handler for %s: overflow", path); c->delete_self_handler_called = true; } else if (ev->mask & IN_DELETE_SELF) { - log_info("delete-self-handler: delete-self"); + log_info("delete-self-handler for %s: delete-self", path); c->delete_self_handler_called = true; } else if (ev->mask & IN_IGNORED) { - log_info("delete-self-handler: ignore"); + log_info("delete-self-handler for %s: ignore", path); } else assert_not_reached(); @@ -480,7 +490,7 @@ static void test_inotify_one(unsigned n_create_events) { .n_create_events = n_create_events, }; sd_event *e = NULL; - const char *q; + const char *q, *pp; unsigned i; log_info("/* %s(%u) */", __func__, n_create_events); @@ -488,6 +498,7 @@ static void test_inotify_one(unsigned n_create_events) { assert_se(sd_event_default(&e) >= 0); assert_se(mkdtemp_malloc("/tmp/test-inotify-XXXXXX", &p) >= 0); + context.path = p; assert_se(sd_event_add_inotify(e, &a, p, IN_CREATE|IN_ONLYDIR, inotify_handler, &context) >= 0); assert_se(sd_event_add_inotify(e, &b, p, IN_CREATE|IN_DELETE|IN_DONT_FOLLOW, inotify_handler, &context) >= 0); @@ -500,6 +511,13 @@ static void test_inotify_one(unsigned n_create_events) { assert_se(sd_event_source_set_description(b, "1") >= 0); assert_se(sd_event_source_set_description(c, "2") >= 0); + assert_se(sd_event_source_get_inotify_path(a, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + assert_se(sd_event_source_get_inotify_path(b, &pp) >= 0); + assert_se(path_equal_or_inode_same(pp, p, 0)); + q = strjoina(p, "/sub"); assert_se(touch(q) >= 0); assert_se(sd_event_add_inotify(e, &d, q, IN_DELETE_SELF, delete_self_handler, &context) >= 0); @@ -556,7 +574,7 @@ TEST(pidfd) { int pidfd; pid_t pid, pid2; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); pid = fork(); if (pid == 0) diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c index f163314..f623d47 100644 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ b/src/libsystemd/sd-hwdb/sd-hwdb.c @@ -313,15 +313,15 @@ static int hwdb_new(const char *path, sd_hwdb **ret) { if (!hwdb->f) return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), - "hwdb.bin does not exist, please run 'systemd-hwdb update'"); + "hwdb.bin does not exist, please run 'systemd-hwdb update'."); } if (fstat(fileno(hwdb->f), &hwdb->st) < 0) return log_debug_errno(errno, "Failed to stat %s: %m", path); if (hwdb->st.st_size < (off_t) offsetof(struct trie_header_f, strings_len) + 8) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "File %s is too short: %m", path); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "File %s is too short.", path); if (file_offset_beyond_memory_size(hwdb->st.st_size)) - return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "File %s is too long: %m", path); + return log_debug_errno(SYNTHETIC_ERRNO(EFBIG), "File %s is too long.", path); hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); if (hwdb->map == MAP_FAILED) @@ -330,7 +330,7 @@ static int hwdb_new(const char *path, sd_hwdb **ret) { if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || (size_t) hwdb->st.st_size != le64toh(hwdb->head->file_size)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to recognize the format of %s", path); + "Failed to recognize the format of %s.", path); log_debug("=== trie on-disk ==="); log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c index b9714ee..298d21e 100644 --- a/src/libsystemd/sd-id128/id128-util.c +++ b/src/libsystemd/sd-id128/id128-util.c @@ -9,9 +9,12 @@ #include "hexdecoct.h" #include "id128-util.h" #include "io-util.h" +#include "namespace-util.h" +#include "process-util.h" #include "sha256.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "sync-util.h" #include "virt.h" @@ -192,11 +195,11 @@ int id128_write_at(int dir_fd, const char *path, Id128Flag f, sd_id128_t id) { } void id128_hash_func(const sd_id128_t *p, struct siphash *state) { - siphash24_compress(p, sizeof(sd_id128_t), state); + siphash24_compress_typesafe(*p, state); } int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) { - return memcmp(a, b, 16); + return memcmp(a, b, sizeof(sd_id128_t)); } sd_id128_t id128_make_v4_uuid(sd_id128_t id) { @@ -231,11 +234,15 @@ int id128_get_product(sd_id128_t *ret) { * of the host */ return -ENOENT; - r = id128_read("/sys/class/dmi/id/product_uuid", ID128_FORMAT_UUID, &uuid); - if (r == -ENOENT) - r = id128_read("/proc/device-tree/vm,uuid", ID128_FORMAT_UUID, &uuid); - if (r == -ENOENT) - r = id128_read("/sys/hypervisor/uuid", ID128_FORMAT_UUID, &uuid); + FOREACH_STRING(i, + "/sys/class/dmi/id/product_uuid", /* KVM */ + "/proc/device-tree/vm,uuid", /* Device tree */ + "/sys/hypervisor/uuid") { /* Xen */ + + r = id128_read(i, ID128_FORMAT_UUID, &uuid); + if (r != -ENOENT) + break; + } if (r < 0) return r; @@ -263,3 +270,64 @@ sd_id128_t id128_digest(const void *data, size_t size) { return id128_make_v4_uuid(id); } + +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret) { + _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + pid_t pid, child; + sd_id128_t id; + ssize_t k; + int r; + + assert(ret); + + if (isempty(machine)) + return sd_id128_get_boot(ret); + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, + pidnsfd, mntnsfd, -1, -1, rootfd, &child); + if (r < 0) + return r; + if (r == 0) { + pair[0] = safe_close(pair[0]); + + r = id128_get_boot(&id); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], &id, sizeof(id), MSG_NOSIGNAL); + if (k != sizeof(id)) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); + if (r < 0) + return r; + if (r != EXIT_SUCCESS) + return -EIO; + + k = recv(pair[0], &id, sizeof(id), 0); + if (k != sizeof(id)) + return -EIO; + + if (sd_id128_is_null(id)) + return -EIO; + + *ret = id; + return 0; +} diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h index 53ba50a..458d430 100644 --- a/src/libsystemd/sd-id128/id128-util.h +++ b/src/libsystemd/sd-id128/id128-util.h @@ -49,6 +49,9 @@ int id128_get_product(sd_id128_t *ret); sd_id128_t id128_digest(const void *data, size_t size); +int id128_get_boot(sd_id128_t *ret); +int id128_get_boot_for_machine(const char *machine, sd_id128_t *ret); + /* A helper to check for the three relevant cases of "machine ID not initialized" */ #define ERRNO_IS_NEG_MACHINE_ID_UNSET(r) \ IN_SET(r, \ diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c index 9fda79a..fc1107b 100644 --- a/src/libsystemd/sd-id128/sd-id128.c +++ b/src/libsystemd/sd-id128/sd-id128.c @@ -13,6 +13,7 @@ #include "hmac.h" #include "id128-util.h" #include "io-util.h" +#include "keyring-util.h" #include "macro.h" #include "missing_syscall.h" #include "missing_threads.h" @@ -170,14 +171,24 @@ int id128_get_machine(const char *root, sd_id128_t *ret) { return id128_read_fd(fd, ID128_FORMAT_PLAIN | ID128_REFUSE_NULL, ret); } +int id128_get_boot(sd_id128_t *ret) { + int r; + + assert(ret); + + r = id128_read("/proc/sys/kernel/random/boot_id", ID128_FORMAT_UUID | ID128_REFUSE_NULL, ret); + if (r == -ENOENT && proc_mounted() == 0) + return -ENOSYS; + + return r; +} + _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 | ID128_REFUSE_NULL, &saved_boot_id); - if (r == -ENOENT && proc_mounted() == 0) - return -ENOSYS; + r = id128_get_boot(&saved_boot_id); if (r < 0) return r; } @@ -192,7 +203,6 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { char *d, *p, *g, *u, *e; unsigned long perms; key_serial_t key; - size_t sz = 256; uid_t uid; gid_t gid; int r, c; @@ -211,24 +221,9 @@ static int get_invocation_from_keyring(sd_id128_t *ret) { return -errno; } - for (;;) { - description = new(char, sz); - if (!description) - return -ENOMEM; - - c = keyctl(KEYCTL_DESCRIBE, key, (unsigned long) description, sz, 0); - if (c < 0) - return -errno; - - if ((size_t) c <= sz) - break; - - sz = c; - free(description); - } - - /* The kernel returns a final NUL in the string, verify that. */ - assert(description[c-1] == 0); + r = keyring_describe(key, &description); + if (r < 0) + return r; /* Chop off the final description string */ d = strrchr(description, ';'); @@ -380,3 +375,16 @@ _public_ int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret) return sd_id128_get_app_specific(id, app_id, ret); } + +_public_ int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret) { + sd_id128_t id; + int r; + + assert_return(ret, -EINVAL); + + r = sd_id128_get_invocation(&id); + if (r < 0) + return r; + + return sd_id128_get_app_specific(id, app_id, ret); +} diff --git a/src/libsystemd/sd-journal/catalog.c b/src/libsystemd/sd-journal/catalog.c index ae91534..a0b673f 100644 --- a/src/libsystemd/sd-journal/catalog.c +++ b/src/libsystemd/sd-journal/catalog.c @@ -16,6 +16,7 @@ #include "conf-files.h" #include "fd-util.h" #include "fileio.h" +#include "fs-util.h" #include "hashmap.h" #include "log.h" #include "memory-util.h" @@ -54,7 +55,7 @@ typedef struct CatalogItem { } CatalogItem; static void catalog_hash_func(const CatalogItem *i, struct siphash *state) { - siphash24_compress(&i->id, sizeof(i->id), state); + siphash24_compress_typesafe(i->id, state); siphash24_compress_string(i->language, state); } @@ -222,10 +223,7 @@ static int catalog_entry_lang( const char* deflang, char **ret) { - size_t c; - char *z; - - c = strlen(t); + size_t c = strlen(t); if (c < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "[%s:%u] Language too short.", filename, line); @@ -242,12 +240,7 @@ static int catalog_entry_lang( log_warning("[%s:%u] language differs from default for file", filename, line); } - z = strdup(t); - if (!z) - return -ENOMEM; - - *ret = z; - return 0; + return strdup_to(ret, t); } int catalog_import_file(OrderedHashmap *h, const char *path) { @@ -382,10 +375,8 @@ static int64_t write_catalog( CatalogItem *items, size_t n) { + _cleanup_(unlink_and_freep) char *p = NULL; _cleanup_fclose_ FILE *w = NULL; - _cleanup_free_ char *p = NULL; - CatalogHeader header; - size_t k; int r; r = mkdir_parents(database, 0755); @@ -394,54 +385,35 @@ static int64_t write_catalog( r = fopen_temporary(database, &w, &p); if (r < 0) - return log_error_errno(r, "Failed to open database for writing: %s: %m", - database); + return log_error_errno(r, "Failed to open database for writing: %s: %m", database); - header = (CatalogHeader) { + CatalogHeader header = { .signature = CATALOG_SIGNATURE, .header_size = htole64(CONST_ALIGN_TO(sizeof(CatalogHeader), 8)), .catalog_item_size = htole64(sizeof(CatalogItem)), .n_items = htole64(n), }; - r = -EIO; + if (fwrite(&header, sizeof(header), 1, w) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write header.", p); - k = fwrite(&header, 1, sizeof(header), w); - if (k != sizeof(header)) { - log_error("%s: failed to write header.", p); - goto error; - } + if (fwrite(items, sizeof(CatalogItem), n, w) != n) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write database.", p); - k = fwrite(items, 1, n * sizeof(CatalogItem), w); - if (k != n * sizeof(CatalogItem)) { - log_error("%s: failed to write database.", p); - goto error; - } - - k = fwrite(sb->buf, 1, sb->len, w); - if (k != sb->len) { - log_error("%s: failed to write strings.", p); - goto error; - } + if (fwrite(sb->buf, sb->len, 1, w) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "%s: failed to write strings.", p); r = fflush_and_check(w); - if (r < 0) { - log_error_errno(r, "%s: failed to write database: %m", p); - goto error; - } + if (r < 0) + return log_error_errno(r, "%s: failed to write database: %m", p); (void) fchmod(fileno(w), 0644); - if (rename(p, database) < 0) { - r = log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); - goto error; - } + if (rename(p, database) < 0) + return log_error_errno(errno, "rename (%s -> %s) failed: %m", p, database); + p = mfree(p); /* free without unlinking */ return ftello(w); - -error: - (void) unlink(p); - return r; } int catalog_update(const char* database, const char* root, const char* const* dirs) { @@ -472,11 +444,12 @@ int catalog_update(const char* database, const char* root, const char* const* di return log_error_errno(r, "Failed to import file '%s': %m", *f); } - if (ordered_hashmap_size(h) <= 0) { + if (ordered_hashmap_isempty(h)) { log_info("No items in catalog."); return 0; - } else - log_debug("Found %u items in catalog.", ordered_hashmap_size(h)); + } + + log_debug("Found %u items in catalog.", ordered_hashmap_size(h)); items = new(CatalogItem, ordered_hashmap_size(h)); if (!items) @@ -607,15 +580,14 @@ static const char *find_id(void *p, sd_id128_t id) { le64toh(f->offset); } -int catalog_get(const char* database, sd_id128_t id, char **_text) { +int catalog_get(const char* database, sd_id128_t id, char **ret_text) { _cleanup_close_ int fd = -EBADF; void *p = NULL; - struct stat st = {}; - char *text = NULL; + struct stat st; int r; const char *s; - assert(_text); + assert(ret_text); r = open_mmap(database, &fd, &st, &p); if (r < 0) @@ -627,18 +599,9 @@ int catalog_get(const char* database, sd_id128_t id, char **_text) { goto finish; } - text = strdup(s); - if (!text) { - r = -ENOMEM; - goto finish; - } - - *_text = text; - r = 0; - + r = strdup_to(ret_text, s); finish: - if (p) - munmap(p, st.st_size); + (void) munmap(p, st.st_size); return r; } diff --git a/src/libsystemd/sd-journal/catalog.h b/src/libsystemd/sd-journal/catalog.h index df27869..b5a97fa 100644 --- a/src/libsystemd/sd-journal/catalog.h +++ b/src/libsystemd/sd-journal/catalog.h @@ -11,7 +11,7 @@ int catalog_import_file(OrderedHashmap *h, const char *path); int catalog_update(const char* database, const char* root, const char* const* dirs); -int catalog_get(const char* database, sd_id128_t id, char **data); +int catalog_get(const char* database, sd_id128_t id, char **ret_text); int catalog_list(FILE *f, const char* database, bool oneline); int catalog_list_items(FILE *f, const char* database, bool oneline, char **items); int catalog_file_lang(const char *filename, char **lang); diff --git a/src/libsystemd/sd-journal/fsprg.c b/src/libsystemd/sd-journal/fsprg.c index e86be6a..85632b0 100644 --- a/src/libsystemd/sd-journal/fsprg.c +++ b/src/libsystemd/sd-journal/fsprg.c @@ -3,21 +3,6 @@ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator * Copyright © 2012 B. Poettering * Contact: fsprg@point-at-infinity.org - * - * This library 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. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA */ /* @@ -50,11 +35,11 @@ static void mpi_export(void *buf, size_t buflen, const gcry_mpi_t x) { unsigned len; size_t nwritten; - assert(gcry_mpi_cmp_ui(x, 0) >= 0); - len = (gcry_mpi_get_nbits(x) + 7) / 8; + assert(sym_gcry_mpi_cmp_ui(x, 0) >= 0); + len = (sym_gcry_mpi_get_nbits(x) + 7) / 8; assert(len <= buflen); memzero(buf, buflen); - gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); + sym_gcry_mpi_print(GCRYMPI_FMT_USG, buf + (buflen - len), len, &nwritten, x); assert(nwritten == len); } @@ -62,10 +47,10 @@ static gcry_mpi_t mpi_import(const void *buf, size_t buflen) { gcry_mpi_t h; _unused_ unsigned len; - assert_se(gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); - len = (gcry_mpi_get_nbits(h) + 7) / 8; + assert_se(sym_gcry_mpi_scan(&h, GCRYMPI_FMT_USG, buf, buflen, NULL) == 0); + len = (sym_gcry_mpi_get_nbits(h) + 7) / 8; assert(len <= buflen); - assert(gcry_mpi_cmp_ui(h, 0) >= 0); + assert(sym_gcry_mpi_cmp_ui(h, 0) >= 0); return h; } @@ -102,30 +87,30 @@ static void det_randomize(void *buf, size_t buflen, const void *seed, size_t see gcry_error_t err; uint32_t ctr; - olen = gcry_md_get_algo_dlen(RND_HASH); - err = gcry_md_open(&hd, RND_HASH, 0); + olen = sym_gcry_md_get_algo_dlen(RND_HASH); + err = sym_gcry_md_open(&hd, RND_HASH, 0); assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */ - gcry_md_write(hd, seed, seedlen); - gcry_md_putc(hd, (idx >> 24) & 0xff); - gcry_md_putc(hd, (idx >> 16) & 0xff); - gcry_md_putc(hd, (idx >> 8) & 0xff); - gcry_md_putc(hd, (idx >> 0) & 0xff); + sym_gcry_md_write(hd, seed, seedlen); + sym_gcry_md_putc(hd, (idx >> 24) & 0xff); + sym_gcry_md_putc(hd, (idx >> 16) & 0xff); + sym_gcry_md_putc(hd, (idx >> 8) & 0xff); + sym_gcry_md_putc(hd, (idx >> 0) & 0xff); for (ctr = 0; buflen; ctr++) { - err = gcry_md_copy(&hd2, hd); + err = sym_gcry_md_copy(&hd2, hd); assert_se(gcry_err_code(err) == GPG_ERR_NO_ERROR); /* This shouldn't happen */ - gcry_md_putc(hd2, (ctr >> 24) & 0xff); - gcry_md_putc(hd2, (ctr >> 16) & 0xff); - gcry_md_putc(hd2, (ctr >> 8) & 0xff); - gcry_md_putc(hd2, (ctr >> 0) & 0xff); - gcry_md_final(hd2); + sym_gcry_md_putc(hd2, (ctr >> 24) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 16) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 8) & 0xff); + sym_gcry_md_putc(hd2, (ctr >> 0) & 0xff); + sym_gcry_md_ctl(hd2, GCRYCTL_FINALIZE, NULL, 0); cpylen = (buflen < olen) ? buflen : olen; - memcpy(buf, gcry_md_read(hd2, RND_HASH), cpylen); - gcry_md_close(hd2); + memcpy(buf, sym_gcry_md_read(hd2, RND_HASH), cpylen); + sym_gcry_md_close(hd2); buf += cpylen; buflen -= cpylen; } - gcry_md_close(hd); + sym_gcry_md_close(hd); } /* deterministically generate from seed/idx a prime of length `bits' that is 3 (mod 4) */ @@ -142,8 +127,8 @@ static gcry_mpi_t genprime3mod4(int bits, const void *seed, size_t seedlen, uint buf[buflen - 1] |= 0x03; /* set lower two bits, to have result 3 (mod 4) */ p = mpi_import(buf, buflen); - while (gcry_prime_check(p, 0)) - gcry_mpi_add_ui(p, p, 4); + while (sym_gcry_prime_check(p, 0)) + sym_gcry_mpi_add_ui(p, p, 4); return p; } @@ -157,8 +142,8 @@ static gcry_mpi_t gensquare(const gcry_mpi_t n, const void *seed, size_t seedlen det_randomize(buf, buflen, seed, seedlen, idx); buf[0] &= 0x7f; /* clear upper bit, so that we have x < n */ x = mpi_import(buf, buflen); - assert(gcry_mpi_cmp(x, n) < 0); - gcry_mpi_mulm(x, x, x, n); + assert(sym_gcry_mpi_cmp(x, n) < 0); + sym_gcry_mpi_mulm(x, x, x, n); return x; } @@ -167,51 +152,51 @@ static gcry_mpi_t twopowmodphi(uint64_t m, const gcry_mpi_t p) { gcry_mpi_t phi, r; int n; - phi = gcry_mpi_new(0); - gcry_mpi_sub_ui(phi, p, 1); + phi = sym_gcry_mpi_new(0); + sym_gcry_mpi_sub_ui(phi, p, 1); /* count number of used bits in m */ for (n = 0; (1ULL << n) <= m; n++) ; - r = gcry_mpi_new(0); - gcry_mpi_set_ui(r, 1); + r = sym_gcry_mpi_new(0); + sym_gcry_mpi_set_ui(r, 1); while (n) { /* square and multiply algorithm for fast exponentiation */ n--; - gcry_mpi_mulm(r, r, r, phi); + sym_gcry_mpi_mulm(r, r, r, phi); if (m & ((uint64_t)1 << n)) { - gcry_mpi_add(r, r, r); - if (gcry_mpi_cmp(r, phi) >= 0) - gcry_mpi_sub(r, r, phi); + sym_gcry_mpi_add(r, r, r); + if (sym_gcry_mpi_cmp(r, phi) >= 0) + sym_gcry_mpi_sub(r, r, phi); } } - gcry_mpi_release(phi); + sym_gcry_mpi_release(phi); return r; } /* Decompose $x \in Z_n$ into $(xp,xq) \in Z_p \times Z_q$ using Chinese Remainder Theorem */ static void CRT_decompose(gcry_mpi_t *xp, gcry_mpi_t *xq, const gcry_mpi_t x, const gcry_mpi_t p, const gcry_mpi_t q) { - *xp = gcry_mpi_new(0); - *xq = gcry_mpi_new(0); - gcry_mpi_mod(*xp, x, p); - gcry_mpi_mod(*xq, x, q); + *xp = sym_gcry_mpi_new(0); + *xq = sym_gcry_mpi_new(0); + sym_gcry_mpi_mod(*xp, x, p); + sym_gcry_mpi_mod(*xq, x, q); } /* Compose $(xp,xq) \in Z_p \times Z_q$ into $x \in Z_n$ using Chinese Remainder Theorem */ static void CRT_compose(gcry_mpi_t *x, const gcry_mpi_t xp, const gcry_mpi_t xq, const gcry_mpi_t p, const gcry_mpi_t q) { gcry_mpi_t a, u; - a = gcry_mpi_new(0); - u = gcry_mpi_new(0); - *x = gcry_mpi_new(0); - gcry_mpi_subm(a, xq, xp, q); - gcry_mpi_invm(u, p, q); - gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ - gcry_mpi_mul(*x, p, a); - gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ - gcry_mpi_release(a); - gcry_mpi_release(u); + a = sym_gcry_mpi_new(0); + u = sym_gcry_mpi_new(0); + *x = sym_gcry_mpi_new(0); + sym_gcry_mpi_subm(a, xq, xp, q); + sym_gcry_mpi_invm(u, p, q); + sym_gcry_mpi_mulm(a, a, u, q); /* a = (xq - xp) / p (mod q) */ + sym_gcry_mpi_mul(*x, p, a); + sym_gcry_mpi_add(*x, *x, xp); /* x = p * ((xq - xp) / p mod q) + xp */ + sym_gcry_mpi_release(a); + sym_gcry_mpi_release(u); } /******************************************************************************/ @@ -245,18 +230,21 @@ static uint16_t read_secpar(const void *buf) { return 16 * (secpar + 1); } -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { +int FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned _secpar) { uint8_t iseed[FSPRG_RECOMMENDED_SEEDLEN]; gcry_mpi_t n, p, q; uint16_t secpar; + int r; VALIDATE_SECPAR(_secpar); secpar = _secpar; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; if (!seed) { - gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); + sym_gcry_randomize(iseed, FSPRG_RECOMMENDED_SEEDLEN, GCRY_STRONG_RANDOM); seed = iseed; seedlen = FSPRG_RECOMMENDED_SEEDLEN; } @@ -271,25 +259,30 @@ void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigne } if (mpk) { - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); - assert(gcry_mpi_get_nbits(n) == secpar); + n = sym_gcry_mpi_new(0); + sym_gcry_mpi_mul(n, p, q); + assert(sym_gcry_mpi_get_nbits(n) == secpar); store_secpar(mpk + 0, secpar); mpi_export(mpk + 2, secpar / 8, n); - gcry_mpi_release(n); + sym_gcry_mpi_release(n); } - gcry_mpi_release(p); - gcry_mpi_release(q); + sym_gcry_mpi_release(p); + sym_gcry_mpi_release(q); + + return 0; } -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { +int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen) { gcry_mpi_t n, x; uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(mpk + 0); n = mpi_import(mpk + 2, secpar / 8); @@ -299,30 +292,37 @@ void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seed mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); memzero(state + 2 + 2 * secpar / 8, 8); - gcry_mpi_release(n); - gcry_mpi_release(x); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + + return 0; } -void FSPRG_Evolve(void *state) { +int FSPRG_Evolve(void *state) { gcry_mpi_t n, x; uint16_t secpar; uint64_t epoch; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(state + 0); n = mpi_import(state + 2 + 0 * secpar / 8, secpar / 8); x = mpi_import(state + 2 + 1 * secpar / 8, secpar / 8); epoch = uint64_import(state + 2 + 2 * secpar / 8, 8); - gcry_mpi_mulm(x, x, x, n); + sym_gcry_mpi_mulm(x, x, x, n); epoch++; mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, x); uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - gcry_mpi_release(n); - gcry_mpi_release(x); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + + return 0; } uint64_t FSPRG_GetEpoch(const void *state) { @@ -331,18 +331,21 @@ uint64_t FSPRG_GetEpoch(const void *state) { return uint64_import(state + 2 + 2 * secpar / 8, 8); } -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { +int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen) { gcry_mpi_t p, q, n, x, xp, xq, kp, kq, xm; uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(msk + 0); p = mpi_import(msk + 2 + 0 * (secpar / 2) / 8, (secpar / 2) / 8); q = mpi_import(msk + 2 + 1 * (secpar / 2) / 8, (secpar / 2) / 8); - n = gcry_mpi_new(0); - gcry_mpi_mul(n, p, q); + n = sym_gcry_mpi_new(0); + sym_gcry_mpi_mul(n, p, q); x = gensquare(n, seed, seedlen, RND_GEN_X, secpar); CRT_decompose(&xp, &xq, x, p, q); /* split (mod n) into (mod p) and (mod q) using CRT */ @@ -350,8 +353,8 @@ void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, kp = twopowmodphi(epoch, p); /* compute 2^epoch (mod phi(p)) */ kq = twopowmodphi(epoch, q); /* compute 2^epoch (mod phi(q)) */ - gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ - gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ + sym_gcry_mpi_powm(xp, xp, kp, p); /* compute x^(2^epoch) (mod p) */ + sym_gcry_mpi_powm(xq, xq, kq, q); /* compute x^(2^epoch) (mod q) */ CRT_compose(&xm, xp, xq, p, q); /* combine (mod p) and (mod q) to (mod n) using CRT */ @@ -360,22 +363,29 @@ void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, mpi_export(state + 2 + 1 * secpar / 8, secpar / 8, xm); uint64_export(state + 2 + 2 * secpar / 8, 8, epoch); - gcry_mpi_release(p); - gcry_mpi_release(q); - gcry_mpi_release(n); - gcry_mpi_release(x); - gcry_mpi_release(xp); - gcry_mpi_release(xq); - gcry_mpi_release(kp); - gcry_mpi_release(kq); - gcry_mpi_release(xm); + sym_gcry_mpi_release(p); + sym_gcry_mpi_release(q); + sym_gcry_mpi_release(n); + sym_gcry_mpi_release(x); + sym_gcry_mpi_release(xp); + sym_gcry_mpi_release(xq); + sym_gcry_mpi_release(kp); + sym_gcry_mpi_release(kq); + sym_gcry_mpi_release(xm); + + return 0; } -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { +int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx) { uint16_t secpar; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; secpar = read_secpar(state + 0); det_randomize(key, keylen, state + 2, 2 * secpar / 8 + 8, idx); + + return 0; } diff --git a/src/libsystemd/sd-journal/fsprg.h b/src/libsystemd/sd-journal/fsprg.h index d3d88aa..a0594cd 100644 --- a/src/libsystemd/sd-journal/fsprg.h +++ b/src/libsystemd/sd-journal/fsprg.h @@ -5,21 +5,6 @@ * fsprg v0.1 - (seekable) forward-secure pseudorandom generator * Copyright © 2012 B. Poettering * Contact: fsprg@point-at-infinity.org - * - * This library 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. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301 USA */ #include @@ -39,22 +24,22 @@ size_t FSPRG_mpkinbytes(unsigned secpar) _const_; size_t FSPRG_stateinbytes(unsigned secpar) _const_; /* Setup msk and mpk. Providing seed != NULL makes this algorithm deterministic. */ -void FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); +int FSPRG_GenMK(void *msk, void *mpk, const void *seed, size_t seedlen, unsigned secpar); /* Initialize state deterministically in dependence on seed. */ /* Note: in case one wants to run only one GenState0 per GenMK it is safe to use the same seed for both GenMK and GenState0. */ -void FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); +int FSPRG_GenState0(void *state, const void *mpk, const void *seed, size_t seedlen); -void FSPRG_Evolve(void *state); +int FSPRG_Evolve(void *state); uint64_t FSPRG_GetEpoch(const void *state) _pure_; /* Seek to any arbitrary state (by providing msk together with seed from GenState0). */ -void FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); +int FSPRG_Seek(void *state, uint64_t epoch, const void *msk, const void *seed, size_t seedlen); -void FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); +int FSPRG_GetKey(const void *state, void *key, size_t keylen, uint32_t idx); #ifdef __cplusplus } diff --git a/src/libsystemd/sd-journal/journal-authenticate.c b/src/libsystemd/sd-journal/journal-authenticate.c index 8e7533e..64f5739 100644 --- a/src/libsystemd/sd-journal/journal-authenticate.c +++ b/src/libsystemd/sd-journal/journal-authenticate.c @@ -71,7 +71,7 @@ int journal_file_append_tag(JournalFile *f) { return r; /* Get the HMAC tag and store it in the object */ - memcpy(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH); + memcpy(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH); f->hmac_running = false; return 0; @@ -80,6 +80,7 @@ int journal_file_append_tag(JournalFile *f) { int journal_file_hmac_start(JournalFile *f) { uint8_t key[256 / 8]; /* Let's pass 256 bit from FSPRG to HMAC */ gcry_error_t err; + int r; assert(f); @@ -90,13 +91,17 @@ int journal_file_hmac_start(JournalFile *f) { return 0; /* Prepare HMAC for next cycle */ - gcry_md_reset(f->hmac); - FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); - err = gcry_md_setkey(f->hmac, key, sizeof(key)); + sym_gcry_md_reset(f->hmac); + + r = FSPRG_GetKey(f->fsprg_state, key, sizeof(key), 0); + if (r < 0) + return r; + + err = sym_gcry_md_setkey(f->hmac, key, sizeof(key)); if (gcry_err_code(err) != GPG_ERR_NO_ERROR) return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "gcry_md_setkey() failed with error code: %s", - gcry_strerror(err)); + "sym_gcry_md_setkey() failed with error code: %s", + sym_gcry_strerror(err)); f->hmac_running = true; @@ -167,7 +172,10 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { if (epoch == goal) return 0; - FSPRG_Evolve(f->fsprg_state); + r = FSPRG_Evolve(f->fsprg_state); + if (r < 0) + return r; + epoch = FSPRG_GetEpoch(f->fsprg_state); if (epoch < goal) { r = journal_file_append_tag(f); @@ -180,6 +188,7 @@ int journal_file_fsprg_evolve(JournalFile *f, uint64_t realtime) { int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { void *msk; uint64_t epoch; + int r; assert(f); @@ -195,10 +204,8 @@ int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { if (goal == epoch) return 0; - if (goal == epoch + 1) { - FSPRG_Evolve(f->fsprg_state); - return 0; - } + if (goal == epoch + 1) + return FSPRG_Evolve(f->fsprg_state); } else { f->fsprg_state_size = FSPRG_stateinbytes(FSPRG_RECOMMENDED_SECPAR); f->fsprg_state = malloc(f->fsprg_state_size); @@ -209,10 +216,12 @@ int journal_file_fsprg_seek(JournalFile *f, uint64_t goal) { log_debug("Seeking FSPRG key to %"PRIu64".", goal); msk = alloca_safe(FSPRG_mskinbytes(FSPRG_RECOMMENDED_SECPAR)); - FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); - FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); - return 0; + r = FSPRG_GenMK(msk, NULL, f->fsprg_seed, f->fsprg_seed_size, FSPRG_RECOMMENDED_SECPAR); + if (r < 0) + return r; + + return FSPRG_Seek(f->fsprg_state, goal, msk, f->fsprg_seed, f->fsprg_seed_size); } int journal_file_maybe_append_tag(JournalFile *f, uint64_t realtime) { @@ -260,25 +269,25 @@ int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uin } else if (type > OBJECT_UNUSED && o->object.type != type) return -EBADMSG; - gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); + sym_gcry_md_write(f->hmac, o, offsetof(ObjectHeader, payload)); switch (o->object.type) { case OBJECT_DATA: /* All but hash and payload are mutable */ - gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); - gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); + sym_gcry_md_write(f->hmac, &o->data.hash, sizeof(o->data.hash)); + sym_gcry_md_write(f->hmac, journal_file_data_payload_field(f, o), le64toh(o->object.size) - journal_file_data_payload_offset(f)); break; case OBJECT_FIELD: /* Same here */ - gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); - gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); + sym_gcry_md_write(f->hmac, &o->field.hash, sizeof(o->field.hash)); + sym_gcry_md_write(f->hmac, o->field.payload, le64toh(o->object.size) - offsetof(Object, field.payload)); break; case OBJECT_ENTRY: /* All */ - gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)); + sym_gcry_md_write(f->hmac, &o->entry.seqnum, le64toh(o->object.size) - offsetof(Object, entry.seqnum)); break; case OBJECT_FIELD_HASH_TABLE: @@ -289,8 +298,8 @@ int journal_file_hmac_put_object(JournalFile *f, ObjectType type, Object *o, uin case OBJECT_TAG: /* All but the tag itself */ - gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); - gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); + sym_gcry_md_write(f->hmac, &o->tag.seqnum, sizeof(o->tag.seqnum)); + sym_gcry_md_write(f->hmac, &o->tag.epoch, sizeof(o->tag.epoch)); break; default: return -EINVAL; @@ -318,10 +327,10 @@ int journal_file_hmac_put_header(JournalFile *f) { * tail_entry_monotonic, n_data, n_fields, n_tags, * n_entry_arrays. */ - gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); - gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)); - gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); - gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); + sym_gcry_md_write(f->hmac, f->header->signature, offsetof(Header, state) - offsetof(Header, signature)); + sym_gcry_md_write(f->hmac, &f->header->file_id, offsetof(Header, tail_entry_boot_id) - offsetof(Header, file_id)); + sym_gcry_md_write(f->hmac, &f->header->seqnum_id, offsetof(Header, arena_size) - offsetof(Header, seqnum_id)); + sym_gcry_md_write(f->hmac, &f->header->data_hash_table_offset, offsetof(Header, tail_object_offset) - offsetof(Header, data_hash_table_offset)); return 0; } @@ -406,13 +415,16 @@ int journal_file_fss_load(JournalFile *f) { int journal_file_hmac_setup(JournalFile *f) { gcry_error_t e; + int r; if (!JOURNAL_HEADER_SEALED(f->header)) return 0; - initialize_libgcrypt(true); + r = initialize_libgcrypt(true); + if (r < 0) + return r; - e = gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + e = sym_gcry_md_open(&f->hmac, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); if (e != 0) return -EOPNOTSUPP; diff --git a/src/libsystemd/sd-journal/journal-file.c b/src/libsystemd/sd-journal/journal-file.c index 08cbf86..7e941ed 100644 --- a/src/libsystemd/sd-journal/journal-file.c +++ b/src/libsystemd/sd-journal/journal-file.c @@ -20,6 +20,7 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "gcrypt-util.h" #include "id128-util.h" #include "journal-authenticate.h" #include "journal-def.h" @@ -47,10 +48,6 @@ #define DEFAULT_COMPRESS_THRESHOLD (512ULL) #define MIN_COMPRESS_THRESHOLD (8ULL) -#define U64_KB UINT64_C(1024) -#define U64_MB (UINT64_C(1024) * U64_KB) -#define U64_GB (UINT64_C(1024) * U64_MB) - /* This is the minimum journal file size */ #define JOURNAL_FILE_SIZE_MIN (512 * U64_KB) /* 512 KiB */ #define JOURNAL_COMPACT_SIZE_MAX ((uint64_t) UINT32_MAX) /* 4 GiB */ @@ -285,6 +282,8 @@ JournalFile* journal_file_close(JournalFile *f) { assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); + sd_event_source_disable_unref(f->post_change_timer); + if (f->cache_fd) mmap_cache_fd_free(f->cache_fd); @@ -309,7 +308,7 @@ JournalFile* journal_file_close(JournalFile *f) { free(f->fsprg_seed); if (f->hmac) - gcry_md_close(f->hmac); + sym_gcry_md_close(f->hmac); #endif return mfree(f); @@ -2249,7 +2248,6 @@ static int journal_file_link_entry( f->header->tail_entry_monotonic = o->entry.monotonic; if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) f->header->tail_entry_offset = htole64(offset); - f->newest_mtime = 0; /* we have a new tail entry now, explicitly invalidate newest boot id/timestamp info */ /* Link up the items */ for (uint64_t i = 0; i < n_items; i++) { @@ -2762,7 +2760,7 @@ static int generic_array_get( Object **ret_object, /* The found object. */ uint64_t *ret_offset) { /* The offset of the found object. */ - uint64_t a, t = 0, k; + uint64_t a, t = 0, k = 0; /* Explicit initialization of k to appease gcc */ ChainCacheItem *ci; Object *o = NULL; int r; @@ -3052,7 +3050,7 @@ static int generic_array_bisect( left = 0; right = m - 1; - if (direction == DIRECTION_UP) { + if (direction == DIRECTION_UP && left < right) { /* If we're going upwards, the last entry of the previous array may pass the test, * and the first entry of the current array may not pass. In that case, the last * entry of the previous array must be returned. Hence, we need to test the first @@ -3167,10 +3165,21 @@ previous: if (direction == DIRECTION_DOWN) return 0; - /* Indicate to go to the previous array later. Note, do not move to the previous array here, - * as that may invalidate the current array object in the mmap cache and - * journal_file_entry_array_item() below may read invalid address. */ - i = UINT64_MAX; + /* Get the last entry of the previous array. */ + r = bump_entry_array(f, NULL, a, first, DIRECTION_UP, &a); + if (r <= 0) + return r; + + r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); + if (r < 0) + return r; + + p = journal_file_entry_array_n_items(f, array); + if (p == 0 || t < p) + return -EBADMSG; + + t -= p; + i = p - 1; found: p = journal_file_entry_array_item(f, array, 0); @@ -3180,27 +3189,6 @@ found: /* Let's cache this item for the next invocation */ chain_cache_put(f->chain_cache, ci, first, a, p, t, i); - if (i == UINT64_MAX) { - uint64_t m; - - /* Get the last entry of the previous array. */ - - r = bump_entry_array(f, NULL, a, first, DIRECTION_UP, &a); - if (r <= 0) - return r; - - r = journal_file_move_to_object(f, OBJECT_ENTRY_ARRAY, a, &array); - if (r < 0) - return r; - - m = journal_file_entry_array_n_items(f, array); - if (m == 0 || t < m) - return -EBADMSG; - - t -= m; - i = m - 1; - } - p = journal_file_entry_array_item(f, array, i); if (p == 0) return -EBADMSG; @@ -3267,7 +3255,7 @@ static int generic_array_bisect_for_data( } else { /* If we are going upwards, then we need to return the last object that passes the test. - * When there is no object that passes the test, we need to return the the last object that + * When there is no object that passes the test, we need to return the last object that * test_object() returns TEST_LEFT for. */ if (r == TEST_RIGHT) return 0; /* Not only the 'extra' object, but also all objects in the chained arrays @@ -3554,7 +3542,8 @@ int journal_file_next_entry( p, test_object_offset, direction, - ret_object ? &o : NULL, &q, &i); + NULL, &q, &i); /* Here, do not read entry object, as the result object + * may not be the one we want, and it may be broken. */ if (r <= 0) return r; @@ -3564,12 +3553,11 @@ int journal_file_next_entry( * the same offset, and the index needs to be shifted. Otherwise, use the found object as is, * as it is the nearest entry object from the input offset 'p'. */ - if (p != q) - goto found; - - r = bump_array_index(&i, direction, n); - if (r <= 0) - return r; + if (p == q) { + r = bump_array_index(&i, direction, n); + if (r <= 0) + return r; + } /* And jump to it */ r = generic_array_get(f, le64toh(f->header->entry_array_offset), i, direction, ret_object ? &o : NULL, &q); @@ -3581,7 +3569,7 @@ int journal_file_next_entry( return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%s: entry array not properly ordered at entry index %" PRIu64, f->path, i); -found: + if (ret_object) *ret_object = o; if (ret_offset) @@ -3813,35 +3801,35 @@ void journal_file_dump(JournalFile *f) { case OBJECT_ENTRY: assert(s); - printf("Type: %s seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", - s, - le64toh(o->entry.seqnum), - le64toh(o->entry.monotonic), - le64toh(o->entry.realtime)); + log_info("Type: %s seqnum=%"PRIu64" monotonic=%"PRIu64" realtime=%"PRIu64"\n", + s, + le64toh(o->entry.seqnum), + le64toh(o->entry.monotonic), + le64toh(o->entry.realtime)); break; case OBJECT_TAG: assert(s); - printf("Type: %s seqnum=%"PRIu64" epoch=%"PRIu64"\n", - s, - le64toh(o->tag.seqnum), - le64toh(o->tag.epoch)); + log_info("Type: %s seqnum=%"PRIu64" epoch=%"PRIu64"\n", + s, + le64toh(o->tag.seqnum), + le64toh(o->tag.epoch)); break; default: if (s) - printf("Type: %s \n", s); + log_info("Type: %s \n", s); else - printf("Type: unknown (%i)", o->object.type); + log_info("Type: unknown (%i)", o->object.type); break; } c = COMPRESSION_FROM_OBJECT(o); if (c > COMPRESSION_NONE) - printf("Flags: %s\n", - compression_to_string(c)); + log_info("Flags: %s\n", + compression_to_string(c)); if (p == le64toh(f->header->tail_object_offset)) p = 0; @@ -4374,11 +4362,9 @@ int journal_file_archive(JournalFile *f, char **ret_previous_path) { (void) fsync_directory_of_file(f->fd); if (ret_previous_path) - *ret_previous_path = f->path; - else - free(f->path); + *ret_previous_path = TAKE_PTR(f->path); - f->path = TAKE_PTR(p); + free_and_replace(f->path, p); /* Set as archive so offlining commits w/state=STATE_ARCHIVED. Previously we would set old_file->header->state * to STATE_ARCHIVED directly here, but journal_file_set_offline() short-circuits when state != STATE_ONLINE, diff --git a/src/libsystemd/sd-journal/journal-file.h b/src/libsystemd/sd-journal/journal-file.h index 81fafb9..8100388 100644 --- a/src/libsystemd/sd-journal/journal-file.h +++ b/src/libsystemd/sd-journal/journal-file.h @@ -129,7 +129,8 @@ typedef struct JournalFile { uint64_t newest_monotonic_usec; uint64_t newest_realtime_usec; unsigned newest_boot_id_prioq_idx; - usec_t newest_mtime; + uint64_t newest_entry_offset; + uint8_t newest_state; } JournalFile; typedef enum JournalFileFlags { @@ -309,7 +310,6 @@ void journal_file_print_header(JournalFile *f); int journal_file_archive(JournalFile *f, char **ret_previous_path); int journal_file_parse_uid_from_filename(const char *path, uid_t *uid); -JournalFile* journal_initiate_close(JournalFile *f, Set *deferred_closes); int journal_file_dispose(int dir_fd, const char *fname); diff --git a/src/libsystemd/sd-journal/journal-internal.h b/src/libsystemd/sd-journal/journal-internal.h index 259aac8..b95080c 100644 --- a/src/libsystemd/sd-journal/journal-internal.h +++ b/src/libsystemd/sd-journal/journal-internal.h @@ -12,7 +12,7 @@ #include "journal-def.h" #include "journal-file.h" #include "list.h" -#include "set.h" +#include "prioq.h" #define JOURNAL_FILES_MAX 7168u @@ -62,12 +62,18 @@ struct Location { }; struct Directory { + sd_journal *journal; char *path; int wd; bool is_root; unsigned last_seen_generation; }; +typedef struct NewestByBootId { + sd_id128_t boot_id; + Prioq *prioq; /* JournalFile objects ordered by monotonic timestamp of last update. */ +} NewestByBootId; + struct sd_journal { int toplevel_fd; @@ -78,7 +84,10 @@ struct sd_journal { OrderedHashmap *files; IteratedCache *files_cache; MMapCache *mmap; - Hashmap *newest_by_boot_id; /* key: boot_id, value: prioq, ordered by monotonic timestamp of last update */ + + /* a bisectable array of NewestByBootId, ordered by boot id. */ + NewestByBootId *newest_by_boot_id; + size_t n_newest_by_boot_id; Location current_location; @@ -86,6 +95,7 @@ struct sd_journal { uint64_t current_field; Match *level0, *level1, *level2; + Set *exclude_syslog_identifiers; uint64_t origin_id; @@ -128,6 +138,10 @@ struct sd_journal { char *journal_make_match_string(sd_journal *j); void journal_print_header(sd_journal *j); +int journal_get_directories(sd_journal *j, char ***ret); + +int journal_add_match_pair(sd_journal *j, const char *field, const char *value); +int journal_add_matchf(sd_journal *j, const char *format, ...) _printf_(2, 3); #define JOURNAL_FOREACH_DATA_RETVAL(j, data, l, retval) \ for (sd_journal_restart_data(j); ((retval) = sd_journal_enumerate_data((j), &(data), &(l))) > 0; ) diff --git a/src/libsystemd/sd-journal/journal-send.c b/src/libsystemd/sd-journal/journal-send.c index be23b2f..7d02b57 100644 --- a/src/libsystemd/sd-journal/journal-send.c +++ b/src/libsystemd/sd-journal/journal-send.c @@ -121,7 +121,7 @@ _public_ int sd_journal_printv(int priority, const char *format, va_list ap) { assert_return(priority <= 7, -EINVAL); assert_return(format, -EINVAL); - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(p, "PRIORITY=%i", LOG_PRI(priority)); va_copy(aq, ap); len = vsnprintf(buffer + 8, LINE_MAX, format, aq); @@ -398,20 +398,28 @@ _public_ int sd_journal_perror(const char *message) { return fill_iovec_perror_and_send(message, 0, iovec); } -_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { +_public_ int sd_journal_stream_fd_with_namespace( + const char *name_space, + const char *identifier, + int priority, + int level_prefix) { + _cleanup_close_ int fd = -EBADF; - char *header; - size_t l; + const char *path; int r; assert_return(priority >= 0, -EINVAL); assert_return(priority <= 7, -EINVAL); + path = journal_stream_path(name_space); + if (!path) + return -EINVAL; + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); if (fd < 0) return -errno; - r = connect_unix_path(fd, AT_FDCWD, "/run/systemd/journal/stdout"); + r = connect_unix_path(fd, AT_FDCWD, path); if (r < 0) return r; @@ -422,6 +430,9 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve identifier = strempty(identifier); + char *header; + size_t l; + l = strlen(identifier); header = newa(char, l + 1 + 1 + 2 + 2 + 2 + 2 + 2); @@ -446,6 +457,10 @@ _public_ int sd_journal_stream_fd(const char *identifier, int priority, int leve return TAKE_FD(fd); } +_public_ int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix) { + return sd_journal_stream_fd_with_namespace(NULL, identifier, priority, level_prefix); +} + _public_ int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) { int r; va_list ap; @@ -470,7 +485,7 @@ _public_ int sd_journal_printv_with_location(int priority, const char *file, con assert_return(priority <= 7, -EINVAL); assert_return(format, -EINVAL); - xsprintf(p, "PRIORITY=%i", priority & LOG_PRIMASK); + xsprintf(p, "PRIORITY=%i", LOG_PRI(priority)); va_copy(aq, ap); len = vsnprintf(buffer + 8, LINE_MAX, format, aq); diff --git a/src/libsystemd/sd-journal/journal-send.h b/src/libsystemd/sd-journal/journal-send.h index 24315e2..6fe6325 100644 --- a/src/libsystemd/sd-journal/journal-send.h +++ b/src/libsystemd/sd-journal/journal-send.h @@ -2,6 +2,23 @@ #pragma once #include +#include + +#include "syslog-util.h" int journal_fd_nonblock(bool nonblock); void close_journal_fd(void); + +/* We declare sd_journal_stream_fd() as async-signal-safe. So instead of strjoin(), which calls malloc() + * internally, use a macro + alloca(). */ +#define journal_stream_path(log_namespace) \ + ({ \ + const char *_ns = (log_namespace), *_ret; \ + if (!_ns) \ + _ret = "/run/systemd/journal/stdout"; \ + else if (log_namespace_name_valid(_ns)) \ + _ret = strjoina("/run/systemd/journal.", _ns, "/stdout"); \ + else \ + _ret = NULL; \ + _ret; \ + }) diff --git a/src/libsystemd/sd-journal/journal-verify.c b/src/libsystemd/sd-journal/journal-verify.c index b5ce55a..e852591 100644 --- a/src/libsystemd/sd-journal/journal-verify.c +++ b/src/libsystemd/sd-journal/journal-verify.c @@ -10,6 +10,7 @@ #include "fd-util.h" #include "fileio.h" #include "fs-util.h" +#include "gcrypt-util.h" #include "journal-authenticate.h" #include "journal-def.h" #include "journal-file.h" @@ -1224,7 +1225,7 @@ int journal_file_verify( if (r < 0) goto fail; - if (memcmp(o->tag.tag, gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { + if (memcmp(o->tag.tag, sym_gcry_md_read(f->hmac, 0), TAG_LENGTH) != 0) { error(p, "Tag failed verification"); r = -EBADMSG; goto fail; diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c index ca1ef0c..0aa3726 100644 --- a/src/libsystemd/sd-journal/sd-journal.c +++ b/src/libsystemd/sd-journal/sd-journal.c @@ -38,12 +38,13 @@ #include "prioq.h" #include "process-util.h" #include "replace-var.h" +#include "sort-util.h" #include "stat-util.h" #include "stdio-util.h" #include "string-util.h" #include "strv.h" #include "syslog-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #define JOURNAL_FILES_RECHECK_USEC (2 * USEC_PER_SEC) @@ -232,7 +233,11 @@ _public_ int sd_journal_add_match(sd_journal *j, const void *data, size_t size) assert_return(!journal_origin_changed(j), -ECHILD); assert_return(data, -EINVAL); - if (size == 0) + /* If the size is unspecified, assume it's a string. Note: 0 is the public value we document for + * this, for historical reasons. Internally, we pretty widely started using SIZE_MAX for this in + * similar cases however, hence accept that too. And internally we actually prefer it, to make things + * less surprising. */ + if (IN_SET(size, 0, SIZE_MAX)) size = strlen(data); if (!match_is_valid(data, size)) @@ -324,6 +329,37 @@ fail: return -ENOMEM; } +int journal_add_match_pair(sd_journal *j, const char *field, const char *value) { + _cleanup_free_ char *s = NULL; + + assert(j); + assert(field); + assert(value); + + s = strjoin(field, "=", value); + if (!s) + return -ENOMEM; + + return sd_journal_add_match(j, s, SIZE_MAX); +} + +int journal_add_matchf(sd_journal *j, const char *format, ...) { + _cleanup_free_ char *s = NULL; + va_list ap; + int r; + + assert(j); + assert(format); + + va_start(ap, format); + r = vasprintf(&s, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return sd_journal_add_match(j, s, SIZE_MAX); +} + _public_ int sd_journal_add_conjunction(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); @@ -413,6 +449,99 @@ _public_ void sd_journal_flush_matches(sd_journal *j) { detach_location(j); } +static int newest_by_boot_id_compare(const NewestByBootId *a, const NewestByBootId *b) { + return id128_compare_func(&a->boot_id, &b->boot_id); +} + +static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) { + NewestByBootId *found; + + assert(j); + assert(f); + + if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */ + return; + + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + assert(found); + + assert_se(prioq_remove(found->prioq, f, &f->newest_boot_id_prioq_idx) > 0); + f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; + + /* The prioq may be empty, but that should not cause any issue. Let's keep it. */ +} + +static void journal_clear_newest_by_boot_id(sd_journal *j) { + FOREACH_ARRAY(i, j->newest_by_boot_id, j->n_newest_by_boot_id) { + JournalFile *f; + + while ((f = prioq_peek(i->prioq))) + journal_file_unlink_newest_by_boot_id(j, f); + + prioq_free(i->prioq); + } + + j->newest_by_boot_id = mfree(j->newest_by_boot_id); + j->n_newest_by_boot_id = 0; +} + +static int journal_file_newest_monotonic_compare(const void *a, const void *b) { + const JournalFile *x = a, *y = b; + + return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */ +} + +static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) { + NewestByBootId *found; + int r; + + assert(j); + assert(f); + + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = f->newest_boot_id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + if (found) { + /* There's already a priority queue for this boot ID */ + + if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) { + r = prioq_put(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */ + if (r < 0) + return r; + } else + prioq_reshuffle(found->prioq, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */ + + } else { + _cleanup_(prioq_freep) Prioq *q = NULL; + + /* No priority queue yet, then allocate one */ + + assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */ + + q = prioq_new(journal_file_newest_monotonic_compare); + if (!q) + return -ENOMEM; + + r = prioq_put(q, f, &f->newest_boot_id_prioq_idx); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(j->newest_by_boot_id, j->n_newest_by_boot_id + 1)) { + f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; + return -ENOMEM; + } + + j->newest_by_boot_id[j->n_newest_by_boot_id++] = (NewestByBootId) { + .boot_id = f->newest_boot_id, + .prioq = TAKE_PTR(q), + }; + + typesafe_qsort(j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + } + + return 0; +} + static int journal_file_find_newest_for_boot_id( sd_journal *j, sd_id128_t id, @@ -427,16 +556,17 @@ static int journal_file_find_newest_for_boot_id( /* Before we use it, let's refresh the timestamp from the header, and reshuffle our prioq * accordingly. We do this only a bunch of times, to not be caught in some update loop. */ for (unsigned n_tries = 0;; n_tries++) { + NewestByBootId *found; JournalFile *f; - Prioq *q; - q = hashmap_get(j->newest_by_boot_id, &id); - if (!q) + found = typesafe_bsearch(&(NewestByBootId) { .boot_id = id }, + j->newest_by_boot_id, j->n_newest_by_boot_id, newest_by_boot_id_compare); + + f = found ? prioq_peek(found->prioq) : NULL; + if (!f) return log_debug_errno(SYNTHETIC_ERRNO(ENODATA), "Requested delta for boot ID %s, but we have no information about that boot ID.", SD_ID128_TO_STRING(id)); - assert_se(f = prioq_peek(q)); /* we delete hashmap entries once the prioq is empty, so this must hold */ - if (f == prev || n_tries >= 5) { /* This was already the best answer in the previous run, or we tried too often, use it */ *ret = f; @@ -449,6 +579,11 @@ static int journal_file_find_newest_for_boot_id( r = journal_file_read_tail_timestamp(j, f); if (r < 0) return log_debug_errno(r, "Failed to read tail timestamp while trying to find newest journal file for boot ID %s.", SD_ID128_TO_STRING(id)); + if (r == 0) { + /* No new entry found. */ + *ret = f; + return 0; + } /* Refreshing the timestamp we read might have reshuffled the prioq, hence let's check the * prioq again and only use the information once we reached an equilibrium or hit a limit */ @@ -932,8 +1067,8 @@ static int real_journal_next(sd_journal *j, direction_t direction) { if (r < 0) return r; - for (unsigned i = 0; i < n_files; i++) { - JournalFile *f = (JournalFile *)files[i]; + FOREACH_ARRAY(_f, files, n_files) { + JournalFile *f = (JournalFile*) *_f; bool found; r = next_beyond_location(j, f, direction); @@ -1373,17 +1508,6 @@ static void track_file_disposition(sd_journal *j, JournalFile *f) { j->has_persistent_files = true; } -static const char *skip_slash(const char *p) { - - if (!p) - return NULL; - - while (*p == '/') - p++; - - return p; -} - static int add_any_file( sd_journal *j, int fd, @@ -1403,7 +1527,7 @@ static int add_any_file( /* If there's a top-level fd defined make the path relative, explicitly, since otherwise * openat() ignores the first argument. */ - fd = our_fd = openat(j->toplevel_fd, skip_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK); + fd = our_fd = openat(j->toplevel_fd, skip_leading_slash(path), O_RDONLY|O_CLOEXEC|O_NONBLOCK); else fd = our_fd = open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK); if (fd < 0) { @@ -1494,6 +1618,41 @@ error: return r; } +int journal_get_directories(sd_journal *j, char ***ret) { + _cleanup_strv_free_ char **paths = NULL; + JournalFile *f; + const char *p; + size_t n = SIZE_MAX; + int r; + + assert(j); + assert(ret); + + /* This returns parent directories of opened journal files. */ + + ORDERED_HASHMAP_FOREACH_KEY(f, p, j->files) { + _cleanup_free_ char *d = NULL; + + /* Ignore paths generated from fd. */ + if (path_startswith(p, "/proc/")) + continue; + + r = path_extract_directory(p, &d); + if (r < 0) + return r; + + if (path_strv_contains(paths, d)) + continue; + + r = strv_extend_with_size(&paths, &n, d); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(paths); + return 0; +} + static int add_file_by_name( sd_journal *j, const char *prefix, @@ -1676,7 +1835,7 @@ static int directory_open(sd_journal *j, const char *path, DIR **ret) { else /* Open the specified directory relative to the toplevel fd. Enforce that the path specified is * relative, by dropping the initial slash */ - d = xopendirat(j->toplevel_fd, skip_slash(path), 0); + d = xopendirat(j->toplevel_fd, skip_leading_slash(path), 0); if (!d) return -errno; @@ -1684,6 +1843,100 @@ static int directory_open(sd_journal *j, const char *path, DIR **ret) { return 0; } +static Directory* directory_free(Directory *d) { + if (!d) + return NULL; + + if (d->journal) { + if (d->wd > 0 && + hashmap_remove_value(d->journal->directories_by_wd, INT_TO_PTR(d->wd), d) && + d->journal->inotify_fd >= 0) + (void) inotify_rm_watch(d->journal->inotify_fd, d->wd); + + if (d->path) + hashmap_remove_value(d->journal->directories_by_path, d->path, d); + } + + if (d->path) { + if (d->is_root) + log_debug("Root directory %s removed.", d->path); + else + log_debug("Directory %s removed.", d->path); + + free(d->path); + } + + return mfree(d); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Directory*, directory_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + directories_by_path_hash_ops, + char, + path_hash_func, + path_compare, + Directory, + directory_free); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + directories_by_wd_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + Directory, + directory_free); + +static int add_directory_impl(sd_journal *j, const char *path, bool is_root, Directory **ret) { + _cleanup_(directory_freep) Directory *m = NULL; + Directory *existing; + int r; + + assert(j); + assert(path); + assert(ret); + + existing = hashmap_get(j->directories_by_path, path); + if (existing) { + if (existing->is_root != is_root) { + /* Don't 'downgrade' from root directory */ + *ret = NULL; + return 0; + } + + *ret = existing; + return 1; + } + + m = new(Directory, 1); + if (!m) + return -ENOMEM; + + *m = (Directory) { + .journal = j, + .is_root = is_root, + .path = strdup(path), + .wd = -1, + }; + + if (!m->path) + return -ENOMEM; + + r = hashmap_ensure_put(&j->directories_by_path, &directories_by_path_hash_ops, m->path, m); + if (r < 0) + return r; + + j->current_invalidate_counter++; + + if (is_root) + log_debug("Root directory %s added.", m->path); + else + log_debug("Directory %s added.", m->path); + + *ret = TAKE_PTR(m); + return 1; +} + static int add_directory(sd_journal *j, const char *prefix, const char *dirname); static void directory_enumerate(sd_journal *j, Directory *m, DIR *d) { @@ -1724,12 +1977,14 @@ static void directory_watch(sd_journal *j, Directory *m, int fd, uint32_t mask) return; } - r = hashmap_put(j->directories_by_wd, INT_TO_PTR(m->wd), m); - if (r == -EEXIST) - log_debug_errno(r, "Directory '%s' already being watched under a different path, ignoring: %m", m->path); + r = hashmap_ensure_put(&j->directories_by_wd, &directories_by_wd_hash_ops, INT_TO_PTR(m->wd), m); if (r < 0) { - log_debug_errno(r, "Failed to add watch for journal directory '%s' to hashmap, ignoring: %m", m->path); - (void) inotify_rm_watch(j->inotify_fd, m->wd); + if (r == -EEXIST) + log_debug_errno(r, "Directory '%s' already being watched under a different path, ignoring: %m", m->path); + else { + log_debug_errno(r, "Failed to add watch for journal directory '%s' to hashmap, ignoring: %m", m->path); + (void) inotify_rm_watch(j->inotify_fd, m->wd); + } m->wd = -1; } } @@ -1775,32 +2030,11 @@ static int add_directory( goto fail; } - m = hashmap_get(j->directories_by_path, path); - if (!m) { - m = new(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - *m = (Directory) { - .is_root = false, - .path = path, - }; - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m); - r = -ENOMEM; - goto fail; - } - - path = NULL; /* avoid freeing in cleanup */ - j->current_invalidate_counter++; - - log_debug("Directory %s added.", m->path); - - } else if (m->is_root) - return 0; /* Don't 'downgrade' from root directory */ + r = add_directory_impl(j, path, /* is_root = */ false, &m); + if (r < 0) + goto fail; + if (r == 0) + return 0; m->last_seen_generation = j->generation; @@ -1878,35 +2112,10 @@ static int add_root_directory(sd_journal *j, const char *p, bool missing_ok) { rewinddir(d); } - m = hashmap_get(j->directories_by_path, p); - if (!m) { - m = new0(Directory, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->is_root = true; - - m->path = strdup(p); - if (!m->path) { - free(m); - r = -ENOMEM; - goto fail; - } - - if (hashmap_put(j->directories_by_path, m->path, m) < 0) { - free(m->path); - free(m); - r = -ENOMEM; - goto fail; - } - - j->current_invalidate_counter++; - - log_debug("Root directory %s added.", m->path); - - } else if (!m->is_root) + r = add_directory_impl(j, p, /* is_root = */ true, &m); + if (r < 0) + goto fail; + if (r == 0) return 0; directory_watch(j, m, dirfd(d), @@ -1928,27 +2137,6 @@ fail: return r; } -static void remove_directory(sd_journal *j, Directory *d) { - assert(j); - - if (d->wd > 0) { - hashmap_remove(j->directories_by_wd, INT_TO_PTR(d->wd)); - - if (j->inotify_fd >= 0) - (void) inotify_rm_watch(j->inotify_fd, d->wd); - } - - hashmap_remove(j->directories_by_path, d->path); - - if (d->is_root) - log_debug("Root directory %s removed.", d->path); - else - log_debug("Directory %s removed.", d->path); - - free(d->path); - free(d); -} - static int add_search_paths(sd_journal *j) { static const char search_paths[] = @@ -2003,7 +2191,7 @@ static int allocate_inotify(sd_journal *j) { return -errno; } - return hashmap_ensure_allocated(&j->directories_by_wd, NULL); + return 0; } static sd_journal *journal_new(int flags, const char *path, const char *namespace) { @@ -2045,9 +2233,8 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac return NULL; j->files_cache = ordered_hashmap_iterated_cache_new(j->files); - j->directories_by_path = hashmap_new(&path_hash_ops); j->mmap = mmap_cache_new(); - if (!j->files_cache || !j->directories_by_path || !j->mmap) + if (!j->files_cache || !j->mmap) return NULL; return TAKE_PTR(j); @@ -2059,7 +2246,8 @@ static sd_journal *journal_new(int flags, const char *path, const char *namespac SD_JOURNAL_SYSTEM | \ SD_JOURNAL_CURRENT_USER | \ SD_JOURNAL_ALL_NAMESPACES | \ - SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE) + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_namespace(sd_journal **ret, const char *namespace, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2085,7 +2273,9 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) { } #define OPEN_CONTAINER_ALLOWED_FLAGS \ - (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM) + (SD_JOURNAL_LOCAL_ONLY | \ + SD_JOURNAL_SYSTEM | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) { _cleanup_free_ char *root = NULL, *class = NULL; @@ -2129,7 +2319,9 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in #define OPEN_DIRECTORY_ALLOWED_FLAGS \ (SD_JOURNAL_OS_ROOT | \ - SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER ) + SD_JOURNAL_SYSTEM | \ + SD_JOURNAL_CURRENT_USER | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2154,12 +2346,15 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f return 0; } +#define OPEN_FILES_ALLOWED_FLAGS \ + (SD_JOURNAL_ASSUME_IMMUTABLE) + _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; int r; assert_return(ret, -EINVAL); - assert_return(flags == 0, -EINVAL); + assert_return((flags & ~OPEN_FILES_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, NULL, NULL); if (!j) @@ -2181,7 +2376,8 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla (SD_JOURNAL_OS_ROOT | \ SD_JOURNAL_SYSTEM | \ SD_JOURNAL_CURRENT_USER | \ - SD_JOURNAL_TAKE_DIRECTORY_FD) + SD_JOURNAL_TAKE_DIRECTORY_FD | \ + SD_JOURNAL_ASSUME_IMMUTABLE) _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2219,6 +2415,9 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) { return 0; } +#define OPEN_FILES_FD_ALLOWED_FLAGS \ + (SD_JOURNAL_ASSUME_IMMUTABLE) + _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags) { JournalFile *f; _cleanup_(sd_journal_closep) sd_journal *j = NULL; @@ -2226,7 +2425,7 @@ _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fd assert_return(ret, -EINVAL); assert_return(n_fds > 0, -EBADF); - assert_return(flags == 0, -EINVAL); + assert_return((flags & ~OPEN_FILES_FD_ALLOWED_FLAGS) == 0, -EINVAL); j = journal_new(flags, NULL, NULL); if (!j) @@ -2270,27 +2469,16 @@ fail: } _public_ void sd_journal_close(sd_journal *j) { - Directory *d; - Prioq *p; - if (!j || journal_origin_changed(j)) return; - while ((p = hashmap_first(j->newest_by_boot_id))) - journal_file_unlink_newest_by_boot_id(j, prioq_peek(p)); - hashmap_free(j->newest_by_boot_id); + journal_clear_newest_by_boot_id(j); sd_journal_flush_matches(j); ordered_hashmap_free_with_destructor(j->files, journal_file_close); iterated_cache_free(j->files_cache); - while ((d = hashmap_first(j->directories_by_path))) - remove_directory(j, d); - - while ((d = hashmap_first(j->directories_by_wd))) - remove_directory(j, d); - hashmap_free(j->directories_by_path); hashmap_free(j->directories_by_wd); @@ -2306,6 +2494,8 @@ _public_ void sd_journal_close(sd_journal *j) { hashmap_free_free(j->errors); + set_free(j->exclude_syslog_identifiers); + free(j->path); free(j->prefix); free(j->namespace); @@ -2314,84 +2504,6 @@ _public_ void sd_journal_close(sd_journal *j) { free(j); } -static void journal_file_unlink_newest_by_boot_id(sd_journal *j, JournalFile *f) { - JournalFile *nf; - Prioq *p; - - assert(j); - assert(f); - - if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) /* not linked currently, hence this is a NOP */ - return; - - assert_se(p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id)); - assert_se(prioq_remove(p, f, &f->newest_boot_id_prioq_idx) > 0); - - nf = prioq_peek(p); - if (nf) - /* There's still a member in the prioq? Then make sure the hashmap key now points to its - * .newest_boot_id field (and not ours!). Not we only replace the memory of the key here, the - * value of the key (and the data associated with it) remain the same. */ - assert_se(hashmap_replace(j->newest_by_boot_id, &nf->newest_boot_id, p) >= 0); - else { - assert_se(hashmap_remove(j->newest_by_boot_id, &f->newest_boot_id) == p); - prioq_free(p); - } - - f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; -} - -static int journal_file_newest_monotonic_compare(const void *a, const void *b) { - const JournalFile *x = a, *y = b; - - return -CMP(x->newest_monotonic_usec, y->newest_monotonic_usec); /* Invert order, we want newest first! */ -} - -static int journal_file_reshuffle_newest_by_boot_id(sd_journal *j, JournalFile *f) { - Prioq *p; - int r; - - assert(j); - assert(f); - - p = hashmap_get(j->newest_by_boot_id, &f->newest_boot_id); - if (p) { - /* There's already a priority queue for this boot ID */ - - if (f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL) { - r = prioq_put(p, f, &f->newest_boot_id_prioq_idx); /* Insert if we aren't in there yet */ - if (r < 0) - return r; - } else - prioq_reshuffle(p, f, &f->newest_boot_id_prioq_idx); /* Reshuffle otherwise */ - - } else { - _cleanup_(prioq_freep) Prioq *q = NULL; - - /* No priority queue yet, then allocate one */ - - assert(f->newest_boot_id_prioq_idx == PRIOQ_IDX_NULL); /* we can't be a member either */ - - q = prioq_new(journal_file_newest_monotonic_compare); - if (!q) - return -ENOMEM; - - r = prioq_put(q, f, &f->newest_boot_id_prioq_idx); - if (r < 0) - return r; - - r = hashmap_ensure_put(&j->newest_by_boot_id, &id128_hash_ops, &f->newest_boot_id, q); - if (r < 0) { - f->newest_boot_id_prioq_idx = PRIOQ_IDX_NULL; - return r; - } - - TAKE_PTR(q); - } - - return 0; -} - static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { uint64_t offset, mo, rt; sd_id128_t id; @@ -2405,11 +2517,13 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { /* Tries to read the timestamp of the most recently written entry. */ - r = journal_file_fstat(f); - if (r < 0) - return r; - if (f->newest_mtime == timespec_load(&f->last_stat.st_mtim)) - return 0; /* mtime didn't change since last time, don't bother */ + if (FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE) && f->newest_entry_offset != 0) + return 0; /* We have already read the file, and we assume that the file is immutable. */ + + if (f->header->state == f->newest_state && + f->header->state == STATE_ARCHIVED && + f->newest_entry_offset != 0) + return 0; /* We have already read archived file. */ if (JOURNAL_HEADER_CONTAINS(f->header, tail_entry_offset)) { offset = le64toh(READ_NOW(f->header->tail_entry_offset)); @@ -2420,6 +2534,8 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { } if (offset == 0) return -ENODATA; /* not a single object/entry, hence no tail timestamp */ + if (offset == f->newest_entry_offset) + return 0; /* No new entry is added after we read last time. */ /* Move to the last object in the journal file, in the hope it is an entry (which it usually will * be). If we lack the "tail_entry_offset" field in the header, we specify the type as OBJECT_UNUSED @@ -2429,6 +2545,7 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { if (r < 0) { log_debug_errno(r, "Failed to move to last object in journal file, ignoring: %m"); o = NULL; + offset = 0; } if (o && o->object.type == OBJECT_ENTRY) { /* Yay, last object is an entry, let's use the data. */ @@ -2446,10 +2563,11 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { mo = le64toh(f->header->tail_entry_monotonic); rt = le64toh(f->header->tail_entry_realtime); id = f->header->tail_entry_boot_id; + offset = UINT64_MAX; } else { /* Otherwise let's find the last entry manually (this possibly means traversing the * chain of entry arrays, till the end */ - r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, NULL); + r = journal_file_next_entry(f, 0, DIRECTION_UP, &o, offset == 0 ? &offset : NULL); if (r < 0) return r; if (r == 0) @@ -2464,6 +2582,17 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { if (mo > rt) /* monotonic clock is further ahead than realtime? that's weird, refuse to use the data */ return -ENODATA; + if (offset == f->newest_entry_offset) { + /* Cached data and the current one should be equivalent. */ + if (!sd_id128_equal(f->newest_machine_id, f->header->machine_id) || + !sd_id128_equal(f->newest_boot_id, id) || + f->newest_monotonic_usec != mo || + f->newest_realtime_usec != rt) + return -EBADMSG; + + return 0; /* No new entry is added after we read last time. */ + } + if (!sd_id128_equal(f->newest_boot_id, id)) journal_file_unlink_newest_by_boot_id(j, f); @@ -2471,13 +2600,14 @@ static int journal_file_read_tail_timestamp(sd_journal *j, JournalFile *f) { f->newest_monotonic_usec = mo; f->newest_realtime_usec = rt; f->newest_machine_id = f->header->machine_id; - f->newest_mtime = timespec_load(&f->last_stat.st_mtim); + f->newest_entry_offset = offset; + f->newest_state = f->header->state; r = journal_file_reshuffle_newest_by_boot_id(j, f); if (r < 0) return r; - return 0; + return 1; /* Updated. */ } _public_ int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret) { @@ -2526,9 +2656,7 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (r < 0) return r; - if (ret_boot_id) - *ret_boot_id = o->entry.boot_id; - else { + if (!ret_boot_id) { sd_id128_t id; r = sd_id128_get_boot(&id); @@ -2545,6 +2673,8 @@ _public_ int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id12 if (ret) *ret = t; + if (ret_boot_id) + *ret_boot_id = o->entry.boot_id; return 0; } @@ -2748,6 +2878,7 @@ _public_ int sd_journal_get_fd(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); if (j->no_inotify) return -EMEDIUMTYPE; @@ -2774,6 +2905,7 @@ _public_ int sd_journal_get_events(sd_journal *j) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); fd = sd_journal_get_fd(j); if (fd < 0) @@ -2787,6 +2919,7 @@ _public_ int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); assert_return(timeout_usec, -EINVAL); fd = sd_journal_get_fd(j); @@ -2839,7 +2972,7 @@ static void process_q_overflow(sd_journal *j) { continue; log_debug("Directory '%s' hasn't been seen in this enumeration, removing.", f->path); - remove_directory(j, m); + directory_free(m); } log_debug("Reiteration complete."); @@ -2875,7 +3008,7 @@ static void process_inotify_event(sd_journal *j, const struct inotify_event *e) /* Event for a subdirectory */ if (e->mask & (IN_DELETE_SELF|IN_MOVE_SELF|IN_UNMOUNT)) - remove_directory(j, d); + directory_free(d); } else if (d->is_root && (e->mask & IN_ISDIR) && e->len > 0 && id128_is_valid(e->name)) { @@ -2914,6 +3047,8 @@ _public_ int sd_journal_process(sd_journal *j) { if (j->inotify_fd < 0) /* We have no inotify fd yet? Then there's noting to process. */ return 0; + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); + j->last_process_usec = now(CLOCK_MONOTONIC); j->last_invalidate_counter = j->current_invalidate_counter; @@ -2942,6 +3077,7 @@ _public_ int sd_journal_wait(sd_journal *j, uint64_t timeout_usec) { assert_return(j, -EINVAL); assert_return(!journal_origin_changed(j), -ECHILD); + assert_return(!FLAGS_SET(j->flags, SD_JOURNAL_ASSUME_IMMUTABLE), -EUNATCH); if (j->inotify_fd < 0) { JournalFile *f; diff --git a/src/libsystemd/sd-journal/test-journal-append.c b/src/libsystemd/sd-journal/test-journal-append.c index 24b98c8..b7e7c78 100644 --- a/src/libsystemd/sd-journal/test-journal-append.c +++ b/src/libsystemd/sd-journal/test-journal-append.c @@ -62,7 +62,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { log_debug("Opening journal %s/system.journal", tempdir); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "system.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, @@ -114,7 +114,7 @@ static int journal_corrupt_and_append(uint64_t start_offset, uint64_t step) { * the corrupted journal */ mj = journal_file_offline_close(mj); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "system.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, diff --git a/src/libsystemd/sd-journal/test-journal-enum.c b/src/libsystemd/sd-journal/test-journal-enum.c index 03fe8e2..d101fe7 100644 --- a/src/libsystemd/sd-journal/test-journal-enum.c +++ b/src/libsystemd/sd-journal/test-journal-enum.c @@ -15,10 +15,10 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY) >= 0); + assert_se(sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", 0) >= 0); - assert_se(sd_journal_add_match(j, "_UID=0", 0) >= 0); + assert_se(sd_journal_add_match(j, "_TRANSPORT=syslog", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "_UID=0", SIZE_MAX) >= 0); SD_JOURNAL_FOREACH_BACKWARDS(j) { const void *d; diff --git a/src/libsystemd/sd-journal/test-journal-flush.c b/src/libsystemd/sd-journal/test-journal-flush.c index 3f07835..b06645a 100644 --- a/src/libsystemd/sd-journal/test-journal-flush.c +++ b/src/libsystemd/sd-journal/test-journal-flush.c @@ -7,6 +7,8 @@ #include "alloc-util.h" #include "chattr-util.h" +#include "dirent-util.h" +#include "fd-util.h" #include "journal-file-util.h" #include "journal-internal.h" #include "logs-show.h" @@ -17,6 +19,83 @@ #include "tests.h" #include "tmpfile-util.h" +static int open_archive_file(sd_journal **ret) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_close_ int newest_fd = -EBADF; + unsigned long long newest_realtime = 0; + bool newest_is_system = false; + sd_id128_t machine_id; + const char *p; + int r; + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + p = strjoina("/var/log/journal/", SD_ID128_TO_STRING(machine_id), "/"); + + d = opendir(p); + if (!d) + return -errno; + + FOREACH_DIRENT_ALL(de, d, return -errno) { + unsigned long long realtime; + bool is_system; + size_t q; + int fd; + + if (!dirent_is_file_with_suffix(de, ".journal")) + continue; + + is_system = startswith(de->d_name, "system@"); + if (newest_is_system && !is_system) + continue; + + q = strlen(de->d_name); + + if (q < 1 + 32 + 1 + 16 + 1 + 16 + 8) + continue; + + if (de->d_name[q-8-16-1] != '-' || + de->d_name[q-8-16-1-16-1] != '-' || + de->d_name[q-8-16-1-16-1-32-1] != '@') + continue; + + if (sscanf(de->d_name + q-8-16, "%16llx.journal", &realtime) != 1) + continue; + + if (newest_realtime >= realtime) + continue; + + fd = openat(dirfd(d), de->d_name, O_CLOEXEC | O_NONBLOCK | O_RDONLY); + if (fd < 0) { + log_info_errno(errno, "Failed to open /var/log/journal/%s, ignoring: %m", de->d_name); + continue; + } + + close_and_replace(newest_fd, fd); + newest_realtime = realtime; + newest_is_system = is_system; + } + + if (newest_fd < 0) + return log_info_errno(SYNTHETIC_ERRNO(ENOENT), "No archive journal found."); + + r = sd_journal_open_files_fd(ret, &newest_fd, 1, SD_JOURNAL_ASSUME_IMMUTABLE); + + _cleanup_free_ char *path = NULL; + (void) fd_get_path(newest_fd, &path); + + if (r < 0) + log_info_errno(r, "Failed to open %s, ignoring: %m", strna(path)); + else { + log_info("Opened %s.", strna(path)); + TAKE_FD(newest_fd); + } + + return r; +} + static void test_journal_flush_one(int argc, char *argv[]) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; _cleanup_free_ char *fn = NULL; @@ -32,13 +111,16 @@ static void test_journal_flush_one(int argc, char *argv[]) { assert_se(fn = path_join(dn, "test.journal")); - r = journal_file_open(-1, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); + r = journal_file_open(-EBADF, fn, O_CREAT|O_RDWR, 0, 0644, 0, NULL, m, NULL, &new_journal); assert_se(r >= 0); if (argc > 1) - r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), 0); - else - r = sd_journal_open(&j, 0); + r = sd_journal_open_files(&j, (const char **) strv_skip(argv, 1), SD_JOURNAL_ASSUME_IMMUTABLE); + else { + r = open_archive_file(&j); + if (r < 0) + r = sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE); + } assert_se(r == 0); sd_journal_set_data_threshold(j, 0); @@ -75,7 +157,7 @@ static void test_journal_flush_one(int argc, char *argv[]) { /* Open the new journal before archiving and offlining the file. */ sd_journal_close(j); - assert_se(sd_journal_open_directory(&j, dn, 0) >= 0); + assert_se(sd_journal_open_directory(&j, dn, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); /* Read the online journal. */ assert_se(sd_journal_seek_tail(j) >= 0); diff --git a/src/libsystemd/sd-journal/test-journal-init.c b/src/libsystemd/sd-journal/test-journal-init.c index c8a1977..ef66efd 100644 --- a/src/libsystemd/sd-journal/test-journal-init.c +++ b/src/libsystemd/sd-journal/test-journal-init.c @@ -31,12 +31,12 @@ int main(int argc, char *argv[]) { (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); for (i = 0; i < I; i++) { - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); assert_se(r == 0); sd_journal_close(j); - r = sd_journal_open_directory(&j, t, 0); + r = sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE); assert_se(r == 0); assert_se(sd_journal_seek_head(j) == 0); @@ -45,8 +45,8 @@ int main(int argc, char *argv[]) { 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_RETURN_EXPECTED_SE(sd_journal_get_realtime_usec(j, NULL) == -ECHILD); + ASSERT_RETURN_EXPECTED_SE(sd_journal_seek_tail(j) == -ECHILD); assert_se(j->current_location.type == LOCATION_HEAD); sd_journal_close(j); _exit(EXIT_SUCCESS); @@ -57,8 +57,7 @@ int main(int argc, char *argv[]) { sd_journal_close(j); j = NULL; - r = sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY); - assert_se(r == -EINVAL); + ASSERT_RETURN_EXPECTED(assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_LOCAL_ONLY) == -EINVAL)); assert_se(j == NULL); } diff --git a/src/libsystemd/sd-journal/test-journal-interleaving.c b/src/libsystemd/sd-journal/test-journal-interleaving.c index 8aeef8f..d98b3ce 100644 --- a/src/libsystemd/sd-journal/test-journal-interleaving.c +++ b/src/libsystemd/sd-journal/test-journal-interleaving.c @@ -43,7 +43,7 @@ static JournalFile *test_open_internal(const char *name, JournalFileFlags flags) m = mmap_cache_new(); assert_se(m != NULL); - assert_ret(journal_file_open(-1, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); + assert_ret(journal_file_open(-EBADF, name, O_RDWR|O_CREAT, flags, 0644, UINT64_MAX, NULL, m, NULL, &f)); return f; } @@ -74,9 +74,9 @@ static void test_done(const char *t) { } static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint64_t *seqnum, uint64_t *ret_offset) { - _cleanup_free_ char *p = NULL, *q = NULL; + _cleanup_free_ char *p = NULL, *q = NULL, *s = NULL; dual_timestamp ts; - struct iovec iovec[2]; + struct iovec iovec[3]; size_t n_iov = 0; dual_timestamp_now(&ts); @@ -92,6 +92,9 @@ static void append_number(JournalFile *f, int n, const sd_id128_t *boot_id, uint assert_se(asprintf(&p, "NUMBER=%d", n) >= 0); iovec[n_iov++] = IOVEC_MAKE_STRING(p); + assert_se(s = strjoin("LESS_THAN_FIVE=%d", yes_no(n < 5))); + iovec[n_iov++] = IOVEC_MAKE_STRING(s); + if (boot_id) { assert_se(q = strjoin("_BOOT_ID=", SD_ID128_TO_STRING(*boot_id))); iovec[n_iov++] = IOVEC_MAKE_STRING(q); @@ -250,6 +253,37 @@ static void mkdtemp_chdir_chattr(char *path) { (void) chattr_path(path, FS_NOCOW_FL, FS_NOCOW_FL, NULL); } +static void test_cursor(sd_journal *j) { + _cleanup_strv_free_ char **cursors = NULL; + int r; + + assert_se(sd_journal_seek_head(j) >= 0); + + for (;;) { + r = sd_journal_next(j); + assert_se(r >= 0); + if (r == 0) + break; + + _cleanup_free_ char *cursor = NULL; + assert_se(sd_journal_get_cursor(j, &cursor) >= 0); + assert_se(sd_journal_test_cursor(j, cursor) > 0); + assert_se(strv_consume(&cursors, TAKE_PTR(cursor)) >= 0); + } + + STRV_FOREACH(c, cursors) { + assert_se(sd_journal_seek_cursor(j, *c) >= 0); + assert_se(sd_journal_next(j) >= 0); + assert_se(sd_journal_test_cursor(j, *c) > 0); + } + + assert_se(sd_journal_seek_head(j) >= 0); + STRV_FOREACH(c, cursors) { + assert_se(sd_journal_next(j) >= 0); + assert_se(sd_journal_test_cursor(j, *c) > 0); + } +} + static void test_skip_one(void (*setup)(void)) { char t[] = "/var/tmp/journal-skip-XXXXXX"; sd_journal *j; @@ -260,14 +294,14 @@ static void test_skip_one(void (*setup)(void)) { setup(); /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ test_check_numbers_down(j, 9); sd_journal_close(j); /* Seek to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ assert_se(sd_journal_previous(j) == 0); /* no-op */ @@ -275,7 +309,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head twice, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ assert_ret(sd_journal_seek_head(j)); @@ -284,7 +318,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, move to previous, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); /* no-op */ assert_se(sd_journal_next(j) == 1); /* pointing to the first entry */ @@ -292,7 +326,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, walk several steps, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); /* no-op */ assert_se(sd_journal_previous(j) == 0); /* no-op */ @@ -304,14 +338,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ test_check_numbers_up(j, 9); sd_journal_close(j); /* Seek to tail twice, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ assert_ret(sd_journal_seek_tail(j)); @@ -320,7 +354,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, move to next, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); /* no-op */ assert_se(sd_journal_previous(j) == 1); /* pointing to the last entry */ @@ -328,7 +362,7 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, walk several steps, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); /* no-op */ assert_se(sd_journal_next(j) == 0); /* no-op */ @@ -340,14 +374,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to tail, skip to head, iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_previous_skip(j, 9) == 9); /* pointing to the first entry. */ test_check_numbers_down(j, 9); sd_journal_close(j); /* Seek to tail, skip to head in a more complex way, then iterate down. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_tail(j)); assert_se(sd_journal_next(j) == 0); assert_se(sd_journal_previous_skip(j, 4) == 4); @@ -366,14 +400,14 @@ static void test_skip_one(void (*setup)(void)) { sd_journal_close(j); /* Seek to head, skip to tail, iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_next_skip(j, 9) == 9); test_check_numbers_up(j, 9); sd_journal_close(j); /* Seek to head, skip to tail in a more complex way, then iterate up. */ - assert_ret(sd_journal_open_directory(&j, t, 0)); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); assert_ret(sd_journal_seek_head(j)); assert_se(sd_journal_previous(j) == 0); assert_se(sd_journal_next_skip(j, 4) == 4); @@ -391,6 +425,30 @@ static void test_skip_one(void (*setup)(void)) { test_check_numbers_up(j, 9); sd_journal_close(j); + /* For issue #31516. */ + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=no", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=hoge", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0); + test_cursor(j); + sd_journal_flush_matches(j); + assert_se(sd_journal_add_match(j, "LESS_THAN_FIVE=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=3", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=4", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=5", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=6", SIZE_MAX) >= 0); + test_cursor(j); + test_done(t); } @@ -401,7 +459,7 @@ TEST(skip) { static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { char t[] = "/var/tmp/journal-boot-id-XXXXXX"; - sd_journal *j; + _cleanup_(sd_journal_closep) sd_journal *j = NULL; _cleanup_free_ BootId *boots = NULL; size_t n_boots; @@ -409,28 +467,59 @@ static void test_boot_id_one(void (*setup)(void), size_t n_boots_expected) { setup(); - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_get_boots(j, &boots, &n_boots) >= 0); + assert_ret(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE)); + assert_se(journal_get_boots( + j, + /* advance_older = */ false, /* max_ids = */ SIZE_MAX, + &boots, &n_boots) >= 0); assert_se(boots); assert_se(n_boots == n_boots_expected); - sd_journal_close(j); - FOREACH_ARRAY(b, boots, n_boots) { - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_id(j, b->id) == 1); - sd_journal_close(j); + for (size_t i = 0; i < n_boots; i++) { + sd_id128_t id; + + /* positive offset */ + assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1), &id) == 1); + assert_se(sd_id128_equal(id, boots[i].id)); + + /* negative offset */ + assert_se(journal_find_boot(j, SD_ID128_NULL, (int) (i + 1) - (int) n_boots, &id) == 1); + assert_se(sd_id128_equal(id, boots[i].id)); + + for (size_t k = 0; k < n_boots; k++) { + int offset = (int) k - (int) i; + + /* relative offset */ + assert_se(journal_find_boot(j, boots[i].id, offset, &id) == 1); + assert_se(sd_id128_equal(id, boots[k].id)); + } } - for (int i = - (int) n_boots + 1; i <= (int) n_boots; i++) { - sd_id128_t id; + for (size_t i = 0; i <= n_boots_expected + 1; i++) { + _cleanup_free_ BootId *boots_limited = NULL; + size_t n_boots_limited; + + assert_se(journal_get_boots( + j, + /* advance_older = */ false, /* max_ids = */ i, + &boots_limited, &n_boots_limited) >= 0); + assert_se(boots_limited || i == 0); + assert_se(n_boots_limited == MIN(i, n_boots_expected)); + assert_se(memcmp_safe(boots, boots_limited, n_boots_limited * sizeof(BootId)) == 0); + } - assert_ret(sd_journal_open_directory(&j, t, 0)); - assert_se(journal_find_boot_by_offset(j, i, &id) == 1); - if (i <= 0) - assert_se(sd_id128_equal(id, boots[n_boots + i - 1].id)); - else - assert_se(sd_id128_equal(id, boots[i - 1].id)); - sd_journal_close(j); + for (size_t i = 0; i <= n_boots_expected + 1; i++) { + _cleanup_free_ BootId *boots_limited = NULL; + size_t n_boots_limited; + + assert_se(journal_get_boots( + j, + /* advance_older = */ true, /* max_ids = */ i, + &boots_limited, &n_boots_limited) >= 0); + assert_se(boots_limited || i == 0); + assert_se(n_boots_limited == MIN(i, n_boots_expected)); + for (size_t k = 0; k < n_boots_limited; k++) + assert_se(memcmp(&boots[n_boots - k - 1], &boots_limited[k], sizeof(BootId)) == 0); } test_done(t); @@ -453,7 +542,7 @@ static void test_sequence_numbers_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, NULL, &one) == 0); append_number(one, 1, NULL, &seqnum, NULL); @@ -470,7 +559,7 @@ static void test_sequence_numbers_one(void) { memcpy(&seqnum_id, &one->header->seqnum_id, sizeof(sd_id128_t)); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, one, &two) == 0); assert_se(two->header->state == STATE_ONLINE); @@ -502,12 +591,12 @@ static void test_sequence_numbers_one(void) { test_close(one); /* If the machine-id is not initialized, the header file verification - * (which happens when re-opening a journal file) will fail. */ + * (which happens when reopening a journal file) will fail. */ if (sd_id128_get_machine(NULL) >= 0) { /* restart server */ seqnum = 0; - assert_se(journal_file_open(-1, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR, JOURNAL_COMPRESS, 0, UINT64_MAX, NULL, m, NULL, &two) == 0); assert_se(sd_id128_equal(two->header->seqnum_id, seqnum_id)); @@ -567,7 +656,41 @@ static int expected_result(uint64_t needle, const uint64_t *candidates, const ui } } -static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offset, size_t n) { +static int expected_result_next(uint64_t needle, const uint64_t *candidates, const uint64_t *offset, size_t n, direction_t direction, uint64_t *ret) { + switch (direction) { + case DIRECTION_DOWN: + for (size_t i = 0; i < n; i++) + if (needle < offset[i]) { + *ret = candidates[i]; + return candidates[i] > 0; + } + *ret = 0; + return 0; + + case DIRECTION_UP: + for (size_t i = 0; i < n; i++) + if (needle <= offset[i]) { + n = i; + break; + } + + for (; n > 0 && candidates[n - 1] == 0; n--) + ; + + if (n == 0) { + *ret = 0; + return 0; + } + + *ret = candidates[n - 1]; + return candidates[n - 1] > 0; + + default: + assert_not_reached(); + } +} + +static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offset_candidates, const uint64_t *offset, size_t n) { uint64_t p, q; int r, e; @@ -664,12 +787,81 @@ static void verify(JournalFile *f, const uint64_t *seqnum, const uint64_t *offse assert_se(r == e); assert_se(p == q); } + + /* by journal_file_next_entry() */ + for (size_t i = 0; i < n; i++) { + p = 0; + r = journal_file_next_entry(f, offset[i] - 2, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] - 2, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] - 1, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] - 1, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i], DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i], offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 1, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(offset[i] + 1, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] - 1, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] - 1, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i], DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i], offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 1, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] + 1, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + + p = 0; + r = journal_file_next_entry(f, offset[i] + 2, DIRECTION_UP, NULL, &p); + e = expected_result_next(offset[i] + 2, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } + for (size_t trial = 0; trial < 3 * n; trial++) { + uint64_t i = offset[0] - 1 + random_u64_range(offset[n-1] - offset[0] + 2); + + p = 0; + r = journal_file_next_entry(f, i, DIRECTION_DOWN, NULL, &p); + e = expected_result_next(i, offset_candidates, offset, n, DIRECTION_DOWN, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } + for (size_t trial = 0; trial < 3 * n; trial++) { + uint64_t i = offset[0] - 1 + random_u64_range(offset[n-1] - offset[0] + 2); + + p = 0; + r = journal_file_next_entry(f, i, DIRECTION_UP, NULL, &p); + e = expected_result_next(i, offset_candidates, offset, n, DIRECTION_UP, &q); + assert_se(e == 0 ? r <= 0 : r > 0); + assert_se(p == q); + } } static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; char t[] = "/var/tmp/journal-seq-XXXXXX"; - _cleanup_free_ uint64_t *seqnum = NULL, *offset = NULL; + _cleanup_free_ uint64_t *seqnum = NULL, *offset = NULL, *offset_candidates = NULL; JournalFile *f; log_info("/* %s(%zu, %zu) */", __func__, n, num_corrupted); @@ -678,7 +870,7 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0644, UINT64_MAX, NULL, m, NULL, &f) == 0); assert_se(seqnum = new0(uint64_t, n)); @@ -695,7 +887,9 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { } } - verify(f, seqnum, offset, n); + assert_se(offset_candidates = newdup(uint64_t, offset, n)); + + verify(f, seqnum, offset_candidates, offset, n); /* Reset chain cache. */ assert_se(journal_file_move_to_entry_by_offset(f, offset[0], DIRECTION_DOWN, NULL, NULL) > 0); @@ -708,9 +902,10 @@ static void test_generic_array_bisect_one(size_t n, size_t num_corrupted) { assert_se(o); o->entry.seqnum = 0; seqnum[i] = 0; + offset_candidates[i] = 0; } - verify(f, seqnum, offset, n); + verify(f, seqnum, offset_candidates, offset, n); test_close(f); test_done(t); diff --git a/src/libsystemd/sd-journal/test-journal-match.c b/src/libsystemd/sd-journal/test-journal-match.c index 571a88c..c2c345f 100644 --- a/src/libsystemd/sd-journal/test-journal-match.c +++ b/src/libsystemd/sd-journal/test-journal-match.c @@ -16,40 +16,40 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sd_journal_open(&j, 0) >= 0); + assert_se(sd_journal_open(&j, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "foobar", 0) < 0); - assert_se(sd_journal_add_match(j, "foobar=waldo", 0) < 0); - assert_se(sd_journal_add_match(j, "", 0) < 0); - assert_se(sd_journal_add_match(j, "=", 0) < 0); - assert_se(sd_journal_add_match(j, "=xxxxx", 0) < 0); + assert_se(sd_journal_add_match(j, "foobar", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "foobar=waldo", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "=", SIZE_MAX) < 0); + assert_se(sd_journal_add_match(j, "=xxxxx", SIZE_MAX) < 0); assert_se(sd_journal_add_match(j, (uint8_t[4]){'A', '=', '\1', '\2'}, 4) >= 0); assert_se(sd_journal_add_match(j, (uint8_t[5]){'B', '=', 'C', '\0', 'D'}, 5) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=WALDO", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=mmmm", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "HALLO=", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=xxxxx", 0) >= 0); - assert_se(sd_journal_add_match(j, "QUUX=yyyyy", 0) >= 0); - assert_se(sd_journal_add_match(j, "PIFF=paff", 0) >= 0); + assert_se(sd_journal_add_match(j, "HALLO=WALDO", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=mmmm", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "HALLO=", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=xxxxx", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "QUUX=yyyyy", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "PIFF=paff", SIZE_MAX) >= 0); assert_se(sd_journal_add_disjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "ONE=one", 0) >= 0); - assert_se(sd_journal_add_match(j, "ONE=two", 0) >= 0); - assert_se(sd_journal_add_match(j, "TWO=two", 0) >= 0); + assert_se(sd_journal_add_match(j, "ONE=one", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "ONE=two", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "TWO=two", SIZE_MAX) >= 0); assert_se(sd_journal_add_conjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_1=ok", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L4_2=ok", 0) >= 0); + assert_se(sd_journal_add_match(j, "L4_1=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_1=ok", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L4_2=ok", SIZE_MAX) >= 0); assert_se(sd_journal_add_disjunction(j) >= 0); - assert_se(sd_journal_add_match(j, "L3=yes", 0) >= 0); - assert_se(sd_journal_add_match(j, "L3=ok", 0) >= 0); + assert_se(sd_journal_add_match(j, "L3=yes", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "L3=ok", SIZE_MAX) >= 0); assert_se(t = journal_make_match_string(j)); diff --git a/src/libsystemd/sd-journal/test-journal-stream.c b/src/libsystemd/sd-journal/test-journal-stream.c index 3a370ef..00c0435 100644 --- a/src/libsystemd/sd-journal/test-journal-stream.c +++ b/src/libsystemd/sd-journal/test-journal-stream.c @@ -76,9 +76,9 @@ static void run_test(void) { assert_se(chdir(t) >= 0); (void) chattr_path(t, FS_NOCOW_FL, FS_NOCOW_FL, NULL); - assert_se(journal_file_open(-1, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); - assert_se(journal_file_open(-1, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); - assert_se(journal_file_open(-1, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); + assert_se(journal_file_open(-EBADF, "one.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &one) == 0); + assert_se(journal_file_open(-EBADF, "two.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &two) == 0); + assert_se(journal_file_open(-EBADF, "three.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &three) == 0); for (i = 0; i < N_ENTRIES; i++) { char *p, *q; @@ -119,9 +119,9 @@ static void run_test(void) { (void) journal_file_offline_close(two); (void) journal_file_offline_close(three); - assert_se(sd_journal_open_directory(&j, t, 0) >= 0); + assert_se(sd_journal_open_directory(&j, t, SD_JOURNAL_ASSUME_IMMUTABLE) >= 0); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); SD_JOURNAL_FOREACH_BACKWARDS(j) { _cleanup_free_ char *c; @@ -147,7 +147,7 @@ static void run_test(void) { verify_contents(j, 1); printf("NEXT TEST\n"); - assert_se(sd_journal_add_match(j, "MAGIC=quux", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=quux", SIZE_MAX) >= 0); assert_se(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); @@ -157,10 +157,10 @@ static void run_test(void) { printf("NEXT TEST\n"); sd_journal_flush_matches(j); - assert_se(sd_journal_add_match(j, "MAGIC=waldo", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=10", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=11", 0) >= 0); - assert_se(sd_journal_add_match(j, "NUMBER=12", 0) >= 0); + assert_se(sd_journal_add_match(j, "MAGIC=waldo", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=10", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=11", SIZE_MAX) >= 0); + assert_se(sd_journal_add_match(j, "NUMBER=12", SIZE_MAX) >= 0); assert_se(z = journal_make_match_string(j)); printf("resulting match expression is: %s\n", z); diff --git a/src/libsystemd/sd-journal/test-journal-verify.c b/src/libsystemd/sd-journal/test-journal-verify.c index edce440..396ebe1 100644 --- a/src/libsystemd/sd-journal/test-journal-verify.c +++ b/src/libsystemd/sd-journal/test-journal-verify.c @@ -47,7 +47,7 @@ static int raw_verify(const char *fn, const char *verification_key) { assert_se(m != NULL); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, fn, O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), @@ -92,7 +92,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { log_info("Generating a test journal"); assert_se(journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), @@ -128,7 +128,7 @@ static int run_test(const char *verification_key, ssize_t max_iterations) { log_info("Verifying with key: %s", strna(verification_key)); assert_se(journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, "test.journal", O_RDONLY, JOURNAL_COMPRESS|(verification_key ? JOURNAL_SEAL : 0), diff --git a/src/libsystemd/sd-journal/test-journal.c b/src/libsystemd/sd-journal/test-journal.c index 96f2b67..19a6d2d 100644 --- a/src/libsystemd/sd-journal/test-journal.c +++ b/src/libsystemd/sd-journal/test-journal.c @@ -39,7 +39,7 @@ static void test_non_empty_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f) == 0); assert_se(dual_timestamp_now(&ts)); assert_se(sd_id128_randomize(&fake_boot_id) == 0); @@ -136,10 +136,10 @@ static void test_empty_one(void) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); - assert_se(journal_file_open(-1, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); - assert_se(journal_file_open(-1, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); - assert_se(journal_file_open(-1, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, 0, 0666, UINT64_MAX, NULL, m, NULL, &f1) == 0); + assert_se(journal_file_open(-EBADF, "test-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS, 0666, UINT64_MAX, NULL, m, NULL, &f2) == 0); + assert_se(journal_file_open(-EBADF, "test-seal.journal", O_RDWR|O_CREAT, JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f3) == 0); + assert_se(journal_file_open(-EBADF, "test-seal-compress.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, UINT64_MAX, NULL, m, NULL, &f4) == 0); journal_file_print_header(f1); puts(""); @@ -194,7 +194,7 @@ static bool check_compressed(uint64_t compress_threshold, uint64_t data_size) { mkdtemp_chdir_chattr(t); - assert_se(journal_file_open(-1, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); + assert_se(journal_file_open(-EBADF, "test.journal", O_RDWR|O_CREAT, JOURNAL_COMPRESS|JOURNAL_SEAL, 0666, compress_threshold, NULL, m, NULL, &f) == 0); dual_timestamp_now(&ts); diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c index f9e86c6..4d91ba9 100644 --- a/src/libsystemd/sd-login/sd-login.c +++ b/src/libsystemd/sd-login/sd-login.c @@ -579,10 +579,7 @@ _public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) if (isempty(content)) return 0; - char t[DECIMAL_STR_MAX(uid_t)]; - xsprintf(t, UID_FMT, uid); - - return string_contains_word(content, NULL, t); + return string_contains_word(content, NULL, FORMAT_UID(uid)); } static int uid_get_array(uid_t uid, const char *variable, char ***array) { @@ -1275,7 +1272,7 @@ _public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { _public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { if (m) - (void) close_nointr(MONITOR_TO_FD(m)); + (void) close(MONITOR_TO_FD(m)); return NULL; } diff --git a/src/libsystemd/sd-login/test-login.c b/src/libsystemd/sd-login/test-login.c index 819f86f..66e4274 100644 --- a/src/libsystemd/sd-login/test-login.c +++ b/src/libsystemd/sd-login/test-login.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#if HAVE_PIDFD_OPEN +#include +#endif #include "sd-login.h" @@ -10,6 +13,7 @@ #include "format-util.h" #include "log.h" #include "missing_syscall.h" +#include "mountpoint-util.h" #include "process-util.h" #include "string-util.h" #include "strv.h" @@ -103,7 +107,7 @@ TEST(login) { assert_se(IN_SET(r, 0, -ENOMEDIUM)); } - r = sd_uid_get_display(u2, &display_session); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_display(u2, &display_session)); log_info("sd_uid_get_display("UID_FMT", …) → %s / \"%s\"", u2, e(r), strnull(display_session)); if (u2 == UID_INVALID) assert_se(r == -EINVAL); @@ -115,7 +119,7 @@ TEST(login) { sd_peer_get_session(pair[1], &qq); assert_se(streq_ptr(pp, qq)); - r = sd_uid_get_sessions(u2, false, &sessions); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_sessions(u2, false, &sessions)); assert_se(t = strv_join(sessions, " ")); log_info("sd_uid_get_sessions("UID_FMT", …) → %s \"%s\"", u2, e(r), t); if (u2 == UID_INVALID) @@ -127,9 +131,9 @@ TEST(login) { sessions = strv_free(sessions); free(t); - assert_se(r == sd_uid_get_sessions(u2, false, NULL)); + assert_se(r == ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_sessions(u2, false, NULL))); - r = sd_uid_get_seats(u2, false, &seats); + r = ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_seats(u2, false, &seats)); assert_se(t = strv_join(seats, " ")); log_info("sd_uid_get_seats("UID_FMT", …) → %s \"%s\"", u2, e(r), t); if (u2 == UID_INVALID) @@ -141,7 +145,7 @@ TEST(login) { seats = strv_free(seats); free(t); - assert_se(r == sd_uid_get_seats(u2, false, NULL)); + assert_se(r == ASSERT_RETURN_IS_CRITICAL(uid_is_valid(u2), sd_uid_get_seats(u2, false, NULL))); if (session) { r = sd_session_is_active(session); @@ -327,6 +331,9 @@ TEST(monitor) { } static int intro(void) { + if (IN_SET(cg_unified(), -ENOENT, -ENOMEDIUM)) + return log_tests_skipped("cgroupfs is not mounted"); + log_info("/* Information printed is from the live system */"); return EXIT_SUCCESS; } diff --git a/src/libsystemd/sd-netlink/netlink-message-rtnl.c b/src/libsystemd/sd-netlink/netlink-message-rtnl.c index 008e802..fb11c7e 100644 --- a/src/libsystemd/sd-netlink/netlink-message-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-message-rtnl.c @@ -56,6 +56,10 @@ static bool rtnl_message_type_is_mdb(uint16_t type) { return IN_SET(type, RTM_NEWMDB, RTM_DELMDB, RTM_GETMDB); } +static bool rtnl_message_type_is_nsid(uint16_t type) { + return IN_SET(type, RTM_NEWNSID, RTM_DELNSID, RTM_GETNSID); +} + int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { struct rtmsg *rtm; @@ -92,6 +96,20 @@ int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char return 0; } +int sd_rtnl_message_route_set_tos(sd_netlink_message *m, unsigned char tos) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_tos = tos; + + return 0; +} + int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { struct rtmsg *rtm; @@ -336,7 +354,7 @@ int sd_rtnl_message_new_nexthop(sd_netlink *rtnl, sd_netlink_message **ret, return r; if (nlmsg_type == RTM_NEWNEXTHOP) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_REPLACE; nhm = NLMSG_DATA((*ret)->hdr); @@ -1202,3 +1220,24 @@ int sd_rtnl_message_new_mdb( return 0; } + +int sd_rtnl_message_new_nsid( + sd_netlink *rtnl, + sd_netlink_message **ret, + uint16_t nlmsg_type) { + + struct rtgenmsg *rt; + int r; + + assert_return(rtnl_message_type_is_nsid(nlmsg_type), -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + rt = NLMSG_DATA((*ret)->hdr); + rt->rtgen_family = AF_UNSPEC; + + return 0; +} diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c index 000a50e..49d000d 100644 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ b/src/libsystemd/sd-netlink/netlink-message.c @@ -17,9 +17,6 @@ #define GET_CONTAINER(m, i) ((struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset)) -#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) -#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) - int message_new_empty(sd_netlink *nl, sd_netlink_message **ret) { sd_netlink_message *m; @@ -782,32 +779,6 @@ int sd_netlink_message_read_data(sd_netlink_message *m, uint16_t attr_type, size assert_return(m, -EINVAL); - r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); - if (r < 0) - return r; - - if (ret_data) { - void *data; - - data = memdup(attr_data, r); - if (!data) - return -ENOMEM; - - *ret_data = data; - } - - if (ret_size) - *ret_size = r; - - return r; -} - -int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data) { - void *attr_data; - int r; - - assert_return(m, -EINVAL); - r = netlink_message_read_internal(m, attr_type, &attr_data, NULL); if (r < 0) return r; diff --git a/src/libsystemd/sd-netlink/netlink-types-genl.c b/src/libsystemd/sd-netlink/netlink-types-genl.c index 6fe9adc..226ac86 100644 --- a/src/libsystemd/sd-netlink/netlink-types-genl.c +++ b/src/libsystemd/sd-netlink/netlink-types-genl.c @@ -199,6 +199,7 @@ static const NLAPolicy genl_nl80211_policies[] = { [NL80211_ATTR_SSID] = BUILD_POLICY_WITH_SIZE(BINARY, IEEE80211_MAX_SSID_LEN), [NL80211_ATTR_STATUS_CODE] = BUILD_POLICY(U16), [NL80211_ATTR_4ADDR] = BUILD_POLICY(U8), + [NL80211_ATTR_NETNS_FD] = BUILD_POLICY(U32), }; /***************** genl wireguard type systems *****************/ diff --git a/src/libsystemd/sd-netlink/netlink-types-rtnl.c b/src/libsystemd/sd-netlink/netlink-types-rtnl.c index 0153456..e39a75c 100644 --- a/src/libsystemd/sd-netlink/netlink-types-rtnl.c +++ b/src/libsystemd/sd-netlink/netlink-types-rtnl.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,7 @@ static const NLAPolicy rtnl_link_info_data_bond_policies[] = { [IFLA_BOND_AD_ACTOR_SYSTEM] = BUILD_POLICY_WITH_SIZE(ETHER_ADDR, ETH_ALEN), [IFLA_BOND_TLB_DYNAMIC_LB] = BUILD_POLICY(U8), [IFLA_BOND_PEER_NOTIF_DELAY] = BUILD_POLICY(U32), + [IFLA_BOND_MISSED_MAX] = BUILD_POLICY(U8), }; static const NLAPolicy rtnl_link_info_data_bridge_policies[] = { @@ -306,6 +308,7 @@ static const NLAPolicy rtnl_link_info_data_macvlan_policies[] = { [IFLA_MACVLAN_MACADDR_COUNT] = BUILD_POLICY(U32), [IFLA_MACVLAN_BC_QUEUE_LEN] = BUILD_POLICY(U32), [IFLA_MACVLAN_BC_QUEUE_LEN_USED] = BUILD_POLICY(U32), + [IFLA_MACVLAN_BC_CUTOFF] = BUILD_POLICY(S32), }; static const NLAPolicy rtnl_link_info_data_tun_policies[] = { @@ -1185,6 +1188,13 @@ static const NLAPolicy rtnl_mdb_policies[] = { DEFINE_POLICY_SET(rtnl_mdb); +static const NLAPolicy rtnl_nsid_policies[] = { + [NETNSA_FD] = BUILD_POLICY(S32), + [NETNSA_NSID] = BUILD_POLICY(U32), +}; + +DEFINE_POLICY_SET(rtnl_nsid); + static const NLAPolicy rtnl_policies[] = { [RTM_NEWLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), [RTM_DELLINK] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_link, sizeof(struct ifinfomsg)), @@ -1220,6 +1230,9 @@ static const NLAPolicy rtnl_policies[] = { [RTM_NEWMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), [RTM_DELMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), [RTM_GETMDB] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_mdb, sizeof(struct br_port_msg)), + [RTM_NEWNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), + [RTM_DELNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), + [RTM_GETNSID] = BUILD_POLICY_NESTED_WITH_SIZE(rtnl_nsid, sizeof(struct rtgenmsg)), }; DEFINE_POLICY_SET(rtnl); diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c index 832159a..9061609 100644 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ b/src/libsystemd/sd-netlink/netlink-util.c @@ -11,6 +11,154 @@ #include "process-util.h" #include "strv.h" +static int parse_newlink_message( + sd_netlink_message *message, + char **ret_name, + char ***ret_altnames) { + + _cleanup_strv_free_ char **altnames = NULL; + int r, ifindex; + + assert(message); + + uint16_t type; + r = sd_netlink_message_get_type(message, &type); + if (r < 0) + return r; + if (type != RTM_NEWLINK) + return -EPROTO; + + r = sd_rtnl_message_link_get_ifindex(message, &ifindex); + if (r < 0) + return r; + if (ifindex <= 0) + return -EPROTO; + + if (ret_altnames) { + r = sd_netlink_message_read_strv(message, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &altnames); + if (r < 0 && r != -ENODATA) + return r; + } + + if (ret_name) { + r = sd_netlink_message_read_string_strdup(message, IFLA_IFNAME, ret_name); + if (r < 0) + return r; + } + + if (ret_altnames) + *ret_altnames = TAKE_PTR(altnames); + + return ifindex; +} + +int rtnl_get_ifname_full(sd_netlink **rtnl, int ifindex, char **ret_name, char ***ret_altnames) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r; + + assert(ifindex > 0); + + /* This is similar to if_indextoname(), but also optionally provides alternative names. */ + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r < 0) + return r; + + return parse_newlink_message(reply, ret_name, ret_altnames); +} + +int rtnl_resolve_ifname_full( + sd_netlink **rtnl, + ResolveInterfaceNameFlag flags, + const char *name, + char **ret_name, + char ***ret_altnames) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r; + + assert(name); + assert(flags > 0); + + /* This is similar to if_nametoindex(), but also resolves alternative names and decimal formatted + * ifindex too. Returns ifindex, and optionally provides the main interface name and alternative + * names.*/ + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + /* First, use IFLA_IFNAME */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_MAIN) && ifname_valid(name)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r >= 0) + return parse_newlink_message(reply, ret_name, ret_altnames); + if (r != -ENODEV) + return r; + } + + /* Next, try IFLA_ALT_IFNAME */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_ALTERNATIVE) && + ifname_valid_full(name, IFNAME_VALID_ALTERNATIVE)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_ALT_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, &reply); + if (r >= 0) + return parse_newlink_message(reply, ret_name, ret_altnames); + /* The kernels older than 76c9ac0ee878f6693d398d3a95ccaf85e1f597a6 (v5.5) return -EINVAL. */ + if (!IN_SET(r, -ENODEV, -EINVAL)) + return r; + } + + /* Finally, assume the string is a decimal formatted ifindex. */ + if (FLAGS_SET(flags, RESOLVE_IFNAME_NUMERIC)) { + int ifindex; + + ifindex = parse_ifindex(name); + if (ifindex <= 0) + return -ENODEV; + + return rtnl_get_ifname_full(rtnl, ifindex, ret_name, ret_altnames); + } + + return -ENODEV; +} + static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; int r; @@ -20,6 +168,13 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { assert(name); /* Assign the requested name. */ + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); if (r < 0) return r; @@ -31,6 +186,37 @@ static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { return sd_netlink_call(*rtnl, message, 0, NULL); } +int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name) { + _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; + int r, ifindex; + + assert(orig_name); + assert(new_name); + + /* This does not check alternative names. Callers must check the requested name is not used as an + * alternative name. */ + + if (streq(orig_name, new_name)) + return 0; + + if (!ifname_valid(new_name)) + return -EINVAL; + + if (!rtnl) + rtnl = &our_rtnl; + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + ifindex = rtnl_resolve_ifname(rtnl, orig_name); + if (ifindex < 0) + return ifindex; + + return set_link_name(rtnl, ifindex, new_name); +} + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) { _cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL; bool altname_deleted = false; @@ -204,38 +390,6 @@ int rtnl_set_link_properties( return 0; } -int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; - _cleanup_strv_free_ char **names = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - assert(ret); - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, ifindex); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, &reply); - if (r < 0) - return r; - - r = sd_netlink_message_read_strv(reply, IFLA_PROP_LIST, IFLA_ALT_IFNAME, &names); - if (r < 0 && r != -ENODATA) - return r; - - *ret = TAKE_PTR(names); - - return 0; -} - static int rtnl_update_link_alternative_names( sd_netlink **rtnl, uint16_t nlmsg_type, @@ -336,92 +490,6 @@ int rtnl_set_link_alternative_names_by_ifname( return 0; } -int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *our_rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; - int r, ifindex; - - assert(name); - - /* This returns ifindex and the main interface name. */ - - if (!ifname_valid_full(name, IFNAME_VALID_ALTERNATIVE)) - return -EINVAL; - - if (!rtnl) - rtnl = &our_rtnl; - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(message, IFLA_ALT_IFNAME, name); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, &reply); - if (r == -EINVAL) - return -ENODEV; /* The device doesn't exist */ - if (r < 0) - return r; - - r = sd_rtnl_message_link_get_ifindex(reply, &ifindex); - if (r < 0) - return r; - assert(ifindex > 0); - - if (ret) { - r = sd_netlink_message_read_string_strdup(reply, IFLA_IFNAME, ret); - if (r < 0) - return r; - } - - return ifindex; -} - -int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name) { - int r; - - /* Like if_nametoindex, but resolves "alternative names" too. */ - - assert(name); - - r = if_nametoindex(name); - if (r > 0) - return r; - - return rtnl_resolve_link_alternative_name(rtnl, name, NULL); -} - -int rtnl_resolve_interface(sd_netlink **rtnl, const char *name) { - int r; - - /* Like rtnl_resolve_ifname, but resolves interface numbers too. */ - - assert(name); - - r = parse_ifindex(name); - if (r > 0) - return r; - assert(r < 0); - - return rtnl_resolve_ifname(rtnl, name); -} - -int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name) { - int r; - - r = rtnl_resolve_interface(rtnl, name); - if (r < 0) - return log_error_errno(r, "Failed to resolve interface \"%s\": %m", name); - return r; -} - int rtnl_get_link_info( sd_netlink **rtnl, int ifindex, @@ -441,7 +509,7 @@ int rtnl_get_link_info( assert(rtnl); assert(ifindex > 0); - if (!ret_iftype && !ret_flags) + if (!ret_iftype && !ret_flags && !ret_kind && !ret_hw_addr && !ret_permanent_hw_addr) return 0; if (!*rtnl) { @@ -575,121 +643,6 @@ int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void return 0; } -MultipathRoute *multipath_route_free(MultipathRoute *m) { - if (!m) - return NULL; - - free(m->ifname); - - return mfree(m); -} - -int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret) { - _cleanup_(multipath_route_freep) MultipathRoute *n = NULL; - _cleanup_free_ char *ifname = NULL; - - assert(m); - assert(ret); - - if (m->ifname) { - ifname = strdup(m->ifname); - if (!ifname) - return -ENOMEM; - } - - n = new(MultipathRoute, 1); - if (!n) - return -ENOMEM; - - *n = (MultipathRoute) { - .gateway = m->gateway, - .weight = m->weight, - .ifindex = m->ifindex, - .ifname = TAKE_PTR(ifname), - }; - - *ret = TAKE_PTR(n); - - return 0; -} - -int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret) { - _cleanup_ordered_set_free_free_ OrderedSet *set = NULL; - int r; - - assert(rtnh); - assert(IN_SET(family, AF_INET, AF_INET6)); - - if (size < sizeof(struct rtnexthop)) - return -EBADMSG; - - for (; size >= sizeof(struct rtnexthop); ) { - _cleanup_(multipath_route_freep) MultipathRoute *m = NULL; - - if (NLMSG_ALIGN(rtnh->rtnh_len) > size) - return -EBADMSG; - - if (rtnh->rtnh_len < sizeof(struct rtnexthop)) - return -EBADMSG; - - m = new(MultipathRoute, 1); - if (!m) - return -ENOMEM; - - *m = (MultipathRoute) { - .ifindex = rtnh->rtnh_ifindex, - .weight = rtnh->rtnh_hops, - }; - - if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { - size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); - - for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_GATEWAY) { - if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) - return -EBADMSG; - - m->gateway.family = family; - memcpy(&m->gateway.address, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); - break; - } else if (attr->rta_type == RTA_VIA) { - uint16_t gw_family; - - if (family != AF_INET) - return -EINVAL; - - if (attr->rta_len < RTA_LENGTH(sizeof(uint16_t))) - return -EBADMSG; - - gw_family = *(uint16_t *) RTA_DATA(attr); - - if (gw_family != AF_INET6) - return -EBADMSG; - - if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family))) - return -EBADMSG; - - memcpy(&m->gateway, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(gw_family) + sizeof(gw_family)); - break; - } - } - } - - r = ordered_set_ensure_put(&set, NULL, m); - if (r < 0) - return r; - - TAKE_PTR(m); - - size -= NLMSG_ALIGN(rtnh->rtnh_len); - rtnh = RTNH_NEXT(rtnh); - } - - if (ret) - *ret = TAKE_PTR(set); - return 0; -} - bool netlink_pid_changed(sd_netlink *nl) { /* We don't support people creating an nl connection and * keeping it around over a fork(). Let's complain. */ diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h index 369f5d5..4ba64f0 100644 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ b/src/libsystemd/sd-netlink/netlink-util.h @@ -10,24 +10,32 @@ #include "ordered-set.h" #include "socket-util.h" +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) +#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) + /* See struct rtvia in rtnetlink.h */ typedef struct RouteVia { uint16_t family; union in_addr_union address; } _packed_ RouteVia; -typedef struct MultipathRoute { - RouteVia gateway; - uint32_t weight; - int ifindex; - char *ifname; -} MultipathRoute; +int rtnl_get_ifname_full(sd_netlink **rtnl, int ifindex, char **ret_name, char ***ret_altnames); -MultipathRoute *multipath_route_free(MultipathRoute *m); -DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free); +typedef enum ResolveInterfaceNameFlag { + RESOLVE_IFNAME_MAIN = 1 << 0, /* resolve main interface name */ + RESOLVE_IFNAME_ALTERNATIVE = 1 << 1, /* resolve alternative name */ + RESOLVE_IFNAME_NUMERIC = 1 << 2, /* resolve decimal formatted ifindex */ + _RESOLVE_IFNAME_ALL = RESOLVE_IFNAME_MAIN | RESOLVE_IFNAME_ALTERNATIVE | RESOLVE_IFNAME_NUMERIC, +} ResolveInterfaceNameFlag; -int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret); +int rtnl_resolve_ifname_full( + sd_netlink **rtnl, + ResolveInterfaceNameFlag flags, + const char *name, + char **ret_name, + char ***ret_altnames); +int rtnl_rename_link(sd_netlink **rtnl, const char *orig_name, const char *new_name); int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names); static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names); @@ -43,14 +51,32 @@ int rtnl_set_link_properties( uint32_t mtu, uint32_t gso_max_size, size_t gso_max_segments); -int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret); +static inline int rtnl_get_link_alternative_names(sd_netlink **rtnl, int ifindex, char ***ret) { + return rtnl_get_ifname_full(rtnl, ifindex, NULL, ret); +} int rtnl_set_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); int rtnl_set_link_alternative_names_by_ifname(sd_netlink **rtnl, const char *ifname, char* const *alternative_names); int rtnl_delete_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names); -int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret); -int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name); -int rtnl_resolve_interface(sd_netlink **rtnl, const char *name); -int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name); +static inline int rtnl_resolve_link_alternative_name(sd_netlink **rtnl, const char *name, char **ret) { + return rtnl_resolve_ifname_full(rtnl, RESOLVE_IFNAME_ALTERNATIVE, name, ret, NULL); +} +static inline int rtnl_resolve_ifname(sd_netlink **rtnl, const char *name) { + return rtnl_resolve_ifname_full(rtnl, RESOLVE_IFNAME_MAIN | RESOLVE_IFNAME_ALTERNATIVE, name, NULL, NULL); +} +static inline int rtnl_resolve_interface(sd_netlink **rtnl, const char *name) { + return rtnl_resolve_ifname_full(rtnl, _RESOLVE_IFNAME_ALL, name, NULL, NULL); +} +static inline int rtnl_resolve_interface_or_warn(sd_netlink **rtnl, const char *name) { + int r; + + assert(name); + + r = rtnl_resolve_interface(rtnl, name); + if (r < 0) + return log_error_errno(r, "Failed to resolve interface \"%s\": %m", name); + return r; +} + int rtnl_get_link_info( sd_netlink **rtnl, int ifindex, @@ -103,8 +129,6 @@ int netlink_message_read_in_addr_union(sd_netlink_message *m, unsigned short typ void rtattr_append_attribute_internal(struct rtattr *rta, unsigned short type, const void *data, size_t data_length); int rtattr_append_attribute(struct rtattr **rta, unsigned short type, const void *data, size_t data_length); -int rtattr_read_nexthop(const struct rtnexthop *rtnh, size_t size, int family, OrderedSet **ret); - void netlink_seal_message(sd_netlink *nl, sd_netlink_message *m); size_t netlink_get_reply_callback_count(sd_netlink *nl); diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c index b6730b7..b347ee6 100644 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ b/src/libsystemd/sd-netlink/sd-netlink.c @@ -176,7 +176,7 @@ static int dispatch_rqueue(sd_netlink *nl, sd_netlink_message **ret) { assert(nl); assert(ret); - if (ordered_set_size(nl->rqueue) <= 0) { + if (ordered_set_isempty(nl->rqueue)) { /* Try to read a new message */ r = socket_read_message(nl); if (r == -ENOBUFS) /* FIXME: ignore buffer overruns for now */ @@ -443,7 +443,7 @@ int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { assert_return(nl, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - if (ordered_set_size(nl->rqueue) > 0) + if (!ordered_set_isempty(nl->rqueue)) return 0; r = netlink_poll(nl, false, timeout_usec); @@ -623,7 +623,7 @@ int sd_netlink_get_events(sd_netlink *nl) { assert_return(nl, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - return ordered_set_size(nl->rqueue) == 0 ? POLLIN : 0; + return ordered_set_isempty(nl->rqueue) ? POLLIN : 0; } int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout_usec) { @@ -633,7 +633,7 @@ int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout_usec) { assert_return(timeout_usec, -EINVAL); assert_return(!netlink_pid_changed(nl), -ECHILD); - if (ordered_set_size(nl->rqueue) > 0) { + if (!ordered_set_isempty(nl->rqueue)) { *timeout_usec = 0; return 1; } diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c index 13aedc4..cf19c48 100644 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ b/src/libsystemd/sd-netlink/test-netlink.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -681,6 +682,19 @@ TEST(rtnl_set_link_name) { _cleanup_free_ char *resolved = NULL; assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex); assert_se(streq_ptr(resolved, "test-shortname")); + resolved = mfree(resolved); + + assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname") >= 0); + assert_se(rtnl_rename_link(&rtnl, "test-shortname", "test-shortname2") >= 0); + assert_se(rtnl_rename_link(NULL, "test-shortname2", "test-shortname3") >= 0); + + assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-additional-name", &resolved) == ifindex); + assert_se(streq_ptr(resolved, "test-shortname3")); + resolved = mfree(resolved); + + assert_se(rtnl_resolve_link_alternative_name(&rtnl, "test-shortname3", &resolved) == ifindex); + assert_se(streq_ptr(resolved, "test-shortname3")); + resolved = mfree(resolved); } DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c index 2059567..25c6e44 100644 --- a/src/libsystemd/sd-network/network-util.c +++ b/src/libsystemd/sd-network/network-util.c @@ -90,49 +90,48 @@ static const char *const link_online_state_table[_LINK_ONLINE_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(link_online_state, LinkOnlineState); -int parse_operational_state_range(const char *str, LinkOperationalStateRange *out) { - LinkOperationalStateRange range = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID }; - _cleanup_free_ const char *min = NULL; +int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret) { + LinkOperationalStateRange range = LINK_OPERSTATE_RANGE_INVALID; + _cleanup_free_ char *buf = NULL; const char *p; - assert(str); - assert(out); - - p = strchr(str, ':'); - if (p) { - min = strndup(str, p - str); + assert(s); + assert(ret); - if (!isempty(p + 1)) { - range.max = link_operstate_from_string(p + 1); - if (range.max < 0) - return -EINVAL; - } - } else - min = strdup(str); + /* allowed formats: "min", "min:", "min:max", ":max" */ - if (!min) - return -ENOMEM; + if (isempty(s) || streq(s, ":")) + return -EINVAL; - if (!isempty(min)) { - range.min = link_operstate_from_string(min); - if (range.min < 0) + p = strchr(s, ':'); + if (!p || isempty(p + 1)) + range.max = LINK_OPERSTATE_ROUTABLE; + else { + range.max = link_operstate_from_string(p + 1); + if (range.max < 0) return -EINVAL; } - /* Fail on empty strings. */ - if (range.min == _LINK_OPERSTATE_INVALID && range.max == _LINK_OPERSTATE_INVALID) - return -EINVAL; + if (p) { + buf = strndup(s, p - s); + if (!buf) + return -ENOMEM; - if (range.min == _LINK_OPERSTATE_INVALID) + s = buf; + } + + if (isempty(s)) range.min = LINK_OPERSTATE_MISSING; - if (range.max == _LINK_OPERSTATE_INVALID) - range.max = LINK_OPERSTATE_ROUTABLE; + else { + range.min = link_operstate_from_string(s); + if (range.min < 0) + return -EINVAL; + } - if (range.min > range.max) + if (!operational_state_range_is_valid(&range)) return -EINVAL; - *out = range; - + *ret = range; return 0; } diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h index c47e271..6fc6015 100644 --- a/src/libsystemd/sd-network/network-util.h +++ b/src/libsystemd/sd-network/network-util.h @@ -79,8 +79,30 @@ typedef struct LinkOperationalStateRange { LinkOperationalState max; } LinkOperationalStateRange; -#define LINK_OPERSTATE_RANGE_DEFAULT (LinkOperationalStateRange) { LINK_OPERSTATE_DEGRADED, \ - LINK_OPERSTATE_ROUTABLE } - -int parse_operational_state_range(const char *str, LinkOperationalStateRange *out); +#define LINK_OPERSTATE_RANGE_DEFAULT \ + (const LinkOperationalStateRange) { \ + .min = LINK_OPERSTATE_DEGRADED, \ + .max = LINK_OPERSTATE_ROUTABLE, \ + } + +#define LINK_OPERSTATE_RANGE_INVALID \ + (const LinkOperationalStateRange) { \ + .min = _LINK_OPERSTATE_INVALID, \ + .max = _LINK_OPERSTATE_INVALID, \ + } + +int parse_operational_state_range(const char *s, LinkOperationalStateRange *ret); int network_link_get_operational_state(int ifindex, LinkOperationalState *ret); + +static inline bool operational_state_is_valid(LinkOperationalState s) { + return s >= 0 && s < _LINK_OPERSTATE_MAX; +} +static inline bool operational_state_range_is_valid(const LinkOperationalStateRange *range) { + return range && + operational_state_is_valid(range->min) && + operational_state_is_valid(range->max) && + range->min <= range->max; +} +static inline bool operational_state_is_in_range(LinkOperationalState s, const LinkOperationalStateRange *range) { + return range && range->min <= s && s <= range->max; +} diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c index cf3c400..9d11dce 100644 --- a/src/libsystemd/sd-network/sd-network.c +++ b/src/libsystemd/sd-network/sd-network.c @@ -394,7 +394,7 @@ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { if (m) - (void) close_nointr(MONITOR_TO_FD(m)); + (void) close(MONITOR_TO_FD(m)); return NULL; } diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c index 7290d1c..8edbde9 100644 --- a/src/libsystemd/sd-path/sd-path.c +++ b/src/libsystemd/sd-path/sd-path.c @@ -443,7 +443,7 @@ _public_ int sd_path_lookup(uint64_t type, const char *suffix, char **path) { } static int search_from_environment( - char ***list, + char ***ret, const char *env_home, const char *home_suffix, const char *env_search, @@ -455,7 +455,7 @@ static int search_from_environment( char *h = NULL; int r; - assert(list); + assert(ret); if (env_search) { e = secure_getenv(env_search); @@ -465,7 +465,7 @@ static int search_from_environment( return -ENOMEM; if (env_search_sufficient) { - *list = TAKE_PTR(l); + *ret = TAKE_PTR(l); return 0; } } @@ -506,7 +506,7 @@ static int search_from_environment( return -ENOMEM; } - *list = TAKE_PTR(l); + *ret = TAKE_PTR(l); return 0; } @@ -516,15 +516,15 @@ static int search_from_environment( # define ARRAY_SBIN_BIN(x) x "bin" #endif -static int get_search(uint64_t type, char ***list) { +static int get_search(uint64_t type, char ***ret) { int r; - assert(list); + assert(ret); switch (type) { case SD_PATH_SEARCH_BINARIES: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/bin", "PATH", @@ -534,7 +534,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_LIBRARY_PRIVATE: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/lib", NULL, @@ -544,7 +544,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_LIBRARY_ARCH: - return search_from_environment(list, + return search_from_environment(ret, NULL, ".local/lib/" LIB_ARCH_TUPLE, "LD_LIBRARY_PATH", @@ -553,7 +553,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_SHARED: - return search_from_environment(list, + return search_from_environment(ret, "XDG_DATA_HOME", ".local/share", "XDG_DATA_DIRS", @@ -563,7 +563,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_CONFIGURATION_FACTORY: - return search_from_environment(list, + return search_from_environment(ret, NULL, NULL, NULL, @@ -573,7 +573,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_STATE_FACTORY: - return search_from_environment(list, + return search_from_environment(ret, NULL, NULL, NULL, @@ -583,7 +583,7 @@ static int get_search(uint64_t type, char ***list) { NULL); case SD_PATH_SEARCH_CONFIGURATION: - return search_from_environment(list, + return search_from_environment(ret, "XDG_CONFIG_HOME", ".config", "XDG_CONFIG_DIRS", @@ -591,12 +591,18 @@ static int get_search(uint64_t type, char ***list) { "/etc", NULL); - case SD_PATH_SEARCH_BINARIES_DEFAULT: - return strv_from_nulstr(list, DEFAULT_PATH_NULSTR); + case SD_PATH_SEARCH_BINARIES_DEFAULT: { + char **t = strv_split(default_PATH(), ":"); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; + } case SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT: case SD_PATH_SYSTEMD_SEARCH_USER_UNIT: { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; RuntimeScope scope = type == SD_PATH_SYSTEMD_SEARCH_SYSTEM_UNIT ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER; @@ -604,7 +610,7 @@ static int get_search(uint64_t type, char ***list) { if (r < 0) return r; - *list = TAKE_PTR(lp.search_path); + *ret = TAKE_PTR(lp.search_path); return 0; } @@ -618,7 +624,7 @@ static int get_search(uint64_t type, char ***list) { if (!t) return -ENOMEM; - *list = t; + *ret = t; return 0; } @@ -631,12 +637,12 @@ static int get_search(uint64_t type, char ***list) { if (!t) return -ENOMEM; - *list = t; + *ret = t; return 0; } case SD_PATH_SYSTEMD_SEARCH_NETWORK: - return strv_from_nulstr(list, NETWORK_DIRS_NULSTR); + return strv_from_nulstr(ret, NETWORK_DIRS_NULSTR); } diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c index 7b9f54c..50e2459 100644 --- a/src/libudev/libudev-device.c +++ b/src/libudev/libudev-device.c @@ -1,11 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include #include #include -#include #include #include #include diff --git a/src/libudev/test-libudev.c b/src/libudev/test-libudev.c index e05a062..99934c6 100644 --- a/src/libudev/test-libudev.c +++ b/src/libudev/test-libudev.c @@ -339,6 +339,9 @@ static void test_hwdb(struct udev *udev, const char *modalias) { if (!hwdb) log_warning_errno(errno, "Failed to open hwdb: %m"); + SAVE_ASSERT_RETURN_IS_CRITICAL; + log_set_assert_return_is_critical(hwdb); + udev_list_entry_foreach(entry, udev_hwdb_get_properties_list_entry(hwdb, modalias, 0)) log_info("'%s'='%s'", udev_list_entry_get_name(entry), udev_list_entry_get_value(entry)); diff --git a/src/libudev/test-udev-device-thread.c b/src/libudev/test-udev-device-thread.c index c082fdc..fdf0818 100644 --- a/src/libudev/test-udev-device-thread.c +++ b/src/libudev/test-udev-device-thread.c @@ -6,6 +6,7 @@ #include #include "libudev.h" +#include "tests.h" #define handle_error_errno(error, msg) \ ({ \ @@ -29,8 +30,12 @@ int main(int argc, char *argv[]) { int r; loopback = udev_device_new_from_syspath(NULL, "/sys/class/net/lo"); - if (!loopback) + if (!loopback) { + if (errno == ENODEV) + return log_tests_skipped_errno(errno, "Loopback device not found"); + return handle_error_errno(errno, "Failed to create loopback device object"); + } entry = udev_device_get_properties_list_entry(loopback); udev_list_entry_foreach(e, entry) diff --git a/src/locale/localed-util.c b/src/locale/localed-util.c index e4e57a0..5699659 100644 --- a/src/locale/localed-util.c +++ b/src/locale/localed-util.c @@ -304,7 +304,7 @@ void context_clear(Context *c) { c->x11_cache = sd_bus_message_unref(c->x11_cache); c->vc_cache = sd_bus_message_unref(c->vc_cache); - c->polkit_registry = bus_verify_polkit_async_registry_free(c->polkit_registry); + c->polkit_registry = hashmap_free(c->polkit_registry); }; X11Context *context_get_x11_context(Context *c) { @@ -735,7 +735,9 @@ int vconsole_convert_to_x11(const VCContext *vc, X11Context *ret) { } int find_converted_keymap(const X11Context *xc, char **ret) { - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *n = NULL, *p = NULL, *pz = NULL; + _cleanup_strv_free_ char **keymap_dirs = NULL; + int r; assert(xc); assert(!isempty(xc->layout)); @@ -748,18 +750,29 @@ int find_converted_keymap(const X11Context *xc, char **ret) { if (!n) return -ENOMEM; - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { - _cleanup_free_ char *p = NULL, *pz = NULL; + p = strjoin("xkb/", n, ".map"); + pz = strjoin("xkb/", n, ".map.gz"); + if (!p || !pz) + return -ENOMEM; + + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + + STRV_FOREACH(dir, keymap_dirs) { + _cleanup_close_ int dir_fd = -EBADF; bool uncompressed; - p = strjoin(dir, "xkb/", n, ".map"); - pz = strjoin(dir, "xkb/", n, ".map.gz"); - if (!p || !pz) - return -ENOMEM; + dir_fd = open(*dir, O_CLOEXEC | O_DIRECTORY | O_PATH); + if (dir_fd < 0) { + if (errno != ENOENT) + log_debug_errno(errno, "Failed to open %s, ignoring: %m", *dir); + continue; + } - uncompressed = access(p, F_OK) == 0; - if (uncompressed || access(pz, F_OK) == 0) { - log_debug("Found converted keymap %s at %s", n, uncompressed ? p : pz); + uncompressed = faccessat(dir_fd, p, F_OK, 0) >= 0; + if (uncompressed || faccessat(dir_fd, pz, F_OK, 0) >= 0) { + log_debug("Found converted keymap %s at %s/%s", n, *dir, uncompressed ? p : pz); *ret = TAKE_PTR(n); return 1; } diff --git a/src/locale/localed.c b/src/locale/localed.c index 5d96237..c0d1045 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -15,6 +15,7 @@ #include "bus-polkit.h" #include "bus-unit-util.h" #include "constants.h" +#include "daemon-util.h" #include "kbd-util.h" #include "localed-util.h" #include "macro.h" @@ -157,11 +158,7 @@ static int process_locale_list_item( if (new_locale[p]) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale variable %s set twice, refusing.", name); - new_locale[p] = strdup(e); - if (!new_locale[p]) - return -ENOMEM; - - return 0; + return strdup_to(&new_locale[p], e); } return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Locale assignment %s not valid, refusing.", assignment); @@ -281,13 +278,12 @@ static int method_set_locale(sd_bus_message *m, void *userdata, sd_bus_error *er return sd_bus_reply_method_return(m, NULL); } - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.locale1.set-locale", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -386,13 +382,12 @@ static int method_set_vc_keyboard(sd_bus_message *m, void *userdata, sd_bus_erro if (vc_context_equal(&c->vc, &in) && !x_needs_update) return sd_bus_reply_method_return(m, NULL); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -506,13 +501,12 @@ static int method_set_x11_keyboard(sd_bus_message *m, void *userdata, sd_bus_err if (x11_context_equal(&c->x11_from_vc, &in) && x11_context_equal(&c->x11_from_xorg, &in) && !convert) return sd_bus_reply_method_return(m, NULL); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_ADMIN, "org.freedesktop.locale1.set-keyboard", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -650,26 +644,24 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - r = sd_event_default(&event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); (void) sd_event_set_watchdog(event, true); - r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to install SIGINT handler: %m"); - - r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + r = sd_event_set_signal_exit(event, true); if (r < 0) - return log_error_errno(r, "Failed to install SIGTERM handler: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); r = connect_bus(&context, event, &bus); if (r < 0) return r; + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.locale1", DEFAULT_EXIT_USEC, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); diff --git a/src/locale/xkbcommon-util.c b/src/locale/xkbcommon-util.c index 295ac8a..d4e24cd 100644 --- a/src/locale/xkbcommon-util.c +++ b/src/locale/xkbcommon-util.c @@ -9,22 +9,17 @@ #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); +DLSYM_FUNCTION(xkb_context_new); +DLSYM_FUNCTION(xkb_context_unref); +DLSYM_FUNCTION(xkb_context_set_log_fn); +DLSYM_FUNCTION(xkb_keymap_new_from_names); +DLSYM_FUNCTION(xkb_keymap_unref); static int dlopen_xkbcommon(void) { + ELF_NOTE_DLOPEN("xkbcommon", + "Support for keyboard locale descriptions", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, "libxkbcommon.so.0"); + return dlopen_many_sym_or_warn( &xkbcommon_dl, "libxkbcommon.so.0", LOG_DEBUG, DLSYM_ARG(xkb_context_new), diff --git a/src/locale/xkbcommon-util.h b/src/locale/xkbcommon-util.h index e99c2d7..92f45c2 100644 --- a/src/locale/xkbcommon-util.h +++ b/src/locale/xkbcommon-util.h @@ -1,23 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "dlfcn-util.h" + #if HAVE_XKBCOMMON #include -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); +DLSYM_PROTOTYPE(xkb_context_new); +DLSYM_PROTOTYPE(xkb_context_unref); +DLSYM_PROTOTYPE(xkb_context_set_log_fn); +DLSYM_PROTOTYPE(xkb_keymap_new_from_names); +DLSYM_PROTOTYPE(xkb_keymap_unref); int verify_xkb_rmlvo(const char *model, const char *layout, const char *variant, const char *options); diff --git a/src/login/inhibit.c b/src/login/inhibit.c index ad73c4b..4682830 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -111,7 +111,7 @@ static int print_inhibitors(sd_bus *bus) { if (r < 0) return bus_log_parse_error(r); - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { r = table_set_sort(table, (size_t) 1, (size_t) 0, (size_t) 5, (size_t) 6); if (r < 0) return table_log_sort_error(r); @@ -124,10 +124,10 @@ static int print_inhibitors(sd_bus *bus) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu inhibitors listed.\n", table_get_rows(table) - 1); - else + if (table_isempty(table)) printf("No inhibitors.\n"); + else + printf("\n%zu inhibitors listed.\n", table_get_rows(table) - 1); } return 0; @@ -257,9 +257,7 @@ static int run(int argc, char *argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 7fc6efc..cf3bff4 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -44,6 +44,7 @@ static BusPrintPropertyFlags arg_print_flags = 0; static bool arg_full = false; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; +static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static const char *arg_kill_whom = NULL; static int arg_signal = SIGTERM; static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; @@ -113,80 +114,115 @@ static OutputFlags get_output_flags(void) { colors_enabled() * OUTPUT_COLOR; } -static int show_table(Table *table, const char *word) { +static int list_table_print(Table *table, const char *type) { int r; assert(table); - assert(word); + assert(type); - if (table_get_rows(table) > 1 || OUTPUT_MODE_IS_JSON(arg_output)) { - r = table_set_sort(table, (size_t) 0); - if (r < 0) - return table_log_sort_error(r); + r = table_set_sort(table, (size_t) 0); + if (r < 0) + return table_log_sort_error(r); - table_set_header(table, arg_legend); + r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); + if (r < 0) + return r; - if (OUTPUT_MODE_IS_JSON(arg_output)) - r = table_print_json(table, NULL, output_mode_to_json_format_flags(arg_output) | JSON_FORMAT_COLOR_AUTO); + if (arg_legend) { + if (table_isempty(table)) + printf("No %s.\n", type); else - r = table_print(table, NULL); - if (r < 0) - return table_log_print_error(r); + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, type); } - if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); + return 0; +} + +static int list_sessions_table_add(Table *table, sd_bus_message *reply) { + int r; + + assert(table); + assert(reply); + + r = sd_bus_message_enter_container(reply, 'a', "(sussussbto)"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + const char *session_id, *user, *seat, *class, *tty; + uint32_t uid, leader_pid; + int idle; + uint64_t idle_timestamp_monotonic; + + r = sd_bus_message_read(reply, "(sussussbto)", + &session_id, + &uid, + &user, + &seat, + &leader_pid, + &class, + &tty, + &idle, + &idle_timestamp_monotonic, + /* object = */ NULL); + if (r < 0) + return bus_log_parse_error(r); + if (r == 0) + break; + + r = table_add_many(table, + TABLE_STRING, session_id, + TABLE_UID, (uid_t) uid, + TABLE_STRING, user, + TABLE_STRING, empty_to_null(seat), + TABLE_PID, (pid_t) leader_pid, + TABLE_STRING, class, + TABLE_STRING, empty_to_null(tty), + TABLE_BOOLEAN, idle); + if (r < 0) + return table_log_add_error(r); + + if (idle) + r = table_add_cell(table, NULL, TABLE_TIMESTAMP_RELATIVE_MONOTONIC, &idle_timestamp_monotonic); else - printf("No %s.\n", word); + r = table_add_cell(table, NULL, TABLE_EMPTY, NULL); + if (r < 0) + return table_log_add_error(r); } + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + return 0; } -static int list_sessions(int argc, char *argv[], void *userdata) { +static int list_sessions_table_add_fallback(Table *table, sd_bus_message *reply, sd_bus *bus) { static const struct bus_properties_map map[] = { + { "Leader", "u", NULL, offsetof(SessionStatusInfo, leader) }, + { "Class", "s", NULL, offsetof(SessionStatusInfo, class) }, + { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) }, { "IdleHint", "b", NULL, offsetof(SessionStatusInfo, idle_hint) }, { "IdleSinceHintMonotonic", "t", NULL, offsetof(SessionStatusInfo, idle_hint_timestamp.monotonic) }, - { "State", "s", NULL, offsetof(SessionStatusInfo, state) }, - { "TTY", "s", NULL, offsetof(SessionStatusInfo, tty) }, {}, }; - _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; - sd_bus *bus = ASSERT_PTR(userdata); int r; - assert(argv); - - pager_open(arg_pager_flags); - - r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r)); + assert(table); + assert(reply); + assert(bus); r = sd_bus_message_enter_container(reply, 'a', "(susso)"); if (r < 0) return bus_log_parse_error(r); - table = table_new("session", "uid", "user", "seat", "tty", "state", "idle", "since"); - if (!table) - return log_oom(); - - /* Right-align the first two fields (since they are numeric) */ - (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); - (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100); - - (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - for (;;) { _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; const char *id, *user, *seat, *object; uint32_t uid; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; SessionStatusInfo i = {}; r = sd_bus_message_read(reply, "(susso)", &id, &uid, &user, &seat, &object); @@ -209,8 +245,9 @@ static int list_sessions(int argc, char *argv[], void *userdata) { TABLE_UID, (uid_t) uid, TABLE_STRING, user, TABLE_STRING, empty_to_null(seat), + TABLE_PID, i.leader, + TABLE_STRING, i.class, TABLE_STRING, empty_to_null(i.tty), - TABLE_STRING, i.state, TABLE_BOOLEAN, i.idle_hint); if (r < 0) return table_log_add_error(r); @@ -227,7 +264,49 @@ static int list_sessions(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - return show_table(table, "sessions"); + return 0; +} + +static int list_sessions(int argc, char *argv[], void *userdata) { + sd_bus *bus = ASSERT_PTR(userdata); + _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; + bool use_ex = true; + int r; + + assert(argv); + + r = bus_call_method(bus, bus_login_mgr, "ListSessionsEx", &error, &reply, NULL); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) { + sd_bus_error_free(&error); + + use_ex = false; + r = bus_call_method(bus, bus_login_mgr, "ListSessions", &error, &reply, NULL); + } + if (r < 0) + return log_error_errno(r, "Failed to list sessions: %s", bus_error_message(&error, r)); + } + + table = table_new("session", "uid", "user", "seat", "leader", "class", "tty", "idle", "since"); + if (!table) + return log_oom(); + + /* Right-align the first two fields (since they are numeric) */ + (void) table_set_align_percent(table, TABLE_HEADER_CELL(0), 100); + (void) table_set_align_percent(table, TABLE_HEADER_CELL(1), 100); + + (void) table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + + if (use_ex) + r = list_sessions_table_add(table, reply); + else + r = list_sessions_table_add_fallback(table, reply, bus); + if (r < 0) + return r; + + return list_table_print(table, "sessions"); } static int list_users(int argc, char *argv[], void *userdata) { @@ -246,8 +325,6 @@ static int list_users(int argc, char *argv[], void *userdata) { assert(argv); - pager_open(arg_pager_flags); - r = bus_call_method(bus, bus_login_mgr, "ListUsers", &error, &reply, NULL); if (r < 0) return log_error_errno(r, "Failed to list users: %s", bus_error_message(&error, r)); @@ -305,7 +382,7 @@ static int list_users(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - return show_table(table, "users"); + return list_table_print(table, "users"); } static int list_seats(int argc, char *argv[], void *userdata) { @@ -317,8 +394,6 @@ static int list_seats(int argc, char *argv[], void *userdata) { assert(argv); - pager_open(arg_pager_flags); - r = bus_call_method(bus, bus_login_mgr, "ListSeats", &error, &reply, NULL); if (r < 0) return log_error_errno(r, "Failed to list seats: %s", bus_error_message(&error, r)); @@ -351,7 +426,7 @@ static int list_seats(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - return show_table(table, "seats"); + return list_table_print(table, "seats"); } static int show_unit_cgroup( @@ -658,16 +733,15 @@ static int print_session_status_info(sd_bus *bus, const char *path) { show_journal_by_unit( stdout, i.scope, - NULL, + /* namespace = */ NULL, arg_output, - 0, + /* n_columns = */ 0, i.timestamp.monotonic, arg_lines, - 0, get_output_flags() | OUTPUT_BEGIN_NEWLINE, SD_JOURNAL_LOCAL_ONLY, - true, - NULL); + /* system_unit = */ true, + /* ellipsized = */ NULL); } return 0; @@ -764,16 +838,15 @@ static int print_user_status_info(sd_bus *bus, const char *path) { show_journal_by_unit( stdout, i.slice, - NULL, + /* namespace = */ NULL, arg_output, - 0, + /* n_columns = */ 0, i.timestamp.monotonic, arg_lines, - 0, get_output_flags() | OUTPUT_BEGIN_NEWLINE, SD_JOURNAL_LOCAL_ONLY, - true, - NULL); + /* system_unit = */ true, + /* ellipsized = */ NULL); } return 0; @@ -953,7 +1026,6 @@ static int get_bus_path_by_id( _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *p = NULL; const char *path; int r; @@ -972,12 +1044,7 @@ static int get_bus_path_by_id( if (r < 0) return bus_log_parse_error(r); - p = strdup(path); - if (!p) - return log_oom(); - - *ret = TAKE_PTR(p); - return 0; + return strdup_to(ret, path); } static int show_session(int argc, char *argv[], void *userdata) { @@ -1442,7 +1509,10 @@ static int help(int argc, char *argv[], void *userdata) { " --kill-whom=WHOM Whom to send signal to\n" " -s --signal=SIGNAL Which signal to send\n" " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short, short-precise,\n" + " --json=MODE Generate JSON output for list-sessions/users/seats\n" + " (takes one of pretty, short, or off)\n" + " -j Same as --json=pretty on tty, --json=short otherwise\n" + " -o --output=MODE Change journal output mode (short, short-precise,\n" " short-iso, short-iso-precise, short-full,\n" " short-monotonic, short-unix, short-delta,\n" " json, json-pretty, json-sse, json-seq, cat,\n" @@ -1464,6 +1534,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_VALUE, ARG_NO_PAGER, ARG_NO_LEGEND, + ARG_JSON, ARG_KILL_WHOM, ARG_NO_ASK_PASSWORD, }; @@ -1477,6 +1548,7 @@ static int parse_argv(int argc, char *argv[]) { { "full", no_argument, NULL, 'l' }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "json", required_argument, NULL, ARG_JSON }, { "kill-whom", required_argument, NULL, ARG_KILL_WHOM }, { "signal", required_argument, NULL, 's' }, { "host", required_argument, NULL, 'H' }, @@ -1492,7 +1564,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hp:P:als:H:M:n:o:j", options, NULL)) >= 0) switch (c) { @@ -1546,7 +1618,19 @@ static int parse_argv(int argc, char *argv[]) { if (arg_output < 0) return log_error_errno(arg_output, "Unknown output '%s'.", optarg); - if (OUTPUT_MODE_IS_JSON(arg_output)) + break; + + case 'j': + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + arg_legend = false; + break; + + case ARG_JSON: + r = parse_json_argument(optarg, &arg_json_format_flags); + if (r <= 0) + return r; + + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) arg_legend = false; break; diff --git a/src/login/logind-action.c b/src/login/logind-action.c index 8269f52..9325d91 100644 --- a/src/login/logind-action.c +++ b/src/login/logind-action.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "bus-error.h" +#include "bus-unit-util.h" #include "bus-util.h" #include "conf-parser.h" #include "format-util.h" @@ -133,6 +134,63 @@ const HandleActionData* handle_action_lookup(HandleAction action) { return &handle_action_data_table[action]; } +static bool handle_action_sleep_supported(HandleAction action) { + assert(HANDLE_ACTION_IS_SLEEP(action) && action != HANDLE_SLEEP); + return sleep_supported(ASSERT_PTR(handle_action_lookup(action))->sleep_operation) > 0; +} + +/* The order in which we try each sleep operation. We should typically prefer operations without a delay, + * i.e. s2h and suspend, and use hibernation at last since it requires minimum hardware support. + * hybrid-sleep is disabled by default, and thus should be ordered before suspend if manually chosen by user, + * since it implies suspend and will probably never be selected by us otherwise. */ +static const HandleAction sleep_actions[] = { + HANDLE_SUSPEND_THEN_HIBERNATE, + HANDLE_HYBRID_SLEEP, + HANDLE_SUSPEND, + HANDLE_HIBERNATE, +}; + +int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret) { + _cleanup_strv_free_ char **actions = NULL; + int r; + + assert(ret); + + FOREACH_ELEMENT(i, sleep_actions) + if (FLAGS_SET(mask, 1U << *i)) { + r = strv_extend(&actions, handle_action_to_string(*i)); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(actions); + return 0; +} + +HandleAction handle_action_sleep_select(Manager *m) { + assert(m); + + FOREACH_ELEMENT(i, sleep_actions) { + HandleActionSleepMask action_mask = 1U << *i; + const HandleActionData *a; + _cleanup_free_ char *load_state = NULL; + + if (!FLAGS_SET(m->handle_action_sleep_mask, action_mask)) + continue; + + a = ASSERT_PTR(handle_action_lookup(*i)); + + if (sleep_supported(a->sleep_operation) <= 0) + continue; + + (void) unit_load_state(m->bus, a->target, &load_state); + if (streq_ptr(load_state, "loaded")) + return *i; + } + + return _HANDLE_ACTION_INVALID; +} + static int handle_action_execute( Manager *m, HandleAction handle, @@ -158,6 +216,7 @@ static int handle_action_execute( int r; assert(m); + assert(!IN_SET(handle, HANDLE_IGNORE, HANDLE_LOCK, HANDLE_SLEEP)); if (handle == HANDLE_KEXEC && access(KEXEC, X_OK) < 0) return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), @@ -169,7 +228,7 @@ static int handle_action_execute( handle_action_to_string(m->delayed_action->handle), handle_action_to_string(handle)); - inhibit_operation = handle_action_lookup(handle)->inhibit_what; + inhibit_operation = ASSERT_PTR(handle_action_lookup(handle))->inhibit_what; /* If the actual operation is inhibited, warn and fail */ if (inhibit_what_is_valid(inhibit_operation) && @@ -208,21 +267,21 @@ static int handle_action_sleep_execute( bool ignore_inhibited, bool is_edge) { - bool supported; - assert(m); assert(HANDLE_ACTION_IS_SLEEP(handle)); - if (handle == HANDLE_SUSPEND) - supported = sleep_supported(SLEEP_SUSPEND) > 0; - else if (handle == HANDLE_HIBERNATE) - supported = sleep_supported(SLEEP_HIBERNATE) > 0; - else if (handle == HANDLE_HYBRID_SLEEP) - supported = sleep_supported(SLEEP_HYBRID_SLEEP) > 0; - else if (handle == HANDLE_SUSPEND_THEN_HIBERNATE) - supported = sleep_supported(SLEEP_SUSPEND_THEN_HIBERNATE) > 0; - else - assert_not_reached(); + if (handle == HANDLE_SLEEP) { + HandleAction a; + + a = handle_action_sleep_select(m); + if (a < 0) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "None of the configured sleep operations are supported, ignoring."); + + return handle_action_sleep_execute(m, a, ignore_inhibited, is_edge); + } + + bool supported = handle_action_sleep_supported(handle); if (!supported && handle != HANDLE_SUSPEND) { supported = sleep_supported(SLEEP_SUSPEND) > 0; @@ -302,8 +361,9 @@ static const char* const handle_action_verb_table[_HANDLE_ACTION_MAX] = { [HANDLE_SOFT_REBOOT] = "soft-reboot", [HANDLE_SUSPEND] = "suspend", [HANDLE_HIBERNATE] = "hibernate", - [HANDLE_HYBRID_SLEEP] = "enter hybrid sleep", + [HANDLE_HYBRID_SLEEP] = "hybrid sleep", [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend and later hibernate", + [HANDLE_SLEEP] = "sleep", [HANDLE_FACTORY_RESET] = "perform a factory reset", [HANDLE_LOCK] = "be locked", }; @@ -323,9 +383,66 @@ static const char* const handle_action_table[_HANDLE_ACTION_MAX] = { [HANDLE_HIBERNATE] = "hibernate", [HANDLE_HYBRID_SLEEP] = "hybrid-sleep", [HANDLE_SUSPEND_THEN_HIBERNATE] = "suspend-then-hibernate", + [HANDLE_SLEEP] = "sleep", [HANDLE_FACTORY_RESET] = "factory-reset", [HANDLE_LOCK] = "lock", }; DEFINE_STRING_TABLE_LOOKUP(handle_action, HandleAction); DEFINE_CONFIG_PARSE_ENUM(config_parse_handle_action, handle_action, HandleAction, "Failed to parse handle action setting"); + +int config_parse_handle_action_sleep( + 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) { + + HandleActionSleepMask *mask = ASSERT_PTR(data); + _cleanup_strv_free_ char **actions = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + goto empty; + + if (strv_split_full(&actions, rvalue, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE) < 0) + return log_oom(); + + *mask = 0; + + STRV_FOREACH(action, actions) { + HandleAction a; + + a = handle_action_from_string(*action); + if (a < 0) { + log_syntax(unit, LOG_WARNING, filename, line, a, + "Failed to parse SleepOperation '%s', ignoring: %m", *action); + continue; + } + + if (!HANDLE_ACTION_IS_SLEEP(a) || a == HANDLE_SLEEP) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "HandleAction '%s' is not a sleep operation, ignoring: %m", *action); + continue; + } + + *mask |= 1U << a; + } + + if (*mask == 0) + goto empty; + + return 0; + +empty: + *mask = HANDLE_ACTION_SLEEP_MASK_DEFAULT; + return 0; +} diff --git a/src/login/logind-action.h b/src/login/logind-action.h index dbca963..c78c18c 100644 --- a/src/login/logind-action.h +++ b/src/login/logind-action.h @@ -5,6 +5,7 @@ typedef enum HandleAction { HANDLE_IGNORE, + HANDLE_POWEROFF, _HANDLE_ACTION_SHUTDOWN_FIRST = HANDLE_POWEROFF, HANDLE_REBOOT, @@ -12,20 +13,33 @@ typedef enum HandleAction { HANDLE_KEXEC, HANDLE_SOFT_REBOOT, _HANDLE_ACTION_SHUTDOWN_LAST = HANDLE_SOFT_REBOOT, + HANDLE_SUSPEND, _HANDLE_ACTION_SLEEP_FIRST = HANDLE_SUSPEND, HANDLE_HIBERNATE, HANDLE_HYBRID_SLEEP, HANDLE_SUSPEND_THEN_HIBERNATE, - _HANDLE_ACTION_SLEEP_LAST = HANDLE_SUSPEND_THEN_HIBERNATE, + HANDLE_SLEEP, /* A "high-level" action that automatically choose an appropriate low-level sleep action */ + _HANDLE_ACTION_SLEEP_LAST = HANDLE_SLEEP, + HANDLE_LOCK, HANDLE_FACTORY_RESET, + _HANDLE_ACTION_MAX, _HANDLE_ACTION_INVALID = -EINVAL, } HandleAction; typedef struct HandleActionData HandleActionData; +typedef enum HandleActionSleepMask { + HANDLE_SLEEP_SUSPEND_MASK = 1U << HANDLE_SUSPEND, + HANDLE_SLEEP_HIBERNATE_MASK = 1U << HANDLE_HIBERNATE, + HANDLE_SLEEP_HYBRID_SLEEP_MASK = 1U << HANDLE_HYBRID_SLEEP, + HANDLE_SLEEP_SUSPEND_THEN_HIBERNATE_MASK = 1U << HANDLE_SUSPEND_THEN_HIBERNATE, +} HandleActionSleepMask; + +#define HANDLE_ACTION_SLEEP_MASK_DEFAULT (HANDLE_SLEEP_SUSPEND_THEN_HIBERNATE_MASK|HANDLE_SLEEP_SUSPEND_MASK|HANDLE_SLEEP_HIBERNATE_MASK) + #include "logind-inhibit.h" #include "logind.h" #include "sleep-config.h" @@ -55,6 +69,9 @@ struct HandleActionData { const char* log_verb; }; +int handle_action_get_enabled_sleep_actions(HandleActionSleepMask mask, char ***ret); +HandleAction handle_action_sleep_select(Manager *m); + int manager_handle_action( Manager *m, InhibitWhat inhibit_key, @@ -70,3 +87,5 @@ HandleAction handle_action_from_string(const char *s) _pure_; const HandleActionData* handle_action_lookup(HandleAction handle); CONFIG_PARSER_PROTOTYPE(config_parse_handle_action); + +CONFIG_PARSER_PROTOTYPE(config_parse_handle_action_sleep); diff --git a/src/login/logind-core.c b/src/login/logind-core.c index f15008e..71e4247 100644 --- a/src/login/logind-core.c +++ b/src/login/logind-core.c @@ -40,6 +40,8 @@ void manager_reset_config(Manager *m) { m->inhibit_delay_max = 5 * USEC_PER_SEC; m->user_stop_delay = 10 * USEC_PER_SEC; + m->handle_action_sleep_mask = HANDLE_ACTION_SLEEP_MASK_DEFAULT; + m->handle_power_key = HANDLE_POWEROFF; m->handle_power_key_long_press = HANDLE_IGNORE; m->handle_reboot_key = HANDLE_REBOOT; @@ -80,9 +82,12 @@ void manager_reset_config(Manager *m) { int manager_parse_config_file(Manager *m) { assert(m); - return config_parse_config_file("logind.conf", "Login\0", - config_item_perf_lookup, logind_gperf_lookup, - CONFIG_PARSE_WARN, m); + return config_parse_standard_file_with_dropins( + "systemd/logind.conf", + "Login\0", + config_item_perf_lookup, logind_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ m); } int manager_add_device(Manager *m, const char *sysfs, bool master, Device **ret_device) { @@ -116,7 +121,7 @@ int manager_add_seat(Manager *m, const char *id, Seat **ret_seat) { s = hashmap_get(m->seats, id); if (!s) { - r = seat_new(&s, m, id); + r = seat_new(m, id, &s); if (r < 0) return r; } @@ -136,7 +141,7 @@ int manager_add_session(Manager *m, const char *id, Session **ret_session) { s = hashmap_get(m->sessions, id); if (!s) { - r = session_new(&s, m, id); + r = session_new(m, id, &s); if (r < 0) return r; } @@ -160,7 +165,7 @@ int manager_add_user( u = hashmap_get(m->users, UID_TO_PTR(ur->uid)); if (!u) { - r = user_new(&u, m, ur); + r = user_new(m, ur, &u); if (r < 0) return r; } @@ -216,7 +221,7 @@ int manager_add_inhibitor(Manager *m, const char* id, Inhibitor **ret) { i = hashmap_get(m->inhibitors, id); if (!i) { - r = inhibitor_new(&i, m, id); + r = inhibitor_new(m, id, &i); if (r < 0) return r; } @@ -366,10 +371,8 @@ int manager_get_session_by_pidref(Manager *m, const PidRef *pid, Session **ret) return r; } else { r = cg_pidref_get_unit(pid, &unit); - if (r < 0) - return r; - - s = hashmap_get(m->session_units, unit); + if (r >= 0) + s = hashmap_get(m->session_units, unit); } if (ret) @@ -411,6 +414,9 @@ int manager_get_idle_hint(Manager *m, dual_timestamp *t) { dual_timestamp k; int ih; + if (!SESSION_CLASS_CAN_IDLE(s->class)) + continue; + ih = session_get_idle_hint(s, &k); if (ih < 0) return ih; @@ -586,7 +592,7 @@ static int manager_count_external_displays(Manager *m) { return r; FOREACH_DEVICE(e, d) { - const char *status, *enabled, *dash, *nn, *subsys; + const char *status, *enabled, *dash, *nn; sd_device *p; if (sd_device_get_parent(d, &p) < 0) @@ -595,7 +601,7 @@ static int manager_count_external_displays(Manager *m) { /* If the parent shares the same subsystem as the * device we are looking at then it is a connector, * which is what we are interested in. */ - if (sd_device_get_subsystem(p, &subsys) < 0 || !streq(subsys, "drm")) + if (!device_in_subsystem(p, "drm")) continue; if (sd_device_get_sysname(d, &nn) < 0) diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index cd2db2d..a657b6e 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -49,6 +49,7 @@ #include "sleep-config.h" #include "special.h" #include "serialize.h" +#include "signal-util.h" #include "stdio-util.h" #include "strv.h" #include "terminal-util.h" @@ -70,9 +71,7 @@ #define SHUTDOWN_SCHEDULE_FILE "/run/systemd/shutdown/scheduled" -static int update_schedule_file(Manager *m); static void reset_scheduled_shutdown(Manager *m); -static int manager_setup_shutdown_timers(Manager* m); static int get_sender_session( Manager *m, @@ -141,9 +140,9 @@ int manager_get_session_from_creds( assert(m); assert(ret); - if (SEAT_IS_SELF(name)) /* the caller's own session */ + if (SESSION_IS_SELF(name)) /* the caller's own session */ return get_sender_session(m, message, false, error, ret); - if (SEAT_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */ + if (SESSION_IS_AUTO(name)) /* The caller's own session if they have one, otherwise their user's display session */ return get_sender_session(m, message, true, error, ret); session = hashmap_get(m->sessions, name); @@ -236,7 +235,6 @@ int manager_get_seat_from_creds( static int return_test_polkit( sd_bus_message *message, - int capability, const char *action, const char **details, uid_t good_user, @@ -246,7 +244,7 @@ static int return_test_polkit( bool challenge; int r; - r = bus_test_polkit(message, capability, action, details, good_user, &challenge, e); + r = bus_test_polkit(message, action, details, good_user, &challenge, e); if (r < 0) return r; @@ -342,6 +340,29 @@ static int property_get_preparing( return sd_bus_message_append(reply, "b", b); } +static int property_get_sleep_operations( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Manager *m = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **actions = NULL; + int r; + + assert(bus); + assert(reply); + + r = handle_action_get_enabled_sleep_actions(m->handle_action_sleep_mask, &actions); + if (r < 0) + return r; + + return sd_bus_message_append_strv(reply, actions); +} + static int property_get_scheduled_shutdown( sd_bus *bus, const char *path, @@ -361,9 +382,10 @@ static int property_get_scheduled_shutdown( if (r < 0) return r; - r = sd_bus_message_append(reply, "st", - m->scheduled_shutdown_action ? handle_action_to_string(m->scheduled_shutdown_action->handle) : NULL, - m->scheduled_shutdown_timeout); + r = sd_bus_message_append( + reply, "st", + handle_action_to_string(m->scheduled_shutdown_action), + m->scheduled_shutdown_timeout); if (r < 0) return r; @@ -568,6 +590,60 @@ static int method_list_sessions(sd_bus_message *message, void *userdata, sd_bus_ return sd_bus_send(NULL, reply, NULL); } +static int method_list_sessions_ex(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = ASSERT_PTR(userdata); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(message); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(sussussbto)"); + if (r < 0) + return r; + + Session *s; + HASHMAP_FOREACH(s, m->sessions) { + _cleanup_free_ char *path = NULL; + dual_timestamp idle_ts; + bool idle; + + assert(s->user); + + path = session_bus_path(s); + if (!path) + return -ENOMEM; + + r = session_get_idle_hint(s, &idle_ts); + if (r < 0) + return r; + idle = r > 0; + + r = sd_bus_message_append(reply, "(sussussbto)", + s->id, + (uint32_t) s->user->user_record->uid, + s->user->user_record->user_name, + s->seat ? s->seat->id : "", + (uint32_t) s->leader.pid, + session_class_to_string(s->class), + s->tty, + idle, + idle_ts.monotonic, + path); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + static int method_list_users(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; Manager *m = ASSERT_PTR(userdata); @@ -682,8 +758,8 @@ static int create_session( void *userdata, sd_bus_error *error, uid_t uid, - pid_t pid, - int pidfd, + pid_t leader_pid, + int leader_pidfd, const char *service, const char *type, const char *class, @@ -716,32 +792,18 @@ static int create_session( if (flags != 0) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Flags must be zero."); - if (pidfd >= 0) { - r = pidref_set_pidfd(&leader, pidfd); - if (r < 0) - return r; - } else if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t p; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &p); - if (r < 0) - return r; - - r = pidref_set_pid(&leader, p); - if (r < 0) - return r; - } else { - assert(pid > 0); + if (leader_pidfd >= 0) + r = pidref_set_pidfd(&leader, leader_pidfd); + else if (leader_pid == 0) + r = bus_query_sender_pidref(message, &leader); + else { + if (leader_pid < 0) + return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Leader PID is not valid"); - r = pidref_set_pid(&leader, pid); - if (r < 0) - return r; + r = pidref_set_pid(&leader, leader_pid); } + if (r < 0) + return r; if (leader.pid == 1 || leader.pid == getpid_cached()) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); @@ -842,25 +904,22 @@ static int create_session( c = SESSION_USER; } - /* Check if we are already in a logind session. Or if we are in user@.service - * which is a special PAM session that avoids creating a logind session. */ - r = manager_get_user_by_pid(m, leader.pid, NULL); + /* Check if we are already in a logind session, and if so refuse. */ + r = manager_get_session_by_pidref(m, &leader, /* ret_session= */ NULL); if (r < 0) - return r; + return log_debug_errno( + r, + "Failed to check if process " PID_FMT " is already in a session: %m", + leader.pid); if (r > 0) return sd_bus_error_setf(error, BUS_ERROR_SESSION_BUSY, "Already running in a session or user slice"); - /* - * Old gdm and lightdm start the user-session on the same VT as - * the greeter session. But they destroy the greeter session - * after the user-session and want the user-session to take - * over the VT. We need to support this for - * backwards-compatibility, so make sure we allow new sessions - * on a VT that a greeter is running on. Furthermore, to allow - * re-logins, we have to allow a greeter to take over a used VT for - * the exact same reasons. - */ + /* Old gdm and lightdm start the user-session on the same VT as the greeter session. But they destroy + * the greeter session after the user-session and want the user-session to take over the VT. We need + * to support this for backwards-compatibility, so make sure we allow new sessions on a VT that a + * greeter is running on. Furthermore, to allow re-logins, we have to allow a greeter to take over a + * used VT for the exact same reasons. */ if (c != SESSION_GREETER && vtnr > 0 && vtnr < MALLOC_ELEMENTSOF(m->seat0->positions) && @@ -920,58 +979,54 @@ static int create_session( goto fail; session->original_type = session->type = t; - session->class = c; session->remote = remote; session->vtnr = vtnr; + session->class = c; + + /* Once the first session that is of a pinning class shows up we'll change the GC mode for the user + * from USER_GC_BY_ANY to USER_GC_BY_PIN, so that the user goes away once the last pinning session + * goes away. Background: we want that user@.service – when started manually – remains around (which + * itself is a non-pinning session), but gets stopped when the last pinning session goes away. */ + + if (SESSION_CLASS_PIN_USER(c)) + user->gc_mode = USER_GC_BY_PIN; if (!isempty(tty)) { - session->tty = strdup(tty); - if (!session->tty) { - r = -ENOMEM; + r = strdup_to(&session->tty, tty); + if (r < 0) goto fail; - } session->tty_validity = TTY_FROM_PAM; } if (!isempty(display)) { - session->display = strdup(display); - if (!session->display) { - r = -ENOMEM; + r = strdup_to(&session->display, display); + if (r < 0) goto fail; - } } if (!isempty(remote_user)) { - session->remote_user = strdup(remote_user); - if (!session->remote_user) { - r = -ENOMEM; + r = strdup_to(&session->remote_user, remote_user); + if (r < 0) goto fail; - } } if (!isempty(remote_host)) { - session->remote_host = strdup(remote_host); - if (!session->remote_host) { - r = -ENOMEM; + r = strdup_to(&session->remote_host, remote_host); + if (r < 0) goto fail; - } } if (!isempty(service)) { - session->service = strdup(service); - if (!session->service) { - r = -ENOMEM; + r = strdup_to(&session->service, service); + if (r < 0) goto fail; - } } if (!isempty(desktop)) { - session->desktop = strdup(desktop); - if (!session->desktop) { - r = -ENOMEM; + r = strdup_to(&session->desktop, desktop); + if (r < 0) goto fail; - } } if (seat) { @@ -994,8 +1049,14 @@ static int create_session( session->create_message = sd_bus_message_ref(message); - /* Now, let's wait until the slice unit and stuff got created. We send the reply back from - * session_send_create_reply(). */ + /* Now call into session_send_create_reply(), which will reply to this method call for us. Or it + * won't – in case we just spawned a session scope and/or user service manager, and they aren't ready + * yet. We'll call session_create_reply() again once the session scope or the user service manager is + * ready, where the function will check again if a reply is then ready to be sent, and then do so if + * all is complete - or wait again. */ + r = session_send_create_reply(session, /* error= */ NULL); + if (r < 0) + return r; return 1; @@ -1011,32 +1072,32 @@ fail: static int method_create_session(sd_bus_message *message, void *userdata, sd_bus_error *error) { const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop; - pid_t leader; + pid_t leader_pid; + uint32_t vtnr; uid_t uid; - int remote; - uint32_t vtnr = 0; - int r; + int remote, r; assert(message); assert_cc(sizeof(pid_t) == sizeof(uint32_t)); assert_cc(sizeof(uid_t) == sizeof(uint32_t)); - r = sd_bus_message_read(message, - "uusssssussbss", - &uid, - &leader, - &service, - &type, - &class, - &desktop, - &cseat, - &vtnr, - &tty, - &display, - &remote, - &remote_user, - &remote_host); + r = sd_bus_message_read( + message, + "uusssssussbss", + &uid, + &leader_pid, + &service, + &type, + &class, + &desktop, + &cseat, + &vtnr, + &tty, + &display, + &remote, + &remote_user, + &remote_host); if (r < 0) return r; @@ -1045,7 +1106,7 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus userdata, error, uid, - leader, + leader_pid, /* pidfd = */ -EBADF, service, type, @@ -1063,29 +1124,28 @@ static int method_create_session(sd_bus_message *message, void *userdata, sd_bus static int method_create_session_pidfd(sd_bus_message *message, void *userdata, sd_bus_error *error) { const char *service, *type, *class, *cseat, *tty, *display, *remote_user, *remote_host, *desktop; - int leaderfd = -EBADF; - uid_t uid; - int remote; - uint32_t vtnr = 0; uint64_t flags; - int r; + uint32_t vtnr; + uid_t uid; + int leader_fd = -EBADF, remote, r; - r = sd_bus_message_read(message, - "uhsssssussbsst", - &uid, - &leaderfd, - &service, - &type, - &class, - &desktop, - &cseat, - &vtnr, - &tty, - &display, - &remote, - &remote_user, - &remote_host, - &flags); + r = sd_bus_message_read( + message, + "uhsssssussbsst", + &uid, + &leader_fd, + &service, + &type, + &class, + &desktop, + &cseat, + &vtnr, + &tty, + &display, + &remote, + &remote_user, + &remote_host, + &flags); if (r < 0) return r; @@ -1094,8 +1154,8 @@ static int method_create_session_pidfd(sd_bus_message *message, void *userdata, userdata, error, uid, - /* pid = */ 0, - leaderfd, + /* leader_pid = */ 0, + leader_fd, service, type, class, @@ -1112,7 +1172,7 @@ static int method_create_session_pidfd(sd_bus_message *message, void *userdata, static int method_release_session(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); - Session *session; + Session *session, *sender_session; const char *name; int r; @@ -1126,6 +1186,14 @@ static int method_release_session(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; + r = get_sender_session(m, message, /* consult_display= */ false, error, &sender_session); + if (r < 0) + return r; + + if (session != sender_session) + return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, + "Refused to release session, since it doesn't match the one of the client"); + r = session_release(session); if (r < 0) return r; @@ -1221,11 +1289,8 @@ static int method_lock_sessions(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.lock-sessions", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -1336,28 +1401,25 @@ static int method_terminate_seat(sd_bus_message *message, void *userdata, sd_bus } static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - _cleanup_free_ char *cc = NULL; Manager *m = ASSERT_PTR(userdata); - int r, b, interactive; - struct passwd *pw; - const char *path; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; uint32_t uid, auth_uid; + int r, enable, interactive; assert(message); - r = sd_bus_message_read(message, "ubb", &uid, &b, &interactive); + r = sd_bus_message_read(message, "ubb", &uid, &enable, &interactive); if (r < 0) return r; - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID | - SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, &creds); + r = sd_bus_query_sender_creds(message, + SD_BUS_CREDS_EUID|SD_BUS_CREDS_OWNER_UID|SD_BUS_CREDS_AUGMENT, + &creds); if (r < 0) return r; if (!uid_is_valid(uid)) { - /* Note that we get the owner UID of the session or user unit, - * not the actual client UID here! */ + /* Note that we get the owner UID of the session or user unit, not the actual client UID here! */ r = sd_bus_creds_get_owner_uid(creds, &uid); if (r < 0) return r; @@ -1368,19 +1430,19 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; - errno = 0; - pw = getpwuid(uid); - if (!pw) - return errno_or_else(ENOENT); + _cleanup_free_ struct passwd *pw = NULL; - r = bus_verify_polkit_async( + r = getpwuid_malloc(uid, &pw); + if (r < 0) + return r; + + r = bus_verify_polkit_async_full( message, - CAP_SYS_ADMIN, uid == auth_uid ? "org.freedesktop.login1.set-self-linger" : "org.freedesktop.login1.set-user-linger", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1393,24 +1455,30 @@ static int method_set_user_linger(sd_bus_message *message, void *userdata, sd_bu if (r < 0) return r; - cc = cescape(pw->pw_name); - if (!cc) + _cleanup_free_ char *escaped = NULL; + const char *path; + User *u; + + escaped = cescape(pw->pw_name); + if (!escaped) return -ENOMEM; - path = strjoina("/var/lib/systemd/linger/", cc); - if (b) { - User *u; + path = strjoina("/var/lib/systemd/linger/", escaped); + if (enable) { r = touch(path); if (r < 0) return r; - if (manager_add_user_by_uid(m, uid, &u) >= 0) - user_start(u); + if (manager_add_user_by_uid(m, uid, &u) >= 0) { + r = user_start(u); + if (r < 0) { + user_add_to_gc_queue(u); + return r; + } + } } else { - User *u; - r = unlink(path); if (r < 0 && errno != ENOENT) return -errno; @@ -1541,13 +1609,12 @@ static int method_attach_device(sd_bus_message *message, void *userdata, sd_bus_ } else if (!seat_name_is_valid(seat)) /* Note that a seat does not have to exist yet for this operation to succeed */ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Seat name %s is not valid", seat); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.attach-device", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1572,13 +1639,12 @@ static int method_flush_devices(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.flush-devices", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1604,7 +1670,7 @@ static int have_multiple_sessions( /* Check for other users' sessions. Greeter sessions do not * count, and non-login sessions do not count either. */ HASHMAP_FOREACH(session, m->sessions) - if (session->class == SESSION_USER && + if (IN_SET(session->class, SESSION_USER, SESSION_USER_EARLY) && session->user->user_record->uid != uid) return true; @@ -1713,16 +1779,36 @@ static int send_prepare_for(Manager *m, const HandleActionData *a, bool _active) return RET_GATHER(k, r); } +static int strdup_job(sd_bus_message *reply, char **ret) { + const char *j; + char *job; + int r; + + assert(reply); + assert(ret); + + r = sd_bus_message_read_basic(reply, 'o', &j); + if (r < 0) + return r; + + job = strdup(j); + if (!job) + return -ENOMEM; + + *ret = job; + return 0; +} + static int execute_shutdown_or_sleep( Manager *m, const HandleActionData *a, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *p; int r; assert(m); + assert(!m->action_job); assert(a); if (a->inhibit_what == INHIBIT_SHUTDOWN) @@ -1736,15 +1822,11 @@ static int execute_shutdown_or_sleep( &reply, "ss", a->target, "replace-irreversibly"); if (r < 0) - goto error; - - r = sd_bus_message_read(reply, "o", &p); - if (r < 0) - goto error; + goto fail; - r = free_and_strdup(&m->action_job, p); + r = strdup_job(reply, &m->action_job); if (r < 0) - goto error; + goto fail; m->delayed_action = a; @@ -1753,7 +1835,7 @@ static int execute_shutdown_or_sleep( return 0; -error: +fail: /* Tell people that they now may take a lock again */ (void) send_prepare_for(m, a, false); @@ -1914,13 +1996,12 @@ static int verify_shutdown_creds( interactive = flags & SD_LOGIND_INTERACTIVE; if (multiple_sessions) { - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_SYS_BOOT, a->polkit_action_multiple_sessions, - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1935,12 +2016,12 @@ static int verify_shutdown_creds( return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access denied to root due to active block inhibitor"); - r = bus_verify_polkit_async(message, - CAP_SYS_BOOT, + r = bus_verify_polkit_async_full( + message, a->polkit_action_ignore_inhibit, - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1950,12 +2031,12 @@ static int verify_shutdown_creds( } if (!multiple_sessions && !blocked) { - r = bus_verify_polkit_async(message, - CAP_SYS_BOOT, + r = bus_verify_polkit_async_full( + message, a->polkit_action, - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &m->polkit_registry, error); if (r < 0) @@ -1993,7 +2074,7 @@ static int setup_wall_message_timer(Manager *m, sd_bus_message* message) { static int method_do_shutdown_or_sleep( Manager *m, sd_bus_message *message, - const HandleActionData *a, + HandleAction action, bool with_flags, sd_bus_error *error) { @@ -2002,7 +2083,7 @@ static int method_do_shutdown_or_sleep( assert(m); assert(message); - assert(a); + assert(HANDLE_ACTION_IS_SHUTDOWN(action) || HANDLE_ACTION_IS_SLEEP(action)); if (with_flags) { /* New style method: with flags parameter (and interactive bool in the bus message header) */ @@ -2017,11 +2098,11 @@ static int method_do_shutdown_or_sleep( return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Both reboot via kexec and soft reboot selected, which is not supported"); - if (a->handle != HANDLE_REBOOT) { - if (flags & SD_LOGIND_REBOOT_VIA_KEXEC) + if (action != HANDLE_REBOOT) { + if (FLAGS_SET(flags, SD_LOGIND_REBOOT_VIA_KEXEC)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Reboot via kexec option is only applicable with reboot operations"); - if ((flags & SD_LOGIND_SOFT_REBOOT) || (flags & SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP)) + if (flags & (SD_LOGIND_SOFT_REBOOT|SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Soft reboot option is only applicable with reboot operations"); } @@ -2038,20 +2119,32 @@ static int method_do_shutdown_or_sleep( flags = interactive ? SD_LOGIND_INTERACTIVE : 0; } - if ((flags & SD_LOGIND_SOFT_REBOOT) || - ((flags & SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP) && path_is_os_tree("/run/nextroot") > 0)) + const HandleActionData *a = NULL; + + if (FLAGS_SET(flags, SD_LOGIND_SOFT_REBOOT) || + (FLAGS_SET(flags, SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP) && path_is_os_tree("/run/nextroot") > 0)) a = handle_action_lookup(HANDLE_SOFT_REBOOT); - else if ((flags & SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded()) + else if (FLAGS_SET(flags, SD_LOGIND_REBOOT_VIA_KEXEC) && kexec_loaded()) a = handle_action_lookup(HANDLE_KEXEC); - /* Don't allow multiple jobs being executed at the same time */ - if (m->delayed_action) - return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, - "There's already a shutdown or sleep operation in progress"); + if (action == HANDLE_SLEEP) { + HandleAction selected; + + selected = handle_action_sleep_select(m); + if (selected < 0) + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "None of the configured sleep operations are supported"); - if (a->sleep_operation >= 0) { + assert_se(a = handle_action_lookup(selected)); + + } else if (HANDLE_ACTION_IS_SLEEP(action)) { SleepSupport support; + assert_se(a = handle_action_lookup(action)); + + assert(a->sleep_operation >= 0); + assert(a->sleep_operation < _SLEEP_OPERATION_MAX); + r = sleep_supported_full(a->sleep_operation, &support); if (r < 0) return r; @@ -2074,6 +2167,14 @@ static int method_do_shutdown_or_sleep( return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not running on EFI and resume= is not set, or noresume is set. No available method to resume from hibernation"); + case SLEEP_RESUME_DEVICE_MISSING: + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Specified resume device is missing or is not an active swap device"); + + case SLEEP_RESUME_MISCONFIGURED: + return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, + "Invalid resume config: resume= is not populated yet resume_offset= is"); + case SLEEP_NOT_ENOUGH_SWAP_SPACE: return sd_bus_error_set(error, BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, "Not enough suitable swap space for hibernation available on compatible block devices and file systems"); @@ -2082,18 +2183,25 @@ static int method_do_shutdown_or_sleep( assert_not_reached(); } - } + } else if (!a) + assert_se(a = handle_action_lookup(action)); r = verify_shutdown_creds(m, message, a, flags, error); if (r != 0) return r; + if (m->delayed_action) + return sd_bus_error_setf(error, BUS_ERROR_OPERATION_IN_PROGRESS, + "Action %s already in progress, refusing requested %s operation.", + handle_action_to_string(m->delayed_action->handle), + handle_action_to_string(a->handle)); + /* reset case we're shorting a scheduled shutdown */ m->unlink_nologin = false; reset_scheduled_shutdown(m); m->scheduled_shutdown_timeout = 0; - m->scheduled_shutdown_action = a; + m->scheduled_shutdown_action = action; (void) setup_wall_message_timer(m, message); @@ -2109,7 +2217,7 @@ static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_POWEROFF), + HANDLE_POWEROFF, sd_bus_message_is_method_call(message, NULL, "PowerOffWithFlags"), error); } @@ -2119,7 +2227,7 @@ static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error * return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_REBOOT), + HANDLE_REBOOT, sd_bus_message_is_method_call(message, NULL, "RebootWithFlags"), error); } @@ -2129,7 +2237,7 @@ static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *er return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_HALT), + HANDLE_HALT, sd_bus_message_is_method_call(message, NULL, "HaltWithFlags"), error); } @@ -2139,7 +2247,7 @@ static int method_suspend(sd_bus_message *message, void *userdata, sd_bus_error return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_SUSPEND), + HANDLE_SUSPEND, sd_bus_message_is_method_call(message, NULL, "SuspendWithFlags"), error); } @@ -2149,7 +2257,7 @@ static int method_hibernate(sd_bus_message *message, void *userdata, sd_bus_erro return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_HIBERNATE), + HANDLE_HIBERNATE, sd_bus_message_is_method_call(message, NULL, "HibernateWithFlags"), error); } @@ -2159,7 +2267,7 @@ static int method_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_e return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_HYBRID_SLEEP), + HANDLE_HYBRID_SLEEP, sd_bus_message_is_method_call(message, NULL, "HybridSleepWithFlags"), error); } @@ -2169,17 +2277,27 @@ static int method_suspend_then_hibernate(sd_bus_message *message, void *userdata return method_do_shutdown_or_sleep( m, message, - handle_action_lookup(HANDLE_SUSPEND_THEN_HIBERNATE), + HANDLE_SUSPEND_THEN_HIBERNATE, sd_bus_message_is_method_call(message, NULL, "SuspendThenHibernateWithFlags"), error); } +static int method_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + + return method_do_shutdown_or_sleep( + m, message, + HANDLE_SLEEP, + /* with_flags = */ true, + error); +} + static int nologin_timeout_handler( sd_event_source *s, uint64_t usec, void *userdata) { - Manager *m = userdata; + Manager *m = ASSERT_PTR(userdata); log_info("Creating /run/nologin, blocking further logins..."); @@ -2194,79 +2312,25 @@ static usec_t nologin_timeout_usec(usec_t elapse) { return LESS_BY(elapse, 5 * USEC_PER_MINUTE); } -void manager_load_scheduled_shutdown(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *usec = NULL, - *warn_wall = NULL, - *mode = NULL, - *wall_message = NULL, - *uid = NULL, - *tty = NULL; - int r; - +static void reset_scheduled_shutdown(Manager *m) { assert(m); - r = parse_env_file(f, SHUTDOWN_SCHEDULE_FILE, - "USEC", &usec, - "WARN_WALL", &warn_wall, - "MODE", &mode, - "WALL_MESSAGE", &wall_message, - "UID", &uid, - "TTY", &tty); - - /* reset will delete the file */ - reset_scheduled_shutdown(m); - - if (r == -ENOENT) - return; - if (r < 0) - return (void) log_debug_errno(r, "Failed to parse " SHUTDOWN_SCHEDULE_FILE ": %m"); - - HandleAction handle = handle_action_from_string(mode); - if (handle < 0) - return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse scheduled shutdown type: %s", mode); - - if (!usec) - return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "USEC is required"); - if (deserialize_usec(usec, &m->scheduled_shutdown_timeout) < 0) - return; - - /* assign parsed type only after we know usec is also valid */ - m->scheduled_shutdown_action = handle_action_lookup(handle); - - if (warn_wall) { - r = parse_boolean(warn_wall); - if (r < 0) - log_debug_errno(r, "Failed to parse enabling wall messages"); - else - m->enable_wall_messages = r; - } + m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); + m->wall_message_timeout_source = sd_event_source_unref(m->wall_message_timeout_source); + m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source); - if (wall_message) { - _cleanup_free_ char *unescaped = NULL; - r = cunescape(wall_message, 0, &unescaped); - if (r < 0) - log_debug_errno(r, "Failed to parse wall message: %s", wall_message); - else - free_and_replace(m->wall_message, unescaped); - } + m->scheduled_shutdown_action = _HANDLE_ACTION_INVALID; + m->scheduled_shutdown_timeout = USEC_INFINITY; + m->scheduled_shutdown_uid = UID_INVALID; + m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); + m->shutdown_dry_run = false; - if (uid) { - r = parse_uid(uid, &m->scheduled_shutdown_uid); - if (r < 0) - log_debug_errno(r, "Failed to parse wall uid: %s", uid); + if (m->unlink_nologin) { + (void) unlink_or_warn("/run/nologin"); + m->unlink_nologin = false; } - free_and_replace(m->scheduled_shutdown_tty, tty); - - r = manager_setup_shutdown_timers(m); - if (r < 0) - return reset_scheduled_shutdown(m); - - (void) manager_setup_wall_message_timer(m); - (void) update_schedule_file(m); - - return; + (void) unlink(SHUTDOWN_SCHEDULE_FILE); } static int update_schedule_file(Manager *m) { @@ -2275,7 +2339,7 @@ static int update_schedule_file(Manager *m) { int r; assert(m); - assert(m->scheduled_shutdown_action); + assert(handle_action_valid(m->scheduled_shutdown_action)); r = mkdir_parents_label(SHUTDOWN_SCHEDULE_FILE, 0755); if (r < 0) @@ -2289,7 +2353,7 @@ static int update_schedule_file(Manager *m) { serialize_usec(f, "USEC", m->scheduled_shutdown_timeout); serialize_item_format(f, "WARN_WALL", "%s", one_zero(m->enable_wall_messages)); - serialize_item_format(f, "MODE", "%s", handle_action_to_string(m->scheduled_shutdown_action->handle)); + serialize_item_format(f, "MODE", "%s", handle_action_to_string(m->scheduled_shutdown_action)); serialize_item_format(f, "UID", UID_FMT, m->scheduled_shutdown_uid); if (m->scheduled_shutdown_tty) @@ -2319,71 +2383,155 @@ fail: return log_error_errno(r, "Failed to write information about scheduled shutdowns: %m"); } -static void reset_scheduled_shutdown(Manager *m) { +static int manager_scheduled_shutdown_handler( + sd_event_source *s, + uint64_t usec, + void *userdata) { + + Manager *m = ASSERT_PTR(userdata); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const HandleActionData *a; + int r; + + assert_se(a = handle_action_lookup(m->scheduled_shutdown_action)); + + /* Don't allow multiple jobs being executed at the same time */ + if (m->delayed_action) { + r = log_error_errno(SYNTHETIC_ERRNO(EALREADY), + "Scheduled shutdown to %s failed: shutdown or sleep operation already in progress.", + a->target); + goto error; + } + + if (m->shutdown_dry_run) { + /* We do not process delay inhibitors here. Otherwise, we + * would have to be considered "in progress" (like the check + * above) for some seconds after our admin has seen the final + * wall message. */ + + bus_manager_log_shutdown(m, a); + log_info("Running in dry run, suppressing action."); + reset_scheduled_shutdown(m); + + return 0; + } + + r = bus_manager_shutdown_or_sleep_now_or_later(m, a, &error); + if (r < 0) { + log_error_errno(r, "Scheduled shutdown to %s failed: %m", a->target); + goto error; + } + + return 0; + +error: + reset_scheduled_shutdown(m); + return r; +} + +static int manager_setup_shutdown_timers(Manager* m) { + int r; + assert(m); + r = event_reset_time(m->event, &m->scheduled_shutdown_timeout_source, + CLOCK_REALTIME, + m->scheduled_shutdown_timeout, 0, + manager_scheduled_shutdown_handler, m, + 0, "scheduled-shutdown-timeout", true); + if (r < 0) + goto fail; + + r = event_reset_time(m->event, &m->nologin_timeout_source, + CLOCK_REALTIME, + nologin_timeout_usec(m->scheduled_shutdown_timeout), 0, + nologin_timeout_handler, m, + 0, "nologin-timeout", true); + if (r < 0) + goto fail; + + return 0; + +fail: m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); - m->wall_message_timeout_source = sd_event_source_unref(m->wall_message_timeout_source); m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source); - m->scheduled_shutdown_action = NULL; - m->scheduled_shutdown_timeout = USEC_INFINITY; - m->scheduled_shutdown_uid = UID_INVALID; - m->scheduled_shutdown_tty = mfree(m->scheduled_shutdown_tty); - m->shutdown_dry_run = false; + return r; +} - if (m->unlink_nologin) { - (void) unlink_or_warn("/run/nologin"); - m->unlink_nologin = false; - } +void manager_load_scheduled_shutdown(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *usec = NULL, + *warn_wall = NULL, + *mode = NULL, + *wall_message = NULL, + *uid = NULL, + *tty = NULL; + int r; - (void) unlink(SHUTDOWN_SCHEDULE_FILE); -} + assert(m); -static int manager_scheduled_shutdown_handler( - sd_event_source *s, - uint64_t usec, - void *userdata) { + r = parse_env_file(f, SHUTDOWN_SCHEDULE_FILE, + "USEC", &usec, + "WARN_WALL", &warn_wall, + "MODE", &mode, + "WALL_MESSAGE", &wall_message, + "UID", &uid, + "TTY", &tty); - const HandleActionData *a = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Manager *m = ASSERT_PTR(userdata); - int r; + /* reset will delete the file */ + reset_scheduled_shutdown(m); - a = m->scheduled_shutdown_action; - assert(a); + if (r == -ENOENT) + return; + if (r < 0) + return (void) log_debug_errno(r, "Failed to parse " SHUTDOWN_SCHEDULE_FILE ": %m"); - /* Don't allow multiple jobs being executed at the same time */ - if (m->delayed_action) { - r = -EALREADY; - log_error("Scheduled shutdown to %s failed: shutdown or sleep operation already in progress", a->target); - goto error; - } + HandleAction handle = handle_action_from_string(mode); + if (handle < 0) + return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse scheduled shutdown type: %s", mode); - if (m->shutdown_dry_run) { - /* We do not process delay inhibitors here. Otherwise, we - * would have to be considered "in progress" (like the check - * above) for some seconds after our admin has seen the final - * wall message. */ + if (!usec) + return (void) log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "USEC is required"); + if (deserialize_usec(usec, &m->scheduled_shutdown_timeout) < 0) + return; - bus_manager_log_shutdown(m, a); - log_info("Running in dry run, suppressing action."); - reset_scheduled_shutdown(m); + /* assign parsed type only after we know usec is also valid */ + m->scheduled_shutdown_action = handle; - return 0; + if (warn_wall) { + r = parse_boolean(warn_wall); + if (r < 0) + log_debug_errno(r, "Failed to parse enabling wall messages"); + else + m->enable_wall_messages = r; } - r = bus_manager_shutdown_or_sleep_now_or_later(m, m->scheduled_shutdown_action, &error); - if (r < 0) { - log_error_errno(r, "Scheduled shutdown to %s failed: %m", a->target); - goto error; + if (wall_message) { + _cleanup_free_ char *unescaped = NULL; + r = cunescape(wall_message, 0, &unescaped); + if (r < 0) + log_debug_errno(r, "Failed to parse wall message: %s", wall_message); + else + free_and_replace(m->wall_message, unescaped); } - return 0; + if (uid) { + r = parse_uid(uid, &m->scheduled_shutdown_uid); + if (r < 0) + log_debug_errno(r, "Failed to parse wall uid: %s", uid); + } -error: - reset_scheduled_shutdown(m); - return r; + free_and_replace(m->scheduled_shutdown_tty, tty); + + r = manager_setup_shutdown_timers(m); + if (r < 0) + return reset_scheduled_shutdown(m); + + (void) manager_setup_wall_message_timer(m); + (void) update_schedule_file(m); + + return; } static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -2410,15 +2558,14 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ if (!HANDLE_ACTION_IS_SHUTDOWN(handle)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unsupported shutdown type: %s", type); - a = handle_action_lookup(handle); - assert(a); + assert_se(a = handle_action_lookup(handle)); assert(a->polkit_action); r = verify_shutdown_creds(m, message, a, 0, error); if (r != 0) return r; - m->scheduled_shutdown_action = a; + m->scheduled_shutdown_action = handle; m->shutdown_dry_run = dry_run; m->scheduled_shutdown_timeout = elapse; @@ -2438,34 +2585,6 @@ static int method_schedule_shutdown(sd_bus_message *message, void *userdata, sd_ return sd_bus_reply_method_return(message, NULL); } -static int manager_setup_shutdown_timers(Manager* m) { - int r; - - r = event_reset_time(m->event, &m->scheduled_shutdown_timeout_source, - CLOCK_REALTIME, - m->scheduled_shutdown_timeout, 0, - manager_scheduled_shutdown_handler, m, - 0, "scheduled-shutdown-timeout", true); - if (r < 0) - goto fail; - - r = event_reset_time(m->event, &m->nologin_timeout_source, - CLOCK_REALTIME, - nologin_timeout_usec(m->scheduled_shutdown_timeout), 0, - nologin_timeout_handler, m, - 0, "nologin-timeout", true); - if (r < 0) - goto fail; - - return 0; - -fail: - m->scheduled_shutdown_timeout_source = sd_event_source_unref(m->scheduled_shutdown_timeout_source); - m->nologin_timeout_source = sd_event_source_unref(m->nologin_timeout_source); - - return r; -} - static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = ASSERT_PTR(userdata); const HandleActionData *a; @@ -2474,22 +2593,18 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd assert(message); - cancelled = m->scheduled_shutdown_action - && !IN_SET(m->scheduled_shutdown_action->handle, HANDLE_IGNORE, _HANDLE_ACTION_INVALID); + cancelled = handle_action_valid(m->scheduled_shutdown_action) && m->scheduled_shutdown_action != HANDLE_IGNORE; if (!cancelled) return sd_bus_reply_method_return(message, "b", false); - a = m->scheduled_shutdown_action; + assert_se(a = handle_action_lookup(m->scheduled_shutdown_action)); if (!a->polkit_action) return sd_bus_error_set(error, SD_BUS_ERROR_AUTH_FAILED, "Unsupported shutdown type"); r = bus_verify_polkit_async( message, - CAP_SYS_BOOT, a->polkit_action, - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -2528,28 +2643,46 @@ static int method_cancel_scheduled_shutdown(sd_bus_message *message, void *userd static int method_can_shutdown_or_sleep( Manager *m, sd_bus_message *message, - const HandleActionData *a, + HandleAction action, sd_bus_error *error) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - bool multiple_sessions, challenge, blocked; + bool multiple_sessions, challenge, blocked, check_unit_state = true; + const HandleActionData *a; const char *result = NULL; uid_t uid; int r; assert(m); assert(message); - assert(a); + assert(HANDLE_ACTION_IS_SHUTDOWN(action) || HANDLE_ACTION_IS_SLEEP(action)); + + if (action == HANDLE_SLEEP) { + HandleAction selected; + + selected = handle_action_sleep_select(m); + if (selected < 0) + return sd_bus_reply_method_return(message, "s", "na"); - if (a->sleep_operation >= 0) { + check_unit_state = false; /* Already handled by handle_action_sleep_select */ + + assert_se(a = handle_action_lookup(selected)); + + } else if (HANDLE_ACTION_IS_SLEEP(action)) { SleepSupport support; + assert_se(a = handle_action_lookup(action)); + + assert(a->sleep_operation >= 0); + assert(a->sleep_operation < _SLEEP_OPERATION_MAX); + r = sleep_supported_full(a->sleep_operation, &support); if (r < 0) return r; if (r == 0) return sd_bus_reply_method_return(message, "s", support == SLEEP_DISABLED ? "no" : "na"); - } + } else + assert_se(a = handle_action_lookup(action)); r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); if (r < 0) @@ -2566,27 +2699,27 @@ static int method_can_shutdown_or_sleep( multiple_sessions = r > 0; blocked = manager_is_inhibited(m, a->inhibit_what, INHIBIT_BLOCK, NULL, false, true, uid, NULL); - HandleAction handle = handle_action_from_string(sleep_operation_to_string(a->sleep_operation)); - if (handle >= 0) { - const char *target; + if (check_unit_state && a->target) { + _cleanup_free_ char *load_state = NULL; - target = handle_action_lookup(handle)->target; - if (target) { - _cleanup_free_ char *load_state = NULL; - - r = unit_load_state(m->bus, target, &load_state); - if (r < 0) - return r; + r = unit_load_state(m->bus, a->target, &load_state); + if (r < 0) + return r; - if (!streq(load_state, "loaded")) { - result = "no"; - goto finish; - } + if (!streq(load_state, "loaded")) { + result = "no"; + goto finish; } } if (multiple_sessions) { - r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_multiple_sessions, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit( + message, + a->polkit_action_multiple_sessions, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + &challenge, + error); if (r < 0) return r; @@ -2599,7 +2732,13 @@ static int method_can_shutdown_or_sleep( } if (blocked) { - r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action_ignore_inhibit, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit( + message, + a->polkit_action_ignore_inhibit, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + &challenge, + error); if (r < 0) return r; @@ -2617,7 +2756,13 @@ static int method_can_shutdown_or_sleep( /* If neither inhibit nor multiple sessions * apply then just check the normal policy */ - r = bus_test_polkit(message, CAP_SYS_BOOT, a->polkit_action, NULL, UID_INVALID, &challenge, error); + r = bus_test_polkit( + message, + a->polkit_action, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + &challenge, + error); if (r < 0) return r; @@ -2636,57 +2781,49 @@ static int method_can_shutdown_or_sleep( static int method_can_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_POWEROFF), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_POWEROFF, error); } static int method_can_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_REBOOT), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_REBOOT, error); } static int method_can_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_HALT), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_HALT, error); } static int method_can_suspend(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_SUSPEND), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_SUSPEND, error); } static int method_can_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_HIBERNATE), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_HIBERNATE, error); } static int method_can_hybrid_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_HYBRID_SLEEP), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_HYBRID_SLEEP, error); } static int method_can_suspend_then_hibernate(sd_bus_message *message, void *userdata, sd_bus_error *error) { Manager *m = userdata; - return method_can_shutdown_or_sleep( - m, message, handle_action_lookup(HANDLE_SUSPEND_THEN_HIBERNATE), - error); + return method_can_shutdown_or_sleep(m, message, HANDLE_SUSPEND_THEN_HIBERNATE, error); +} + +static int method_can_sleep(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + + return method_can_shutdown_or_sleep(m, message, HANDLE_SLEEP, error); } static int property_get_reboot_parameter( @@ -2726,6 +2863,9 @@ static int method_set_reboot_parameter( if (r < 0) return r; + if (!reboot_parameter_is_valid(arg)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid reboot parameter '%s'.", arg); + r = detect_container(); if (r < 0) return r; @@ -2733,14 +2873,12 @@ static int method_set_reboot_parameter( return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot parameter not supported in containers, refusing."); - r = bus_verify_polkit_async(message, - CAP_SYS_ADMIN, - "org.freedesktop.login1.set-reboot-parameter", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.login1.set-reboot-parameter", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -2771,10 +2909,9 @@ static int method_can_reboot_parameter( return return_test_polkit( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.set-reboot-parameter", - NULL, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, error); } @@ -2852,14 +2989,12 @@ static int method_set_reboot_to_firmware_setup( /* non-EFI case: $SYSTEMD_REBOOT_TO_FIRMWARE_SETUP is set to on */ use_efi = false; - r = bus_verify_polkit_async(message, - CAP_SYS_ADMIN, - "org.freedesktop.login1.set-reboot-to-firmware-setup", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.login1.set-reboot-to-firmware-setup", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -2916,10 +3051,9 @@ static int method_can_reboot_to_firmware_setup( return return_test_polkit( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.set-reboot-to-firmware-setup", - NULL, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, error); } @@ -3016,14 +3150,12 @@ static int method_set_reboot_to_boot_loader_menu( /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_MENU is set to on */ use_efi = false; - r = bus_verify_polkit_async(message, - CAP_SYS_ADMIN, - "org.freedesktop.login1.set-reboot-to-boot-loader-menu", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.login1.set-reboot-to-boot-loader-menu", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -3091,10 +3223,9 @@ static int method_can_reboot_to_boot_loader_menu( return return_test_polkit( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.set-reboot-to-boot-loader-menu", - NULL, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, error); } @@ -3215,14 +3346,12 @@ static int method_set_reboot_to_boot_loader_entry( /* non-EFI case: $SYSTEMD_REBOOT_TO_BOOT_LOADER_ENTRY is set to on */ use_efi = false; - r = bus_verify_polkit_async(message, - CAP_SYS_ADMIN, - "org.freedesktop.login1.set-reboot-to-boot-loader-entry", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.login1.set-reboot-to-boot-loader-entry", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -3283,10 +3412,9 @@ static int method_can_reboot_to_boot_loader_entry( return return_test_polkit( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.set-reboot-to-boot-loader-entry", - NULL, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, error); } @@ -3357,14 +3485,12 @@ static int method_set_wall_message( m->enable_wall_messages == enable_wall_messages) goto done; - r = bus_verify_polkit_async(message, - CAP_SYS_ADMIN, - "org.freedesktop.login1.set-wall-message", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.login1.set-wall-message", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -3389,7 +3515,6 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error Manager *m = ASSERT_PTR(userdata); InhibitMode mm; InhibitWhat w; - pid_t pid; uid_t uid; int r; @@ -3424,7 +3549,6 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error r = bus_verify_polkit_async( message, - CAP_SYS_BOOT, w == INHIBIT_SHUTDOWN ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-shutdown" : "org.freedesktop.login1.inhibit-delay-shutdown") : w == INHIBIT_SLEEP ? (mm == INHIBIT_BLOCK ? "org.freedesktop.login1.inhibit-block-sleep" : "org.freedesktop.login1.inhibit-delay-sleep") : w == INHIBIT_IDLE ? "org.freedesktop.login1.inhibit-block-idle" : @@ -3433,9 +3557,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error w == INHIBIT_HANDLE_REBOOT_KEY ? "org.freedesktop.login1.inhibit-handle-reboot-key" : w == INHIBIT_HANDLE_HIBERNATE_KEY ? "org.freedesktop.login1.inhibit-handle-hibernate-key" : "org.freedesktop.login1.inhibit-handle-lid-switch", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -3443,7 +3565,7 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error if (r == 0) return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID, &creds); + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds); if (r < 0) return r; @@ -3451,14 +3573,10 @@ static int method_inhibit(sd_bus_message *message, void *userdata, sd_bus_error if (r < 0) return r; - r = sd_bus_creds_get_pid(creds, &pid); + r = bus_creds_get_pidref(creds, &pidref); if (r < 0) return r; - r = pidref_set_pid(&pidref, pid); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed pin source process "PID_FMT": %m", pid); - if (hashmap_size(m->inhibitors) >= m->inhibitors_max) return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Maximum number of inhibitors (%" PRIu64 ") reached, refusing further inhibitors.", @@ -3521,6 +3639,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("DelayInhibited", "s", property_get_inhibited, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("InhibitDelayMaxUSec", "t", NULL, offsetof(Manager, inhibit_delay_max), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UserStopDelayUSec", "t", NULL, offsetof(Manager, user_stop_delay), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SleepOperation", "as", property_get_sleep_operations, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HandlePowerKey", "s", property_get_handle_action, offsetof(Manager, handle_power_key), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HandlePowerKeyLongPress", "s", property_get_handle_action, offsetof(Manager, handle_power_key_long_press), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("HandleRebootKey", "s", property_get_handle_action, offsetof(Manager, handle_reboot_key), SD_BUS_VTABLE_PROPERTY_CONST), @@ -3581,6 +3700,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("a(susso)", sessions), method_list_sessions, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("ListSessionsEx", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("a(sussussbto)", sessions), + method_list_sessions_ex, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("ListUsers", SD_BUS_NO_ARGS, SD_BUS_RESULT("a(uso)", users), @@ -3651,7 +3775,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_ARGS("s", session_id), SD_BUS_NO_RESULT, method_release_session, - 0), + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("ActivateSession", SD_BUS_ARGS("s", session_id), SD_BUS_NO_RESULT, @@ -3792,6 +3916,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_NO_RESULT, method_suspend_then_hibernate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("Sleep", + SD_BUS_ARGS("t", flags), + SD_BUS_NO_RESULT, + method_sleep, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("CanPowerOff", SD_BUS_NO_ARGS, SD_BUS_RESULT("s", result), @@ -3827,6 +3956,11 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("s", result), method_can_suspend_then_hibernate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("CanSleep", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("s", result), + method_can_sleep, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("ScheduleShutdown", SD_BUS_ARGS("s", type, "t", usec), SD_BUS_NO_RESULT, @@ -3928,30 +4062,32 @@ const BusObjectImplementation manager_object = { &user_object), }; -static int session_jobs_reply(Session *s, uint32_t jid, const char *unit, const char *result) { +static void session_jobs_reply(Session *s, uint32_t jid, const char *unit, const char *result) { assert(s); assert(unit); if (!s->started) - return 0; + return; if (result && !streq(result, "done")) { _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Job %u for unit '%s' failed with '%s'", jid, unit, result); - return session_send_create_reply(s, &e); + + (void) session_send_create_reply(s, &e); + (void) session_send_upgrade_reply(s, &e); + return; } - return session_send_create_reply(s, NULL); + (void) session_send_create_reply(s, /* error= */ NULL); + (void) session_send_upgrade_reply(s, /* error= */ NULL); } int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *result, *unit; Manager *m = ASSERT_PTR(userdata); - Session *session; + const char *path, *result, *unit; uint32_t id; - User *user; int r; assert(message); @@ -3974,11 +4110,14 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err return 0; } + Session *session; + User *user; + session = hashmap_get(m->session_units, unit); if (session) { if (streq_ptr(path, session->scope_job)) { session->scope_job = mfree(session->scope_job); - (void) session_jobs_reply(session, id, unit, result); + session_jobs_reply(session, id, unit, result); session_save(session); user_save(session->user); @@ -3989,13 +4128,20 @@ int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *err user = hashmap_get(m->user_units, unit); if (user) { - if (streq_ptr(path, user->service_job)) { - user->service_job = mfree(user->service_job); - - LIST_FOREACH(sessions_by_user, s, user->sessions) - (void) session_jobs_reply(s, id, unit, NULL /* don't propagate user service failures to the client */); - - user_save(user); + /* If the user is stopping, we're tracking stop jobs here. So don't send reply. */ + if (!user->stopping) { + char **user_job; + FOREACH_ARGUMENT(user_job, &user->runtime_dir_job, &user->service_manager_job) + if (streq_ptr(path, *user_job)) { + *user_job = mfree(*user_job); + + LIST_FOREACH(sessions_by_user, s, user->sessions) + /* Don't propagate user service failures to the client */ + session_jobs_reply(s, id, unit, /* error = */ NULL); + + user_save(user); + break; + } } user_add_to_gc_queue(user); @@ -4102,49 +4248,34 @@ int manager_send_changed(Manager *manager, const char *property, ...) { l); } -static int strdup_job(sd_bus_message *reply, char **job) { - const char *j; - char *copy; - int r; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - return 1; -} - int manager_start_scope( Manager *manager, const char *scope, const PidRef *pidref, + bool allow_pidfd, const char *slice, const char *description, - char **wants, - char **after, + const char * const *requires, + const char * const *extra_after, const char *requires_mounts_for, sd_bus_message *more_properties, sd_bus_error *error, - char **job) { + char **ret_job) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; int r; assert(manager); assert(scope); assert(pidref_is_set(pidref)); - assert(job); + assert(ret_job); r = bus_message_new_method_call(manager->bus, &m, bus_systemd_mgr, "StartTransientUnit"); if (r < 0) return r; - r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); + r = sd_bus_message_append(m, "ss", scope, "fail"); if (r < 0) return r; @@ -4164,13 +4295,17 @@ int manager_start_scope( return r; } - STRV_FOREACH(i, wants) { - r = sd_bus_message_append(m, "(sv)", "Wants", "as", 1, *i); + STRV_FOREACH(i, requires) { + r = sd_bus_message_append(m, "(sv)", "Requires", "as", 1, *i); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i); if (r < 0) return r; } - STRV_FOREACH(i, after) { + STRV_FOREACH(i, extra_after) { r = sd_bus_message_append(m, "(sv)", "After", "as", 1, *i); if (r < 0) return r; @@ -4188,7 +4323,7 @@ int manager_start_scope( if (r < 0) return r; - r = bus_append_scope_pidref(m, pidref); + r = bus_append_scope_pidref(m, pidref, allow_pidfd); if (r < 0) return r; @@ -4218,20 +4353,39 @@ int manager_start_scope( if (r < 0) return r; - r = sd_bus_call(manager->bus, m, 0, error, &reply); - if (r < 0) - return r; + r = sd_bus_call(manager->bus, m, 0, &e, &reply); + if (r < 0) { + /* If this failed with a property we couldn't write, this is quite likely because the server + * doesn't support PIDFDs yet, let's try without. */ + if (allow_pidfd && + sd_bus_error_has_names(&e, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY)) + return manager_start_scope( + manager, + scope, + pidref, + /* allow_pidfd = */ false, + slice, + description, + requires, + extra_after, + requires_mounts_for, + more_properties, + error, + ret_job); + + return sd_bus_error_move(error, &e); + } - return strdup_job(reply, job); + return strdup_job(reply, ret_job); } -int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { +int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **ret_job) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; int r; assert(manager); assert(unit); - assert(job); + assert(ret_job); r = bus_call_method( manager->bus, @@ -4243,11 +4397,18 @@ int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, if (r < 0) return r; - return strdup_job(reply, job); + return strdup_job(reply, ret_job); } -int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **ret_job) { +int manager_stop_unit( + Manager *manager, + const char *unit, + const char *job_mode, + sd_bus_error *ret_error, + char **ret_job) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(manager); @@ -4258,22 +4419,24 @@ int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, manager->bus, bus_systemd_mgr, "StopUnit", - error, + &error, &reply, "ss", unit, job_mode ?: "fail"); if (r < 0) { - if (sd_bus_error_has_names(error, BUS_ERROR_NO_SUCH_UNIT, - BUS_ERROR_LOAD_FAILED)) { - + if (sd_bus_error_has_names(&error, BUS_ERROR_NO_SUCH_UNIT, BUS_ERROR_LOAD_FAILED)) { *ret_job = NULL; - sd_bus_error_free(error); return 0; } + sd_bus_error_move(ret_error, &error); return r; } - return strdup_job(reply, ret_job); + r = strdup_job(reply, ret_job); + if (r < 0) + return r; + + return 1; } int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret_error) { @@ -4313,6 +4476,7 @@ int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *ret int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error) { assert(manager); assert(unit); + assert(SIGNAL_VALID(signo)); return bus_call_method( manager->bus, @@ -4320,7 +4484,10 @@ int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo "KillUnit", error, NULL, - "ssi", unit, who == KILL_LEADER ? "main" : "all", signo); + "ssi", + unit, + who == KILL_LEADER ? "main" : "all", + signo); } int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *ret_error) { diff --git a/src/login/logind-dbus.h b/src/login/logind-dbus.h index c9d5923..8459d04 100644 --- a/src/login/logind-dbus.h +++ b/src/login/logind-dbus.h @@ -24,9 +24,21 @@ int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error int manager_send_changed(Manager *manager, const char *property, ...) _sentinel_; -int manager_start_scope(Manager *manager, const char *scope, const PidRef *pidref, const char *slice, const char *description, char **wants, char **after, const char *requires_mounts_for, sd_bus_message *more_properties, sd_bus_error *error, char **job); -int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); -int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **job); +int manager_start_scope( + Manager *manager, + const char *scope, + const PidRef *pidref, + bool allow_pidfd, + const char *slice, + const char *description, + const char * const *requires, + const char * const *extra_after, + const char *requires_mounts_for, + sd_bus_message *more_properties, + sd_bus_error *error, + char **ret_job); +int manager_start_unit(Manager *manager, const char *unit, sd_bus_error *error, char **ret_job); +int manager_stop_unit(Manager *manager, const char *unit, const char *job_mode, sd_bus_error *error, char **ret_job); int manager_abandon_scope(Manager *manager, const char *scope, sd_bus_error *error); int manager_kill_unit(Manager *manager, const char *unit, KillWho who, int signo, sd_bus_error *error); int manager_unit_is_active(Manager *manager, const char *unit, sd_bus_error *error); diff --git a/src/login/logind-device.h b/src/login/logind-device.h index 0d89613..f96c4db 100644 --- a/src/login/logind-device.h +++ b/src/login/logind-device.h @@ -16,7 +16,7 @@ struct Device { dual_timestamp timestamp; - LIST_FIELDS(struct Device, devices); + LIST_FIELDS(Device, devices); LIST_HEAD(SessionDevice, session_devices); }; diff --git a/src/login/logind-gperf.gperf b/src/login/logind-gperf.gperf index c95a3b2..7d1520b 100644 --- a/src/login/logind-gperf.gperf +++ b/src/login/logind-gperf.gperf @@ -25,6 +25,7 @@ Login.KillOnlyUsers, config_parse_strv, 0, offse Login.KillExcludeUsers, config_parse_strv, 0, offsetof(Manager, kill_exclude_users) Login.InhibitDelayMaxSec, config_parse_sec, 0, offsetof(Manager, inhibit_delay_max) Login.UserStopDelaySec, config_parse_sec, 0, offsetof(Manager, user_stop_delay) +Login.SleepOperation, config_parse_handle_action_sleep, 0, offsetof(Manager, handle_action_sleep_mask) Login.HandlePowerKey, config_parse_handle_action, 0, offsetof(Manager, handle_power_key) Login.HandlePowerKeyLongPress, config_parse_handle_action, 0, offsetof(Manager, handle_power_key_long_press) Login.HandleRebootKey, config_parse_handle_action, 0, offsetof(Manager, handle_reboot_key) diff --git a/src/login/logind-inhibit.c b/src/login/logind-inhibit.c index 1566dab..203665c 100644 --- a/src/login/logind-inhibit.c +++ b/src/login/logind-inhibit.c @@ -29,13 +29,13 @@ static void inhibitor_remove_fifo(Inhibitor *i); -int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) { +int inhibitor_new(Manager *m, const char* id, Inhibitor **ret) { _cleanup_(inhibitor_freep) Inhibitor *i = NULL; int r; - assert(ret); assert(m); assert(id); + assert(ret); i = new(Inhibitor, 1); if (!i) @@ -43,6 +43,8 @@ int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) { *i = (Inhibitor) { .manager = m, + .id = strdup(id), + .state_file = path_join("/run/systemd/inhibit/", id), .what = _INHIBIT_WHAT_INVALID, .mode = _INHIBIT_MODE_INVALID, .uid = UID_INVALID, @@ -50,12 +52,9 @@ int inhibitor_new(Inhibitor **ret, Manager *m, const char* id) { .pid = PIDREF_NULL, }; - i->state_file = path_join("/run/systemd/inhibit", id); - if (!i->state_file) + if (!i->id || !i->state_file) return -ENOMEM; - i->id = basename(i->state_file); - r = hashmap_put(m->inhibitors, i->id, i); if (r < 0) return r; @@ -79,8 +78,9 @@ Inhibitor* inhibitor_free(Inhibitor *i) { /* Note that we don't remove neither the state file nor the fifo path here, since we want both to * survive daemon restarts */ - free(i->state_file); free(i->fifo_path); + free(i->state_file); + free(i->id); pidref_done(&i->pid); @@ -270,7 +270,7 @@ int inhibitor_load(Inhibitor *i) { if (i->fifo_path) { _cleanup_close_ int fd = -EBADF; - /* Let's re-open the FIFO on both sides, and close the writing side right away */ + /* Let's reopen the FIFO on both sides, and close the writing side right away */ fd = inhibitor_create_fifo(i); if (fd < 0) return log_error_errno(fd, "Failed to reopen FIFO: %m"); diff --git a/src/login/logind-inhibit.h b/src/login/logind-inhibit.h index c34c225..8f822de 100644 --- a/src/login/logind-inhibit.h +++ b/src/login/logind-inhibit.h @@ -32,7 +32,7 @@ struct Inhibitor { sd_event_source *event_source; - const char *id; + char *id; char *state_file; bool started; @@ -51,7 +51,7 @@ struct Inhibitor { int fifo_fd; }; -int inhibitor_new(Inhibitor **ret, Manager *m, const char* id); +int inhibitor_new(Manager *m, const char* id, Inhibitor **ret); Inhibitor* inhibitor_free(Inhibitor *i); DEFINE_TRIVIAL_CLEANUP_FUNC(Inhibitor*, inhibitor_free); diff --git a/src/login/logind-polkit.c b/src/login/logind-polkit.c index e4efd64..2f1523a 100644 --- a/src/login/logind-polkit.c +++ b/src/login/logind-polkit.c @@ -9,11 +9,8 @@ int check_polkit_chvt(sd_bus_message *message, Manager *manager, sd_bus_error *e #if ENABLE_POLKIT return bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.chvt", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &manager->polkit_registry, error); #else diff --git a/src/login/logind-seat-dbus.c b/src/login/logind-seat-dbus.c index 877b9c1..0a395c6 100644 --- a/src/login/logind-seat-dbus.c +++ b/src/login/logind-seat-dbus.c @@ -134,11 +134,8 @@ int bus_seat_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er r = bus_verify_polkit_async( message, - CAP_KILL, "org.freedesktop.login1.manage", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &s->manager->polkit_registry, error); if (r < 0) diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index 8d875d2..bed1f7d 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -25,13 +25,13 @@ #include "terminal-util.h" #include "tmpfile-util.h" -int seat_new(Seat** ret, Manager *m, const char *id) { +int seat_new(Manager *m, const char *id, Seat **ret) { _cleanup_(seat_freep) Seat *s = NULL; int r; - assert(ret); assert(m); assert(id); + assert(ret); if (!seat_name_is_valid(id)) return -EINVAL; @@ -42,14 +42,12 @@ int seat_new(Seat** ret, Manager *m, const char *id) { *s = (Seat) { .manager = m, + .id = strdup(id), + .state_file = path_join("/run/systemd/seats/", id), }; - - s->state_file = path_join("/run/systemd/seats", id); - if (!s->state_file) + if (!s->id || !s->state_file) return -ENOMEM; - s->id = basename(s->state_file); - r = hashmap_put(m->seats, s->id, s); if (r < 0) return r; @@ -77,6 +75,7 @@ Seat* seat_free(Seat *s) { free(s->positions); free(s->state_file); + free(s->id); return mfree(s); } diff --git a/src/login/logind-seat.h b/src/login/logind-seat.h index 2d18e75..76a69e6 100644 --- a/src/login/logind-seat.h +++ b/src/login/logind-seat.h @@ -26,10 +26,10 @@ struct Seat { LIST_FIELDS(Seat, gc_queue); }; -int seat_new(Seat **ret, Manager *m, const char *id); +int seat_new(Manager *m, const char *id, Seat **ret); Seat* seat_free(Seat *s); -DEFINE_TRIVIAL_CLEANUP_FUNC(Seat *, seat_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Seat*, seat_free); int seat_save(Seat *s); int seat_load(Seat *s); diff --git a/src/login/logind-session-dbus.c b/src/login/logind-session-dbus.c index a136ae4..f20ef4f 100644 --- a/src/login/logind-session-dbus.c +++ b/src/login/logind-session-dbus.c @@ -158,13 +158,12 @@ int bus_session_method_terminate(sd_bus_message *message, void *userdata, sd_bus assert(message); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_KILL, "org.freedesktop.login1.manage", - NULL, - false, + /* details= */ NULL, s->user->user_record->uid, + /* flags= */ 0, &s->manager->polkit_registry, error); if (r < 0) @@ -204,13 +203,12 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro assert(message); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_SYS_ADMIN, "org.freedesktop.login1.lock-sessions", - NULL, - false, + /* details= */ NULL, s->user->user_record->uid, + /* flags= */ 0, &s->manager->polkit_registry, error); if (r < 0) @@ -218,7 +216,9 @@ int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_erro if (r == 0) return 1; /* Will call us back */ - r = session_send_lock(s, strstr(sd_bus_message_get_member(message), "Lock")); + r = session_send_lock(s, /* lock= */ strstr(sd_bus_message_get_member(message), "Lock")); + if (r == -ENOTTY) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen."); if (r < 0) return r; @@ -250,7 +250,7 @@ static int method_set_idle_hint(sd_bus_message *message, void *userdata, sd_bus_ r = session_set_idle_hint(s, b); if (r == -ENOTTY) - return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical sessions."); + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Idle hint control is not supported on non-graphical and non-user sessions."); if (r < 0) return r; @@ -280,7 +280,11 @@ static int method_set_locked_hint(sd_bus_message *message, void *userdata, sd_bu if (uid != 0 && uid != s->user->user_record->uid) return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may set locked hint"); - session_set_locked_hint(s, b); + r = session_set_locked_hint(s, b); + if (r == -ENOTTY) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session does not support lock screen."); + if (r < 0) + return r; return sd_bus_reply_method_return(message, NULL); } @@ -309,13 +313,12 @@ int bus_session_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro if (!SIGNAL_VALID(signo)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_KILL, "org.freedesktop.login1.manage", - NULL, - false, + /* details= */ NULL, s->user->user_record->uid, + /* flags= */ 0, &s->manager->polkit_registry, error); if (r < 0) @@ -390,6 +393,9 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid session type '%s'", t); + if (!SESSION_CLASS_CAN_CHANGE_TYPE(s->class)) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support changing type."); + 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 type"); @@ -398,6 +404,62 @@ static int method_set_type(sd_bus_message *message, void *userdata, sd_bus_error return sd_bus_reply_method_return(message, NULL); } +static int method_set_class(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + Session *s = ASSERT_PTR(userdata); + SessionClass class; + const char *c; + uid_t uid; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &c); + if (r < 0) + return r; + + class = session_class_from_string(c); + if (class < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid session class '%s'", c); + + /* For now, we'll allow only upgrades user-incomplete → user */ + if (class != SESSION_USER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Class may only be set to 'user'"); + + if (s->class == SESSION_USER) /* No change, shortcut */ + return sd_bus_reply_method_return(message, NULL); + if (s->class != SESSION_USER_INCOMPLETE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Only sessions with class 'user-incomplete' may change class"); + + if (s->upgrade_message) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Set session class operation already in progress"); + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &uid); + if (r < 0) + return r; + + if (uid != 0 && uid != s->user->user_record->uid) + return sd_bus_error_set(error, SD_BUS_ERROR_ACCESS_DENIED, "Only owner of session may change its class"); + + session_set_class(s, class); + + s->upgrade_message = sd_bus_message_ref(message); + + r = session_send_upgrade_reply(s, /* error= */ NULL); + if (r < 0) + return r; + + return 1; +} + static int method_set_display(sd_bus_message *message, void *userdata, sd_bus_error *error) { Session *s = ASSERT_PTR(userdata); const char *display; @@ -473,6 +535,9 @@ static int method_take_device(sd_bus_message *message, void *userdata, sd_bus_er if (!DEVICE_MAJOR_VALID(major) || !DEVICE_MINOR_VALID(minor)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Device major/minor is not valid."); + if (!SESSION_CLASS_CAN_TAKE_DEVICE(s->class)) + return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Session class doesn't support taking device control."); + if (!session_is_controller(s, sd_bus_message_get_sender(message))) return sd_bus_error_set(error, BUS_ERROR_NOT_IN_CONTROL, "You are not in control of this session"); @@ -768,6 +833,9 @@ int session_send_lock(Session *s, bool lock) { assert(s); + if (!SESSION_CLASS_CAN_LOCK(s->class)) + return -ENOTTY; + p = session_bus_path(s); if (!p) return -ENOMEM; @@ -789,6 +857,9 @@ int session_send_lock_all(Manager *m, bool lock) { HASHMAP_FOREACH(session, m->sessions) { int k; + if (!SESSION_CLASS_CAN_LOCK(session->class)) + continue; + k = session_send_lock(session, lock); if (k < 0) r = k; @@ -797,20 +868,23 @@ int session_send_lock_all(Manager *m, bool lock) { return r; } -static bool session_ready(Session *s) { +static bool session_job_pending(Session *s) { assert(s); + assert(s->user); - /* Returns true when the session is ready, i.e. all jobs we enqueued for it are done (regardless if successful or not) */ + /* Check if we have some jobs enqueued and not finished yet. Each time we get JobRemoved signal about + * relevant units, session_send_create_reply and hence us is called (see match_job_removed). + * Note that we don't care about job result here. */ - return !s->scope_job && - !s->user->service_job; + return s->scope_job || + s->user->runtime_dir_job || + (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) && s->user->service_manager_job); } int session_send_create_reply(Session *s, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; _cleanup_close_ int fifo_fd = -EBADF; _cleanup_free_ char *p = NULL; - int r; assert(s); @@ -820,7 +894,9 @@ int session_send_create_reply(Session *s, sd_bus_error *error) { if (!s->create_message) return 0; - if (!sd_bus_error_is_set(error) && !session_ready(s)) + /* If error occurred, return it immediately. Otherwise let's wait for all jobs to finish before + * continuing. */ + if (!sd_bus_error_is_set(error) && session_job_pending(s)) return 0; c = TAKE_PTR(s->create_message); @@ -831,10 +907,6 @@ int session_send_create_reply(Session *s, sd_bus_error *error) { if (fifo_fd < 0) return fifo_fd; - r = session_watch_pidfd(s); - if (r < 0) - return r; - /* Update the session state file before we notify the client about the result. */ session_save(s); @@ -865,6 +937,26 @@ int session_send_create_reply(Session *s, sd_bus_error *error) { false); } +int session_send_upgrade_reply(Session *s, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + assert(s); + + if (!s->upgrade_message) + return 0; + + /* See comments in session_send_create_reply */ + if (!sd_bus_error_is_set(error) && session_job_pending(s)) + return 0; + + c = TAKE_PTR(s->upgrade_message); + if (error) + return sd_bus_reply_method_error(c, error); + + session_save(s); + + return sd_bus_reply_method_return(c, NULL); +} + static const sd_bus_vtable session_vtable[] = { SD_BUS_VTABLE_START(0), @@ -885,7 +977,7 @@ static const sd_bus_vtable session_vtable[] = { SD_BUS_PROPERTY("Leader", "u", bus_property_get_pid, offsetof(Session, leader.pid), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Audit", "u", NULL, offsetof(Session, audit_id), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Session, type), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Session, class), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Session, class), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("Active", "b", property_get_active, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("State", "s", property_get_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), @@ -943,6 +1035,11 @@ static const sd_bus_vtable session_vtable[] = { SD_BUS_NO_RESULT, method_set_type, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("SetClass", + SD_BUS_ARGS("s", class), + SD_BUS_NO_RESULT, + method_set_class, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("SetDisplay", SD_BUS_ARGS("s", display), SD_BUS_NO_RESULT, diff --git a/src/login/logind-session-dbus.h b/src/login/logind-session-dbus.h index 751ca86..a026562 100644 --- a/src/login/logind-session-dbus.h +++ b/src/login/logind-session-dbus.h @@ -16,6 +16,7 @@ int session_send_lock(Session *s, bool lock); int session_send_lock_all(Manager *m, bool lock); int session_send_create_reply(Session *s, sd_bus_error *error); +int session_send_upgrade_reply(Session *s, sd_bus_error *error); int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_session_method_lock(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/login/logind-session-device.c b/src/login/logind-session-device.c index 44d8d52..c924f1e 100644 --- a/src/login/logind-session-device.c +++ b/src/login/logind-session-device.c @@ -11,6 +11,7 @@ #include "alloc-util.h" #include "bus-util.h" #include "daemon-util.h" +#include "device-util.h" #include "fd-util.h" #include "logind-session-dbus.h" #include "logind-session-device.h" @@ -114,7 +115,8 @@ static int sd_drmdropmaster(int fd) { } static int session_device_open(SessionDevice *sd, bool active) { - int fd, r; + _cleanup_close_ int fd = -EBADF; + int r; assert(sd); assert(sd->type != DEVICE_TYPE_UNKNOWN); @@ -132,10 +134,8 @@ static int session_device_open(SessionDevice *sd, bool active) { /* Weird legacy DRM semantics might return an error even though we're master. No way to detect * that so fail at all times and let caller retry in inactive state. */ r = sd_drmsetmaster(fd); - if (r < 0) { - (void) close_nointr(fd); + if (r < 0) return r; - } } else /* DRM-Master is granted to the first user who opens a device automatically (ughh, * racy!). Hence, we just drop DRM-Master in case we were the first. */ @@ -153,7 +153,7 @@ static int session_device_open(SessionDevice *sd, bool active) { break; } - return fd; + return TAKE_FD(fd); } static int session_device_start(SessionDevice *sd) { @@ -239,22 +239,21 @@ static void session_device_stop(SessionDevice *sd) { } static DeviceType detect_device_type(sd_device *dev) { - const char *sysname, *subsystem; - DeviceType type = DEVICE_TYPE_UNKNOWN; + const char *sysname; - if (sd_device_get_sysname(dev, &sysname) < 0 || - sd_device_get_subsystem(dev, &subsystem) < 0) - return type; + if (sd_device_get_sysname(dev, &sysname) < 0) + return DEVICE_TYPE_UNKNOWN; - if (streq(subsystem, "drm")) { + if (device_in_subsystem(dev, "drm")) { if (startswith(sysname, "card")) - type = DEVICE_TYPE_DRM; - } else if (streq(subsystem, "input")) { + return DEVICE_TYPE_DRM; + + } else if (device_in_subsystem(dev, "input")) { if (startswith(sysname, "event")) - type = DEVICE_TYPE_EVDEV; + return DEVICE_TYPE_EVDEV; } - return type; + return DEVICE_TYPE_UNKNOWN; } static int session_device_verify(SessionDevice *sd) { @@ -315,31 +314,28 @@ static int session_device_verify(SessionDevice *sd) { if (sd->device->seat != sd->session->seat) return -EPERM; - sd->node = strdup(node); - if (!sd->node) - return -ENOMEM; - - return 0; + return strdup_to(&sd->node, node); } -int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out) { +int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret) { SessionDevice *sd; int r; assert(s); - assert(out); if (!s->seat) return -EPERM; - sd = new0(SessionDevice, 1); + sd = new(SessionDevice, 1); if (!sd) return -ENOMEM; - sd->session = s; - sd->dev = dev; - sd->fd = -EBADF; - sd->type = DEVICE_TYPE_UNKNOWN; + *sd = (SessionDevice) { + .session = s, + .dev = dev, + .fd = -EBADF, + .type = DEVICE_TYPE_UNKNOWN, + }; r = session_device_verify(sd); if (r < 0) @@ -370,7 +366,9 @@ int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice ** LIST_PREPEND(sd_by_device, sd->device->session_devices, sd); - *out = sd; + if (ret) + *ret = sd; + return 0; error: diff --git a/src/login/logind-session-device.h b/src/login/logind-session-device.h index 04654d1..06a8f80 100644 --- a/src/login/logind-session-device.h +++ b/src/login/logind-session-device.h @@ -27,7 +27,7 @@ struct SessionDevice { LIST_FIELDS(struct SessionDevice, sd_by_device); }; -int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **out); +int session_device_new(Session *s, dev_t dev, bool open_device, SessionDevice **ret); SessionDevice *session_device_free(SessionDevice *sd); DEFINE_TRIVIAL_CLEANUP_FUNC(SessionDevice*, session_device_free); diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 3988e55..4713aa0 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -15,6 +15,7 @@ #include "audit-util.h" #include "bus-error.h" #include "bus-util.h" +#include "daemon-util.h" #include "devnum-util.h" #include "env-file.h" #include "escape.h" @@ -37,7 +38,7 @@ #include "strv.h" #include "terminal-util.h" #include "tmpfile-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" #define RELEASE_USEC (20*USEC_PER_SEC) @@ -45,13 +46,13 @@ static void session_remove_fifo(Session *s); static void session_restore_vt(Session *s); -int session_new(Session **ret, Manager *m, const char *id) { +int session_new(Manager *m, const char *id, Session **ret) { _cleanup_(session_freep) Session *s = NULL; int r; - assert(ret); assert(m); assert(id); + assert(ret); if (!session_id_valid(id)) return -EINVAL; @@ -62,19 +63,17 @@ int session_new(Session **ret, Manager *m, const char *id) { *s = (Session) { .manager = m, + .id = strdup(id), + .state_file = path_join("/run/systemd/sessions/", id), .fifo_fd = -EBADF, .vtfd = -EBADF, .audit_id = AUDIT_SESSION_INVALID, .tty_validity = _TTY_VALIDITY_INVALID, .leader = PIDREF_NULL, }; - - s->state_file = path_join("/run/systemd/sessions", id); - if (!s->state_file) + if (!s->id || !s->state_file) return -ENOMEM; - s->id = basename(s->state_file); - s->devices = hashmap_new(&devt_hash_ops); if (!s->devices) return -ENOMEM; @@ -87,12 +86,53 @@ int session_new(Session **ret, Manager *m, const char *id) { return 0; } -static void session_reset_leader(Session *s) { +static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + Session *s = ASSERT_PTR(userdata); + + assert(s->leader.fd == fd); + session_stop(s, /* force= */ false); + + return 1; +} + +static int session_watch_pidfd(Session *s) { + int r; + + assert(s); + assert(s->manager); + assert(pidref_is_set(&s->leader)); + + if (s->leader.fd < 0) + return 0; + + r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s); + if (r < 0) + return r; + + r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd"); + + return 0; +} + +static void session_reset_leader(Session *s, bool keep_fdstore) { assert(s); + if (!keep_fdstore) { + /* Clear fdstore if we're asked to, no matter if s->leader is set or not, so that when + * initially deserializing leader fd we clear the old fd too. */ + (void) notify_remove_fd_warnf("session-%s-leader-fd", s->id); + s->leader_fd_saved = false; + } + if (!pidref_is_set(&s->leader)) return; + s->leader_pidfd_event_source = sd_event_source_disable_unref(s->leader_pidfd_event_source); + (void) hashmap_remove_value(s->manager->sessions_by_leader, &s->leader, s); return pidref_done(&s->leader); @@ -104,10 +144,12 @@ Session* session_free(Session *s) { if (!s) return NULL; + sd_event_source_unref(s->stop_on_idle_event_source); + if (s->in_gc_queue) LIST_REMOVE(gc_queue, s->manager->session_gc_queue, s); - s->timer_event_source = sd_event_source_unref(s->timer_event_source); + sd_event_source_unref(s->timer_event_source); session_drop_controller(s); @@ -142,9 +184,10 @@ Session* session_free(Session *s) { free(s->scope_job); - session_reset_leader(s); + session_reset_leader(s, /* keep_fdstore = */ true); sd_bus_message_unref(s->create_message); + sd_bus_message_unref(s->upgrade_message); free(s->tty); free(s->display); @@ -160,10 +203,9 @@ Session* session_free(Session *s) { /* Note that we remove neither the state file nor the fifo path here, since we want both to survive * daemon restarts */ - free(s->state_file); free(s->fifo_path); - - sd_event_source_unref(s->stop_on_idle_event_source); + free(s->state_file); + free(s->id); return mfree(s); } @@ -188,15 +230,27 @@ int session_set_leader_consume(Session *s, PidRef _leader) { if (pidref_equal(&s->leader, &pidref)) return 0; - session_reset_leader(s); + session_reset_leader(s, /* keep_fdstore = */ false); s->leader = TAKE_PIDREF(pidref); + r = session_watch_pidfd(s); + if (r < 0) + return log_error_errno(r, "Failed to watch leader pidfd for session '%s': %m", s->id); + r = hashmap_ensure_put(&s->manager->sessions_by_leader, &pidref_hash_ops, &s->leader, s); if (r < 0) return r; assert(r > 0); + if (s->leader.fd >= 0) { + r = notify_push_fdf(s->leader.fd, "session-%s-leader-fd", s->id); + if (r < 0) + log_warning_errno(r, "Failed to push leader pidfd for session '%s', ignoring: %m", s->id); + else + s->leader_fd_saved = true; + } + (void) audit_session_from_pid(s->leader.pid, &s->audit_id); return 1; @@ -240,16 +294,18 @@ int session_save(Session *s) { "# This is private data. Do not parse.\n" "UID="UID_FMT"\n" "USER=%s\n" - "ACTIVE=%i\n" - "IS_DISPLAY=%i\n" + "ACTIVE=%s\n" + "IS_DISPLAY=%s\n" "STATE=%s\n" - "REMOTE=%i\n", + "REMOTE=%s\n" + "LEADER_FD_SAVED=%s\n", s->user->user_record->uid, s->user->user_record->user_name, - session_is_active(s), - s->user->display == s, + one_zero(session_is_active(s)), + one_zero(s->user->display == s), session_state_to_string(session_get_state(s)), - s->remote); + one_zero(s->remote), + one_zero(s->leader_fd_saved)); if (s->type >= 0) fprintf(f, "TYPE=%s\n", session_type_to_string(s->type)); @@ -377,33 +433,27 @@ static int session_load_devices(Session *s, const char *devices) { for (const char *p = devices;;) { _cleanup_free_ char *word = NULL; - SessionDevice *sd; dev_t dev; int k; k = extract_first_word(&p, &word, NULL, 0); - if (k == 0) - break; - if (k < 0) { - r = k; + if (k <= 0) { + RET_GATHER(r, k); break; } k = parse_devnum(word, &dev); if (k < 0) { - r = k; + RET_GATHER(r, k); continue; } /* The file descriptors for loaded devices will be reattached later. */ - k = session_device_new(s, dev, false, &sd); - if (k < 0) - r = k; + RET_GATHER(r, session_device_new(s, dev, /* open_device = */ false, /* ret = */ NULL)); } if (r < 0) - log_error_errno(r, "Loading session devices for session %s failed: %m", s->id); - + log_error_errno(r, "Failed to load some session devices for session '%s': %m", s->id); return r; } @@ -414,7 +464,8 @@ int session_load(Session *s) { *vtnr = NULL, *state = NULL, *position = NULL, - *leader = NULL, + *leader_pid = NULL, + *leader_fd_saved = NULL, *type = NULL, *original_type = NULL, *class = NULL, @@ -431,32 +482,33 @@ int session_load(Session *s) { assert(s); r = parse_env_file(NULL, s->state_file, - "REMOTE", &remote, - "SCOPE", &s->scope, - "SCOPE_JOB", &s->scope_job, - "FIFO", &s->fifo_path, - "SEAT", &seat, - "TTY", &s->tty, - "TTY_VALIDITY", &tty_validity, - "DISPLAY", &s->display, - "REMOTE_HOST", &s->remote_host, - "REMOTE_USER", &s->remote_user, - "SERVICE", &s->service, - "DESKTOP", &s->desktop, - "VTNR", &vtnr, - "STATE", &state, - "POSITION", &position, - "LEADER", &leader, - "TYPE", &type, - "ORIGINAL_TYPE", &original_type, - "CLASS", &class, - "UID", &uid, - "REALTIME", &realtime, - "MONOTONIC", &monotonic, - "CONTROLLER", &controller, - "ACTIVE", &active, - "DEVICES", &devices, - "IS_DISPLAY", &is_display); + "REMOTE", &remote, + "SCOPE", &s->scope, + "SCOPE_JOB", &s->scope_job, + "FIFO", &s->fifo_path, + "SEAT", &seat, + "TTY", &s->tty, + "TTY_VALIDITY", &tty_validity, + "DISPLAY", &s->display, + "REMOTE_HOST", &s->remote_host, + "REMOTE_USER", &s->remote_user, + "SERVICE", &s->service, + "DESKTOP", &s->desktop, + "VTNR", &vtnr, + "STATE", &state, + "POSITION", &position, + "LEADER", &leader_pid, + "LEADER_FD_SAVED", &leader_fd_saved, + "TYPE", &type, + "ORIGINAL_TYPE", &original_type, + "CLASS", &class, + "UID", &uid, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, + "CONTROLLER", &controller, + "ACTIVE", &active, + "DEVICES", &devices, + "IS_DISPLAY", &is_display); if (r < 0) return log_error_errno(r, "Failed to read %s: %m", s->state_file); @@ -523,19 +575,6 @@ int session_load(Session *s) { s->tty_validity = v; } - if (leader) { - _cleanup_(pidref_done) PidRef p = PIDREF_NULL; - - r = pidref_set_pidstr(&p, leader); - if (r < 0) - log_debug_errno(r, "Failed to parse leader PID of session: %s", leader); - else { - r = session_set_leader_consume(s, TAKE_PIDREF(p)); - if (r < 0) - log_warning_errno(r, "Failed to set session leader PID, ignoring: %m"); - } - } - if (type) { SessionType t; @@ -610,6 +649,33 @@ int session_load(Session *s) { session_restore_vt(s); } + if (leader_pid) { + assert(!pidref_is_set(&s->leader)); + + r = parse_pid(leader_pid, &s->deserialized_pid); + if (r < 0) + return log_error_errno(r, "Failed to parse LEADER=%s: %m", leader_pid); + + if (leader_fd_saved) { + r = parse_boolean(leader_fd_saved); + if (r < 0) + return log_error_errno(r, "Failed to parse LEADER_FD_SAVED=%s: %m", leader_fd_saved); + s->leader_fd_saved = r > 0; + + if (s->leader_fd_saved) + /* The leader fd will be acquired from fdstore later */ + return 0; + } + + _cleanup_(pidref_done) PidRef p = PIDREF_NULL; + + r = pidref_set_pid(&p, s->deserialized_pid); + if (r >= 0) + r = session_set_leader_consume(s, TAKE_PIDREF(p)); + if (r < 0) + log_warning_errno(r, "Failed to set leader PID for session '%s': %m", s->id); + } + return r; } @@ -651,60 +717,56 @@ int session_activate(Session *s) { } static int session_start_scope(Session *s, sd_bus_message *properties, sd_bus_error *error) { + _cleanup_free_ char *scope = NULL; + const char *description; int r; assert(s); assert(s->user); - if (!s->scope) { - _cleanup_strv_free_ char **after = NULL; - _cleanup_free_ char *scope = NULL; - const char *description; - - s->scope_job = mfree(s->scope_job); - - scope = strjoin("session-", s->id, ".scope"); - if (!scope) - return log_oom(); - - description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name); - - /* We usually want to order session scopes after systemd-user-sessions.service since the - * latter unit is used as login session barrier for unprivileged users. However the barrier - * doesn't apply for root as sysadmin should always be able to log in (and without waiting - * for any timeout to expire) in case something goes wrong during the boot process. Since - * ordering after systemd-user-sessions.service and the user instance is optional we make use - * of STRV_IGNORE with strv_new() to skip these order constraints when needed. */ - after = strv_new("systemd-logind.service", - s->user->runtime_dir_service, - !uid_is_system(s->user->user_record->uid) ? "systemd-user-sessions.service" : STRV_IGNORE, - s->user->service); - if (!after) - return log_oom(); - - r = manager_start_scope( - s->manager, - scope, - &s->leader, - s->user->slice, - description, - /* These two have StopWhenUnneeded= set, hence add a dep towards them */ - STRV_MAKE(s->user->runtime_dir_service, - s->user->service), - after, - user_record_home_directory(s->user->user_record), - properties, - error, - &s->scope_job); - if (r < 0) - return log_error_errno(r, "Failed to start session scope %s: %s", - scope, bus_error_message(error, r)); + if (!SESSION_CLASS_WANTS_SCOPE(s->class)) + return 0; - s->scope = TAKE_PTR(scope); - } + if (s->scope) + goto finish; - (void) hashmap_put(s->manager->session_units, s->scope, s); + s->scope_job = mfree(s->scope_job); + scope = strjoin("session-", s->id, ".scope"); + if (!scope) + return log_oom(); + + description = strjoina("Session ", s->id, " of User ", s->user->user_record->user_name); + + r = manager_start_scope( + s->manager, + scope, + &s->leader, + /* allow_pidfd = */ true, + s->user->slice, + description, + /* These should have been pulled in explicitly in user_start(). Just to be sure. */ + STRV_MAKE_CONST(s->user->runtime_dir_unit, + SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class) ? s->user->service_manager_unit : NULL), + /* We usually want to order session scopes after systemd-user-sessions.service + * since the unit is used as login session barrier for unprivileged users. However + * the barrier doesn't apply for root as sysadmin should always be able to log in + * (and without waiting for any timeout to expire) in case something goes wrong + * during the boot process. */ + STRV_MAKE_CONST("systemd-logind.service", + SESSION_CLASS_IS_EARLY(s->class) ? NULL : "systemd-user-sessions.service"), + user_record_home_directory(s->user->user_record), + properties, + error, + &s->scope_job); + if (r < 0) + return log_error_errno(r, "Failed to start session scope %s: %s", + scope, bus_error_message(error, r)); + + s->scope = TAKE_PTR(scope); + +finish: + (void) hashmap_put(s->manager->session_units, s->scope, s); return 0; } @@ -744,7 +806,7 @@ static int session_setup_stop_on_idle_timer(Session *s) { assert(s); - if (s->manager->stop_idle_session_usec == USEC_INFINITY) + if (s->manager->stop_idle_session_usec == USEC_INFINITY || !SESSION_CLASS_CAN_STOP_ON_IDLE(s->class)) return 0; r = sd_event_add_time_relative( @@ -804,17 +866,17 @@ int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error) { user_elect_display(s->user); /* Save data */ - session_save(s); - user_save(s->user); + (void) session_save(s); + (void) user_save(s->user); if (s->seat) - seat_save(s->seat); + (void) seat_save(s->seat); /* Send signals */ session_send_signal(s, true); user_send_changed(s->user, "Display", NULL); if (s->seat && s->seat->active == s) - seat_send_changed(s->seat, "ActiveSession", NULL); + (void) seat_send_changed(s->seat, "ActiveSession", NULL); return 0; } @@ -900,8 +962,8 @@ int session_stop(Session *s, bool force) { user_elect_display(s->user); - session_save(s); - user_save(s->user); + (void) session_save(s); + (void) user_save(s->user); return r; } @@ -923,7 +985,6 @@ int session_finalize(Session *s) { LOG_MESSAGE("Removed session %s.", s->id)); s->timer_event_source = sd_event_source_unref(s->timer_event_source); - s->leader_pidfd_event_source = sd_event_source_unref(s->leader_pidfd_event_source); if (s->seat) seat_evict_position(s->seat, s); @@ -948,10 +1009,10 @@ int session_finalize(Session *s) { seat_save(s->seat); } - session_reset_leader(s); + session_reset_leader(s, /* keep_fdstore = */ false); - user_save(s->user); - user_send_changed(s->user, "Display", NULL); + (void) user_save(s->user); + (void) user_send_changed(s->user, "Display", NULL); return 0; } @@ -1042,19 +1103,21 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) { return s->idle_hint; } - /* For sessions with an explicitly configured tty, let's check its atime */ - if (s->tty) { - r = get_tty_atime(s->tty, &atime); - if (r >= 0) - goto found_atime; - } + if (s->type == SESSION_TTY) { + /* For sessions with an explicitly configured tty, let's check its atime */ + if (s->tty) { + r = get_tty_atime(s->tty, &atime); + if (r >= 0) + goto found_atime; + } - /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of - * the leader */ - if (pidref_is_set(&s->leader)) { - r = get_process_ctty_atime(s->leader.pid, &atime); - if (r >= 0) - goto found_atime; + /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of + * the leader */ + if (pidref_is_set(&s->leader)) { + r = get_process_ctty_atime(s->leader.pid, &atime); + if (r >= 0) + goto found_atime; + } } if (t) @@ -1081,7 +1144,9 @@ found_atime: int session_set_idle_hint(Session *s, bool b) { assert(s); - if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) + if (!SESSION_CLASS_CAN_IDLE(s->class)) /* Only some session classes know the idle concept at all */ + return -ENOTTY; + if (!SESSION_TYPE_IS_GRAPHICAL(s->type)) /* And only graphical session types can set the field explicitly */ return -ENOTTY; if (s->idle_hint == b) @@ -1107,15 +1172,20 @@ int session_get_locked_hint(Session *s) { return s->locked_hint; } -void session_set_locked_hint(Session *s, bool b) { +int session_set_locked_hint(Session *s, bool b) { assert(s); + if (!SESSION_CLASS_CAN_LOCK(s->class)) + return -ENOTTY; + if (s->locked_hint == b) - return; + return 0; s->locked_hint = b; + (void) session_save(s); + (void) session_send_changed(s, "LockedHint", NULL); - session_send_changed(s, "LockedHint", NULL); + return 1; } void session_set_type(Session *s, SessionType t) { @@ -1125,9 +1195,22 @@ void session_set_type(Session *s, SessionType t) { return; s->type = t; - session_save(s); + (void) session_save(s); + (void) session_send_changed(s, "Type", NULL); +} + +void session_set_class(Session *s, SessionClass c) { + assert(s); + + if (s->class == c) + return; - session_send_changed(s, "Type", NULL); + s->class = c; + (void) session_save(s); + (void) session_send_changed(s, "Class", NULL); + + /* This class change might mean we need the per-user session manager now. Try to start it. */ + (void) user_start_service_manager(s->user); } int session_set_display(Session *s, const char *display) { @@ -1140,9 +1223,8 @@ int session_set_display(Session *s, const char *display) { if (r <= 0) /* 0 means the strings were equal */ return r; - session_save(s); - - session_send_changed(s, "Display", NULL); + (void) session_save(s); + (void) session_send_changed(s, "Display", NULL); return 1; } @@ -1157,9 +1239,8 @@ int session_set_tty(Session *s, const char *tty) { if (r <= 0) /* 0 means the strings were equal */ return r; - session_save(s); - - session_send_changed(s, "TTY", NULL); + (void) session_save(s); + (void) session_send_changed(s, "TTY", NULL); return 1; } @@ -1224,41 +1305,7 @@ static void session_remove_fifo(Session *s) { s->fifo_event_source = sd_event_source_unref(s->fifo_event_source); s->fifo_fd = safe_close(s->fifo_fd); - - if (s->fifo_path) { - (void) unlink(s->fifo_path); - s->fifo_path = mfree(s->fifo_path); - } -} - -static int session_dispatch_leader_pidfd(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - Session *s = ASSERT_PTR(userdata); - - assert(s->leader.fd == fd); - session_stop(s, /* force= */ false); - - return 1; -} - -int session_watch_pidfd(Session *s) { - int r; - - assert(s); - - if (s->leader.fd < 0) - return 0; - - r = sd_event_add_io(s->manager->event, &s->leader_pidfd_event_source, s->leader.fd, EPOLLIN, session_dispatch_leader_pidfd, s); - if (r < 0) - return r; - - r = sd_event_source_set_priority(s->leader_pidfd_event_source, SD_EVENT_PRIORITY_IMPORTANT); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->leader_pidfd_event_source, "session-pidfd"); - - return 0; + s->fifo_path = unlink_and_free(s->fifo_path); } bool session_may_gc(Session *s, bool drop_not_started) { @@ -1273,15 +1320,17 @@ bool session_may_gc(Session *s, bool drop_not_started) { return true; r = pidref_is_alive(&s->leader); + if (r == -ESRCH) + /* Session has no leader. This is probably because the leader vanished before deserializing + * pidfd from FD store. */ + return true; if (r < 0) - log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not.", s->leader.pid); + log_debug_errno(r, "Unable to determine if leader PID " PID_FMT " is still alive, assuming not: %m", s->leader.pid); if (r > 0) return false; - if (s->fifo_fd >= 0) { - if (pipe_eof(s->fifo_fd) <= 0) - return false; - } + if (s->fifo_fd >= 0 && pipe_eof(s->fifo_fd) <= 0) + return false; if (s->scope_job) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -1561,7 +1610,7 @@ int session_set_controller(Session *s, const char *sender, bool force, bool prep session_release_controller(s, true); s->controller = TAKE_PTR(name); - session_save(s); + (void) session_save(s); return 0; } @@ -1575,7 +1624,7 @@ void session_drop_controller(Session *s) { s->track = sd_bus_track_unref(s->track); session_set_type(s, s->original_type); session_release_controller(s, false); - session_save(s); + (void) session_save(s); session_restore_vt(s); } @@ -1600,10 +1649,15 @@ static const char* const session_type_table[_SESSION_TYPE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(session_type, SessionType); static const char* const session_class_table[_SESSION_CLASS_MAX] = { - [SESSION_USER] = "user", - [SESSION_GREETER] = "greeter", - [SESSION_LOCK_SCREEN] = "lock-screen", - [SESSION_BACKGROUND] = "background", + [SESSION_USER] = "user", + [SESSION_USER_EARLY] = "user-early", + [SESSION_USER_INCOMPLETE] = "user-incomplete", + [SESSION_GREETER] = "greeter", + [SESSION_LOCK_SCREEN] = "lock-screen", + [SESSION_BACKGROUND] = "background", + [SESSION_BACKGROUND_LIGHT] = "background-light", + [SESSION_MANAGER] = "manager", + [SESSION_MANAGER_EARLY] = "manager-early", }; DEFINE_STRING_TABLE_LOOKUP(session_class, SessionClass); diff --git a/src/login/logind-session.h b/src/login/logind-session.h index 8b63843..c187bcd 100644 --- a/src/login/logind-session.h +++ b/src/login/logind-session.h @@ -20,14 +20,50 @@ typedef enum SessionState { } SessionState; typedef enum SessionClass { - SESSION_USER, - SESSION_GREETER, - SESSION_LOCK_SCREEN, - SESSION_BACKGROUND, + SESSION_USER, /* A regular user session */ + SESSION_USER_EARLY, /* A user session, that is not ordered after systemd-user-sessions.service (i.e. for root) */ + SESSION_USER_INCOMPLETE, /* A user session that is only half-way set up and doesn't pull in the service manager, and can be upgraded to a full user session later */ + SESSION_GREETER, /* A login greeter pseudo-session */ + SESSION_LOCK_SCREEN, /* A lock screen */ + SESSION_BACKGROUND, /* Things like cron jobs, which are non-interactive */ + SESSION_BACKGROUND_LIGHT, /* Like SESSION_BACKGROUND, but without the service manager */ + SESSION_MANAGER, /* The service manager */ + SESSION_MANAGER_EARLY, /* The service manager for root (which is allowed to run before systemd-user-sessions.service) */ _SESSION_CLASS_MAX, _SESSION_CLASS_INVALID = -EINVAL, } SessionClass; +/* Whether we shall allow sessions of this class to run before 'systemd-user-sessions.service'. It's + * generally set for root sessions, but no one else. */ +#define SESSION_CLASS_IS_EARLY(class) IN_SET((class), SESSION_USER_EARLY, SESSION_MANAGER_EARLY) + +/* Which session classes want their own scope units? (all of them, except the manager, which comes in its own service unit already */ +#define SESSION_CLASS_WANTS_SCOPE(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_USER_INCOMPLETE, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND, SESSION_BACKGROUND_LIGHT) + +/* Which session classes want their own per-user service manager? */ +#define SESSION_CLASS_WANTS_SERVICE_MANAGER(class) IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN, SESSION_BACKGROUND) + +/* Which session classes can pin our user tracking? */ +#define SESSION_CLASS_PIN_USER(class) (!IN_SET((class), SESSION_MANAGER, SESSION_MANAGER_EARLY)) + +/* Which session classes decide whether system is idle? (should only cover sessions that have input, and are not idle screens themselves)*/ +#define SESSION_CLASS_CAN_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) + +/* Which session classes have a lock screen concept? */ +#define SESSION_CLASS_CAN_LOCK(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) + +/* Which sessions are candidates to become "display" sessions */ +#define SESSION_CLASS_CAN_DISPLAY(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER)) + +/* Which sessions classes should be subject to stop-in-idle */ +#define SESSION_CLASS_CAN_STOP_ON_IDLE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY)) + +/* Which session classes can take control of devices */ +#define SESSION_CLASS_CAN_TAKE_DEVICE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) + +/* Which session classes allow changing session types */ +#define SESSION_CLASS_CAN_CHANGE_TYPE(class) (IN_SET((class), SESSION_USER, SESSION_USER_EARLY, SESSION_GREETER, SESSION_LOCK_SCREEN)) + typedef enum SessionType { SESSION_UNSPECIFIED, SESSION_TTY, @@ -59,7 +95,8 @@ typedef enum TTYValidity { struct Session { Manager *manager; - const char *id; + char *id; + unsigned position; SessionType type; SessionType original_type; @@ -89,6 +126,8 @@ struct Session { int vtfd; PidRef leader; + bool leader_fd_saved; /* pidfd of leader uploaded to fdstore */ + pid_t deserialized_pid; /* PID deserialized from state file (for verification when pidfd is used) */ uint32_t audit_id; int fifo_fd; @@ -97,18 +136,19 @@ struct Session { sd_event_source *fifo_event_source; sd_event_source *leader_pidfd_event_source; - bool idle_hint; - dual_timestamp idle_hint_timestamp; + bool in_gc_queue; + bool started; + bool stopping; - bool locked_hint; + bool was_active; - bool in_gc_queue:1; - bool started:1; - bool stopping:1; + bool locked_hint; - bool was_active:1; + bool idle_hint; + dual_timestamp idle_hint_timestamp; - sd_bus_message *create_message; + sd_bus_message *create_message; /* The D-Bus message used to create the session, which we haven't responded to yet */ + sd_bus_message *upgrade_message; /* The D-Bus message used to upgrade the session class user-incomplete → user, which we haven't responded to yet */ /* Set up when a client requested to release the session via the bus */ sd_event_source *timer_event_source; @@ -125,10 +165,10 @@ struct Session { LIST_FIELDS(Session, gc_queue); }; -int session_new(Session **ret, Manager *m, const char *id); +int session_new(Manager *m, const char *id, Session **ret); Session* session_free(Session *s); -DEFINE_TRIVIAL_CLEANUP_FUNC(Session *, session_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Session*, session_free); void session_set_user(Session *s, User *u); int session_set_leader_consume(Session *s, PidRef _leader); @@ -139,12 +179,12 @@ bool session_is_active(Session *s); int session_get_idle_hint(Session *s, dual_timestamp *t); int session_set_idle_hint(Session *s, bool b); int session_get_locked_hint(Session *s); -void session_set_locked_hint(Session *s, bool b); +int session_set_locked_hint(Session *s, bool b); void session_set_type(Session *s, SessionType t); +void session_set_class(Session *s, SessionClass c); 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_watch_pidfd(Session *s); int session_start(Session *s, sd_bus_message *properties, sd_bus_error *error); int session_stop(Session *s, bool force); int session_finalize(Session *s); diff --git a/src/login/logind-user-dbus.c b/src/login/logind-user-dbus.c index 88649b2..ba83dc5 100644 --- a/src/login/logind-user-dbus.c +++ b/src/login/logind-user-dbus.c @@ -192,13 +192,12 @@ int bus_user_method_terminate(sd_bus_message *message, void *userdata, sd_bus_er assert(message); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_KILL, "org.freedesktop.login1.manage", - NULL, - false, + /* details= */ NULL, u->user_record->uid, + /* flags= */ 0, &u->manager->polkit_registry, error); if (r < 0) @@ -220,13 +219,12 @@ int bus_user_method_kill(sd_bus_message *message, void *userdata, sd_bus_error * assert(message); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( message, - CAP_KILL, "org.freedesktop.login1.manage", - NULL, - false, + /* details= */ NULL, u->user_record->uid, + /* flags= */ 0, &u->manager->polkit_registry, error); if (r < 0) @@ -358,7 +356,7 @@ static const sd_bus_vtable user_vtable[] = { SD_BUS_PROPERTY("Name", "s", property_get_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(User, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RuntimePath", "s", NULL, offsetof(User, runtime_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Service", "s", NULL, offsetof(User, service), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Service", "s", NULL, offsetof(User, service_manager_unit), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Slice", "s", NULL, offsetof(User, slice), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Display", "(so)", property_get_display, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), diff --git a/src/login/logind-user.c b/src/login/logind-user.c index c613307..8066b3e 100644 --- a/src/login/logind-user.c +++ b/src/login/logind-user.c @@ -33,21 +33,18 @@ #include "string-table.h" #include "strv.h" #include "tmpfile-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "unit-name.h" #include "user-util.h" -int user_new(User **ret, - Manager *m, - UserRecord *ur) { - +int user_new(Manager *m, UserRecord *ur, User **ret) { _cleanup_(user_freep) User *u = NULL; char lu[DECIMAL_STR_MAX(uid_t) + 1]; int r; - assert(ret); assert(m); assert(ur); + assert(ret); if (!ur->user_name) return -EINVAL; @@ -63,6 +60,7 @@ int user_new(User **ret, .manager = m, .user_record = user_record_ref(ur), .last_session_timestamp = USEC_INFINITY, + .gc_mode = USER_GC_BY_ANY, }; if (asprintf(&u->state_file, "/run/systemd/users/" UID_FMT, ur->uid) < 0) @@ -76,11 +74,11 @@ int user_new(User **ret, if (r < 0) return r; - r = unit_name_build("user", lu, ".service", &u->service); + r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_unit); if (r < 0) return r; - r = unit_name_build("user-runtime-dir", lu, ".service", &u->runtime_dir_service); + r = unit_name_build("user", lu, ".service", &u->service_manager_unit); if (r < 0) return r; @@ -92,11 +90,11 @@ int user_new(User **ret, if (r < 0) return r; - r = hashmap_put(m->user_units, u->service, u); + r = hashmap_put(m->user_units, u->runtime_dir_unit, u); if (r < 0) return r; - r = hashmap_put(m->user_units, u->runtime_dir_service, u); + r = hashmap_put(m->user_units, u->service_manager_unit, u); if (r < 0) return r; @@ -114,26 +112,29 @@ User *user_free(User *u) { while (u->sessions) session_free(u->sessions); - if (u->service) - hashmap_remove_value(u->manager->user_units, u->service, u); - - if (u->runtime_dir_service) - hashmap_remove_value(u->manager->user_units, u->runtime_dir_service, u); + sd_event_source_unref(u->timer_event_source); - if (u->slice) - hashmap_remove_value(u->manager->user_units, u->slice, u); + if (u->service_manager_unit) { + (void) hashmap_remove_value(u->manager->user_units, u->service_manager_unit, u); + free(u->service_manager_job); + free(u->service_manager_unit); + } - hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); + if (u->runtime_dir_unit) { + (void) hashmap_remove_value(u->manager->user_units, u->runtime_dir_unit, u); + free(u->runtime_dir_job); + free(u->runtime_dir_unit); + } - sd_event_source_unref(u->timer_event_source); + if (u->slice) { + (void) hashmap_remove_value(u->manager->user_units, u->slice, u); + free(u->slice); + } - u->service_job = mfree(u->service_job); + (void) hashmap_remove_value(u->manager->users, UID_TO_PTR(u->user_record->uid), u); - u->service = mfree(u->service); - u->runtime_dir_service = mfree(u->runtime_dir_service); - u->slice = mfree(u->slice); - u->runtime_path = mfree(u->runtime_path); - u->state_file = mfree(u->state_file); + free(u->runtime_path); + free(u->state_file); user_record_unref(u->user_record); @@ -162,17 +163,22 @@ static int user_save_internal(User *u) { "# This is private data. Do not parse.\n" "NAME=%s\n" "STATE=%s\n" /* friendly user-facing state */ - "STOPPING=%s\n", /* low-level state */ + "STOPPING=%s\n" /* low-level state */ + "GC_MODE=%s\n", u->user_record->user_name, user_state_to_string(user_get_state(u)), - yes_no(u->stopping)); + yes_no(u->stopping), + user_gc_mode_to_string(u->gc_mode)); /* LEGACY: no-one reads RUNTIME= anymore, drop it at some point */ if (u->runtime_path) fprintf(f, "RUNTIME=%s\n", u->runtime_path); - if (u->service_job) - fprintf(f, "SERVICE_JOB=%s\n", u->service_job); + if (u->runtime_dir_job) + fprintf(f, "RUNTIME_DIR_JOB=%s\n", u->runtime_dir_job); + + if (u->service_manager_job) + fprintf(f, "SERVICE_JOB=%s\n", u->service_manager_job); if (u->display) fprintf(f, "DISPLAY=%s\n", u->display->id); @@ -302,17 +308,19 @@ int user_save(User *u) { } int user_load(User *u) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *stopping = NULL, *last_session_timestamp = NULL, *gc_mode = NULL; int r; assert(u); r = parse_env_file(NULL, u->state_file, - "SERVICE_JOB", &u->service_job, + "RUNTIME_DIR_JOB", &u->runtime_dir_job, + "SERVICE_JOB", &u->service_manager_job, "STOPPING", &stopping, "REALTIME", &realtime, "MONOTONIC", &monotonic, - "LAST_SESSION_TIMESTAMP", &last_session_timestamp); + "LAST_SESSION_TIMESTAMP", &last_session_timestamp, + "GC_MODE", &gc_mode); if (r == -ENOENT) return 0; if (r < 0) @@ -322,8 +330,11 @@ int user_load(User *u) { r = parse_boolean(stopping); if (r < 0) log_debug_errno(r, "Failed to parse 'STOPPING' boolean: %s", stopping); - else + else { u->stopping = r; + if (u->stopping && !u->runtime_dir_job) + log_debug("User '%s' is stopping, but no job is being tracked.", u->user_record->user_name); + } } if (realtime) @@ -333,25 +344,74 @@ int user_load(User *u) { if (last_session_timestamp) (void) deserialize_usec(last_session_timestamp, &u->last_session_timestamp); + u->gc_mode = user_gc_mode_from_string(gc_mode); + if (u->gc_mode < 0) + u->gc_mode = USER_GC_BY_PIN; + return 0; } -static void user_start_service(User *u) { +static int user_start_runtime_dir(User *u) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; int r; assert(u); + assert(!u->stopping); + assert(u->manager); + assert(u->runtime_dir_unit); - /* Start the service containing the "systemd --user" instance (user@.service). Note that we don't explicitly - * start the per-user slice or the systemd-runtime-dir@.service instance, as those are pulled in both by - * user@.service and the session scopes as dependencies. */ + u->runtime_dir_job = mfree(u->runtime_dir_job); - u->service_job = mfree(u->service_job); - - r = manager_start_unit(u->manager, u->service, &error, &u->service_job); + r = manager_start_unit(u->manager, u->runtime_dir_unit, &error, &u->runtime_dir_job); if (r < 0) - log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to start user service '%s', ignoring: %s", u->service, bus_error_message(&error, r)); + return log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED) ? LOG_DEBUG : LOG_ERR, + r, "Failed to start user service '%s': %s", + u->runtime_dir_unit, bus_error_message(&error, r)); + + return 0; +} + +static bool user_wants_service_manager(const User *u) { + assert(u); + + LIST_FOREACH(sessions_by_user, s, u->sessions) + if (SESSION_CLASS_WANTS_SERVICE_MANAGER(s->class)) + return true; + + if (user_check_linger_file(u) > 0) + return true; + + return false; +} + +int user_start_service_manager(User *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(u); + assert(!u->stopping); + assert(u->manager); + assert(u->service_manager_unit); + + if (u->service_manager_started) + return 1; + + /* Only start user service manager if there's at least one session which wants it */ + if (!user_wants_service_manager(u)) + return 0; + + u->service_manager_job = mfree(u->service_manager_job); + + r = manager_start_unit(u->manager, u->service_manager_unit, &error, &u->service_manager_job); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_UNIT_MASKED)) + return 0; + + return log_error_errno(r, "Failed to start user service '%s': %s", + u->service_manager_unit, bus_error_message(&error, r)); + } + + return (u->service_manager_started = true); } static int update_slice_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { @@ -412,12 +472,14 @@ static int user_update_slice(User *u) { { "IOWeight", u->user_record->io_weight }, }; - for (size_t i = 0; i < ELEMENTSOF(settings); i++) - if (settings[i].value != UINT64_MAX) { - r = sd_bus_message_append(m, "(sv)", settings[i].name, "t", settings[i].value); - if (r < 0) - return bus_log_create_error(r); - } + FOREACH_ELEMENT(st, settings) { + if (st->value == UINT64_MAX) + continue; + + r = sd_bus_message_append(m, "(sv)", st->name, "t", st->value); + if (r < 0) + return bus_log_create_error(r); + } r = sd_bus_message_close_container(m); if (r < 0) @@ -434,33 +496,47 @@ static int user_update_slice(User *u) { } int user_start(User *u) { + int r; + assert(u); - if (u->started && !u->stopping) + if (u->service_manager_started) { + /* Everything is up. No action needed. */ + assert(u->started && !u->stopping); return 0; + } - /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. We have to clear - * that flag before queueing the start-jobs again. If they succeed, the user object can be re-used just fine - * (pid1 takes care of job-ordering and proper restart), but if they fail, we want to force another user_stop() - * so possibly pending units are stopped. */ - u->stopping = false; + if (!u->started || u->stopping) { + /* If u->stopping is set, the user is marked for removal and service stop-jobs are queued. + * We have to clear that flag before queueing the start-jobs again. If they succeed, the + * user object can be reused just fine (pid1 takes care of job-ordering and proper restart), + * but if they fail, we want to force another user_stop() so possibly pending units are + * stopped. */ + u->stopping = false; - if (!u->started) - log_debug("Starting services for new user %s.", u->user_record->user_name); + if (!u->started) + log_debug("Tracking new user %s.", u->user_record->user_name); - /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it while starting up - * systemd --user. We need to do user_save_internal() because we have not "officially" started yet. */ - user_save_internal(u); + /* Save the user data so far, because pam_systemd will read the XDG_RUNTIME_DIR out of it + * while starting up systemd --user. We need to do user_save_internal() because we have not + * "officially" started yet. */ + user_save_internal(u); - /* Set slice parameters */ - (void) user_update_slice(u); + /* Set slice parameters */ + (void) user_update_slice(u); - /* Start user@UID.service */ - user_start_service(u); + (void) user_start_runtime_dir(u); + } + + /* Start user@UID.service if needed. */ + r = user_start_service_manager(u); + if (r < 0) + return r; if (!u->started) { if (!dual_timestamp_is_set(&u->timestamp)) dual_timestamp_now(&u->timestamp); + user_send_signal(u, true); u->started = true; } @@ -476,16 +552,22 @@ static void user_stop_service(User *u, bool force) { int r; assert(u); - assert(u->service); + assert(u->manager); + assert(u->runtime_dir_unit); + + /* Note that we only stop user-runtime-dir@.service here, and let BindsTo= deal with the user@.service + * instance. However, we still need to clear service_manager_job here, so that if the stop is + * interrupted, the new sessions won't be confused by leftovers. */ - /* The reverse of user_start_service(). Note that we only stop user@UID.service here, and let StopWhenUnneeded= - * deal with the slice and the user-runtime-dir@.service instance. */ + u->service_manager_job = mfree(u->service_manager_job); + u->service_manager_started = false; - u->service_job = mfree(u->service_job); + u->runtime_dir_job = mfree(u->runtime_dir_job); - r = manager_stop_unit(u->manager, u->service, force ? "replace" : "fail", &error, &u->service_job); + r = manager_stop_unit(u->manager, u->runtime_dir_unit, force ? "replace" : "fail", &error, &u->runtime_dir_job); if (r < 0) - log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", u->service, bus_error_message(&error, r)); + log_warning_errno(r, "Failed to stop user service '%s', ignoring: %s", + u->runtime_dir_unit, bus_error_message(&error, r)); } int user_stop(User *u, bool force) { @@ -506,13 +588,8 @@ int user_stop(User *u, bool force) { return 0; } - LIST_FOREACH(sessions_by_user, s, u->sessions) { - int k; - - k = session_stop(s, force); - if (k < 0) - r = k; - } + LIST_FOREACH(sessions_by_user, s, u->sessions) + RET_GATHER(r, session_stop(s, force)); user_stop_service(u, force); @@ -524,7 +601,7 @@ int user_stop(User *u, bool force) { } int user_finalize(User *u) { - int r = 0, k; + int r = 0; assert(u); @@ -532,13 +609,10 @@ int user_finalize(User *u) { * done. This is called as a result of an earlier user_done() when all jobs are completed. */ if (u->started) - log_debug("User %s logged out.", u->user_record->user_name); + log_debug("User %s exited.", u->user_record->user_name); - LIST_FOREACH(sessions_by_user, s, u->sessions) { - k = session_finalize(s); - if (k < 0) - r = k; - } + LIST_FOREACH(sessions_by_user, s, u->sessions) + RET_GATHER(r, session_finalize(s)); /* Clean SysV + POSIX IPC objects, but only if this is not a system user. Background: in many setups cronjobs * are run in full PAM and thus logind sessions, even if the code run doesn't belong to actual users but to @@ -546,11 +620,8 @@ int user_finalize(User *u) { * cases, as we shouldn't accidentally remove a system service's IPC objects while it is running, just because * a cronjob running as the same user just finished. Hence: exclude system users generally from IPC clean-up, * and do it only for normal users. */ - if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) { - k = clean_ipc_by_uid(u->user_record->uid); - if (k < 0) - r = k; - } + if (u->manager->remove_ipc && !uid_is_system(u->user_record->uid)) + RET_GATHER(r, clean_ipc_by_uid(u->user_record->uid)); (void) unlink(u->state_file); user_add_to_gc_queue(u); @@ -573,6 +644,9 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { dual_timestamp k; int ih; + if (!SESSION_CLASS_CAN_IDLE(s->class)) + continue; + ih = session_get_idle_hint(s, &k); if (ih < 0) return ih; @@ -598,9 +672,12 @@ int user_get_idle_hint(User *u, dual_timestamp *t) { return idle_hint; } -int user_check_linger_file(User *u) { +int user_check_linger_file(const User *u) { _cleanup_free_ char *cc = NULL; - char *p = NULL; + const char *p; + + assert(u); + assert(u->user_record); cc = cescape(u->user_record->user_name); if (!cc) @@ -620,11 +697,11 @@ int user_check_linger_file(User *u) { static bool user_unit_active(User *u) { int r; - assert(u->service); - assert(u->runtime_dir_service); assert(u->slice); + assert(u->runtime_dir_unit); + assert(u->service_manager_unit); - FOREACH_STRING(i, u->service, u->runtime_dir_service, u->slice) { + FOREACH_STRING(i, u->slice, u->runtime_dir_unit, u->service_manager_unit) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; r = manager_unit_is_active(u->manager, i, &error); @@ -649,6 +726,29 @@ static usec_t user_get_stop_delay(User *u) { return u->manager->user_stop_delay; } +static bool user_pinned_by_sessions(User *u) { + assert(u); + + /* Returns true if at least one session exists that shall keep the user tracking alive. That + * generally means one session that isn't the service manager still exists. */ + + switch (u->gc_mode) { + + case USER_GC_BY_ANY: + return u->sessions; + + case USER_GC_BY_PIN: + LIST_FOREACH(sessions_by_user, i, u->sessions) + if (SESSION_CLASS_PIN_USER(i->class)) + return true; + + return false; + + default: + assert_not_reached(); + } +} + bool user_may_gc(User *u, bool drop_not_started) { int r; @@ -657,7 +757,7 @@ bool user_may_gc(User *u, bool drop_not_started) { if (drop_not_started && !u->started) return true; - if (u->sessions) + if (user_pinned_by_sessions(u)) return false; if (u->last_session_timestamp != USEC_INFINITY) { @@ -681,18 +781,23 @@ bool user_may_gc(User *u, bool drop_not_started) { return false; /* Check if our job is still pending */ - if (u->service_job) { + const char *j; + FOREACH_ARGUMENT(j, u->runtime_dir_job, u->service_manager_job) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - r = manager_job_is_active(u->manager, u->service_job, &error); + if (!j) + continue; + + r = manager_job_is_active(u->manager, j, &error); if (r < 0) - log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", u->service_job, bus_error_message(&error, r)); + log_debug_errno(r, "Failed to determine whether job '%s' is pending, ignoring: %s", + j, bus_error_message(&error, r)); if (r != 0) return false; } - /* Note that we don't care if the three units we manage for each user object are up or not, as we are managing - * their state rather than tracking it. */ + /* Note that we don't care if the three units we manage for each user object are up or not, as we are + * managing their state rather than tracking it. */ return true; } @@ -713,25 +818,29 @@ UserState user_get_state(User *u) { if (u->stopping) return USER_CLOSING; - if (!u->started || u->service_job) + if (!u->started || u->runtime_dir_job) return USER_OPENING; - if (u->sessions) { - bool all_closing = true; + bool any = false, all_closing = true; + LIST_FOREACH(sessions_by_user, i, u->sessions) { + SessionState state; - LIST_FOREACH(sessions_by_user, i, u->sessions) { - SessionState state; + /* Ignore sessions that don't pin the user, i.e. are not supposed to have an effect on user state */ + if (!SESSION_CLASS_PIN_USER(i->class)) + continue; - state = session_get_state(i); - if (state == SESSION_ACTIVE) - return USER_ACTIVE; - if (state != SESSION_CLOSING) - all_closing = false; - } + state = session_get_state(i); + if (state == SESSION_ACTIVE) + return USER_ACTIVE; + if (state != SESSION_CLOSING) + all_closing = false; - return all_closing ? USER_CLOSING : USER_ONLINE; + any = true; } + if (any) + return all_closing ? USER_CLOSING : USER_ONLINE; + if (user_check_linger_file(u) > 0 && user_unit_active(u)) return USER_LINGERING; @@ -748,7 +857,7 @@ static bool elect_display_filter(Session *s) { /* Return true if the session is a candidate for the user’s ‘primary session’ or ‘display’. */ assert(s); - return IN_SET(s->class, SESSION_USER, SESSION_GREETER) && s->started && !s->stopping; + return SESSION_CLASS_CAN_DISPLAY(s->class) && s->started && !s->stopping; } static int elect_display_compare(Session *s1, Session *s2) { @@ -780,6 +889,9 @@ static int elect_display_compare(Session *s1, Session *s2) { if ((s1->class != SESSION_USER) != (s2->class != SESSION_USER)) return (s1->class != SESSION_USER) - (s2->class != SESSION_USER); + if ((s1->class != SESSION_USER_EARLY) != (s2->class != SESSION_USER_EARLY)) + return (s1->class != SESSION_USER_EARLY) - (s2->class != SESSION_USER_EARLY); + if ((s1->type == _SESSION_TYPE_INVALID) != (s2->type == _SESSION_TYPE_INVALID)) return (s1->type == _SESSION_TYPE_INVALID) - (s2->type == _SESSION_TYPE_INVALID); @@ -823,7 +935,7 @@ void user_update_last_session_timer(User *u) { assert(u); - if (u->sessions) { + if (user_pinned_by_sessions(u)) { /* There are sessions, turn off the timer */ u->last_session_timestamp = USEC_INFINITY; u->timer_event_source = sd_event_source_unref(u->timer_event_source); @@ -871,6 +983,13 @@ static const char* const user_state_table[_USER_STATE_MAX] = { DEFINE_STRING_TABLE_LOOKUP(user_state, UserState); +static const char* const user_gc_mode_table[_USER_GC_MODE_MAX] = { + [USER_GC_BY_PIN] = "pin", + [USER_GC_BY_ANY] = "any", +}; + +DEFINE_STRING_TABLE_LOOKUP(user_gc_mode, UserGCMode); + int config_parse_tmpfs_size( const char* unit, const char *filename, diff --git a/src/login/logind-user.h b/src/login/logind-user.h index 21b9f8f..5c82f49 100644 --- a/src/login/logind-user.h +++ b/src/login/logind-user.h @@ -19,6 +19,13 @@ typedef enum UserState { _USER_STATE_INVALID = -EINVAL, } UserState; +typedef enum UserGCMode { + USER_GC_BY_ANY, /* any session pins this user */ + USER_GC_BY_PIN, /* only sessions with an explicitly pinning class pin this user */ + _USER_GC_MODE_MAX, + _USER_GC_MODE_INVALID = -EINVAL, +} UserGCMode; + struct User { Manager *manager; @@ -27,11 +34,17 @@ struct User { char *state_file; char *runtime_path; - char *slice; /* user-UID.slice */ - char *service; /* user@UID.service */ - char *runtime_dir_service; /* user-runtime-dir@UID.service */ + /* user-UID.slice */ + char *slice; + + /* user-runtime-dir@UID.service */ + char *runtime_dir_unit; + char *runtime_dir_job; - char *service_job; + /* user@UID.service */ + bool service_manager_started; + char *service_manager_unit; + char *service_manager_job; Session *display; @@ -41,23 +54,27 @@ struct User { /* Set up when the last session of the user logs out */ sd_event_source *timer_event_source; + UserGCMode gc_mode; bool in_gc_queue:1; - bool started:1; /* Whenever the user being started, has been started or is being stopped again. */ + bool started:1; /* Whenever the user being started, has been started or is being stopped again + (tracked through user-runtime-dir@.service) */ bool stopping:1; /* Whenever the user is being stopped or has been stopped. */ LIST_HEAD(Session, sessions); LIST_FIELDS(User, gc_queue); }; -int user_new(User **out, Manager *m, UserRecord *ur); +int user_new(Manager *m, UserRecord *ur, User **ret); User *user_free(User *u); -DEFINE_TRIVIAL_CLEANUP_FUNC(User *, user_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(User*, user_free); + +int user_start_service_manager(User *u); +int user_start(User *u); bool user_may_gc(User *u, bool drop_not_started); void user_add_to_gc_queue(User *u); -int user_start(User *u); int user_stop(User *u, bool force); int user_finalize(User *u); UserState user_get_state(User *u); @@ -65,11 +82,14 @@ int user_get_idle_hint(User *u, dual_timestamp *t); int user_save(User *u); int user_load(User *u); int user_kill(User *u, int signo); -int user_check_linger_file(User *u); +int user_check_linger_file(const User *u); void user_elect_display(User *u); void user_update_last_session_timer(User *u); const char* user_state_to_string(UserState s) _const_; UserState user_state_from_string(const char *s) _pure_; +const char* user_gc_mode_to_string(UserGCMode m) _const_; +UserGCMode user_gc_mode_from_string(const char *s) _pure_; + CONFIG_PARSER_PROTOTYPE(config_parse_compat_user_tasks_max); diff --git a/src/login/logind-wall.c b/src/login/logind-wall.c index 97b74e9..ff70a59 100644 --- a/src/login/logind-wall.c +++ b/src/login/logind-wall.c @@ -42,7 +42,7 @@ static usec_t when_wall(usec_t n, usec_t elapse) { bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) { Manager *m = ASSERT_PTR(userdata); - assert(m->scheduled_shutdown_action); + assert(handle_action_valid(m->scheduled_shutdown_action)); const char *p = path_startswith(tty, "/dev/"); if (!p) @@ -52,7 +52,7 @@ bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) { * can assume that if the system enters sleep or hibernation, this will be visible in an obvious way * for any local user. And once the systems exits sleep or hibernation, the notification would be * just noise, in particular for auto-suspend. */ - if (is_local && HANDLE_ACTION_IS_SLEEP(m->scheduled_shutdown_action->handle)) + if (is_local && HANDLE_ACTION_IS_SLEEP(m->scheduled_shutdown_action)) return false; return !streq_ptr(p, m->scheduled_shutdown_tty); @@ -61,7 +61,7 @@ bool logind_wall_tty_filter(const char *tty, bool is_local, void *userdata) { static int warn_wall(Manager *m, usec_t n) { assert(m); - if (!m->scheduled_shutdown_action) + if (!handle_action_valid(m->scheduled_shutdown_action)) return 0; bool left = m->scheduled_shutdown_timeout > n; @@ -70,7 +70,7 @@ static int warn_wall(Manager *m, usec_t n) { if (asprintf(&l, "%s%sThe system will %s %s%s!", strempty(m->wall_message), isempty(m->wall_message) ? "" : "\n", - handle_action_verb_to_string(m->scheduled_shutdown_action->handle), + handle_action_verb_to_string(m->scheduled_shutdown_action), left ? "at " : "now", left ? FORMAT_TIMESTAMP(m->scheduled_shutdown_timeout) : "") < 0) { @@ -84,7 +84,7 @@ static int warn_wall(Manager *m, usec_t n) { log_struct(level, LOG_MESSAGE("%s", l), - "ACTION=%s", handle_action_to_string(m->scheduled_shutdown_action->handle), + "ACTION=%s", handle_action_to_string(m->scheduled_shutdown_action), "MESSAGE_ID=" SD_MESSAGE_SHUTDOWN_SCHEDULED_STR, username ? "OPERATOR=%s" : NULL, username); @@ -134,7 +134,7 @@ int manager_setup_wall_message_timer(Manager *m) { /* wall message handling */ - if (!m->scheduled_shutdown_action) + if (!handle_action_valid(m->scheduled_shutdown_action)) return 0; if (elapse > 0 && elapse < n) diff --git a/src/login/logind.c b/src/login/logind.c index 88e05bb..ac4b860 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -18,6 +18,7 @@ #include "constants.h" #include "daemon-util.h" #include "device-util.h" +#include "devnum-util.h" #include "dirent-util.h" #include "escape.h" #include "fd-util.h" @@ -65,17 +66,18 @@ static int manager_new(Manager **ret) { .reserve_vt_fd = -EBADF, .enable_wall_messages = true, .idle_action_not_before_usec = now(CLOCK_MONOTONIC), - }; + .scheduled_shutdown_action = _HANDLE_ACTION_INVALID, - m->devices = hashmap_new(&device_hash_ops); - m->seats = hashmap_new(&seat_hash_ops); - m->sessions = hashmap_new(&session_hash_ops); - m->users = hashmap_new(&user_hash_ops); - m->inhibitors = hashmap_new(&inhibitor_hash_ops); - m->buttons = hashmap_new(&button_hash_ops); + .devices = hashmap_new(&device_hash_ops), + .seats = hashmap_new(&seat_hash_ops), + .sessions = hashmap_new(&session_hash_ops), + .users = hashmap_new(&user_hash_ops), + .inhibitors = hashmap_new(&inhibitor_hash_ops), + .buttons = hashmap_new(&button_hash_ops), - m->user_units = hashmap_new(&string_hash_ops); - m->session_units = hashmap_new(&string_hash_ops); + .user_units = hashmap_new(&string_hash_ops), + .session_units = hashmap_new(&string_hash_ops), + }; if (!m->devices || !m->seats || !m->sessions || !m->users || !m->inhibitors || !m->buttons || !m->user_units || !m->session_units) return -ENOMEM; @@ -153,7 +155,7 @@ static Manager* manager_free(Manager *m) { if (m->unlink_nologin) (void) unlink_or_warn("/run/nologin"); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); @@ -190,12 +192,12 @@ static int manager_enumerate_devices(Manager *m) { if (r < 0) return r; - FOREACH_DEVICE(e, d) { - int k; + r = 0; - k = manager_process_seat_device(m, d); - if (k < 0) - r = k; + FOREACH_DEVICE(e, d) { + if (device_is_processed(d) <= 0) + continue; + RET_GATHER(r, manager_process_seat_device(m, d)); } return r; @@ -224,12 +226,12 @@ static int manager_enumerate_buttons(Manager *m) { if (r < 0) return r; - FOREACH_DEVICE(e, d) { - int k; + r = 0; - k = manager_process_button_device(m, d); - if (k < 0) - r = k; + FOREACH_DEVICE(e, d) { + if (device_is_processed(d) <= 0) + continue; + RET_GATHER(r, manager_process_button_device(m, d)); } return r; @@ -250,12 +252,11 @@ static int manager_enumerate_seats(Manager *m) { if (errno == ENOENT) return 0; - return log_error_errno(errno, "Failed to open /run/systemd/seats: %m"); + return log_error_errno(errno, "Failed to open /run/systemd/seats/: %m"); } FOREACH_DIRENT(de, d, return -errno) { Seat *s; - int k; if (!dirent_is_file(de)) continue; @@ -268,9 +269,7 @@ static int manager_enumerate_seats(Manager *m) { continue; } - k = seat_load(s); - if (k < 0) - r = k; + RET_GATHER(r, seat_load(s)); } return r; @@ -291,19 +290,21 @@ static int manager_enumerate_linger_users(Manager *m) { } FOREACH_DIRENT(de, d, return -errno) { - int k; _cleanup_free_ char *n = NULL; + int k; if (!dirent_is_file(de)) continue; + k = cunescape(de->d_name, 0, &n); if (k < 0) { - r = log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Failed to unescape username '%s', ignoring: %m", de->d_name)); continue; } + k = manager_add_user_by_name(m, n, NULL); if (k < 0) - r = log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Couldn't add lingering user %s, ignoring: %m", de->d_name)); } return r; @@ -311,7 +312,7 @@ static int manager_enumerate_linger_users(Manager *m) { static int manager_enumerate_users(Manager *m) { _cleanup_closedir_ DIR *d = NULL; - int r, k; + int r; assert(m); @@ -324,148 +325,205 @@ static int manager_enumerate_users(Manager *m) { if (errno == ENOENT) return 0; - return log_error_errno(errno, "Failed to open /run/systemd/users: %m"); + return log_error_errno(errno, "Failed to open /run/systemd/users/: %m"); } FOREACH_DIRENT(de, d, return -errno) { User *u; uid_t uid; + int k; if (!dirent_is_file(de)) continue; k = parse_uid(de->d_name, &uid); if (k < 0) { - r = log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID.", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Failed to parse filename /run/systemd/users/%s as UID, ignoring: %m", de->d_name)); continue; } k = manager_add_user_by_uid(m, uid, &u); if (k < 0) { - r = log_warning_errno(k, "Failed to add user by file name %s, ignoring: %m", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Failed to add user by filename %s, ignoring: %m", de->d_name)); continue; } user_add_to_gc_queue(u); - k = user_load(u); - if (k < 0) - r = k; + RET_GATHER(r, user_load(u)); } return r; } -static int parse_fdname(const char *fdname, char **session_id, dev_t *dev) { +static int parse_fdname(const char *fdname, char **ret_session_id, dev_t *ret_devno) { _cleanup_strv_free_ char **parts = NULL; _cleanup_free_ char *id = NULL; - unsigned major, minor; int r; + assert(ret_session_id); + assert(ret_devno); + parts = strv_split(fdname, "-"); if (!parts) return -ENOMEM; - if (strv_length(parts) != 5) - return -EINVAL; - if (!streq(parts[0], "session")) + if (_unlikely_(!streq(parts[0], "session"))) return -EINVAL; id = strdup(parts[1]); if (!id) return -ENOMEM; - if (!streq(parts[2], "device")) + if (streq(parts[2], "leader")) { + *ret_session_id = TAKE_PTR(id); + *ret_devno = 0; + + return 0; + } + + if (_unlikely_(!streq(parts[2], "device"))) return -EINVAL; + unsigned major, minor; + r = safe_atou(parts[3], &major); if (r < 0) return r; + r = safe_atou(parts[4], &minor); if (r < 0) return r; - *dev = makedev(major, minor); - *session_id = TAKE_PTR(id); + *ret_session_id = TAKE_PTR(id); + *ret_devno = makedev(major, minor); return 0; } -static int deliver_fd(Manager *m, const char *fdname, int fd) { - _cleanup_free_ char *id = NULL; +static int deliver_session_device_fd(Session *s, const char *fdname, int fd, dev_t devno) { SessionDevice *sd; struct stat st; - Session *s; - dev_t dev; - int r; - assert(m); + assert(s); + assert(fdname); assert(fd >= 0); - - r = parse_fdname(fdname, &id, &dev); - if (r < 0) - return log_debug_errno(r, "Failed to parse fd name %s: %m", fdname); - - s = hashmap_get(m->sessions, id); - if (!s) - /* If the session doesn't exist anymore, the associated session device attached to this fd - * doesn't either. Let's simply close this fd. */ - return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Failed to attach fd for unknown session: %s", id); + assert(devno > 0); if (fstat(fd, &st) < 0) /* The device is allowed to go away at a random point, in which case fstat() failing is * expected. */ - return log_debug_errno(errno, "Failed to stat device fd for session %s: %m", id); + return log_debug_errno(errno, "Failed to stat device fd '%s' for session '%s': %m", + fdname, s->id); - if (!S_ISCHR(st.st_mode) || st.st_rdev != dev) - return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "Device fd doesn't point to the expected character device node"); + if (!S_ISCHR(st.st_mode) || st.st_rdev != devno) + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), + "Device fd '%s' doesn't point to the expected character device node.", + fdname); - sd = hashmap_get(s->devices, &dev); + sd = hashmap_get(s->devices, &devno); if (!sd) /* Weird, we got an fd for a session device which wasn't recorded in the session state * file... */ - return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), "Got fd for missing session device [%u:%u] in session %s", - major(dev), minor(dev), s->id); + return log_warning_errno(SYNTHETIC_ERRNO(ENODEV), + "Got session device fd '%s' [" DEVNUM_FORMAT_STR "], but not present in session state.", + fdname, DEVNUM_FORMAT_VAL(devno)); - log_debug("Attaching fd to session device [%u:%u] for session %s", - major(dev), minor(dev), s->id); + log_debug("Attaching session device fd '%s' [" DEVNUM_FORMAT_STR "] to session '%s'.", + fdname, DEVNUM_FORMAT_VAL(devno), s->id); session_device_attach_fd(sd, fd, s->was_active); return 0; } -static int manager_attach_fds(Manager *m) { - _cleanup_strv_free_ char **fdnames = NULL; - int n; +static int deliver_session_leader_fd_consume(Session *s, const char *fdname, int fd) { + _cleanup_(pidref_done) PidRef leader_fdstore = PIDREF_NULL; + int r; - /* Upon restart, PID1 will send us back all fds of session devices that we previously opened. Each - * file descriptor is associated with a given session. The session ids are passed through FDNAMES. */ + assert(s); + assert(fdname); + assert(fd >= 0); - assert(m); + if (!pid_is_valid(s->deserialized_pid)) { + r = log_warning_errno(SYNTHETIC_ERRNO(EOWNERDEAD), + "Got leader pidfd for session '%s', but LEADER= is not set, refusing.", + s->id); + goto fail_close; + } - n = sd_listen_fds_with_names(true, &fdnames); - if (n < 0) - return log_warning_errno(n, "Failed to acquire passed fd list: %m"); - if (n == 0) - return 0; + if (!s->leader_fd_saved) + log_warning("Got leader pidfd for session '%s', but not recorded in session state, proceeding anyway.", + s->id); + else + assert(!pidref_is_set(&s->leader)); + + r = pidref_set_pidfd_take(&leader_fdstore, fd); + if (r < 0) { + if (r == -ESRCH) + log_debug_errno(r, "Leader of session '%s' is gone while deserializing.", s->id); + else + log_warning_errno(r, "Failed to create reference to leader of session '%s': %m", s->id); + goto fail_close; + } - for (int i = 0; i < n; i++) { - int fd = SD_LISTEN_FDS_START + i; + if (leader_fdstore.pid != s->deserialized_pid) + log_warning("Leader from pidfd (" PID_FMT ") doesn't match with LEADER=" PID_FMT " for session '%s', proceeding anyway.", + leader_fdstore.pid, s->deserialized_pid, s->id); - if (deliver_fd(m, fdnames[i], fd) >= 0) - continue; + r = session_set_leader_consume(s, TAKE_PIDREF(leader_fdstore)); + if (r < 0) + return log_warning_errno(r, "Failed to attach leader pidfd for session '%s': %m", s->id); + + return 0; + +fail_close: + close_and_notify_warn(fd, fdname); + return r; +} + +static int manager_attach_session_fd_one_consume(Manager *m, const char *fdname, int fd) { + _cleanup_free_ char *id = NULL; + dev_t devno = 0; /* Explicit initialization to appease gcc */ + Session *s; + int r; + + assert(m); + assert(fdname); + assert(fd >= 0); - /* Hmm, we couldn't deliver the fd to any session device object? If so, let's close the fd - * and remove it from fdstore. */ - close_and_notify_warn(fd, fdnames[i]); + r = parse_fdname(fdname, &id, &devno); + if (r < 0) { + log_warning_errno(r, "Failed to parse stored fd name '%s': %m", fdname); + goto fail_close; } - return 0; + s = hashmap_get(m->sessions, id); + if (!s) { + /* If the session doesn't exist anymore, let's simply close this fd. */ + r = log_debug_errno(SYNTHETIC_ERRNO(ENXIO), + "Cannot attach fd '%s' to unknown session '%s', ignoring.", fdname, id); + goto fail_close; + } + + if (devno > 0) { + r = deliver_session_device_fd(s, fdname, fd, devno); + if (r < 0) + goto fail_close; + return 0; + } + + /* Takes ownership of fd on both success and failure */ + return deliver_session_leader_fd_consume(s, fdname, fd); + +fail_close: + close_and_notify_warn(fd, fdname); + return r; } static int manager_enumerate_sessions(Manager *m) { + _cleanup_strv_free_ char **fdnames = NULL; _cleanup_closedir_ DIR *d = NULL; - int r = 0, k; + int r = 0, n; assert(m); @@ -475,18 +533,19 @@ static int manager_enumerate_sessions(Manager *m) { if (errno == ENOENT) return 0; - return log_error_errno(errno, "Failed to open /run/systemd/sessions: %m"); + return log_error_errno(errno, "Failed to open /run/systemd/sessions/: %m"); } FOREACH_DIRENT(de, d, return -errno) { - struct Session *s; + Session *s; + int k; if (!dirent_is_file(de)) continue; k = manager_add_session(m, de->d_name, &s); if (k < 0) { - r = log_warning_errno(k, "Failed to add session by file name %s, ignoring: %m", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Failed to add session by filename %s, ignoring: %m", de->d_name)); continue; } @@ -494,12 +553,18 @@ static int manager_enumerate_sessions(Manager *m) { k = session_load(s); if (k < 0) - r = k; + RET_GATHER(r, log_warning_errno(k, "Failed to deserialize session '%s', ignoring: %m", s->id)); } - /* We might be restarted and PID1 could have sent us back the session device fds we previously - * saved. */ - (void) manager_attach_fds(m); + n = sd_listen_fds_with_names(/* unset_environment = */ true, &fdnames); + if (n < 0) + return log_error_errno(n, "Failed to acquire passed fd list: %m"); + + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; + + RET_GATHER(r, manager_attach_session_fd_one_consume(m, fdnames[i], fd)); + } return r; } @@ -515,25 +580,23 @@ static int manager_enumerate_inhibitors(Manager *m) { if (errno == ENOENT) return 0; - return log_error_errno(errno, "Failed to open /run/systemd/inhibit: %m"); + return log_error_errno(errno, "Failed to open /run/systemd/inhibit/: %m"); } FOREACH_DIRENT(de, d, return -errno) { - int k; Inhibitor *i; + int k; if (!dirent_is_file(de)) continue; k = manager_add_inhibitor(m, de->d_name, &i); if (k < 0) { - r = log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name); + RET_GATHER(r, log_warning_errno(k, "Couldn't add inhibitor %s, ignoring: %m", de->d_name)); continue; } - k = inhibitor_load(i); - if (k < 0) - r = k; + RET_GATHER(r, inhibitor_load(i)); } return r; @@ -775,7 +838,7 @@ static int manager_connect_console(Manager *m) { SIGRTMIN, SIGRTMAX); assert_se(ignore_signals(SIGRTMIN + 1) >= 0); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN) >= 0); r = sd_event_add_signal(m->event, NULL, SIGRTMIN, manager_vt_switch, m); if (r < 0) @@ -986,7 +1049,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us m->event, &m->idle_action_event_source, CLOCK_MONOTONIC, - elapse, USEC_PER_SEC*30, + elapse, MIN(USEC_PER_SEC*30, m->idle_action_usec), /* accuracy of 30s, but don't have an accuracy lower than the idle action timeout */ manager_dispatch_idle_action, m); if (r < 0) return log_error_errno(r, "Failed to add idle event source: %m"); @@ -1011,10 +1074,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa Manager *m = userdata; int r; - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Reloading configuration...\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + (void) notify_reloading(); manager_reset_config(m); r = manager_parse_config_file(m); @@ -1187,7 +1247,7 @@ static int run(int argc, char *argv[]) { (void) mkdir_label("/run/systemd/users", 0755); (void) mkdir_label("/run/systemd/sessions", 0755); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGCHLD, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGHUP, SIGTERM, SIGINT, SIGCHLD, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) diff --git a/src/login/logind.conf.in b/src/login/logind.conf.in index e5fe924..b62458e 100644 --- a/src/login/logind.conf.in +++ b/src/login/logind.conf.in @@ -24,6 +24,7 @@ #KillExcludeUsers=root #InhibitDelayMaxSec=5 #UserStopDelaySec=10 +#SleepOperation=suspend-then-hibernate suspend #HandlePowerKey=poweroff #HandlePowerKeyLongPress=ignore #HandleRebootKey=reboot diff --git a/src/login/logind.h b/src/login/logind.h index 7532d37..cac6a30 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -76,7 +76,7 @@ struct Manager { char *action_job; sd_event_source *inhibit_timeout_source; - const HandleActionData *scheduled_shutdown_action; + HandleAction scheduled_shutdown_action; usec_t scheduled_shutdown_timeout; sd_event_source *scheduled_shutdown_timeout_source; uid_t scheduled_shutdown_uid; @@ -98,6 +98,8 @@ struct Manager { usec_t stop_idle_session_usec; + HandleActionSleepMask handle_action_sleep_mask; + HandleAction handle_power_key; HandleAction handle_power_key_long_press; HandleAction handle_reboot_key; diff --git a/src/login/meson.build b/src/login/meson.build index b5bb150..43db031 100644 --- a/src/login/meson.build +++ b/src/login/meson.build @@ -65,9 +65,9 @@ executables += [ 'conditions' : ['ENABLE_LOGIND'], 'sources' : loginctl_sources, 'dependencies' : [ - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ], }, diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf index 8ba094b..dff944f 100644 --- a/src/login/org.freedesktop.login1.conf +++ b/src/login/org.freedesktop.login1.conf @@ -62,6 +62,10 @@ send_interface="org.freedesktop.login1.Manager" send_member="ListSessions"/> + + @@ -182,6 +186,10 @@ send_interface="org.freedesktop.login1.Manager" send_member="SuspendThenHibernateWithFlags"/> + + @@ -210,6 +218,10 @@ send_interface="org.freedesktop.login1.Manager" send_member="CanSuspendThenHibernate"/> + + @@ -262,6 +274,10 @@ send_interface="org.freedesktop.login1.Manager" send_member="FlushDevices"/> + + @@ -322,6 +338,10 @@ send_interface="org.freedesktop.login1.Session" send_member="SetType"/> + + @@ -338,14 +358,6 @@ send_interface="org.freedesktop.login1.Session" send_member="SetBrightness"/> - - - - @@ -354,6 +366,14 @@ send_interface="org.freedesktop.login1.Session" send_member="SetTTY"/> + + + + diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c index bf45974..a711c89 100644 --- a/src/login/pam_systemd.c +++ b/src/login/pam_systemd.c @@ -10,6 +10,9 @@ #include #include #include +#if HAVE_PIDFD_OPEN +#include +#endif #include #include #include @@ -525,6 +528,26 @@ static const char* getenv_harder(pam_handle_t *handle, const char *key, const ch return fallback; } +static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallback) { + const char *v; + int r; + + assert(handle); + assert(key); + + v = getenv_harder(handle, key, NULL); + if (isempty(v)) + return fallback; + + r = parse_boolean(v); + if (r < 0) { + pam_syslog(handle, LOG_ERR, "Boolean environment variable value of '%s' is not valid: %s", key, v); + return fallback; + } + + return r; +} + static int update_environment(pam_handle_t *handle, const char *key, const char *value) { int r; @@ -606,6 +629,7 @@ static int apply_user_record_settings( bool debug, uint64_t default_capability_bounding_set, uint64_t default_capability_ambient_set) { + _cleanup_strv_free_ char **langs = NULL; int r; assert(handle); @@ -617,48 +641,25 @@ static int apply_user_record_settings( } STRV_FOREACH(i, ur->environment) { - _cleanup_free_ char *n = NULL; - const char *e; - - assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */ - - n = strndup(*i, e - *i); - if (!n) - return pam_log_oom(handle); - - if (pam_getenv(handle, n)) { - pam_debug_syslog(handle, debug, - "PAM environment variable $%s already set, not changing based on record.", *i); - continue; - } - r = pam_putenv_and_log(handle, *i, debug); if (r != PAM_SUCCESS) return r; } if (ur->email_address) { - if (pam_getenv(handle, "EMAIL")) - pam_debug_syslog(handle, debug, - "PAM environment variable $EMAIL already set, not changing based on user record."); - else { - _cleanup_free_ char *joined = NULL; + _cleanup_free_ char *joined = NULL; - joined = strjoin("EMAIL=", ur->email_address); - if (!joined) - return pam_log_oom(handle); + joined = strjoin("EMAIL=", ur->email_address); + if (!joined) + return pam_log_oom(handle); - r = pam_putenv_and_log(handle, joined, debug); - if (r != PAM_SUCCESS) - return r; - } + r = pam_putenv_and_log(handle, joined, debug); + if (r != PAM_SUCCESS) + return r; } if (ur->time_zone) { - if (pam_getenv(handle, "TZ")) - pam_debug_syslog(handle, debug, - "PAM environment variable $TZ already set, not changing based on user record."); - else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) + if (!timezone_is_valid(ur->time_zone, LOG_DEBUG)) pam_debug_syslog(handle, debug, "Time zone specified in user record is not valid locally, not setting $TZ."); else { @@ -674,21 +675,38 @@ static int apply_user_record_settings( } } - if (ur->preferred_language) { - if (pam_getenv(handle, "LANG")) - pam_debug_syslog(handle, debug, - "PAM environment variable $LANG already set, not changing based on user record."); - else if (locale_is_installed(ur->preferred_language) <= 0) - pam_debug_syslog(handle, debug, - "Preferred language specified in user record is not valid or not installed, not setting $LANG."); - else { - _cleanup_free_ char *joined = NULL; + r = user_record_languages(ur, &langs); + if (r < 0) + pam_syslog_errno(handle, LOG_ERR, r, + "Failed to acquire user's language preferences, ignoring: %m"); + else if (strv_isempty(langs)) + ; /* User has no preference set so we do nothing */ + else if (locale_is_installed(langs[0]) <= 0) + pam_debug_syslog(handle, debug, + "Preferred languages specified in user record are not installed locally, not setting $LANG or $LANGUAGE."); + else { + _cleanup_free_ char *lang = NULL; + + lang = strjoin("LANG=", langs[0]); + if (!lang) + return pam_log_oom(handle); + + r = pam_putenv_and_log(handle, lang, debug); + if (r != PAM_SUCCESS) + return r; + + if (strv_length(langs) > 1) { + _cleanup_free_ char *joined = NULL, *language = NULL; - joined = strjoin("LANG=", ur->preferred_language); + joined = strv_join(langs, ":"); if (!joined) return pam_log_oom(handle); - r = pam_putenv_and_log(handle, joined, debug); + language = strjoin("LANGUAGE=", joined); + if (!language) + return pam_log_oom(handle); + + r = pam_putenv_and_log(handle, language, debug); if (r != PAM_SUCCESS) return r; } @@ -908,12 +926,14 @@ _public_ PAM_EXTERN int pam_sm_open_session( _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(user_record_unrefp) UserRecord *ur = NULL; int session_fd = -EBADF, existing, r; - bool debug = false, remote; + bool debug = false, remote, incomplete; uint32_t vtnr = 0; uid_t original_uid; assert(handle); + pam_log_setup(); + if (parse_argv(handle, argc, argv, &class_pam, @@ -934,57 +954,39 @@ _public_ PAM_EXTERN int pam_sm_open_session( if (!logind_running()) goto success; - /* Make sure we don't enter a loop by talking to - * systemd-logind when it is actually waiting for the - * background to finish start-up. If the service is - * "systemd-user" we simply set XDG_RUNTIME_DIR and - * leave. */ - - r = pam_get_item(handle, PAM_SERVICE, (const void**) &service); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM service: @PAMERR@"); - if (streq_ptr(service, "systemd-user")) { - char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)]; - - xsprintf(rt, "/run/user/"UID_FMT, ur->uid); - r = configure_runtime_directory(handle, ur, rt); - if (r != PAM_SUCCESS) - return r; - - goto success; - } - - /* Otherwise, we ask logind to create a session for us */ - - r = pam_get_item(handle, PAM_XDISPLAY, (const void**) &display); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_XDISPLAY: @PAMERR@"); - r = pam_get_item(handle, PAM_TTY, (const void**) &tty); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_TTY: @PAMERR@"); - r = pam_get_item(handle, PAM_RUSER, (const void**) &remote_user); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RUSER: @PAMERR@"); - r = pam_get_item(handle, PAM_RHOST, (const void**) &remote_host); - if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) - return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RHOST: @PAMERR@"); + r = pam_get_item_many( + handle, + PAM_SERVICE, &service, + PAM_XDISPLAY, &display, + PAM_TTY, &tty, + PAM_RUSER, &remote_user, + PAM_RHOST, &remote_host); + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@"); seat = getenv_harder(handle, "XDG_SEAT", NULL); cvtnr = getenv_harder(handle, "XDG_VTNR", NULL); type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam); class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam); desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam); + incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false); - tty = strempty(tty); + if (streq_ptr(service, "systemd-user")) { + /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to + * 'manager' if not set, simply for robustness reasons. */ + type = "unspecified"; + class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ? + "manager-early" : "manager"; + tty = NULL; - if (strchr(tty, ':')) { + } else if (tty && strchr(tty, ':')) { /* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things * and don't pretend that an X display was a tty. */ if (isempty(display)) display = tty; tty = NULL; - } else if (streq(tty, "cron")) { + } else if (streq_ptr(tty, "cron")) { /* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but * probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set * (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked @@ -993,10 +995,10 @@ _public_ PAM_EXTERN int pam_sm_open_session( class = "background"; tty = NULL; - } else if (streq(tty, "ssh")) { + } else if (streq_ptr(tty, "ssh")) { /* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further * details look for "PAM_TTY_KLUDGE" in the openssh sources). */ - type ="tty"; + type = "tty"; class = "user"; tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually * associated with a pty — won't be tracked by their tty in logind. This is because ssh @@ -1004,7 +1006,7 @@ _public_ PAM_EXTERN int pam_sm_open_session( * much later (this is because it doesn't know yet if it needs one at all, as whether to * register a pty or not is negotiated much later in the protocol). */ - } else + } else if (tty) /* Chop off leading /dev prefix that some clients specify, but others do not. */ tty = skip_dev_prefix(tty); @@ -1029,7 +1031,16 @@ _public_ PAM_EXTERN int pam_sm_open_session( !isempty(tty) ? "tty" : "unspecified"; if (isempty(class)) - class = streq(type, "unspecified") ? "background" : "user"; + class = streq(type, "unspecified") ? "background" : + ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) && + streq(type, "tty")) ? "user-early" : "user"); + + if (incomplete) { + if (streq(class, "user")) + class = "user-incomplete"; + else + pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", class); + } remote = !isempty(remote_host) && !is_localhost(remote_host); @@ -1144,6 +1155,9 @@ _public_ PAM_EXTERN int pam_sm_open_session( "id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u", id, object_path, runtime_path, session_fd, seat, vtnr, original_uid); + /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars + * shall be added. */ + r = update_environment(handle, "XDG_SESSION_ID", id); if (r != PAM_SUCCESS) return r; @@ -1221,6 +1235,8 @@ _public_ PAM_EXTERN int pam_sm_close_session( assert(handle); + pam_log_setup(); + if (parse_argv(handle, argc, argv, NULL, diff --git a/src/login/pam_systemd_loadkey.c b/src/login/pam_systemd_loadkey.c index 3b4e911..3b11d76 100644 --- a/src/login/pam_systemd_loadkey.c +++ b/src/login/pam_systemd_loadkey.c @@ -18,13 +18,15 @@ * This can be overridden by the keyname= parameter. */ static const char DEFAULT_KEYNAME[] = "cryptsetup"; -_public_ int pam_sm_authenticate( +_public_ PAM_EXTERN int pam_sm_authenticate( pam_handle_t *handle, int flags, int argc, const char **argv) { assert(handle); + pam_log_setup(); + /* Parse argv. */ assert(argc >= 0); @@ -89,7 +91,7 @@ _public_ int pam_sm_authenticate( return PAM_SUCCESS; } -_public_ int pam_sm_setcred( +_public_ PAM_EXTERN int pam_sm_setcred( pam_handle_t *handle, int flags, int argc, const char **argv) { diff --git a/src/login/test-login-tables.c b/src/login/test-login-tables.c index 3c5ec04..1278af7 100644 --- a/src/login/test-login-tables.c +++ b/src/login/test-login-tables.c @@ -2,9 +2,24 @@ #include "logind-action.h" #include "logind-session.h" +#include "sleep-config.h" #include "test-tables.h" #include "tests.h" +static void test_sleep_handle_action(void) { + for (HandleAction action = _HANDLE_ACTION_SLEEP_FIRST; action < _HANDLE_ACTION_SLEEP_LAST; action++) { + const HandleActionData *data; + const char *sleep_operation_str, *handle_action_str; + + assert_se(data = handle_action_lookup(action)); + + assert_se(handle_action_str = handle_action_to_string(action)); + assert_se(sleep_operation_str = sleep_operation_to_string(data->sleep_operation)); + + assert_se(streq(handle_action_str, sleep_operation_str)); + } +} + int main(int argc, char **argv) { test_setup_logging(LOG_DEBUG); @@ -16,5 +31,7 @@ int main(int argc, char **argv) { test_table(session_type, SESSION_TYPE); test_table(user_state, USER_STATE); + test_sleep_handle_action(); + return EXIT_SUCCESS; } diff --git a/src/login/user-runtime-dir.c b/src/login/user-runtime-dir.c index ad04b04..575f8eb 100644 --- a/src/login/user-runtime-dir.c +++ b/src/login/user-runtime-dir.c @@ -67,7 +67,7 @@ static int user_mkdir_runtime_path( if (r < 0) return log_error_errno(r, "Failed to create /run/user: %m"); - if (path_is_mount_point(runtime_path, NULL, 0) > 0) + if (path_is_mount_point(runtime_path) > 0) log_debug("%s is already a mount point", runtime_path); else { char options[sizeof("mode=0700,uid=,gid=,size=,nr_inodes=,smackfsroot=*") @@ -190,8 +190,7 @@ static int do_umount(const char *user) { static int run(int argc, char *argv[]) { int r; - log_parse_environment(); - log_open(); + log_setup(); if (argc != 3) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), diff --git a/src/machine-id-setup/machine-id-setup-main.c b/src/machine-id-setup/machine-id-setup-main.c index 59aad98..6f98f74 100644 --- a/src/machine-id-setup/machine-id-setup-main.c +++ b/src/machine-id-setup/machine-id-setup-main.c @@ -139,8 +139,7 @@ static int run(int argc, char *argv[]) { _cleanup_(umount_and_freep) char *mounted_dir = NULL; int r; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -156,7 +155,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_GROWFS, + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index aa4525d..d8068cd 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -50,11 +50,8 @@ int bus_image_method_remove( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-images", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -121,11 +118,8 @@ int bus_image_method_rename( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-images", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -133,9 +127,17 @@ int bus_image_method_rename( if (r == 0) return 1; /* Will call us back */ + /* The image is cached with its name, hence it is necessary to remove from the cache before renaming. */ + assert_se(hashmap_remove_value(m->image_cache, image->name, image)); + r = image_rename(image, new_name); - if (r < 0) + if (r < 0) { + image_unref(image); return r; + } + + /* Then save the object again in the cache. */ + assert_se(hashmap_put(m->image_cache, image->name, image) > 0); return sd_bus_reply_method_return(message, NULL); } @@ -173,11 +175,8 @@ int bus_image_method_clone( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-images", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -240,11 +239,8 @@ int bus_image_method_mark_read_only( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-images", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -285,11 +281,8 @@ int bus_image_method_set_limit( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-images", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -393,30 +386,17 @@ static int image_flush_cache(sd_event_source *s, void *userdata) { return 0; } -static int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - Image *image = NULL; - const char *p; +int manager_acquire_image(Manager *m, const char *name, Image **ret) { int r; - assert(bus); - assert(path); - assert(interface); - assert(found); + assert(m); + assert(name); - p = startswith(path, "/org/freedesktop/machine1/image/"); - if (!p) + Image *existing = hashmap_get(m->image_cache, name); + if (existing) { + if (ret) + *ret = existing; return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - image = hashmap_get(m->image_cache, e); - if (image) { - *found = image; - return 1; } if (!m->image_cache_defer_event) { @@ -433,19 +413,49 @@ static int image_object_find(sd_bus *bus, const char *path, const char *interfac if (r < 0) return r; - r = image_find(IMAGE_MACHINE, e, NULL, &image); - if (r == -ENOENT) - return 0; + _cleanup_(image_unrefp) Image *image = NULL; + r = image_find(IMAGE_MACHINE, name, NULL, &image); if (r < 0) return r; image->userdata = m; r = hashmap_ensure_put(&m->image_cache, &image_hash_ops, image->name, image); - if (r < 0) { - image_unref(image); + if (r < 0) + return r; + + if (ret) + *ret = image; + + TAKE_PTR(image); + return 0; +} + +static int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + Image *image; + const char *p; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + p = startswith(path, "/org/freedesktop/machine1/image/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + r = manager_acquire_image(m, e, &image); + if (r == -ENOENT) + return 0; + if (r < 0) return r; - } *found = image; return 1; diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h index 4b00203..0c4fab1 100644 --- a/src/machine/image-dbus.h +++ b/src/machine/image-dbus.h @@ -2,10 +2,12 @@ #pragma once #include "bus-object.h" +#include "discover-image.h" #include "machined.h" extern const BusObjectImplementation image_object; +int manager_acquire_image(Manager *m, const char *name, Image **ret); char *image_bus_path(const char *name); int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c index 4620f32..a4f04c0 100644 --- a/src/machine/machine-dbus.c +++ b/src/machine/machine-dbus.c @@ -73,11 +73,8 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu r = bus_verify_polkit_async( message, - CAP_KILL, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -106,11 +103,8 @@ int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus r = bus_verify_polkit_async( message, - CAP_KILL, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -157,11 +151,8 @@ int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_erro r = bus_verify_polkit_async( message, - CAP_KILL, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -241,7 +232,12 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd if (streq(us, them)) return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); - r = namespace_open(m->leader.pid, NULL, NULL, &netns_fd, NULL, NULL); + r = namespace_open(m->leader.pid, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + &netns_fd, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); if (r < 0) return r; @@ -351,6 +347,27 @@ int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd return sd_bus_send(NULL, reply, NULL); } +int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Machine *m = ASSERT_PTR(userdata); + int r; + + assert(message); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + if (!m->ssh_address || !m->ssh_private_key_path) + return -ENOENT; + + r = sd_bus_message_append(reply, "ss", m->ssh_address, m->ssh_private_key_path); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + #define EXIT_NOT_FOUND 2 int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { @@ -375,7 +392,12 @@ int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, s _cleanup_fclose_ FILE *f = NULL; pid_t child; - r = namespace_open(m->leader.pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd); + r = namespace_open(m->leader.pid, + &pidns_fd, + &mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + &root_fd); if (r < 0) return r; @@ -449,11 +471,8 @@ int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_ r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -541,11 +560,8 @@ int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bu r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -656,11 +672,8 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -861,11 +874,8 @@ int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bu r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -949,11 +959,8 @@ int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_erro r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -1070,11 +1077,8 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->manager->polkit_registry, error); if (r < 0) @@ -1096,7 +1100,12 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda _cleanup_close_pair_ int pair[2] = EBADF_PAIR; pid_t child; - r = namespace_open(m->leader.pid, NULL, &mntns_fd, NULL, NULL, &root_fd); + r = namespace_open(m->leader.pid, + /* ret_pidns_fd = */ NULL, + &mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + &root_fd); if (r < 0) return r; @@ -1273,6 +1282,9 @@ static const sd_bus_vtable machine_vtable[] = { SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("VSockCID", "u", NULL, offsetof(Machine, vsock_cid), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SSHAddress", "s", NULL, offsetof(Machine, ssh_address), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SSHPrivateKeyPath", "s", NULL, offsetof(Machine, ssh_private_key_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), SD_BUS_METHOD("Terminate", @@ -1290,6 +1302,11 @@ static const sd_bus_vtable machine_vtable[] = { SD_BUS_RESULT("a(iay)", addresses), bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetSSHInfo", + SD_BUS_NO_ARGS, + SD_BUS_RESULT("s", ssh_address, "s", ssh_private_key_path), + bus_machine_method_get_ssh_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetOSRelease", SD_BUS_NO_ARGS, SD_BUS_RESULT("a{ss}", fields), diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h index a013345..fccad71 100644 --- a/src/machine/machine-dbus.h +++ b/src/machine/machine-dbus.h @@ -19,6 +19,7 @@ int bus_machine_method_unregister(sd_bus_message *message, void *userdata, sd_bu int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c new file mode 100644 index 0000000..968da3c --- /dev/null +++ b/src/machine/machine-varlink.c @@ -0,0 +1,173 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-id128.h" + +#include "hostname-util.h" +#include "json.h" +#include "machine-varlink.h" +#include "machine.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "varlink.h" + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_machine_class, MachineClass, machine_class_from_string); + +static int machine_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **m = ASSERT_PTR(userdata); + const char *hostname; + int r; + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + hostname = json_variant_string(variant); + if (!hostname_is_valid(hostname, /* flags= */ 0)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid machine name"); + + r = free_and_strdup(m, hostname); + if (r < 0) + return json_log_oom(variant, flags); + + return 0; +} + +static int machine_leader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + PidRef *leader = ASSERT_PTR(userdata); + _cleanup_(pidref_done) PidRef temp = PIDREF_NULL; + uint64_t k; + int r; + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + + k = json_variant_unsigned(variant); + if (k > PID_T_MAX || !pid_is_valid(k)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid PID.", strna(name)); + + if (k == 1) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid leader PID.", strna(name)); + + r = pidref_set_pid(&temp, k); + if (r < 0) + return json_log(variant, flags, r, "Failed to pin process " PID_FMT ": %m", leader->pid); + + pidref_done(leader); + + *leader = TAKE_PIDREF(temp); + + return 0; +} + +static int machine_ifindices(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + Machine *m = ASSERT_PTR(userdata); + _cleanup_free_ int *netif = NULL; + size_t n_netif, k = 0; + + assert(variant); + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + n_netif = json_variant_elements(variant); + + netif = new(int, n_netif); + if (!netif) + return json_log_oom(variant, flags); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + uint64_t b; + + if (!json_variant_is_unsigned(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name)); + + b = json_variant_unsigned(i); + if (b > INT_MAX || b <= 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid network interface index %"PRIu64, b); + + netif[k++] = (int) b; + } + assert(k == n_netif); + + free_and_replace(m->netif, netif); + m->n_netif = n_netif; + + return 0; +} + +static int machine_cid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + unsigned cid, *c = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + cid = json_variant_unsigned(variant); + if (!VSOCK_CID_IS_REGULAR(cid)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a regular VSOCK CID.", strna(name)); + + *c = cid; + + return 0; +} + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(machine_freep) Machine *machine = NULL; + int r; + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), JSON_MANDATORY }, + { "id", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(Machine, id), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, service), 0 }, + { "class", JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), JSON_MANDATORY }, + { "leader", JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, + { "rootDirectory", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, root_directory), 0 }, + { "ifIndices", JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, + { "vSockCid", JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, + { "sshAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, ssh_address), JSON_SAFE }, + { "sshPrivateKeyPath", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, ssh_private_key_path), 0 }, + {} + }; + + r = machine_new(_MACHINE_CLASS_INVALID, NULL, &machine); + if (r < 0) + return r; + + r = varlink_dispatch(link, parameters, dispatch_table, machine); + if (r != 0) + return r; + + if (!pidref_is_set(&machine->leader)) { + r = varlink_get_peer_pidref(link, &machine->leader); + if (r < 0) + return r; + } + + r = machine_link(manager, machine); + if (r == -EEXIST) + return varlink_error(link, "io.systemd.Machine.MachineExists", NULL); + if (r < 0) + return r; + + r = cg_pidref_get_unit(&machine->leader, &machine->unit); + if (r < 0) + return r; + + r = machine_start(machine, NULL, NULL); + if (r < 0) + return r; + + /* the manager will free this machine */ + TAKE_PTR(machine); + + return varlink_reply(link, NULL); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h new file mode 100644 index 0000000..ce4ec54 --- /dev/null +++ b/src/machine/machine-varlink.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink.h" + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/machine/machine.c b/src/machine/machine.c index 44ff5c1..0eb9a55 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -27,23 +27,21 @@ #include "path-util.h" #include "process-util.h" #include "serialize.h" +#include "socket-util.h" #include "special.h" #include "stdio-util.h" #include "string-table.h" +#include "string-util.h" #include "terminal-util.h" #include "tmpfile-util.h" +#include "uid-range.h" #include "unit-name.h" #include "user-util.h" -DEFINE_TRIVIAL_CLEANUP_FUNC(Machine*, machine_free); - -int machine_new(Manager *manager, MachineClass class, const char *name, Machine **ret) { +int machine_new(MachineClass class, const char *name, Machine **ret) { _cleanup_(machine_freep) Machine *m = NULL; - int r; - assert(manager); assert(class < _MACHINE_CLASS_MAX); - assert(name); assert(ret); /* Passing class == _MACHINE_CLASS_INVALID here is fine. It @@ -56,27 +54,46 @@ int machine_new(Manager *manager, MachineClass class, const char *name, Machine *m = (Machine) { .leader = PIDREF_NULL, + .vsock_cid = VMADDR_CID_ANY, }; - m->name = strdup(name); - if (!m->name) - return -ENOMEM; - - if (class != MACHINE_HOST) { - m->state_file = path_join("/run/systemd/machines", m->name); - if (!m->state_file) + if (name) { + m->name = strdup(name); + if (!m->name) return -ENOMEM; } m->class = class; - r = hashmap_put(manager->machines, m->name, m); + *ret = TAKE_PTR(m); + return 0; +} + +int machine_link(Manager *manager, Machine *machine) { + int r; + + assert(manager); + assert(machine); + + if (machine->manager) + return -EEXIST; + if (!machine->name) + return -EINVAL; + + if (machine->class != MACHINE_HOST) { + char *temp = path_join("/run/systemd/machines", machine->name); + if (!temp) + return -ENOMEM; + + free_and_replace(machine->state_file, temp); + } + + r = hashmap_put(manager->machines, machine->name, machine); if (r < 0) return r; - m->manager = manager; + machine->manager = manager; - *ret = TAKE_PTR(m); return 0; } @@ -87,30 +104,36 @@ Machine* machine_free(Machine *m) { while (m->operations) operation_free(m->operations); - if (m->in_gc_queue) + if (m->in_gc_queue) { + assert(m->manager); LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); + } - machine_release_unit(m); - - free(m->scope_job); + if (m->manager) { + machine_release_unit(m); - (void) hashmap_remove(m->manager->machines, m->name); + (void) hashmap_remove(m->manager->machines, m->name); - if (m->manager->host_machine == m) - m->manager->host_machine = NULL; + if (m->manager->host_machine == m) + m->manager->host_machine = NULL; + } if (pidref_is_set(&m->leader)) { - (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader.pid), m); + if (m->manager) + (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader.pid), m); pidref_done(&m->leader); } sd_bus_message_unref(m->create_message); free(m->name); + free(m->scope_job); free(m->state_file); free(m->service); free(m->root_directory); free(m->netif); + free(m->ssh_address); + free(m->ssh_private_key_path); return mfree(m); } @@ -339,10 +362,12 @@ int machine_load(Machine *m) { static int machine_start_scope( Machine *machine, + bool allow_pidfd, sd_bus_message *more_properties, sd_bus_error *error) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; _cleanup_free_ char *escaped = NULL, *unit = NULL; const char *description; int r; @@ -384,7 +409,7 @@ static int machine_start_scope( if (r < 0) return r; - r = bus_append_scope_pidref(m, &machine->leader); + r = bus_append_scope_pidref(m, &machine->leader, allow_pidfd); if (r < 0) return r; @@ -410,9 +435,16 @@ static int machine_start_scope( if (r < 0) return r; - r = sd_bus_call(NULL, m, 0, error, &reply); - if (r < 0) - return r; + r = sd_bus_call(NULL, m, 0, &e, &reply); + if (r < 0) { + /* If this failed with a property we couldn't write, this is quite likely because the server + * doesn't support PIDFDs yet, let's try without. */ + if (allow_pidfd && + sd_bus_error_has_names(&e, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY)) + return machine_start_scope(machine, /* allow_pidfd = */ false, more_properties, error); + + return sd_bus_error_move(error, &e); + } machine->unit = TAKE_PTR(unit); machine->referenced = true; @@ -432,7 +464,7 @@ static int machine_ensure_scope(Machine *m, sd_bus_message *properties, sd_bus_e assert(m->class != MACHINE_HOST); if (!m->unit) { - r = machine_start_scope(m, properties, error); + r = machine_start_scope(m, /* allow_pidfd = */ true, properties, error); if (r < 0) return log_error_errno(r, "Failed to start machine scope: %s", bus_error_message(error, r)); } @@ -643,8 +675,9 @@ void machine_release_unit(Machine *m) { r = manager_unref_unit(m->manager, m->unit, &error); if (r < 0) - log_warning_errno(r, "Failed to drop reference to machine scope, ignoring: %s", - bus_error_message(&error, r)); + log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to drop reference to machine scope, ignoring: %s", + bus_error_message(&error, r)); m->referenced = false; } @@ -658,7 +691,7 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { uid_t uid_base, uid_shift, uid_range; gid_t gid_base, gid_shift, gid_range; _cleanup_fclose_ FILE *f = NULL; - int k, r; + int r; assert(m); assert(ret); @@ -690,14 +723,9 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { } /* Read the first line. There's at least one. */ - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range); - if (k != 3) { - if (ferror(f)) - return errno_or_else(EIO); - - return -EBADMSG; - } + r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); + if (r < 0) + return r; /* Not a mapping starting at 0? Then it's a complex mapping we can't expose here. */ if (uid_base != 0) @@ -722,13 +750,12 @@ int machine_get_uid_shift(Machine *m, uid_t *ret) { /* Read the first line. There's at least one. */ errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range); - if (k != 3) { - if (ferror(f)) - return errno_or_else(EIO); - + r = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range); + if (r == EOF) + return errno_or_else(ENOMSG); + assert(r >= 0); + if (r != 3) return -EBADMSG; - } /* If there's more than one line, then we don't support this file. */ r = safe_fgetc(f, NULL); @@ -757,6 +784,7 @@ static int machine_owns_uid_internal( _cleanup_fclose_ FILE *f = NULL; const char *p; + int r; /* This is a generic implementation for both uids and gids, under the assumptions they have the same types and semantics. */ assert_cc(sizeof(uid_t) == sizeof(gid_t)); @@ -778,18 +806,12 @@ static int machine_owns_uid_internal( for (;;) { uid_t uid_base, uid_shift, uid_range, converted; - int k; - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) + r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); + if (r == -ENOMSG) break; - if (k != 3) { - if (ferror(f)) - return errno_or_else(EIO); - - return -EIO; - } + if (r < 0) + return r; /* The private user namespace is disabled, ignoring. */ if (uid_shift == 0) @@ -831,6 +853,7 @@ static int machine_translate_uid_internal( _cleanup_fclose_ FILE *f = NULL; const char *p; + int r; /* This is a generic implementation for both uids and gids, under the assumptions they have the same types and semantics. */ assert_cc(sizeof(uid_t) == sizeof(gid_t)); @@ -850,18 +873,12 @@ static int machine_translate_uid_internal( for (;;) { uid_t uid_base, uid_shift, uid_range, converted; - int k; - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) + r = uid_map_read_one(f, &uid_base, &uid_shift, &uid_range); + if (r == -ENOMSG) break; - if (k != 3) { - if (ferror(f)) - return errno_or_else(EIO); - - return -EIO; - } + if (r < 0) + return r; if (uid < uid_base || uid >= uid_base + uid_range) continue; @@ -872,6 +889,7 @@ static int machine_translate_uid_internal( if (ret_host_uid) *ret_host_uid = converted; + return 0; } diff --git a/src/machine/machine.h b/src/machine/machine.h index 30ef93b..f8146fb 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -62,12 +62,17 @@ struct Machine { int *netif; size_t n_netif; + unsigned vsock_cid; + char *ssh_address; + char *ssh_private_key_path; + LIST_HEAD(Operation, operations); LIST_FIELDS(Machine, gc_queue); }; -int machine_new(Manager *manager, MachineClass class, const char *name, Machine **ret); +int machine_new(MachineClass class, const char *name, Machine **ret); +int machine_link(Manager *manager, Machine *machine); Machine* machine_free(Machine *m); bool machine_may_gc(Machine *m, bool drop_not_started); void machine_add_to_gc_queue(Machine *m); @@ -78,6 +83,8 @@ int machine_save(Machine *m); int machine_load(Machine *m); int machine_kill(Machine *m, KillWho who, int signo); +DEFINE_TRIVIAL_CLEANUP_FUNC(Machine*, machine_free); + void machine_release_unit(Machine *m); MachineState machine_get_state(Machine *u); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 418dd00..1b63e6d 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -15,6 +15,7 @@ #include "alloc-util.h" #include "build.h" +#include "build-path.h" #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" @@ -62,6 +63,25 @@ #include "verbs.h" #include "web-util.h" +typedef enum MachineRunner { + RUNNER_NSPAWN, + RUNNER_VMSPAWN, + _RUNNER_MAX, + _RUNNER_INVALID = -EINVAL, +} MachineRunner; + +static const char* const machine_runner_table[_RUNNER_MAX] = { + [RUNNER_NSPAWN] = "nspawn", + [RUNNER_VMSPAWN] = "vmspawn", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(machine_runner, MachineRunner); + +static const char* const machine_runner_unit_prefix_table[_RUNNER_MAX] = { + [RUNNER_NSPAWN] = "systemd-nspawn", + [RUNNER_VMSPAWN] = "systemd-vmspawn", +}; + static char **arg_property = NULL; static bool arg_all = false; static BusPrintPropertyFlags arg_print_flags = 0; @@ -81,6 +101,7 @@ static OutputMode arg_output = OUTPUT_SHORT; static bool arg_now = false; static bool arg_force = false; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static MachineRunner arg_runner = RUNNER_NSPAWN; static const char* arg_format = NULL; static const char *arg_uid = NULL; static char **arg_setenv = NULL; @@ -100,19 +121,14 @@ static OutputFlags get_output_flags(void) { static int call_get_os_release(sd_bus *bus, const char *method, const char *name, const char *query, ...) { _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 *k, *v, **query_res = NULL; - size_t count = 0, awaited_args = 0; va_list ap; int r; assert(bus); + assert(method); assert(name); assert(query); - NULSTR_FOREACH(iter, query) - awaited_args++; - query_res = newa0(const char *, awaited_args); - r = bus_call_method(bus, bus_machine_mgr, method, &error, &reply, "s", name); if (r < 0) return log_debug_errno(r, "Failed to call '%s()': %s", method, bus_error_message(&error, r)); @@ -121,14 +137,23 @@ static int call_get_os_release(sd_bus *bus, const char *method, const char *name if (r < 0) return bus_log_parse_error(r); + const char **res; + size_t n_fields = 0; + + NULSTR_FOREACH(i, query) + n_fields++; + + res = newa0(const char*, n_fields); + + const char *k, *v; while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { - count = 0; - NULSTR_FOREACH(iter, query) { - if (streq(k, iter)) { - query_res[count] = v; + size_t c = 0; + NULSTR_FOREACH(i, query) { + if (streq(i, k)) { + res[c] = v; break; } - count++; + c++; } } if (r < 0) @@ -138,24 +163,17 @@ static int call_get_os_release(sd_bus *bus, const char *method, const char *name if (r < 0) return bus_log_parse_error(r); + r = 0; + va_start(ap, query); - for (count = 0; count < awaited_args; count++) { - char *val, **out; - - out = va_arg(ap, char **); - assert(out); - if (query_res[count]) { - val = strdup(query_res[count]); - if (!val) { - va_end(ap); - return -ENOMEM; - } - *out = val; - } + FOREACH_ARRAY(i, res, n_fields) { + r = strdup_to(va_arg(ap, char**), *i); + if (r < 0) + break; } va_end(ap); - return 0; + return r; } static int call_get_addresses( @@ -184,12 +202,12 @@ static int call_get_addresses( addresses = strdup(prefix); if (!addresses) return log_oom(); - prefix = ""; r = sd_bus_message_enter_container(reply, 'a', "(iay)"); if (r < 0) return bus_log_parse_error(r); + prefix = ""; while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { int family; const void *a; @@ -235,7 +253,7 @@ static int show_table(Table *table, const char *word) { assert(table); assert(word); - if (table_get_rows(table) > 1 || OUTPUT_MODE_IS_JSON(arg_output)) { + if (!table_isempty(table) || OUTPUT_MODE_IS_JSON(arg_output)) { r = table_set_sort(table, (size_t) 0); if (r < 0) return table_log_sort_error(r); @@ -251,10 +269,10 @@ static int show_table(Table *table, const char *word) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); - else + if (table_isempty(table)) printf("No %s.\n", word); + else + printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); } return 0; @@ -587,16 +605,15 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { show_journal_by_unit( stdout, i->unit, - NULL, + /* namespace = */ NULL, arg_output, - 0, + /* n_columns = */ 0, i->timestamp.monotonic, arg_lines, - 0, get_output_flags() | OUTPUT_BEGIN_NEWLINE, SD_JOURNAL_LOCAL_ONLY, - true, - NULL); + /* system_unit = */ true, + /* ellipsized = */ NULL); } } @@ -747,22 +764,17 @@ static int print_image_hostname(sd_bus *bus, const char *name) { static int print_image_machine_id(sd_bus *bus, const char *name) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_id128_t id = SD_ID128_NULL; - const void *p; - size_t size; + sd_id128_t id; int r; r = bus_call_method(bus, bus_machine_mgr, "GetImageMachineID", NULL, &reply, "s", name); if (r < 0) return r; - r = sd_bus_message_read_array(reply, 'y', &p, &size); + r = bus_message_read_id128(reply, &id); if (r < 0) return r; - if (size == sizeof(sd_id128_t)) - memcpy(&id, p, size); - if (!sd_id128_is_null(id)) printf(" Machine ID: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); @@ -1052,6 +1064,12 @@ static int kill_machine(int argc, char *argv[], void *userdata) { } static int reboot_machine(int argc, char *argv[], void *userdata) { + if (arg_runner == RUNNER_VMSPAWN) + return log_error_errno( + SYNTHETIC_ERRNO(EOPNOTSUPP), + "%s only support supported for --runner=nspawn", + streq(argv[0], "reboot") ? "Reboot" : "Restart"); + arg_kill_whom = "leader"; arg_signal = SIGINT; /* sysvinit + systemd */ @@ -1201,7 +1219,7 @@ static int process_forward(sd_event *event, PTYForward **forward, int master, PT assert(master >= 0); assert(name); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT) >= 0); if (!arg_quiet) { if (streq(name, ".host")) @@ -1462,6 +1480,9 @@ static int edit_settings(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Edit is only supported on the host machine."); + if (arg_runner == RUNNER_VMSPAWN) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Edit is only supported for --runner=nspawn"); + r = mac_init(); if (r < 0) return r; @@ -1525,6 +1546,9 @@ static int cat_settings(int argc, char *argv[], void *userdata) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cat is only supported on the host machine."); + if (arg_runner == RUNNER_VMSPAWN) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Cat is only supported for --runner=nspawn"); + pager_open(arg_pager_flags); STRV_FOREACH(name, strv_skip(argv, 1)) { @@ -1682,12 +1706,13 @@ static int make_service_name(const char *name, char **ret) { assert(name); assert(ret); + assert(arg_runner >= 0 && arg_runner < (MachineRunner) ELEMENTSOF(machine_runner_unit_prefix_table)); if (!hostname_is_valid(name, 0)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid machine name %s.", name); - r = unit_name_build("systemd-nspawn", name, ".service", ret); + r = unit_name_build(machine_runner_unit_prefix_table[arg_runner], name, ".service", ret); if (r < 0) return log_error_errno(r, "Failed to build unit name: %m"); @@ -1846,633 +1871,6 @@ static int enable_machine(int argc, char *argv[], void *userdata) { return 0; } -static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *line; - unsigned priority; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "us", &priority, &line); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) - return 0; - - if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) - return 0; - - log_full(priority, "%s", line); - return 0; -} - -static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *path, *result; - uint32_t id; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "uos", &id, &path, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, path)) - return 0; - - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); - return 0; -} - -static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - assert(si); - - if (!arg_quiet) - log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); - - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = 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_event_unrefp) sd_event* event = NULL; - const char *path = NULL; - uint32_t id; - int r; - - assert(bus); - assert(m); - - polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = bus_match_signal_async( - bus, - &slot_job_removed, - bus_import_mgr, - "TransferRemoved", - match_transfer_removed, NULL, &path); - if (r < 0) - return log_error_errno(r, "Failed to request match: %m"); - - r = sd_bus_match_signal_async( - bus, - &slot_log_message, - "org.freedesktop.import1", - NULL, - "org.freedesktop.import1.Transfer", - "LogMessage", - match_log_message, NULL, &path); - if (r < 0) - return log_error_errno(r, "Failed to request match: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to transfer image: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "uo", &id, &path); - if (r < 0) - return bus_log_parse_error(r); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - if (!arg_quiet) - log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); - - (void) sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); - (void) sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - return -r; -} - -static int import_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL, *fn = NULL; - const char *local = NULL, *path = NULL; - _cleanup_close_ int fd = -EBADF; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - if (argc >= 2) - path = empty_or_dash_to_null(argv[1]); - - if (argc >= 3) - local = empty_or_dash_to_null(argv[2]); - else if (path) { - r = path_extract_filename(path, &fn); - if (r < 0) - return log_error_errno(r, "Cannot extract container name from filename: %m"); - if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), - "Path '%s' refers to directory, but we need a regular file: %m", path); - - local = fn; - } - if (!local) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Need either path or local name."); - - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable machine name.", - local); - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int import_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL, *fn = NULL; - const char *local = NULL, *path = NULL; - _cleanup_close_ int fd = -EBADF; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - if (argc >= 2) - path = empty_or_dash_to_null(argv[1]); - - if (argc >= 3) - local = empty_or_dash_to_null(argv[2]); - else if (path) { - r = path_extract_filename(path, &fn); - if (r < 0) - return log_error_errno(r, "Cannot extract container name from filename: %m"); - if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), - "Path '%s' refers to directory, but we need a regular file: %m", path); - - local = fn; - } - if (!local) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Need either path or local name."); - - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable machine name.", - local); - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int import_fs(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - const char *local = NULL, *path = NULL; - _cleanup_free_ char *fn = NULL; - _cleanup_close_ int fd = -EBADF; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - if (argc >= 2) - path = empty_or_dash_to_null(argv[1]); - - if (argc >= 3) - local = empty_or_dash_to_null(argv[2]); - else if (path) { - r = path_extract_filename(path, &fn); - if (r < 0) - return log_error_errno(r, "Cannot extract container name from filename: %m"); - - local = fn; - } - if (!local) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Need either path or local name."); - - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable machine name.", - local); - - if (path) { - fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open directory '%s': %m", path); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ImportFileSystem"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static void determine_compression_from_filename(const char *p) { - if (arg_format) - return; - - if (!p) - return; - - if (endswith(p, ".xz")) - arg_format = "xz"; - else if (endswith(p, ".gz")) - arg_format = "gzip"; - else if (endswith(p, ".bz2")) - arg_format = "bzip2"; -} - -static int export_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -EBADF; - const char *local = NULL, *path = NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - local = argv[1]; - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Machine name %s is not valid.", local); - - if (argc >= 3) - path = argv[2]; - path = empty_or_dash_to_null(path); - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int export_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -EBADF; - const char *local = NULL, *path = NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - local = argv[1]; - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Machine name %s is not valid.", local); - - if (argc >= 3) - path = argv[2]; - path = empty_or_dash_to_null(path); - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "ExportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable machine name.", - local); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - remote = argv[1]; - if (!http_url_is_valid(remote) && !file_url_is_valid(remote)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "URL '%s' is not valid.", remote); - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - local = empty_or_dash_to_null(local); - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!hostname_is_valid(local, 0)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Local name %s is not a suitable machine name.", - local); - } - - r = bus_message_new_method_call(bus, &m, bus_import_mgr, "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -typedef struct TransferInfo { - uint32_t id; - const char *type; - const char *remote; - const char *local; - double progress; -} TransferInfo; - -static int compare_transfer_info(const TransferInfo *a, const TransferInfo *b) { - return strcmp(a->local, b->local); -} - -static int list_transfers(int argc, char *argv[], void *userdata) { - size_t max_type = STRLEN("TYPE"), max_local = STRLEN("LOCAL"), max_remote = STRLEN("REMOTE"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ TransferInfo *transfers = NULL; - const char *type, *remote, *local; - sd_bus *bus = userdata; - uint32_t id, max_id = 0; - size_t n_transfers = 0; - double progress; - int r; - - pager_open(arg_pager_flags); - - r = bus_call_method(bus, bus_import_mgr, "ListTransfers", &error, &reply, NULL); - if (r < 0) - return log_error_errno(r, "Could not get transfers: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, NULL)) > 0) { - size_t l; - - if (!GREEDY_REALLOC(transfers, n_transfers + 1)) - return log_oom(); - - transfers[n_transfers].id = id; - transfers[n_transfers].type = type; - transfers[n_transfers].remote = remote; - transfers[n_transfers].local = local; - transfers[n_transfers].progress = progress; - - l = strlen(type); - if (l > max_type) - max_type = l; - - l = strlen(remote); - if (l > max_remote) - max_remote = l; - - l = strlen(local); - if (l > max_local) - max_local = l; - - if (id > max_id) - max_id = id; - - n_transfers++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - typesafe_qsort(transfers, n_transfers, compare_transfer_info); - - if (arg_legend && n_transfers > 0) - printf("%-*s %-*s %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", - (int) 7, "PERCENT", - (int) max_type, "TYPE", - (int) max_local, "LOCAL", - (int) max_remote, "REMOTE"); - - for (size_t j = 0; j < n_transfers; j++) - - if (transfers[j].progress < 0) - printf("%*" PRIu32 " %*s %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, - (int) 7, "n/a", - (int) max_type, transfers[j].type, - (int) max_local, transfers[j].local, - (int) max_remote, transfers[j].remote); - else - printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, - (int) 6, (unsigned) (transfers[j].progress * 100), - (int) max_type, transfers[j].type, - (int) max_local, transfers[j].local, - (int) max_remote, transfers[j].remote); - - if (arg_legend) { - if (n_transfers > 0) - printf("\n%zu transfers listed.\n", n_transfers); - else - printf("No transfers.\n"); - } - - return 0; -} - -static int cancel_transfer(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = ASSERT_PTR(userdata); - int r; - - polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - - for (int i = 1; i < argc; i++) { - uint32_t id; - - r = safe_atou32(argv[i], &id); - if (r < 0) - return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); - - r = bus_call_method(bus, bus_import_mgr, "CancelTransfer", &error, NULL, "u", id); - if (r < 0) - return log_error_errno(r, "Could not cancel transfer: %s", bus_error_message(&error, r)); - } - - return 0; -} - static int set_limit(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; sd_bus *bus = userdata; @@ -2557,6 +1955,52 @@ static int clean_images(int argc, char *argv[], void *userdata) { return 0; } +static int chainload_importctl(int argc, char *argv[]) { + int r; + + log_notice("The 'machinectl %1$s' command has been replaced by 'importctl -m %1$s'. Redirecting invocation.", argv[optind]); + + _cleanup_strv_free_ char **c = + strv_new("importctl", "--class=machine"); + if (!c) + return log_oom(); + + if (FLAGS_SET(arg_pager_flags, PAGER_DISABLE)) + if (strv_extend(&c, "--no-pager") < 0) + return log_oom(); + if (!arg_legend) + if (strv_extend(&c, "--no-legend") < 0) + return log_oom(); + if (arg_read_only) + if (strv_extend(&c, "--read-only") < 0) + return log_oom(); + if (arg_force) + if (strv_extend(&c, "--force") < 0) + return log_oom(); + if (arg_quiet) + if (strv_extend(&c, "--quiet") < 0) + return log_oom(); + if (!arg_ask_password) + if (strv_extend(&c, "--no-ask-password") < 0) + return log_oom(); + if (strv_extend_many(&c, "--verify", import_verify_to_string(arg_verify)) < 0) + return log_oom(); + if (arg_format) + if (strv_extend_many(&c, "--format", arg_format) < 0) + return log_oom(); + + if (strv_extend_strv(&c, argv + optind, /* filter_duplicates= */ false) < 0) + return log_oom(); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = strv_join(c, " "); + log_debug("Chainloading: %s", joined); + } + + r = invoke_callout_binary(BINDIR "/importctl", c); + return log_error_errno(r, "Failed to invoke 'importctl': %m"); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -2601,16 +2045,6 @@ static int help(int argc, char *argv[], void *userdata) { " remove NAME... Remove an image\n" " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n" " clean Remove hidden (or all) images\n" - "\n%3$sImage Transfer Commands:%4$s\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " import-fs DIRECTORY [NAME] Import a local directory container image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of downloads in progress\n" - " cancel-transfer Cancel a download\n" "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" @@ -2620,15 +2054,16 @@ static int help(int argc, char *argv[], void *userdata) { " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" " -p --property=NAME Show only properties by this name\n" + " --value When showing properties, only print the value\n" + " -P NAME Equivalent to --value --property=NAME\n" " -q --quiet Suppress output\n" " -a --all Show all properties, including empty ones\n" - " --value When showing properties, only print the value\n" " -l --full Do not ellipsize output\n" " --kill-whom=WHOM Whom to send signal to\n" " -s --signal=SIGNAL Which signal to send\n" " --uid=USER Specify user ID to invoke shell as\n" " -E --setenv=VAR[=VALUE] Add an environment variable for shell\n" - " --read-only Create read-only bind mount\n" + " --read-only Create read-only bind mount or clone\n" " --mkdir Create directory before bind mounting, if missing\n" " -n --lines=INTEGER Number of journal entries to show\n" " --max-addresses=INTEGER Number of internet addresses to show at most\n" @@ -2637,11 +2072,11 @@ static int help(int argc, char *argv[], void *userdata) { " short-monotonic, short-unix, short-delta,\n" " json, json-pretty, json-sse, json-seq, cat,\n" " verbose, export, with-unit)\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --force Download image even if already exists\n" + " --force Replace target file when copying, if necessary\n" " --now Start or power off container after enabling or\n" " disabling it\n" + " --runner=RUNNER Select between nspawn and vmspawn as the runner\n" + " -V Short for --runner=vmspawn\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -2665,6 +2100,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_MKDIR, ARG_NO_ASK_PASSWORD, ARG_VERIFY, + ARG_RUNNER, ARG_NOW, ARG_FORCE, ARG_FORMAT, @@ -2676,8 +2112,8 @@ static int parse_argv(int argc, char *argv[]) { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, ARG_VERSION }, { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, { "value", no_argument, NULL, ARG_VALUE }, + { "all", no_argument, NULL, 'a' }, { "full", no_argument, NULL, 'l' }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, @@ -2692,6 +2128,7 @@ static int parse_argv(int argc, char *argv[]) { { "output", required_argument, NULL, 'o' }, { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, { "verify", required_argument, NULL, ARG_VERIFY }, + { "runner", required_argument, NULL, ARG_RUNNER }, { "now", no_argument, NULL, ARG_NOW }, { "force", no_argument, NULL, ARG_FORCE }, { "format", required_argument, NULL, ARG_FORMAT }, @@ -2712,7 +2149,7 @@ static int parse_argv(int argc, char *argv[]) { optind = 0; for (;;) { - static const char option_string[] = "-hp:als:H:M:qn:o:E:"; + static const char option_string[] = "-hp:P:als:H:M:qn:o:E:V"; c = getopt_long(argc, argv, option_string + reorder, options, NULL); if (c < 0) @@ -2740,7 +2177,7 @@ static int parse_argv(int argc, char *argv[]) { /* If we already found the "shell" verb on the command line, and now found the next * non-option argument, then this is the machine name and we should stop processing * further arguments. */ - optind --; /* don't process this argument, go one step back */ + optind--; /* don't process this argument, go one step back */ goto done; } if (streq(optarg, "shell")) @@ -2773,14 +2210,20 @@ static int parse_argv(int argc, char *argv[]) { return version(); case 'p': + case 'P': r = strv_extend(&arg_property, optarg); if (r < 0) return log_oom(); - /* If the user asked for a particular - * property, show it to them, even if it is - * empty. */ + /* If the user asked for a particular property, show it to them, even if empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + + if (c == 'p') + break; + _fallthrough_; + + case ARG_VALUE: + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; case 'a': @@ -2788,10 +2231,6 @@ static int parse_argv(int argc, char *argv[]) { arg_all = true; break; - case ARG_VALUE: - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); - break; - case 'l': arg_full = true; break; @@ -2873,6 +2312,18 @@ static int parse_argv(int argc, char *argv[]) { arg_verify = r; break; + case 'V': + arg_runner = RUNNER_VMSPAWN; + break; + + case ARG_RUNNER: + r = machine_runner_from_string(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --runner= setting: %s", optarg); + + arg_runner = r; + break; + case ARG_NOW: arg_now = true; break; @@ -2945,6 +2396,7 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, { "terminate", 2, VERB_ANY, 0, terminate_machine }, { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "restart", 2, VERB_ANY, 0, reboot_machine }, /* Convenience alias */ { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, { "stop", 2, VERB_ANY, 0, poweroff_machine }, /* Convenience alias */ { "kill", 2, VERB_ANY, 0, kill_machine }, @@ -2962,15 +2414,6 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) { { "start", 2, VERB_ANY, 0, start_machine }, { "enable", 2, VERB_ANY, 0, enable_machine }, { "disable", 2, VERB_ANY, 0, enable_machine }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "import-fs", 2, 3, 0, import_fs }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, 0, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, { "set-limit", 2, 3, 0, set_limit }, { "clean", VERB_ANY, 1, 0, clean_images }, {} @@ -2988,13 +2431,19 @@ static int run(int argc, char *argv[]) { /* The journal merging logic potentially needs a lot of fds. */ (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE); - sigbus_install(); r = parse_argv(argc, argv); if (r <= 0) return r; + if (STRPTR_IN_SET(argv[optind], + "import-tar", "import-raw", "import-fs", + "export-tar", "export-raw", + "pull-tar", "pull-raw", + "list-transfers", "cancel-transfer")) + return chainload_importctl(argc, argv); + r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus); if (r < 0) return bus_log_connect_error(r, arg_transport); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 9fec047..944b52e 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -462,6 +462,10 @@ static int method_get_machine_addresses(sd_bus_message *message, void *userdata, return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_addresses); } +static int method_get_machine_ssh_info(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_ssh_info); +} + static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { return redirect_method_to_machine(message, userdata, error, bus_machine_method_get_os_release); } @@ -546,8 +550,8 @@ static int method_get_machine_uid_shift(sd_bus_message *message, void *userdata, } static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_error *error, sd_bus_message_handler_t method) { - _cleanup_(image_unrefp) Image* i = NULL; const char *name; + Image *i; int r; assert(message); @@ -561,13 +565,12 @@ static int redirect_method_to_image(sd_bus_message *message, Manager *m, sd_bus_ if (!image_name_is_valid(name)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - r = image_find(IMAGE_MACHINE, name, NULL, &i); + r = manager_acquire_image(m, name, &i); if (r == -ENOENT) return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); if (r < 0) return r; - i->userdata = m; return method(message, i, error); } @@ -720,11 +723,8 @@ static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_err r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -855,11 +855,8 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.machine1.manage-machines", details, - false, - UID_INVALID, &m->polkit_registry, error); if (r < 0) @@ -1073,6 +1070,11 @@ const sd_bus_vtable manager_vtable[] = { SD_BUS_RESULT("a(iay)", addresses), method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("GetMachineSSHInfo", + SD_BUS_ARGS("s", name), + SD_BUS_RESULT("s", ssh_address, "s", ssh_private_key_path), + method_get_machine_ssh_info, + SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("GetMachineOSRelease", SD_BUS_ARGS("s", name), SD_BUS_RESULT("a{ss}", fields), @@ -1504,9 +1506,13 @@ int manager_add_machine(Manager *m, const char *name, Machine **_machine) { machine = hashmap_get(m->machines, name); if (!machine) { - r = machine_new(m, _MACHINE_CLASS_INVALID, name, &machine); + r = machine_new(_MACHINE_CLASS_INVALID, name, &machine); if (r < 0) return r; + + r = machine_link(m, machine); + if (r < 0) + return 0; } if (_machine) diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 6ca98e2..0d3ae62 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-util.h" +#include "machine-varlink.h" #include "machined-varlink.h" #include "mkdir.h" #include "user-util.h" #include "varlink.h" +#include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.UserDatabase.h" typedef struct LookupParameters { @@ -378,13 +380,13 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } -int manager_varlink_init(Manager *m) { +static int manager_varlink_init_userdb(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; assert(m); - if (m->varlink_server) + if (m->varlink_userdb_server) return 0; r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); @@ -415,12 +417,64 @@ int manager_varlink_init(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - m->varlink_server = TAKE_PTR(s); + m->varlink_userdb_server = TAKE_PTR(s); + return 0; +} + +static int manager_varlink_init_machine(Manager *m) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + + if (m->varlink_machine_server) + return 0; + + r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + r = varlink_server_add_interface(s, &vl_interface_io_systemd_Machine); + if (r < 0) + return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); + + r = varlink_server_bind_method(s, "io.systemd.Machine.Register", vl_method_register); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + (void) mkdir_p("/run/systemd/machine", 0755); + + r = varlink_server_listen_address(s, "/run/systemd/machine/io.systemd.Machine", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + m->varlink_machine_server = TAKE_PTR(s); + return 0; +} + +int manager_varlink_init(Manager *m) { + int r; + + r = manager_varlink_init_userdb(m); + if (r < 0) + return r; + + r = manager_varlink_init_machine(m); + if (r < 0) + return r; + return 0; } void manager_varlink_done(Manager *m) { assert(m); - m->varlink_server = varlink_server_unref(m->varlink_server); + m->varlink_userdb_server = varlink_server_unref(m->varlink_userdb_server); + m->varlink_machine_server = varlink_server_unref(m->varlink_machine_server); } diff --git a/src/machine/machined.c b/src/machine/machined.c index 58a407d..d7087e4 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -96,7 +96,7 @@ static Manager* manager_unref(Manager *m) { sd_event_source_unref(m->nscd_cache_flush_event); #endif - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); manager_varlink_done(m); @@ -132,10 +132,14 @@ static int manager_add_host_machine(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to open reference to PID 1: %m"); - r = machine_new(m, MACHINE_HOST, ".host", &t); + r = machine_new(MACHINE_HOST, ".host", &t); if (r < 0) return log_error_errno(r, "Failed to create machine: %m"); + r = machine_link(m, t); + if (r < 0) + return log_error_errno(r, "Failed to link machine to manager: %m"); + t->leader = TAKE_PIDREF(pidref); t->id = mid; @@ -312,7 +316,10 @@ static bool check_idle(void *userdata) { if (m->operations) return false; - if (varlink_server_current_connections(m->varlink_server) > 0) + if (varlink_server_current_connections(m->varlink_userdb_server) > 0) + return false; + + if (varlink_server_current_connections(m->varlink_machine_server) > 0) return false; manager_gc(m, true); @@ -320,17 +327,6 @@ static bool check_idle(void *userdata) { return hashmap_isempty(m->machines); } -static int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.machine1", - DEFAULT_EXIT_USEC, - check_idle, m); -} - static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; int r; @@ -353,7 +349,7 @@ static int run(int argc, char *argv[]) { * make sure this check stays in. */ (void) mkdir_label("/run/systemd/machines", 0755); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) @@ -363,16 +359,20 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to fully start up daemon: %m"); - log_debug("systemd-machined running as pid "PID_FMT, getpid_cached()); r = sd_notify(false, NOTIFY_READY); if (r < 0) log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); - r = manager_run(m); + r = bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.machine1", + DEFAULT_EXIT_USEC, + check_idle, m); + if (r < 0) + return log_error_errno(r, "Failed to run main loop: %m"); - (void) sd_notify(false, NOTIFY_STOPPING); - log_debug("systemd-machined stopped as pid "PID_FMT, getpid_cached()); - return r; + return 0; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/machine/machined.h b/src/machine/machined.h index 280c32b..67abed0 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -40,7 +40,8 @@ struct Manager { sd_event_source *nscd_cache_flush_event; #endif - VarlinkServer *varlink_server; + VarlinkServer *varlink_userdb_server; + VarlinkServer *varlink_machine_server; }; int manager_add_machine(Manager *m, const char *name, Machine **_machine); diff --git a/src/machine/meson.build b/src/machine/meson.build index b3a1ffc..3150b33 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -3,6 +3,7 @@ libmachine_core_sources = files( 'image-dbus.c', 'machine-dbus.c', + 'machine-varlink.c', 'machine.c', 'machined-core.c', 'machined-dbus.c', @@ -35,9 +36,9 @@ executables += [ 'conditions' : ['ENABLE_MACHINED'], 'sources' : files('machinectl.c'), 'dependencies' : [ - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ], }, diff --git a/src/modules-load/meson.build b/src/modules-load/meson.build index 2f1decc..d6375a6 100644 --- a/src/modules-load/meson.build +++ b/src/modules-load/meson.build @@ -5,7 +5,7 @@ executables += [ 'name' : 'systemd-modules-load', 'conditions' : ['HAVE_KMOD'], 'sources' : files('modules-load.c'), - 'dependencies' : libkmod, + 'dependencies' : libkmod_cflags, }, ] diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index efca237..c1f8183 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -23,14 +23,6 @@ static const char conf_file_dirs[] = CONF_PATHS_NULSTR("modules-load.d"); STATIC_DESTRUCTOR_REGISTER(arg_proc_cmdline_modules, strv_freep); -static void systemd_kmod_log(void *data, int priority, const char *file, int line, - const char *fn, const char *format, va_list args) { - - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(priority, 0, file, line, fn, format, args); - REENABLE_WARNING; -} - static int add_modules(const char *p) { _cleanup_strv_free_ char **k = NULL; @@ -156,7 +148,7 @@ static int parse_argv(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { - _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL; + _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL; int r, k; r = parse_argv(argc, argv); @@ -171,14 +163,9 @@ static int run(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - ctx = kmod_new(NULL, NULL); - if (!ctx) { - log_error("Failed to allocate memory for kmod."); - return -ENOMEM; - } - - kmod_load_resources(ctx); - kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + r = module_setup_context(&ctx); + if (r < 0) + return log_error_errno(r, "Failed to initialize libkmod context: %m"); r = 0; diff --git a/src/mount/mount-tool.c b/src/mount/mount-tool.c index f626f07..fcebdca 100644 --- a/src/mount/mount-tool.c +++ b/src/mount/mount-tool.c @@ -665,7 +665,7 @@ static int start_transient_mount( if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return r; } @@ -774,7 +774,7 @@ static int start_transient_automount( if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return r; } @@ -936,7 +936,7 @@ static int stop_mount( if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return r; } @@ -1267,7 +1267,7 @@ static int acquire_removable(sd_device *d) { if (sd_device_get_parent(d, &d) < 0) return 0; - if (sd_device_get_subsystem(d, &v) < 0 || !streq(v, "block")) + if (!device_in_subsystem(d, "block")) return 0; } @@ -1492,9 +1492,7 @@ static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/mountfsd/io.systemd.mount-file-system.policy b/src/mountfsd/io.systemd.mount-file-system.policy new file mode 100644 index 0000000..6a151eb --- /dev/null +++ b/src/mountfsd/io.systemd.mount-file-system.policy @@ -0,0 +1,70 @@ + + + + + + + + The systemd Project + https://systemd.io + + + + + Allow mounting of file system image + Authentication is required for an application to mount a file system image. + + auth_admin_keep + auth_admin_keep + yes + + + + + + Allow mounting of untrusted file system image + Authentication is required for an application to mount a cryptographically unsigned file system image or an image whose cryptographic signature is not recognized. + + auth_admin + auth_admin + auth_admin + + + io.systemd.mount-file-system.mount-image + + + + + Allow private mounting of trusted file system image + Authentication is required for an application to privately mount a file system image or an image whose cryptographic signature is recognized. + + yes + yes + yes + + + + + Allow private mounting of untrusted file system image + Authentication is required for an application to privately mount a cryptographically unsigned file system image or an image whose cryptographic signature is not recognized. + + auth_admin + auth_admin + auth_admin + + + io.systemd.mount-file-system.mount-image-privately + + diff --git a/src/mountfsd/meson.build b/src/mountfsd/meson.build new file mode 100644 index 0000000..53fc3d3 --- /dev/null +++ b/src/mountfsd/meson.build @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +systemd_mountwork_sources = files( + 'mountwork.c', +) + +systemd_mountfsd_sources = files( + 'mountfsd.c', + 'mountfsd-manager.c', +) + +executables += [ + libexec_template + { + 'name' : 'systemd-mountfsd', + 'conditions' : ['ENABLE_MOUNTFSD'], + 'sources' : systemd_mountfsd_sources, + }, + libexec_template + { + 'name' : 'systemd-mountwork', + 'conditions' : ['ENABLE_MOUNTFSD'], + 'sources' : systemd_mountwork_sources, + }, +] + +install_data('io.systemd.mount-file-system.policy', + install_dir : polkitpolicydir) diff --git a/src/mountfsd/mountfsd-manager.c b/src/mountfsd/mountfsd-manager.c new file mode 100644 index 0000000..b05c6e8 --- /dev/null +++ b/src/mountfsd/mountfsd-manager.c @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + +#include "sd-daemon.h" + +#include "build-path.h" +#include "common-signal.h" +#include "env-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "mountfsd-manager.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "umask-util.h" + +#define LISTEN_TIMEOUT_USEC (25 * USEC_PER_SEC) + +static int start_workers(Manager *m, bool explicit_request); + +static size_t manager_current_workers(Manager *m) { + assert(m); + + return set_size(m->workers_fixed) + set_size(m->workers_dynamic); +} + +static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(s); + + assert_se(!set_remove(m->workers_dynamic, s) != !set_remove(m->workers_fixed, s)); + sd_event_source_disable_unref(s); + + if (si->si_code == CLD_EXITED) { + if (si->si_status == EXIT_SUCCESS) + log_debug("Worker " PID_FMT " exited successfully.", si->si_pid); + else + log_warning("Worker " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status); + } else if (si->si_code == CLD_KILLED) + log_warning("Worker " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status)); + else if (si->si_code == CLD_DUMPED) + log_warning("Worker " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status)); + else + log_warning("Got unexpected exit code via SIGCHLD, ignoring."); + + (void) start_workers(m, /* explicit_request= */ false); /* Fill up workers again if we fell below the low watermark */ + return 0; +} + +static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + (void) start_workers(m, /* explicit_request= */ true); /* Workers told us there's more work, let's add one more worker as long as we are below the high watermark */ + return 0; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + event_source_hash_ops, + sd_event_source, + (void (*)(const sd_event_source*, struct siphash*)) trivial_hash_func, + (int (*)(const sd_event_source*, const sd_event_source*)) trivial_compare_func, + sd_event_source_disable_unref); + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + m = new(Manager, 1); + if (!m) + return -ENOMEM; + + *m = (Manager) { + .listen_fd = -EBADF, + .worker_ratelimit = { + .interval = 5 * USEC_PER_SEC, + .burst = 50, + }, + }; + + r = sd_event_new(&m->event); + if (r < 0) + return r; + + r = sd_event_set_signal_exit(m->event, true); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); + if (r < 0) + return r; + + r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL); + if (r < 0) + log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); + + r = sd_event_set_watchdog(m->event, true); + if (r < 0) + log_debug_errno(r, "Failed to enable watchdog handling, ignoring: %m"); + + r = sd_event_add_signal(m->event, NULL, SIGUSR2|SD_EVENT_SIGNAL_PROCMASK, on_sigusr2, m); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +Manager* manager_free(Manager *m) { + if (!m) + return NULL; + + set_free(m->workers_fixed); + set_free(m->workers_dynamic); + + /* Note: we rely on PR_DEATHSIG to kill the workers for us */ + + sd_event_unref(m->event); + + return mfree(m); +} + +static int start_one_worker(Manager *m) { + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *source = NULL; + bool fixed; + pid_t pid; + int r; + + assert(m); + + fixed = set_size(m->workers_fixed) < MOUNTFS_WORKERS_MIN; + + r = safe_fork_full( + "(sd-worker)", + /* stdio_fds= */ NULL, + &m->listen_fd, 1, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS, + &pid); + if (r < 0) + return log_error_errno(r, "Failed to fork new worker child: %m"); + if (r == 0) { + char pids[DECIMAL_STR_MAX(pid_t)]; + /* Child */ + + if (m->listen_fd == 3) { + r = fd_cloexec(3, false); + if (r < 0) { + log_error_errno(r, "Failed to turn off O_CLOEXEC for fd 3: %m"); + _exit(EXIT_FAILURE); + } + } else { + if (dup2(m->listen_fd, 3) < 0) { /* dup2() creates with O_CLOEXEC off */ + log_error_errno(errno, "Failed to move listen fd to 3: %m"); + _exit(EXIT_FAILURE); + } + + safe_close(m->listen_fd); + } + + xsprintf(pids, PID_FMT, pid); + if (setenv("LISTEN_PID", pids, 1) < 0) { + log_error_errno(errno, "Failed to set $LISTEN_PID: %m"); + _exit(EXIT_FAILURE); + } + + if (setenv("LISTEN_FDS", "1", 1) < 0) { + log_error_errno(errno, "Failed to set $LISTEN_FDS: %m"); + _exit(EXIT_FAILURE); + } + + if (setenv("MOUNTFS_FIXED_WORKER", one_zero(fixed), 1) < 0) { + log_error_errno(errno, "Failed to set $MOUNTFS_FIXED_WORKER: %m"); + _exit(EXIT_FAILURE); + } + + r = setenv_systemd_log_level(); + if (r < 0) { + log_error_errno(r, "Failed to set $SYSTEMD_LOG_LEVEL: %m"); + _exit(EXIT_FAILURE); + } + + r = invoke_callout_binary(SYSTEMD_MOUNTWORK_PATH, STRV_MAKE("systemd-mountwork", "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */ + log_error_errno(r, "Failed start worker process: %m"); + _exit(EXIT_FAILURE); + } + + r = sd_event_add_child(m->event, &source, pid, WEXITED, on_worker_exit, m); + if (r < 0) + return log_error_errno(r, "Failed to watch child " PID_FMT ": %m", pid); + + r = set_ensure_put( + fixed ? &m->workers_fixed : &m->workers_dynamic, + &event_source_hash_ops, + source); + if (r < 0) + return log_error_errno(r, "Failed to add child process to set: %m"); + + TAKE_PTR(source); + + return 0; +} + +static int start_workers(Manager *m, bool explicit_request) { + int r; + + assert(m); + + for (;;) { + size_t n; + + n = manager_current_workers(m); + + log_debug("%zu workers running.", n); + + if (n >= MOUNTFS_WORKERS_MIN && (!explicit_request || n >= MOUNTFS_WORKERS_MAX)) + break; + + if (!ratelimit_below(&m->worker_ratelimit)) { + /* If we keep starting workers too often, let's fail the whole daemon, something is wrong */ + sd_event_exit(m->event, EXIT_FAILURE); + + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Worker threads requested too frequently, something is wrong."); + } + + r = start_one_worker(m); + if (r < 0) + return r; + + explicit_request = false; + } + + return 0; +} + +int manager_startup(Manager *m) { + int n; + + assert(m); + assert(m->listen_fd < 0); + + n = sd_listen_fds(false); + if (n < 0) + return log_error_errno(n, "Failed to determine number of passed file descriptors: %m"); + if (n > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one listening fd, got %i.", n); + if (n == 1) + m->listen_fd = SD_LISTEN_FDS_START; + else { + static const union sockaddr_union sockaddr = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/io.systemd.MountFileSystem", + }; + + m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (m->listen_fd < 0) + return log_error_errno(errno, "Failed to bind on socket: %m"); + + (void) sockaddr_un_unlink(&sockaddr.un); + + WITH_UMASK(0000) + if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0) + return log_error_errno(errno, "Failed to bind socket: %m"); + + if (listen(m->listen_fd, SOMAXCONN) < 0) + return log_error_errno(errno, "Failed to listen on socket: %m"); + } + + /* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be + * GC'ed on idle */ + if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0) + return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m"); + + return start_workers(m, /* explicit_request= */ false); +} diff --git a/src/mountfsd/mountfsd-manager.h b/src/mountfsd/mountfsd-manager.h new file mode 100644 index 0000000..6bfbddc --- /dev/null +++ b/src/mountfsd/mountfsd-manager.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" +#include "sd-event.h" + +typedef struct Manager Manager; + +#include "hashmap.h" +#include "ratelimit.h" + +#define MOUNTFS_WORKERS_MIN 3 +#define MOUNTFS_WORKERS_MAX 4096 + +struct Manager { + sd_event *event; + + Set *workers_fixed; /* Workers 0…MOUNTFS_WORKERS_MIN */ + Set *workers_dynamic; /* Workers MOUNTFS_WORKERS_MIN+1…MOUNTFS_WORKERS_MAX */ + + int listen_fd; + + RateLimit worker_ratelimit; +}; + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +int manager_startup(Manager *m); diff --git a/src/mountfsd/mountfsd.c b/src/mountfsd/mountfsd.c new file mode 100644 index 0000000..6073bd5 --- /dev/null +++ b/src/mountfsd/mountfsd.c @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "daemon-util.h" +#include "log.h" +#include "main-func.h" +#include "mountfsd-manager.h" +#include "signal-util.h" + +static int run(int argc, char *argv[]) { + _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + log_setup(); + + umask(0022); + + if (argc != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); + + r = manager_new(&m); + if (r < 0) + return log_error_errno(r, "Could not create manager: %m"); + + r = manager_startup(m); + if (r < 0) + return log_error_errno(r, "Failed to start up daemon: %m"); + + notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING); + + r = sd_event_loop(m->event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/mountfsd/mountwork.c b/src/mountfsd/mountwork.c new file mode 100644 index 0000000..1d218a6 --- /dev/null +++ b/src/mountfsd/mountwork.c @@ -0,0 +1,703 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-daemon.h" + +#include "argv-util.h" +#include "bus-polkit.h" +#include "chase.h" +#include "discover-image.h" +#include "dissect-image.h" +#include "env-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "main-func.h" +#include "missing_loop.h" +#include "namespace-util.h" +#include "nsresource.h" +#include "nulstr-util.h" +#include "os-util.h" +#include "process-util.h" +#include "stat-util.h" +#include "user-util.h" +#include "varlink.h" +#include "varlink-io.systemd.MountFileSystem.h" + +#define ITERATIONS_MAX 64U +#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE) +#define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC) +#define LISTEN_IDLE_USEC (90 * USEC_PER_SEC) + +static const ImagePolicy image_policy_untrusted = { + .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, +}; + +static int json_dispatch_image_policy(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_(image_policy_freep) ImagePolicy *q = NULL; + ImagePolicy **p = ASSERT_PTR(userdata); + int r; + + assert(p); + + if (json_variant_is_null(variant)) { + *p = image_policy_free(*p); + return 0; + } + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + r = image_policy_from_string(json_variant_string(variant), &q); + if (r < 0) + return json_log(variant, flags, r, "JSON field '%s' is not a valid image policy.", strna(name)); + + image_policy_free(*p); + *p = TAKE_PTR(q); + return 0; +} + +typedef struct MountImageParameters { + unsigned image_fd_idx; + unsigned userns_fd_idx; + int read_only; + int growfs; + char *password; + ImagePolicy *image_policy; +} MountImageParameters; + +static void mount_image_parameters_done(MountImageParameters *p) { + assert(p); + + p->password = erase_and_free(p->password); + p->image_policy = image_policy_free(p->image_policy); +} + +static int validate_image_fd(int fd, MountImageParameters *p) { + int r, fl; + + assert(fd >= 0); + assert(p); + + r = fd_verify_regular(fd); + if (r < 0) + return r; + + fl = fd_verify_safe_flags(fd); + if (fl < 0) + return log_debug_errno(fl, "Image file descriptor has unsafe flags set: %m"); + + switch (fl & O_ACCMODE) { + + case O_RDONLY: + p->read_only = true; + break; + + case O_RDWR: + break; + + default: + return -EBADF; + } + + return 0; +} + +static int verify_trusted_image_fd_by_path(int fd) { + _cleanup_free_ char *p = NULL; + struct stat sta; + int r; + + assert(fd >= 0); + + r = secure_getenv_bool("SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES"); + if (r == -ENXIO) { + if (!DEFAULT_MOUNTFSD_TRUSTED_DIRECTORIES) { + log_debug("Trusted directory mechanism disabled at compile time."); + return false; + } + } else if (r < 0) { + log_debug_errno(r, "Failed to parse $SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES environment variable, not trusting any image."); + return false; + } else if (!r) { + log_debug("Trusted directory mechanism disabled via $SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES environment variable."); + return false; + } + + r = fd_get_path(fd, &p); + if (r < 0) + return log_debug_errno(r, "Failed to get path of passed image file descriptor: %m"); + if (fstat(fd, &sta) < 0) + return log_debug_errno(errno, "Failed to stat() passed image file descriptor: %m"); + + log_debug("Checking if image '%s' is in trusted directories.", p); + + for (ImageClass c = 0; c < _IMAGE_CLASS_MAX; c++) + NULSTR_FOREACH(s, image_search_path[c]) { + _cleanup_close_ int dir_fd = -EBADF, inode_fd = -EBADF; + _cleanup_free_ char *q = NULL; + struct stat stb; + const char *e; + + r = chase(s, NULL, CHASE_SAFE, &q, &dir_fd); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to resolve search path '%s', ignoring: %m", s); + continue; + } + + /* Check that the inode refers to a file immediately inside the image directory, + * i.e. not the image directory itself, and nothing further down the tree */ + e = path_startswith(p, q); + if (isempty(e)) + continue; + + e += strspn(e, "/"); + if (!filename_is_valid(e)) + continue; + + r = chaseat(dir_fd, e, CHASE_SAFE, NULL, &inode_fd); + if (r < 0) + return log_error_errno(r, "Couldn't verify that specified image '%s' is in search path '%s': %m", p, s); + + if (fstat(inode_fd, &stb) < 0) + return log_error_errno(errno, "Failed to stat image file '%s/%s': %m", q, e); + + if (stat_inode_same(&sta, &stb)) { + log_debug("Image '%s' is *in* trusted directories.", p); + return true; /* Yay */ + } + } + + log_debug("Image '%s' is *not* in trusted directories.", p); + return false; +} + +static int determine_image_policy( + int image_fd, + bool trusted, + ImagePolicy *client_policy, + ImagePolicy **ret) { + + _cleanup_(image_policy_freep) ImagePolicy *envvar_policy = NULL; + const ImagePolicy *default_policy; + const char *envvar, *e; + int r; + + assert(image_fd >= 0); + assert(ret); + + if (trusted) { + envvar = "SYSTEMD_MOUNTFSD_IMAGE_POLICY_TRUSTED"; + default_policy = &image_policy_allow; + } else { + envvar = "SYSTEMD_MOUNTFSD_IMAGE_POLICY_UNTRUSTED"; + default_policy = &image_policy_untrusted; + } + + e = secure_getenv(envvar); + if (e) { + r = image_policy_from_string(e, &envvar_policy); + if (r < 0) + return log_error_errno(r, "Failed to parse image policy supplied via $%s: %m", envvar); + + default_policy = envvar_policy; + } + + return image_policy_intersect(default_policy, client_policy, ret); +} + +static int validate_userns(Varlink *link, int *userns_fd) { + int r; + + assert(link); + assert(userns_fd); + + if (*userns_fd < 0) + return 0; + + r = fd_verify_safe_flags(*userns_fd); + if (r < 0) + return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m"); + + r = fd_is_ns(*userns_fd, CLONE_NEWUSER); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + /* Our own host user namespace? Then close the fd, and handle it as if none was specified. */ + r = is_our_namespace(*userns_fd, NAMESPACE_USER); + if (r < 0) + return log_debug_errno(r, "Failed to determine if user namespace provided by client is our own."); + if (r > 0) { + log_debug("User namespace provided by client is our own."); + *userns_fd = safe_close(*userns_fd); + } + + return 0; +} + +static int vl_method_mount_image( + Varlink *link, + JsonVariant *parameters, + VarlinkMethodFlags flags, + void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "imageFileDescriptor", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MountImageParameters, image_fd_idx), JSON_MANDATORY }, + { "userNamespaceFileDescriptor", JSON_VARIANT_UNSIGNED, json_dispatch_uint, offsetof(MountImageParameters, userns_fd_idx), 0 }, + { "readOnly", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MountImageParameters, read_only), 0 }, + { "growFileSystems", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MountImageParameters, growfs), 0 }, + { "password", JSON_VARIANT_STRING, json_dispatch_string, offsetof(MountImageParameters, password), 0 }, + { "imagePolicy", JSON_VARIANT_STRING, json_dispatch_image_policy, offsetof(MountImageParameters, image_policy), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, + {} + }; + + _cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT; + _cleanup_(mount_image_parameters_done) MountImageParameters p = { + .image_fd_idx = UINT_MAX, + .userns_fd_idx = UINT_MAX, + .read_only = -1, + .growfs = -1, + }; + _cleanup_(dissected_image_unrefp) DissectedImage *di = NULL; + _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *aj = NULL; + _cleanup_close_ int image_fd = -EBADF, userns_fd = -EBADF; + _cleanup_(image_policy_freep) ImagePolicy *use_policy = NULL; + Hashmap **polkit_registry = ASSERT_PTR(userdata); + _cleanup_free_ char *ps = NULL; + bool image_is_trusted = false; + uid_t peer_uid; + int r; + + assert(link); + assert(parameters); + + json_variant_sensitive(parameters); /* might contain passwords */ + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return log_debug_errno(r, "Failed to get client UID: %m"); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.image_fd_idx != UINT_MAX) { + image_fd = varlink_peek_dup_fd(link, p.image_fd_idx); + if (image_fd < 0) + return log_debug_errno(image_fd, "Failed to peek image fd from client: %m"); + } + + if (p.userns_fd_idx != UINT_MAX) { + userns_fd = varlink_peek_dup_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to peek user namespace fd from client: %m"); + } + + r = validate_image_fd(image_fd, &p); + if (r < 0) + return r; + + r = validate_userns(link, &userns_fd); + if (r != 0) + return r; + + r = verify_trusted_image_fd_by_path(image_fd); + if (r < 0) + return r; + image_is_trusted = r; + + const char *polkit_details[] = { + "read_only", one_zero(p.read_only > 0), + NULL, + }; + + const char *polkit_action, *polkit_untrusted_action; + PolkitFlags polkit_flags; + if (userns_fd < 0) { + /* Mount into the host user namespace */ + polkit_action = "io.systemd.mount-file-system.mount-image"; + polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-image"; + polkit_flags = 0; + } else { + /* Mount into a private user namespace */ + polkit_action = "io.systemd.mount-file-system.mount-image-privately"; + polkit_untrusted_action = "io.systemd.mount-file-system.mount-untrusted-image-privately"; + + /* If polkit is not around, let's allow mounting authenticated images by default */ + polkit_flags = POLKIT_DEFAULT_ALLOW; + } + + /* Let's definitely acquire the regular action privilege, for mounting properly signed images */ + r = varlink_verify_polkit_async_full( + link, + /* bus= */ NULL, + polkit_action, + polkit_details, + /* good_user= */ UID_INVALID, + polkit_flags, + polkit_registry); + if (r <= 0) + return r; + + /* Generate the common dissection directory here. We are not going to use it, but the clients might, + * and they likely are unprivileged, hence cannot create it themselves. Hence let's just create it + * here, if it is missing. */ + r = get_common_dissect_directory(NULL); + if (r < 0) + return r; + + r = loop_device_make( + image_fd, + p.read_only == 0 ? O_RDONLY : O_RDWR, + 0, + UINT64_MAX, + UINT32_MAX, + LO_FLAGS_PARTSCAN, + LOCK_EX, + &loop); + if (r < 0) + return r; + + DissectImageFlags dissect_flags = + (p.read_only == 0 ? DISSECT_IMAGE_READ_ONLY : 0) | + (p.growfs != 0 ? DISSECT_IMAGE_GROWFS : 0) | + DISSECT_IMAGE_DISCARD_ANY | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + + /* Let's see if we have acquired the privilege to mount untrusted images already */ + bool polkit_have_untrusted_action = + varlink_has_polkit_action(link, polkit_untrusted_action, polkit_details, polkit_registry); + + for (;;) { + use_policy = image_policy_free(use_policy); + ps = mfree(ps); + + /* We use the image policy for trusted images if either the path is below a trusted + * directory, or if we have already acquired a PK authentication that tells us that untrusted + * images are OK */ + bool use_trusted_policy = + image_is_trusted || + polkit_have_untrusted_action; + + r = determine_image_policy( + image_fd, + use_trusted_policy, + p.image_policy, + &use_policy); + if (r < 0) + return r; + + r = image_policy_to_string(use_policy, /* simplify= */ true, &ps); + if (r < 0) + return r; + + log_debug("Using image policy: %s", ps); + + r = dissect_loop_device( + loop, + &verity, + /* mount_options= */ NULL, + use_policy, + dissect_flags, + &di); + if (r == -ENOPKG) + return varlink_error(link, "io.systemd.MountFileSystem.IncompatibleImage", NULL); + if (r == -ENOTUNIQ) + return varlink_error(link, "io.systemd.MountFileSystem.MultipleRootPartitionsFound", NULL); + if (r == -ENXIO) + return varlink_error(link, "io.systemd.MountFileSystem.RootPartitionNotFound", NULL); + if (r == -ERFKILL) { + /* The image policy refused this, let's retry after trying to get PolicyKit */ + + if (!polkit_have_untrusted_action) { + log_debug("Denied by image policy. Trying a stronger polkit authentication before continuing."); + r = varlink_verify_polkit_async_full( + link, + /* bus= */ NULL, + polkit_untrusted_action, + polkit_details, + /* good_user= */ UID_INVALID, + /* flags= */ 0, /* NB: the image cannot be authenticated, hence unless PK is around to allow this anyway, fail! */ + polkit_registry); + if (r <= 0 && !ERRNO_IS_NEG_PRIVILEGE(r)) + return r; + if (r > 0) { + /* Try again, now that we know the client has enough privileges. */ + log_debug("Denied by image policy, retrying after polkit authentication."); + polkit_have_untrusted_action = true; + continue; + } + } + + return varlink_error(link, "io.systemd.MountFileSystem.DeniedByImagePolicy", NULL); + } + if (r < 0) + return r; + + /* Success */ + break; + } + + r = dissected_image_load_verity_sig_partition( + di, + loop->fd, + &verity); + if (r < 0) + return r; + + r = dissected_image_decrypt( + di, + p.password, + &verity, + dissect_flags); + if (r == -ENOKEY) /* new dm-verity userspace returns ENOKEY if the dm-verity signature key is not in + * key chain. That's great. */ + return varlink_error(link, "io.systemd.MountFileSystem.KeyNotFound", NULL); + if (r == -EBUSY) /* DM kernel subsystem is shit with returning useful errors hence we keep retrying + * under the assumption that some errors are transitional. Which the errors might + * not actually be. After all retries failed we return EBUSY. Let's turn that into a + * generic Verity error. It's not very helpful, could mean anything, but at least it + * gives client a clear idea that this has to do with Verity. */ + return varlink_error(link, "io.systemd.MountFileSystem.VerityFailure", NULL); + if (r < 0) + return r; + + r = dissected_image_mount( + di, + /* where= */ NULL, + /* uid_shift= */ UID_INVALID, + /* uid_range= */ UID_INVALID, + userns_fd, + dissect_flags); + if (r < 0) + return r; + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + _cleanup_(json_variant_unrefp) JsonVariant *pj = NULL; + DissectedPartition *pp = di->partitions + d; + int fd_idx; + + if (!pp->found) + continue; + + if (pp->fsmount_fd < 0) + continue; + + if (userns_fd >= 0) { + r = nsresource_add_mount(userns_fd, pp->fsmount_fd); + if (r < 0) + return r; + } + + fd_idx = varlink_push_fd(link, pp->fsmount_fd); + if (fd_idx < 0) + return fd_idx; + + TAKE_FD(pp->fsmount_fd); + + r = json_build(&pj, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("designator", JSON_BUILD_STRING(partition_designator_to_string(d))), + JSON_BUILD_PAIR("writable", JSON_BUILD_BOOLEAN(pp->rw)), + JSON_BUILD_PAIR("growFileSystem", JSON_BUILD_BOOLEAN(pp->growfs)), + JSON_BUILD_PAIR_CONDITION(pp->partno > 0, "partitionNumber", JSON_BUILD_INTEGER(pp->partno)), + JSON_BUILD_PAIR_CONDITION(pp->architecture > 0, "architecture", JSON_BUILD_STRING(architecture_to_string(pp->architecture))), + JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(pp->uuid), "partitionUuid", JSON_BUILD_UUID(pp->uuid)), + JSON_BUILD_PAIR("fileSystemType", JSON_BUILD_STRING(dissected_partition_fstype(pp))), + JSON_BUILD_PAIR_CONDITION(pp->label, "partitionLabel", JSON_BUILD_STRING(pp->label)), + JSON_BUILD_PAIR("size", JSON_BUILD_INTEGER(pp->size)), + JSON_BUILD_PAIR("offset", JSON_BUILD_INTEGER(pp->offset)), + JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_INTEGER(fd_idx)))); + if (r < 0) + return r; + + r = json_variant_append_array(&aj, pj); + if (r < 0) + return r; + } + + loop_device_relinquish(loop); + + r = varlink_replyb(link, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("partitions", JSON_BUILD_VARIANT(aj)), + JSON_BUILD_PAIR("imagePolicy", JSON_BUILD_STRING(ps)), + JSON_BUILD_PAIR("imageSize", JSON_BUILD_INTEGER(di->image_size)), + JSON_BUILD_PAIR("sectorSize", JSON_BUILD_INTEGER(di->sector_size)), + JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(di->image_uuid), "imageUuid", JSON_BUILD_UUID(di->image_uuid)))); + if (r < 0) + return r; + + return r; +} + +static int process_connection(VarlinkServer *server, int _fd) { + _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ + _cleanup_(varlink_close_unrefp) Varlink *vl = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int r; + + r = sd_event_new(&event); + if (r < 0) + return r; + + r = varlink_server_attach_event(server, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach Varlink server to event loop: %m"); + + r = varlink_server_add_connection(server, fd, &vl); + if (r < 0) + return log_error_errno(r, "Failed to add connection: %m"); + + TAKE_FD(fd); + vl = varlink_ref(vl); + + r = varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing for read: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing for write: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + r = varlink_server_detach_event(server); + if (r < 0) + return log_error_errno(r, "Failed to detach Varlink server from event loop: %m"); + + return 0; +} + +static int run(int argc, char *argv[]) { + usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY; + _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; + _cleanup_(hashmap_freep) Hashmap *polkit_registry = NULL; + _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; + unsigned n_iterations = 0; + int m, listen_fd, r; + + log_setup(); + + m = sd_listen_fds(false); + if (m < 0) + return log_error_errno(m, "Failed to determine number of listening fds: %m"); + if (m == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received."); + if (m > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time."); + + listen_fd = SD_LISTEN_FDS_START; + + r = fd_nonblock(listen_fd, false); + if (r < 0) + return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m"); + + r = varlink_server_new(&server, VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate server: %m"); + + r = varlink_server_add_interface(server, &vl_interface_io_systemd_MountFileSystem); + if (r < 0) + return log_error_errno(r, "Failed to add MountFileSystem interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + server, + "io.systemd.MountFileSystem.MountImage", vl_method_mount_image); + if (r < 0) + return log_error_errno(r, "Failed to bind methods: %m"); + + varlink_server_set_userdata(server, &polkit_registry); + + r = varlink_server_set_exit_on_idle(server, true); + if (r < 0) + return log_error_errno(r, "Failed to enable exit-on-idle mode: %m"); + + r = getenv_bool("MOUNTFS_FIXED_WORKER"); + if (r < 0) + return log_error_errno(r, "Failed to parse MOUNTFSD_FIXED_WORKER: %m"); + listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC; + + r = pidref_set_parent(&parent); + if (r < 0) + return log_error_errno(r, "Failed to acquire pidfd of parent process: %m"); + + start_time = now(CLOCK_MONOTONIC); + + for (;;) { + _cleanup_close_ int fd = -EBADF; + usec_t n; + + /* Exit the worker in regular intervals, to flush out all memory use */ + if (n_iterations++ > ITERATIONS_MAX) { + log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations); + break; + } + + n = now(CLOCK_MONOTONIC); + if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) { + log_debug("Exiting worker, ran for %s, that's enough.", + FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0)); + break; + } + + if (last_busy_usec == USEC_INFINITY) + last_busy_usec = n; + else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) { + log_debug("Exiting worker, been idle for %s.", + FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0)); + break; + } + + (void) rename_process("systemd-mountwork: waiting..."); + fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); + (void) rename_process("systemd-mountwork: processing..."); + + if (fd == -EAGAIN) + continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected + * after a while, let's check if it's time to exit though. */ + if (fd == -EINTR) + continue; /* Might be that somebody attached via strace, let's just continue in that + * case */ + if (fd < 0) + return log_error_errno(fd, "Failed to accept() from listening socket: %m"); + + if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) { + /* We only slept a very short time? If so, let's see if there are more sockets + * pending, and if so, let's ask our parent for more workers */ + + r = fd_wait_for_event(listen_fd, POLLIN, 0); + if (r < 0) + return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m"); + + if (FLAGS_SET(r, POLLIN)) { + r = pidref_kill(&parent, SIGUSR2); + if (r == -ESRCH) + return log_error_errno(r, "Parent already died?"); + if (r < 0) + return log_error_errno(r, "Failed to send SIGUSR2 signal to parent. %m"); + } + } + + (void) process_connection(server, TAKE_FD(fd)); + last_busy_usec = USEC_INFINITY; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/generator/main.c b/src/network/generator/main.c index 0439a9d..0911656 100644 --- a/src/network/generator/main.c +++ b/src/network/generator/main.c @@ -3,6 +3,7 @@ #include #include "build.h" +#include "creds-util.h" #include "fd-util.h" #include "fs-util.h" #include "generator.h" @@ -13,7 +14,7 @@ #include "path-util.h" #include "proc-cmdline.h" -#define NETWORKD_UNIT_DIRECTORY "/run/systemd/network" +#define NETWORK_UNIT_DIRECTORY "/run/systemd/network/" static const char *arg_root = NULL; @@ -25,7 +26,13 @@ static int network_save(Network *network, const char *dest_dir) { assert(network); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -39,7 +46,7 @@ static int network_save(Network *network, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -53,7 +60,13 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) { assert(netdev); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -64,7 +77,7 @@ static int netdev_save(NetDev *netdev, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -78,7 +91,13 @@ static int link_save(Link *link, const char *dest_dir) { assert(link); - r = generator_open_unit_file_full(dest_dir, NULL, NULL, &f, &temp_path); + r = generator_open_unit_file_full( + dest_dir, + /* source= */ NULL, + /* name= */ NULL, + &f, + /* ret_final_path= */ NULL, + &temp_path); if (r < 0) return r; @@ -92,7 +111,7 @@ static int link_save(Link *link, const char *dest_dir) { r = conservative_rename(temp_path, p); if (r < 0) - return r; + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", temp_path, p); temp_path = mfree(temp_path); return 0; @@ -104,11 +123,11 @@ static int context_save(Context *context) { Link *link; int r; - const char *p = prefix_roota(arg_root, NETWORKD_UNIT_DIRECTORY); + const char *p = prefix_roota(arg_root, NETWORK_UNIT_DIRECTORY); r = mkdir_p(p, 0755); if (r < 0) - return log_error_errno(r, "Failed to create directory " NETWORKD_UNIT_DIRECTORY ": %m"); + return log_error_errno(r, "Failed to create directory " NETWORK_UNIT_DIRECTORY ": %m"); HASHMAP_FOREACH(network, context->networks_by_name) RET_GATHER(r, network_save(network, p)); @@ -174,7 +193,7 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context context = {}; - int r; + int r, ret = 0; log_setup(); @@ -212,7 +231,17 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_warning_errno(r, "Failed to merge multiple command line options: %m"); - return context_save(&context); + RET_GATHER(ret, context_save(&context)); + + static const PickUpCredential table[] = { + { "network.conf.", "/run/systemd/networkd.conf.d/", ".conf" }, + { "network.link.", NETWORK_UNIT_DIRECTORY, ".link" }, + { "network.netdev.", NETWORK_UNIT_DIRECTORY, ".netdev" }, + { "network.network.", NETWORK_UNIT_DIRECTORY, ".network" }, + }; + RET_GATHER(ret, pick_up_credentials(table, ELEMENTSOF(table))); + + return ret; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/network/generator/network-generator.c b/src/network/generator/network-generator.c index 48527a2..ec66520 100644 --- a/src/network/generator/network-generator.c +++ b/src/network/generator/network-generator.c @@ -27,7 +27,7 @@ # .link ifname=: - net.ifname-policy=policy1[,policy2,...][,] # This is an original rule, not supported by other tools. + net.ifname_policy=policy1[,policy2,...][,] # This is an original rule, not supported by other tools. # .netdev vlan=: @@ -373,13 +373,13 @@ static int network_set_dhcp_type(Context *context, const char *ifname, const cha t = dracut_dhcp_type_from_string(dhcp_type); if (t < 0) - return t; + return log_debug_errno(t, "Invalid DHCP type '%s'", dhcp_type); network = network_get(context, ifname); if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } network->dhcp_type = t; @@ -394,13 +394,14 @@ static int network_set_hostname(Context *context, const char *ifname, const char network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); return free_and_strdup(&network->hostname, hostname); } static int network_set_mtu(Context *context, const char *ifname, const char *mtu) { Network *network; + int r; assert(context); assert(ifname); @@ -410,13 +411,18 @@ static int network_set_mtu(Context *context, const char *ifname, const char *mtu network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); - return parse_mtu(AF_UNSPEC, mtu, &network->mtu); + r = parse_mtu(AF_UNSPEC, mtu, &network->mtu); + if (r < 0) + return log_debug_errno(r, "Invalid MTU '%s' for '%s': %m", mtu, ifname); + + return r; } static int network_set_mac_address(Context *context, const char *ifname, const char *mac) { Network *network; + int r; assert(context); assert(ifname); @@ -424,9 +430,13 @@ static int network_set_mac_address(Context *context, const char *ifname, const c network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); - return parse_ether_addr(mac, &network->mac); + r = parse_ether_addr(mac, &network->mac); + if (r < 0) + return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", mac, ifname); + + return r; } static int network_set_address(Context *context, const char *ifname, int family, unsigned char prefixlen, @@ -443,7 +453,7 @@ static int network_set_address(Context *context, const char *ifname, int family, network = network_get(context, ifname); if (!network) - return -ENODEV; + return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No network found for '%s'", ifname); return address_new(network, family, prefixlen, addr, peer, NULL); } @@ -465,7 +475,7 @@ static int network_set_route(Context *context, const char *ifname, int family, u if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return route_new(network, family, prefixlen, dest, gateway, NULL); @@ -486,13 +496,13 @@ static int network_set_dns(Context *context, const char *ifname, int family, con else r = in_addr_from_string(family, dns, &a); if (r < 0) - return r; + return log_debug_errno(r, "Invalid DNS address '%s' for '%s'", dns, ifname); network = network_get(context, ifname); if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return strv_extend(&network->dns, dns); @@ -509,7 +519,7 @@ static int network_set_dhcp_use_dns(Context *context, const char *ifname, bool v if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } network->dhcp_use_dns = value; @@ -528,7 +538,7 @@ static int network_set_vlan(Context *context, const char *ifname, const char *va if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->vlan, value); @@ -545,7 +555,7 @@ static int network_set_bridge(Context *context, const char *ifname, const char * if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->bridge, value); @@ -562,7 +572,7 @@ static int network_set_bond(Context *context, const char *ifname, const char *va if (!network) { r = network_new(context, ifname, &network); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create network for '%s': %m", ifname); } return free_and_strdup(&network->bond, value); @@ -615,21 +625,21 @@ static int parse_ip_address_one(int family, const char **value, union in_addr_un if (family == AF_INET6) { if (p[0] != '[') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); q = strchr(p + 1, ']'); if (!q) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); if (q[1] != ':') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", p); buf = strndupa_safe(p + 1, q - p - 1); p = q + 2; } else { q = strchr(p, ':'); if (!q) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", p); buf = strndupa_safe(p, q - p); p = q + 1; @@ -637,7 +647,7 @@ static int parse_ip_address_one(int family, const char **value, union in_addr_un r = in_addr_from_string(family, buf, ret); if (r < 0) - return r; + return log_debug_errno(r, "Invalid IP address '%s': %m", buf); *value = p; return 1; @@ -657,7 +667,7 @@ static int parse_netmask_or_prefixlen(int family, const char **value, unsigned c if (r > 0) { if (family == AF_INET6) /* TODO: Not supported yet. */ - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "IPv6 prefix length is not supported yet"); *ret = in4_addr_netmask_to_prefixlen(&netmask.in); } else if (r == 0) @@ -665,12 +675,12 @@ static int parse_netmask_or_prefixlen(int family, const char **value, unsigned c else { p = strchr(*value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid netmask or prefix length '%s'", *value); q = strndupa_safe(*value, p - *value); r = safe_atou8(q, ret); if (r < 0) - return r; + return log_debug_errno(r, "Invalid netmask or prefix length '%s': %m", q); *value = p + 1; } @@ -693,10 +703,8 @@ static int parse_ip_dns_address_one(Context *context, const char *ifname, const if (p[0] == '[') { q = strchr(p + 1, ']'); - if (!q) - return -EINVAL; - if (!IN_SET(q[1], ':', '\0')) - return -EINVAL; + if (!q || !IN_SET(q[1], ':', '\0')) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP DNS address '%s'", p); buf = strndupa_safe(p + 1, q - p - 1); p = q + 1; @@ -749,12 +757,12 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* hostname */ p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); if (p != value) { hostname = strndupa_safe(value, p - value); if (!hostname_is_valid(hostname, 0)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid hostname '%s'", hostname); } value = p + 1; @@ -762,7 +770,7 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* ifname */ p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); ifname = strndupa_safe(value, p - value); @@ -813,7 +821,7 @@ static int parse_cmdline_ip_address(Context *context, int family, const char *va /* refuse unexpected trailing strings */ if (!isempty(value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); return 0; } @@ -829,7 +837,7 @@ static int parse_cmdline_ip_interface(Context *context, const char *value) { p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IP address '%s'", value); ifname = strndupa_safe(value, p - value); @@ -858,7 +866,7 @@ static int parse_cmdline_ip(Context *context, const char *key, const char *value assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) @@ -887,15 +895,15 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char /* rd.route=/:[:] */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); if (value[0] == '[') { p = strchr(value, ']'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); if (p[1] != ':') - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv6 address '%s'", value); buf = strndupa_safe(value + 1, p - value - 1); value = p + 2; @@ -903,7 +911,7 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char } else { p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid IPv4 address '%s'", value); buf = strndupa_safe(value, p - value); value = p + 1; @@ -912,7 +920,7 @@ static int parse_cmdline_rd_route(Context *context, const char *key, const char r = in_addr_prefix_from_string(buf, family, &addr, &prefixlen); if (r < 0) - return r; + return log_debug_errno(r, "Invalid IP address '%s': %m", buf); p = strchr(value, ':'); if (!p) @@ -930,7 +938,7 @@ static int parse_cmdline_nameserver(Context *context, const char *key, const cha assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); return network_set_dns(context, "", AF_UNSPEC, value); } @@ -946,7 +954,7 @@ static int parse_cmdline_rd_peerdns(Context *context, const char *key, const cha r = parse_boolean(value); if (r < 0) - return r; + return log_debug_errno(r, "Invalid boolean value '%s'", value); return network_set_dhcp_use_dns(context, "", r); } @@ -960,11 +968,11 @@ static int parse_cmdline_vlan(Context *context, const char *key, const char *val assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid VLAN value '%s'", value); name = strndupa_safe(value, p - value); @@ -972,7 +980,7 @@ static int parse_cmdline_vlan(Context *context, const char *key, const char *val if (!netdev) { r = netdev_new(context, "vlan", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create VLAN device for '%s': %m", name); } return network_set_vlan(context, p + 1, name); @@ -987,11 +995,11 @@ static int parse_cmdline_bridge(Context *context, const char *key, const char *v assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bridge value '%s'", value); name = strndupa_safe(value, p - value); @@ -999,19 +1007,21 @@ static int parse_cmdline_bridge(Context *context, const char *key, const char *v if (!netdev) { r = netdev_new(context, "bridge", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create bridge device for '%s': %m", name); } p++; if (isempty(p)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bridge '%s'", name); for (;;) { _cleanup_free_ char *word = NULL; r = extract_first_word(&p, &word, ",", 0); - if (r <= 0) - return r; + if (r < 0) + return log_debug_errno(r, "Failed to parse slave interfaces for bridge '%s'", name); + if (r == 0) + return 0; r = network_set_bridge(context, word, name); if (r < 0) @@ -1028,11 +1038,11 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val assert(key); if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bond value '%s'", value); name = strndupa_safe(value, p - value); @@ -1040,7 +1050,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val if (!netdev) { r = netdev_new(context, "bond", name, &netdev); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create bond device for '%s': %m", name); } value = p + 1; @@ -1051,7 +1061,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val slaves = strndupa_safe(value, p - value); if (isempty(slaves)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing slave interfaces for bond '%s'", name); for (const char *q = slaves; ; ) { _cleanup_free_ char *word = NULL; @@ -1060,7 +1070,7 @@ static int parse_cmdline_bond(Context *context, const char *key, const char *val if (r == 0) break; if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse slave interfaces for bond '%s'", name); r = network_set_bond(context, word, name); if (r < 0) @@ -1090,19 +1100,23 @@ static int parse_cmdline_ifname(Context *context, const char *key, const char *v /* ifname=: */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); p = strchr(value, ':'); if (!p) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid ifname value '%s'", value); name = strndupa_safe(value, p - value); r = parse_hw_addr(p + 1, &mac); if (r < 0) - return r; + return log_debug_errno(r, "Invalid MAC address '%s' for '%s'", p + 1, name); - return link_new(context, name, &mac, NULL); + r = link_new(context, name, &mac, NULL); + if (r < 0) + return log_debug_errno(r, "Failed to create link for '%s': %m", name); + + return 0; } static int parse_cmdline_ifname_policy(Context *context, const char *key, const char *value) { @@ -1114,10 +1128,10 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const assert(context); assert(key); - /* net.ifname-policy=policy1[,policy2,...][,] */ + /* net.ifname_policy=policy1[,policy2,...][,] */ if (proc_cmdline_value_missing(key, value)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for '%s'", key); for (const char *q = value; ; ) { _cleanup_free_ char *word = NULL; @@ -1127,19 +1141,19 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const if (r == 0) break; if (r < 0) - return r; + return log_debug_errno(r, "Failed to parse ifname policy '%s'", value); p = name_policy_from_string(word); if (p < 0) { r = parse_hw_addr(word, &mac); if (r < 0) - return r; + return log_debug_errno(r, "Invalid MAC address '%s'", word); if (hw_addr_is_null(&mac)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "MAC address is not set"); if (!isempty(q)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing string '%s' in ifname policy '%s'", q, value); break; } @@ -1147,20 +1161,20 @@ static int parse_cmdline_ifname_policy(Context *context, const char *key, const if (alternative_names_policy_from_string(word) >= 0) { r = strv_extend(&alt_policies, word); if (r < 0) - return r; + return log_oom_debug(); } r = strv_consume(&policies, TAKE_PTR(word)); if (r < 0) - return r; + return log_oom_debug(); } if (strv_isempty(policies)) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "No ifname policy specified"); r = link_new(context, NULL, &mac, &link); if (r < 0) - return r; + return log_debug_errno(r, "Failed to create link: %m"); link->policies = TAKE_PTR(policies); link->alt_policies = TAKE_PTR(alt_policies); @@ -1172,23 +1186,23 @@ int parse_cmdline_item(const char *key, const char *value, void *data) { assert(key); - if (streq(key, "ip")) + if (proc_cmdline_key_streq(key, "ip")) return parse_cmdline_ip(context, key, value); - if (streq(key, "rd.route")) + if (proc_cmdline_key_streq(key, "rd.route")) return parse_cmdline_rd_route(context, key, value); - if (streq(key, "nameserver")) + if (proc_cmdline_key_streq(key, "nameserver")) return parse_cmdline_nameserver(context, key, value); - if (streq(key, "rd.peerdns")) + if (proc_cmdline_key_streq(key, "rd.peerdns")) return parse_cmdline_rd_peerdns(context, key, value); - if (streq(key, "vlan")) + if (proc_cmdline_key_streq(key, "vlan")) return parse_cmdline_vlan(context, key, value); - if (streq(key, "bridge")) + if (proc_cmdline_key_streq(key, "bridge")) return parse_cmdline_bridge(context, key, value); - if (streq(key, "bond")) + if (proc_cmdline_key_streq(key, "bond")) return parse_cmdline_bond(context, key, value); - if (streq(key, "ifname")) + if (proc_cmdline_key_streq(key, "ifname")) return parse_cmdline_ifname(context, key, value); - if (streq(key, "net.ifname-policy")) + if (proc_cmdline_key_streq(key, "net.ifname_policy")) return parse_cmdline_ifname_policy(context, key, value); return 0; @@ -1220,12 +1234,12 @@ int context_merge_networks(Context *context) { r = strv_extend_strv(&network->dns, all->dns, false); if (r < 0) - return r; + return log_oom_debug(); LIST_FOREACH(routes, route, all->routes) { r = route_new(network, route->family, route->prefixlen, &route->dest, &route->gateway, NULL); if (r < 0) - return r; + return log_debug_errno(r, "Failed to copy route: %m"); } } @@ -1392,7 +1406,7 @@ int network_format(Network *network, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); network_dump(network, f); @@ -1408,7 +1422,7 @@ int netdev_format(NetDev *netdev, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); netdev_dump(netdev, f); @@ -1424,7 +1438,7 @@ int link_format(Link *link, char **ret) { f = memstream_init(&m); if (!f) - return -ENOMEM; + return log_oom_debug(); link_dump(link, f); diff --git a/src/network/meson.build b/src/network/meson.build index 5c05eba..a983dff 100644 --- a/src/network/meson.build +++ b/src/network/meson.build @@ -47,6 +47,7 @@ sources = files( 'networkd-dhcp4.c', 'networkd-dhcp6-bus.c', 'networkd-dhcp6.c', + 'networkd-dns.c', 'networkd-ipv4acd.c', 'networkd-ipv4ll.c', 'networkd-ipv6-proxy-ndp.c', @@ -56,18 +57,22 @@ sources = files( 'networkd-link.c', 'networkd-lldp-rx.c', 'networkd-lldp-tx.c', - 'networkd-manager-bus.c', 'networkd-manager.c', + 'networkd-manager-bus.c', + 'networkd-manager-varlink.c', 'networkd-ndisc.c', 'networkd-neighbor.c', 'networkd-netlabel.c', 'networkd-network-bus.c', 'networkd-network.c', 'networkd-nexthop.c', + 'networkd-ntp.c', 'networkd-queue.c', 'networkd-radv.c', - 'networkd-route-util.c', 'networkd-route.c', + 'networkd-route-metric.c', + 'networkd-route-nexthop.c', + 'networkd-route-util.c', 'networkd-routing-policy-rule.c', 'networkd-setlink.c', 'networkd-speed-meter.c', @@ -109,7 +114,10 @@ systemd_networkd_wait_online_sources = files( 'wait-online/wait-online.c', ) -networkctl_sources = files('networkctl.c') +networkctl_sources = files( + 'networkctl.c', + 'networkctl-config-file.c' +) network_generator_sources = files( 'generator/main.c', @@ -141,8 +149,7 @@ if get_option('link-networkd-shared') networkd_link_with = [libshared] else networkd_link_with = [libsystemd_static, - libshared_static, - libbasic_gcrypt] + libshared_static] endif network_includes = [libsystemd_network_includes, include_directories(['.', 'netdev', 'tc'])] diff --git a/src/network/netdev/bond.c b/src/network/netdev/bond.c index 4d75a0d..52a7f12 100644 --- a/src/network/netdev/bond.c +++ b/src/network/netdev/bond.c @@ -88,6 +88,12 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } + if (b->peer_notify_delay != 0) { + r = sd_netlink_message_append_u32(m, IFLA_BOND_PEER_NOTIF_DELAY, b->peer_notify_delay / USEC_PER_MSEC); + if (r < 0) + return r; + } + if (b->downdelay != 0) { r = sd_netlink_message_append_u32(m, IFLA_BOND_DOWNDELAY, b->downdelay / USEC_PER_MSEC); if (r < 0) @@ -198,6 +204,12 @@ static int netdev_bond_fill_message_create(NetDev *netdev, Link *link, sd_netlin return r; } + if (b->arp_missed_max > 0) { + r = sd_netlink_message_append_u8(m, IFLA_BOND_MISSED_MAX, b->arp_missed_max); + if (r < 0) + return r; + } + if (b->arp_interval > 0 && !ordered_set_isempty(b->arp_ip_targets)) { void *val; int n = 0; diff --git a/src/network/netdev/bond.h b/src/network/netdev/bond.h index e4b0a0d..ea94001 100644 --- a/src/network/netdev/bond.h +++ b/src/network/netdev/bond.h @@ -34,11 +34,14 @@ typedef struct Bond { uint16_t ad_user_port_key; struct ether_addr ad_actor_system; + uint8_t arp_missed_max; + usec_t miimon; usec_t updelay; usec_t downdelay; usec_t arp_interval; usec_t lp_interval; + usec_t peer_notify_delay; OrderedSet *arp_ip_targets; } Bond; diff --git a/src/network/netdev/bridge.c b/src/network/netdev/bridge.c index 3e394ed..d426c0c 100644 --- a/src/network/netdev/bridge.c +++ b/src/network/netdev/bridge.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include -#include #include #include +#include #include "bridge.h" #include "netlink-util.h" diff --git a/src/network/netdev/fou-tunnel.c b/src/network/netdev/fou-tunnel.c index 3bf41a8..bddee5e 100644 --- a/src/network/netdev/fou-tunnel.c +++ b/src/network/netdev/fou-tunnel.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +/* Make sure the net/if.h header is included before any linux/ one */ #include +#include #include #include diff --git a/src/network/netdev/geneve.c b/src/network/netdev/geneve.c index bc655ec..22c2b00 100644 --- a/src/network/netdev/geneve.c +++ b/src/network/netdev/geneve.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include -#include #include +#include #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/network/netdev/ipvlan.c b/src/network/netdev/ipvlan.c index 05d5d01..51ae643 100644 --- a/src/network/netdev/ipvlan.c +++ b/src/network/netdev/ipvlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include diff --git a/src/network/netdev/macsec.c b/src/network/netdev/macsec.c index 17d6ace..4b9f19c 100644 --- a/src/network/netdev/macsec.c +++ b/src/network/netdev/macsec.c @@ -18,6 +18,7 @@ #include "socket-util.h" #include "string-table.h" #include "string-util.h" +#include "unaligned.h" static void security_association_clear(SecurityAssociation *sa) { if (!sa) @@ -711,7 +712,7 @@ int config_parse_macsec_key( dest = a ? &a->sa : &b->sa; - r = unhexmem_full(rvalue, strlen(rvalue), true, &p, &l); + r = unhexmem_full(rvalue, SIZE_MAX, /* secure = */ true, &p, &l); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse key. Ignoring assignment: %m"); return 0; @@ -819,7 +820,7 @@ int config_parse_macsec_key_id( if (r < 0) return log_oom(); - r = unhexmem(rvalue, strlen(rvalue), &p, &l); + r = unhexmem(rvalue, &p, &l); if (r == -ENOMEM) return log_oom(); if (r < 0) { diff --git a/src/network/netdev/macvlan.c b/src/network/netdev/macvlan.c index 203807e..21933d3 100644 --- a/src/network/netdev/macvlan.c +++ b/src/network/netdev/macvlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -10,6 +11,11 @@ #include "networkd-network.h" #include "parse-util.h" +typedef enum BCQueueThreshold { + BC_QUEUE_THRESHOLD_UNDEF = INT32_MIN, + BC_QUEUE_THRESHOLD_DISABLE = -1, +} BCQueueThreshold; + DEFINE_CONFIG_PARSE_ENUM(config_parse_macvlan_mode, macvlan_mode, MacVlanMode, "Failed to parse macvlan mode"); static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_netlink_message *req) { @@ -62,6 +68,12 @@ static int netdev_macvlan_fill_message_create(NetDev *netdev, Link *link, sd_net return r; } + if (m->bc_queue_threshold != BC_QUEUE_THRESHOLD_UNDEF) { + r = sd_netlink_message_append_s32(req, IFLA_MACVLAN_BC_CUTOFF, m->bc_queue_threshold); + if (r < 0) + return r; + } + return 0; } @@ -96,6 +108,53 @@ int config_parse_macvlan_broadcast_queue_size( &m->bc_queue_length); } +int config_parse_macvlan_broadcast_queue_threshold( + 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) { + + assert(filename); + assert(lvalue); + assert(rvalue); + + int32_t v, *threshold = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + *threshold = BC_QUEUE_THRESHOLD_UNDEF; + return 0; + } + + if (streq(rvalue, "no")) { + *threshold = BC_QUEUE_THRESHOLD_DISABLE; + return 0; + } + + r = safe_atoi32(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + if (v < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s= value specified, ignoring assignment: %s", + lvalue, rvalue); + return 0; + } + + *threshold = v; + return 0; +} + static void macvlan_done(NetDev *netdev) { MacVlan *m = ASSERT_PTR(netdev)->kind == NETDEV_KIND_MACVLAN ? MACVLAN(netdev) : MACVTAP(netdev); @@ -107,6 +166,7 @@ static void macvlan_init(NetDev *netdev) { m->mode = _NETDEV_MACVLAN_MODE_INVALID; m->bc_queue_length = UINT32_MAX; + m->bc_queue_threshold = BC_QUEUE_THRESHOLD_UNDEF; } const NetDevVTable macvtap_vtable = { diff --git a/src/network/netdev/macvlan.h b/src/network/netdev/macvlan.h index c45fc4f..76b53a6 100644 --- a/src/network/netdev/macvlan.h +++ b/src/network/netdev/macvlan.h @@ -14,6 +14,7 @@ struct MacVlan { Set *match_source_mac; uint32_t bc_queue_length; + int32_t bc_queue_threshold; }; DEFINE_NETDEV_CAST(MACVLAN, MacVlan); @@ -23,3 +24,4 @@ extern const NetDevVTable macvtap_vtable; CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_mode); CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_broadcast_queue_size); +CONFIG_PARSER_PROTOTYPE(config_parse_macvlan_broadcast_queue_threshold); diff --git a/src/network/netdev/netdev-gperf.gperf b/src/network/netdev/netdev-gperf.gperf index d5aa522..4883a26 100644 --- a/src/network/netdev/netdev-gperf.gperf +++ b/src/network/netdev/netdev-gperf.gperf @@ -64,6 +64,7 @@ VLAN.IngressQOSMaps, config_parse_vlan_qos_maps, MACVLAN.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVLAN.SourceMACAddress, config_parse_ether_addrs, 0, offsetof(MacVlan, match_source_mac) MACVLAN.BroadcastMulticastQueueLength, config_parse_macvlan_broadcast_queue_size, 0, offsetof(MacVlan, bc_queue_length) +MACVLAN.BroadcastQueueThreshold, config_parse_macvlan_broadcast_queue_threshold, 0, offsetof(MacVlan, bc_queue_threshold) MACVTAP.Mode, config_parse_macvlan_mode, 0, offsetof(MacVlan, mode) MACVTAP.SourceMACAddress, config_parse_ether_addrs, 0, offsetof(MacVlan, match_source_mac) IPVLAN.Mode, config_parse_ipvlan_mode, 0, offsetof(IPVlan, mode) @@ -215,9 +216,11 @@ Bond.UpDelaySec, config_parse_sec, Bond.DownDelaySec, config_parse_sec, 0, offsetof(Bond, downdelay) Bond.ARPIntervalSec, config_parse_sec, 0, offsetof(Bond, arp_interval) Bond.LearnPacketIntervalSec, config_parse_sec, 0, offsetof(Bond, lp_interval) +Bond.PeerNotifyDelaySec, config_parse_sec, 0, offsetof(Bond, peer_notify_delay) Bond.AdActorSystemPriority, config_parse_ad_actor_sys_prio, 0, offsetof(Bond, ad_actor_sys_prio) Bond.AdUserPortKey, config_parse_ad_user_port_key, 0, offsetof(Bond, ad_user_port_key) Bond.AdActorSystem, config_parse_ad_actor_system, 0, offsetof(Bond, ad_actor_system) +Bond.ARPMissedMax, config_parse_uint8, 0, offsetof(Bond, arp_missed_max) Bridge.HelloTimeSec, config_parse_sec, 0, offsetof(Bridge, hello_time) Bridge.MaxAgeSec, config_parse_sec, 0, offsetof(Bridge, max_age) Bridge.AgeingTimeSec, config_parse_sec, 0, offsetof(Bridge, ageing_time) diff --git a/src/network/netdev/netdev.c b/src/network/netdev/netdev.c index 57127a8..2b41142 100644 --- a/src/network/netdev/netdev.c +++ b/src/network/netdev/netdev.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -198,14 +199,6 @@ static void netdev_detach_from_manager(NetDev *netdev) { static NetDev *netdev_free(NetDev *netdev) { assert(netdev); - netdev_detach_from_manager(netdev); - - free(netdev->filename); - - free(netdev->description); - free(netdev->ifname); - condition_free_list(netdev->conditions); - /* Invoke the per-kind done() destructor, but only if the state field is initialized. We conditionalize that * because we parse .netdev files twice: once to determine the kind (with a short, minimal NetDev structure * allocation, with no room for per-kind fields), and once to read the kind's properties (with a full, @@ -218,6 +211,13 @@ static NetDev *netdev_free(NetDev *netdev) { NETDEV_VTABLE(netdev)->done) NETDEV_VTABLE(netdev)->done(netdev); + netdev_detach_from_manager(netdev); + + condition_free_list(netdev->conditions); + free(netdev->filename); + free(netdev->description); + free(netdev->ifname); + return mfree(netdev); } @@ -449,8 +449,7 @@ int netdev_generate_hw_addr( memcpy(a.bytes, &result, a.length); if (ether_addr_is_null(&a.ether) || ether_addr_is_broadcast(&a.ether)) { - log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "Failed to generate persistent MAC address, ignoring: %m"); + log_netdev_warning(netdev, "Failed to generate persistent MAC address, ignoring."); a = HW_ADDR_NULL; goto finalize; } @@ -458,8 +457,7 @@ int netdev_generate_hw_addr( break; case ARPHRD_INFINIBAND: if (result == 0) { - log_netdev_warning_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "Failed to generate persistent MAC address: %m"); + log_netdev_warning(netdev, "Failed to generate persistent MAC address."); goto finalize; } diff --git a/src/network/netdev/tuntap.c b/src/network/netdev/tuntap.c index 9e909d1..f5be31e 100644 --- a/src/network/netdev/tuntap.c +++ b/src/network/netdev/tuntap.c @@ -1,13 +1,14 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include -#include +#include #include #include #include #include -#include #include "alloc-util.h" #include "daemon-util.h" @@ -33,11 +34,6 @@ static TunTap* TUNTAP(NetDev *netdev) { } } -static void *close_fd_ptr(void *p) { - safe_close(PTR_TO_FD(p)); - return NULL; -} - DEFINE_PRIVATE_HASH_OPS_FULL(named_fd_hash_ops, char, string_hash_func, string_compare_func, free, void, close_fd_ptr); int manager_add_tuntap_fd(Manager *m, int fd, const char *name) { diff --git a/src/network/netdev/veth.c b/src/network/netdev/veth.c index e0f5b4e..7855528 100644 --- a/src/network/netdev/veth.c +++ b/src/network/netdev/veth.c @@ -1,10 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +/* Make sure the net/if.h header is included before any linux/ one */ #include -#include +#include #include #include +#include #include "netlink-util.h" #include "veth.h" diff --git a/src/network/netdev/vlan.c b/src/network/netdev/vlan.c index 2390206..60e49a5 100644 --- a/src/network/netdev/vlan.c +++ b/src/network/netdev/vlan.c @@ -1,7 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include +/* Make sure the net/if.h header is included before any linux/ one */ #include +#include #include #include @@ -91,8 +92,8 @@ static int netdev_vlan_fill_message_create(NetDev *netdev, Link *link, sd_netlin } static void vlan_qos_maps_hash_func(const struct ifla_vlan_qos_mapping *x, struct siphash *state) { - siphash24_compress(&x->from, sizeof(x->from), state); - siphash24_compress(&x->to, sizeof(x->to), state); + siphash24_compress_typesafe(x->from, state); + siphash24_compress_typesafe(x->to, state); } static int vlan_qos_maps_compare_func(const struct ifla_vlan_qos_mapping *a, const struct ifla_vlan_qos_mapping *b) { diff --git a/src/network/netdev/vrf.c b/src/network/netdev/vrf.c index b75ec2b..24079a7 100644 --- a/src/network/netdev/vrf.c +++ b/src/network/netdev/vrf.c @@ -1,8 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include -#include #include +#include #include "vrf.h" diff --git a/src/network/netdev/vxlan.c b/src/network/netdev/vxlan.c index b11fdbb..37f6596 100644 --- a/src/network/netdev/vxlan.c +++ b/src/network/netdev/vxlan.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -289,7 +290,7 @@ int config_parse_port_range( VxLan *v = ASSERT_PTR(userdata); int r; - r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high); + r = parse_ip_port_range(rvalue, &v->port_range.low, &v->port_range.high, /* allow_zero = */ false); if (r < 0) log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VXLAN port range '%s'. Port should be greater than 0 and less than 65535.", rvalue); diff --git a/src/network/netdev/wireguard.c b/src/network/netdev/wireguard.c index 4c7d837..fed1be8 100644 --- a/src/network/netdev/wireguard.c +++ b/src/network/netdev/wireguard.c @@ -3,15 +3,17 @@ Copyright © 2015-2017 Jason A. Donenfeld . All Rights Reserved. ***/ -#include +/* Make sure the net/if.h header is included before any linux/ one */ #include -#include #include #include +#include +#include #include "sd-resolve.h" #include "alloc-util.h" +#include "creds-util.h" #include "dns-domain.h" #include "event-util.h" #include "fd-util.h" @@ -25,6 +27,7 @@ #include "networkd-util.h" #include "parse-helpers.h" #include "parse-util.h" +#include "path-util.h" #include "random-util.h" #include "resolve-private.h" #include "string-util.h" @@ -480,6 +483,8 @@ static int wireguard_decode_key_and_warn( const char *lvalue) { _cleanup_(erase_and_freep) void *key = NULL; + _cleanup_(erase_and_freep) char *cred = NULL; + const char *cred_name; size_t len; int r; @@ -493,10 +498,22 @@ static int wireguard_decode_key_and_warn( return 0; } - if (!streq(lvalue, "PublicKey")) + cred_name = startswith(rvalue, "@"); + if (cred_name) { + r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to read credential for wireguard key (%s=), ignoring assignment: %m", + lvalue); + return 0; + } + + } else if (!streq(lvalue, "PublicKey")) (void) warn_file_is_world_accessible(filename, NULL, unit, line); - r = unbase64mem_full(rvalue, strlen(rvalue), true, &key, &len); + r = unbase64mem_full(cred ?: rvalue, SIZE_MAX, /* secure = */ true, &key, &len); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -721,23 +738,39 @@ int config_parse_wireguard_endpoint( void *data, void *userdata) { - assert(filename); - assert(rvalue); - assert(userdata); - Wireguard *w = WIREGUARD(userdata); _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL; - _cleanup_free_ char *host = NULL; - union in_addr_union addr; - const char *p; + _cleanup_free_ char *cred = NULL; + const char *cred_name, *endpoint; uint16_t port; - int family, r; + int r; + + assert(filename); + assert(rvalue); r = wireguard_peer_new_static(w, filename, section_line, &peer); if (r < 0) return log_oom(); - r = in_addr_port_ifindex_name_from_string_auto(rvalue, &family, &addr, &port, NULL, NULL); + cred_name = startswith(rvalue, "@"); + if (cred_name) { + r = read_credential(cred_name, (void**) &cred, /* ret_size = */ NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to read credential for wireguard endpoint, ignoring assignment: %m"); + return 0; + } + + endpoint = strstrip(cred); + } else + endpoint = rvalue; + + union in_addr_union addr; + int family; + + r = in_addr_port_ifindex_name_from_string_auto(endpoint, &family, &addr, &port, NULL, NULL); if (r >= 0) { if (family == AF_INET) peer->endpoint.in = (struct sockaddr_in) { @@ -761,17 +794,23 @@ int config_parse_wireguard_endpoint( return 0; } - p = strrchr(rvalue, ':'); + _cleanup_free_ char *host = NULL; + const char *p; + + p = strrchr(endpoint, ':'); if (!p) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Unable to find port of endpoint, ignoring assignment: %s", - rvalue); + rvalue); /* We log the original assignment instead of resolved credential here, + as the latter might be previously encrypted and we'd expose them in + unprotected logs otherwise. */ return 0; } - host = strndup(rvalue, p - rvalue); + host = strndup(endpoint, p - endpoint); if (!host) return log_oom(); + p++; if (!dns_name_is_valid(host)) { log_syntax(unit, LOG_WARNING, filename, line, 0, @@ -780,7 +819,6 @@ int config_parse_wireguard_endpoint( return 0; } - p++; r = parse_ip_port(p, &port); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, @@ -1078,6 +1116,55 @@ static int wireguard_peer_verify(WireguardPeer *peer) { return 0; } +static int wireguard_read_default_key_cred(NetDev *netdev, const char *filename) { + Wireguard *w = WIREGUARD(netdev); + _cleanup_free_ char *config_name = NULL; + int r; + + assert(filename); + + r = path_extract_filename(filename, &config_name); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: Failed to extract config name, ignoring network device: %m", + filename); + + char *p = endswith(config_name, ".netdev"); + if (!p) + /* Fuzzer run? Then we just ignore this device. */ + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid netdev config name, refusing default key lookup.", + filename); + *p = '\0'; + + _cleanup_(erase_and_freep) char *cred = NULL; + + r = read_credential(strjoina("network.wireguard.private.", config_name), (void**) &cred, /* ret_size = */ NULL); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: No private key specified and default key isn't available, " + "ignoring network device: %m", + filename); + + _cleanup_(erase_and_freep) void *key = NULL; + size_t len; + + r = unbase64mem_full(cred, SIZE_MAX, /* secure = */ true, &key, &len); + if (r < 0) + return log_netdev_error_errno(netdev, r, + "%s: No private key specified and default key cannot be parsed, " + "ignoring network device: %m", + filename); + if (len != WG_KEY_LEN) + return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), + "%s: No private key specified and default key is invalid. " + "Ignoring network device.", + filename); + + memcpy(w->private_key, key, WG_KEY_LEN); + return 0; +} + static int wireguard_verify(NetDev *netdev, const char *filename) { Wireguard *w = WIREGUARD(netdev); int r; @@ -1088,10 +1175,11 @@ static int wireguard_verify(NetDev *netdev, const char *filename) { "Failed to read private key from %s. Ignoring network device.", w->private_key_file); - if (eqzero(w->private_key)) - return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL), - "%s: Missing PrivateKey= or PrivateKeyFile=, " - "Ignoring network device.", filename); + if (eqzero(w->private_key)) { + r = wireguard_read_default_key_cred(netdev, filename); + if (r < 0) + return r; + } LIST_FOREACH(peers, peer, w->peers) { if (wireguard_peer_verify(peer) < 0) { @@ -1103,26 +1191,39 @@ static int wireguard_verify(NetDev *netdev, const char *filename) { continue; LIST_FOREACH(ipmasks, ipmask, peer->ipmasks) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; r = route_new(&route); if (r < 0) return log_oom(); + /* For route_section_verify() below. */ + r = config_section_new(peer->section->filename, peer->section->line, &route->section); + if (r < 0) + return log_oom(); + + route->source = NETWORK_CONFIG_SOURCE_STATIC; route->family = ipmask->family; route->dst = ipmask->ip; route->dst_prefixlen = ipmask->cidr; - route->scope = RT_SCOPE_UNIVERSE; route->protocol = RTPROT_STATIC; + route->protocol_set = true; route->table = peer->route_table_set ? peer->route_table : w->route_table; + route->table_set = true; route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority; - if (route->priority == 0 && route->family == AF_INET6) - route->priority = IP6_RT_PRIO_USER; - route->source = NETWORK_CONFIG_SOURCE_STATIC; + route->priority_set = true; - r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route)); + if (route_section_verify(route) < 0) + continue; + + r = set_ensure_put(&w->routes, &route_hash_ops, route); if (r < 0) return log_oom(); + if (r == 0) + continue; + + route->wireguard = w; + TAKE_PTR(route); } } diff --git a/src/network/networkctl-config-file.c b/src/network/networkctl-config-file.c new file mode 100644 index 0000000..216e9d4 --- /dev/null +++ b/src/network/networkctl-config-file.c @@ -0,0 +1,632 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-daemon.h" +#include "sd-device.h" +#include "sd-netlink.h" +#include "sd-network.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" +#include "conf-files.h" +#include "edit-util.h" +#include "mkdir-label.h" +#include "netlink-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" +#include "pager.h" +#include "path-lookup.h" +#include "path-util.h" +#include "pretty-print.h" +#include "selinux-util.h" +#include "strv.h" +#include "virt.h" + +typedef enum ReloadFlags { + RELOAD_NETWORKD = 1 << 0, + RELOAD_UDEVD = 1 << 1, +} ReloadFlags; + +static int get_config_files_by_name( + const char *name, + bool allow_masked, + char **ret_path, + char ***ret_dropins) { + + _cleanup_free_ char *path = NULL; + int r; + + assert(name); + assert(ret_path); + + STRV_FOREACH(i, NETWORK_DIRS) { + _cleanup_free_ char *p = NULL; + + p = path_join(*i, name); + if (!p) + return -ENOMEM; + + r = RET_NERRNO(access(p, F_OK)); + if (r >= 0) { + if (!allow_masked) { + r = null_or_empty_path(p); + if (r < 0) + return log_debug_errno(r, + "Failed to check if network config '%s' is masked: %m", + name); + if (r > 0) + return -ERFKILL; + } + + path = TAKE_PTR(p); + break; + } + + if (r != -ENOENT) + log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p); + } + + if (!path) + return -ENOENT; + + if (ret_dropins) { + _cleanup_free_ char *dropin_dirname = NULL; + + dropin_dirname = strjoin(name, ".d"); + if (!dropin_dirname) + return -ENOMEM; + + r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS); + if (r < 0) + return r; + } + + *ret_path = TAKE_PTR(path); + + return 0; +} + +static int get_dropin_by_name( + const char *name, + char * const *dropins, + char **ret) { + + assert(name); + assert(ret); + + STRV_FOREACH(i, dropins) + if (path_equal_filename(*i, name)) { + _cleanup_free_ char *d = NULL; + + d = strdup(*i); + if (!d) + return -ENOMEM; + + *ret = TAKE_PTR(d); + return 1; + } + + *ret = NULL; + return 0; +} + +static int get_network_files_by_link( + sd_netlink **rtnl, + const char *link, + char **ret_path, + char ***ret_dropins) { + + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + int r, ifindex; + + assert(rtnl); + assert(link); + assert(ret_path); + assert(ret_dropins); + + ifindex = rtnl_resolve_interface_or_warn(rtnl, link); + if (ifindex < 0) + return ifindex; + + r = sd_network_link_get_network_file(ifindex, &path); + if (r == -ENODATA) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Link '%s' has no associated network file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get network file for link '%s': %m", link); + + r = sd_network_link_get_network_file_dropins(ifindex, &dropins); + if (r < 0 && r != -ENODATA) + return log_error_errno(r, "Failed to get network drop-ins for link '%s': %m", link); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + return 0; +} + +static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_strv_free_ char **dropins_split = NULL; + _cleanup_free_ char *p = NULL; + const char *path, *dropins; + int r; + + assert(link); + assert(ret_path); + assert(ret_dropins); + + r = sd_device_new_from_ifname(&device, link); + if (r < 0) + return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path); + if (r == -ENOENT) + return log_error_errno(r, "Link '%s' has no associated link file.", link); + if (r < 0) + return log_error_errno(r, "Failed to get link file for link '%s': %m", link); + + r = sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &dropins); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to get link drop-ins for link '%s': %m", link); + if (r >= 0) { + r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE); + if (r < 0) + return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link); + } + + p = strdup(path); + if (!p) + return log_oom(); + + *ret_path = TAKE_PTR(p); + *ret_dropins = TAKE_PTR(dropins_split); + + return 0; +} + +static int get_config_files_by_link_config( + const char *link_config, + sd_netlink **rtnl, + char **ret_path, + char ***ret_dropins, + ReloadFlags *ret_reload) { + + _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL; + _cleanup_free_ char *path = NULL; + const char *ifname, *type; + ReloadFlags reload; + size_t n; + int r; + + assert(link_config); + assert(rtnl); + assert(ret_path); + assert(ret_dropins); + + link_config_split = strv_split(link_config, ":"); + if (!link_config_split) + return log_oom(); + + n = strv_length(link_config_split); + if (n == 0 || isempty(link_config_split[0])) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); + if (n > 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config); + + ifname = link_config_split[0]; + type = n == 2 ? link_config_split[1] : "network"; + + if (streq(type, "network")) { + if (!networkd_is_running()) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), + "Cannot get network file for link if systemd-networkd is not running."); + + r = get_network_files_by_link(rtnl, ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_NETWORKD; + } else if (streq(type, "link")) { + r = get_link_files_by_link(ifname, &path, &dropins); + if (r < 0) + return r; + + reload = RELOAD_UDEVD; + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid config type '%s' for link '%s'.", type, ifname); + + *ret_path = TAKE_PTR(path); + *ret_dropins = TAKE_PTR(dropins); + + if (ret_reload) + *ret_reload = reload; + + return 0; +} + +static int add_config_to_edit( + EditFileContext *context, + const char *path, + char * const *dropins) { + + _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL; + _cleanup_strv_free_ char **comment_paths = NULL; + int r; + + assert(context); + assert(path); + + /* If we're supposed to edit main config file in /run/, but a config with the same name is present + * under /etc/, we bail out since the one in /etc/ always overrides that in /run/. */ + if (arg_runtime && !arg_drop_in && path_startswith(path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot edit runtime config file: overridden by %s", path); + + if (path_startswith(path, "/usr") || arg_runtime != !!path_startswith(path, "/run")) { + _cleanup_free_ char *name = NULL; + + r = path_extract_filename(path, &name); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from '%s': %m", path); + + new_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], name); + if (!new_path) + return log_oom(); + } + + if (!arg_drop_in) + return edit_files_add(context, new_path ?: path, path, NULL); + + bool need_new_dropin; + + r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin); + if (r < 0) + return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in); + if (r > 0) { + /* See the explanation above */ + if (arg_runtime && path_startswith(old_dropin, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot edit runtime config file: overridden by %s", old_dropin); + + need_new_dropin = path_startswith(old_dropin, "/usr") || arg_runtime != !!path_startswith(old_dropin, "/run"); + } else + need_new_dropin = true; + + if (!need_new_dropin) + /* An existing drop-in is found in the correct scope. Let's edit it directly. */ + dropin_path = TAKE_PTR(old_dropin); + else { + /* No drop-in was found or an existing drop-in is in a different scope. Let's create a new + * drop-in file. */ + dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in); + if (!dropin_path) + return log_oom(); + } + + comment_paths = strv_new(path); + if (!comment_paths) + return log_oom(); + + r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false); + if (r < 0) + return log_oom(); + + return edit_files_add(context, dropin_path, old_dropin, comment_paths); +} + +static int udevd_reload(sd_bus *bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + const char *job_path; + int r; + + assert(bus); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + + r = bus_call_method(bus, + bus_systemd_mgr, + "ReloadUnit", + &error, + &reply, + "ss", + "systemd-udevd.service", + "replace"); + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job_path); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, job_path, /* flags = */ 0, NULL); + if (r == -ENOEXEC) { + log_debug("systemd-udevd is not running, skipping reload."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to reload systemd-udevd: %m"); + + return 1; +} + +static int reload_daemons(ReloadFlags flags) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r, ret = 1; + + if (arg_no_reload) + return 0; + + if (flags == 0) + return 0; + + if (!sd_booted() || running_in_chroot() > 0) { + log_debug("System is not booted with systemd or is running in chroot, skipping reload."); + return 0; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + if (FLAGS_SET(flags, RELOAD_UDEVD)) + RET_GATHER(ret, udevd_reload(bus)); + + if (FLAGS_SET(flags, RELOAD_NETWORKD)) { + if (networkd_is_running()) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); + if (r < 0) + RET_GATHER(ret, log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r))); + } else + log_debug("systemd-networkd is not running, skipping reload."); + } + + return ret; +} + +int verb_edit(int argc, char *argv[], void *userdata) { + _cleanup_(edit_file_context_done) EditFileContext context = { + .marker_start = DROPIN_MARKER_START, + .marker_end = DROPIN_MARKER_END, + .remove_parent = !!arg_drop_in, + }; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + ReloadFlags reload = 0; + int r; + + if (!on_tty()) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty."); + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + ReloadFlags flags; + + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); + if (r < 0) + return r; + + reload |= flags; + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + + continue; + } + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload |= RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload |= RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); + if (r == -ERFKILL) + return log_error_errno(r, "Network config '%s' is masked.", *name); + if (r == -ENOENT) { + if (arg_drop_in) + return log_error_errno(r, "Cannot find network config '%s'.", *name); + + log_debug("No existing network config '%s' found, creating a new file.", *name); + + path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!path) + return log_oom(); + + r = edit_files_add(&context, path, NULL, NULL); + if (r < 0) + return r; + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = add_config_to_edit(&context, path, dropins); + if (r < 0) + return r; + } + + r = do_edit_files_and_install(&context); + if (r < 0) + return r; + + return reload_daemons(reload); +} + +int verb_cat(int argc, char *argv[], void *userdata) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **args = strv_skip(argv, 1); + int r, ret = 0; + + pager_open(arg_pager_flags); + + if (strv_isempty(args)) + return conf_files_cat(NULL, "systemd/networkd.conf", CAT_FORMAT_HAS_SECTIONS); + + bool first = true; + STRV_FOREACH(name, args) { + _cleanup_strv_free_ char **dropins = NULL; + _cleanup_free_ char *path = NULL; + const char *link_config; + + link_config = startswith(*name, "@"); + if (link_config) { + r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL); + if (r < 0) + return RET_GATHER(ret, r); + } else { + r = get_config_files_by_name(*name, /* allow_masked = */ false, &path, &dropins); + if (r == -ENOENT) { + RET_GATHER(ret, log_error_errno(r, "Cannot find network config file '%s'.", *name)); + continue; + } + if (r == -ERFKILL) { + RET_GATHER(ret, log_debug_errno(r, "Network config '%s' is masked, ignoring.", *name)); + continue; + } + if (r < 0) { + log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + return RET_GATHER(ret, r); + } + } + + if (!first) + putchar('\n'); + + r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); + if (r < 0) + return RET_GATHER(ret, r); + + first = false; + } + + return ret; +} + +int verb_mask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + r = mac_selinux_init(); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *config_path = NULL, *symlink_path = NULL; + ReloadFlags reload; + + /* We update the real 'flags' at last, since the operation can be skipped. */ + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &config_path, /* ret_dropins = */ NULL); + if (r == -ENOENT) + log_warning("No existing network config '%s' found, proceeding anyway.", *name); + else if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + else if (!path_startswith(config_path, "/usr")) { + r = null_or_empty_path(config_path); + if (r < 0) + return log_error_errno(r, + "Failed to check if '%s' is masked: %m", config_path); + if (r > 0) { + log_debug("%s is already masked, skipping.", config_path); + continue; + } + + /* At this point, we have found a config under mutable dir (/run/ or /etc/), + * so masking through /run/ (--runtime) is not possible. If it's under /etc/, + * then it doesn't work without --runtime either. */ + if (arg_runtime || path_startswith(config_path, "/etc")) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Cannot mask network config %s: %s exists", + *name, config_path); + } + + symlink_path = path_join(NETWORK_DIRS[arg_runtime ? 1 : 0], *name); + if (!symlink_path) + return log_oom(); + + (void) mkdir_parents_label(symlink_path, 0755); + + if (symlink("/dev/null", symlink_path) < 0) + return log_error_errno(errno, + "Failed to create symlink '%s' to /dev/null: %m", symlink_path); + + flags |= reload; + log_info("Successfully created symlink '%s' to /dev/null.", symlink_path); + } + + return reload_daemons(flags); +} + +int verb_unmask(int argc, char *argv[], void *userdata) { + ReloadFlags flags = 0; + int r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *path = NULL; + ReloadFlags reload; + + if (ENDSWITH_SET(*name, ".network", ".netdev")) + reload = RELOAD_NETWORKD; + else if (endswith(*name, ".link")) + reload = RELOAD_UDEVD; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); + + r = get_config_files_by_name(*name, /* allow_masked = */ true, &path, /* ret_dropins = */ NULL); + if (r == -ENOENT) { + log_debug_errno(r, "Network configuration '%s' doesn't exist, skipping.", *name); + continue; + } + if (r < 0) + return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); + + r = null_or_empty_path(path); + if (r < 0) + return log_error_errno(r, "Failed to check if '%s' is masked: %m", path); + if (r == 0) + continue; + + if (path_startswith(path, "/usr")) + return log_error_errno(r, "Cannot unmask network config under /usr/: %s", path); + + if (unlink(path) < 0) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to remove '%s': %m", path); + } + + flags |= reload; + log_info("Successfully removed masked network config '%s'.", path); + } + + return reload_daemons(flags); +} diff --git a/src/network/networkctl-config-file.h b/src/network/networkctl-config-file.h new file mode 100644 index 0000000..38210a8 --- /dev/null +++ b/src/network/networkctl-config-file.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int verb_edit(int argc, char *argv[], void *userdata); +int verb_cat(int argc, char *argv[], void *userdata); + +int verb_mask(int argc, char *argv[], void *userdata); +int verb_unmask(int argc, char *argv[], void *userdata); diff --git a/src/network/networkctl.c b/src/network/networkctl.c index ec31e8e..a447c39 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -1,9 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include -#include #include #include #include @@ -15,7 +16,6 @@ #include "sd-device.h" #include "sd-dhcp-client.h" #include "sd-hwdb.h" -#include "sd-lldp-rx.h" #include "sd-netlink.h" #include "sd-network.h" @@ -26,10 +26,7 @@ #include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" -#include "bus-wait-for-jobs.h" -#include "conf-files.h" #include "device-util.h" -#include "edit-util.h" #include "escape.h" #include "ether-addr-util.h" #include "ethtool-util.h" @@ -41,6 +38,7 @@ #include "glob-util.h" #include "hwdb-util.h" #include "ipvlan-util.h" +#include "journal-internal.h" #include "local-addresses.h" #include "locale-util.h" #include "logs-show.h" @@ -51,6 +49,8 @@ #include "netlink-util.h" #include "network-internal.h" #include "network-util.h" +#include "networkctl.h" +#include "networkctl-config-file.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" @@ -71,8 +71,8 @@ #include "terminal-util.h" #include "udev-util.h" #include "unit-def.h" +#include "varlink.h" #include "verbs.h" -#include "virt.h" #include "wifi-util.h" /* Kernel defines MODULE_NAME_LEN as 64 - sizeof(unsigned long). So, 64 is enough. */ @@ -81,47 +81,67 @@ /* use 128 kB for receive socket kernel queue, we shouldn't need more here */ #define RCVBUF_SIZE (128*1024) -static PagerFlags arg_pager_flags = 0; -static bool arg_legend = true; -static bool arg_no_reload = false; -static bool arg_all = false; -static bool arg_stats = false; -static bool arg_full = false; -static unsigned arg_lines = 10; -static char *arg_drop_in = NULL; -static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; +PagerFlags arg_pager_flags = 0; +bool arg_legend = true; +bool arg_no_reload = false; +bool arg_all = false; +bool arg_stats = false; +bool arg_full = false; +bool arg_runtime = false; +unsigned arg_lines = 10; +char *arg_drop_in = NULL; +JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; STATIC_DESTRUCTOR_REGISTER(arg_drop_in, freep); -static int check_netns_match(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct stat st; +static int varlink_connect_networkd(Varlink **ret_varlink) { + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; + JsonVariant *reply; uint64_t id; int r; - assert(bus); + r = varlink_connect_address(&vl, "/run/systemd/netif/io.systemd.Network"); + if (r < 0) + return log_error_errno(r, "Failed to connect to network service /run/systemd/netif/io.systemd.Network: %m"); - 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) { + (void) varlink_set_description(vl, "varlink-network"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to allow passing file descriptor through varlink: %m"); + + r = varlink_call_and_log(vl, "io.systemd.Network.GetNamespaceId", /* parameters= */ NULL, &reply); + if (r < 0) + return r; + + static const JsonDispatch dispatch_table[] = { + { "NamespaceId", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, 0, JSON_MANDATORY }, + {}, + }; + + r = json_dispatch(reply, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &id); + if (r < 0) + return r; + + if (id == 0) log_debug("systemd-networkd.service not running in a network namespace (?), skipping netns check."); - return 0; - } + else { + struct stat st; - if (stat("/proc/self/ns/net", &st) < 0) - return log_error_errno(errno, "Failed to determine our own network namespace ID: %m"); + 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."); + if (id != st.st_ino) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), + "networkctl must be invoked in same network namespace as systemd-networkd.service."); + } + if (ret_varlink) + *ret_varlink = TAKE_PTR(vl); return 0; } -static bool networkd_is_running(void) { +bool networkd_is_running(void) { static int cached = -1; int r; @@ -140,7 +160,7 @@ static bool networkd_is_running(void) { return cached; } -static int acquire_bus(sd_bus **ret) { +int acquire_bus(sd_bus **ret) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; @@ -151,7 +171,7 @@ static int acquire_bus(sd_bus **ret) { return log_error_errno(r, "Failed to connect to system bus: %m"); if (networkd_is_running()) { - r = check_netns_match(bus); + r = varlink_connect_networkd(/* ret_varlink = */ NULL); if (r < 0) return r; } else @@ -781,14 +801,14 @@ static void acquire_ether_link_info(int *fd, LinkInfo *link) { static void acquire_wlan_link_info(LinkInfo *link) { _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; - const char *type = NULL; int r, k = 0; assert(link); - if (link->sd_device) - (void) sd_device_get_devtype(link->sd_device, &type); - if (!streq_ptr(type, "wlan")) + if (!link->sd_device) + return; + + if (!device_is_devtype(link->sd_device, "wlan")) return; r = sd_genl_socket_open(&genl); @@ -1053,9 +1073,7 @@ static int get_gateway_description( } if (type != RTM_NEWNEIGH) { - log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Got unexpected netlink message type %u, ignoring", - type); + log_error("Got unexpected netlink message type %u, ignoring.", type); continue; } @@ -1066,7 +1084,7 @@ static int get_gateway_description( } if (fam != family) { - log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Got invalid rtnl family %d, ignoring", fam); + log_error("Got invalid rtnl family %d, ignoring.", fam); continue; } @@ -1096,7 +1114,7 @@ static int get_gateway_description( break; default: - continue; + assert_not_reached(); } if (!in_addr_equal(fam, &gw, gateway)) @@ -1198,7 +1216,7 @@ static int dump_addresses( r = strv_extendf(&buf, "%s%s%s%s%s%s", IN_ADDR_TO_STRING(local->family, &local->address), - dhcp4 ? " (DHCP4 via " : "", + dhcp4 ? " (DHCPv4 via " : "", dhcp4 ? IN4_ADDR_TO_STRING(&server_address) : "", dhcp4 ? ")" : "", ifindex <= 0 ? " on " : "", @@ -1300,96 +1318,96 @@ static int list_address_labels(int argc, char *argv[], void *userdata) { 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)]; - - assert(ifindex >= 0); - assert(ret); - - xsprintf(p, "/run/systemd/netif/lldp/%i", ifindex); - - f = fopen(p, "re"); - if (!f) - return -errno; - - *ret = TAKE_PTR(f); - return 0; -} - -static int next_lldp_neighbor(FILE *f, sd_lldp_neighbor **ret) { - _cleanup_free_ void *raw = NULL; - size_t l; - le64_t u; - int r; - - assert(f); - assert(ret); - - l = fread(&u, 1, sizeof(u), f); - if (l == 0 && feof(f)) - return 0; - if (l != sizeof(u)) - return -EBADMSG; - - /* each LLDP packet is at most MTU size, but let's allow up to 4KiB just in case */ - if (le64toh(u) >= 4096) - return -EBADMSG; - - raw = new(uint8_t, le64toh(u)); - if (!raw) - return -ENOMEM; - - if (fread(raw, 1, le64toh(u), f) != le64toh(u)) - return -EBADMSG; - - r = sd_lldp_neighbor_from_raw(ret, raw, le64toh(u)); - if (r < 0) - return r; +typedef struct InterfaceInfo { + int ifindex; + const char *ifname; + char **altnames; + JsonVariant *v; +} InterfaceInfo; - return 1; -} +static void interface_info_done(InterfaceInfo *p) { + if (!p) + return; -static int dump_lldp_neighbors(Table *table, const char *prefix, int ifindex) { + strv_free(p->altnames); + json_variant_unref(p->v); +} + +static const JsonDispatch interface_info_dispatch_table[] = { + { "InterfaceIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(InterfaceInfo, ifindex), JSON_MANDATORY }, + { "InterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(InterfaceInfo, ifname), JSON_MANDATORY }, + { "InterfaceAlternativeNames", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(InterfaceInfo, altnames), 0 }, + { "Neighbors", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(InterfaceInfo, v), 0 }, + {}, +}; + +typedef struct LLDPNeighborInfo { + const char *chassis_id; + const char *port_id; + const char *port_description; + const char *system_name; + const char *system_description; + uint16_t capabilities; +} LLDPNeighborInfo; + +static const JsonDispatch lldp_neighbor_dispatch_table[] = { + { "ChassisID", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, chassis_id), 0 }, + { "PortID", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_id), 0 }, + { "PortDescription", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, port_description), 0 }, + { "SystemName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_name), 0 }, + { "SystemDescription", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LLDPNeighborInfo, system_description), 0 }, + { "EnabledCapabilities", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LLDPNeighborInfo, capabilities), 0 }, + {}, +}; + +static int dump_lldp_neighbors(Varlink *vl, Table *table, int ifindex) { _cleanup_strv_free_ char **buf = NULL; - _cleanup_fclose_ FILE *f = NULL; + JsonVariant *reply; int r; + assert(vl); assert(table); - assert(prefix); assert(ifindex > 0); - r = open_lldp_neighbors(ifindex, &f); - if (r == -ENOENT) - return 0; + r = varlink_callb_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", &reply, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_INTEGER("InterfaceIndex", ifindex))); if (r < 0) return r; - for (;;) { - const char *system_name = NULL, *port_id = NULL, *port_description = NULL; - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; - r = next_lldp_neighbor(f, &n); + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); if (r < 0) return r; - if (r == 0) - break; - (void) sd_lldp_neighbor_get_system_name(n, &system_name); - (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); - (void) sd_lldp_neighbor_get_port_description(n, &port_description); + if (info.ifindex != ifindex) + continue; - r = strv_extendf(&buf, "%s on port %s%s%s%s", - strna(system_name), - strna(port_id), - isempty(port_description) ? "" : " (", - strempty(port_description), - isempty(port_description) ? "" : ")"); - if (r < 0) - return log_oom(); + JsonVariant *neighbor; + JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) { + LLDPNeighborInfo neighbor_info = {}; + + r = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &neighbor_info); + if (r < 0) + return r; + + r = strv_extendf(&buf, "%s%s%s%s on port %s%s%s%s", + strna(neighbor_info.system_name), + isempty(neighbor_info.system_description) ? "" : " (", + strempty(neighbor_info.system_description), + isempty(neighbor_info.system_description) ? "" : ")", + strna(neighbor_info.port_id), + isempty(neighbor_info.port_description) ? "" : " (", + strempty(neighbor_info.port_description), + isempty(neighbor_info.port_description) ? "" : ")"); + if (r < 0) + return log_oom(); + } } - return dump_list(table, prefix, buf); + return dump_list(table, "Connected To", buf); } static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const LinkInfo *link) { @@ -1447,7 +1465,7 @@ static int dump_dhcp_leases(Table *table, const char *prefix, sd_bus *bus, const if (r < 0) return bus_log_parse_error(r); - r = sd_dhcp_client_id_to_string(client_id, client_id_sz, &id); + r = sd_dhcp_client_id_to_string_from_raw(client_id, client_id_sz, &id); if (r < 0) return bus_log_parse_error(r); @@ -1586,7 +1604,7 @@ static int show_logs(const LinkInfo *info) { if (arg_lines == 0) return 0; - r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); + r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); @@ -1595,22 +1613,12 @@ static int show_logs(const LinkInfo *info) { return log_error_errno(r, "Failed to add boot matches: %m"); if (info) { - char m1[STRLEN("_KERNEL_DEVICE=n") + DECIMAL_STR_MAX(int)]; - const char *m2, *m3; - - /* kernel */ - xsprintf(m1, "_KERNEL_DEVICE=n%i", info->ifindex); - /* networkd */ - m2 = strjoina("INTERFACE=", info->name); - /* udevd */ - m3 = strjoina("DEVICE=", info->name); - - (void)( - (r = sd_journal_add_match(j, m1, 0)) || + (void) ( + (r = journal_add_matchf(j, "_KERNEL_DEVICE=n%i", info->ifindex)) || /* kernel */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || + (r = journal_add_match_pair(j, "INTERFACE", info->name)) || /* networkd */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) + (r = journal_add_match_pair(j, "DEVICE", info->name)) /* udevd */ ); if (r < 0) return log_error_errno(r, "Failed to add link matches: %m"); @@ -1672,6 +1680,7 @@ static int link_status_one( sd_bus *bus, sd_netlink *rtnl, sd_hwdb *hwdb, + Varlink *vl, const LinkInfo *info) { _cleanup_strv_free_ char **dns = NULL, **ntp = NULL, **sip = NULL, **search_domains = NULL, @@ -1687,6 +1696,7 @@ static int link_status_one( assert(bus); assert(rtnl); + assert(vl); assert(info); (void) sd_network_link_get_operational_state(info->ifindex, &operational_state); @@ -2260,8 +2270,7 @@ static int link_status_one( } if (lease) { - const void *client_id; - size_t client_id_len; + const sd_dhcp_client_id *client_id; const char *tz; r = sd_dhcp_lease_get_timezone(lease, &tz); @@ -2273,14 +2282,14 @@ static int link_status_one( return table_log_add_error(r); } - r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); + r = sd_dhcp_lease_get_client_id(lease, &client_id); if (r >= 0) { _cleanup_free_ char *id = NULL; - r = sd_dhcp_client_id_to_string(client_id, client_id_len, &id); + r = sd_dhcp_client_id_to_string(client_id, &id); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP4 Client ID", + TABLE_FIELD, "DHCPv4 Client ID", TABLE_STRING, id); if (r < 0) return table_log_add_error(r); @@ -2291,7 +2300,7 @@ static int link_status_one( r = sd_network_link_get_dhcp6_client_iaid_string(info->ifindex, &iaid); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP6 Client IAID", + TABLE_FIELD, "DHCPv6 Client IAID", TABLE_STRING, iaid); if (r < 0) return table_log_add_error(r); @@ -2300,13 +2309,13 @@ static int link_status_one( r = sd_network_link_get_dhcp6_client_duid_string(info->ifindex, &duid); if (r >= 0) { r = table_add_many(table, - TABLE_FIELD, "DHCP6 Client DUID", + TABLE_FIELD, "DHCPv6 Client DUID", TABLE_STRING, duid); if (r < 0) return table_log_add_error(r); } - r = dump_lldp_neighbors(table, "Connected To", info->ifindex); + r = dump_lldp_neighbors(vl, table, info->ifindex); if (r < 0) return r; @@ -2414,6 +2423,7 @@ static int link_status(int argc, char *argv[], void *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_(varlink_flush_close_unrefp) Varlink *vl = NULL; _cleanup_(link_info_array_freep) LinkInfo *links = NULL; int r, c; @@ -2438,6 +2448,10 @@ static int link_status(int argc, char *argv[], void *userdata) { if (r < 0) log_debug_errno(r, "Failed to open hardware database: %m"); + r = varlink_connect_networkd(&vl); + if (r < 0) + return r; + if (arg_all) c = acquire_link_info(bus, rtnl, NULL, &links); else if (argc <= 1) @@ -2454,7 +2468,7 @@ static int link_status(int argc, char *argv[], void *userdata) { if (!first) putchar('\n'); - RET_GATHER(r, link_status_one(bus, rtnl, hwdb, i)); + RET_GATHER(r, link_status_one(bus, rtnl, hwdb, vl, i)); first = false; } @@ -2462,7 +2476,7 @@ static int link_status(int argc, char *argv[], void *userdata) { return r; } -static char *lldp_capabilities_to_string(uint16_t x) { +static char *lldp_capabilities_to_string(uint64_t x) { static const char characters[] = { 'o', 'p', 'b', 'w', 'r', 't', 'd', 'a', 'c', 's', 'm', }; @@ -2512,30 +2526,94 @@ static void lldp_capabilities_legend(uint16_t x) { puts(""); } +static bool interface_match_pattern(const InterfaceInfo *info, char * const *patterns) { + assert(info); + + if (strv_isempty(patterns)) + return true; + + if (strv_fnmatch(patterns, info->ifname)) + return true; + + char str[DECIMAL_STR_MAX(int)]; + xsprintf(str, "%i", info->ifindex); + if (strv_fnmatch(patterns, str)) + return true; + + STRV_FOREACH(a, info->altnames) + if (strv_fnmatch(patterns, *a)) + return true; + + return false; +} + +static int dump_lldp_neighbors_json(JsonVariant *reply, char * const *patterns) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL, *v = NULL; + int r; + + assert(reply); + + if (strv_isempty(patterns)) + return json_variant_dump(reply, arg_json_format_flags, NULL, NULL); + + /* Filter and dump the result. */ + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; + + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); + if (r < 0) + return r; + + if (!interface_match_pattern(&info, patterns)) + continue; + + r = json_variant_append_array(&array, i); + if (r < 0) + return log_error_errno(r, "Failed to append json variant to array: %m"); + } + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array)))); + if (r < 0) + return log_error_errno(r, "Failed to build json varinat: %m"); + + return json_variant_dump(v, arg_json_format_flags, NULL, NULL); +} + static int link_lldp_status(int argc, char *argv[], void *userdata) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(link_info_array_freep) LinkInfo *links = NULL; + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; _cleanup_(table_unrefp) Table *table = NULL; - int r, c, m = 0; - uint16_t all = 0; + JsonVariant *reply; + uint64_t all = 0; TableCell *cell; + size_t m = 0; + int r; - r = sd_netlink_open(&rtnl); + r = varlink_connect_networkd(&vl); if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); + return r; - c = acquire_link_info(NULL, rtnl, argc > 1 ? argv + 1 : NULL, &links); - if (c < 0) - return c; + r = varlink_call_and_log(vl, "io.systemd.Network.GetLLDPNeighbors", NULL, &reply); + if (r < 0) + return r; + + if (arg_json_format_flags != JSON_FORMAT_OFF) + return dump_lldp_neighbors_json(reply, strv_skip(argv, 1)); pager_open(arg_pager_flags); - table = table_new("link", - "chassis-id", + table = table_new("index", + "link", "system-name", - "caps", + "system-description", + "chassis-id", "port-id", - "port-description"); + "port-description", + "caps"); if (!table) return log_oom(); @@ -2543,53 +2621,46 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { table_set_width(table, 0); table_set_header(table, arg_legend); + table_set_ersatz_string(table, TABLE_ERSATZ_DASH); + table_set_sort(table, (size_t) 0, (size_t) 2); + table_hide_column_from_display(table, (size_t) 0); - assert_se(cell = table_get_cell(table, 0, 3)); + /* Make the capabilities not truncated */ + assert_se(cell = table_get_cell(table, 0, 7)); table_set_minimum_width(table, cell, 11); - table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - FOREACH_ARRAY(link, links, c) { - _cleanup_fclose_ FILE *f = NULL; + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, json_variant_by_key(reply, "Neighbors")) { + _cleanup_(interface_info_done) InterfaceInfo info = {}; - 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", link->ifindex); + r = json_dispatch(i, interface_info_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &info); + if (r < 0) + return r; + + if (!interface_match_pattern(&info, strv_skip(argv, 1))) continue; - } - for (;;) { - const char *chassis_id = NULL, *port_id = NULL, *system_name = NULL, *port_description = NULL; - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - _cleanup_free_ char *capabilities = NULL; - uint16_t cc; + JsonVariant *neighbor; + JSON_VARIANT_ARRAY_FOREACH(neighbor, info.v) { + LLDPNeighborInfo neighbor_info = {}; - r = next_lldp_neighbor(f, &n); - if (r < 0) { - log_warning_errno(r, "Failed to read neighbor data: %m"); - break; - } - if (r == 0) - break; + r = json_dispatch(neighbor, lldp_neighbor_dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &neighbor_info); + if (r < 0) + return r; - (void) sd_lldp_neighbor_get_chassis_id_as_string(n, &chassis_id); - (void) sd_lldp_neighbor_get_port_id_as_string(n, &port_id); - (void) sd_lldp_neighbor_get_system_name(n, &system_name); - (void) sd_lldp_neighbor_get_port_description(n, &port_description); + all |= neighbor_info.capabilities; - if (sd_lldp_neighbor_get_enabled_capabilities(n, &cc) >= 0) { - capabilities = lldp_capabilities_to_string(cc); - all |= cc; - } + _cleanup_free_ char *cap_str = lldp_capabilities_to_string(neighbor_info.capabilities); r = table_add_many(table, - TABLE_STRING, link->name, - TABLE_STRING, chassis_id, - TABLE_STRING, system_name, - TABLE_STRING, capabilities, - TABLE_STRING, port_id, - TABLE_STRING, port_description); + TABLE_INT, info.ifindex, + TABLE_STRING, info.ifname, + TABLE_STRING, neighbor_info.system_name, + TABLE_STRING, neighbor_info.system_description, + TABLE_STRING, neighbor_info.chassis_id, + TABLE_STRING, neighbor_info.port_id, + TABLE_STRING, neighbor_info.port_description, + TABLE_STRING, cap_str); if (r < 0) return table_log_add_error(r); @@ -2603,7 +2674,7 @@ static int link_lldp_status(int argc, char *argv[], void *userdata) { if (arg_legend) { lldp_capabilities_legend(all); - printf("\n%i neighbors listed.\n", m); + printf("\n%zu neighbor(s) listed.\n", m); } return 0; @@ -2741,23 +2812,25 @@ static int link_renew_one(sd_bus *bus, int index, const char *name) { static int link_renew(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int index, k = 0, r; + int r; r = acquire_bus(&bus); if (r < 0) return r; + r = 0; + for (int i = 1; i < argc; i++) { + int index; + index = rtnl_resolve_interface_or_warn(&rtnl, argv[i]); if (index < 0) return index; - r = link_renew_one(bus, index, argv[i]); - if (r < 0 && k >= 0) - k = r; + RET_GATHER(r, link_renew_one(bus, index, argv[i])); } - return k; + return r; } static int link_force_renew_one(sd_bus *bus, int index, const char *name) { @@ -2852,455 +2925,36 @@ static int verb_reconfigure(int argc, char *argv[], void *userdata) { return 0; } -typedef enum ReloadFlags { - RELOAD_NETWORKD = 1 << 0, - RELOAD_UDEVD = 1 << 1, -} ReloadFlags; - -static int get_config_files_by_name(const char *name, char **ret_path, char ***ret_dropins) { - _cleanup_free_ char *path = NULL; - int r; - - assert(name); - assert(ret_path); - - STRV_FOREACH(i, NETWORK_DIRS) { - _cleanup_free_ char *p = NULL; - - p = path_join(*i, name); - if (!p) - return -ENOMEM; - - r = RET_NERRNO(access(p, F_OK)); - if (r >= 0) { - path = TAKE_PTR(p); - break; - } - - if (r != -ENOENT) - log_debug_errno(r, "Failed to determine whether '%s' exists, ignoring: %m", p); - } - - if (!path) - return -ENOENT; - - if (ret_dropins) { - _cleanup_free_ char *dropin_dirname = NULL; - - dropin_dirname = strjoin(name, ".d"); - if (!dropin_dirname) - return -ENOMEM; - - r = conf_files_list_dropins(ret_dropins, dropin_dirname, /* root = */ NULL, NETWORK_DIRS); - if (r < 0) - return r; - } - - *ret_path = TAKE_PTR(path); - - return 0; -} - -static int get_dropin_by_name( - const char *name, - char * const *dropins, - char **ret) { - - assert(name); - assert(dropins); - assert(ret); - - STRV_FOREACH(i, dropins) - if (path_equal_filename(*i, name)) { - _cleanup_free_ char *d = NULL; - - d = strdup(*i); - if (!d) - return -ENOMEM; - - *ret = TAKE_PTR(d); - return 1; - } - - *ret = NULL; - return 0; -} - -static int get_network_files_by_link( - sd_netlink **rtnl, - const char *link, - char **ret_path, - char ***ret_dropins) { - - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - int r, ifindex; - - assert(rtnl); - assert(link); - assert(ret_path); - assert(ret_dropins); - - ifindex = rtnl_resolve_interface_or_warn(rtnl, link); - if (ifindex < 0) - return ifindex; - - r = sd_network_link_get_network_file(ifindex, &path); - if (r == -ENODATA) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "Link '%s' has no associated network file.", link); - if (r < 0) - return log_error_errno(r, "Failed to get network file for link '%s': %m", link); - - r = sd_network_link_get_network_file_dropins(ifindex, &dropins); - if (r < 0 && r != -ENODATA) - return log_error_errno(r, "Failed to get network drop-ins for link '%s': %m", link); - - *ret_path = TAKE_PTR(path); - *ret_dropins = TAKE_PTR(dropins); - - return 0; -} - -static int get_link_files_by_link(const char *link, char **ret_path, char ***ret_dropins) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_strv_free_ char **dropins_split = NULL; - _cleanup_free_ char *p = NULL; - const char *path, *dropins; - int r; - - assert(link); - assert(ret_path); - assert(ret_dropins); - - r = sd_device_new_from_ifname(&device, link); - if (r < 0) - return log_error_errno(r, "Failed to create sd-device object for link '%s': %m", link); - - r = sd_device_get_property_value(device, "ID_NET_LINK_FILE", &path); - if (r == -ENOENT) - return log_error_errno(r, "Link '%s' has no associated link file.", link); - if (r < 0) - return log_error_errno(r, "Failed to get link file for link '%s': %m", link); - - r = sd_device_get_property_value(device, "ID_NET_LINK_FILE_DROPINS", &dropins); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to get link drop-ins for link '%s': %m", link); - if (r >= 0) { - r = strv_split_full(&dropins_split, dropins, ":", EXTRACT_CUNESCAPE); - if (r < 0) - return log_error_errno(r, "Failed to parse link drop-ins for link '%s': %m", link); - } - - p = strdup(path); - if (!p) - return log_oom(); - - *ret_path = TAKE_PTR(p); - *ret_dropins = TAKE_PTR(dropins_split); - - return 0; -} - -static int get_config_files_by_link_config( - const char *link_config, - sd_netlink **rtnl, - char **ret_path, - char ***ret_dropins, - ReloadFlags *ret_reload) { - - _cleanup_strv_free_ char **dropins = NULL, **link_config_split = NULL; - _cleanup_free_ char *path = NULL; - const char *ifname, *type; - ReloadFlags reload; - size_t n; +static int verb_persistent_storage(int argc, char *argv[], void *userdata) { + _cleanup_(varlink_flush_close_unrefp) Varlink *vl = NULL; + bool ready; int r; - assert(link_config); - assert(rtnl); - assert(ret_path); - assert(ret_dropins); - - link_config_split = strv_split(link_config, ":"); - if (!link_config_split) - return log_oom(); - - n = strv_length(link_config_split); - if (n == 0 || isempty(link_config_split[0])) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No link name is given."); - if (n > 2) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid link config '%s'.", link_config); - - ifname = link_config_split[0]; - type = n == 2 ? link_config_split[1] : "network"; - - if (streq(type, "network")) { - if (!networkd_is_running()) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), - "Cannot get network file for link if systemd-networkd is not running."); - - r = get_network_files_by_link(rtnl, ifname, &path, &dropins); - if (r < 0) - return r; - - reload = RELOAD_NETWORKD; - } else if (streq(type, "link")) { - r = get_link_files_by_link(ifname, &path, &dropins); - if (r < 0) - return r; - - reload = RELOAD_UDEVD; - } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid config type '%s' for link '%s'.", type, ifname); - - *ret_path = TAKE_PTR(path); - *ret_dropins = TAKE_PTR(dropins); - - if (ret_reload) - *ret_reload = reload; - - return 0; -} - -static int add_config_to_edit( - EditFileContext *context, - const char *path, - char * const *dropins) { - - _cleanup_free_ char *new_path = NULL, *dropin_path = NULL, *old_dropin = NULL; - _cleanup_strv_free_ char **comment_paths = NULL; - int r; - - assert(context); - assert(path); - assert(!arg_drop_in || dropins); - - if (path_startswith(path, "/usr")) { - _cleanup_free_ char *name = NULL; - - r = path_extract_filename(path, &name); - if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", path); - - new_path = path_join(NETWORK_DIRS[0], name); - if (!new_path) - return log_oom(); - } - - if (!arg_drop_in) - return edit_files_add(context, new_path ?: path, path, NULL); - - r = get_dropin_by_name(arg_drop_in, dropins, &old_dropin); + r = parse_boolean(argv[1]); if (r < 0) - return log_error_errno(r, "Failed to acquire drop-in '%s': %m", arg_drop_in); - - if (r > 0 && !path_startswith(old_dropin, "/usr")) - /* An existing drop-in is found and not in /usr/. Let's edit it directly. */ - dropin_path = TAKE_PTR(old_dropin); - else { - /* No drop-in was found or an existing drop-in resides in /usr/. Let's create - * a new drop-in file. */ - dropin_path = strjoin(new_path ?: path, ".d/", arg_drop_in); - if (!dropin_path) - return log_oom(); - } - - comment_paths = strv_new(path); - if (!comment_paths) - return log_oom(); + return log_error_errno(r, "Failed to parse argument: %s", argv[1]); + ready = r; - r = strv_extend_strv(&comment_paths, dropins, /* filter_duplicates = */ false); - if (r < 0) - return log_oom(); - - return edit_files_add(context, dropin_path, old_dropin, comment_paths); -} - -static int udevd_reload(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *job_path; - int r; - - assert(bus); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - - r = bus_call_method(bus, - bus_systemd_mgr, - "ReloadUnit", - &error, - &reply, - "ss", - "systemd-udevd.service", - "replace"); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &job_path); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, job_path, /* quiet = */ true, NULL); - if (r == -ENOEXEC) { - log_debug("systemd-udevd is not running, skipping reload."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-udevd: %m"); - - return 1; -} - -static int verb_edit(int argc, char *argv[], void *userdata) { - _cleanup_(edit_file_context_done) EditFileContext context = { - .marker_start = DROPIN_MARKER_START, - .marker_end = DROPIN_MARKER_END, - .remove_parent = !!arg_drop_in, - }; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - ReloadFlags reload = 0; - int r; - - if (!on_tty()) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit network config files if not on a tty."); - - r = mac_selinux_init(); - if (r < 0) - return r; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - const char *link_config; - - link_config = startswith(*name, "@"); - if (link_config) { - ReloadFlags flags; - - r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, &flags); - if (r < 0) - return r; - - reload |= flags; - - r = add_config_to_edit(&context, path, dropins); - if (r < 0) - return r; - - continue; - } - - if (ENDSWITH_SET(*name, ".network", ".netdev")) - reload |= RELOAD_NETWORKD; - else if (endswith(*name, ".link")) - reload |= RELOAD_UDEVD; - else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid network config name '%s'.", *name); - - r = get_config_files_by_name(*name, &path, &dropins); - if (r == -ENOENT) { - if (arg_drop_in) - return log_error_errno(r, "Cannot find network config '%s'.", *name); - - log_debug("No existing network config '%s' found, creating a new file.", *name); - - path = path_join(NETWORK_DIRS[0], *name); - if (!path) - return log_oom(); - - r = edit_files_add(&context, path, NULL, NULL); - if (r < 0) - return r; - continue; - } - if (r < 0) - return log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); - - r = add_config_to_edit(&context, path, dropins); - if (r < 0) - return r; - } - - r = do_edit_files_and_install(&context); + r = varlink_connect_networkd(&vl); if (r < 0) return r; - if (arg_no_reload) - return 0; - - if (!sd_booted() || running_in_chroot() > 0) { - log_debug("System is not booted with systemd or is running in chroot, skipping reload."); - return 0; - } - - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + if (ready) { + _cleanup_close_ int fd = -EBADF; - r = sd_bus_open_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); + fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY); + if (fd < 0) + return log_error_errno(errno, "Failed to open /var/lib/systemd/network/: %m"); - if (FLAGS_SET(reload, RELOAD_UDEVD)) { - r = udevd_reload(bus); + r = varlink_push_fd(vl, fd); if (r < 0) - return r; - } - - if (FLAGS_SET(reload, RELOAD_NETWORKD)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (!networkd_is_running()) { - log_debug("systemd-networkd is not running, skipping reload."); - return 0; - } + return log_error_errno(r, "Failed to push file descriptor of /var/lib/systemd/network/ into varlink: %m"); - r = bus_call_method(bus, bus_network_mgr, "Reload", &error, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to reload systemd-networkd: %s", bus_error_message(&error, r)); + TAKE_FD(fd); } - return 0; -} - -static int verb_cat(int argc, char *argv[], void *userdata) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r, ret = 0; - - pager_open(arg_pager_flags); - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_strv_free_ char **dropins = NULL; - _cleanup_free_ char *path = NULL; - const char *link_config; - - link_config = startswith(*name, "@"); - if (link_config) { - r = get_config_files_by_link_config(link_config, &rtnl, &path, &dropins, /* ret_reload = */ NULL); - if (r < 0) - return ret < 0 ? ret : r; - } else { - r = get_config_files_by_name(*name, &path, &dropins); - if (r == -ENOENT) { - log_error_errno(r, "Cannot find network config file '%s'.", *name); - ret = ret < 0 ? ret : r; - continue; - } - if (r < 0) { - log_error_errno(r, "Failed to get the path of network config '%s': %m", *name); - return ret < 0 ? ret : r; - } - } - - r = cat_files(path, dropins, /* flags = */ CAT_FORMAT_HAS_SECTIONS); - if (r < 0) - return ret < 0 ? ret : r; - } - - return ret; + return varlink_callb_and_log(vl, "io.systemd.Network.SetPersistentStorage", /* reply = */ NULL, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BOOLEAN("Ready", ready))); } static int help(void) { @@ -3326,7 +2980,11 @@ static int help(void) { " reconfigure DEVICES... Reconfigure interfaces\n" " reload Reload .network and .netdev files\n" " edit FILES|DEVICES... Edit network configuration files\n" - " cat FILES|DEVICES... Show network configuration files\n" + " cat [FILES|DEVICES...] Show network configuration files\n" + " mask FILES... Mask network configuration files\n" + " unmask FILES... Unmask network configuration files\n" + " persistent-storage BOOL\n" + " Notify systemd-networkd if persistent storage is ready\n" "\nOptions:\n" " -h --help Show this help\n" " --version Show package version\n" @@ -3341,6 +2999,7 @@ static int help(void) { " --no-reload Do not reload systemd-networkd or systemd-udevd\n" " after editing network config\n" " --drop-in=NAME Edit specified drop-in instead of main config file\n" + " --runtime Edit runtime config files\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -3358,6 +3017,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_NO_RELOAD, ARG_DROP_IN, + ARG_RUNTIME, }; static const struct option options[] = { @@ -3372,6 +3032,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, { "drop-in", required_argument, NULL, ARG_DROP_IN }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, {} }; @@ -3402,6 +3063,10 @@ static int parse_argv(int argc, char *argv[]) { arg_no_reload = true; break; + case ARG_RUNTIME: + arg_runtime = true; + break; + case ARG_DROP_IN: if (isempty(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty drop-in file name."); @@ -3463,19 +3128,22 @@ static int parse_argv(int argc, char *argv[]) { static int networkctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, - { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, - { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, - { "label", 1, 1, 0, list_address_labels }, - { "delete", 2, VERB_ANY, 0, link_delete }, - { "up", 2, VERB_ANY, 0, link_up_down }, - { "down", 2, VERB_ANY, 0, link_up_down }, - { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, - { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, - { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, - { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, - { "edit", 2, VERB_ANY, 0, verb_edit }, - { "cat", 2, VERB_ANY, 0, verb_cat }, + { "list", VERB_ANY, VERB_ANY, VERB_DEFAULT|VERB_ONLINE_ONLY, list_links }, + { "status", VERB_ANY, VERB_ANY, VERB_ONLINE_ONLY, link_status }, + { "lldp", VERB_ANY, VERB_ANY, 0, link_lldp_status }, + { "label", 1, 1, 0, list_address_labels }, + { "delete", 2, VERB_ANY, 0, link_delete }, + { "up", 2, VERB_ANY, 0, link_up_down }, + { "down", 2, VERB_ANY, 0, link_up_down }, + { "renew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_renew }, + { "forcerenew", 2, VERB_ANY, VERB_ONLINE_ONLY, link_force_renew }, + { "reconfigure", 2, VERB_ANY, VERB_ONLINE_ONLY, verb_reconfigure }, + { "reload", 1, 1, VERB_ONLINE_ONLY, verb_reload }, + { "edit", 2, VERB_ANY, 0, verb_edit }, + { "cat", 1, VERB_ANY, 0, verb_cat }, + { "mask", 2, VERB_ANY, 0, verb_mask }, + { "unmask", 2, VERB_ANY, 0, verb_unmask }, + { "persistent-storage", 2, 2, 0, verb_persistent_storage }, {} }; diff --git a/src/network/networkctl.h b/src/network/networkctl.h new file mode 100644 index 0000000..46b44f7 --- /dev/null +++ b/src/network/networkctl.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-bus.h" + +#include "output-mode.h" +#include "pager.h" + +extern PagerFlags arg_pager_flags; +extern bool arg_legend; +extern bool arg_no_reload; +extern bool arg_all; +extern bool arg_stats; +extern bool arg_full; +extern bool arg_runtime; +extern unsigned arg_lines; +extern char *arg_drop_in; +extern JsonFormatFlags arg_json_format_flags; + +bool networkd_is_running(void); +int acquire_bus(sd_bus **ret); diff --git a/src/network/networkd-address-generation.c b/src/network/networkd-address-generation.c index 65f0009..816cdf7 100644 --- a/src/network/networkd-address-generation.c +++ b/src/network/networkd-address-generation.c @@ -34,11 +34,125 @@ typedef enum AddressGenerationType { _ADDRESS_GENERATION_TYPE_INVALID = -EINVAL, } AddressGenerationType; -typedef struct IPv6Token { +struct IPv6Token { + unsigned n_ref; AddressGenerationType type; struct in6_addr address; sd_id128_t secret_key; -} IPv6Token; +}; + +DEFINE_TRIVIAL_REF_UNREF_FUNC(IPv6Token, ipv6_token, mfree); +DEFINE_TRIVIAL_CLEANUP_FUNC(IPv6Token*, ipv6_token_unref); + +static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { + siphash24_compress_typesafe(p->type, state); + siphash24_compress_typesafe(p->address, state); + id128_hash_func(&p->secret_key, state); +} + +static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { + int r; + + r = CMP(a->type, b->type); + if (r != 0) + return r; + + r = memcmp(&a->address, &b->address, sizeof(struct in6_addr)); + if (r != 0) + return r; + + return id128_compare_func(&a->secret_key, &b->secret_key); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ipv6_token_hash_ops, + IPv6Token, + ipv6_token_hash_func, + ipv6_token_compare_func, + ipv6_token_unref); + +DEFINE_PRIVATE_HASH_OPS_FULL( + ipv6_token_by_addr_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func, + free, + IPv6Token, + ipv6_token_unref); + +static int ipv6_token_new(AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key, IPv6Token **ret) { + IPv6Token *p; + + assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX); + assert(addr); + assert(secret_key); + assert(ret); + + p = new(IPv6Token, 1); + if (!p) + return -ENOMEM; + + *p = (IPv6Token) { + .n_ref = 1, + .type = type, + .address = *addr, + .secret_key = *secret_key, + }; + + *ret = p; + return 0; +} + +static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) { + IPv6Token *p; + int r; + + assert(tokens); + + r = ipv6_token_new(type, addr, secret_key, &p); + if (r < 0) + return r; + + return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); +} + +static int ipv6_token_put_by_addr(Hashmap **tokens_by_address, const struct in6_addr *addr, IPv6Token *token) { + _cleanup_free_ struct in6_addr *copy = NULL; + int r; + + assert(tokens_by_address); + assert(addr); + assert(token); + + copy = newdup(struct in6_addr, addr, 1); + if (!copy) + return -ENOMEM; + + r = hashmap_ensure_put(tokens_by_address, &ipv6_token_by_addr_hash_ops, copy, token); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + TAKE_PTR(copy); + ipv6_token_ref(token); + return 1; +} + +static int ipv6_token_type_put_by_addr(Hashmap **tokens_by_addr, const struct in6_addr *addr, AddressGenerationType type) { + _cleanup_(ipv6_token_unrefp) IPv6Token *token = NULL; + int r; + + assert(tokens_by_addr); + assert(addr); + + r = ipv6_token_new(type, &(struct in6_addr) {}, &SD_ID128_NULL, &token); + if (r < 0) + return r; + + return ipv6_token_put_by_addr(tokens_by_addr, addr, token); +} + static int generate_eui64_address(const Link *link, const struct in6_addr *prefix, struct in6_addr *ret) { assert(link); @@ -121,7 +235,7 @@ static void generate_stable_private_address_one( if (link->ssid) siphash24_compress_string(link->ssid, &state); - siphash24_compress(&dad_counter, sizeof(uint8_t), &state); + siphash24_compress_typesafe(dad_counter, &state); rid = htole64(siphash24_finalize(&state)); @@ -134,10 +248,12 @@ static int generate_stable_private_address( const sd_id128_t *app_id, const sd_id128_t *secret_key, const struct in6_addr *prefix, + const struct in6_addr *previous, struct in6_addr *ret) { sd_id128_t secret_machine_key; struct in6_addr addr; + bool found = false; uint8_t i; int r; @@ -162,16 +278,29 @@ static int generate_stable_private_address( for (i = 0; i < DAD_CONFLICTS_IDGEN_RETRIES_RFC7217; i++) { generate_stable_private_address_one(link, secret_key, prefix, i, &addr); - if (stable_private_address_is_valid(&addr)) - break; + if (!stable_private_address_is_valid(&addr)) + continue; + + /* When 'previous' is non-NULL, then this is called after DAD in the kernel triggered. + * Let's increment the counter and provide the next address. */ + if (previous && !found) { + found = in6_addr_equal(previous, &addr); + continue; + } + + break; } - if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) + if (i >= DAD_CONFLICTS_IDGEN_RETRIES_RFC7217) { /* propagate recognizable errors. */ - return log_link_debug_errno(link, SYNTHETIC_ERRNO(ENOANO), + if (previous && !found) + return -EADDRNOTAVAIL; + + return log_link_debug_errno(link, SYNTHETIC_ERRNO(EADDRINUSE), "Failed to generate stable private address."); + } *ret = addr; - return 0; + return 1; } static int generate_addresses( @@ -180,10 +309,10 @@ static int generate_addresses( const sd_id128_t *app_id, const struct in6_addr *prefix, uint8_t prefixlen, - Set **ret) { + Hashmap **ret) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr masked; + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + struct in6_addr masked, addr; IPv6Token *j; int r; @@ -197,8 +326,6 @@ static int generate_addresses( in6_addr_mask(&masked, prefixlen); SET_FOREACH(j, tokens) { - struct in6_addr addr, *copy; - switch (j->type) { case ADDRESS_GENERATION_EUI64: if (generate_eui64_address(link, &masked, &addr) < 0) @@ -214,7 +341,7 @@ static int generate_addresses( if (in6_addr_is_set(&j->address) && !in6_addr_equal(&j->address, &masked)) continue; - if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, &addr) < 0) + if (generate_stable_private_address(link, app_id, &j->secret_key, &masked, /* previous = */ NULL, &addr) < 0) continue; break; @@ -223,97 +350,77 @@ static int generate_addresses( assert_not_reached(); } - copy = newdup(struct in6_addr, &addr, 1); - if (!copy) - return -ENOMEM; - - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, copy); + r = ipv6_token_put_by_addr(&tokens_by_address, &addr, j); if (r < 0) return r; } /* fall back to EUI-64 if no token is provided */ - if (set_isempty(addresses)) { - _cleanup_free_ struct in6_addr *addr = NULL; - - addr = new(struct in6_addr, 1); - if (!addr) - return -ENOMEM; + if (hashmap_isempty(tokens_by_address)) { + AddressGenerationType type; - if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) - r = generate_eui64_address(link, &masked, addr); - else - r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, addr); + if (IN_SET(link->iftype, ARPHRD_ETHER, ARPHRD_INFINIBAND)) { + type = ADDRESS_GENERATION_EUI64; + r = generate_eui64_address(link, &masked, &addr); + } else { + type = ADDRESS_GENERATION_PREFIXSTABLE; + r = generate_stable_private_address(link, app_id, &SD_ID128_NULL, &masked, /* previous = */ NULL, &addr); + } if (r < 0) return r; - r = set_ensure_consume(&addresses, &in6_addr_hash_ops_free, TAKE_PTR(addr)); + r = ipv6_token_type_put_by_addr(&tokens_by_address, &addr, type); if (r < 0) return r; } - *ret = TAKE_PTR(addresses); + *ret = TAKE_PTR(tokens_by_address); return 0; } -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret) { +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret) { return generate_addresses(link, link->network->dhcp_pd_tokens, &DHCP_PD_APP_ID, prefix, 64, ret); } -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, link->network->ndisc_tokens, &NDISC_APP_ID, prefix, prefixlen, ret); } -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret) { +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret) { return generate_addresses(link, tokens, &RADV_APP_ID, prefix, prefixlen, ret); } -static void ipv6_token_hash_func(const IPv6Token *p, struct siphash *state) { - siphash24_compress(&p->type, sizeof(p->type), state); - siphash24_compress(&p->address, sizeof(p->address), state); - id128_hash_func(&p->secret_key, state); -} - -static int ipv6_token_compare_func(const IPv6Token *a, const IPv6Token *b) { - int r; - - r = CMP(a->type, b->type); - if (r != 0) - return r; - - r = memcmp(&a->address, &b->address, sizeof(struct in6_addr)); - if (r != 0) - return r; - - return id128_compare_func(&a->secret_key, &b->secret_key); -} - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - ipv6_token_hash_ops, - IPv6Token, - ipv6_token_hash_func, - ipv6_token_compare_func, - free); +int regenerate_address(Address *address, Link *link) { + struct in6_addr masked; + sd_id128_t app_id; -static int ipv6_token_add(Set **tokens, AddressGenerationType type, const struct in6_addr *addr, const sd_id128_t *secret_key) { - IPv6Token *p; + assert(link); + assert(address); + assert(address->family == AF_INET6); + assert(!address->link && !address->network); - assert(tokens); - assert(type >= 0 && type < _ADDRESS_GENERATION_TYPE_MAX); - assert(addr); - assert(secret_key); + if (!address->token || + address->token->type != ADDRESS_GENERATION_PREFIXSTABLE) + return 0; - p = new(IPv6Token, 1); - if (!p) - return -ENOMEM; + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + app_id = RADV_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + app_id = DHCP_PD_APP_ID; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + app_id = NDISC_APP_ID; + break; + default: + assert_not_reached(); + } - *p = (IPv6Token) { - .type = type, - .address = *addr, - .secret_key = *secret_key, - }; + masked = address->in_addr.in6; + in6_addr_mask(&masked, address->prefixlen); - return set_ensure_consume(tokens, &ipv6_token_hash_ops, p); + return generate_stable_private_address(link, &app_id, &address->token->secret_key, &masked, &address->in_addr.in6, &address->in_addr.in6); } int config_parse_address_generation_type( diff --git a/src/network/networkd-address-generation.h b/src/network/networkd-address-generation.h index 901b2ec..2c60913 100644 --- a/src/network/networkd-address-generation.h +++ b/src/network/networkd-address-generation.h @@ -2,13 +2,20 @@ #pragma once #include "conf-parser.h" +#include "hashmap.h" #include "in-addr-util.h" -#include "set.h" +typedef struct Address Address; +typedef struct IPv6Token IPv6Token; typedef struct Link Link; -int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Set **ret); -int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); -int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Set **ret); +IPv6Token* ipv6_token_ref(IPv6Token *token); +IPv6Token* ipv6_token_unref(IPv6Token *token); + +int dhcp_pd_generate_addresses(Link *link, const struct in6_addr *prefix, Hashmap **ret); +int ndisc_generate_addresses(Link *link, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); +int radv_generate_addresses(Link *link, Set *tokens, const struct in6_addr *prefix, uint8_t prefixlen, Hashmap **ret); + +int regenerate_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_address_generation_type); diff --git a/src/network/networkd-address-label.c b/src/network/networkd-address-label.c index 745b959..e91ce3d 100644 --- a/src/network/networkd-address-label.c +++ b/src/network/networkd-address-label.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include diff --git a/src/network/networkd-address.c b/src/network/networkd-address.c index 0e4d87b..b4ac0bc 100644 --- a/src/network/networkd-address.c +++ b/src/network/networkd-address.c @@ -10,9 +10,11 @@ #include "netlink-util.h" #include "networkd-address-pool.h" #include "networkd-address.h" +#include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp-server.h" #include "networkd-ipv4acd.h" #include "networkd-manager.h" +#include "networkd-ndisc.h" #include "networkd-netlabel.h" #include "networkd-network.h" #include "networkd-queue.h" @@ -133,14 +135,40 @@ void link_get_address_states( *ret_all = address_state_from_scope(MIN(ipv4_scope, ipv6_scope)); } +static void address_hash_func(const Address *a, struct siphash *state); +static int address_compare_func(const Address *a1, const Address *a2); +static void address_detach(Address *address); + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + address_hash_ops_detach, + Address, + address_hash_func, + address_compare_func, + address_detach); + +DEFINE_HASH_OPS( + address_hash_ops, + Address, + address_hash_func, + address_compare_func); + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + address_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Address, + address_detach); + int address_new(Address **ret) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; address = new(Address, 1); if (!address) return -ENOMEM; *address = (Address) { + .n_ref = 1, .family = AF_UNSPEC, .scope = RT_SCOPE_UNIVERSE, .lifetime_valid_usec = USEC_INFINITY, @@ -155,7 +183,7 @@ int address_new(Address **ret) { int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(network); @@ -186,7 +214,7 @@ int address_new_static(Network *network, const char *filename, unsigned section_ /* This will be adjusted in address_section_verify(). */ address->duplicate_address_detection = _ADDRESS_FAMILY_INVALID; - r = ordered_hashmap_ensure_put(&network->addresses_by_section, &config_section_hash_ops, address->section, address); + r = ordered_hashmap_ensure_put(&network->addresses_by_section, &address_section_hash_ops, address->section, address); if (r < 0) return r; @@ -194,32 +222,53 @@ int address_new_static(Network *network, const char *filename, unsigned section_ return 0; } -Address *address_free(Address *address) { - if (!address) - return NULL; +static Address* address_detach_impl(Address *address) { + assert(address); + assert(!address->link || !address->network); if (address->network) { assert(address->section); ordered_hashmap_remove(address->network->addresses_by_section, address->section); + + if (address->network->dhcp_server_address == address) + address->network->dhcp_server_address = NULL; + + address->network = NULL; + return address; } if (address->link) { set_remove(address->link->addresses, address); - if (address->family == AF_INET6 && - in6_addr_equal(&address->in_addr.in6, &address->link->ipv6ll_address)) - memzero(&address->link->ipv6ll_address, sizeof(struct in6_addr)); - - ipv4acd_detach(address->link, address); + address->link = NULL; + return address; } + return NULL; +} + +static void address_detach(Address *address) { + assert(address); + + address_unref(address_detach_impl(address)); +} + +static Address* address_free(Address *address) { + if (!address) + return NULL; + + address_detach_impl(address); + config_section_free(address->section); free(address->label); free(address->netlabel); + ipv6_token_unref(address->token); nft_set_context_clear(&address->nft_set_context); return mfree(address); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(Address, address, address_free); + static bool address_lifetime_is_valid(const Address *a) { assert(a); @@ -400,25 +449,25 @@ static int address_ipv4_prefix(const Address *a, struct in_addr *ret) { static void address_hash_func(const Address *a, struct siphash *state) { assert(a); - siphash24_compress(&a->family, sizeof(a->family), state); + siphash24_compress_typesafe(a->family, state); switch (a->family) { case AF_INET: { struct in_addr prefix; - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); + siphash24_compress_typesafe(a->prefixlen, state); assert_se(address_ipv4_prefix(a, &prefix) >= 0); - siphash24_compress(&prefix, sizeof(prefix), state); + siphash24_compress_typesafe(prefix, state); - siphash24_compress(&a->in_addr.in, sizeof(a->in_addr.in), state); + siphash24_compress_typesafe(a->in_addr.in, state); break; } case AF_INET6: - siphash24_compress(&a->in_addr.in6, sizeof(a->in_addr.in6), state); + siphash24_compress_typesafe(a->in_addr.in6, state); if (in6_addr_is_null(&a->in_addr.in6)) - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); + siphash24_compress_typesafe(a->prefixlen, state); break; default: @@ -470,24 +519,9 @@ static int address_compare_func(const Address *a1, const Address *a2) { } } -DEFINE_HASH_OPS( - address_hash_ops, - Address, - address_hash_func, - address_compare_func); - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - address_hash_ops_free, - Address, - address_hash_func, - address_compare_func, - address_free); - -static bool address_can_update(const Address *la, const Address *na) { - assert(la); - assert(la->link); - assert(na); - assert(na->network); +bool address_can_update(const Address *existing, const Address *requesting) { + assert(existing); + assert(requesting); /* * property | IPv4 | IPv6 @@ -512,32 +546,32 @@ static bool address_can_update(const Address *la, const Address *na) { * IPv6 : See inet6_addr_modify() in net/ipv6/addrconf.c. */ - if (la->family != na->family) + if (existing->family != requesting->family) return false; - if (la->prefixlen != na->prefixlen) + if (existing->prefixlen != requesting->prefixlen) return false; /* When a null address is requested, the address to be assigned/updated will be determined later. */ - if (!address_is_static_null(na) && - in_addr_equal(la->family, &la->in_addr, &na->in_addr) <= 0) + if (!address_is_static_null(requesting) && + in_addr_equal(existing->family, &existing->in_addr, &requesting->in_addr) <= 0) return false; - switch (la->family) { + switch (existing->family) { case AF_INET: { struct in_addr bcast; - if (la->scope != na->scope) + if (existing->scope != requesting->scope) return false; - if (((la->flags ^ na->flags) & KNOWN_FLAGS & ~IPV6ONLY_FLAGS & ~UNMANAGED_FLAGS) != 0) + if (((existing->flags ^ requesting->flags) & KNOWN_FLAGS & ~IPV6ONLY_FLAGS & ~UNMANAGED_FLAGS) != 0) return false; - if (!streq_ptr(la->label, na->label)) + if (!streq_ptr(existing->label, requesting->label)) return false; - if (!in4_addr_equal(&la->in_addr_peer.in, &na->in_addr_peer.in)) + if (!in4_addr_equal(&existing->in_addr_peer.in, &requesting->in_addr_peer.in)) return false; - if (address_get_broadcast(na, la->link, &bcast) >= 0) { + if (existing->link && address_get_broadcast(requesting, existing->link, &bcast) >= 0) { /* If the broadcast address can be determined now, check if they match. */ - if (!in4_addr_equal(&la->broadcast, &bcast)) + if (!in4_addr_equal(&existing->broadcast, &bcast)) return false; } else { /* When a null address is requested, then the broadcast address will be @@ -545,7 +579,7 @@ static bool address_can_update(const Address *la, const Address *na) { * 192.168.0.10/24 -> 192.168.0.255 * So, here let's only check if the broadcast is the last address in the range, e.g. * 0.0.0.0/24 -> 0.0.0.255 */ - if (!FLAGS_SET(la->broadcast.s_addr, htobe32(UINT32_C(0xffffffff) >> la->prefixlen))) + if (!FLAGS_SET(existing->broadcast.s_addr, htobe32(UINT32_C(0xffffffff) >> existing->prefixlen))) return false; } break; @@ -561,7 +595,7 @@ static bool address_can_update(const Address *la, const Address *na) { } int address_dup(const Address *src, Address **ret) { - _cleanup_(address_freep) Address *dest = NULL; + _cleanup_(address_unrefp) Address *dest = NULL; int r; assert(src); @@ -571,22 +605,24 @@ int address_dup(const Address *src, Address **ret) { if (!dest) return -ENOMEM; - /* clear all pointers */ + /* clear the reference counter and all pointers */ + dest->n_ref = 1; dest->network = NULL; dest->section = NULL; dest->link = NULL; dest->label = NULL; + dest->token = ipv6_token_ref(src->token); dest->netlabel = NULL; dest->nft_set_context.sets = NULL; dest->nft_set_context.n_sets = 0; if (src->family == AF_INET) { - r = free_and_strdup(&dest->label, src->label); + r = strdup_to(&dest->label, src->label); if (r < 0) return r; } - r = free_and_strdup(&dest->netlabel, src->netlabel); + r = strdup_to(&dest->netlabel, src->netlabel); if (r < 0) return r; @@ -674,8 +710,8 @@ static void address_modify_nft_set_context(Address *address, bool add, NFTSetCon } if (r < 0) - log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring", - add? "add" : "delete", + log_warning_errno(r, "Failed to %s NFT set: family %s, table %s, set %s, IP address %s, ignoring: %m", + add ? "add" : "delete", nfproto_to_string(nft_set->nfproto), nft_set->table, nft_set->set, IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen)); else @@ -712,19 +748,21 @@ static void address_modify_nft_set(Address *address, bool add) { } } -static int address_add(Link *link, Address *address) { +static int address_attach(Link *link, Address *address) { int r; assert(link); assert(address); + assert(!address->link); - r = set_ensure_put(&link->addresses, &address_hash_ops_free, address); + r = set_ensure_put(&link->addresses, &address_hash_ops_detach, address); if (r < 0) return r; if (r == 0) return -EEXIST; address->link = link; + address_ref(address); return 0; } @@ -766,8 +804,49 @@ static int address_update(Address *address) { return 0; } -static int address_drop(Address *address) { - Link *link = ASSERT_PTR(ASSERT_PTR(address)->link); +static int address_removed_maybe_kernel_dad(Link *link, Address *address) { + int r; + + assert(link); + assert(address); + + if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + return 0; + + if (address->family != AF_INET6) + return 0; + + if (!FLAGS_SET(address->flags, IFA_F_TENTATIVE)) + return 0; + + log_link_info(link, "Address %s with tentative flag is removed, maybe a duplicated address is assigned on another node or link?", + IN6_ADDR_TO_STRING(&address->in_addr.in6)); + + /* Reset the address state, as the object may be reused in the below. */ + address->state = 0; + + switch (address->source) { + case NETWORK_CONFIG_SOURCE_STATIC: + r = link_reconfigure_radv_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_DHCP_PD: + r = dhcp_pd_reconfigure_address(address, link); + break; + case NETWORK_CONFIG_SOURCE_NDISC: + r = ndisc_reconfigure_address(address, link); + break; + default: + r = 0; + } + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure an alternative address: %m"); + + return 0; +} + +static int address_drop(Address *in, bool removed_by_us) { + _cleanup_(address_unrefp) Address *address = address_ref(ASSERT_PTR(in)); + Link *link = ASSERT_PTR(address->link); int r; r = address_set_masquerade(address, /* add = */ false); @@ -778,7 +857,22 @@ static int address_drop(Address *address) { address_del_netlabel(address); - address_free(address); + /* FIXME: if the IPv6LL address is dropped, stop DHCPv6, NDISC, RADV. */ + if (address->family == AF_INET6 && + in6_addr_equal(&address->in_addr.in6, &link->ipv6ll_address)) + link->ipv6ll_address = (const struct in6_addr) {}; + + ipv4acd_detach(link, address); + + address_detach(address); + + if (!removed_by_us) { + r = address_removed_maybe_kernel_dad(link, address); + if (r < 0) { + link_enter_failed(link); + return r; + } + } link_update_operstate(link, /* also_update_master = */ true); link_check_ready(link); @@ -907,7 +1001,7 @@ int link_get_address(Link *link, int family, const union in_addr_union *address, * arbitrary prefixlen will be returned. */ if (family == AF_INET6 || prefixlen != 0) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; /* In this case, we can use address_get(). */ @@ -975,7 +1069,7 @@ int manager_get_address(Manager *manager, int family, const union in_addr_union return -ENOENT; } -bool manager_has_address(Manager *manager, int family, const union in_addr_union *address, bool check_ready) { +bool manager_has_address(Manager *manager, int family, const union in_addr_union *address) { Address *a; assert(manager); @@ -985,7 +1079,7 @@ bool manager_has_address(Manager *manager, int family, const union in_addr_union if (manager_get_address(manager, family, address, 0, &a) < 0) return false; - return check_ready ? address_is_ready(a) : (address_exists(a) && address_lifetime_is_valid(a)); + return address_is_ready(a); } const char* format_lifetime(char *buf, size_t l, usec_t lifetime_usec) { @@ -1068,36 +1162,52 @@ static int address_set_netlink_message(const Address *address, sd_netlink_messag return 0; } -static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int address_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); - assert(link); + assert(rreq); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) + Link *link = ASSERT_PTR(rreq->link); + Address *address = ASSERT_PTR(rreq->userdata); + + if (link->state == LINK_STATE_LINGER) return 0; r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EADDRNOTAVAIL) - log_link_message_warning_errno(link, m, r, "Could not drop address"); + if (r < 0) { + log_link_message_full_errno(link, m, + (r == -EADDRNOTAVAIL || !address->link) ? LOG_DEBUG : LOG_WARNING, + r, "Could not drop address"); + + if (address->link) { + /* If the address cannot be removed, then assume the address is already removed. */ + log_address_debug(address, "Forgetting", link); + + Request *req; + if (address_get_request(link, address, &req) >= 0) + address_enter_removed(req->userdata); + + (void) address_drop(address, /* removed_by_us = */ true); + } + } return 1; } -int address_remove(Address *address) { +int address_remove(Address *address, Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Request *req; - Link *link; int r; assert(address); assert(IN_SET(address->family, AF_INET, AF_INET6)); - assert(address->link); - assert(address->link->ifindex > 0); - assert(address->link->manager); - assert(address->link->manager->rtnl); + assert(link); + assert(link->ifindex > 0); + assert(link->manager); + assert(link->manager->rtnl); - link = address->link; + /* If the address is remembered, use the remembered object. */ + (void) address_get(link, address, &address); log_address_debug(address, "Removing", link); @@ -1110,17 +1220,11 @@ int address_remove(Address *address) { if (r < 0) return log_link_warning_errno(link, r, "Could not set netlink attributes: %m"); - r = netlink_call_async(link->manager->rtnl, NULL, m, - address_remove_handler, - link_netlink_destroy_callback, link); + r = link_remove_request_add(link, address, address, link->manager->rtnl, m, address_remove_handler); if (r < 0) - return log_link_warning_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); + return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m"); address_enter_removing(address); - if (address_get_request(link, address, &req) >= 0) - address_enter_removing(req->userdata); /* The operational state is determined by address state and carrier state. Hence, if we remove * an address, the operational state may be changed. */ @@ -1128,22 +1232,38 @@ int address_remove(Address *address) { return 0; } -int address_remove_and_drop(Address *address) { - if (!address) - return 0; +int address_remove_and_cancel(Address *address, Link *link) { + _cleanup_(request_unrefp) Request *req = NULL; + bool waiting = false; + + assert(address); + assert(link); + assert(link->manager); - address_cancel_request(address); + /* If the address is remembered by the link, then use the remembered object. */ + (void) address_get(link, address, &address); - if (address_exists(address)) - return address_remove(address); + /* Cancel the request for the address. If the request is already called but we have not received the + * notification about the request, then explicitly remove the address. */ + if (address_get_request(link, address, &req) >= 0) { + request_ref(req); /* avoid the request freed by request_detach() */ + waiting = req->waiting_reply; + request_detach(req); + address_cancel_requesting(address); + } + + /* If we know the address will come or already exists, remove it. */ + if (waiting || (address->link && address_exists(address))) + return address_remove(address, link); - return address_drop(address); + return 0; } bool link_address_is_dynamic(const Link *link, const Address *address) { Route *route; assert(link); + assert(link->manager); assert(address); if (address->lifetime_preferred_usec != USEC_INFINITY) @@ -1152,7 +1272,7 @@ bool link_address_is_dynamic(const Link *link, const Address *address) { /* Even when the address is leased from a DHCP server, networkd assign the address * without lifetime when KeepConfiguration=dhcp. So, let's check that we have * corresponding routes with RTPROT_DHCP. */ - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN) continue; @@ -1163,6 +1283,9 @@ bool link_address_is_dynamic(const Link *link, const Address *address) { if (route->protocol != RTPROT_DHCP) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; + if (address->family != route->family) continue; @@ -1200,10 +1323,9 @@ int link_drop_ipv6ll_addresses(Link *link) { return r; for (sd_netlink_message *addr = reply; addr; addr = sd_netlink_message_next(addr)) { - _cleanup_(address_freep) Address *a = NULL; + _cleanup_(address_unrefp) Address *a = NULL; unsigned char flags, prefixlen; struct in6_addr address; - Address *existing; int ifindex; /* NETLINK_GET_STRICT_CHK socket option is supported since kernel 4.20. To support @@ -1249,15 +1371,7 @@ int link_drop_ipv6ll_addresses(Link *link) { a->prefixlen = prefixlen; a->flags = flags; - if (address_get(link, a, &existing) < 0) { - r = address_add(link, a); - if (r < 0) - return r; - - existing = TAKE_PTR(a); - } - - r = address_remove(existing); + r = address_remove(a, link); if (r < 0) return r; } @@ -1317,28 +1431,29 @@ int link_drop_foreign_addresses(Link *link) { if (!address_is_marked(address)) continue; - RET_GATHER(r, address_remove(address)); + RET_GATHER(r, address_remove(address, link)); } return r; } -int link_drop_managed_addresses(Link *link) { +int link_drop_static_addresses(Link *link) { Address *address; int r = 0; assert(link); SET_FOREACH(address, link->addresses) { - /* Do not touch addresses managed by kernel or other tools. */ - if (address->source == NETWORK_CONFIG_SOURCE_FOREIGN) + /* Remove only static addresses here. Dynamic addresses will be removed e.g. on lease + * expiration or stopping the DHCP client. */ + if (address->source != NETWORK_CONFIG_SOURCE_STATIC) continue; /* Ignore addresses not assigned yet or already removing. */ if (!address_exists(address)) continue; - RET_GATHER(r, address_remove(address)); + RET_GATHER(r, address_remove(address, link)); } return r; @@ -1353,43 +1468,6 @@ void link_foreignize_addresses(Link *link) { address->source = NETWORK_CONFIG_SOURCE_FOREIGN; } -static int address_acquire(Link *link, const Address *original, Address **ret) { - _cleanup_(address_freep) Address *na = NULL; - union in_addr_union in_addr; - int r; - - assert(link); - assert(original); - assert(ret); - - /* Something useful was configured? just use it */ - if (in_addr_is_set(original->family, &original->in_addr)) - return address_dup(original, ret); - - /* The address is configured to be 0.0.0.0 or [::] by the user? - * Then let's acquire something more useful from the pool. */ - r = address_pool_acquire(link->manager, original->family, original->prefixlen, &in_addr); - if (r < 0) - return r; - if (r == 0) - return -EBUSY; - - /* Pick first address in range for ourselves. */ - if (original->family == AF_INET) - in_addr.in.s_addr = in_addr.in.s_addr | htobe32(1); - else if (original->family == AF_INET6) - in_addr.in6.s6_addr[15] |= 1; - - r = address_dup(original, &na); - if (r < 0) - return r; - - na->in_addr = in_addr; - - *ret = TAKE_PTR(na); - return 0; -} - int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) { int r; @@ -1462,25 +1540,75 @@ static int address_configure(const Address *address, const struct ifa_cacheinfo return request_call_netlink_async(link->manager->rtnl, m, req); } -static bool address_is_ready_to_configure(Link *link, const Address *address) { +static int address_acquire(Link *link, const Address *address, union in_addr_union *ret) { + union in_addr_union a; + int r; + assert(link); assert(address); + assert(ret); - if (!link_is_ready_to_configure(link, false)) - return false; + r = address_acquire_from_dhcp_server_leases_file(link, address, ret); + if (!IN_SET(r, -ENOENT, -ENXIO, -EINVAL)) + return r; - if (!ipv4acd_bound(link, address)) - return false; + r = address_pool_acquire(link->manager, address->family, address->prefixlen, &a); + if (r < 0) + return r; + if (r == 0) + return -EBUSY; - /* Refuse adding more than the limit */ - if (set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX) - return false; + /* Pick first address in range for ourselves. */ + if (address->family == AF_INET) + a.in.s_addr |= htobe32(1); + else if (address->family == AF_INET6) + a.in6.s6_addr[15] |= 1; + else + assert_not_reached(); - return true; + *ret = a; + return 0; +} + +static int address_requeue_request(Request *req, Link *link, const Address *address) { + int r; + + assert(req); + assert(link); + assert(link->manager); + assert(link->network); + assert(address); + + /* Something useful was configured? just use it */ + if (in_addr_is_set(address->family, &address->in_addr)) + return 0; + + /* The address is configured to be 0.0.0.0 or [::] by the user? + * Then let's acquire something more useful. */ + union in_addr_union a; + r = address_acquire(link, address, &a); + if (r < 0) + return r; + + _cleanup_(address_unrefp) Address *tmp = NULL; + r = address_dup(address, &tmp); + if (r < 0) + return r; + + tmp->in_addr = a; + + r = link_requeue_request(link, req, tmp, NULL); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; /* Already queued?? Strange... */ + + TAKE_PTR(tmp); + return 1; /* A new request is queued. it is not necessary to process this request anymore. */ } static int address_process_request(Request *req, Link *link, Address *address) { - struct Address *existing; + Address *existing; struct ifa_cacheinfo c; int r; @@ -1488,7 +1616,26 @@ static int address_process_request(Request *req, Link *link, Address *address) { assert(link); assert(address); - if (!address_is_ready_to_configure(link, address)) + if (!link_is_ready_to_configure(link, false)) + return 0; + + /* Refuse adding more than the limit */ + if (set_size(link->addresses) >= ADDRESSES_PER_LINK_MAX) + return 0; + + r = address_requeue_request(req, link, address); + if (r == -EBUSY) + return 0; + if (r != 0) + return r; + + address_set_broadcast(address, link); + + r = ipv4acd_configure(link, address); + if (r < 0) + return r; + + if (!ipv4acd_bound(link, address)) return 0; address_set_cinfo(link->manager, address, &c); @@ -1521,7 +1668,7 @@ int link_request_address( address_netlink_handler_t netlink_handler, Request **ret) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; Address *existing = NULL; int r; @@ -1533,22 +1680,14 @@ int link_request_address( /* The requested address is outdated. Let's ignore the request. */ return 0; - if (address_get(link, address, &existing) < 0) { - if (address_get_request(link, address, NULL) >= 0) - return 0; /* already requested, skipping. */ - - r = address_acquire(link, address, &tmp); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to acquire an address from pool: %m"); + if (address_get_request(link, address, NULL) >= 0) + return 0; /* already requested, skipping. */ - /* Consider address tentative until we get the real flags from the kernel */ - tmp->flags |= IFA_F_TENTATIVE; - - } else { - r = address_dup(address, &tmp); - if (r < 0) - return log_oom(); + r = address_dup(address, &tmp); + if (r < 0) + return log_oom(); + if (address_get(link, address, &existing) >= 0) { /* Copy already assigned address when it is requested as a null address. */ if (address_is_static_null(address)) tmp->in_addr = existing->in_addr; @@ -1557,16 +1696,10 @@ int link_request_address( tmp->state = existing->state; } - address_set_broadcast(tmp, link); - - r = ipv4acd_configure(link, tmp); - if (r < 0) - return r; - log_address_debug(tmp, "Requesting", link); r = link_queue_request_safe(link, REQUEST_TYPE_ADDRESS, tmp, - address_free, + address_unref, address_hash_func, address_compare_func, address_process_request, @@ -1641,29 +1774,8 @@ int link_request_static_addresses(Link *link) { return 0; } -void address_cancel_request(Address *address) { - Request req; - - assert(address); - assert(address->link); - - if (!address_is_requesting(address)) - return; - - req = (Request) { - .link = address->link, - .type = REQUEST_TYPE_ADDRESS, - .userdata = address, - .hash_func = (hash_func_t) address_hash_func, - .compare_func = (compare_func_t) address_compare_func, - }; - - request_detach(address->link->manager, &req); - address_cancel_requesting(address); -} - int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(address_freep) Address *tmp = NULL; + _cleanup_(address_unrefp) Address *tmp = NULL; struct ifa_cacheinfo cinfo; Link *link; uint16_t type; @@ -1786,9 +1898,11 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (type == RTM_DELADDR) { if (address) { + bool removed_by_us = FLAGS_SET(address->state, NETWORK_CONFIG_STATE_REMOVING); + address_enter_removed(address); log_address_debug(address, "Forgetting removed", link); - (void) address_drop(address); + (void) address_drop(address, removed_by_us); } else log_address_debug(tmp, "Kernel removed unknown", link); @@ -1800,13 +1914,13 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, if (!address) { /* If we did not know the address, then save it. */ - r = address_add(link, tmp); + r = address_attach(link, tmp); if (r < 0) { log_link_warning_errno(link, r, "Failed to save received address %s, ignoring: %m", IN_ADDR_PREFIX_TO_STRING(tmp->family, &tmp->in_addr, tmp->prefixlen)); return 0; } - address = TAKE_PTR(tmp); + address = tmp; is_new = true; @@ -1827,6 +1941,10 @@ int manager_rtnl_process_address(sd_netlink *rtnl, sd_netlink_message *message, (void) nft_set_context_dup(&a->nft_set_context, &address->nft_set_context); address->requested_as_null = a->requested_as_null; address->callback = a->callback; + + ipv6_token_ref(a->token); + ipv6_token_unref(address->token); + address->token = a->token; } /* Then, update miscellaneous info. */ @@ -1906,7 +2024,7 @@ int config_parse_broadcast( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; union in_addr_union u; int r; @@ -1983,7 +2101,7 @@ int config_parse_address( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; union in_addr_union buffer; unsigned char prefixlen; int r, f; @@ -1994,10 +2112,16 @@ int config_parse_address( assert(rvalue); assert(data); - if (streq(section, "Network")) + if (streq(section, "Network")) { + if (isempty(rvalue)) { + /* If an empty string specified in [Network] section, clear previously assigned addresses. */ + network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section); + return 0; + } + /* we are not in an Address section, so use line number instead. */ r = address_new_static(network, filename, line, &n); - else + } else r = address_new_static(network, filename, section_line, &n); if (r == -ENOMEM) return log_oom(); @@ -2064,7 +2188,7 @@ int config_parse_label( void *data, void *userdata) { - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; Network *network = userdata; int r; @@ -2116,7 +2240,7 @@ int config_parse_lifetime( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; usec_t k; int r; @@ -2165,7 +2289,7 @@ int config_parse_address_flags( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2212,7 +2336,7 @@ int config_parse_address_scope( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2256,7 +2380,7 @@ int config_parse_address_route_metric( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2298,7 +2422,7 @@ int config_parse_duplicate_address_detection( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2352,7 +2476,7 @@ int config_parse_address_netlabel( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); @@ -2494,8 +2618,8 @@ int network_drop_invalid_addresses(Network *network) { if (address_section_verify(address) < 0) { /* Drop invalid [Address] sections or Address= settings in [Network]. - * Note that address_free() will drop the address from addresses_by_section. */ - address_free(address); + * Note that address_detach() will drop the address from addresses_by_section. */ + address_detach(address); continue; } @@ -2508,12 +2632,13 @@ int network_drop_invalid_addresses(Network *network) { IN_ADDR_PREFIX_TO_STRING(address->family, &address->in_addr, address->prefixlen), address->section->line, dup->section->line, dup->section->line); - /* address_free() will drop the address from addresses_by_section. */ - address_free(dup); + + /* address_detach() will drop the address from addresses_by_section. */ + address_detach(dup); } - /* Use address_hash_ops, instead of address_hash_ops_free. Otherwise, the Address objects - * will be freed. */ + /* Use address_hash_ops, instead of address_hash_ops_detach. Otherwise, the Address objects + * will be detached. */ r = set_ensure_put(&addresses, &address_hash_ops, address); if (r < 0) return log_oom(); @@ -2540,7 +2665,7 @@ int config_parse_address_ip_nft_set( void *userdata) { Network *network = userdata; - _cleanup_(address_free_or_set_invalidp) Address *n = NULL; + _cleanup_(address_unref_or_set_invalidp) Address *n = NULL; int r; assert(filename); diff --git a/src/network/networkd-address.h b/src/network/networkd-address.h index 5be2f77..e09551e 100644 --- a/src/network/networkd-address.h +++ b/src/network/networkd-address.h @@ -10,6 +10,7 @@ #include "hash-funcs.h" #include "in-addr-util.h" #include "network-util.h" +#include "networkd-address-generation.h" #include "networkd-link.h" #include "networkd-util.h" #include "time-util.h" @@ -34,6 +35,8 @@ struct Address { NetworkConfigState state; union in_addr_union provider; /* DHCP server or router address */ + unsigned n_ref; + int family; unsigned char prefixlen; unsigned char scope; @@ -55,11 +58,15 @@ struct Address { bool scope_set:1; bool ip_masquerade_done:1; bool requested_as_null:1; + bool used_by_dhcp_server:1; /* duplicate_address_detection is only used by static or IPv4 dynamic addresses. * To control DAD for IPv6 dynamic addresses, set IFA_F_NODAD to flags. */ AddressFamily duplicate_address_detection; + /* Used by address generator. */ + IPv6Token *token; + /* Called when address become ready */ address_ready_callback_t callback; @@ -83,21 +90,25 @@ void link_get_address_states( extern const struct hash_ops address_hash_ops; +bool address_can_update(const Address *existing, const Address *requesting); + +Address* address_ref(Address *address); +Address* address_unref(Address *address); + int address_new(Address **ret); int address_new_static(Network *network, const char *filename, unsigned section_line, Address **ret); -Address* address_free(Address *address); int address_get(Link *link, const Address *in, Address **ret); int address_get_harder(Link *link, const Address *in, Address **ret); int address_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); -int address_remove(Address *address); -int address_remove_and_drop(Address *address); +int address_remove(Address *address, Link *link); +int address_remove_and_cancel(Address *address, Link *link); int address_dup(const Address *src, Address **ret); bool address_is_ready(const Address *a); bool link_check_addresses_ready(Link *link, NetworkConfigSource source); -DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Address, address_unref); -int link_drop_managed_addresses(Link *link); +int link_drop_static_addresses(Link *link); int link_drop_foreign_addresses(Link *link); int link_drop_ipv6ll_addresses(Link *link); void link_foreignize_addresses(Link *link); @@ -112,9 +123,8 @@ static inline int link_get_ipv4_address(Link *link, const struct in_addr *addres return link_get_address(link, AF_INET, &(union in_addr_union) { .in = *address }, prefixlen, ret); } int manager_get_address(Manager *manager, int family, const union in_addr_union *address, unsigned char prefixlen, Address **ret); -bool manager_has_address(Manager *manager, int family, const union in_addr_union *address, bool check_ready); +bool manager_has_address(Manager *manager, int family, const union in_addr_union *address); -void address_cancel_request(Address *address); int link_request_address( Link *link, const Address *address, diff --git a/src/network/networkd-bridge-mdb.c b/src/network/networkd-bridge-mdb.c index bd1a974..7ff4a18 100644 --- a/src/network/networkd-bridge-mdb.c +++ b/src/network/networkd-bridge-mdb.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include diff --git a/src/network/networkd-bridge-vlan.c b/src/network/networkd-bridge-vlan.c index 36e3610..0deffa4 100644 --- a/src/network/networkd-bridge-vlan.c +++ b/src/network/networkd-bridge-vlan.c @@ -17,166 +17,327 @@ #include "parse-util.h" #include "vlan-util.h" -static bool is_bit_set(unsigned bit, uint32_t scope) { - assert(bit < sizeof(scope)*8); - return scope & (UINT32_C(1) << bit); +static bool is_bit_set(unsigned nr, const uint32_t *addr) { + assert(nr < BRIDGE_VLAN_BITMAP_MAX); + return addr[nr / 32] & (UINT32_C(1) << (nr % 32)); } static void set_bit(unsigned nr, uint32_t *addr) { - if (nr < BRIDGE_VLAN_BITMAP_MAX) - addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); + assert(nr < BRIDGE_VLAN_BITMAP_MAX); + addr[nr / 32] |= (UINT32_C(1) << (nr % 32)); } -static int find_next_bit(int i, uint32_t x) { - int j; +static int add_single(sd_netlink_message *m, uint16_t id, bool untagged, bool is_pvid, char **str) { + assert(m); + assert(id < BRIDGE_VLAN_BITMAP_MAX); + + if (DEBUG_LOGGING) + (void) strextendf_with_separator(str, ",", "%u%s%s%s%s%s", id, + (untagged || is_pvid) ? "(" : "", + untagged ? "untagged" : "", + (untagged && is_pvid) ? "," : "", + is_pvid ? "pvid" : "", + (untagged || is_pvid) ? ")" : ""); + + return sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = id, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + (is_pvid ? BRIDGE_VLAN_INFO_PVID : 0), + }, + sizeof(struct bridge_vlan_info)); +} + +static int add_range(sd_netlink_message *m, uint16_t begin, uint16_t end, bool untagged, char **str) { + int r; + + assert(m); + assert(begin <= end); + assert(end < BRIDGE_VLAN_BITMAP_MAX); - if (i >= 32) - return -1; + if (begin == end) + return add_single(m, begin, untagged, /* is_pvid = */ false, str); + + if (DEBUG_LOGGING) + (void) strextendf_with_separator(str, ",", "%u-%u%s", begin, end, untagged ? "(untagged)" : ""); + + r = sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = begin, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + BRIDGE_VLAN_INFO_RANGE_BEGIN, + }, + sizeof(struct bridge_vlan_info)); + if (r < 0) + return r; - /* find first bit */ - if (i < 0) - return BUILTIN_FFS_U32(x); + r = sd_netlink_message_append_data(m, IFLA_BRIDGE_VLAN_INFO, + &(struct bridge_vlan_info) { + .vid = end, + .flags = (untagged ? BRIDGE_VLAN_INFO_UNTAGGED : 0) | + BRIDGE_VLAN_INFO_RANGE_END, + }, + sizeof(struct bridge_vlan_info)); + if (r < 0) + return r; - /* mask off prior finds to get next */ - j = __builtin_ffs(x >> i); - return j ? j + i : 0; + return 0; } -int bridge_vlan_append_info( - const Link *link, - sd_netlink_message *req, - uint16_t pvid, - const uint32_t *br_vid_bitmap, - const uint32_t *br_untagged_bitmap) { +static uint16_t link_get_pvid(Link *link, bool *ret_untagged) { + assert(link); + assert(link->network); + + if (vlanid_is_valid(link->network->bridge_vlan_pvid)) { + if (ret_untagged) + *ret_untagged = is_bit_set(link->network->bridge_vlan_pvid, + link->network->bridge_vlan_untagged_bitmap); + return link->network->bridge_vlan_pvid; + } - struct bridge_vlan_info br_vlan; - bool done, untagged = false; - uint16_t begin, end; - int r, cnt; + if (link->network->bridge_vlan_pvid == BRIDGE_VLAN_KEEP_PVID) { + if (ret_untagged) + *ret_untagged = link->bridge_vlan_pvid_is_untagged; + return link->bridge_vlan_pvid; + } + + if (ret_untagged) + *ret_untagged = false; + return UINT16_MAX; +} + +static int bridge_vlan_append_set_info(Link *link, sd_netlink_message *m) { + _cleanup_free_ char *str = NULL; + uint16_t pvid, begin = UINT16_MAX; + bool untagged, pvid_is_untagged; + int r; assert(link); - assert(req); - assert(br_vid_bitmap); - assert(br_untagged_bitmap); - - cnt = 0; - - begin = end = UINT16_MAX; - for (int k = 0; k < BRIDGE_VLAN_BITMAP_LEN; k++) { - uint32_t untagged_map = br_untagged_bitmap[k]; - uint32_t vid_map = br_vid_bitmap[k]; - unsigned base_bit = k * 32; - int i = -1; - - done = false; - do { - int j = find_next_bit(i, vid_map); - if (j > 0) { - /* first hit of any bit */ - if (begin == UINT16_MAX && end == UINT16_MAX) { - begin = end = j - 1 + base_bit; - untagged = is_bit_set(j - 1, untagged_map); - goto next; - } - - /* this bit is a continuation of prior bits */ - if (j - 2 + base_bit == end && untagged == is_bit_set(j - 1, untagged_map) && (uint16_t)j - 1 + base_bit != pvid && (uint16_t)begin != pvid) { - end++; - goto next; - } - } else - done = true; + assert(link->network); + assert(m); + + pvid = link_get_pvid(link, &pvid_is_untagged); + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) { + + if (k == pvid) { + /* PVID needs to be sent alone. Finish previous bits. */ if (begin != UINT16_MAX) { - cnt++; - if (done && k < BRIDGE_VLAN_BITMAP_LEN - 1) - break; - - br_vlan.flags = 0; - if (untagged) - br_vlan.flags |= BRIDGE_VLAN_INFO_UNTAGGED; - - if (begin == end) { - br_vlan.vid = begin; - - if (begin == pvid) - br_vlan.flags |= BRIDGE_VLAN_INFO_PVID; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - } else { - br_vlan.vid = begin; - br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - - br_vlan.vid = end; - br_vlan.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; - br_vlan.flags |= BRIDGE_VLAN_INFO_RANGE_END; - - r = sd_netlink_message_append_data(req, IFLA_BRIDGE_VLAN_INFO, &br_vlan, sizeof(br_vlan)); - if (r < 0) - return r; - } - - if (done) - break; + assert(begin < k); + + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; } - if (j > 0) { - begin = end = j - 1 + base_bit; - untagged = is_bit_set(j - 1, untagged_map); + + r = add_single(m, pvid, pvid_is_untagged, /* is_pvid = */ true, &str); + if (r < 0) + return r; + + continue; + } + + if (!is_bit_set(k, link->network->bridge_vlan_bitmap)) { + /* This bit is not set. Finish previous bits. */ + if (begin != UINT16_MAX) { + assert(begin < k); + + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; } - next: - i = j; - } while (!done); + continue; + } + + if (begin != UINT16_MAX) { + bool u; + + assert(begin < k); + + u = is_bit_set(k, link->network->bridge_vlan_untagged_bitmap); + if (untagged == u) + continue; + + /* Tagging flag is changed from the previous bits. Finish them. */ + r = add_range(m, begin, k - 1, untagged, &str); + if (r < 0) + return r; + + begin = k; + untagged = u; + continue; + } + + /* This is the starting point of a new bit sequence. Save the position and the tagging flag. */ + begin = k; + untagged = is_bit_set(k, link->network->bridge_vlan_untagged_bitmap); } - assert(cnt > 0); - return cnt; + /* No pending bit sequence. + * Why? There is a trick. The conf parsers below only accepts vlan ID in the range 0…4094, but in + * the above loop, we run 0…4095. */ + assert_cc(BRIDGE_VLAN_BITMAP_MAX > VLANID_MAX); + assert(begin == UINT16_MAX); + + log_link_debug(link, "Setting Bridge VLAN IDs: %s", strna(str)); + return 0; } -void network_adjust_bridge_vlan(Network *network) { - assert(network); +static int bridge_vlan_append_del_info(Link *link, sd_netlink_message *m) { + _cleanup_free_ char *str = NULL; + uint16_t pvid, begin = UINT16_MAX; + int r; - if (!network->use_br_vlan) - return; + assert(link); + assert(link->network); + assert(m); - /* pvid might not be in br_vid_bitmap yet */ - if (network->pvid) - set_bit(network->pvid, network->br_vid_bitmap); -} + pvid = link_get_pvid(link, NULL); -int config_parse_brvlan_pvid( - 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) { + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) { + + if (k == pvid || + !is_bit_set(k, link->bridge_vlan_bitmap) || + is_bit_set(k, link->network->bridge_vlan_bitmap)) { + /* This bit is not necessary to be removed. Finish previous bits. */ + if (begin != UINT16_MAX) { + assert(begin < k); + + r = add_range(m, begin, k - 1, /* untagged = */ false, &str); + if (r < 0) + return r; + + begin = UINT16_MAX; + } + + continue; + } + + if (begin != UINT16_MAX) + continue; + + /* This is the starting point of a new bit sequence. Save the position. */ + begin = k; + } + + /* No pending bit sequence. */ + assert(begin == UINT16_MAX); - Network *network = userdata; - uint16_t pvid; + log_link_debug(link, "Removing Bridge VLAN IDs: %s", strna(str)); + return 0; +} + +int bridge_vlan_set_message(Link *link, sd_netlink_message *m, bool is_set) { int r; - r = parse_vlanid(rvalue, &pvid); + assert(link); + assert(m); + + r = sd_rtnl_message_link_set_family(m, AF_BRIDGE); + if (r < 0) + return r; + + r = sd_netlink_message_open_container(m, IFLA_AF_SPEC); if (r < 0) return r; - network->pvid = pvid; - network->use_br_vlan = true; + if (link->master_ifindex <= 0) { + /* master needs BRIDGE_FLAGS_SELF flag */ + r = sd_netlink_message_append_u16(m, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); + if (r < 0) + return r; + } + + if (is_set) + r = bridge_vlan_append_set_info(link, m); + else + r = bridge_vlan_append_del_info(link, m); + if (r < 0) + return r; + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; return 0; } -int config_parse_brvlan_vlan( +int link_update_bridge_vlan(Link *link, sd_netlink_message *m) { + _cleanup_free_ void *data = NULL; + size_t len; + uint16_t begin = UINT16_MAX; + int r, family; + + assert(link); + assert(m); + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + if (family != AF_BRIDGE) + return 0; + + r = sd_netlink_message_read_data(m, IFLA_AF_SPEC, &len, &data); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + memzero(link->bridge_vlan_bitmap, sizeof(link->bridge_vlan_bitmap)); + + for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + struct bridge_vlan_info *p; + + if (RTA_TYPE(rta) != IFLA_BRIDGE_VLAN_INFO) + continue; + if (RTA_PAYLOAD(rta) != sizeof(struct bridge_vlan_info)) + continue; + + p = RTA_DATA(rta); + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_RANGE_BEGIN)) { + begin = p->vid; + continue; + } + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_RANGE_END)) { + for (uint16_t k = begin; k <= p->vid; k++) + set_bit(k, link->bridge_vlan_bitmap); + + begin = UINT16_MAX; + continue; + } + + if (FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_PVID)) { + link->bridge_vlan_pvid = p->vid; + link->bridge_vlan_pvid_is_untagged = FLAGS_SET(p->flags, BRIDGE_VLAN_INFO_UNTAGGED); + } + + set_bit(p->vid, link->bridge_vlan_bitmap); + begin = UINT16_MAX; + } + + return 0; +} + +void network_adjust_bridge_vlan(Network *network) { + assert(network); + + for (uint16_t k = 0; k < BRIDGE_VLAN_BITMAP_MAX; k++) + if (is_bit_set(k, network->bridge_vlan_untagged_bitmap)) + set_bit(k, network->bridge_vlan_bitmap); + + if (vlanid_is_valid(network->bridge_vlan_pvid)) + set_bit(network->bridge_vlan_pvid, network->bridge_vlan_bitmap); +} + +int config_parse_bridge_vlan_id( const char *unit, const char *filename, unsigned line, @@ -188,30 +349,37 @@ int config_parse_brvlan_vlan( void *data, void *userdata) { - Network *network = userdata; - uint16_t vid, vid_end; + uint16_t v, *id = ASSERT_PTR(data); int r; assert(filename); assert(section); assert(lvalue); assert(rvalue); - assert(data); - r = parse_vid_range(rvalue, &vid, &vid_end); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse VLAN, ignoring: %s", rvalue); + if (isempty(rvalue)) { + *id = BRIDGE_VLAN_KEEP_PVID; return 0; } - for (; vid <= vid_end; vid++) - set_bit(vid, network->br_vid_bitmap); + if (parse_boolean(rvalue) == 0) { + *id = BRIDGE_VLAN_REMOVE_PVID; + return 0; + } - network->use_br_vlan = true; + r = parse_vlanid(rvalue, &v); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring: %s", + lvalue, rvalue); + return 0; + } + + *id = v; return 0; } -int config_parse_brvlan_untagged( +int config_parse_bridge_vlan_id_range( const char *unit, const char *filename, unsigned line, @@ -223,7 +391,7 @@ int config_parse_brvlan_untagged( void *data, void *userdata) { - Network *network = userdata; + uint32_t *bitmap = ASSERT_PTR(data); uint16_t vid, vid_end; int r; @@ -231,19 +399,22 @@ int config_parse_brvlan_untagged( assert(section); assert(lvalue); assert(rvalue); - assert(data); + + if (isempty(rvalue)) { + memzero(bitmap, BRIDGE_VLAN_BITMAP_LEN * sizeof(uint32_t)); + return 0; + } r = parse_vid_range(rvalue, &vid, &vid_end); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Could not parse VLAN: %s", rvalue); + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring: %s", + lvalue, rvalue); return 0; } - for (; vid <= vid_end; vid++) { - set_bit(vid, network->br_vid_bitmap); - set_bit(vid, network->br_untagged_bitmap); - } + for (; vid <= vid_end; vid++) + set_bit(vid, bitmap); - network->use_br_vlan = true; return 0; } diff --git a/src/network/networkd-bridge-vlan.h b/src/network/networkd-bridge-vlan.h index f44b810..0366cc6 100644 --- a/src/network/networkd-bridge-vlan.h +++ b/src/network/networkd-bridge-vlan.h @@ -6,26 +6,28 @@ ***/ #include +#include #include "sd-netlink.h" #include "conf-parser.h" +#include "vlan-util.h" #define BRIDGE_VLAN_BITMAP_MAX 4096 #define BRIDGE_VLAN_BITMAP_LEN (BRIDGE_VLAN_BITMAP_MAX / 32) +#define BRIDGE_VLAN_KEEP_PVID UINT16_MAX +#define BRIDGE_VLAN_REMOVE_PVID (UINT16_MAX - 1) +assert_cc(BRIDGE_VLAN_REMOVE_PVID > VLANID_MAX); + typedef struct Link Link; typedef struct Network Network; void network_adjust_bridge_vlan(Network *network); -int bridge_vlan_append_info( - const Link * link, - sd_netlink_message *req, - uint16_t pvid, - const uint32_t *br_vid_bitmap, - const uint32_t *br_untagged_bitmap); +int bridge_vlan_set_message(Link *link, sd_netlink_message *m, bool is_set); + +int link_update_bridge_vlan(Link *link, sd_netlink_message *m); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_pvid); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_vlan); -CONFIG_PARSER_PROTOTYPE(config_parse_brvlan_untagged); +CONFIG_PARSER_PROTOTYPE(config_parse_bridge_vlan_id); +CONFIG_PARSER_PROTOTYPE(config_parse_bridge_vlan_id_range); diff --git a/src/network/networkd-can.c b/src/network/networkd-can.c index b8a1871..a5b003a 100644 --- a/src/network/networkd-can.c +++ b/src/network/networkd-can.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include diff --git a/src/network/networkd-conf.c b/src/network/networkd-conf.c index 063732a..6a6814d 100644 --- a/src/network/networkd-conf.c +++ b/src/network/networkd-conf.c @@ -14,14 +14,17 @@ int manager_parse_config_file(Manager *m) { assert(m); - r = config_parse_config_file("networkd.conf", - "Network\0" - "DHCPv4\0" - "DHCPv6\0" - "DHCP\0", - config_item_perf_lookup, networkd_gperf_lookup, - CONFIG_PARSE_WARN, - m); + r = config_parse_standard_file_with_dropins( + "systemd/networkd.conf", + "Network\0" + "IPv6AcceptRA\0" + "DHCPv4\0" + "DHCPv6\0" + "DHCPServer\0" + "DHCP\0", + config_item_perf_lookup, networkd_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ m); if (r < 0) return r; diff --git a/src/network/networkd-dhcp-common.c b/src/network/networkd-dhcp-common.c index 080b153..9f0268d 100644 --- a/src/network/networkd-dhcp-common.c +++ b/src/network/networkd-dhcp-common.c @@ -5,7 +5,6 @@ #include "bus-error.h" #include "bus-locator.h" -#include "dhcp-identifier.h" #include "dhcp-option.h" #include "dhcp6-internal.h" #include "escape.h" @@ -41,12 +40,12 @@ uint32_t link_get_dhcp4_route_table(Link *link) { return link_get_vrf_table(link); } -uint32_t link_get_ipv6_accept_ra_route_table(Link *link) { +uint32_t link_get_ndisc_route_table(Link *link) { assert(link); assert(link->network); - if (link->network->ipv6_accept_ra_route_table_set) - return link->network->ipv6_accept_ra_route_table; + if (link->network->ndisc_route_table_set) + return link->network->ndisc_route_table; return link_get_vrf_table(link); } @@ -282,7 +281,7 @@ int link_get_captive_portal(Link *link, const char **ret) { return r; } - if (link->network->ipv6_accept_ra_use_captive_portal) { + if (link->network->ndisc_use_captive_portal) { NDiscCaptivePortal *cp; usec_t usec = 0; @@ -410,10 +409,10 @@ int config_parse_dhcp_route_metric( /* For backward compatibility. */ if (!network->dhcp_route_metric_set) network->dhcp_route_metric = metric; - if (!network->ipv6_accept_ra_route_metric_set) { - network->ipv6_accept_ra_route_metric_high = metric; - network->ipv6_accept_ra_route_metric_medium = metric; - network->ipv6_accept_ra_route_metric_low = metric; + if (!network->ndisc_route_metric_set) { + network->ndisc_route_metric_high = metric; + network->ndisc_route_metric_medium = metric; + network->ndisc_route_metric_low = metric; } break; default: @@ -423,7 +422,7 @@ int config_parse_dhcp_route_metric( return 0; } -int config_parse_ipv6_accept_ra_route_metric( +int config_parse_ndisc_route_metric( const char *unit, const char *filename, unsigned line, @@ -448,7 +447,7 @@ int config_parse_ipv6_accept_ra_route_metric( _cleanup_free_ char *high = NULL, *medium = NULL, *low = NULL; const char *p = rvalue; - r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &high, &medium, &low, NULL); + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &high, &medium, &low); if (r == -ENOMEM) return log_oom(); if (r != 3 || !isempty(p)) { @@ -473,10 +472,10 @@ int config_parse_ipv6_accept_ra_route_metric( } } - network->ipv6_accept_ra_route_metric_high = metric_high; - network->ipv6_accept_ra_route_metric_medium = metric_medium; - network->ipv6_accept_ra_route_metric_low = metric_low; - network->ipv6_accept_ra_route_metric_set = true; + network->ndisc_route_metric_high = metric_high; + network->ndisc_route_metric_medium = metric_medium; + network->ndisc_route_metric_low = metric_low; + network->ndisc_route_metric_set = true; return 0; } @@ -531,158 +530,6 @@ int config_parse_dhcp_send_hostname( return 0; } -int config_parse_dhcp_use_dns( - 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) { - - Network *network = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse UseDNS=%s, ignoring assignment: %m", rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_dns = r; - network->dhcp_use_dns_set = true; - break; - case AF_INET6: - network->dhcp6_use_dns = r; - network->dhcp6_use_dns_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_dns_set) - network->dhcp_use_dns = r; - if (!network->dhcp6_use_dns_set) - network->dhcp6_use_dns = r; - break; - default: - assert_not_reached(); - } - - return 0; -} - -int config_parse_dhcp_use_domains( - 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) { - - Network *network = userdata; - DHCPUseDomains d; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - d = dhcp_use_domains_from_string(rvalue); - if (d < 0) { - log_syntax(unit, LOG_WARNING, filename, line, d, - "Failed to parse %s=%s, ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_domains = d; - network->dhcp_use_domains_set = true; - break; - case AF_INET6: - network->dhcp6_use_domains = d; - network->dhcp6_use_domains_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_domains_set) - network->dhcp_use_domains = d; - if (!network->dhcp6_use_domains_set) - network->dhcp6_use_domains = d; - break; - default: - assert_not_reached(); - } - - return 0; -} - -int config_parse_dhcp_use_ntp( - 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) { - - Network *network = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(IN_SET(ltype, AF_UNSPEC, AF_INET, AF_INET6)); - assert(rvalue); - assert(data); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse UseNTP=%s, ignoring assignment: %m", rvalue); - return 0; - } - - switch (ltype) { - case AF_INET: - network->dhcp_use_ntp = r; - network->dhcp_use_ntp_set = true; - break; - case AF_INET6: - network->dhcp6_use_ntp = r; - network->dhcp6_use_ntp_set = true; - break; - case AF_UNSPEC: - /* For backward compatibility. */ - if (!network->dhcp_use_ntp_set) - network->dhcp_use_ntp = r; - if (!network->dhcp6_use_ntp_set) - network->dhcp6_use_ntp = r; - break; - default: - assert_not_reached(); - } - - return 0; -} int config_parse_dhcp_or_ra_route_table( const char *unit, @@ -718,8 +565,8 @@ int config_parse_dhcp_or_ra_route_table( network->dhcp_route_table_set = true; break; case AF_INET6: - network->ipv6_accept_ra_route_table = rt; - network->ipv6_accept_ra_route_table_set = true; + network->ndisc_route_table = rt; + network->ndisc_route_table_set = true; break; default: assert_not_reached(); @@ -1138,14 +985,6 @@ int config_parse_dhcp_request_options( } } -static const char* const dhcp_use_domains_table[_DHCP_USE_DOMAINS_MAX] = { - [DHCP_USE_DOMAINS_NO] = "no", - [DHCP_USE_DOMAINS_ROUTE] = "route", - [DHCP_USE_DOMAINS_YES] = "yes", -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dhcp_use_domains, DHCPUseDomains, DHCP_USE_DOMAINS_YES); - static const char * const dhcp_option_data_type_table[_DHCP_OPTION_DATA_MAX] = { [DHCP_OPTION_DATA_UINT8] = "uint8", [DHCP_OPTION_DATA_UINT16] = "uint16", diff --git a/src/network/networkd-dhcp-common.h b/src/network/networkd-dhcp-common.h index 6e3f3b2..3390d7d 100644 --- a/src/network/networkd-dhcp-common.h +++ b/src/network/networkd-dhcp-common.h @@ -4,7 +4,7 @@ #include #include "conf-parser.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "in-addr-util.h" #include "set.h" #include "time-util.h" @@ -24,14 +24,6 @@ typedef struct Link Link; typedef struct Manager Manager; typedef struct Network Network; -typedef enum DHCPUseDomains { - DHCP_USE_DOMAINS_NO, - DHCP_USE_DOMAINS_YES, - DHCP_USE_DOMAINS_ROUTE, - _DHCP_USE_DOMAINS_MAX, - _DHCP_USE_DOMAINS_INVALID = -EINVAL, -} DHCPUseDomains; - typedef enum DHCPOptionDataType { DHCP_OPTION_DATA_UINT8, DHCP_OPTION_DATA_UINT16, @@ -54,7 +46,7 @@ typedef struct DUID { } DUID; uint32_t link_get_dhcp4_route_table(Link *link); -uint32_t link_get_ipv6_accept_ra_route_table(Link *link); +uint32_t link_get_ndisc_route_table(Link *link); bool link_dhcp_enabled(Link *link, int family); static inline bool link_dhcp4_enabled(Link *link) { @@ -87,19 +79,13 @@ static inline bool in6_prefix_is_filtered(const struct in6_addr *prefix, uint8_t int link_get_captive_portal(Link *link, const char **ret); -const char* dhcp_use_domains_to_string(DHCPUseDomains p) _const_; -DHCPUseDomains dhcp_use_domains_from_string(const char *s) _pure_; - const char *dhcp_option_data_type_to_string(DHCPOptionDataType d) _const_; DHCPOptionDataType dhcp_option_data_type_from_string(const char *d) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_dhcp); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_route_metric); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_route_metric); +CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_route_metric); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_send_hostname); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_domains); -CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_iaid); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_or_ra_route_table); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_user_or_vendor_class); diff --git a/src/network/networkd-dhcp-prefix-delegation.c b/src/network/networkd-dhcp-prefix-delegation.c index af2fe9e..2e660b7 100644 --- a/src/network/networkd-dhcp-prefix-delegation.c +++ b/src/network/networkd-dhcp-prefix-delegation.c @@ -101,6 +101,7 @@ static int link_get_by_dhcp_pd_subnet_prefix(Manager *manager, const struct in6_ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr *pd_prefix, uint8_t pd_prefix_len, struct in6_addr *ret) { assert(link); + assert(link->manager); assert(pd_prefix); if (!link_dhcp_pd_is_enabled(link)) @@ -128,11 +129,14 @@ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr } else { Route *route; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP_PD) continue; assert(route->family == AF_INET6); + if (route->nexthop.ifindex != link->ifindex) + continue; + if (in6_addr_prefix_covers(pd_prefix, pd_prefix_len, &route->dst.in6) > 0) { if (ret) *ret = route->dst.in6; @@ -145,7 +149,7 @@ static int dhcp_pd_get_assigned_subnet_prefix(Link *link, const struct in6_addr } int dhcp_pd_remove(Link *link, bool only_marked) { - int k, r = 0; + int ret = 0; assert(link); assert(link->manager); @@ -159,9 +163,11 @@ int dhcp_pd_remove(Link *link, bool only_marked) { if (!link->network->dhcp_pd_assign) { Route *route; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP_PD) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; if (only_marked && !route_is_marked(route)) continue; @@ -170,11 +176,7 @@ int dhcp_pd_remove(Link *link, bool only_marked) { link_remove_dhcp_pd_subnet_prefix(link, &route->dst.in6); - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } } else { Address *address; @@ -195,13 +197,11 @@ int dhcp_pd_remove(Link *link, bool only_marked) { link_remove_dhcp_pd_subnet_prefix(link, &prefix); - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } } - return r; + return ret; } static int dhcp_pd_check_ready(Link *link); @@ -270,7 +270,7 @@ static int dhcp_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to add prefix route for DHCP delegated subnet prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to add prefix route for DHCP delegated subnet prefix"); if (r <= 0) return r; @@ -282,11 +282,12 @@ static int dhcp_pd_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques } static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec_t lifetime_usec) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(link->network); assert(prefix); @@ -305,13 +306,16 @@ static int dhcp_pd_request_route(Link *link, const struct in6_addr *prefix, usec route->priority = link->network->dhcp_pd_route_metric; route->lifetime_usec = lifetime_usec; - if (route_get(NULL, link, route, &existing) < 0) + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) link->dhcp_pd_configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages, - dhcp_pd_route_handler, NULL); + r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler); if (r < 0) return log_link_error_errno(link, r, "Failed to request DHCP-PD prefix route: %m"); @@ -349,14 +353,50 @@ static void log_dhcp_pd_address(Link *link, const Address *address) { FORMAT_LIFETIME(address->lifetime_preferred_usec)); } +static int dhcp_pd_request_address_one(Address *address, Link *link) { + Address *existing; + + assert(address); + assert(link); + + log_dhcp_pd_address(link, address); + + if (address_get(link, address, &existing) < 0) + link->dhcp_pd_configured = false; + else + address_unmark(existing); + + return link_request_address(link, address, &link->dhcp_pd_messages, dhcp_pd_address_handler, NULL); +} + +int dhcp_pd_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_DHCP_PD); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = dhcp_pd_request_address_one(address, link); + if (r < 0) + return r; + + if (!link->dhcp_pd_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + static int dhcp_pd_request_address( Link *link, const struct in6_addr *prefix, usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; int r; assert(link); @@ -366,13 +406,15 @@ static int dhcp_pd_request_address( if (!link->network->dhcp_pd_assign) return 0; - r = dhcp_pd_generate_addresses(link, prefix, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = dhcp_pd_generate_addresses(link, prefix, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate addresses for acquired DHCP delegated prefix: %m"); - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; - Address *existing; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) @@ -386,20 +428,13 @@ static int dhcp_pd_request_address( address->lifetime_valid_usec = lifetime_valid_usec; SET_FLAG(address->flags, IFA_F_MANAGETEMPADDR, link->network->dhcp_pd_manage_temporary_address); address->route_metric = link->network->dhcp_pd_route_metric; - - log_dhcp_pd_address(link, address); + address->token = ipv6_token_ref(token); r = free_and_strdup_warn(&address->netlabel, link->network->dhcp_pd_netlabel); if (r < 0) return r; - if (address_get(link, address, &existing) < 0) - link->dhcp_pd_configured = false; - else - address_unmark(existing); - - r = link_request_address(link, address, &link->dhcp_pd_messages, - dhcp_pd_address_handler, NULL); + r = dhcp_pd_request_address_one(address, link); if (r < 0) return log_link_error_errno(link, r, "Failed to request DHCP delegated prefix address: %m"); } @@ -550,7 +585,7 @@ static int dhcp_pd_prepare(Link *link) { return 0; link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP_PD); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP_PD); + manager_mark_routes(link->manager, link, NETWORK_CONFIG_SOURCE_DHCP_PD); return 1; } @@ -607,9 +642,7 @@ void dhcp_pd_prefix_lost(Link *uplink) { .address = route->dst })) continue; - (void) route_remove(route); - - route_cancel_request(route, uplink); + (void) route_remove_and_cancel(route, uplink->manager); } set_clear(uplink->dhcp_pd_prefixes); @@ -630,7 +663,7 @@ static int dhcp4_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv4 delegated prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv4 delegated prefix"); if (r <= 0) return r; @@ -646,7 +679,7 @@ static int dhcp6_unreachable_route_handler(sd_netlink *rtnl, sd_netlink_message assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Failed to set unreachable route for DHCPv6 delegated prefix"); + r = route_configure_handler_internal(rtnl, m, link, route, "Failed to set unreachable route for DHCPv6 delegated prefix"); if (r <= 0) return r; @@ -668,11 +701,12 @@ static int dhcp_request_unreachable_route( route_netlink_handler_t callback, bool *configured) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(addr); assert(IN_SET(source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6)); assert(server_address); @@ -700,12 +734,16 @@ static int dhcp_request_unreachable_route( route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; - if (route_get(link->manager, NULL, route, &existing) < 0) + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) *configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, counter, callback, NULL); + r = link_request_route(link, route, counter, callback); if (r < 0) return log_link_error_errno(link, r, "Failed to request unreachable route for DHCP delegated prefix %s: %m", IN6_ADDR_PREFIX_TO_STRING(addr, prefixlen)); @@ -774,11 +812,12 @@ static int dhcp_pd_prefix_add(Link *link, const struct in6_addr *prefix, uint8_t } static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const struct in_addr *br_address, usec_t lifetime_usec) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; Route *existing; int r; assert(link); + assert(link->manager); assert(br_address); r = route_new(&route); @@ -787,20 +826,23 @@ static int dhcp4_pd_request_default_gateway_on_6rd_tunnel(Link *link, const stru route->source = NETWORK_CONFIG_SOURCE_DHCP_PD; route->family = AF_INET6; - route->gw_family = AF_INET6; - route->gw.in6.s6_addr32[3] = br_address->s_addr; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6.s6_addr32[3] = br_address->s_addr; route->scope = RT_SCOPE_UNIVERSE; route->protocol = RTPROT_DHCP; route->priority = IP6_RT_PRIO_USER; route->lifetime_usec = lifetime_usec; - if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) /* This is a new route. */ link->dhcp_pd_configured = false; else route_unmark(existing); - r = link_request_route(link, TAKE_PTR(route), true, &link->dhcp_pd_messages, - dhcp_pd_route_handler, NULL); + r = link_request_route(link, route, &link->dhcp_pd_messages, dhcp_pd_route_handler); if (r < 0) return log_link_debug_errno(link, r, "Failed to request default gateway for DHCP delegated prefix: %m"); diff --git a/src/network/networkd-dhcp-prefix-delegation.h b/src/network/networkd-dhcp-prefix-delegation.h index e591b8a..4a8cca9 100644 --- a/src/network/networkd-dhcp-prefix-delegation.h +++ b/src/network/networkd-dhcp-prefix-delegation.h @@ -8,6 +8,7 @@ #include "conf-parser.h" +typedef struct Address Address; typedef struct Link Link; bool link_dhcp_pd_is_enabled(Link *link); @@ -19,5 +20,6 @@ int dhcp4_pd_prefix_acquired(Link *uplink); int dhcp6_pd_prefix_acquired(Link *uplink); void dhcp_pd_prefix_lost(Link *uplink); void dhcp4_pd_prefix_lost(Link *uplink); +int dhcp_pd_reconfigure_address(Address *address, Link *link); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_pd_subnet_id); diff --git a/src/network/networkd-dhcp-server-bus.c b/src/network/networkd-dhcp-server-bus.c index e3397c3..470e559 100644 --- a/src/network/networkd-dhcp-server-bus.c +++ b/src/network/networkd-dhcp-server-bus.c @@ -3,7 +3,7 @@ #include "alloc-util.h" #include "bus-common-errors.h" #include "bus-util.h" -#include "dhcp-server-internal.h" +#include "dhcp-server-lease-internal.h" #include "networkd-dhcp-server-bus.h" #include "networkd-link-bus.h" #include "networkd-manager.h" @@ -19,7 +19,7 @@ static int property_get_leases( sd_bus_error *error) { Link *l = ASSERT_PTR(userdata); sd_dhcp_server *s; - DHCPLease *lease; + sd_dhcp_server_lease *lease; int r; assert(reply); @@ -44,7 +44,7 @@ static int property_get_leases( if (r < 0) return r; - r = sd_bus_message_append_array(reply, 'y', lease->client_id.data, lease->client_id.length); + r = sd_bus_message_append_array(reply, 'y', lease->client_id.raw, lease->client_id.size); if (r < 0) return r; diff --git a/src/network/networkd-dhcp-server.c b/src/network/networkd-dhcp-server.c index 607fe00..c35102a 100644 --- a/src/network/networkd-dhcp-server.c +++ b/src/network/networkd-dhcp-server.c @@ -7,6 +7,7 @@ #include "sd-dhcp-server.h" #include "dhcp-protocol.h" +#include "dhcp-server-lease-internal.h" #include "fd-util.h" #include "fileio.h" #include "network-common.h" @@ -17,9 +18,11 @@ #include "networkd-link.h" #include "networkd-manager.h" #include "networkd-network.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route-util.h" #include "parse-util.h" +#include "path-util.h" #include "socket-netlink.h" #include "string-table.h" #include "string-util.h" @@ -81,6 +84,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { /* TODO: check if the prefix length is small enough for the pool. */ network->dhcp_server_address = address; + address->used_by_dhcp_server = true; break; } if (!network->dhcp_server_address) { @@ -92,7 +96,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { } } else { - _cleanup_(address_freep) Address *a = NULL; + _cleanup_(address_unrefp) Address *a = NULL; Address *existing; unsigned line; @@ -127,6 +131,7 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { a->prefixlen = network->dhcp_server_address_prefixlen; a->in_addr.in = network->dhcp_server_address_in_addr; a->requested_as_null = !in4_addr_is_set(&network->dhcp_server_address_in_addr); + a->used_by_dhcp_server = true; r = address_section_verify(a); if (r < 0) @@ -143,6 +148,139 @@ int network_adjust_dhcp_server(Network *network, Set **addresses) { return 0; } +static bool dhcp_server_persist_leases(Link *link) { + assert(link); + assert(link->manager); + assert(link->network); + + if (in4_addr_is_set(&link->network->dhcp_server_relay_target)) + return false; /* On relay mode. Nothing saved in the persistent storage. */ + + if (link->network->dhcp_server_persist_leases >= 0) + return link->network->dhcp_server_persist_leases; + + return link->manager->dhcp_server_persist_leases; +} + +int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret) { + struct in_addr a; + uint8_t prefixlen; + int r; + + assert(link); + assert(link->manager); + assert(address); + assert(ret); + + /* If the DHCP server address is configured as a null address, reuse the server address of the + * previous instance. */ + if (address->family != AF_INET) + return -ENOENT; + + if (!address->used_by_dhcp_server) + return -ENOENT; + + if (!link_dhcp4_server_enabled(link)) + return -ENOENT; + + if (!dhcp_server_persist_leases(link)) + return -ENOENT; + + if (link->manager->persistent_storage_fd < 0) + return -EBUSY; /* The persistent storage is not ready, try later again. */ + + _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname); + if (!lease_file) + return -ENOMEM; + + r = dhcp_server_leases_file_get_server_address( + link->manager->persistent_storage_fd, + lease_file, + &a, + &prefixlen); + if (r == -ENOENT) + return r; + if (r < 0) + return log_warning_errno(r, "Failed to load lease file %s: %s", + lease_file, + r == -ENXIO ? "expected JSON content not found" : + r == -EINVAL ? "invalid JSON" : + STRERROR(r)); + + if (prefixlen != address->prefixlen) + return -ENOENT; + + ret->in = a; + return 0; +} + +int link_start_dhcp4_server(Link *link) { + int r; + + assert(link); + assert(link->manager); + + if (!link->dhcp_server) + return 0; /* Not configured yet. */ + + if (!link_has_carrier(link)) + return 0; + + if (sd_dhcp_server_is_running(link->dhcp_server)) + return 0; /* already started. */ + + /* TODO: Maybe, also check the system time is synced. If the system does not have RTC battery, then + * the realtime clock in not usable in the early boot stage, and all saved leases may be wrongly + * handled as expired and dropped. */ + if (dhcp_server_persist_leases(link)) { + + if (link->manager->persistent_storage_fd < 0) + return 0; /* persistent storage is not ready. */ + + _cleanup_free_ char *lease_file = path_join("dhcp-server-lease", link->ifname); + if (!lease_file) + return -ENOMEM; + + r = sd_dhcp_server_set_lease_file(link->dhcp_server, link->manager->persistent_storage_fd, lease_file); + if (r < 0) + return r; + } + + r = sd_dhcp_server_start(link->dhcp_server); + if (r < 0) + return r; + + log_link_debug(link, "Offering DHCPv4 leases"); + return 0; +} + +void manager_toggle_dhcp4_server_state(Manager *manager, bool start) { + Link *link; + int r; + + assert(manager); + + HASHMAP_FOREACH(link, manager->links_by_index) { + if (!link->dhcp_server) + continue; + if (!dhcp_server_persist_leases(link)) + continue; + + /* Even if 'start' is true, first we need to stop the server. Otherwise, we cannot (re)set + * the lease file in link_start_dhcp4_server(). */ + r = sd_dhcp_server_stop(link->dhcp_server); + if (r < 0) + log_link_debug_errno(link, r, "Failed to stop DHCP server, ignoring: %m"); + + if (!start) + continue; + + r = link_start_dhcp4_server(link); + if (r < 0) + log_link_debug_errno(link, r, "Failed to start DHCP server, ignoring: %m"); + } +} + static int dhcp_server_find_uplink(Link *link, Link **ret) { assert(link); @@ -205,7 +343,7 @@ static int link_push_uplink_to_dhcp_server( addresses[n_addresses++] = ia; } - use_dhcp_lease_data = link->network->dhcp_use_dns; + use_dhcp_lease_data = link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4); break; case SD_DHCP_LEASE_NTP: { @@ -228,7 +366,7 @@ static int link_push_uplink_to_dhcp_server( addresses[n_addresses++] = ia.in; } - use_dhcp_lease_data = link->network->dhcp_use_ntp; + use_dhcp_lease_data = link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4); break; } @@ -423,7 +561,7 @@ static int dhcp4_server_configure(Link *link) { return log_link_warning_errno(link, r, "Failed to %s Rapid Commit support for DHCPv4 server instance: %m", enable_disable(link->network->dhcp_server_rapid_commit)); - for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type ++) { + for (sd_dhcp_lease_server_type_t type = 0; type < _SD_DHCP_LEASE_SERVER_TYPE_MAX; type++) { if (!link->network->dhcp_server_emit[type].emit) continue; @@ -522,11 +660,10 @@ static int dhcp4_server_configure(Link *link) { return log_link_error_errno(link, r, "Failed to set DHCPv4 static lease for DHCP server: %m"); } - r = sd_dhcp_server_start(link->dhcp_server); + r = link_start_dhcp4_server(link); if (r < 0) return log_link_error_errno(link, r, "Could not start DHCPv4 server instance: %m"); - log_link_debug(link, "Offering DHCPv4 leases"); return 0; } diff --git a/src/network/networkd-dhcp-server.h b/src/network/networkd-dhcp-server.h index 960232a..e839fac 100644 --- a/src/network/networkd-dhcp-server.h +++ b/src/network/networkd-dhcp-server.h @@ -2,15 +2,21 @@ #pragma once #include "conf-parser.h" +#include "in-addr-util.h" #include "set.h" +typedef struct Address Address; typedef struct Link Link; +typedef struct Manager Manager; typedef struct Network Network; int network_adjust_dhcp_server(Network *network, Set **addresses); - +int address_acquire_from_dhcp_server_leases_file(Link *link, const Address *address, union in_addr_union *ret); int link_request_dhcp_server(Link *link); +int link_start_dhcp4_server(Link *link); +void manager_toggle_dhcp4_server_state(Manager *manager, bool start); + CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_relay_agent_suboption); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_emit); CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_server_address); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 49c452d..4dd6044 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -20,6 +20,7 @@ #include "networkd-manager.h" #include "networkd-network.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-setlink.h" @@ -146,12 +147,11 @@ static int dhcp4_find_gateway_for_destination( Link *link, const struct in_addr *destination, uint8_t prefixlength, - bool allow_null, struct in_addr *ret) { _cleanup_free_ sd_dhcp_route **routes = NULL; size_t n_routes = 0; - bool is_classless, reachable; + bool is_classless; uint8_t max_prefixlen = UINT8_MAX; struct in_addr gw; int r; @@ -164,14 +164,21 @@ static int dhcp4_find_gateway_for_destination( /* This tries to find the most suitable gateway for an address or address range. * E.g. if the server provides the default gateway 192.168.0.1 and a classless static route for * 8.0.0.0/8 with gateway 192.168.0.2, then this returns 192.168.0.2 for 8.8.8.8/32, and 192.168.0.1 - * for 9.9.9.9/32. If 'allow_null' flag is set, and the input address or address range is in the - * assigned network, then the default gateway will be ignored and the null address will be returned - * unless a matching non-default gateway found. */ + * for 9.9.9.9/32. If the input address or address range is in the assigned network, then the null + * address will be returned. */ + /* First, check with the assigned prefix, and if the destination is in the prefix, set the null + * address for the gateway, and return it unless more suitable static route is found. */ r = dhcp4_prefix_covers(link, destination, prefixlength); if (r < 0) return r; - reachable = r > 0; + if (r > 0) { + r = sd_dhcp_lease_get_prefix(link->dhcp_lease, NULL, &max_prefixlen); + if (r < 0) + return r; + + gw = (struct in_addr) {}; + } r = dhcp4_get_classless_static_or_static_routes(link, &routes, &n_routes); if (r < 0 && r != -ENODATA) @@ -207,25 +214,17 @@ static int dhcp4_find_gateway_for_destination( max_prefixlen = len; } - /* Found a suitable gateway in classless static routes or static routes. */ + /* The destination is reachable. Note, the gateway address returned here may be NULL. */ if (max_prefixlen != UINT8_MAX) { - if (max_prefixlen == 0 && reachable && allow_null) - /* Do not return the default gateway, if the destination is in the assigned network. */ - *ret = (struct in_addr) {}; - else - *ret = gw; - return 0; - } - - /* When the destination is in the assigned network, return the null address if allowed. */ - if (reachable && allow_null) { - *ret = (struct in_addr) {}; + *ret = gw; return 0; } /* According to RFC 3442: If the DHCP server returns both a Classless Static Routes option and * a Router option, the DHCP client MUST ignore the Router option. */ if (!is_classless) { + /* No matching static route is found, and the destination is not in the acquired network, + * falling back to the Router option. */ r = dhcp4_get_router(link, ret); if (r >= 0) return 0; @@ -233,31 +232,26 @@ static int dhcp4_find_gateway_for_destination( return r; } - if (!reachable) - return -EHOSTUNREACH; /* Not in the same network, cannot reach the destination. */ - - assert(!allow_null); - return -ENODATA; /* No matching gateway found. */ + return -EHOSTUNREACH; /* Cannot reach the destination. */ } static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) { Address *address; Route *route; - int k, r = 0; + int ret = 0; assert(link); + assert(link->manager); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP4) continue; + if (route->nexthop.ifindex != 0 && route->nexthop.ifindex != link->ifindex) + continue; if (only_marked && !route_is_marked(route)) continue; - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } SET_FOREACH(address, link->addresses) { @@ -266,12 +260,10 @@ static int dhcp4_remove_address_and_routes(Link *link, bool only_marked) { if (only_marked && !address_is_marked(address)) continue; - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } - return r; + return ret; } static int dhcp4_address_get(Link *link, Address **ret) { @@ -347,12 +339,9 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request assert(m); assert(link); - r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { - log_link_message_warning_errno(link, m, r, "Could not set DHCPv4 route"); - link_enter_failed(link); - return 1; - } + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set DHCPv4 route"); + if (r <= 0) + return r; r = dhcp4_check_ready(link); if (r < 0) @@ -361,14 +350,14 @@ static int dhcp4_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request return 1; } -static int dhcp4_request_route(Route *in, Link *link) { - _cleanup_(route_freep) Route *route = in; +static int dhcp4_request_route(Route *route, Link *link) { struct in_addr server; Route *existing; int r; assert(route); assert(link); + assert(link->manager); assert(link->network); assert(link->dhcp_lease); @@ -385,22 +374,29 @@ static int dhcp4_request_route(Route *in, Link *link) { route->priority = link->network->dhcp_route_metric; if (!route->table_set) route->table = link_get_dhcp4_route_table(link); - if (route->mtu == 0) - route->mtu = link->network->dhcp_route_mtu; - if (route->quickack < 0) - route->quickack = link->network->dhcp_quickack; - if (route->initcwnd == 0) - route->initcwnd = link->network->dhcp_initial_congestion_window; - if (route->initrwnd == 0) - route->initrwnd = link->network->dhcp_advertised_receive_window; - - if (route_get(NULL, link, route, &existing) < 0) /* This is a new route. */ + r = route_metric_set(&route->metric, RTAX_MTU, link->network->dhcp_route_mtu); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_INITCWND, link->network->dhcp_initial_congestion_window); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_INITRWND, link->network->dhcp_advertised_receive_window); + if (r < 0) + return r; + r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->dhcp_quickack); + if (r < 0) + return r; + + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route_get(link->manager, route, &existing) < 0) /* This is a new route. */ link->dhcp4_configured = false; else route_unmark(existing); - return link_request_route(link, TAKE_PTR(route), true, &link->dhcp4_messages, - dhcp4_route_handler, NULL); + return link_request_route(link, route, &link->dhcp4_messages, dhcp4_route_handler); } static bool link_prefixroute(Link *link) { @@ -409,7 +405,7 @@ static bool link_prefixroute(Link *link) { } static int dhcp4_request_prefix_route(Link *link) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; int r; assert(link); @@ -433,11 +429,11 @@ static int dhcp4_request_prefix_route(Link *link) { if (r < 0) return r; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr address; int r; @@ -458,15 +454,14 @@ static int dhcp4_request_route_to_gateway(Link *link, const struct in_addr *gw) route->prefsrc.in = address; route->scope = RT_SCOPE_LINK; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_route_auto( - Route *in, + Route *route, Link *link, const struct in_addr *gw) { - _cleanup_(route_freep) Route *route = in; struct in_addr address; int r; @@ -486,8 +481,8 @@ static int dhcp4_request_route_auto( IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); route->scope = RT_SCOPE_HOST; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc = IN_ADDR_NULL; } else if (in4_addr_equal(&route->dst.in, &address)) { @@ -497,8 +492,8 @@ static int dhcp4_request_route_auto( IPV4_ADDRESS_FMT_VAL(route->dst.in), route->dst_prefixlen, IPV4_ADDRESS_FMT_VAL(*gw)); route->scope = RT_SCOPE_HOST; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc.in = address; } else if (in4_addr_is_null(gw)) { @@ -520,8 +515,8 @@ static int dhcp4_request_route_auto( } route->scope = RT_SCOPE_LINK; - route->gw_family = AF_UNSPEC; - route->gw = IN_ADDR_NULL; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; route->prefsrc.in = address; } else { @@ -530,12 +525,12 @@ static int dhcp4_request_route_auto( return r; route->scope = RT_SCOPE_UNIVERSE; - route->gw_family = AF_INET; - route->gw.in = *gw; + route->nexthop.family = AF_INET; + route->nexthop.gw.in = *gw; route->prefsrc.in = address; } - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_classless_static_or_static_routes(Link *link) { @@ -556,7 +551,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { return r; FOREACH_ARRAY(e, routes, n_routes) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; r = route_new(&route); @@ -575,7 +570,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { if (r < 0) return r; - r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw); + r = dhcp4_request_route_auto(route, link, &gw); if (r < 0) return r; } @@ -584,7 +579,7 @@ static int dhcp4_request_classless_static_or_static_routes(Link *link) { } static int dhcp4_request_default_gateway(Link *link) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr address, router; int r; @@ -623,11 +618,11 @@ static int dhcp4_request_default_gateway(Link *link) { return r; /* Next, add a default gateway. */ - route->gw_family = AF_INET; - route->gw.in = router; + route->nexthop.family = AF_INET; + route->nexthop.gw.in = router; route->prefsrc.in = address; - return dhcp4_request_route(TAKE_PTR(route), link); + return dhcp4_request_route(route, link); } static int dhcp4_request_semi_static_routes(Link *link) { @@ -639,19 +634,19 @@ static int dhcp4_request_semi_static_routes(Link *link) { assert(link->network); HASHMAP_FOREACH(rt, link->network->routes_by_section) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; if (!rt->gateway_from_dhcp_or_ra) continue; - if (rt->gw_family != AF_INET) + if (rt->nexthop.family != AF_INET) continue; assert(rt->family == AF_INET); - r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, /* allow_null = */ false, &gw); - if (IN_SET(r, -EHOSTUNREACH, -ENODATA)) { + r = dhcp4_find_gateway_for_destination(link, &rt->dst.in, rt->dst_prefixlen, &gw); + if (r == -EHOSTUNREACH) { log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s of semi-static route, ignoring: %m", IN4_ADDR_PREFIX_TO_STRING(&rt->dst.in, rt->dst_prefixlen)); continue; @@ -659,17 +654,23 @@ static int dhcp4_request_semi_static_routes(Link *link) { if (r < 0) return r; + if (in4_addr_is_null(&gw)) { + log_link_debug(link, "DHCP: Destination %s of semi-static route is in the acquired network, skipping configuration.", + IN4_ADDR_PREFIX_TO_STRING(&rt->dst.in, rt->dst_prefixlen)); + continue; + } + r = dhcp4_request_route_to_gateway(link, &gw); if (r < 0) return r; - r = route_dup(rt, &route); + r = route_dup(rt, NULL, &route); if (r < 0) return r; - route->gw.in = gw; + route->nexthop.gw.in = gw; - r = dhcp4_request_route(TAKE_PTR(route), link); + r = dhcp4_request_route(route, link); if (r < 0) return r; } @@ -690,13 +691,13 @@ static int dhcp4_request_routes_to_servers( assert(servers || n_servers == 0); FOREACH_ARRAY(dst, servers, n_servers) { - _cleanup_(route_freep) Route *route = NULL; + _cleanup_(route_unrefp) Route *route = NULL; struct in_addr gw; if (in4_addr_is_null(dst)) continue; - r = dhcp4_find_gateway_for_destination(link, dst, 32, /* allow_null = */ true, &gw); + r = dhcp4_find_gateway_for_destination(link, dst, 32, &gw); if (r == -EHOSTUNREACH) { log_link_debug_errno(link, r, "DHCP: Cannot find suitable gateway for destination %s, ignoring: %m", IN4_ADDR_PREFIX_TO_STRING(dst, 32)); @@ -712,7 +713,7 @@ static int dhcp4_request_routes_to_servers( route->dst.in = *dst; route->dst_prefixlen = 32; - r = dhcp4_request_route_auto(TAKE_PTR(route), link, &gw); + r = dhcp4_request_route_auto(route, link, &gw); if (r < 0) return r; } @@ -728,7 +729,7 @@ static int dhcp4_request_routes_to_dns(Link *link) { assert(link->dhcp_lease); assert(link->network); - if (!link->network->dhcp_use_dns || + if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4) || !link->network->dhcp_routes_to_dns) return 0; @@ -749,7 +750,7 @@ static int dhcp4_request_routes_to_ntp(Link *link) { assert(link->dhcp_lease); assert(link->network); - if (!link->network->dhcp_use_ntp || + if (!link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4) || !link->network->dhcp_routes_to_ntp) return 0; @@ -835,7 +836,7 @@ static int dhcp_reset_hostname(Link *link) { } int dhcp4_lease_lost(Link *link) { - int k, r = 0; + int r = 0; assert(link); assert(link->dhcp_lease); @@ -849,17 +850,9 @@ int dhcp4_lease_lost(Link *link) { sd_dhcp_lease_has_6rd(link->dhcp_lease)) dhcp4_pd_prefix_lost(link); - k = dhcp4_remove_address_and_routes(link, /* only_marked = */ false); - if (k < 0) - r = k; - - k = dhcp_reset_mtu(link); - if (k < 0) - r = k; - - k = dhcp_reset_hostname(link); - if (k < 0) - r = k; + RET_GATHER(r, dhcp4_remove_address_and_routes(link, /* only_marked = */ false)); + RET_GATHER(r, dhcp_reset_mtu(link)); + RET_GATHER(r, dhcp_reset_hostname(link)); link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease); link_dirty(link); @@ -892,7 +885,7 @@ static int dhcp4_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques } static int dhcp4_request_address(Link *link, bool announce) { - _cleanup_(address_freep) Address *addr = NULL; + _cleanup_(address_unrefp) Address *addr = NULL; struct in_addr address, server; uint8_t prefixlen; Address *existing; @@ -997,7 +990,7 @@ static int dhcp4_request_address_and_routes(Link *link, bool announce) { assert(link); link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP4); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP4); + manager_mark_routes(link->manager, link, NETWORK_CONFIG_SOURCE_DHCP4); r = dhcp4_request_address(link, announce); if (r < 0) @@ -1181,7 +1174,7 @@ static int dhcp4_handler(sd_dhcp_client *client, int event, void *userdata) { } r = sd_ipv4ll_start(link->ipv4ll); - if (r < 0) + if (r < 0 && r != -ESTALE) /* On exit, we cannot and should not start sd-ipv4ll. */ return log_link_warning_errno(link, r, "Could not acquire IPv4 link-local address: %m"); } @@ -1548,13 +1541,13 @@ static int dhcp4_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for classless static route: %m"); } - if (link->network->dhcp_use_domains != DHCP_USE_DOMAINS_NO) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) > 0) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_DOMAIN_SEARCH); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for domain search list: %m"); } - if (link->network->dhcp_use_ntp) { + if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_NTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set request flag for NTP server: %m"); @@ -1642,6 +1635,11 @@ static int dhcp4_configure(Link *link) { if (r < 0) return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set listen port: %m"); } + if (link->network->dhcp_port > 0) { + r = sd_dhcp_client_set_port(link->dhcp_client, link->network->dhcp_port); + if (r < 0) + return log_link_debug_errno(link, r, "DHCPv4 CLIENT: Failed to set server port: %m"); + } if (link->network->dhcp_max_attempts > 0) { r = sd_dhcp_client_set_max_attempts(link->dhcp_client, link->network->dhcp_max_attempts); diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index f499d03..852987b 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -14,6 +14,7 @@ #include "networkd-dhcp6.h" #include "networkd-link.h" #include "networkd-manager.h" +#include "networkd-ntp.h" #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-state-file.h" @@ -48,24 +49,21 @@ static DHCP6ClientStartMode link_get_dhcp6_client_start_mode(Link *link) { static int dhcp6_remove(Link *link, bool only_marked) { Address *address; Route *route; - int k, r = 0; + int ret = 0; assert(link); + assert(link->manager); if (!only_marked) link->dhcp6_configured = false; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_DHCP6) continue; if (only_marked && !route_is_marked(route)) continue; - k = route_remove(route); - if (k < 0) - r = k; - - route_cancel_request(route, link); + RET_GATHER(ret, route_remove_and_cancel(route, link->manager)); } SET_FOREACH(address, link->addresses) { @@ -74,12 +72,10 @@ static int dhcp6_remove(Link *link, bool only_marked) { if (only_marked && !address_is_marked(address)) continue; - k = address_remove_and_drop(address); - if (k < 0) - r = k; + RET_GATHER(ret, address_remove_and_cancel(address, link)); } - return r; + return ret; } static int dhcp6_address_ready_callback(Address *address) { @@ -164,8 +160,7 @@ static int verify_dhcp6_address(Link *link, const Address *address) { } else log_level = LOG_DEBUG; - if (address->prefixlen == existing->prefixlen) - /* Currently, only conflict in prefix length is reported. */ + if (address_can_update(existing, address)) goto simple_log; if (existing->source == NETWORK_CONFIG_SOURCE_NDISC) @@ -197,7 +192,7 @@ static int dhcp6_request_address( usec_t lifetime_preferred_usec, usec_t lifetime_valid_usec) { - _cleanup_(address_freep) Address *addr = NULL; + _cleanup_(address_unrefp) Address *addr = NULL; Address *existing; int r; @@ -298,7 +293,7 @@ static int dhcp6_lease_ip_acquired(sd_dhcp6_client *client, Link *link) { int r; link_mark_addresses(link, NETWORK_CONFIG_SOURCE_DHCP6); - link_mark_routes(link, NETWORK_CONFIG_SOURCE_DHCP6); + manager_mark_routes(link->manager, NULL, NETWORK_CONFIG_SOURCE_DHCP6); r = sd_dhcp6_client_get_lease(client, &lease); if (r < 0) @@ -358,6 +353,9 @@ static int dhcp6_lease_lost(Link *link) { assert(link); assert(link->manager); + if (!link->dhcp6_lease) + return 0; + log_link_info(link, "DHCPv6 lease lost"); if (sd_dhcp6_lease_has_pd_prefix(link->dhcp6_lease)) @@ -636,13 +634,13 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to set MUD URL: %m"); } - if (link->network->dhcp6_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request DNS servers: %m"); } - if (link->network->dhcp6_use_domains > 0) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) > 0) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request domains: %m"); @@ -654,7 +652,7 @@ static int dhcp6_configure(Link *link) { return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request captive portal: %m"); } - if (link->network->dhcp6_use_ntp) { + if (link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { r = sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER); if (r < 0) return log_link_debug_errno(link, r, "DHCPv6 CLIENT: Failed to request NTP servers: %m"); @@ -805,7 +803,7 @@ int link_request_dhcp6_client(Link *link) { assert(link); - if (!link_dhcp6_enabled(link) && !link_ipv6_accept_ra_enabled(link)) + if (!link_dhcp6_enabled(link) && !link_ndisc_enabled(link)) return 0; if (link->dhcp6_client) @@ -833,7 +831,7 @@ int link_serialize_dhcp6_client(Link *link, FILE *f) { if (r >= 0) fprintf(f, "DHCP6_CLIENT_IAID=0x%x\n", iaid); - r = sd_dhcp6_client_duid_as_string(link->dhcp6_client, &duid); + r = sd_dhcp6_client_get_duid_as_string(link->dhcp6_client, &duid); if (r >= 0) fprintf(f, "DHCP6_CLIENT_DUID=%s\n", duid); diff --git a/src/network/networkd-dns.c b/src/network/networkd-dns.c new file mode 100644 index 0000000..7078419 --- /dev/null +++ b/src/network/networkd-dns.c @@ -0,0 +1,294 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dns-domain.h" +#include "hostname-util.h" +#include "networkd-dns.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "parse-util.h" +#include "string-table.h" + +UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto) { + UseDomains n, c, m; + + assert(link); + assert(link->manager); + + if (!link->network) + return USE_DOMAINS_NO; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_domains; + c = link->network->compat_dhcp_use_domains; + m = link->manager->dhcp_use_domains; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_domains; + c = link->network->compat_dhcp_use_domains; + m = link->manager->dhcp6_use_domains; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + n = link->network->ndisc_use_domains; + c = _USE_DOMAINS_INVALID; + m = link->manager->ndisc_use_domains; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* If per-network but protocol-independent setting is specified, use it. */ + if (link->network->use_domains >= 0) + return link->network->use_domains; + + /* If global per-protocol setting is specified, use it. */ + if (m >= 0) + return m; + + /* If none of them are specified, use the global protocol-independent value. */ + return link->manager->use_domains; +} + +bool link_get_use_dns(Link *link, NetworkConfigSource proto) { + int n, c; + + assert(link); + + if (!link->network) + return false; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_dns; + c = link->network->compat_dhcp_use_dns; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_dns; + c = link->network->compat_dhcp_use_dns; + break; + case NETWORK_CONFIG_SOURCE_NDISC: + n = link->network->ndisc_use_dns; + c = -1; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* Otherwise, defaults to yes. */ + return true; +} + +int config_parse_domains( + 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) { + + Network *n = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + n->search_domains = ordered_set_free(n->search_domains); + n->route_domains = ordered_set_free(n->route_domains); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL, *normalized = NULL; + const char *domain; + bool is_route; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract search or route domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + is_route = w[0] == '~'; + domain = is_route ? w + 1 : w; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + /* If the root domain appears as is, or the special token "*" is found, we'll + * consider this as routing domain, unconditionally. */ + is_route = true; + domain = "."; /* make sure we don't allow empty strings, thus write the root + * domain as "." */ + } else { + r = dns_name_normalize(domain, 0, &normalized); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "'%s' is not a valid domain name, ignoring.", domain); + continue; + } + + domain = normalized; + + if (is_localhost(domain)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s", + domain); + continue; + } + } + + OrderedSet **set = is_route ? &n->route_domains : &n->search_domains; + r = ordered_set_put_strdup(set, domain); + if (r == -EEXIST) + continue; + if (r < 0) + return log_oom(); + } +} + +int config_parse_dns( + 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) { + + Network *n = ASSERT_PTR(userdata); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + for (unsigned i = 0; i < n->n_dns; i++) + in_addr_full_free(n->dns[i]); + n->dns = mfree(n->dns); + n->n_dns = 0; + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL; + _cleanup_free_ char *w = NULL; + struct in_addr_full **m; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = in_addr_full_new_from_string(w, &dns); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse dns server address, ignoring: %s", w); + continue; + } + + if (IN_SET(dns->port, 53, 853)) + dns->port = 0; + + m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*)); + if (!m) + return log_oom(); + + m[n->n_dns++] = TAKE_PTR(dns); + n->dns = m; + } +} + +int config_parse_dnssec_negative_trust_anchors( + 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) { + + Set **nta = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *nta = set_free_free(*nta); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name, ignoring.", w); + continue; + } + + r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} + +static const char* const use_domains_table[_USE_DOMAINS_MAX] = { + [USE_DOMAINS_NO] = "no", + [USE_DOMAINS_ROUTE] = "route", + [USE_DOMAINS_YES] = "yes", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(use_domains, UseDomains, USE_DOMAINS_YES); +DEFINE_CONFIG_PARSE_ENUM(config_parse_use_domains, use_domains, UseDomains, "Failed to parse UseDomains=") diff --git a/src/network/networkd-dns.h b/src/network/networkd-dns.h new file mode 100644 index 0000000..915cb32 --- /dev/null +++ b/src/network/networkd-dns.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "macro.h" +#include "networkd-util.h" + +typedef struct Link Link; + +typedef enum UseDomains { + USE_DOMAINS_NO, + USE_DOMAINS_YES, + USE_DOMAINS_ROUTE, + _USE_DOMAINS_MAX, + _USE_DOMAINS_INVALID = -EINVAL, +} UseDomains; + +UseDomains link_get_use_domains(Link *link, NetworkConfigSource proto); +bool link_get_use_dns(Link *link, NetworkConfigSource proto); + +const char* use_domains_to_string(UseDomains p) _const_; +UseDomains use_domains_from_string(const char *s) _pure_; + +CONFIG_PARSER_PROTOTYPE(config_parse_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); +CONFIG_PARSER_PROTOTYPE(config_parse_dhcp_use_dns); +CONFIG_PARSER_PROTOTYPE(config_parse_use_domains); diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index 8542ffa..f02dfd7 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -7,6 +7,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "conf-parser.h" #include "networkd-conf.h" #include "networkd-dhcp-common.h" +#include "networkd-dns.h" #include "networkd-manager.h" #include "networkd-route-util.h" %} @@ -25,12 +26,20 @@ Network.SpeedMeter, config_parse_bool, Network.SpeedMeterIntervalSec, config_parse_sec, 0, offsetof(Manager, speed_meter_interval_usec) Network.ManageForeignRoutingPolicyRules, config_parse_bool, 0, offsetof(Manager, manage_foreign_rules) Network.ManageForeignRoutes, config_parse_bool, 0, offsetof(Manager, manage_foreign_routes) +Network.ManageForeignNextHops, config_parse_bool, 0, offsetof(Manager, manage_foreign_nexthops) Network.RouteTable, config_parse_route_table_names, 0, 0 +Network.IPv4Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[0]) +Network.IPv6Forwarding, config_parse_tristate, 0, offsetof(Manager, ip_forwarding[1]) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Manager, ipv6_privacy_extensions) +Network.UseDomains, config_parse_use_domains, 0, offsetof(Manager, use_domains) +IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Manager, ndisc_use_domains) +DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp_use_domains) DHCPv4.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp_duid) DHCPv4.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp_duid) +DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Manager, dhcp6_use_domains) DHCPv6.DUIDType, config_parse_duid_type, 0, offsetof(Manager, dhcp6_duid) DHCPv6.DUIDRawData, config_parse_duid_rawdata, 0, offsetof(Manager, dhcp6_duid) +DHCPServer.PersistLeases, config_parse_bool, 0, offsetof(Manager, dhcp_server_persist_leases) /* Deprecated */ DHCP.DUIDType, config_parse_manager_duid_type, 0, 0 DHCP.DUIDRawData, config_parse_manager_duid_rawdata, 0, 0 diff --git a/src/network/networkd-ipv4acd.c b/src/network/networkd-ipv4acd.c index 3d5e203..de03293 100644 --- a/src/network/networkd-ipv4acd.c +++ b/src/network/networkd-ipv4acd.c @@ -92,7 +92,9 @@ static int static_ipv4acd_address_remove(Link *link, Address *address, bool on_c else log_link_debug(link, "Removing address %s, as the ACD client is stopped.", IN4_ADDR_TO_STRING(&address->in_addr.in)); - r = address_remove(address); + /* Do not call address_remove_and_cancel() here. Otherwise, the request is cancelled, and the + * interface may be in configured state without the address. */ + r = address_remove(address, link); if (r < 0) return log_link_warning_errno(link, r, "Failed to remove address %s: %m", IN4_ADDR_TO_STRING(&address->in_addr.in)); diff --git a/src/network/networkd-ipv4ll.c b/src/network/networkd-ipv4ll.c index c357382..299aaed 100644 --- a/src/network/networkd-ipv4ll.c +++ b/src/network/networkd-ipv4ll.c @@ -28,7 +28,7 @@ bool link_ipv4ll_enabled(Link *link) { } static int address_new_from_ipv4ll(Link *link, Address **ret) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; struct in_addr addr; int r; @@ -56,8 +56,7 @@ static int address_new_from_ipv4ll(Link *link, Address **ret) { } static int ipv4ll_address_lost(Link *link) { - _cleanup_(address_freep) Address *address = NULL; - Address *existing; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(link); @@ -70,19 +69,10 @@ static int ipv4ll_address_lost(Link *link) { if (r < 0) return r; - if (address_get(link, address, &existing) < 0) - return 0; - - if (existing->source != NETWORK_CONFIG_SOURCE_IPV4LL) - return 0; - - if (!address_exists(existing)) - return 0; - log_link_debug(link, "IPv4 link-local release "IPV4_ADDRESS_FMT_STR, IPV4_ADDRESS_FMT_VAL(address->in_addr.in)); - return address_remove(existing); + return address_remove_and_cancel(address, link); } static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { @@ -102,7 +92,7 @@ static int ipv4ll_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reque } static int ipv4ll_address_claimed(sd_ipv4ll *ll, Link *link) { - _cleanup_(address_freep) Address *address = NULL; + _cleanup_(address_unrefp) Address *address = NULL; int r; assert(ll); diff --git a/src/network/networkd-json.c b/src/network/networkd-json.c index eed8d9f..fb9f492 100644 --- a/src/network/networkd-json.c +++ b/src/network/networkd-json.c @@ -2,7 +2,8 @@ #include -#include "dhcp-server-internal.h" +#include "dhcp-lease-internal.h" +#include "dhcp-server-lease-internal.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" #include "dns-domain.h" @@ -16,6 +17,7 @@ #include "networkd-neighbor.h" #include "networkd-network.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-route-util.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" @@ -24,12 +26,12 @@ #include "user-util.h" #include "wifi-util.h" -static int address_build_json(Address *address, JsonVariant **ret) { +static int address_append_json(Address *address, JsonVariant **array) { _cleanup_free_ char *scope = NULL, *flags = NULL, *state = NULL; int r; assert(address); - assert(ret); + assert(array); r = route_scope_to_string_alloc(address->scope, &scope); if (r < 0) @@ -43,7 +45,9 @@ static int address_build_json(Address *address, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", address->family), JSON_BUILD_PAIR_IN_ADDR("Address", &address->in_addr, address->family), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Peer", &address->in_addr_peer, address->family), @@ -71,13 +75,7 @@ static int addresses_append_json(Set *addresses, JsonVariant **v) { assert(v); SET_FOREACH(address, addresses) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = address_build_json(address, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = address_append_json(address, &array); if (r < 0) return r; } @@ -85,18 +83,20 @@ static int addresses_append_json(Set *addresses, JsonVariant **v) { return json_variant_set_field_non_null(v, "Addresses", array); } -static int neighbor_build_json(Neighbor *n, JsonVariant **ret) { +static int neighbor_append_json(Neighbor *n, JsonVariant **array) { _cleanup_free_ char *state = NULL; int r; assert(n); - assert(ret); + assert(array); r = network_config_state_to_string_alloc(n->state, &state); if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", n->family), JSON_BUILD_PAIR_IN_ADDR("Destination", &n->in_addr, n->family), JSON_BUILD_PAIR_HW_ADDR("LinkLayerAddress", &n->ll_addr), @@ -112,13 +112,7 @@ static int neighbors_append_json(Set *neighbors, JsonVariant **v) { assert(v); SET_FOREACH(neighbor, neighbors) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = neighbor_build_json(neighbor, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = neighbor_append_json(neighbor, &array); if (r < 0) return r; } @@ -148,13 +142,13 @@ static int nexthop_group_build_json(NextHop *nexthop, JsonVariant **ret) { return 0; } -static int nexthop_build_json(NextHop *n, JsonVariant **ret) { +static int nexthop_append_json(NextHop *n, JsonVariant **array) { _cleanup_(json_variant_unrefp) JsonVariant *group = NULL; _cleanup_free_ char *flags = NULL, *protocol = NULL, *state = NULL; int r; assert(n); - assert(ret); + assert(array); r = route_flags_to_string_alloc(n->flags, &flags); if (r < 0) @@ -172,7 +166,9 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_UNSIGNED("ID", n->id), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &n->gw, n->family), JSON_BUILD_PAIR_UNSIGNED("Flags", n->flags), @@ -185,21 +181,19 @@ static int nexthop_build_json(NextHop *n, JsonVariant **ret) { JSON_BUILD_PAIR_STRING("ConfigState", state))); } -static int nexthops_append_json(Set *nexthops, JsonVariant **v) { +static int nexthops_append_json(Manager *manager, int ifindex, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; NextHop *nexthop; int r; + assert(manager); assert(v); - SET_FOREACH(nexthop, nexthops) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = nexthop_build_json(nexthop, &e); - if (r < 0) - return r; + HASHMAP_FOREACH(nexthop, manager->nexthops_by_id) { + if (nexthop->ifindex != ifindex) + continue; - r = json_variant_append_array(&array, e); + r = nexthop_append_json(nexthop, &array); if (r < 0) return r; } @@ -207,17 +201,12 @@ static int nexthops_append_json(Set *nexthops, JsonVariant **v) { return json_variant_set_field_non_null(v, "NextHops", array); } -static int route_build_json(Route *route, JsonVariant **ret) { +static int route_append_json(Route *route, JsonVariant **array) { _cleanup_free_ char *scope = NULL, *protocol = NULL, *table = NULL, *flags = NULL, *state = NULL; - Manager *manager; int r; assert(route); - assert(ret); - - manager = route->link ? route->link->manager : route->manager; - - assert(manager); + assert(array); r = route_scope_to_string_alloc(route->scope, &scope); if (r < 0) @@ -227,7 +216,7 @@ static int route_build_json(Route *route, JsonVariant **ret) { if (r < 0) return r; - r = manager_get_route_table_to_string(manager, route->table, /* append_num = */ false, &table); + r = manager_get_route_table_to_string(route->manager, route->table, /* append_num = */ false, &table); if (r < 0) return r; @@ -239,11 +228,13 @@ static int route_build_json(Route *route, JsonVariant **ret) { if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", route->family), JSON_BUILD_PAIR_IN_ADDR("Destination", &route->dst, route->family), JSON_BUILD_PAIR_UNSIGNED("DestinationPrefixLength", route->dst_prefixlen), - JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->gw, route->gw_family), + JSON_BUILD_PAIR_IN_ADDR_NON_NULL("Gateway", &route->nexthop.gw, route->nexthop.family), JSON_BUILD_PAIR_CONDITION(route->src_prefixlen > 0, "Source", JSON_BUILD_IN_ADDR(&route->src, route->family)), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("SourcePrefixLength", route->src_prefixlen), @@ -257,7 +248,7 @@ static int route_build_json(Route *route, JsonVariant **ret) { JSON_BUILD_PAIR_UNSIGNED("Priority", route->priority), JSON_BUILD_PAIR_UNSIGNED("Table", route->table), JSON_BUILD_PAIR_STRING("TableString", table), - JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route->mtu), + JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("MTU", route_metric_get(&route->metric, RTAX_MTU)), JSON_BUILD_PAIR_UNSIGNED("Preference", route->pref), JSON_BUILD_PAIR_UNSIGNED("Flags", route->flags), JSON_BUILD_PAIR_STRING("FlagsString", strempty(flags)), @@ -267,21 +258,19 @@ static int route_build_json(Route *route, JsonVariant **ret) { JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", &route->provider, route->family))); } -static int routes_append_json(Set *routes, JsonVariant **v) { +static int routes_append_json(Manager *manager, int ifindex, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; Route *route; int r; + assert(manager); assert(v); - SET_FOREACH(route, routes) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; + SET_FOREACH(route, manager->routes) { + if (route->nexthop.ifindex != ifindex) + continue; - r = route_build_json(route, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = route_append_json(route, &array); if (r < 0) return r; } @@ -289,13 +278,13 @@ static int routes_append_json(Set *routes, JsonVariant **v) { return json_variant_set_field_non_null(v, "Routes", array); } -static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant **ret) { +static int routing_policy_rule_append_json(RoutingPolicyRule *rule, JsonVariant **array) { _cleanup_free_ char *table = NULL, *protocol = NULL, *state = NULL; int r; assert(rule); assert(rule->manager); - assert(ret); + assert(array); r = manager_get_route_table_to_string(rule->manager, rule->table, /* append_num = */ false, &table); if (r < 0 && r != -EINVAL) @@ -309,7 +298,9 @@ static int routing_policy_rule_build_json(RoutingPolicyRule *rule, JsonVariant * if (r < 0) return r; - return json_build(ret, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", rule->family), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("FromPrefix", &rule->from, rule->family), JSON_BUILD_PAIR_CONDITION(in_addr_is_set(rule->family, &rule->from), @@ -354,13 +345,7 @@ static int routing_policy_rules_append_json(Set *rules, JsonVariant **v) { assert(v); SET_FOREACH(rule, rules) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - - r = routing_policy_rule_build_json(rule, &e); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); + r = routing_policy_rule_append_json(rule, &array); if (r < 0) return r; } @@ -464,7 +449,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { return r; } - if (link->dhcp_lease && link->network->dhcp_use_dns) { + if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *dns; union in_addr_union s; int n_dns; @@ -485,7 +470,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *dns; union in_addr_union s; int n_dns; @@ -506,7 +491,7 @@ static int dns_append_json(Link *link, JsonVariant **v) { } } - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *a; SET_FOREACH(a, link->ndisc_rdnss) { @@ -525,40 +510,30 @@ static int dns_append_json(Link *link, JsonVariant **v) { } static int server_append_json_one_addr(int family, const union in_addr_union *a, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_INET, AF_INET6)); assert(a); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_INTEGER("Family", family), JSON_BUILD_PAIR_IN_ADDR("Address", a, family), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int server_append_json_one_fqdn(int family, const char *fqdn, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); assert(fqdn); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("Server", fqdn), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int server_append_json_one_string(const char *str, NetworkConfigSource s, JsonVariant **array) { @@ -590,7 +565,7 @@ static int ntp_append_json(Link *link, JsonVariant **v) { } if (!link->ntp) { - if (link->dhcp_lease && link->network->dhcp_use_ntp) { + if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *ntp; union in_addr_union s; int n_ntp; @@ -611,7 +586,7 @@ static int ntp_append_json(Link *link, JsonVariant **v) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *ntp_addr; union in_addr_union s; char **ntp_fqdn; @@ -682,27 +657,22 @@ static int sip_append_json(Link *link, JsonVariant **v) { } static int domain_append_json(int family, const char *domain, NetworkConfigSource s, const union in_addr_union *p, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(IN_SET(family, AF_UNSPEC, AF_INET, AF_INET6)); assert(domain); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("Domain", domain), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)), JSON_BUILD_PAIR_IN_ADDR_NON_NULL("ConfigProvider", p, family))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; OrderedSet *link_domains, *network_domains; - DHCPUseDomains use_domains; + UseDomains use_domains; union in_addr_union s; char **domains; const char *domain; @@ -716,7 +686,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { link_domains = is_route ? link->route_domains : link->search_domains; network_domains = is_route ? link->network->route_domains : link->network->search_domains; - use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES; ORDERED_SET_FOREACH(domain, link_domains ?: network_domains) { r = domain_append_json(AF_UNSPEC, domain, @@ -728,7 +698,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { if (!link_domains) { if (link->dhcp_lease && - link->network->dhcp_use_domains == use_domains) { + link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { r = sd_dhcp_lease_get_server_identifier(link->dhcp_lease, &s.in); if (r < 0) return r; @@ -748,7 +718,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } if (link->dhcp6_lease && - link->network->dhcp6_use_domains == use_domains) { + link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { r = sd_dhcp6_lease_get_server_address(link->dhcp6_lease, &s.in6); if (r < 0) return r; @@ -761,7 +731,7 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *a; SET_FOREACH(a, link->ndisc_dnssl) { @@ -778,19 +748,14 @@ static int domains_append_json(Link *link, bool is_route, JsonVariant **v) { } static int nta_append_json(const char *nta, NetworkConfigSource s, JsonVariant **array) { - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - int r; - assert(nta); assert(array); - r = json_build(&v, JSON_BUILD_OBJECT( + return json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("DNSSECNegativeTrustAnchor", nta), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(s)))); - if (r < 0) - return r; - - return json_variant_append_array(array, v); } static int ntas_append_json(Link *link, JsonVariant **v) { @@ -830,70 +795,54 @@ static int dns_misc_append_json(Link *link, JsonVariant **v) { resolve_support = link->llmnr >= 0 ? link->llmnr : link->network->llmnr; if (resolve_support >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->llmnr >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("LLMNR", resolve_support_to_string(resolve_support)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } resolve_support = link->mdns >= 0 ? link->mdns : link->network->mdns; if (resolve_support >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->mdns >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("MDNS", resolve_support_to_string(resolve_support)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } t = link->dns_default_route >= 0 ? link->dns_default_route : link->network->dns_default_route; if (t >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->dns_default_route >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_BOOLEAN("DNSDefaultRoute", t), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } mode = link->dns_over_tls_mode >= 0 ? link->dns_over_tls_mode : link->network->dns_over_tls_mode; if (mode >= 0) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - source = link->dns_over_tls_mode >= 0 ? NETWORK_CONFIG_SOURCE_RUNTIME : NETWORK_CONFIG_SOURCE_STATIC; - r = json_build(&e, JSON_BUILD_OBJECT( + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("DNSOverTLS", dns_over_tls_mode_to_string(mode)), JSON_BUILD_PAIR_STRING("ConfigSource", network_config_source_to_string(source)))); if (r < 0) return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; } return json_variant_set_field_non_null(v, "DNSSettings", array); @@ -921,7 +870,7 @@ static int pref64_append_json(Link *link, JsonVariant **v) { assert(link); assert(v); - if (!link->network || !link->network->ipv6_accept_ra_use_pref64) + if (!link->network || !link->network->ndisc_use_pref64) return 0; SET_FOREACH(i, link->ndisc_pref64) { @@ -942,71 +891,6 @@ static int pref64_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "NDisc", w); } -static int dhcp_server_offered_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - DHCPLease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->bound_leases_by_client_id) { - struct in_addr address = { .s_addr = lease->address }; - - r = json_variant_append_arrayb( - &array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.data, - lease->client_id.length), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address), - JSON_BUILD_PAIR_STRING_NON_EMPTY("Hostname", lease->hostname), - JSON_BUILD_PAIR_FINITE_USEC( - "ExpirationUSec", lease->expiration))); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "Leases", array); -} - -static int dhcp_server_static_leases_append_json(Link *link, JsonVariant **v) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - DHCPLease *lease; - int r; - - assert(link); - assert(v); - - if (!link->dhcp_server) - return 0; - - HASHMAP_FOREACH(lease, link->dhcp_server->static_leases_by_client_id) { - _cleanup_(json_variant_unrefp) JsonVariant *e = NULL; - struct in_addr address = { .s_addr = lease->address }; - - r = json_build(&e, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_BYTE_ARRAY( - "ClientId", - lease->client_id.data, - lease->client_id.length), - JSON_BUILD_PAIR_IN4_ADDR_NON_NULL("Address", &address))); - if (r < 0) - return r; - - r = json_variant_append_array(&array, e); - if (r < 0) - return r; - } - - return json_variant_set_field_non_null(v, "StaticLeases", array); -} - static int dhcp_server_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1024,11 +908,11 @@ static int dhcp_server_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; - r = dhcp_server_offered_leases_append_json(link, &w); + r = dhcp_server_bound_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; - r = dhcp_server_static_leases_append_json(link, &w); + r = dhcp_server_static_leases_append_json(link->dhcp_server, &w); if (r < 0) return r; @@ -1132,6 +1016,29 @@ static int dhcp6_client_pd_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "Prefixes", array); } +static int dhcp6_client_duid_append_json(Link *link, JsonVariant **v) { + const sd_dhcp_duid *duid; + const void *data; + size_t data_size; + int r; + + assert(link); + assert(v); + + if (!link->dhcp6_client) + return 0; + + r = sd_dhcp6_client_get_duid(link->dhcp6_client, &duid); + if (r < 0) + return 0; + + r = sd_dhcp_duid_get_raw(duid, &data, &data_size); + if (r < 0) + return 0; + + return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BYTE_ARRAY("DUID", data, data_size))); +} + static int dhcp6_client_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1154,6 +1061,10 @@ static int dhcp6_client_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; + r = dhcp6_client_duid_append_json(link, &w); + if (r < 0) + return r; + return json_variant_set_field_non_null(v, "DHCPv6Client", w); } @@ -1226,6 +1137,52 @@ static int dhcp_client_pd_append_json(Link *link, JsonVariant **v) { return json_variant_set_field_non_null(v, "6rdPrefix", array); } +static int dhcp_client_private_options_append_json(Link *link, JsonVariant **v) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + int r; + + assert(link); + assert(v); + + if (!link->dhcp_lease) + return 0; + + LIST_FOREACH(options, option, link->dhcp_lease->private_options) { + + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("Option", option->tag), + JSON_BUILD_PAIR_HEX("PrivateOptionData", option->data, option->length))); + if (r < 0) + return 0; + } + return json_variant_set_field_non_null(v, "PrivateOptions", array); +} + +static int dhcp_client_id_append_json(Link *link, JsonVariant **v) { + const sd_dhcp_client_id *client_id; + const void *data; + size_t l; + int r; + + assert(link); + assert(v); + + if (!link->dhcp_client) + return 0; + + r = sd_dhcp_client_get_client_id(link->dhcp_client, &client_id); + if (r < 0) + return 0; + + r = sd_dhcp_client_id_get_raw(client_id, &data, &l); + if (r < 0) + return 0; + + return json_variant_merge_objectb(v, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_BYTE_ARRAY("ClientIdentifier", data, l))); +} + static int dhcp_client_append_json(Link *link, JsonVariant **v) { _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; int r; @@ -1244,6 +1201,14 @@ static int dhcp_client_append_json(Link *link, JsonVariant **v) { if (r < 0) return r; + r = dhcp_client_private_options_append_json(link, &w); + if (r < 0) + return r; + + r = dhcp_client_id_append_json(link, &w); + if (r < 0) + return r; + return json_variant_set_field_non_null(v, "DHCPv4Client", w); } @@ -1354,11 +1319,11 @@ int link_build_json(Link *link, JsonVariant **ret) { if (r < 0) return r; - r = nexthops_append_json(link->nexthops, &v); + r = nexthops_append_json(link->manager, link->ifindex, &v); if (r < 0) return r; - r = routes_append_json(link->routes, &v); + r = routes_append_json(link->manager, link->ifindex, &v); if (r < 0) return r; @@ -1417,11 +1382,11 @@ int manager_build_json(Manager *manager, JsonVariant **ret) { if (r < 0) return r; - r = nexthops_append_json(manager->nexthops, &v); + r = nexthops_append_json(manager, /* ifindex = */ 0, &v); if (r < 0) return r; - r = routes_append_json(manager->routes, &v); + r = routes_append_json(manager, /* ifindex = */ 0, &v); if (r < 0) return r; diff --git a/src/network/networkd-link-bus.c b/src/network/networkd-link-bus.c index 58d4875..743957d 100644 --- a/src/network/networkd-link-bus.c +++ b/src/network/networkd-link-bus.c @@ -100,10 +100,12 @@ int bus_link_method_set_ntp_servers(sd_bus_message *message, void *userdata, sd_ return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NTP server: %s", *i); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-ntp-servers", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-ntp-servers", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -134,10 +136,12 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dns-servers", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dns-servers", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) goto finalize; if (r == 0) { @@ -231,10 +235,12 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-domains", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-domains", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -266,10 +272,12 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-default-route", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-default-route", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -310,10 +318,12 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-llmnr", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-llmnr", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -354,10 +364,12 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-mdns", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-mdns", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -398,10 +410,12 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSOverTLS setting: %s", dns_over_tls); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dns-over-tls", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dns-over-tls", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -442,10 +456,12 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dnssec", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dnssec", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -496,10 +512,12 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v return r; } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.set-dnssec-negative-trust-anchors", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.set-dnssec-negative-trust-anchors", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -525,10 +543,11 @@ int bus_link_method_revert_ntp(sd_bus_message *message, void *userdata, sd_bus_e if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.revert-ntp", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.revert-ntp", + /* details= */ NULL, + &l->manager->polkit_registry, error); if (r < 0) return r; if (r == 0) @@ -553,10 +572,12 @@ int bus_link_method_revert_dns(sd_bus_message *message, void *userdata, sd_bus_e if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.revert-dns", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.revert-dns", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -580,10 +601,12 @@ int bus_link_method_force_renew(sd_bus_message *message, void *userdata, sd_bus_ "Interface %s is not managed by systemd-networkd", l->ifname); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.forcerenew", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.forcerenew", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -607,10 +630,12 @@ int bus_link_method_renew(sd_bus_message *message, void *userdata, sd_bus_error "Interface %s is not managed by systemd-networkd", l->ifname); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.renew", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.renew", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -629,10 +654,12 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_ assert(message); - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.reconfigure", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.reconfigure", + /* details= */ NULL, + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 4ef1be4..6b0f099 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -17,7 +18,6 @@ #include "bus-util.h" #include "device-private.h" #include "device-util.h" -#include "dhcp-identifier.h" #include "dhcp-lease-internal.h" #include "env-file.h" #include "ethtool-util.h" @@ -35,6 +35,7 @@ #include "networkd-address.h" #include "networkd-bridge-fdb.h" #include "networkd-bridge-mdb.h" +#include "networkd-bridge-vlan.h" #include "networkd-can.h" #include "networkd-dhcp-prefix-delegation.h" #include "networkd-dhcp-server.h" @@ -71,6 +72,53 @@ #include "udev-util.h" #include "vrf.h" +void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret) { + assert(link); + assert(ret); + + if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online)) + /* If explicitly specified, use it as is. */ + *ret = link->network->required_operstate_for_online; + else if (link->iftype == ARPHRD_CAN) + /* CAN devices do not support addressing, hence defaults to 'carrier'. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_CARRIER, + .max = LINK_OPERSTATE_CARRIER, + }; + else if (link->network && link->network->bond) + /* Bonding slaves do not support addressing. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_ENSLAVED, + .max = LINK_OPERSTATE_ENSLAVED, + }; + else if (STRPTR_IN_SET(link->kind, "batadv", "bond", "bridge", "vrf")) + /* Some of slave interfaces may be offline. */ + *ret = (const LinkOperationalStateRange) { + .min = LINK_OPERSTATE_DEGRADED_CARRIER, + .max = LINK_OPERSTATE_ROUTABLE, + }; + else + *ret = LINK_OPERSTATE_RANGE_DEFAULT; +} + +AddressFamily link_required_family_for_online(Link *link) { + assert(link); + + if (link->network && link->network->required_family_for_online >= 0) + return link->network->required_family_for_online; + + if (link->network && operational_state_range_is_valid(&link->network->required_operstate_for_online)) + /* If RequiredForOnline= is explicitly specified, defaults to no. */ + return ADDRESS_FAMILY_NO; + + if (STRPTR_IN_SET(link->kind, "batadv", "bond", "bridge", "vrf")) + /* As the minimum required operstate for master interfaces is 'degraded-carrier', + * we should request an address assigned to the link for backward compatibility. */ + return ADDRESS_FAMILY_YES; + + return ADDRESS_FAMILY_NO; +} + bool link_ipv6_enabled(Link *link) { assert(link); @@ -207,8 +255,6 @@ static Link *link_free(Link *link) { link_ntp_settings_clear(link); link_dns_settings_clear(link); - link->routes = set_free(link->routes); - link->nexthops = set_free(link->nexthops); link->neighbors = set_free(link->neighbors); link->addresses = set_free(link->addresses); link->qdiscs = set_free(link->qdiscs); @@ -227,7 +273,6 @@ static Link *link_free(Link *link) { free(link->driver); unlink_and_free(link->lease_file); - unlink_and_free(link->lldp_file); unlink_and_free(link->state_file); sd_device_unref(link->dev); @@ -251,7 +296,9 @@ int link_get_by_index(Manager *m, int ifindex, Link **ret) { Link *link; assert(m); - assert(ifindex > 0); + + if (ifindex <= 0) + return -EINVAL; link = hashmap_get(m->links_by_index, INT_TO_PTR(ifindex)); if (!link) @@ -320,7 +367,7 @@ void link_set_state(Link *link, LinkState state) { } int link_stop_engines(Link *link, bool may_keep_dhcp) { - int r = 0, k; + int r, ret = 0; assert(link); assert(link->manager); @@ -333,53 +380,55 @@ int link_stop_engines(Link *link, bool may_keep_dhcp) { FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP_ON_STOP)); if (!keep_dhcp) { - k = sd_dhcp_client_stop(link->dhcp_client); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv4 client: %m"); + r = sd_dhcp_client_stop(link->dhcp_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 client: %m")); } - k = sd_dhcp_server_stop(link->dhcp_server); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv4 server: %m"); + r = sd_dhcp_server_stop(link->dhcp_server); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv4 server: %m")); - k = sd_lldp_rx_stop(link->lldp_rx); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop LLDP Rx: %m"); + r = sd_lldp_rx_stop(link->lldp_rx); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop LLDP Rx: %m")); - k = sd_lldp_tx_stop(link->lldp_tx); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop LLDP Tx: %m"); + r = sd_lldp_tx_stop(link->lldp_tx); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop LLDP Tx: %m")); - k = sd_ipv4ll_stop(link->ipv4ll); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv4 link-local: %m"); + r = sd_ipv4ll_stop(link->ipv4ll); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 link-local: %m")); - k = ipv4acd_stop(link); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv4 ACD client: %m"); + r = ipv4acd_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv4 ACD client: %m")); - k = sd_dhcp6_client_stop(link->dhcp6_client); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop DHCPv6 client: %m"); + r = sd_dhcp6_client_stop(link->dhcp6_client); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop DHCPv6 client: %m")); - k = dhcp_pd_remove(link, /* only_marked = */ false); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not remove DHCPv6 PD addresses and routes: %m"); + r = dhcp_pd_remove(link, /* only_marked = */ false); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not remove DHCPv6 PD addresses and routes: %m")); - k = ndisc_stop(link); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Discovery: %m"); + r = ndisc_stop(link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Discovery: %m")); ndisc_flush(link); - k = sd_radv_stop(link->radv); - if (k < 0) - r = log_link_warning_errno(link, k, "Could not stop IPv6 Router Advertisement: %m"); + r = sd_radv_stop(link->radv); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Could not stop IPv6 Router Advertisement: %m")); - return r; + return ret; } void link_enter_failed(Link *link) { + int r; + assert(link); if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) @@ -389,7 +438,22 @@ void link_enter_failed(Link *link) { link_set_state(link, LINK_STATE_FAILED); - (void) link_stop_engines(link, false); + if (!ratelimit_below(&link->automatic_reconfigure_ratelimit)) { + log_link_warning(link, "The interface entered the failed state frequently, refusing to reconfigure it automatically."); + goto stop; + } + + log_link_info(link, "Trying to reconfigure the interface."); + r = link_reconfigure(link, /* force = */ true); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to reconfigure interface: %m"); + goto stop; + } + + return; + +stop: + (void) link_stop_engines(link, /* may_keep_dhcp = */ false); } void link_check_ready(Link *link) { @@ -415,11 +479,9 @@ void link_check_ready(Link *link) { if (!link->activated) return (void) log_link_debug(link, "%s(): link is not activated.", __func__); - if (link->iftype == ARPHRD_CAN) { + if (link->iftype == ARPHRD_CAN) /* let's shortcut things for CAN which doesn't need most of checks below. */ - link_set_state(link, LINK_STATE_CONFIGURED); - return; - } + goto ready; if (!link->stacked_netdevs_created) return (void) log_link_debug(link, "%s(): stacked netdevs are not created.", __func__); @@ -479,7 +541,7 @@ void link_check_ready(Link *link) { * Note, ignore NDisc when ConfigureWithoutCarrier= is enabled, as IPv6AcceptRA= is enabled by default. */ if (!link_ipv4ll_enabled(link) && !link_dhcp4_enabled(link) && !link_dhcp6_enabled(link) && !link_dhcp_pd_is_enabled(link) && - (link->network->configure_without_carrier || !link_ipv6_accept_ra_enabled(link))) + (link->network->configure_without_carrier || !link_ndisc_enabled(link))) goto ready; bool ipv4ll_ready = @@ -497,8 +559,8 @@ void link_check_ready(Link *link) { (!link->network->dhcp_pd_assign || link_check_addresses_ready(link, NETWORK_CONFIG_SOURCE_DHCP_PD)); bool ndisc_ready = - link_ipv6_accept_ra_enabled(link) && link->ndisc_configured && - (!link->network->ipv6_accept_ra_use_autonomous_prefix || + link_ndisc_enabled(link) && link->ndisc_configured && + (!link->network->ndisc_use_autonomous_prefix || link_check_addresses_ready(link, NETWORK_CONFIG_SOURCE_NDISC)); /* If the uplink for PD is self, then request the corresponding DHCP protocol is also ready. */ @@ -652,11 +714,9 @@ static int link_acquire_dynamic_ipv4_conf(Link *link) { log_link_debug(link, "Acquiring IPv4 link-local address."); } - if (link->dhcp_server) { - r = sd_dhcp_server_start(link->dhcp_server); - if (r < 0) - return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); - } + r = link_start_dhcp4_server(link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not start DHCP server: %m"); r = ipv4acd_start(link); if (r < 0) @@ -930,15 +990,58 @@ static void link_drop_from_master(Link *link) { link_unref(set_remove(master->slaves, link)); } -static void link_drop_requests(Link *link) { +static int link_drop_requests(Link *link) { Request *req; + int ret = 0; assert(link); assert(link->manager); - ORDERED_SET_FOREACH(req, link->manager->request_queue) - if (req->link == link) - request_detach(link->manager, req); + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->link != link) + continue; + + /* If the request is already called, but its reply is not received, then we need to + * drop the configuration (e.g. address) here. Note, if the configuration is known, + * it will be handled later by link_drop_foreign_addresses() or so. */ + if (req->waiting_reply && link->state != LINK_STATE_LINGER) + switch (req->type) { + case REQUEST_TYPE_ADDRESS: { + Address *address = ASSERT_PTR(req->userdata); + + if (address_get(link, address, NULL) < 0) + RET_GATHER(ret, address_remove(address, link)); + break; + } + case REQUEST_TYPE_NEIGHBOR: { + Neighbor *neighbor = ASSERT_PTR(req->userdata); + + if (neighbor_get(link, neighbor, NULL) < 0) + RET_GATHER(ret, neighbor_remove(neighbor, link)); + break; + } + case REQUEST_TYPE_NEXTHOP: { + NextHop *nexthop = ASSERT_PTR(req->userdata); + + if (nexthop_get_by_id(link->manager, nexthop->id, NULL) < 0) + RET_GATHER(ret, nexthop_remove(nexthop, link->manager)); + break; + } + case REQUEST_TYPE_ROUTE: { + Route *route = ASSERT_PTR(req->userdata); + + if (route_get(link->manager, route, NULL) < 0) + RET_GATHER(ret, route_remove(route, link->manager)); + break; + } + default: + ; + } + + request_detach(req); + } + + return ret; } static Link *link_drop(Link *link) { @@ -952,7 +1055,7 @@ static Link *link_drop(Link *link) { /* Drop all references from other links and manager. Note that async netlink calls may have * references to the link, and they will be dropped when we receive replies. */ - link_drop_requests(link); + (void) link_drop_requests(link); link_free_bound_to_list(link); link_free_bound_by_list(link); @@ -1010,12 +1113,11 @@ static int link_drop_managed_config(Link *link) { assert(link); assert(link->manager); - r = link_drop_managed_routes(link); - - RET_GATHER(r, link_drop_managed_nexthops(link)); - RET_GATHER(r, link_drop_managed_addresses(link)); - RET_GATHER(r, link_drop_managed_neighbors(link)); - RET_GATHER(r, link_drop_managed_routing_policy_rules(link)); + r = link_drop_static_routes(link); + RET_GATHER(r, link_drop_static_nexthops(link)); + RET_GATHER(r, link_drop_static_addresses(link)); + RET_GATHER(r, link_drop_static_neighbors(link)); + RET_GATHER(r, link_drop_static_routing_policy_rules(link)); return r; } @@ -1239,10 +1341,20 @@ int link_reconfigure_impl(Link *link, bool force) { return 0; if (network) { + _cleanup_free_ char *joined = strv_join(network->dropins, ", "); + if (link->state == LINK_STATE_INITIALIZED) - log_link_info(link, "Configuring with %s.", network->filename); + log_link_info(link, "Configuring with %s%s%s%s.", + network->filename, + isempty(joined) ? "" : " (dropins: ", + joined, + isempty(joined) ? "" : ")"); else - log_link_info(link, "Reconfiguring with %s.", network->filename); + log_link_info(link, "Reconfiguring with %s%s%s%s.", + network->filename, + isempty(joined) ? "" : " (dropins: ", + joined, + isempty(joined) ? "" : ")"); } else log_link_full(link, link->state == LINK_STATE_INITIALIZED ? LOG_DEBUG : LOG_INFO, "Unmanaging interface."); @@ -1252,7 +1364,9 @@ int link_reconfigure_impl(Link *link, bool force) { if (r < 0) return r; - link_drop_requests(link); + r = link_drop_requests(link); + if (r < 0) + return r; if (network && !force && network->keep_configuration != KEEP_CONFIGURATION_YES) /* When a new/updated .network file is assigned, first make all configs (addresses, @@ -1346,6 +1460,80 @@ int link_reconfigure(Link *link, bool force) { return 1; /* 1 means the interface will be reconfigured. */ } +typedef struct ReconfigureData { + Link *link; + Manager *manager; + sd_bus_message *message; +} ReconfigureData; + +static void reconfigure_data_destroy_callback(ReconfigureData *data) { + int r; + + assert(data); + assert(data->link); + assert(data->manager); + assert(data->manager->reloading > 0); + assert(data->message); + + link_unref(data->link); + + data->manager->reloading--; + if (data->manager->reloading <= 0) { + r = sd_bus_reply_method_return(data->message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to send reply for 'Reload' DBus method, ignoring: %m"); + } + + sd_bus_message_unref(data->message); + free(data); +} + +static int reconfigure_handler_on_bus_method_reload(sd_netlink *rtnl, sd_netlink_message *m, ReconfigureData *data) { + assert(data); + assert(data->link); + return link_reconfigure_handler_internal(rtnl, m, data->link, /* force = */ false); +} + +int link_reconfigure_on_bus_method_reload(Link *link, sd_bus_message *message) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + _cleanup_free_ ReconfigureData *data = NULL; + int r; + + assert(link); + assert(link->manager); + assert(link->manager->rtnl); + assert(message); + + /* See comments in link_reconfigure() above. */ + if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_INITIALIZED, LINK_STATE_LINGER)) + return 0; + + r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK, link->ifindex); + if (r < 0) + return r; + + data = new(ReconfigureData, 1); + if (!data) + return -ENOMEM; + + r = netlink_call_async(link->manager->rtnl, NULL, req, + reconfigure_handler_on_bus_method_reload, + reconfigure_data_destroy_callback, data); + if (r < 0) + return r; + + *data = (ReconfigureData) { + .link = link_ref(link), + .manager = link->manager, + .message = sd_bus_message_ref(message), + }; + + link->manager->reloading++; + + TAKE_PTR(data); + return 0; +} + static int link_initialized_and_synced(Link *link) { int r; @@ -1443,9 +1631,9 @@ static int link_check_initialized(Link *link) { return 0; } - r = sd_device_get_is_initialized(device); + r = device_is_processed(device); if (r < 0) - return log_link_warning_errno(link, r, "Could not determine whether the device is initialized: %m"); + return log_link_warning_errno(link, r, "Could not determine whether the device is processed by udevd: %m"); if (r == 0) { /* not yet ready */ log_link_debug(link, "link pending udev initialization..."); @@ -1693,7 +1881,7 @@ static int link_admin_state_up(Link *link) { /* We set the ipv6 mtu after the device mtu, but the kernel resets * ipv6 mtu on NETDEV_UP, so we need to reset it. */ - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); @@ -1781,14 +1969,18 @@ void link_update_operstate(Link *link, bool also_update_master) { else operstate = LINK_OPERSTATE_ENSLAVED; + LinkOperationalStateRange req; + link_required_operstate_for_online(link, &req); + /* Only determine online state for managed links with RequiredForOnline=yes */ if (!link->network || !link->network->required_for_online) online_state = _LINK_ONLINE_STATE_INVALID; - else if (operstate < link->network->required_operstate_for_online.min || - operstate > link->network->required_operstate_for_online.max) + + else if (!operational_state_is_in_range(operstate, &req)) online_state = LINK_ONLINE_STATE_OFFLINE; + else { - AddressFamily required_family = link->network->required_family_for_online; + AddressFamily required_family = link_required_family_for_online(link); bool needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4; bool needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6; @@ -1797,14 +1989,14 @@ void link_update_operstate(Link *link, bool also_update_master) { * to offline in the blocks below. */ online_state = LINK_ONLINE_STATE_ONLINE; - if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_DEGRADED) { + if (req.min >= LINK_OPERSTATE_DEGRADED) { if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED) online_state = LINK_ONLINE_STATE_OFFLINE; if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_DEGRADED) online_state = LINK_ONLINE_STATE_OFFLINE; } - if (link->network->required_operstate_for_online.min >= LINK_OPERSTATE_ROUTABLE) { + if (req.min >= LINK_OPERSTATE_ROUTABLE) { if (needs_ipv4 && ipv4_address_state < LINK_ADDRESS_STATE_ROUTABLE) online_state = LINK_ONLINE_STATE_OFFLINE; if (needs_ipv6 && ipv6_address_state < LINK_ADDRESS_STATE_ROUTABLE) @@ -2242,6 +2434,13 @@ static int link_update_mtu(Link *link, sd_netlink_message *message) { link->mtu = mtu; + if (IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) { + /* The kernel resets IPv6 MTU after changing device MTU. So, we need to re-set IPv6 MTU again. */ + r = link_set_ipv6_mtu(link, LOG_INFO); + if (r < 0) + log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); + } + if (link->dhcp_client) { r = sd_dhcp_client_set_mtu(link->dhcp_client, link->mtu); if (r < 0) @@ -2339,7 +2538,7 @@ static int link_update_name(Link *link, sd_netlink_message *message) { if (link->dhcp6_client) { r = sd_dhcp6_client_set_ifname(link->dhcp6_client, link->ifname); if (r < 0) - return log_link_debug_errno(link, r, "Failed to update interface name in DHCP6 client: %m"); + return log_link_debug_errno(link, r, "Failed to update interface name in DHCPv6 client: %m"); } if (link->ndisc) { @@ -2433,6 +2632,10 @@ static int link_update(Link *link, sd_netlink_message *message) { if (r < 0) return r; + r = link_update_bridge_vlan(link, message); + if (r < 0) + return r; + return needs_reconfigure; } @@ -2447,7 +2650,7 @@ static Link *link_drop_or_unref(Link *link) { DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_drop_or_unref); static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { - _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL, *lldp_file = NULL; + _cleanup_free_ char *ifname = NULL, *kind = NULL, *state_file = NULL, *lease_file = NULL; _cleanup_(link_drop_or_unrefp) Link *link = NULL; unsigned short iftype; int r, ifindex; @@ -2488,9 +2691,6 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { if (asprintf(&lease_file, "/run/systemd/netif/leases/%d", ifindex) < 0) return log_oom_debug(); - - if (asprintf(&lldp_file, "/run/systemd/netif/lldp/%d", ifindex) < 0) - return log_oom_debug(); } link = new(Link, 1); @@ -2501,16 +2701,18 @@ static int link_new(Manager *manager, sd_netlink_message *message, Link **ret) { .n_ref = 1, .state = LINK_STATE_PENDING, .online_state = _LINK_ONLINE_STATE_INVALID, + .automatic_reconfigure_ratelimit = (const RateLimit) { .interval = 10 * USEC_PER_SEC, .burst = 5 }, .ifindex = ifindex, .iftype = iftype, .ifname = TAKE_PTR(ifname), .kind = TAKE_PTR(kind), + .bridge_vlan_pvid = UINT16_MAX, + .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, .state_file = TAKE_PTR(state_file), .lease_file = TAKE_PTR(lease_file), - .lldp_file = TAKE_PTR(lldp_file), .n_dns = UINT_MAX, .dns_default_route = -1, diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 938bbf4..b1b2fe4 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -21,9 +21,11 @@ #include "log-link.h" #include "netif-util.h" #include "network-util.h" +#include "networkd-bridge-vlan.h" #include "networkd-ipv6ll.h" #include "networkd-util.h" #include "ordered-set.h" +#include "ratelimit.h" #include "resolve-util.h" #include "set.h" @@ -72,6 +74,11 @@ typedef struct Link { sd_device *dev; char *driver; + /* bridge vlan */ + uint16_t bridge_vlan_pvid; + bool bridge_vlan_pvid_is_untagged; + uint32_t bridge_vlan_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + /* to prevent multiple ethtool calls */ bool ethtool_driver_read; bool ethtool_permanent_hw_addr_read; @@ -100,6 +107,7 @@ typedef struct Link { LinkAddressState ipv4_address_state; LinkAddressState ipv6_address_state; LinkOnlineState online_state; + RateLimit automatic_reconfigure_ratelimit; unsigned static_address_messages; unsigned static_address_label_messages; @@ -118,8 +126,6 @@ typedef struct Link { Set *addresses; Set *neighbors; - Set *routes; - Set *nexthops; Set *qdiscs; Set *tclasses; @@ -149,15 +155,19 @@ typedef struct Link { bool activated:1; bool master_set:1; bool stacked_netdevs_created:1; + bool bridge_vlan_set:1; sd_dhcp_server *dhcp_server; sd_ndisc *ndisc; sd_event_source *ndisc_expire; + Hashmap *ndisc_routers_by_sender; Set *ndisc_rdnss; Set *ndisc_dnssl; Set *ndisc_captive_portals; Set *ndisc_pref64; + Set *ndisc_redirects; + uint32_t ndisc_mtu; unsigned ndisc_messages; bool ndisc_configured:1; @@ -174,7 +184,6 @@ typedef struct Link { /* This is about LLDP reception */ sd_lldp_rx *lldp_rx; - char *lldp_file; /* This is about LLDP transmission */ sd_lldp_tx *lldp_tx; @@ -245,9 +254,13 @@ LinkState link_state_from_string(const char *s) _pure_; int link_reconfigure_impl(Link *link, bool force); int link_reconfigure(Link *link, bool force); +int link_reconfigure_on_bus_method_reload(Link *link, sd_bus_message *message); int manager_udev_process_link(Manager *m, sd_device *device, sd_device_action_t action); int manager_rtnl_process_link(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); int link_flags_to_string_alloc(uint32_t flags, char **ret); const char *kernel_operstate_to_string(int t) _const_; + +void link_required_operstate_for_online(Link *link, LinkOperationalStateRange *ret); +AddressFamily link_required_family_for_online(Link *link); diff --git a/src/network/networkd-lldp-rx.c b/src/network/networkd-lldp-rx.c index 3a59884..f744854 100644 --- a/src/network/networkd-lldp-rx.c +++ b/src/network/networkd-lldp-rx.c @@ -52,8 +52,6 @@ static void lldp_rx_handler(sd_lldp_rx *lldp_rx, sd_lldp_rx_event_t event, sd_ll Link *link = ASSERT_PTR(userdata); int r; - (void) link_lldp_save(link); - if (link->lldp_tx && event == SD_LLDP_RX_EVENT_ADDED) { /* If we received information about a new neighbor, restart the LLDP "fast" logic */ @@ -104,70 +102,3 @@ int link_lldp_rx_configure(Link *link) { return 0; } - -int link_lldp_save(Link *link) { - _cleanup_(unlink_and_freep) char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - sd_lldp_neighbor **l = NULL; - int n = 0, r, i; - - assert(link); - - if (isempty(link->lldp_file)) - return 0; /* Do not update state file when running in test mode. */ - - if (!link->lldp_rx) { - (void) unlink(link->lldp_file); - return 0; - } - - r = sd_lldp_rx_get_neighbors(link->lldp_rx, &l); - if (r < 0) - return r; - if (r == 0) { - (void) unlink(link->lldp_file); - return 0; - } - - n = r; - - r = fopen_temporary(link->lldp_file, &f, &temp_path); - if (r < 0) - goto finish; - - (void) fchmod(fileno(f), 0644); - - for (i = 0; i < n; i++) { - const void *p; - le64_t u; - size_t sz; - - r = sd_lldp_neighbor_get_raw(l[i], &p, &sz); - if (r < 0) - goto finish; - - u = htole64(sz); - (void) fwrite(&u, 1, sizeof(u), f); - (void) fwrite(p, 1, sz, f); - } - - r = fflush_and_check(f); - if (r < 0) - goto finish; - - r = conservative_rename(temp_path, link->lldp_file); - if (r < 0) - goto finish; - -finish: - if (r < 0) - log_link_error_errno(link, r, "Failed to save LLDP data to %s: %m", link->lldp_file); - - if (l) { - for (i = 0; i < n; i++) - sd_lldp_neighbor_unref(l[i]); - free(l); - } - - return r; -} diff --git a/src/network/networkd-lldp-rx.h b/src/network/networkd-lldp-rx.h index 22f6602..75c9f8c 100644 --- a/src/network/networkd-lldp-rx.h +++ b/src/network/networkd-lldp-rx.h @@ -14,7 +14,6 @@ typedef enum LLDPMode { } LLDPMode; int link_lldp_rx_configure(Link *link); -int link_lldp_save(Link *link); const char* lldp_mode_to_string(LLDPMode m) _const_; LLDPMode lldp_mode_from_string(const char *s) _pure_; diff --git a/src/network/networkd-lldp-tx.c b/src/network/networkd-lldp-tx.c index fc9196f..f48781e 100644 --- a/src/network/networkd-lldp-tx.c +++ b/src/network/networkd-lldp-tx.c @@ -8,6 +8,7 @@ #include "networkd-link.h" #include "networkd-lldp-tx.h" #include "networkd-manager.h" +#include "networkd-sysctl.h" #include "parse-util.h" #include "string-table.h" #include "string-util.h" @@ -69,9 +70,8 @@ int link_lldp_tx_configure(Link *link) { SD_LLDP_SYSTEM_CAPABILITIES_STATION | SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE | SD_LLDP_SYSTEM_CAPABILITIES_ROUTER, - (link->network->ip_forward != ADDRESS_FAMILY_NO) ? - SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : - SD_LLDP_SYSTEM_CAPABILITIES_STATION); + (link_get_ip_forwarding(link, AF_INET) > 0 || link_get_ip_forwarding(link, AF_INET6) > 0) ? + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER : SD_LLDP_SYSTEM_CAPABILITIES_STATION); if (r < 0) return r; diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c index aecbc1d..3c3d815 100644 --- a/src/network/networkd-manager-bus.c +++ b/src/network/networkd-manager-bus.c @@ -198,22 +198,30 @@ static int bus_method_reconfigure_link(sd_bus_message *message, void *userdata, } static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; + Manager *manager = ASSERT_PTR(userdata); int r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.network1.reload", - NULL, true, UID_INVALID, - &manager->polkit_registry, error); + if (manager->reloading > 0) + return sd_bus_error_set(error, BUS_ERROR_NETWORK_ALREADY_RELOADING, "Already reloading."); + + r = bus_verify_polkit_async( + message, + "org.freedesktop.network1.reload", + /* details= */ NULL, + &manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) return 1; /* Polkit will call us back */ - r = manager_reload(manager); + r = manager_reload(manager, message); if (r < 0) return r; + if (manager->reloading > 0) + return 1; /* Will reply later. */ + return sd_bus_reply_method_return(message, NULL); } @@ -277,6 +285,31 @@ static int property_get_namespace_id( return sd_bus_message_append(reply, "t", id); } +static int property_get_namespace_nsid( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint32_t nsid = UINT32_MAX; + int r; + + assert(bus); + assert(reply); + + /* Returns our own "nsid", which is another ID for the network namespace, different from the inode + * number. */ + + r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid); + if (r < 0) + log_warning_errno(r, "Failed to query network nsid, ignoring: %m"); + + return sd_bus_message_append(reply, "u", nsid); +} + static const sd_bus_vtable manager_vtable[] = { SD_BUS_VTABLE_START(0), @@ -287,6 +320,7 @@ static const sd_bus_vtable manager_vtable[] = { SD_BUS_PROPERTY("IPv6AddressState", "s", property_get_address_state, offsetof(Manager, ipv6_address_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("OnlineState", "s", property_get_online_state, offsetof(Manager, online_state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), SD_BUS_PROPERTY("NamespaceId", "t", property_get_namespace_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NamespaceNSID", "u", property_get_namespace_nsid, 0, 0), SD_BUS_METHOD_WITH_ARGS("ListLinks", SD_BUS_NO_ARGS, diff --git a/src/network/networkd-manager-varlink.c b/src/network/networkd-manager-varlink.c new file mode 100644 index 0000000..5eeed95 --- /dev/null +++ b/src/network/networkd-manager-varlink.c @@ -0,0 +1,314 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "bus-polkit.h" +#include "fd-util.h" +#include "lldp-rx-internal.h" +#include "networkd-dhcp-server.h" +#include "networkd-manager-varlink.h" +#include "stat-util.h" +#include "varlink.h" +#include "varlink-io.systemd.Network.h" + +static int vl_method_get_states(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + return varlink_replyb(link, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("AddressState", link_address_state_to_string(m->address_state)), + JSON_BUILD_PAIR_STRING("IPv4AddressState", link_address_state_to_string(m->ipv4_address_state)), + JSON_BUILD_PAIR_STRING("IPv6AddressState", link_address_state_to_string(m->ipv6_address_state)), + JSON_BUILD_PAIR_STRING("CarrierState", link_carrier_state_to_string(m->carrier_state)), + JSON_BUILD_PAIR_CONDITION(m->online_state >= 0, "OnlineState", JSON_BUILD_STRING(link_online_state_to_string(m->online_state))), + JSON_BUILD_PAIR_STRING("OperationalState", link_operstate_to_string(m->operational_state)))); +} + +static int vl_method_get_namespace_id(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + uint64_t inode = 0; + uint32_t nsid = UINT32_MAX; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + /* Network namespaces have two identifiers: the inode number (which all namespace types have), and + * the "nsid" (aka the "cookie"), which only network namespaces know as a concept, and which is not + * assigned by default, but once it is, is fixed. Let's return both, to avoid any confusion which one + * this is. */ + + struct stat st; + if (stat("/proc/self/ns/net", &st) < 0) + log_warning_errno(errno, "Failed to stat network namespace, ignoring: %m"); + else + inode = st.st_ino; + + r = netns_get_nsid(/* netnsfd= */ -EBADF, &nsid); + if (r < 0) + log_full_errno(r == -ENODATA ? LOG_DEBUG : LOG_WARNING, r, "Failed to query network nsid, ignoring: %m"); + + return varlink_replyb(link, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("NamespaceId", inode), + JSON_BUILD_PAIR_CONDITION(nsid == UINT32_MAX, "NamespaceNSID", JSON_BUILD_NULL), + JSON_BUILD_PAIR_CONDITION(nsid != UINT32_MAX, "NamespaceNSID", JSON_BUILD_UNSIGNED(nsid)))); +} + +typedef struct InterfaceInfo { + int ifindex; + const char *ifname; +} InterfaceInfo; + +static int dispatch_interface(Varlink *vlink, JsonVariant *parameters, Manager *manager, Link **ret) { + static const JsonDispatch dispatch_table[] = { + { "InterfaceIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(InterfaceInfo, ifindex), 0 }, + { "InterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(InterfaceInfo, ifname), 0 }, + {} + }; + + InterfaceInfo info = {}; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &info); + if (r != 0) + return r; + + if (info.ifindex < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifindex > 0 && link_get_by_index(manager, info.ifindex, &link) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceIndex")); + if (info.ifname) { + Link *link_by_name; + + if (link_get_by_name(manager, info.ifname, &link_by_name) < 0) + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + if (link && link_by_name != link) + /* If both arguments are specified, then these must be consistent. */ + return varlink_error_invalid_parameter(vlink, JSON_VARIANT_STRING_CONST("InterfaceName")); + + link = link_by_name; + } + + /* If neither InterfaceIndex nor InterfaceName specified, this function returns NULL. */ + *ret = link; + return 0; +} + +static int link_append_lldp_neighbors(Link *link, JsonVariant *v, JsonVariant **array) { + assert(link); + assert(array); + + return json_variant_append_arrayb(array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_INTEGER("InterfaceIndex", link->ifindex), + JSON_BUILD_PAIR_STRING("InterfaceName", link->ifname), + JSON_BUILD_PAIR_STRV_NON_EMPTY("InterfaceAlternativeNames", link->alternative_names), + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(v), "Neighbors", JSON_BUILD_VARIANT(v)))); +} + +static int vl_method_get_lldp_neighbors(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, Manager *manager) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + Link *link = NULL; + int r; + + assert(vlink); + assert(manager); + + r = dispatch_interface(vlink, parameters, manager, &link); + if (r != 0) + return r; + + if (link) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (link->lldp_rx) { + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + } + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } else + HASHMAP_FOREACH(link, manager->links_by_index) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (!link->lldp_rx) + continue; + + r = lldp_rx_build_neighbors_json(link->lldp_rx, &v); + if (r < 0) + return r; + + if (json_variant_is_blank_array(v)) + continue; + + r = link_append_lldp_neighbors(link, v, &array); + if (r < 0) + return r; + } + + return varlink_replyb(vlink, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_EMPTY_ARRAY), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_array(array), "Neighbors", JSON_BUILD_VARIANT(array)))); +} + +static int vl_method_set_persistent_storage(Varlink *vlink, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "Ready", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, 0, 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + bool ready; + int r; + + assert(vlink); + + r = varlink_dispatch(vlink, parameters, dispatch_table, &ready); + if (r != 0) + return r; + + if (ready) { + struct stat st, st_prev; + int fd; + + fd = varlink_peek_fd(vlink, 0); + if (fd < 0) + return log_warning_errno(fd, "Failed to peek file descriptor of the persistent storage: %m"); + + r = fd_verify_safe_flags_full(fd, O_DIRECTORY); + if (r == -EREMOTEIO) + return log_warning_errno(r, "Passed persistent storage fd has unexpected flags, refusing."); + if (r < 0) + return log_warning_errno(r, "Failed to verify flags of passed persistent storage fd: %m"); + + r = fd_is_read_only_fs(fd); + if (r < 0) + return log_warning_errno(r, "Failed to check if the persistent storage is writable: %m"); + if (r > 0) { + log_warning("The persistent storage is on read-only filesystem."); + return varlink_error(vlink, "io.systemd.Network.StorageReadOnly", NULL); + } + + if (fstat(fd, &st) < 0) + return log_warning_errno(errno, "Failed to stat the passed persistent storage fd: %m"); + + r = stat_verify_directory(&st); + if (r < 0) + return log_warning_errno(r, "The passed persistent storage fd is not a directory, refusing: %m"); + + if (manager->persistent_storage_fd >= 0 && + fstat(manager->persistent_storage_fd, &st_prev) >= 0 && + stat_inode_same(&st, &st_prev)) + return varlink_reply(vlink, NULL); + + } else { + if (manager->persistent_storage_fd < 0) + return varlink_reply(vlink, NULL); + } + + r = varlink_verify_polkit_async( + vlink, + manager->bus, + "org.freedesktop.network1.set-persistent-storage", + /* details= */ NULL, + &manager->polkit_registry); + if (r <= 0) + return r; + + if (ready) { + _cleanup_close_ int fd = -EBADF; + + fd = varlink_take_fd(vlink, 0); + if (fd < 0) + return log_warning_errno(fd, "Failed to take file descriptor of the persistent storage: %m"); + + close_and_replace(manager->persistent_storage_fd, fd); + } else + manager->persistent_storage_fd = safe_close(manager->persistent_storage_fd); + + manager_toggle_dhcp4_server_state(manager, ready); + + return varlink_reply(vlink, NULL); +} + +static int on_connect(VarlinkServer *s, Varlink *vlink, void *userdata) { + int r; + + assert(vlink); + + r = varlink_set_allow_fd_passing_input(vlink, true); + if (r < 0) + return log_warning_errno(r, "Failed to allow receiving file descriptor through varlink: %m"); + + return 0; +} + +int manager_connect_varlink(Manager *m) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + + if (m->varlink_server) + return 0; + + r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + (void) varlink_server_set_description(s, "varlink-api-network"); + + r = varlink_server_add_interface(s, &vl_interface_io_systemd_Network); + if (r < 0) + return log_error_errno(r, "Failed to add Network interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + s, + "io.systemd.Network.GetStates", vl_method_get_states, + "io.systemd.Network.GetNamespaceId", vl_method_get_namespace_id, + "io.systemd.Network.GetLLDPNeighbors", vl_method_get_lldp_neighbors, + "io.systemd.Network.SetPersistentStorage", vl_method_set_persistent_storage); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + r = varlink_server_listen_address(s, "/run/systemd/netif/io.systemd.Network", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + r = varlink_server_bind_connect(s, on_connect); + if (r < 0) + return log_error_errno(r, "Failed to set on-connect callback for varlink: %m"); + + m->varlink_server = TAKE_PTR(s); + return 0; +} + +void manager_varlink_done(Manager *m) { + assert(m); + + m->varlink_server = varlink_server_unref(m->varlink_server); + (void) unlink("/run/systemd/netif/io.systemd.Network"); +} diff --git a/src/network/networkd-manager-varlink.h b/src/network/networkd-manager-varlink.h new file mode 100644 index 0000000..46078a8 --- /dev/null +++ b/src/network/networkd-manager-varlink.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "networkd-manager.h" + +int manager_connect_varlink(Manager *m); +void manager_varlink_done(Manager *m); diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index c09dcfb..4ec4550 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -23,6 +23,7 @@ #include "device-private.h" #include "device-util.h" #include "dns-domain.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "firewall-util.h" @@ -36,8 +37,9 @@ #include "networkd-dhcp-server-bus.h" #include "networkd-dhcp6.h" #include "networkd-link-bus.h" -#include "networkd-manager-bus.h" #include "networkd-manager.h" +#include "networkd-manager-bus.h" +#include "networkd-manager-varlink.h" #include "networkd-neighbor.h" #include "networkd-network-bus.h" #include "networkd-nexthop.h" @@ -163,7 +165,6 @@ static int manager_connect_bus(Manager *m) { static int manager_process_uevent(sd_device_monitor *monitor, sd_device *device, void *userdata) { Manager *m = ASSERT_PTR(userdata); sd_device_action_t action; - const char *s; int r; assert(device); @@ -172,20 +173,12 @@ static int manager_process_uevent(sd_device_monitor *monitor, sd_device *device, if (r < 0) return log_device_warning_errno(device, r, "Failed to get udev action, ignoring: %m"); - r = sd_device_get_subsystem(device, &s); - if (r < 0) - return log_device_warning_errno(device, r, "Failed to get subsystem, ignoring: %m"); - - if (streq(s, "net")) + if (device_in_subsystem(device, "net")) r = manager_udev_process_link(m, device, action); - else if (streq(s, "ieee80211")) + else if (device_in_subsystem(device, "ieee80211")) r = manager_udev_process_wiphy(m, device, action); - else if (streq(s, "rfkill")) + else if (device_in_subsystem(device, "rfkill")) r = manager_udev_process_rfkill(m, device, action); - else { - log_device_debug(device, "Received device with unexpected subsystem \"%s\", ignoring.", s); - return 0; - } if (r < 0) log_device_warning_errno(device, r, "Failed to process \"%s\" uevent, ignoring: %m", device_action_to_string(action)); @@ -429,24 +422,13 @@ static int manager_connect_rtnl(Manager *m, int fd) { return manager_setup_rtnl_filter(m); } -static int manager_dirty_handler(sd_event_source *s, void *userdata) { - Manager *m = ASSERT_PTR(userdata); - Link *link; - int r; - - if (m->dirty) { - r = manager_save(m); - if (r < 0) - log_warning_errno(r, "Failed to update state file %s, ignoring: %m", m->state_file); - } - - SET_FOREACH(link, m->dirty_links) { - r = link_save_and_clean(link); - if (r < 0) - log_link_warning_errno(link, r, "Failed to update link state file %s, ignoring: %m", link->state_file); - } +static int manager_post_handler(sd_event_source *s, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); - return 1; + (void) manager_process_remove_requests(manager); + (void) manager_process_requests(manager); + (void) manager_clean_all(manager); + return 0; } static int signal_terminate_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { @@ -472,7 +454,7 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { Manager *m = ASSERT_PTR(userdata); - manager_reload(m); + (void) manager_reload(m, /* message = */ NULL); return 0; } @@ -522,11 +504,7 @@ int manager_setup(Manager *m) { if (r < 0) log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); - r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m); - if (r < 0) - return r; - - r = sd_event_add_post(m->event, NULL, manager_process_requests, m); + r = sd_event_add_post(m->event, NULL, manager_post_handler, m); if (r < 0) return r; @@ -545,6 +523,10 @@ int manager_setup(Manager *m) { if (m->test_mode) return 0; + r = manager_connect_varlink(m); + if (r < 0) + return r; + r = manager_connect_bus(m); if (r < 0) return r; @@ -576,6 +558,29 @@ int manager_setup(Manager *m) { return 0; } +static int persistent_storage_open(void) { + _cleanup_close_ int fd = -EBADF; + int r; + + r = getenv_bool("SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY"); + if (r < 0 && r != -ENXIO) + return log_debug_errno(r, "Failed to parse $SYSTEMD_NETWORK_PERSISTENT_STORAGE_READY environment variable, ignoring: %m"); + if (r <= 0) + return -EBADF; + + fd = open("/var/lib/systemd/network/", O_CLOEXEC | O_DIRECTORY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open /var/lib/systemd/network/, ignoring: %m"); + + r = fd_is_read_only_fs(fd); + if (r < 0) + return log_debug_errno(r, "Failed to check if /var/lib/systemd/network/ is writable: %m"); + if (r > 0) + return log_debug_errno(SYNTHETIC_ERRNO(EROFS), "The directory /var/lib/systemd/network/ is on read-only filesystem."); + + return TAKE_FD(fd); +} + int manager_new(Manager **ret, bool test_mode) { _cleanup_(manager_freep) Manager *m = NULL; @@ -591,10 +596,17 @@ int manager_new(Manager **ret, bool test_mode) { .online_state = _LINK_ONLINE_STATE_INVALID, .manage_foreign_routes = true, .manage_foreign_rules = true, + .manage_foreign_nexthops = true, .ethtool_fd = -EBADF, + .persistent_storage_fd = persistent_storage_open(), + .dhcp_use_domains = _USE_DOMAINS_INVALID, + .dhcp6_use_domains = _USE_DOMAINS_INVALID, + .ndisc_use_domains = _USE_DOMAINS_INVALID, .dhcp_duid.type = DUID_TYPE_EN, .dhcp6_duid.type = DUID_TYPE_EN, .duid_product_uuid.type = DUID_TYPE_UUID, + .dhcp_server_persist_leases = true, + .ip_forwarding = { -1, -1, }, }; *ret = TAKE_PTR(m); @@ -613,6 +625,7 @@ Manager* manager_free(Manager *m) { (void) link_stop_engines(link, true); m->request_queue = ordered_set_free(m->request_queue); + m->remove_request_queue = ordered_set_free(m->remove_request_queue); m->dirty_links = set_free_with_destructor(m->dirty_links, link_unref); m->new_wlan_ifindices = set_free(m->new_wlan_ifindices); @@ -648,21 +661,23 @@ Manager* manager_free(Manager *m) { * set_free() must be called after the above sd_netlink_unref(). */ m->routes = set_free(m->routes); - m->nexthops = set_free(m->nexthops); m->nexthops_by_id = hashmap_free(m->nexthops_by_id); + m->nexthop_ids = set_free(m->nexthop_ids); sd_event_source_unref(m->speed_meter_event_source); sd_event_unref(m->event); sd_device_monitor_unref(m->device_monitor); - bus_verify_polkit_async_registry_free(m->polkit_registry); + manager_varlink_done(m); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); free(m->dynamic_timezone); free(m->dynamic_hostname); safe_close(m->ethtool_fd); + safe_close(m->persistent_storage_fd); m->fw_ctx = fw_ctx_free(m->fw_ctx); @@ -675,6 +690,8 @@ int manager_start(Manager *m) { assert(m); + manager_set_sysctl(m); + r = manager_start_speed_meter(m); if (r < 0) return log_error_errno(r, "Failed to initialize speed meter: %m"); @@ -708,7 +725,15 @@ int manager_load_config(Manager *m) { if (r < 0) return r; - return manager_build_dhcp_pd_subnet_ids(m); + r = manager_build_dhcp_pd_subnet_ids(m); + if (r < 0) + return r; + + r = manager_build_nexthop_ids(m); + if (r < 0) + return r; + + return 0; } int manager_enumerate_internal( @@ -752,6 +777,20 @@ static int manager_enumerate_links(Manager *m) { if (r < 0) return r; + r = manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); + if (r < 0) + return r; + + req = sd_netlink_message_unref(req); + + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); + if (r < 0) + return r; + return manager_enumerate_internal(m, m->rtnl, req, manager_rtnl_process_link); } @@ -853,6 +892,9 @@ static int manager_enumerate_nexthop(Manager *m) { assert(m); assert(m->rtnl); + if (!m->manage_foreign_nexthops) + return 0; + r = sd_rtnl_message_new_nexthop(m->rtnl, &req, RTM_GETNEXTHOP, 0, 0); if (r < 0) return r; @@ -1076,16 +1118,13 @@ int manager_set_timezone(Manager *m, const char *tz) { return 0; } -int manager_reload(Manager *m) { +int manager_reload(Manager *m, sd_bus_message *message) { Link *link; int r; assert(m); - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Reloading configuration...\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + (void) notify_reloading(); r = netdev_load(m, /* reload= */ true); if (r < 0) @@ -1096,9 +1135,14 @@ int manager_reload(Manager *m) { goto finish; HASHMAP_FOREACH(link, m->links_by_index) { - r = link_reconfigure(link, /* force = */ false); - if (r < 0) - goto finish; + if (message) + r = link_reconfigure_on_bus_method_reload(link, message); + else + r = link_reconfigure(link, /* force = */ false); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to reconfigure the interface: %m"); + link_enter_failed(link); + } } r = 0; diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h index fbef528..c14a98f 100644 --- a/src/network/networkd-manager.h +++ b/src/network/networkd-manager.h @@ -8,7 +8,7 @@ #include "sd-netlink.h" #include "sd-resolve.h" -#include "dhcp-identifier.h" +#include "dhcp-duid-internal.h" #include "firewall-util.h" #include "hashmap.h" #include "networkd-link.h" @@ -17,6 +17,7 @@ #include "ordered-set.h" #include "set.h" #include "time-util.h" +#include "varlink.h" struct Manager { sd_netlink *rtnl; @@ -25,9 +26,11 @@ struct Manager { sd_event *event; sd_resolve *resolve; sd_bus *bus; + VarlinkServer *varlink_server; sd_device_monitor *device_monitor; Hashmap *polkit_registry; int ethtool_fd; + int persistent_storage_fd; KeepConfiguration keep_configuration; IPv6PrivacyExtensions ipv6_privacy_extensions; @@ -38,6 +41,8 @@ struct Manager { bool restarting; bool manage_foreign_routes; bool manage_foreign_rules; + bool manage_foreign_nexthops; + bool dhcp_server_persist_leases; Set *dirty_links; Set *new_wlan_ifindices; @@ -59,6 +64,11 @@ struct Manager { OrderedSet *address_pools; Set *dhcp_pd_subnet_ids; + UseDomains use_domains; /* default for all protocols */ + UseDomains dhcp_use_domains; + UseDomains dhcp6_use_domains; + UseDomains ndisc_use_domains; + DUID dhcp_duid; DUID dhcp6_duid; DUID duid_product_uuid; @@ -72,9 +82,7 @@ struct Manager { /* Manage nexthops by id. */ Hashmap *nexthops_by_id; - - /* Manager stores nexthops without RTA_OIF attribute. */ - Set *nexthops; + Set *nexthop_ids; /* requested IDs in .network files */ /* Manager stores routes without RTA_OIF attribute. */ unsigned route_remove_messages; @@ -99,9 +107,16 @@ struct Manager { FirewallContext *fw_ctx; + bool request_queued; OrderedSet *request_queue; + OrderedSet *remove_request_queue; Hashmap *tuntap_fds_by_name; + + unsigned reloading; + + /* sysctl */ + int ip_forwarding[2]; }; int manager_new(Manager **ret, bool test_mode); @@ -122,6 +137,6 @@ int manager_enumerate(Manager *m); int manager_set_hostname(Manager *m, const char *hostname); int manager_set_timezone(Manager *m, const char *timezone); -int manager_reload(Manager *m); +int manager_reload(Manager *m, sd_bus_message *message); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index 840ccb1..7cafe1f 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -12,6 +12,7 @@ #include "event-util.h" #include "missing_network.h" +#include "ndisc-router-internal.h" #include "networkd-address-generation.h" #include "networkd-address.h" #include "networkd-dhcp6.h" @@ -20,6 +21,7 @@ #include "networkd-queue.h" #include "networkd-route.h" #include "networkd-state-file.h" +#include "networkd-sysctl.h" #include "string-table.h" #include "string-util.h" #include "strv.h" @@ -34,7 +36,9 @@ * Not sure if the threshold is high enough. Let's adjust later if not. */ #define NDISC_PREF64_MAX 64U -bool link_ipv6_accept_ra_enabled(Link *link) { +static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec); + +bool link_ndisc_enabled(Link *link) { assert(link); if (!socket_ipv6_is_supported()) @@ -52,24 +56,33 @@ bool link_ipv6_accept_ra_enabled(Link *link) { if (!link_may_have_ipv6ll(link, /* check_multicast = */ true)) return false; - assert(link->network->ipv6_accept_ra >= 0); - return link->network->ipv6_accept_ra; + /* Honor explicitly specified value. */ + if (link->network->ndisc >= 0) + return link->network->ndisc; + + /* Disable if RADV is enabled. */ + if (link_radv_enabled(link)) + return false; + + /* Accept RAs if IPv6 forwarding is disabled, and ignore RAs if IPv6 forwarding is enabled. */ + int t = link_get_ip_forwarding(link, AF_INET6); + if (t >= 0) + return !t; + + /* Otherwise, defaults to true. */ + return true; } -void network_adjust_ipv6_accept_ra(Network *network) { +void network_adjust_ndisc(Network *network) { assert(network); if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { - if (network->ipv6_accept_ra > 0) + if (network->ndisc > 0) log_warning("%s: IPv6AcceptRA= is enabled but IPv6 link-local addressing is disabled or not supported. " "Disabling IPv6AcceptRA=.", network->filename); - network->ipv6_accept_ra = false; + network->ndisc = false; } - if (network->ipv6_accept_ra < 0) - /* default to accept RA if ip_forward is disabled and ignore RA if ip_forward is enabled */ - network->ipv6_accept_ra = !FLAGS_SET(network->ip_forward, ADDRESS_FAMILY_IPV6); - /* When RouterAllowList=, PrefixAllowList= or RouteAllowList= are specified, then * RouterDenyList=, PrefixDenyList= or RouteDenyList= are ignored, respectively. */ if (!set_isempty(network->ndisc_allow_listed_router)) @@ -139,7 +152,7 @@ static int ndisc_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Could not set NDisc route"); + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set NDisc route"); if (r <= 0) return r; @@ -159,66 +172,84 @@ static void ndisc_set_route_priority(Link *link, Route *route) { switch (route->pref) { case SD_NDISC_PREFERENCE_LOW: - route->priority = link->network->ipv6_accept_ra_route_metric_low; + route->priority = link->network->ndisc_route_metric_low; break; case SD_NDISC_PREFERENCE_MEDIUM: - route->priority = link->network->ipv6_accept_ra_route_metric_medium; + route->priority = link->network->ndisc_route_metric_medium; break; case SD_NDISC_PREFERENCE_HIGH: - route->priority = link->network->ipv6_accept_ra_route_metric_high; + route->priority = link->network->ndisc_route_metric_high; break; default: assert_not_reached(); } } -static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = in; - struct in6_addr router; - uint8_t hop_limit = 0; - uint32_t mtu = 0; - bool is_new; +static int ndisc_request_route(Route *route, Link *link) { int r; assert(route); assert(link); + assert(link->manager); assert(link->network); - assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + route->source = NETWORK_CONFIG_SOURCE_NDISC; + + if (!route->table_set) + route->table = link_get_ndisc_route_table(link); + + r = route_metric_set(&route->metric, RTAX_QUICKACK, link->network->ndisc_quickack); if (r < 0) return r; - if (link->network->ipv6_accept_ra_use_mtu) { - r = sd_ndisc_router_get_mtu(rt, &mtu); - if (r < 0 && r != -ENODATA) - return log_link_warning_errno(link, r, "Failed to get default router MTU from RA: %m"); - } + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + uint8_t pref, pref_original = route->pref; + FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + Route *existing; + Request *req; + + /* If the preference is specified by the user config (that is, for semi-static routes), + * rather than RA, then only search conflicting routes that have the same preference. */ + if (route->pref_set && pref != pref_original) + continue; + + route->pref = pref; + ndisc_set_route_priority(link, route); - if (link->network->ipv6_accept_ra_use_hop_limit) { - r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); - if (r < 0 && r != -ENODATA) - return log_link_warning_errno(link, r, "Failed to get default router hop limit from RA: %m"); + /* Note, here do not call route_remove_and_cancel() with 'route' directly, otherwise + * existing route(s) may be removed needlessly. */ + + if (route_get(link->manager, route, &existing) >= 0) { + /* Found an existing route that may conflict with this route. */ + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found an existing route that conflicts with new route based on a received RA, removing."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } + + if (route_get_request(link->manager, route, &req) >= 0) { + existing = ASSERT_PTR(req->userdata); + if (!route_can_update(existing, route)) { + log_link_debug(link, "Found a pending route request that conflicts with new request based on a received RA, cancelling."); + r = route_remove_and_cancel(existing, link->manager); + if (r < 0) + return r; + } + } } - route->source = NETWORK_CONFIG_SOURCE_NDISC; - route->provider.in6 = router; - if (!route->table_set) - route->table = link_get_ipv6_accept_ra_route_table(link); + /* The preference (and priority) may be changed in the above loop. Restore it. */ + route->pref = pref_original; ndisc_set_route_priority(link, route); - if (!route->protocol_set) - route->protocol = RTPROT_RA; - if (route->quickack < 0) - route->quickack = link->network->ipv6_accept_ra_quickack; - if (route->mtu == 0) - route->mtu = mtu; - if (route->hop_limit == 0) - route->hop_limit = hop_limit; - is_new = route_get(NULL, link, route, NULL) < 0; + bool is_new = route_get(link->manager, route, NULL) < 0; - r = link_request_route(link, TAKE_PTR(route), true, &link->ndisc_messages, - ndisc_route_handler, NULL); + r = link_request_route(link, route, &link->ndisc_messages, ndisc_route_handler); if (r < 0) return r; if (r > 0 && is_new) @@ -227,6 +258,56 @@ static int ndisc_request_route(Route *in, Link *link, sd_ndisc_router *rt) { return 0; } +static int ndisc_request_router_route(Route *route, Link *link, sd_ndisc_router *rt) { + int r; + + assert(route); + assert(link); + assert(rt); + + r = sd_ndisc_router_get_sender_address(rt, &route->provider.in6); + if (r < 0) + return r; + + if (!route->protocol_set) + route->protocol = RTPROT_RA; + + return ndisc_request_route(route, link); +} + +static int ndisc_remove_route(Route *route, Link *link) { + int r; + + assert(route); + assert(link); + assert(link->manager); + + ndisc_set_route_priority(link, route); + + if (!route->table_set) + route->table = link_get_ndisc_route_table(link); + + r = route_adjust_nexthops(route, link); + if (r < 0) + return r; + + if (route->pref_set) { + ndisc_set_route_priority(link, route); + return route_remove_and_cancel(route, link->manager); + } + + uint8_t pref; + FOREACH_ARGUMENT(pref, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_MEDIUM, SD_NDISC_PREFERENCE_HIGH) { + route->pref = pref; + ndisc_set_route_priority(link, route); + r = route_remove_and_cancel(route, link->manager); + if (r < 0) + return r; + } + + return 0; +} + static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Address *address) { int r; @@ -243,165 +324,805 @@ static int ndisc_address_handler(sd_netlink *rtnl, sd_netlink_message *m, Reques return 1; } -static int ndisc_request_address(Address *in, Link *link, sd_ndisc_router *rt) { - _cleanup_(address_freep) Address *address = in; - struct in6_addr router; +static int ndisc_request_address(Address *address, Link *link) { bool is_new; int r; assert(address); assert(link); - assert(rt); - - r = sd_ndisc_router_get_address(rt, &router); - if (r < 0) - return r; address->source = NETWORK_CONFIG_SOURCE_NDISC; - address->provider.in6 = router; r = free_and_strdup_warn(&address->netlabel, link->network->ndisc_netlabel); if (r < 0) return r; - is_new = address_get(link, address, NULL) < 0; + Address *existing; + if (address_get_harder(link, address, &existing) < 0) + is_new = true; + else if (address_can_update(existing, address)) + is_new = false; + else if (existing->source == NETWORK_CONFIG_SOURCE_DHCP6) { + /* SLAAC address is preferred over DHCPv6 address. */ + log_link_debug(link, "Conflicting DHCPv6 address %s exists, removing.", + IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); + r = address_remove(existing, link); + if (r < 0) + return r; + + is_new = true; + } else { + /* Conflicting static address is configured?? */ + log_link_debug(link, "Conflicting address %s exists, ignoring request.", + IN_ADDR_PREFIX_TO_STRING(existing->family, &existing->in_addr, existing->prefixlen)); + return 0; + } r = link_request_address(link, address, &link->ndisc_messages, ndisc_address_handler, NULL); if (r < 0) - return r; - if (r > 0 && is_new) - link->ndisc_configured = false; + return r; + if (r > 0 && is_new) + link->ndisc_configured = false; + + return 0; +} + +int ndisc_reconfigure_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_NDISC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = ndisc_request_address(address, link); + if (r < 0) + return r; + + if (!link->ndisc_configured) + link_set_state(link, LINK_STATE_CONFIGURING); + + link_check_ready(link); + return 0; +} + +static int ndisc_redirect_route_new(sd_ndisc_redirect *rd, Route **ret) { + _cleanup_(route_unrefp) Route *route = NULL; + struct in6_addr gateway, destination; + int r; + + assert(rd); + assert(ret); + + r = sd_ndisc_redirect_get_target_address(rd, &gateway); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(rd, &destination); + if (r < 0) + return r; + + r = route_new(&route); + if (r < 0) + return r; + + route->family = AF_INET6; + if (!in6_addr_equal(&gateway, &destination)) { + route->nexthop.gw.in6 = gateway; + route->nexthop.family = AF_INET6; + } + route->dst.in6 = destination; + route->dst_prefixlen = 128; + route->protocol = RTPROT_REDIRECT; + + r = sd_ndisc_redirect_get_sender_address(rd, &route->provider.in6); + if (r < 0) + return r; + + *ret = TAKE_PTR(route); + return 0; +} + +static int ndisc_remove_redirect_route(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(route_unrefp) Route *route = NULL; + int r; + + assert(link); + assert(rd); + + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + return ndisc_remove_route(route, link); +} + +static void ndisc_redirect_hash_func(const sd_ndisc_redirect *x, struct siphash *state) { + struct in6_addr dest = {}; + + assert(x); + assert(state); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest); + + siphash24_compress_typesafe(dest, state); +} + +static int ndisc_redirect_compare_func(const sd_ndisc_redirect *x, const sd_ndisc_redirect *y) { + struct in6_addr dest_x = {}, dest_y = {}; + + assert(x); + assert(y); + + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) x, &dest_x); + (void) sd_ndisc_redirect_get_destination_address((sd_ndisc_redirect*) y, &dest_y); + + return memcmp(&dest_x, &dest_y, sizeof(dest_x)); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + ndisc_redirect_hash_ops, + sd_ndisc_redirect, + ndisc_redirect_hash_func, + ndisc_redirect_compare_func, + sd_ndisc_redirect_unref); + +static int ndisc_redirect_equal(sd_ndisc_redirect *x, sd_ndisc_redirect *y) { + struct in6_addr a, b; + int r; + + assert(x); + assert(y); + + r = sd_ndisc_redirect_get_destination_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_destination_address(y, &b); + if (r < 0) + return r; + + if (!in6_addr_equal(&a, &b)) + return false; + + r = sd_ndisc_redirect_get_target_address(x, &a); + if (r < 0) + return r; + + r = sd_ndisc_redirect_get_target_address(y, &b); + if (r < 0) + return r; + + return in6_addr_equal(&a, &b); +} + +static int ndisc_redirect_drop_conflict(Link *link, sd_ndisc_redirect *rd) { + _cleanup_(sd_ndisc_redirect_unrefp) sd_ndisc_redirect *existing = NULL; + int r; + + assert(link); + assert(rd); + + existing = set_remove(link->ndisc_redirects, rd); + if (!existing) + return 0; + + r = ndisc_redirect_equal(rd, existing); + if (r != 0) + return r; + + return ndisc_remove_redirect_route(link, existing); +} + +static int ndisc_redirect_verify_sender(Link *link, sd_ndisc_redirect *rd) { + int r; + + assert(link); + assert(rd); + + /* RFC 4861 section 8.1 + * The IP source address of the Redirect is the same as the current first-hop router for the specified + * ICMP Destination Address. */ + + struct in6_addr sender; + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + /* We will reuse the sender's router lifetime as the lifetime of the redirect route. Hence, if we + * have not remembered an RA from the sender, refuse the Redirect message. */ + sd_ndisc_router *router = hashmap_get(link->ndisc_routers_by_sender, &sender); + if (!router) + return false; + + sd_ndisc_redirect *existing = set_get(link->ndisc_redirects, rd); + if (existing) { + struct in6_addr target, dest; + + /* If we have received Redirect message for the host, the sender must be the previous target. */ + + r = sd_ndisc_redirect_get_target_address(existing, &target); + if (r < 0) + return r; + + if (in6_addr_equal(&sender, &target)) + return true; + + /* If the existing redirect route is on-link, that is, the destination and target address are + * equivalent, then also accept Redirect message from the current default router. This is not + * mentioned by the RFC, but without this, we cannot update on-link redirect route. */ + r = sd_ndisc_redirect_get_destination_address(existing, &dest); + if (r < 0) + return r; + + if (!in6_addr_equal(&dest, &target)) + return false; + } + + /* Check if the sender is one of the known router with highest priority. */ + uint8_t preference; + r = sd_ndisc_router_get_preference(router, &preference); + if (r < 0) + return r; + + if (preference == SD_NDISC_PREFERENCE_HIGH) + return true; + + sd_ndisc_router *rt; + HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) { + if (rt == router) + continue; + + uint8_t pref; + if (sd_ndisc_router_get_preference(rt, &pref) < 0) + continue; + + if (pref == SD_NDISC_PREFERENCE_HIGH || + (pref == SD_NDISC_PREFERENCE_MEDIUM && preference == SD_NDISC_PREFERENCE_LOW)) + return false; + } + + return true; +} + +static int ndisc_redirect_handler(Link *link, sd_ndisc_redirect *rd) { + int r; + + assert(link); + assert(link->network); + assert(rd); + + if (!link->network->ndisc_use_redirect) + return 0; + + usec_t now_usec; + r = sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec); + if (r < 0) + return r; + + r = ndisc_drop_outdated(link, /* router = */ NULL, now_usec); + if (r < 0) + return r; + + r = ndisc_redirect_verify_sender(link, rd); + if (r <= 0) + return r; + + /* First, drop conflicting redirect route, if exists. */ + r = ndisc_redirect_drop_conflict(link, rd); + if (r < 0) + return r; + + /* Then, remember the received message. */ + r = set_ensure_put(&link->ndisc_redirects, &ndisc_redirect_hash_ops, rd); + if (r < 0) + return r; + + sd_ndisc_redirect_ref(rd); + + /* Finally, request the corresponding route. */ + _cleanup_(route_unrefp) Route *route = NULL; + r = ndisc_redirect_route_new(rd, &route); + if (r < 0) + return r; + + sd_ndisc_router *rt = hashmap_get(link->ndisc_routers_by_sender, &route->provider.in6); + if (!rt) + return -EADDRNOTAVAIL; + + r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &route->lifetime_usec); + if (r < 0) + return r; + + return ndisc_request_route(route, link); +} + +static int ndisc_drop_redirect(Link *link, const struct in6_addr *router) { + int r, ret = 0; + + assert(link); + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + if (router) { + struct in6_addr a; + + if (!(sd_ndisc_redirect_get_sender_address(rd, &a) >= 0 && in6_addr_equal(&a, router)) && + !(sd_ndisc_redirect_get_target_address(rd, &a) >= 0 && in6_addr_equal(&a, router))) + continue; + } + + r = ndisc_remove_redirect_route(link, rd); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove redirect route, ignoring: %m")); + + sd_ndisc_redirect_unref(set_remove(link->ndisc_redirects, rd)); + } + + return ret; +} + +static int ndisc_update_redirect_sender(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) { + int r; + + assert(link); + assert(original_address); + assert(current_address); + + sd_ndisc_redirect *rd; + SET_FOREACH(rd, link->ndisc_redirects) { + struct in6_addr sender; + + r = sd_ndisc_redirect_get_sender_address(rd, &sender); + if (r < 0) + return r; + + if (!in6_addr_equal(&sender, original_address)) + continue; + + r = sd_ndisc_redirect_set_sender_address(rd, current_address); + if (r < 0) + return r; + } + + return 0; +} + +static int ndisc_router_drop_default(Link *link, sd_ndisc_router *rt) { + _cleanup_(route_unrefp) Route *route = NULL; + struct in6_addr gateway; + int r; + + assert(link); + assert(link->network); + assert(rt); + + r = sd_ndisc_router_get_sender_address(rt, &gateway); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6 = gateway; + + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove the default gateway configured by RA: %m"); + + Route *route_gw; + HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { + _cleanup_(route_unrefp) Route *tmp = NULL; + + if (!route_gw->gateway_from_dhcp_or_ra) + continue; + + if (route_gw->nexthop.family != AF_INET6) + continue; + + r = route_dup(route_gw, NULL, &tmp); + if (r < 0) + return r; + + tmp->nexthop.gw.in6 = gateway; + + r = ndisc_remove_route(tmp, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not remove semi-static gateway: %m"); + } + + return 0; +} + +static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { + usec_t lifetime_usec; + struct in6_addr gateway; + uint8_t preference; + int r; + + assert(link); + assert(link->network); + assert(rt); + + /* If the router lifetime is zero, the router should not be used as the default gateway. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r < 0) + return r; + if (r == 0) + return ndisc_router_drop_default(link, rt); + + if (!link->network->ndisc_use_gateway && + hashmap_isempty(link->network->routes_by_section)) + return 0; + + r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m"); + + r = sd_ndisc_router_get_sender_address(rt, &gateway); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); + + r = sd_ndisc_router_get_preference(rt, &preference); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); + + if (link->network->ndisc_use_gateway) { + _cleanup_(route_unrefp) Route *route = NULL; + + r = route_new(&route); + if (r < 0) + return log_oom(); + + route->family = AF_INET6; + route->pref = preference; + route->nexthop.family = AF_INET6; + route->nexthop.gw.in6 = gateway; + route->lifetime_usec = lifetime_usec; + + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Could not request default route: %m"); + } + + Route *route_gw; + HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { + _cleanup_(route_unrefp) Route *route = NULL; + + if (!route_gw->gateway_from_dhcp_or_ra) + continue; + + if (route_gw->nexthop.family != AF_INET6) + continue; + + r = route_dup(route_gw, NULL, &route); + if (r < 0) + return r; + + route->nexthop.gw.in6 = gateway; + if (!route->pref_set) + route->pref = preference; + route->lifetime_usec = lifetime_usec; + + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Could not request gateway: %m"); + } + + return 0; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + ndisc_router_hash_ops, + struct in6_addr, + in6_addr_hash_func, + in6_addr_compare_func, + sd_ndisc_router, + sd_ndisc_router_unref); + +static int ndisc_update_router_address(Link *link, const struct in6_addr *original_address, const struct in6_addr *current_address) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + int r; + + assert(link); + assert(original_address); + assert(current_address); + + rt = hashmap_remove(link->ndisc_routers_by_sender, original_address); + if (!rt) + return 0; + + /* If we already received an RA from the new address, then forget the RA from the old address. */ + if (hashmap_contains(link->ndisc_routers_by_sender, current_address)) + return 0; + + /* Otherwise, update the sender address of the previously received RA. */ + r = sd_ndisc_router_set_sender_address(rt, current_address); + if (r < 0) + return r; + + r = hashmap_put(link->ndisc_routers_by_sender, &rt->packet->sender_address, rt); + if (r < 0) + return r; + + TAKE_PTR(rt); + return 0; +} + +static int ndisc_drop_router_one(Link *link, sd_ndisc_router *rt, usec_t timestamp_usec) { + usec_t lifetime_usec; + int r; + + assert(link); + assert(rt); + assert(rt->packet); + + r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); + if (r < 0) + return r; + + if (lifetime_usec > timestamp_usec) + return 0; + + r = ndisc_drop_redirect(link, &rt->packet->sender_address); + + sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address)); + + return r; +} + +static int ndisc_drop_routers(Link *link, const struct in6_addr *router, usec_t timestamp_usec) { + sd_ndisc_router *rt; + int ret = 0; + + assert(link); + + if (router) { + rt = hashmap_get(link->ndisc_routers_by_sender, router); + if (!rt) + return 0; + + return ndisc_drop_router_one(link, rt, timestamp_usec); + } + + HASHMAP_FOREACH_KEY(rt, router, link->ndisc_routers_by_sender) + RET_GATHER(ret, ndisc_drop_router_one(link, rt, timestamp_usec)); + + return ret; +} + +static int ndisc_remember_router(Link *link, sd_ndisc_router *rt) { + int r; + + assert(link); + assert(rt); + assert(rt->packet); + + sd_ndisc_router_unref(hashmap_remove(link->ndisc_routers_by_sender, &rt->packet->sender_address)); + + /* Remember RAs with non-zero lifetime. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r <= 0) + return r; + + r = hashmap_ensure_put(&link->ndisc_routers_by_sender, &ndisc_router_hash_ops, &rt->packet->sender_address, rt); + if (r < 0) + return r; + + sd_ndisc_router_ref(rt); + return 0; +} + +static int ndisc_router_process_reachable_time(Link *link, sd_ndisc_router *rt) { + usec_t reachable_time, msec; + int r; + + assert(link); + assert(link->network); + assert(rt); + + if (!link->network->ndisc_use_reachable_time) + return 0; + + r = sd_ndisc_router_get_reachable_time(rt, &reachable_time); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get reachable time from RA: %m"); + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ + if (!timestamp_is_set(reachable_time)) + return 0; + + msec = DIV_ROUND_UP(reachable_time, USEC_PER_MSEC); + if (msec <= 0 || msec > UINT32_MAX) { + log_link_debug(link, "Failed to get reachable time from RA - out of range (%"PRIu64"), ignoring", msec); + return 0; + } + + /* Set the reachable time for Neighbor Solicitations. */ + r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "base_reachable_time_ms", (uint32_t) msec); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply neighbor reachable time (%"PRIu64"), ignoring: %m", msec); return 0; } -static int ndisc_router_process_default(Link *link, sd_ndisc_router *rt) { - usec_t lifetime_usec; - struct in6_addr gateway; - unsigned preference; +static int ndisc_router_process_retransmission_time(Link *link, sd_ndisc_router *rt) { + usec_t retrans_time, msec; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_gateway && - hashmap_isempty(link->network->routes_by_section)) + if (!link->network->ndisc_use_retransmission_time) return 0; - r = sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); + r = sd_ndisc_router_get_retransmission_time(rt, &retrans_time); if (r < 0) - return log_link_warning_errno(link, r, "Failed to get gateway lifetime from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get retransmission time from RA: %m"); - r = sd_ndisc_router_get_address(rt, &gateway); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4) */ + if (!timestamp_is_set(retrans_time)) + return 0; - if (link_get_ipv6_address(link, &gateway, 0, NULL) >= 0) { - if (DEBUG_LOGGING) - log_link_debug(link, "No NDisc route added, gateway %s matches local address", - IN6_ADDR_TO_STRING(&gateway)); + msec = DIV_ROUND_UP(retrans_time, USEC_PER_MSEC); + if (msec <= 0 || msec > UINT32_MAX) { + log_link_debug(link, "Failed to get retransmission time from RA - out of range (%"PRIu64"), ignoring", msec); return 0; } - r = sd_ndisc_router_get_preference(rt, &preference); + /* Set the retransmission time for Neighbor Solicitations. */ + r = sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", (uint32_t) msec); if (r < 0) - return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + log_link_warning_errno(link, r, "Failed to apply neighbor retransmission time (%"PRIu64"), ignoring: %m", msec); - if (link->network->ipv6_accept_ra_use_gateway) { - _cleanup_(route_freep) Route *route = NULL; + return 0; +} - r = route_new(&route); - if (r < 0) - return log_oom(); +static int ndisc_router_process_hop_limit(Link *link, sd_ndisc_router *rt) { + uint8_t hop_limit; + int r; - route->family = AF_INET6; - route->pref = preference; - route->gw_family = AF_INET6; - route->gw.in6 = gateway; - route->lifetime_usec = lifetime_usec; + assert(link); + assert(link->network); + assert(rt); - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request default route: %m"); - } + if (!link->network->ndisc_use_hop_limit) + return 0; - Route *route_gw; - HASHMAP_FOREACH(route_gw, link->network->routes_by_section) { - _cleanup_(route_freep) Route *route = NULL; + r = sd_ndisc_router_get_hop_limit(rt, &hop_limit); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get hop limit from RA: %m"); + + /* 0 is the unspecified value and must not be set (see RFC4861, 6.3.4): + * + * A Router Advertisement field (e.g., Cur Hop Limit, Reachable Time, and Retrans Timer) may contain + * a value denoting that it is unspecified. In such cases, the parameter should be ignored and the + * host should continue using whatever value it is already using. In particular, a host MUST NOT + * interpret the unspecified value as meaning change back to the default value that was in use before + * the first Router Advertisement was received. + * + * If the received Cur Hop Limit value is non-zero, the host SHOULD set + * its CurHopLimit variable to the received value.*/ + if (hop_limit <= 0) + return 0; - if (!route_gw->gateway_from_dhcp_or_ra) - continue; + r = sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "hop_limit", (uint32_t) hop_limit); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply hop_limit (%u), ignoring: %m", hop_limit); - if (route_gw->gw_family != AF_INET6) - continue; + return 0; +} - r = route_dup(route_gw, &route); - if (r < 0) - return r; +static int ndisc_router_process_mtu(Link *link, sd_ndisc_router *rt) { + uint32_t mtu; + int r; - route->gw.in6 = gateway; - if (!route->pref_set) - route->pref = preference; - route->lifetime_usec = lifetime_usec; + assert(link); + assert(link->network); + assert(rt); - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request gateway: %m"); - } + if (!link->network->ndisc_use_mtu) + return 0; + + r = sd_ndisc_router_get_mtu(rt, &mtu); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get MTU from RA: %m"); + + link->ndisc_mtu = mtu; + + r = link_set_ipv6_mtu(link, LOG_DEBUG); + if (r < 0) + log_link_warning_errno(link, r, "Failed to apply IPv6 MTU (%"PRIu32"), ignoring: %m", mtu); return 0; } -static int ndisc_router_process_icmp6_ratelimit(Link *link, sd_ndisc_router *rt) { - usec_t icmp6_ratelimit, msec; +static int ndisc_address_set_lifetime(Address *address, Link *link, sd_ndisc_router *rt) { + Address *existing; + usec_t t; int r; + assert(address); assert(link); - assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_icmp6_ratelimit) - return 0; + /* This is mostly based on RFC 4862 section 5.5.3 (e). However, the definition of 'RemainingLifetime' + * is ambiguous, and there is no clear explanation when the address is not assigned yet. If we assume + * that 'RemainingLifetime' is zero in that case, then IPv6 Core Conformance test [v6LC.3.2.5 Part C] + * fails. So, in such case, we skip the conditions about 'RemainingLifetime'. */ - r = sd_ndisc_router_get_icmp6_ratelimit(rt, &icmp6_ratelimit); - if (r < 0) { - log_link_debug(link, "Failed to get ICMP6 ratelimit from RA, ignoring: %m"); + r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_valid_usec); + if (r < 0) + return r; + + r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &address->lifetime_preferred_usec); + if (r < 0) + return r; + + /* RFC 4862 section 5.5.3 (e) + * 1. If the received Valid Lifetime is greater than 2 hours or greater than RemainingLifetime, + * set the valid lifetime of the corresponding address to the advertised Valid Lifetime. */ + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &t); + if (r < 0) + return r; + + if (t > 2 * USEC_PER_HOUR) return 0; - } - /* We do not allow 0 here. */ - if (!timestamp_is_set(icmp6_ratelimit)) + if (address_get(link, address, &existing) < 0 || existing->source != NETWORK_CONFIG_SOURCE_NDISC) return 0; - msec = DIV_ROUND_UP(icmp6_ratelimit, USEC_PER_MSEC); - if (msec <= 0 || msec > INT_MAX) + if (address->lifetime_valid_usec > existing->lifetime_valid_usec) return 0; - /* Limit the maximal rates for sending ICMPv6 packets. 0 to disable any limiting, otherwise the - * minimal space between responses in milliseconds. Default: 1000. */ - r = sysctl_write_ip_property_int(AF_INET6, NULL, "icmp/ratelimit", (int) msec); + /* 2. If RemainingLifetime is less than or equal to 2 hours, ignore the Prefix Information option + * with regards to the valid lifetime, unless the Router Advertisement from which this option was + * obtained has been authenticated (e.g., via Secure Neighbor Discovery [RFC3971]). If the Router + * Advertisement was authenticated, the valid lifetime of the corresponding address should be set + * to the Valid Lifetime in the received option. + * + * Currently, authentication is not supported. So check the lifetime of the existing address. */ + r = sd_ndisc_router_get_timestamp(rt, CLOCK_BOOTTIME, &t); if (r < 0) - log_link_warning_errno(link, r, "Failed to apply ICMP6 ratelimit, ignoring: %m"); + return r; + + if (existing->lifetime_valid_usec <= usec_add(t, 2 * USEC_PER_HOUR)) { + address->lifetime_valid_usec = existing->lifetime_valid_usec; + return 0; + } + /* 3. Otherwise, reset the valid lifetime of the corresponding address to 2 hours. */ + address->lifetime_valid_usec = usec_add(t, 2 * USEC_PER_HOUR); return 0; } static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *rt) { usec_t lifetime_valid_usec, lifetime_preferred_usec; - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr prefix, *a; - unsigned prefixlen; + struct in6_addr prefix, router; + uint8_t prefixlen; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_autonomous_prefix) + if (!link->network->ndisc_use_autonomous_prefix) return 0; + r = sd_ndisc_router_get_sender_address(rt, &router); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to get router address: %m"); + r = sd_ndisc_router_prefix_get_address(rt, &prefix); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix address: %m"); @@ -417,37 +1138,48 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r return 0; } - r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_valid_usec); + r = sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid_usec); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix valid lifetime: %m"); - r = sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_preferred_usec); + r = sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred_usec); if (r < 0) return log_link_warning_errno(link, r, "Failed to get prefix preferred lifetime: %m"); - /* The preferred lifetime is never greater than the valid lifetime */ + /* RFC 4862 section 5.5.3 (c) + * If the preferred lifetime is greater than the valid lifetime, silently ignore the Prefix + * Information option. */ if (lifetime_preferred_usec > lifetime_valid_usec) return 0; - r = ndisc_generate_addresses(link, &prefix, prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = ndisc_generate_addresses(link, &prefix, prefixlen, &tokens_by_address); if (r < 0) return log_link_warning_errno(link, r, "Failed to generate SLAAC addresses: %m"); - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) return log_oom(); + address->provider.in6 = router; address->family = AF_INET6; address->in_addr.in6 = *a; address->prefixlen = prefixlen; address->flags = IFA_F_NOPREFIXROUTE|IFA_F_MANAGETEMPADDR; - address->lifetime_valid_usec = lifetime_valid_usec; - address->lifetime_preferred_usec = lifetime_preferred_usec; + address->token = ipv6_token_ref(token); - r = ndisc_request_address(TAKE_PTR(address), link, rt); + r = ndisc_address_set_lifetime(address, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to set lifetime of SLAAC address: %m"); + + assert(address->lifetime_preferred_usec <= address->lifetime_valid_usec); + + r = ndisc_request_address(address, link); if (r < 0) return log_link_warning_errno(link, r, "Could not request SLAAC address: %m"); } @@ -456,8 +1188,8 @@ static int ndisc_router_process_autonomous_prefix(Link *link, sd_ndisc_router *r } static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = NULL; - unsigned prefixlen, preference; + _cleanup_(route_unrefp) Route *route = NULL; + uint8_t prefixlen, preference; usec_t lifetime_usec; struct in6_addr prefix; int r; @@ -466,7 +1198,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_onlink_prefix) + if (!link->network->ndisc_use_onlink_prefix) return 0; r = sd_ndisc_router_prefix_get_valid_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); @@ -484,7 +1216,7 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { /* Prefix Information option does not have preference, hence we use the 'main' preference here */ r = sd_ndisc_router_get_preference(rt, &preference); if (r < 0) - log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); r = route_new(&route); if (r < 0) @@ -496,17 +1228,30 @@ static int ndisc_router_process_onlink_prefix(Link *link, sd_ndisc_router *rt) { route->pref = preference; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request prefix route: %m"); + /* RFC 4861 section 6.3.4: + * - If the prefix is not already present in the Prefix List, and the Prefix Information option's + * Valid Lifetime field is non-zero, create a new entry for the prefix and initialize its + * invalidation timer to the Valid Lifetime value in the Prefix Information option. + * + * - If the prefix is already present in the host's Prefix List as the result of a previously + * received advertisement, reset its invalidation timer to the Valid Lifetime value in the Prefix + * Information option. If the new Lifetime value is zero, time-out the prefix immediately. */ + if (lifetime_usec == 0) { + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to remove prefix route: %m"); + } else { + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to request prefix route: %m"); + } return 0; } static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { - unsigned prefixlen; + uint8_t flags, prefixlen; struct in6_addr a; - uint8_t flags; int r; assert(link); @@ -521,7 +1266,7 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { * A router SHOULD NOT send a prefix option for the link-local prefix and a host SHOULD ignore such * a prefix option. */ if (in6_addr_is_link_local(&a)) { - log_link_debug(link, "Received link-local prefix, ignoring autonomous prefix."); + log_link_debug(link, "Received link-local prefix, ignoring prefix."); return 0; } @@ -558,15 +1303,15 @@ static int ndisc_router_process_prefix(Link *link, sd_ndisc_router *rt) { } static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { - _cleanup_(route_freep) Route *route = NULL; - unsigned preference, prefixlen; + _cleanup_(route_unrefp) Route *route = NULL; + uint8_t preference, prefixlen; struct in6_addr gateway, dst; usec_t lifetime_usec; int r; assert(link); - if (!link->network->ipv6_accept_ra_use_route_prefix) + if (!link->network->ndisc_use_route_prefix) return 0; r = sd_ndisc_router_route_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &lifetime_usec); @@ -581,11 +1326,6 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { if (r < 0) return log_link_warning_errno(link, r, "Failed to get route prefix length: %m"); - if (in6_addr_is_null(&dst) && prefixlen == 0) { - log_link_debug(link, "Route prefix is ::/0, ignoring"); - return 0; - } - if (in6_prefix_is_filtered(&dst, prefixlen, link->network->ndisc_allow_listed_route_prefix, link->network->ndisc_deny_listed_route_prefix)) { @@ -598,7 +1338,7 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { return 0; } - r = sd_ndisc_router_get_address(rt, &gateway); + r = sd_ndisc_router_get_sender_address(rt, &gateway); if (r < 0) return log_link_warning_errno(link, r, "Failed to get gateway address from RA: %m"); @@ -610,12 +1350,12 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { } r = sd_ndisc_router_route_get_preference(rt, &preference); - if (r == -ENOTSUP) { + if (r == -EOPNOTSUPP) { log_link_debug_errno(link, r, "Received route prefix with unsupported preference, ignoring: %m"); return 0; } if (r < 0) - return log_link_warning_errno(link, r, "Failed to get default router preference from RA: %m"); + return log_link_warning_errno(link, r, "Failed to get router preference from RA: %m"); r = route_new(&route); if (r < 0) @@ -623,21 +1363,27 @@ static int ndisc_router_process_route(Link *link, sd_ndisc_router *rt) { route->family = AF_INET6; route->pref = preference; - route->gw.in6 = gateway; - route->gw_family = AF_INET6; + route->nexthop.gw.in6 = gateway; + route->nexthop.family = AF_INET6; route->dst.in6 = dst; route->dst_prefixlen = prefixlen; route->lifetime_usec = lifetime_usec; - r = ndisc_request_route(TAKE_PTR(route), link, rt); - if (r < 0) - return log_link_warning_errno(link, r, "Could not request additional route: %m"); + if (lifetime_usec != 0) { + r = ndisc_request_router_route(route, link, rt); + if (r < 0) + return log_link_warning_errno(link, r, "Could not request additional route: %m"); + } else { + r = ndisc_remove_route(route, link); + if (r < 0) + return log_link_warning_errno(link, r, "Could not remove additional route with zero lifetime: %m"); + } return 0; } static void ndisc_rdnss_hash_func(const NDiscRDNSS *x, struct siphash *state) { - siphash24_compress(&x->address, sizeof(x->address), state); + siphash24_compress_typesafe(x->address, state); } static int ndisc_rdnss_compare_func(const NDiscRDNSS *a, const NDiscRDNSS *b) { @@ -662,10 +1408,10 @@ static int ndisc_router_process_rdnss(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_dns) + if (!link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -744,7 +1490,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( free); static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { - _cleanup_strv_free_ char **l = NULL; + char **l; usec_t lifetime_usec; struct in6_addr router; bool updated = false, logged_about_too_many = false; @@ -754,10 +1500,10 @@ static int ndisc_router_process_dnssl(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (link->network->ipv6_accept_ra_use_domains == DHCP_USE_DOMAINS_NO) + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) <= 0) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -848,21 +1594,20 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) { _cleanup_(ndisc_captive_portal_freep) NDiscCaptivePortal *new_entry = NULL; _cleanup_free_ char *captive_portal = NULL; + const char *uri; usec_t lifetime_usec; NDiscCaptivePortal *exist; struct in6_addr router; - const char *uri; - size_t len; int r; assert(link); assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_captive_portal) + if (!link->network->ndisc_use_captive_portal) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -873,31 +1618,25 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) if (r < 0) return log_link_warning_errno(link, r, "Failed to get lifetime of RA message: %m"); - r = sd_ndisc_router_captive_portal_get_uri(rt, &uri, &len); + r = sd_ndisc_router_get_captive_portal(rt, &uri); if (r < 0) return log_link_warning_errno(link, r, "Failed to get captive portal from RA: %m"); - if (len == 0) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received empty captive portal, ignoring."); - - r = make_cstring(uri, len, MAKE_CSTRING_REFUSE_TRAILING_NUL, &captive_portal); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to convert captive portal URI: %m"); - - if (!in_charset(captive_portal, URI_VALID)) - return log_link_warning_errno(link, SYNTHETIC_ERRNO(EBADMSG), "Received invalid captive portal, ignoring."); + captive_portal = strdup(uri); + if (!captive_portal) + return log_oom(); if (lifetime_usec == 0) { /* Drop the portal with zero lifetime. */ ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, })); return 0; } exist = set_get(link->ndisc_captive_portals, - &(NDiscCaptivePortal) { + &(const NDiscCaptivePortal) { .captive_portal = captive_portal, }); if (exist) { @@ -943,8 +1682,8 @@ static int ndisc_router_process_captive_portal(Link *link, sd_ndisc_router *rt) static void ndisc_pref64_hash_func(const NDiscPREF64 *x, struct siphash *state) { assert(x); - siphash24_compress(&x->prefix_len, sizeof(x->prefix_len), state); - siphash24_compress(&x->prefix, sizeof(x->prefix), state); + siphash24_compress_typesafe(x->prefix_len, state); + siphash24_compress_typesafe(x->prefix, state); } static int ndisc_pref64_compare_func(const NDiscPREF64 *a, const NDiscPREF64 *b) { @@ -971,7 +1710,7 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { _cleanup_free_ NDiscPREF64 *new_entry = NULL; usec_t lifetime_usec; struct in6_addr a, router; - unsigned prefix_len; + uint8_t prefix_len; NDiscPREF64 *exist; int r; @@ -979,10 +1718,10 @@ static int ndisc_router_process_pref64(Link *link, sd_ndisc_router *rt) { assert(link->network); assert(rt); - if (!link->network->ipv6_accept_ra_use_pref64) + if (!link->network->ndisc_use_pref64) return 0; - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r < 0) return log_link_warning_errno(link, r, "Failed to get router address from RA: %m"); @@ -1102,7 +1841,7 @@ static int ndisc_router_process_options(Link *link, sd_ndisc_router *rt) { } } -static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { +static int ndisc_drop_outdated(Link *link, const struct in6_addr *router, usec_t timestamp_usec) { bool updated = false; NDiscDNSSL *dnssl; NDiscRDNSS *rdnss; @@ -1110,9 +1849,10 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { NDiscPREF64 *p64; Address *address; Route *route; - int r = 0, k; + int r, ret = 0; assert(link); + assert(link->manager); /* If an address or friends is already assigned, but not valid anymore, then refuse to update it, * and let's immediately remove it. @@ -1120,58 +1860,86 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { * valid lifetimes to improve the reaction of SLAAC to renumbering events. * See draft-ietf-6man-slaac-renum-02, section 4.2. */ - SET_FOREACH(route, link->routes) { + r = ndisc_drop_routers(link, router, timestamp_usec); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to drop outdated default router, ignoring: %m")); + + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) continue; - if (route->lifetime_usec >= timestamp_usec) + if (route->nexthop.ifindex != link->ifindex) + continue; + + if (route->protocol == RTPROT_REDIRECT) + continue; /* redirect route will be dropped by ndisc_drop_redirect(). */ + + if (route->lifetime_usec > timestamp_usec) continue; /* the route is still valid */ - k = route_remove_and_drop(route); - if (k < 0) - r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC route, ignoring: %m"); + if (router && !in6_addr_equal(&route->provider.in6, router)) + continue; + + r = route_remove_and_cancel(route, link->manager); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC route, ignoring: %m")); } SET_FOREACH(address, link->addresses) { if (address->source != NETWORK_CONFIG_SOURCE_NDISC) continue; - if (address->lifetime_valid_usec >= timestamp_usec) + if (address->lifetime_valid_usec > timestamp_usec) continue; /* the address is still valid */ - k = address_remove_and_drop(address); - if (k < 0) - r = log_link_warning_errno(link, k, "Failed to remove outdated SLAAC address, ignoring: %m"); + if (router && !in6_addr_equal(&address->provider.in6, router)) + continue; + + r = address_remove_and_cancel(address, link); + if (r < 0) + RET_GATHER(ret, log_link_warning_errno(link, r, "Failed to remove outdated SLAAC address, ignoring: %m")); } SET_FOREACH(rdnss, link->ndisc_rdnss) { - if (rdnss->lifetime_usec >= timestamp_usec) + if (rdnss->lifetime_usec > timestamp_usec) continue; /* the DNS server is still valid */ + if (router && !in6_addr_equal(&rdnss->router, router)) + continue; + free(set_remove(link->ndisc_rdnss, rdnss)); updated = true; } SET_FOREACH(dnssl, link->ndisc_dnssl) { - if (dnssl->lifetime_usec >= timestamp_usec) + if (dnssl->lifetime_usec > timestamp_usec) continue; /* the DNS domain is still valid */ + if (router && !in6_addr_equal(&dnssl->router, router)) + continue; + free(set_remove(link->ndisc_dnssl, dnssl)); updated = true; } SET_FOREACH(cp, link->ndisc_captive_portals) { - if (cp->lifetime_usec >= timestamp_usec) + if (cp->lifetime_usec > timestamp_usec) continue; /* the captive portal is still valid */ + if (router && !in6_addr_equal(&cp->router, router)) + continue; + ndisc_captive_portal_free(set_remove(link->ndisc_captive_portals, cp)); updated = true; } SET_FOREACH(p64, link->ndisc_pref64) { - if (p64->lifetime_usec >= timestamp_usec) + if (p64->lifetime_usec > timestamp_usec) continue; /* the pref64 prefix is still valid */ + if (router && !in6_addr_equal(&p64->router, router)) + continue; + free(set_remove(link->ndisc_pref64, p64)); /* The pref64 prefix is not exported through the state file, hence it is not necessary to set * the 'updated' flag. */ @@ -1180,7 +1948,7 @@ static int ndisc_drop_outdated(Link *link, usec_t timestamp_usec) { if (updated) link_dirty(link); - return r; + return ret; } static int ndisc_setup_expire(Link *link); @@ -1193,7 +1961,7 @@ static int ndisc_expire_handler(sd_event_source *s, uint64_t usec, void *userdat assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); - (void) ndisc_drop_outdated(link, now_usec); + (void) ndisc_drop_outdated(link, /* router = */ NULL, now_usec); (void) ndisc_setup_expire(link); return 0; } @@ -1211,10 +1979,23 @@ static int ndisc_setup_expire(Link *link) { assert(link); assert(link->manager); - SET_FOREACH(route, link->routes) { + sd_ndisc_router *rt; + HASHMAP_FOREACH(rt, link->ndisc_routers_by_sender) { + usec_t t; + + if (sd_ndisc_router_get_lifetime_timestamp(rt, CLOCK_BOOTTIME, &t) < 0) + continue; + + lifetime_usec = MIN(lifetime_usec, t); + } + + SET_FOREACH(route, link->manager->routes) { if (route->source != NETWORK_CONFIG_SOURCE_NDISC) continue; + if (route->nexthop.ifindex != link->ifindex) + continue; + if (!route_exists(route)) continue; @@ -1260,7 +2041,13 @@ static int ndisc_start_dhcp6_client(Link *link, sd_ndisc_router *rt) { assert(link); assert(link->network); - switch (link->network->ipv6_accept_ra_start_dhcp6_client) { + /* Do not start DHCPv6 client if the router lifetime is zero, as the message sent as a signal of + * that the router is e.g. shutting down, revoked, etc,. */ + r = sd_ndisc_router_get_lifetime(rt, NULL); + if (r <= 0) + return r; + + switch (link->network->ndisc_start_dhcp6_client) { case IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO: return 0; @@ -1307,7 +2094,7 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { assert(link->manager); assert(rt); - r = sd_ndisc_router_get_address(rt, &router); + r = sd_ndisc_router_get_sender_address(rt, &router); if (r == -ENODATA) { log_link_debug(link, "Received RA without router address, ignoring."); return 0; @@ -1333,7 +2120,11 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; - r = ndisc_drop_outdated(link, timestamp_usec); + r = ndisc_drop_outdated(link, /* router = */ NULL, timestamp_usec); + if (r < 0) + return r; + + r = ndisc_remember_router(link, rt); if (r < 0) return r; @@ -1345,7 +2136,19 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; - r = ndisc_router_process_icmp6_ratelimit(link, rt); + r = ndisc_router_process_reachable_time(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_retransmission_time(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_hop_limit(link, rt); + if (r < 0) + return r; + + r = ndisc_router_process_mtu(link, rt); if (r < 0) return r; @@ -1357,6 +2160,9 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { if (r < 0) return r; + if (sd_ndisc_router_get_lifetime(rt, NULL) <= 0) + (void) ndisc_drop_redirect(link, &router); + if (link->ndisc_messages == 0) link->ndisc_configured = true; else @@ -1369,7 +2175,142 @@ static int ndisc_router_handler(Link *link, sd_ndisc_router *rt) { return 0; } -static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata) { +static int ndisc_neighbor_handle_non_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr address; + int r; + + assert(link); + assert(na); + + /* Received Neighbor Advertisement message without Router flag. The node might have been a router, + * and now it is not. Let's drop all configurations based on RAs sent from the node. */ + + r = sd_ndisc_neighbor_get_target_address(na, &address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + (void) ndisc_drop_outdated(link, /* router = */ &address, /* timestamp_usec = */ USEC_INFINITY); + (void) ndisc_drop_redirect(link, &address); + + return 0; +} + +static int ndisc_neighbor_handle_router_message(Link *link, sd_ndisc_neighbor *na) { + struct in6_addr current_address, original_address; + int r; + + assert(link); + assert(link->manager); + assert(na); + + /* Received Neighbor Advertisement message with Router flag. If the router address is changed, update + * the provider field of configurations. */ + + r = sd_ndisc_neighbor_get_sender_address(na, ¤t_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + r = sd_ndisc_neighbor_get_target_address(na, &original_address); + if (r == -ENODATA) + return 0; + if (r < 0) + return r; + + if (in6_addr_equal(¤t_address, &original_address)) + return 0; /* the router address is not changed */ + + r = ndisc_update_router_address(link, &original_address, ¤t_address); + if (r < 0) + return r; + + r = ndisc_update_redirect_sender(link, &original_address, ¤t_address); + if (r < 0) + return r; + + Route *route; + SET_FOREACH(route, link->manager->routes) { + if (route->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (route->nexthop.ifindex != link->ifindex) + continue; + + if (!in6_addr_equal(&route->provider.in6, &original_address)) + continue; + + route->provider.in6 = current_address; + } + + Address *address; + SET_FOREACH(address, link->addresses) { + if (address->source != NETWORK_CONFIG_SOURCE_NDISC) + continue; + + if (!in6_addr_equal(&address->provider.in6, &original_address)) + continue; + + address->provider.in6 = current_address; + } + + NDiscRDNSS *rdnss; + SET_FOREACH(rdnss, link->ndisc_rdnss) { + if (!in6_addr_equal(&rdnss->router, &original_address)) + continue; + + rdnss->router = current_address; + } + + NDiscDNSSL *dnssl; + SET_FOREACH(dnssl, link->ndisc_dnssl) { + if (!in6_addr_equal(&dnssl->router, &original_address)) + continue; + + dnssl->router = current_address; + } + + NDiscCaptivePortal *cp; + SET_FOREACH(cp, link->ndisc_captive_portals) { + if (!in6_addr_equal(&cp->router, &original_address)) + continue; + + cp->router = current_address; + } + + NDiscPREF64 *p64; + SET_FOREACH(p64, link->ndisc_pref64) { + if (!in6_addr_equal(&p64->router, &original_address)) + continue; + + p64->router = current_address; + } + + return 0; +} + +static int ndisc_neighbor_handler(Link *link, sd_ndisc_neighbor *na) { + int r; + + assert(link); + assert(na); + + r = sd_ndisc_neighbor_is_router(na); + if (r < 0) + return r; + if (r == 0) + r = ndisc_neighbor_handle_non_router_message(link, na); + else + r = ndisc_neighbor_handle_router_message(link, na); + if (r < 0) + return r; + + return 0; +} + +static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata) { Link *link = ASSERT_PTR(userdata); int r; @@ -1379,8 +2320,25 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router switch (event) { case SD_NDISC_EVENT_ROUTER: - r = ndisc_router_handler(link, rt); + r = ndisc_router_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + link_enter_failed(link); + return; + } + break; + + case SD_NDISC_EVENT_NEIGHBOR: + r = ndisc_neighbor_handler(link, ASSERT_PTR(message)); + if (r < 0 && r != -EBADMSG) { + link_enter_failed(link); + return; + } + break; + + case SD_NDISC_EVENT_REDIRECT: + r = ndisc_redirect_handler(link, ASSERT_PTR(message)); if (r < 0 && r != -EBADMSG) { + log_link_warning_errno(link, r, "Failed to process Redirect message: %m"); link_enter_failed(link); return; } @@ -1393,8 +2351,9 @@ static void ndisc_handler(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router link_check_ready(link); } break; + default: - assert_not_reached(); + log_link_debug(link, "Received unsupported NDisc event, ignoring."); } } @@ -1403,7 +2362,7 @@ static int ndisc_configure(Link *link) { assert(link); - if (!link_ipv6_accept_ra_enabled(link)) + if (!link_ndisc_enabled(link)) return 0; if (link->ndisc) @@ -1448,6 +2407,10 @@ int ndisc_start(Link *link) { if (in6_addr_is_null(&link->ipv6ll_address)) return 0; + r = sd_ndisc_set_link_local_address(link->ndisc, &link->ipv6ll_address); + if (r < 0) + return r; + log_link_debug(link, "Discovering IPv6 routers"); r = sd_ndisc_start(link->ndisc); @@ -1483,7 +2446,7 @@ int link_request_ndisc(Link *link) { assert(link); - if (!link_ipv6_accept_ra_enabled(link)) + if (!link_ndisc_enabled(link)) return 0; if (link->ndisc) @@ -1505,27 +2468,29 @@ int ndisc_stop(Link *link) { return sd_ndisc_stop(link->ndisc); } - void ndisc_flush(Link *link) { assert(link); - /* Remove all RDNSS, DNSSL, and Captive Portal entries, without exception. */ + /* Remove all addresses, routes, RDNSS, DNSSL, and Captive Portal entries, without exception. */ + (void) ndisc_drop_outdated(link, /* router = */ NULL, /* timestamp_usec = */ USEC_INFINITY); + (void) ndisc_drop_redirect(link, /* router = */ NULL); + link->ndisc_routers_by_sender = hashmap_free(link->ndisc_routers_by_sender); link->ndisc_rdnss = set_free(link->ndisc_rdnss); link->ndisc_dnssl = set_free(link->ndisc_dnssl); link->ndisc_captive_portals = set_free(link->ndisc_captive_portals); link->ndisc_pref64 = set_free(link->ndisc_pref64); + link->ndisc_redirects = set_free(link->ndisc_redirects); + link->ndisc_mtu = 0; } -static const char* const ipv6_accept_ra_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { +static const char* const ndisc_start_dhcp6_client_table[_IPV6_ACCEPT_RA_START_DHCP6_CLIENT_MAX] = { [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_NO] = "no", [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_ALWAYS] = "always", [IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES] = "yes", }; -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES); -DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_use_domains, dhcp_use_domains, DHCPUseDomains, - "Failed to parse UseDomains= setting"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_ipv6_accept_ra_start_dhcp6_client, ipv6_accept_ra_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, +DEFINE_CONFIG_PARSE_ENUM(config_parse_ndisc_start_dhcp6_client, ndisc_start_dhcp6_client, IPv6AcceptRAStartDHCP6Client, "Failed to parse DHCPv6Client= setting"); diff --git a/src/network/networkd-ndisc.h b/src/network/networkd-ndisc.h index a463f42..6094881 100644 --- a/src/network/networkd-ndisc.h +++ b/src/network/networkd-ndisc.h @@ -4,6 +4,7 @@ #include "conf-parser.h" #include "time-util.h" +typedef struct Address Address; typedef struct Link Link; typedef struct Network Network; @@ -52,15 +53,15 @@ static inline char* NDISC_DNSSL_DOMAIN(const NDiscDNSSL *n) { return ((char*) n) + ALIGN(sizeof(NDiscDNSSL)); } -bool link_ipv6_accept_ra_enabled(Link *link); +bool link_ndisc_enabled(Link *link); -void network_adjust_ipv6_accept_ra(Network *network); +void network_adjust_ndisc(Network *network); int ndisc_start(Link *link); int ndisc_stop(Link *link); void ndisc_flush(Link *link); int link_request_ndisc(Link *link); +int ndisc_reconfigure_address(Address *address, Link *link); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_start_dhcp6_client); -CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_accept_ra_use_domains); +CONFIG_PARSER_PROTOTYPE(config_parse_ndisc_start_dhcp6_client); diff --git a/src/network/networkd-neighbor.c b/src/network/networkd-neighbor.c index 8321831..6b81950 100644 --- a/src/network/networkd-neighbor.c +++ b/src/network/networkd-neighbor.c @@ -10,28 +10,87 @@ #include "networkd-queue.h" #include "set.h" -Neighbor *neighbor_free(Neighbor *neighbor) { - if (!neighbor) - return NULL; +static Neighbor* neighbor_detach_impl(Neighbor *neighbor) { + assert(neighbor); + assert(!neighbor->link || !neighbor->network); if (neighbor->network) { assert(neighbor->section); ordered_hashmap_remove(neighbor->network->neighbors_by_section, neighbor->section); + neighbor->network = NULL; + return neighbor; } - config_section_free(neighbor->section); - - if (neighbor->link) + if (neighbor->link) { set_remove(neighbor->link->neighbors, neighbor); + neighbor->link = NULL; + return neighbor; + } + + return NULL; +} + +static void neighbor_detach(Neighbor *neighbor) { + neighbor_unref(neighbor_detach_impl(neighbor)); +} + +static Neighbor* neighbor_free(Neighbor *neighbor) { + if (!neighbor) + return NULL; + + neighbor_detach_impl(neighbor); + config_section_free(neighbor->section); return mfree(neighbor); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_free); +DEFINE_TRIVIAL_REF_UNREF_FUNC(Neighbor, neighbor, neighbor_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Neighbor, neighbor_unref); + +static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state); +static int neighbor_compare_func(const Neighbor *a, const Neighbor *b); + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + neighbor_hash_ops_detach, + Neighbor, + neighbor_hash_func, + neighbor_compare_func, + neighbor_detach); + +DEFINE_PRIVATE_HASH_OPS( + neighbor_hash_ops, + Neighbor, + neighbor_hash_func, + neighbor_compare_func); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + neighbor_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Neighbor, + neighbor_detach); + +static int neighbor_new(Neighbor **ret) { + Neighbor *neighbor; + + assert(ret); + + neighbor = new(Neighbor, 1); + if (!neighbor) + return -ENOMEM; + + *neighbor = (Neighbor) { + .n_ref = 1, + }; + + *ret = TAKE_PTR(neighbor); + return 0; +} static int neighbor_new_static(Network *network, const char *filename, unsigned section_line, Neighbor **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(neighbor_freep) Neighbor *neighbor = NULL; + _cleanup_(neighbor_unrefp) Neighbor *neighbor = NULL; int r; assert(network); @@ -49,18 +108,15 @@ static int neighbor_new_static(Network *network, const char *filename, unsigned return 0; } - neighbor = new(Neighbor, 1); - if (!neighbor) - return -ENOMEM; + r = neighbor_new(&neighbor); + if (r < 0) + return r; - *neighbor = (Neighbor) { - .network = network, - .family = AF_UNSPEC, - .section = TAKE_PTR(n), - .source = NETWORK_CONFIG_SOURCE_STATIC, - }; + neighbor->network = network; + neighbor->section = TAKE_PTR(n); + neighbor->source = NETWORK_CONFIG_SOURCE_STATIC; - r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &config_section_hash_ops, neighbor->section, neighbor); + r = ordered_hashmap_ensure_put(&network->neighbors_by_section, &neighbor_section_hash_ops, neighbor->section, neighbor); if (r < 0) return r; @@ -69,7 +125,7 @@ static int neighbor_new_static(Network *network, const char *filename, unsigned } static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { - _cleanup_(neighbor_freep) Neighbor *dest = NULL; + _cleanup_(neighbor_unrefp) Neighbor *dest = NULL; assert(neighbor); assert(ret); @@ -78,7 +134,8 @@ static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { if (!dest) return -ENOMEM; - /* Unset all pointers */ + /* Clear the reference counter and all pointers */ + dest->n_ref = 1; dest->link = NULL; dest->network = NULL; dest->section = NULL; @@ -90,7 +147,7 @@ static int neighbor_dup(const Neighbor *neighbor, Neighbor **ret) { static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) { assert(neighbor); - siphash24_compress(&neighbor->family, sizeof(neighbor->family), state); + siphash24_compress_typesafe(neighbor->family, state); if (!IN_SET(neighbor->family, AF_INET, AF_INET6)) /* treat any other address family as AF_UNSPEC */ @@ -98,7 +155,7 @@ static void neighbor_hash_func(const Neighbor *neighbor, struct siphash *state) /* Equality of neighbors are given by the destination address. * See neigh_lookup() in the kernel. */ - siphash24_compress(&neighbor->in_addr, FAMILY_ADDRESS_SIZE(neighbor->family), state); + in_addr_hash_func(&neighbor->in_addr, neighbor->family, state); } static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) { @@ -115,19 +172,6 @@ static int neighbor_compare_func(const Neighbor *a, const Neighbor *b) { return memcmp(&a->in_addr, &b->in_addr, FAMILY_ADDRESS_SIZE(a->family)); } -DEFINE_PRIVATE_HASH_OPS( - neighbor_hash_ops, - Neighbor, - neighbor_hash_func, - neighbor_compare_func); - -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - neighbor_hash_ops_free, - Neighbor, - neighbor_hash_func, - neighbor_compare_func, - neighbor_free); - static int neighbor_get_request(Link *link, const Neighbor *neighbor, Request **ret) { Request *req; @@ -152,7 +196,7 @@ static int neighbor_get_request(Link *link, const Neighbor *neighbor, Request ** return 0; } -static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { +int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { Neighbor *existing; assert(link); @@ -167,19 +211,21 @@ static int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret) { return 0; } -static int neighbor_add(Link *link, Neighbor *neighbor) { +static int neighbor_attach(Link *link, Neighbor *neighbor) { int r; assert(link); assert(neighbor); + assert(!neighbor->link); - r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_free, neighbor); + r = set_ensure_put(&link->neighbors, &neighbor_hash_ops_detach, neighbor); if (r < 0) return r; if (r == 0) return -EEXIST; neighbor->link = link; + neighbor_ref(neighbor); return 0; } @@ -279,7 +325,7 @@ static int static_neighbor_configure_handler(sd_netlink *rtnl, sd_netlink_messag } static int link_request_neighbor(Link *link, const Neighbor *neighbor) { - _cleanup_(neighbor_freep) Neighbor *tmp = NULL; + _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL; Neighbor *existing = NULL; int r; @@ -308,7 +354,7 @@ static int link_request_neighbor(Link *link, const Neighbor *neighbor) { log_neighbor_debug(tmp, "Requesting", link); r = link_queue_request_safe(link, REQUEST_TYPE_NEIGHBOR, tmp, - neighbor_free, + neighbor_unref, neighbor_hash_func, neighbor_compare_func, neighbor_process_request, @@ -353,35 +399,51 @@ int link_request_static_neighbors(Link *link) { return 0; } -static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int neighbor_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); - assert(link); + assert(rreq); - if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 1; + Link *link = ASSERT_PTR(rreq->link); + Neighbor *neighbor = ASSERT_PTR(rreq->userdata); + + if (link->state == LINK_STATE_LINGER) + return 0; r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ESRCH) + if (r < 0) { /* Neighbor may not exist because it already got deleted, ignore that. */ - log_link_message_warning_errno(link, m, r, "Could not remove neighbor"); + log_link_message_full_errno(link, m, + (r == -ESRCH || !neighbor->link) ? LOG_DEBUG : LOG_WARNING, + r, "Could not remove neighbor"); + + if (neighbor->link) { + /* If the neighbor cannot be removed, then assume the neighbor is already removed. */ + log_neighbor_debug(neighbor, "Forgetting", link); + + Request *req; + if (neighbor_get_request(link, neighbor, &req) >= 0) + neighbor_enter_removed(req->userdata); + + neighbor_detach(neighbor); + } + } return 1; } -static int neighbor_remove(Neighbor *neighbor) { +int neighbor_remove(Neighbor *neighbor, Link *link) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Request *req; - Link *link; int r; assert(neighbor); - assert(neighbor->link); - assert(neighbor->link->manager); - assert(neighbor->link->manager->rtnl); + assert(link); + assert(link->manager); + assert(link->manager->rtnl); - link = neighbor->link; + /* If the neighbor is remembered, then use the remembered object. */ + (void) neighbor_get(link, neighbor, &neighbor); log_neighbor_debug(neighbor, "Removing", link); @@ -394,17 +456,11 @@ static int neighbor_remove(Neighbor *neighbor) { if (r < 0) return log_link_error_errno(link, r, "Could not append NDA_DST attribute: %m"); - r = netlink_call_async(link->manager->rtnl, NULL, m, neighbor_remove_handler, - link_netlink_destroy_callback, link); + r = link_remove_request_add(link, neighbor, neighbor, link->manager->rtnl, m, neighbor_remove_handler); if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); + return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m"); neighbor_enter_removing(neighbor); - if (neighbor_get_request(neighbor->link, neighbor, &req) >= 0) - neighbor_enter_removing(req->userdata); - return 0; } @@ -440,13 +496,13 @@ int link_drop_foreign_neighbors(Link *link) { if (!neighbor_is_marked(neighbor)) continue; - RET_GATHER(r, neighbor_remove(neighbor)); + RET_GATHER(r, neighbor_remove(neighbor, link)); } return r; } -int link_drop_managed_neighbors(Link *link) { +int link_drop_static_neighbors(Link *link) { Neighbor *neighbor; int r = 0; @@ -454,14 +510,14 @@ int link_drop_managed_neighbors(Link *link) { SET_FOREACH(neighbor, link->neighbors) { /* Do not touch nexthops managed by kernel or other tools. */ - if (neighbor->source == NETWORK_CONFIG_SOURCE_FOREIGN) + if (neighbor->source != NETWORK_CONFIG_SOURCE_STATIC) continue; /* Ignore neighbors not assigned yet or already removing. */ if (!neighbor_exists(neighbor)) continue; - RET_GATHER(r, neighbor_remove(neighbor)); + RET_GATHER(r, neighbor_remove(neighbor, link)); } return r; @@ -477,7 +533,7 @@ void link_foreignize_neighbors(Link *link) { } int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(neighbor_freep) Neighbor *tmp = NULL; + _cleanup_(neighbor_unrefp) Neighbor *tmp = NULL; Neighbor *neighbor = NULL; Request *req = NULL; uint16_t type, state; @@ -510,10 +566,9 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) { log_warning_errno(r, "rtnl: received neighbor message with invalid state, ignoring: %m"); return 0; - } else if (!FLAGS_SET(state, NUD_PERMANENT)) { - log_debug("rtnl: received non-static neighbor, ignoring."); + } else if (!FLAGS_SET(state, NUD_PERMANENT)) + /* Currently, we are interested in only static neighbors. */ return 0; - } r = sd_rtnl_message_neigh_get_ifindex(message, &ifindex); if (r < 0) { @@ -525,15 +580,13 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, } r = link_get_by_index(m, ifindex, &link); - if (r < 0) { + if (r < 0) /* when enumerating we might be out of sync, but we will get the neighbor again. Also, * kernel sends messages about neighbors after a link is removed. So, just ignore it. */ - log_debug("rtnl: received neighbor for link '%d' we don't know about, ignoring.", ifindex); return 0; - } - tmp = new0(Neighbor, 1); - if (!tmp) + r = neighbor_new(&tmp); + if (r < 0) return log_oom(); /* First, retrieve the fundamental information about the neighbor. */ @@ -541,7 +594,10 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (r < 0) { log_link_warning(link, "rtnl: received neighbor message without family, ignoring."); return 0; - } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + } + if (tmp->family == AF_BRIDGE) /* Currently, we do not support it. */ + return 0; + if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { log_link_debug(link, "rtnl: received neighbor message with invalid family '%i', ignoring.", tmp->family); return 0; } @@ -560,7 +616,7 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, if (neighbor) { neighbor_enter_removed(neighbor); log_neighbor_debug(neighbor, "Forgetting removed", link); - neighbor_free(neighbor); + neighbor_detach(neighbor); } else log_neighbor_debug(tmp, "Kernel removed unknown", link); @@ -572,12 +628,12 @@ int manager_rtnl_process_neighbor(sd_netlink *rtnl, sd_netlink_message *message, /* If we did not know the neighbor, then save it. */ if (!neighbor) { - r = neighbor_add(link, tmp); + r = neighbor_attach(link, tmp); if (r < 0) { log_link_warning_errno(link, r, "Failed to save received neighbor, ignoring: %m"); return 0; } - neighbor = TAKE_PTR(tmp); + neighbor = tmp; is_new = true; } @@ -638,9 +694,9 @@ int network_drop_invalid_neighbors(Network *network) { Neighbor *dup; if (neighbor_section_verify(neighbor) < 0) { - /* Drop invalid [Neighbor] sections. Note that neighbor_free() will drop the + /* Drop invalid [Neighbor] sections. Note that neighbor_detach() will drop the * neighbor from neighbors_by_section. */ - neighbor_free(neighbor); + neighbor_detach(neighbor); continue; } @@ -648,17 +704,17 @@ int network_drop_invalid_neighbors(Network *network) { dup = set_remove(neighbors, neighbor); if (dup) { log_warning("%s: Duplicated neighbor settings for %s is specified at line %u and %u, " - "dropping the address setting specified at line %u.", + "dropping the neighbor setting specified at line %u.", dup->section->filename, IN_ADDR_TO_STRING(neighbor->family, &neighbor->in_addr), neighbor->section->line, dup->section->line, dup->section->line); - /* neighbor_free() will drop the address from neighbors_by_section. */ - neighbor_free(dup); + /* neighbor_detach() will drop the neighbor from neighbors_by_section. */ + neighbor_detach(dup); } - /* Use neighbor_hash_ops, instead of neighbor_hash_ops_free. Otherwise, the Neighbor objects - * will be freed. */ + /* Use neighbor_hash_ops, instead of neighbor_hash_ops_detach. Otherwise, the Neighbor objects + * will be detached. */ r = set_ensure_put(&neighbors, &neighbor_hash_ops, neighbor); if (r < 0) return log_oom(); @@ -681,7 +737,7 @@ int config_parse_neighbor_address( void *data, void *userdata) { - _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL; Network *network = ASSERT_PTR(userdata); int r; @@ -724,7 +780,7 @@ int config_parse_neighbor_lladdr( void *data, void *userdata) { - _cleanup_(neighbor_free_or_set_invalidp) Neighbor *n = NULL; + _cleanup_(neighbor_unref_or_set_invalidp) Neighbor *n = NULL; Network *network = ASSERT_PTR(userdata); int r; diff --git a/src/network/networkd-neighbor.h b/src/network/networkd-neighbor.h index 683a310..7917930 100644 --- a/src/network/networkd-neighbor.h +++ b/src/network/networkd-neighbor.h @@ -21,16 +21,22 @@ typedef struct Neighbor { NetworkConfigSource source; NetworkConfigState state; + unsigned n_ref; + int family; union in_addr_union in_addr; struct hw_addr_data ll_addr; } Neighbor; -Neighbor *neighbor_free(Neighbor *neighbor); +Neighbor* neighbor_ref(Neighbor *neighbor); +Neighbor* neighbor_unref(Neighbor *neighbor); + +int neighbor_get(Link *link, const Neighbor *in, Neighbor **ret); +int neighbor_remove(Neighbor *neighbor, Link *link); int network_drop_invalid_neighbors(Network *network); -int link_drop_managed_neighbors(Link *link); +int link_drop_static_neighbors(Link *link); int link_drop_foreign_neighbors(Link *link); void link_foreignize_neighbors(Link *link); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index a6593a0..23e25ff 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -3,7 +3,9 @@ #if __GNUC__ >= 7 _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #endif +#include #include + #include "conf-parser.h" #include "in-addr-prefix-util.h" #include "netem.h" @@ -20,6 +22,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-dhcp-server.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" +#include "networkd-dns.h" #include "networkd-ipv4ll.h" #include "networkd-ipv6-proxy-ndp.h" #include "networkd-ipv6ll.h" @@ -29,6 +32,7 @@ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"") #include "networkd-network.h" #include "networkd-neighbor.h" #include "networkd-nexthop.h" +#include "networkd-ntp.h" #include "networkd-radv.h" #include "networkd-route.h" #include "networkd-routing-policy-rule.h" @@ -116,6 +120,7 @@ Network.EmitLLDP, config_parse_lldp_multicast_mode, Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Network.Domains, config_parse_domains, 0, 0 +Network.UseDomains, config_parse_use_domains, 0, offsetof(Network, use_domains) Network.DNS, config_parse_dns, 0, 0 Network.DNSDefaultRoute, config_parse_tristate, 0, offsetof(Network, dns_default_route) Network.LLMNR, config_parse_resolve_support, 0, offsetof(Network, llmnr) @@ -124,13 +129,16 @@ Network.DNSOverTLS, config_parse_dns_over_tls_mode, Network.DNSSEC, config_parse_dnssec_mode, 0, offsetof(Network, dnssec_mode) Network.DNSSECNegativeTrustAnchors, config_parse_dnssec_negative_trust_anchors, 0, offsetof(Network, dnssec_negative_trust_anchors) Network.NTP, config_parse_ntp, 0, offsetof(Network, ntp) -Network.IPForward, config_parse_address_family_with_kernel, 0, offsetof(Network, ip_forward) +Network.IPForward, config_parse_ip_forward_deprecated, 0, 0 +Network.IPv4Forwarding, config_parse_tristate, 0, offsetof(Network, ip_forwarding[0]) +Network.IPv6Forwarding, config_parse_tristate, 0, offsetof(Network, ip_forwarding[1]) Network.IPMasquerade, config_parse_ip_masquerade, 0, offsetof(Network, ip_masquerade) Network.IPv6PrivacyExtensions, config_parse_ipv6_privacy_extensions, 0, offsetof(Network, ipv6_privacy_extensions) -Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) -Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ipv6_accept_ra) +Network.IPv6AcceptRA, config_parse_tristate, 0, offsetof(Network, ndisc) +Network.IPv6AcceptRouterAdvertisements, config_parse_tristate, 0, offsetof(Network, ndisc) Network.IPv6DuplicateAddressDetection, config_parse_int, 0, offsetof(Network, ipv6_dad_transmits) Network.IPv6HopLimit, config_parse_uint8, 0, offsetof(Network, ipv6_hop_limit) +Network.IPv6RetransmissionTimeSec, config_parse_sec, 0, offsetof(Network, ipv6_retransmission_time) Network.IPv6ProxyNDP, config_parse_tristate, 0, offsetof(Network, ipv6_proxy_ndp) Network.IPv6MTUBytes, config_parse_mtu, AF_INET6, offsetof(Network, ipv6_mtu) Network.IPv4AcceptLocal, config_parse_tristate, 0, offsetof(Network, ipv4_accept_local) @@ -138,6 +146,7 @@ Network.IPv4RouteLocalnet, config_parse_tristate, Network.ActiveSlave, config_parse_bool, 0, offsetof(Network, active_slave) Network.PrimarySlave, config_parse_bool, 0, offsetof(Network, primary_slave) Network.IPv4ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) +Network.IPv4ProxyARPPrivateVLAN, config_parse_tristate, 0, offsetof(Network, proxy_arp_pvlan) Network.ProxyARP, config_parse_tristate, 0, offsetof(Network, proxy_arp) Network.IPv6ProxyNDPAddress, config_parse_ipv6_proxy_ndp_address, 0, 0 Network.IPv4ReversePathFilter, config_parse_ip_reverse_path_filter, 0, offsetof(Network, ipv4_rp_filter) @@ -179,6 +188,7 @@ RoutingPolicyRule.IPProtocol, config_parse_routing_policy_rule_ip RoutingPolicyRule.SourcePort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.DestinationPort, config_parse_routing_policy_rule_port_range, 0, 0 RoutingPolicyRule.InvertRule, config_parse_routing_policy_rule_invert, 0, 0 +RoutingPolicyRule.L3MasterDevice, config_parse_routing_policy_rule_l3mdev, 0, 0 RoutingPolicyRule.Family, config_parse_routing_policy_rule_family, 0, 0 RoutingPolicyRule.User, config_parse_routing_policy_rule_uid_range, 0, 0 RoutingPolicyRule.SuppressInterfaceGroup, config_parse_routing_policy_rule_suppress_ifgroup, 0, 0 @@ -191,23 +201,23 @@ Route.Metric, config_parse_route_priority, Route.Scope, config_parse_route_scope, 0, 0 Route.PreferredSource, config_parse_preferred_src, 0, 0 Route.Table, config_parse_route_table, 0, 0 -Route.MTUBytes, config_parse_route_mtu, AF_UNSPEC, 0 -Route.GatewayOnLink, config_parse_route_boolean, 0, 0 -Route.GatewayOnlink, config_parse_route_boolean, 0, 0 +Route.GatewayOnLink, config_parse_route_gateway_onlink, 0, 0 +Route.GatewayOnlink, config_parse_route_gateway_onlink, 0, 0 Route.IPv6Preference, config_parse_ipv6_route_preference, 0, 0 Route.Protocol, config_parse_route_protocol, 0, 0 Route.Type, config_parse_route_type, 0, 0 -Route.TCPRetransmissionTimeoutSec, config_parse_route_tcp_rto, 0, 0 -Route.HopLimit, config_parse_route_hop_limit, 0, 0 -Route.InitialCongestionWindow, config_parse_route_tcp_window, 0, 0 -Route.InitialAdvertisedReceiveWindow, config_parse_route_tcp_window, 0, 0 -Route.TCPAdvertisedMaximumSegmentSize, config_parse_tcp_advmss, 0, 0 -Route.TCPCongestionControlAlgorithm, config_parse_tcp_congestion, 0, 0 -Route.QuickAck, config_parse_route_boolean, 0, 0 -Route.FastOpenNoCookie, config_parse_route_boolean, 0, 0 -Route.TTLPropagate, config_parse_route_boolean, 0, 0 Route.MultiPathRoute, config_parse_multipath_route, 0, 0 Route.NextHop, config_parse_route_nexthop, 0, 0 +Route.MTUBytes, config_parse_route_metric_mtu, RTAX_MTU, 0 +Route.TCPAdvertisedMaximumSegmentSize, config_parse_route_metric_advmss, RTAX_ADVMSS, 0 +Route.HopLimit, config_parse_route_metric_hop_limit, RTAX_HOPLIMIT, 0 +Route.InitialCongestionWindow, config_parse_route_metric_tcp_window, RTAX_INITCWND, 0 +Route.TCPRetransmissionTimeoutSec, config_parse_route_metric_tcp_rto, RTAX_RTO_MIN, 0 +Route.InitialAdvertisedReceiveWindow, config_parse_route_metric_tcp_window, RTAX_INITRWND, 0 +Route.QuickAck, config_parse_route_metric_boolean, RTAX_QUICKACK, 0 +Route.TCPCongestionControlAlgorithm, config_parse_route_metric_tcp_congestion, RTAX_CC_ALGO, 0 +Route.FastOpenNoCookie, config_parse_route_metric_boolean, RTAX_FASTOPEN_NO_COOKIE, 0 +Route.TTLPropagate, config_parse_warn_compat, DISABLED_LEGACY, 0 NextHop.Id, config_parse_nexthop_id, 0, 0 NextHop.Gateway, config_parse_nexthop_gateway, 0, 0 NextHop.Family, config_parse_nexthop_family, 0, 0 @@ -216,15 +226,15 @@ NextHop.Blackhole, config_parse_nexthop_blackhole, NextHop.Group, config_parse_nexthop_group, 0, 0 DHCPv4.RequestAddress, config_parse_in_addr_non_null, AF_INET, offsetof(Network, dhcp_request_address) DHCPv4.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) -DHCPv4.UseDNS, config_parse_dhcp_use_dns, AF_INET, 0 +DHCPv4.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp_use_dns) DHCPv4.RoutesToDNS, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_dns) -DHCPv4.UseNTP, config_parse_dhcp_use_ntp, AF_INET, 0 +DHCPv4.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp_use_ntp) DHCPv4.RoutesToNTP, config_parse_bool, 0, offsetof(Network, dhcp_routes_to_ntp) DHCPv4.UseSIP, config_parse_bool, 0, offsetof(Network, dhcp_use_sip) DHCPv4.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp_use_captive_portal) DHCPv4.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) DHCPv4.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) -DHCPv4.UseDomains, config_parse_dhcp_use_domains, AF_INET, 0 +DHCPv4.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCPv4.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) DHCPv4.UseGateway, config_parse_tristate, 0, offsetof(Network, dhcp_use_gateway) DHCPv4.QuickAck, config_parse_bool, 0, offsetof(Network, dhcp_quickack) @@ -245,6 +255,7 @@ DHCPv4.RouteMetric, config_parse_dhcp_route_metric, DHCPv4.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET, 0 DHCPv4.UseTimezone, config_parse_bool, 0, offsetof(Network, dhcp_use_timezone) DHCPv4.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) +DHCPv4.ServerPort, config_parse_uint16, 0, offsetof(Network, dhcp_port) DHCPv4.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp_send_release) DHCPv4.SendDecline, config_parse_bool, 0, offsetof(Network, dhcp_send_decline) DHCPv4.DenyList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) @@ -264,10 +275,10 @@ DHCPv4.NFTSet, config_parse_nft_set, DHCPv4.RapidCommit, config_parse_tristate, 0, offsetof(Network, dhcp_use_rapid_commit) DHCPv6.UseAddress, config_parse_bool, 0, offsetof(Network, dhcp6_use_address) DHCPv6.UseDelegatedPrefix, config_parse_bool, 0, offsetof(Network, dhcp6_use_pd_prefix) -DHCPv6.UseDNS, config_parse_dhcp_use_dns, AF_INET6, 0 +DHCPv6.UseDNS, config_parse_tristate, 0, offsetof(Network, dhcp6_use_dns) DHCPv6.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp6_use_hostname) -DHCPv6.UseDomains, config_parse_dhcp_use_domains, AF_INET6, 0 -DHCPv6.UseNTP, config_parse_dhcp_use_ntp, AF_INET6, 0 +DHCPv6.UseDomains, config_parse_use_domains, 0, offsetof(Network, dhcp6_use_domains) +DHCPv6.UseNTP, config_parse_tristate, 0, offsetof(Network, dhcp6_use_ntp) DHCPv6.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, dhcp6_use_captive_portal) DHCPv6.MUDURL, config_parse_mud_url, 0, offsetof(Network, dhcp6_mudurl) DHCPv6.SendHostname, config_parse_dhcp_send_hostname, AF_INET6, 0 @@ -286,21 +297,23 @@ DHCPv6.RapidCommit, config_parse_bool, DHCPv6.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp6_netlabel) DHCPv6.SendRelease, config_parse_bool, 0, offsetof(Network, dhcp6_send_release) DHCPv6.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp6_nft_set_context) -IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_gateway) -IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_route_prefix) -IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_autonomous_prefix) -IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_onlink_prefix) -IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_pref64) -IPv6AcceptRA.UseDNS, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_dns) -IPv6AcceptRA.UseDomains, config_parse_ipv6_accept_ra_use_domains, 0, offsetof(Network, ipv6_accept_ra_use_domains) -IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_mtu) -IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_hop_limit) -IPv6AcceptRA.UseICMP6RateLimit, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_icmp6_ratelimit) -IPv6AcceptRA.DHCPv6Client, config_parse_ipv6_accept_ra_start_dhcp6_client, 0, offsetof(Network, ipv6_accept_ra_start_dhcp6_client) +IPv6AcceptRA.UseRedirect, config_parse_bool, 0, offsetof(Network, ndisc_use_redirect) +IPv6AcceptRA.UseGateway, config_parse_bool, 0, offsetof(Network, ndisc_use_gateway) +IPv6AcceptRA.UseRoutePrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_route_prefix) +IPv6AcceptRA.UseAutonomousPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_autonomous_prefix) +IPv6AcceptRA.UseOnLinkPrefix, config_parse_bool, 0, offsetof(Network, ndisc_use_onlink_prefix) +IPv6AcceptRA.UsePREF64, config_parse_bool, 0, offsetof(Network, ndisc_use_pref64) +IPv6AcceptRA.UseDNS, config_parse_tristate, 0, offsetof(Network, ndisc_use_dns) +IPv6AcceptRA.UseDomains, config_parse_use_domains, 0, offsetof(Network, ndisc_use_domains) +IPv6AcceptRA.UseMTU, config_parse_bool, 0, offsetof(Network, ndisc_use_mtu) +IPv6AcceptRA.UseHopLimit, config_parse_bool, 0, offsetof(Network, ndisc_use_hop_limit) +IPv6AcceptRA.UseReachableTime, config_parse_bool, 0, offsetof(Network, ndisc_use_reachable_time) +IPv6AcceptRA.UseRetransmissionTime, config_parse_bool, 0, offsetof(Network, ndisc_use_retransmission_time) +IPv6AcceptRA.DHCPv6Client, config_parse_ndisc_start_dhcp6_client, 0, offsetof(Network, ndisc_start_dhcp6_client) IPv6AcceptRA.RouteTable, config_parse_dhcp_or_ra_route_table, AF_INET6, 0 -IPv6AcceptRA.RouteMetric, config_parse_ipv6_accept_ra_route_metric, 0, 0 -IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_quickack) -IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ipv6_accept_ra_use_captive_portal) +IPv6AcceptRA.RouteMetric, config_parse_ndisc_route_metric, 0, 0 +IPv6AcceptRA.QuickAck, config_parse_bool, 0, offsetof(Network, ndisc_quickack) +IPv6AcceptRA.UseCaptivePortal, config_parse_bool, 0, offsetof(Network, ndisc_use_captive_portal) IPv6AcceptRA.RouterAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_router) IPv6AcceptRA.RouterDenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_router) IPv6AcceptRA.PrefixAllowList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_allow_listed_prefix) @@ -343,6 +356,7 @@ DHCPServer.BootServerAddress, config_parse_in_addr_non_null, DHCPServer.BootServerName, config_parse_dns_name, 0, offsetof(Network, dhcp_server_boot_server_name) DHCPServer.BootFilename, config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, offsetof(Network, dhcp_server_boot_filename) DHCPServer.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp_server_rapid_commit) +DHCPServer.PersistLeases, config_parse_tristate, 0, offsetof(Network, dhcp_server_persist_leases) DHCPServerStaticLease.Address, config_parse_dhcp_static_lease_address, 0, 0 DHCPServerStaticLease.MACAddress, config_parse_dhcp_static_lease_hwaddr, 0, 0 Bridge.Cost, config_parse_uint32, 0, offsetof(Network, cost) @@ -368,9 +382,9 @@ BridgeFDB.AssociatedWith, config_parse_fdb_ntf_flags, BridgeFDB.OutgoingInterface, config_parse_fdb_interface, 0, 0 BridgeMDB.MulticastGroupAddress, config_parse_mdb_group_address, 0, 0 BridgeMDB.VLANId, config_parse_mdb_vlan_id, 0, 0 -BridgeVLAN.PVID, config_parse_brvlan_pvid, 0, 0 -BridgeVLAN.VLAN, config_parse_brvlan_vlan, 0, 0 -BridgeVLAN.EgressUntagged, config_parse_brvlan_untagged, 0, 0 +BridgeVLAN.PVID, config_parse_bridge_vlan_id, 0, offsetof(Network, bridge_vlan_pvid) +BridgeVLAN.VLAN, config_parse_bridge_vlan_id_range, 0, offsetof(Network, bridge_vlan_bitmap) +BridgeVLAN.EgressUntagged, config_parse_bridge_vlan_id_range, 0, offsetof(Network, bridge_vlan_untagged_bitmap) DHCPPrefixDelegation.UplinkInterface, config_parse_uplink, 0, 0 DHCPPrefixDelegation.SubnetId, config_parse_dhcp_pd_subnet_id, 0, offsetof(Network, dhcp_pd_subnet_id) DHCPPrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp_pd_announce) @@ -381,7 +395,8 @@ DHCPPrefixDelegation.RouteMetric, config_parse_uint32, DHCPPrefixDelegation.NetLabel, config_parse_string, CONFIG_PARSE_STRING_SAFE, offsetof(Network, dhcp_pd_netlabel) DHCPPrefixDelegation.NFTSet, config_parse_nft_set, NFT_SET_PARSE_NETWORK, offsetof(Network, dhcp_pd_nft_set_context) IPv6SendRA.RouterLifetimeSec, config_parse_router_lifetime, 0, offsetof(Network, router_lifetime_usec) -IPv6SendRA.RetransmitSec, config_parse_router_retransmit, 0, offsetof(Network, router_retransmit_usec) +IPv6SendRA.ReachableTimeSec, config_parse_router_uint32_msec_usec, 0, offsetof(Network, router_reachable_usec) +IPv6SendRA.RetransmitSec, config_parse_router_uint32_msec_usec, 0, offsetof(Network, router_retransmit_usec) IPv6SendRA.Managed, config_parse_bool, 0, offsetof(Network, router_managed) IPv6SendRA.OtherInformation, config_parse_bool, 0, offsetof(Network, router_other_information) IPv6SendRA.RouterPreference, config_parse_router_preference, 0, 0 @@ -396,8 +411,8 @@ IPv6SendRA.HomeAgent, config_parse_bool, IPv6SendRA.HomeAgentLifetimeSec, config_parse_router_home_agent_lifetime, 0, offsetof(Network, home_agent_lifetime_usec) IPv6SendRA.HomeAgentPreference, config_parse_uint16, 0, offsetof(Network, router_home_agent_preference) IPv6Prefix.Prefix, config_parse_prefix, 0, 0 -IPv6Prefix.OnLink, config_parse_prefix_boolean, 0, 0 -IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_boolean, 0, 0 +IPv6Prefix.OnLink, config_parse_prefix_boolean, ND_OPT_PI_FLAG_ONLINK, 0 +IPv6Prefix.AddressAutoconfiguration, config_parse_prefix_boolean, ND_OPT_PI_FLAG_AUTO, 0 IPv6Prefix.ValidLifetimeSec, config_parse_prefix_lifetime, 0, 0 IPv6Prefix.PreferredLifetimeSec, config_parse_prefix_lifetime, 0, 0 IPv6Prefix.Assign, config_parse_prefix_boolean, 0, 0 @@ -578,12 +593,12 @@ IPv6PrefixDelegation.Domains, config_parse_radv_search_domains, IPv6PrefixDelegation.DNSLifetimeSec, config_parse_sec, 0, offsetof(Network, router_dns_lifetime_usec) DHCPv4.BlackList, config_parse_in_addr_prefixes, AF_INET, offsetof(Network, dhcp_deny_listed_ip) DHCP.ClientIdentifier, config_parse_dhcp_client_identifier, 0, offsetof(Network, dhcp_client_identifier) -DHCP.UseDNS, config_parse_dhcp_use_dns, AF_UNSPEC, 0 -DHCP.UseNTP, config_parse_dhcp_use_ntp, AF_UNSPEC, 0 +DHCP.UseDNS, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_dns) +DHCP.UseNTP, config_parse_tristate, 0, offsetof(Network, compat_dhcp_use_ntp) DHCP.UseMTU, config_parse_bool, 0, offsetof(Network, dhcp_use_mtu) DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) -DHCP.UseDomains, config_parse_dhcp_use_domains, AF_UNSPEC, 0 -DHCP.UseDomainName, config_parse_dhcp_use_domains, AF_UNSPEC, 0 +DHCP.UseDomains, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains) +DHCP.UseDomainName, config_parse_use_domains, 0, offsetof(Network, compat_dhcp_use_domains) DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize) DHCP.SendHostname, config_parse_dhcp_send_hostname, AF_UNSPEC, 0 @@ -601,9 +616,9 @@ DHCP.UseTimezone, config_parse_bool, DHCP.ListenPort, config_parse_uint16, 0, offsetof(Network, dhcp_client_port) DHCP.RapidCommit, config_parse_bool, 0, offsetof(Network, dhcp6_use_rapid_commit) DHCP.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0 -DHCPv4.UseDomainName, config_parse_dhcp_use_domains, AF_INET, 0 +DHCPv4.UseDomainName, config_parse_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCPv4.CriticalConnection, config_parse_tristate, 0, offsetof(Network, dhcp_critical) -DHCPv6.RouteMetric, config_parse_ipv6_accept_ra_route_metric, AF_INET6, 0 +DHCPv6.RouteMetric, config_parse_ndisc_route_metric, AF_INET6, 0 DHCPv6.ForceDHCPv6PDOtherInformation, config_parse_warn_compat, DISABLED_LEGACY, 0 DHCPv6PrefixDelegation.SubnetId, config_parse_dhcp_pd_subnet_id, 0, offsetof(Network, dhcp_pd_subnet_id) DHCPv6PrefixDelegation.Announce, config_parse_bool, 0, offsetof(Network, dhcp_pd_announce) @@ -613,6 +628,7 @@ DHCPv6PrefixDelegation.Token, config_parse_address_generation_typ DHCPv6PrefixDelegation.RouteMetric, config_parse_uint32, 0, offsetof(Network, dhcp_pd_route_metric) IPv6AcceptRA.DenyList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_prefix) IPv6AcceptRA.BlackList, config_parse_in_addr_prefixes, AF_INET6, offsetof(Network, ndisc_deny_listed_prefix) +IPv6AcceptRA.UseICMP6RateLimit, config_parse_warn_compat, DISABLED_LEGACY, 0 TrafficControlQueueingDiscipline.Parent, config_parse_qdisc_parent, _QDISC_KIND_INVALID, 0 TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec, config_parse_network_emulator_delay, 0, 0 TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec, config_parse_network_emulator_delay, 0, 0 diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index dcd3e5a..8232db0 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include #include @@ -42,9 +43,6 @@ #include "strv.h" #include "tclass.h" -/* Let's assume that anything above this number is a user misconfiguration. */ -#define MAX_NTP_SERVERS 128U - static int network_resolve_netdev_one(Network *network, const char *name, NetDevKind kind, NetDev **ret) { const char *kind_string; NetDev *netdev; @@ -187,8 +185,8 @@ int network_verify(Network *network) { log_warning("%s: Cannot set routes when Bond= is specified, ignoring routes.", network->filename); - network->addresses_by_section = ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); - network->routes_by_section = hashmap_free_with_destructor(network->routes_by_section, route_free); + network->addresses_by_section = ordered_hashmap_free(network->addresses_by_section); + network->routes_by_section = hashmap_free(network->routes_by_section); } if (network->link_local < 0) { @@ -225,11 +223,8 @@ int network_verify(Network *network) { network->ipv6ll_address_gen_mode < 0) network->ipv6ll_address_gen_mode = IPV6_LINK_LOCAL_ADDRESSS_GEN_MODE_STABLE_PRIVACY; - /* IPMasquerade implies IPForward */ - network->ip_forward |= network->ip_masquerade; - network_adjust_ipv6_proxy_ndp(network); - network_adjust_ipv6_accept_ra(network); + network_adjust_ndisc(network); network_adjust_dhcp(network); network_adjust_radv(network); network_adjust_bridge_vlan(network); @@ -304,15 +299,15 @@ int network_verify(Network *network) { if (r < 0) return r; /* network_drop_invalid_addresses() logs internally. */ network_drop_invalid_routes(network); - network_drop_invalid_nexthops(network); + r = network_drop_invalid_nexthops(network); + if (r < 0) + return r; network_drop_invalid_bridge_fdb_entries(network); network_drop_invalid_bridge_mdb_entries(network); r = network_drop_invalid_neighbors(network); if (r < 0) return r; network_drop_invalid_address_labels(network); - network_drop_invalid_prefixes(network); - network_drop_invalid_route_prefixes(network); network_drop_invalid_routing_policy_rules(network); network_drop_invalid_qdisc(network); network_drop_invalid_tclass(network); @@ -370,7 +365,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .n_ref = 1, .required_for_online = -1, - .required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT, + .required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID, .activation_policy = _ACTIVATION_POLICY_INVALID, .group = -1, .arp = -1, @@ -380,14 +375,21 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .keep_configuration = manager->keep_configuration, + .use_domains = _USE_DOMAINS_INVALID, + + .compat_dhcp_use_domains = _USE_DOMAINS_INVALID, + .compat_dhcp_use_dns = -1, + .compat_dhcp_use_ntp = -1, + .dhcp_duid.type = _DUID_TYPE_INVALID, .dhcp_critical = -1, - .dhcp_use_ntp = true, + .dhcp_use_ntp = -1, .dhcp_routes_to_ntp = true, .dhcp_use_sip = true, .dhcp_use_captive_portal = true, - .dhcp_use_dns = true, + .dhcp_use_dns = -1, .dhcp_routes_to_dns = true, + .dhcp_use_domains = _USE_DOMAINS_INVALID, .dhcp_use_hostname = true, .dhcp_use_routes = true, .dhcp_use_gateway = -1, @@ -403,9 +405,10 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp6_use_address = true, .dhcp6_use_pd_prefix = true, - .dhcp6_use_dns = true, + .dhcp6_use_dns = -1, + .dhcp6_use_domains = _USE_DOMAINS_INVALID, .dhcp6_use_hostname = true, - .dhcp6_use_ntp = true, + .dhcp6_use_ntp = -1, .dhcp6_use_captive_portal = true, .dhcp6_use_rapid_commit = true, .dhcp6_send_hostname = true, @@ -427,6 +430,7 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .dhcp_server_emit_router = true, .dhcp_server_emit_timezone = true, .dhcp_server_rapid_commit = true, + .dhcp_server_persist_leases = -1, .router_lifetime_usec = RADV_DEFAULT_ROUTER_LIFETIME_USEC, .router_dns_lifetime_usec = RADV_DEFAULT_VALID_LIFETIME_USEC, @@ -448,6 +452,8 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .priority = LINK_BRIDGE_PORT_PRIORITY_INVALID, .multicast_router = _MULTICAST_ROUTER_INVALID, + .bridge_vlan_pvid = BRIDGE_VLAN_KEEP_PVID, + .lldp_mode = LLDP_MODE_ROUTERS_ONLY, .lldp_multicast_mode = _SD_LLDP_MULTICAST_MODE_INVALID, @@ -461,29 +467,34 @@ int network_load_one(Manager *manager, OrderedHashmap **networks, const char *fi .link_local = _ADDRESS_FAMILY_INVALID, .ipv6ll_address_gen_mode = _IPV6_LINK_LOCAL_ADDRESS_GEN_MODE_INVALID, + .ip_forwarding = { -1, -1, }, .ipv4_accept_local = -1, .ipv4_route_localnet = -1, .ipv6_privacy_extensions = _IPV6_PRIVACY_EXTENSIONS_INVALID, .ipv6_dad_transmits = -1, .ipv6_proxy_ndp = -1, .proxy_arp = -1, + .proxy_arp_pvlan = -1, .ipv4_rp_filter = _IP_REVERSE_PATH_FILTER_INVALID, - .ipv6_accept_ra = -1, - .ipv6_accept_ra_use_dns = true, - .ipv6_accept_ra_use_gateway = true, - .ipv6_accept_ra_use_captive_portal = true, - .ipv6_accept_ra_use_route_prefix = true, - .ipv6_accept_ra_use_autonomous_prefix = true, - .ipv6_accept_ra_use_onlink_prefix = true, - .ipv6_accept_ra_use_mtu = true, - .ipv6_accept_ra_use_hop_limit = true, - .ipv6_accept_ra_use_icmp6_ratelimit = true, - .ipv6_accept_ra_route_table = RT_TABLE_MAIN, - .ipv6_accept_ra_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH, - .ipv6_accept_ra_route_metric_medium = IPV6RA_ROUTE_METRIC_MEDIUM, - .ipv6_accept_ra_route_metric_low = IPV6RA_ROUTE_METRIC_LOW, - .ipv6_accept_ra_start_dhcp6_client = IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, + .ndisc = -1, + .ndisc_use_redirect = true, + .ndisc_use_dns = -1, + .ndisc_use_gateway = true, + .ndisc_use_captive_portal = true, + .ndisc_use_route_prefix = true, + .ndisc_use_autonomous_prefix = true, + .ndisc_use_onlink_prefix = true, + .ndisc_use_mtu = true, + .ndisc_use_hop_limit = true, + .ndisc_use_reachable_time = true, + .ndisc_use_retransmission_time = true, + .ndisc_use_domains = _USE_DOMAINS_INVALID, + .ndisc_route_table = RT_TABLE_MAIN, + .ndisc_route_metric_high = IPV6RA_ROUTE_METRIC_HIGH, + .ndisc_route_metric_medium = IPV6RA_ROUTE_METRIC_MEDIUM, + .ndisc_route_metric_low = IPV6RA_ROUTE_METRIC_LOW, + .ndisc_start_dhcp6_client = IPV6_ACCEPT_RA_START_DHCP6_CLIENT_YES, .can_termination = -1, @@ -630,7 +641,15 @@ int network_reload(Manager *manager) { ordered_hashmap_free_with_destructor(manager->networks, network_unref); manager->networks = new_networks; - return manager_build_dhcp_pd_subnet_ids(manager); + r = manager_build_dhcp_pd_subnet_ids(manager); + if (r < 0) + return r; + + r = manager_build_nexthop_ids(manager); + if (r < 0) + return r; + + return 0; failure: ordered_hashmap_free_with_destructor(new_networks, network_unref); @@ -767,16 +786,16 @@ static Network *network_free(Network *network) { /* static configs */ set_free_free(network->ipv6_proxy_ndp_addresses); - ordered_hashmap_free_with_destructor(network->addresses_by_section, address_free); - hashmap_free_with_destructor(network->routes_by_section, route_free); - hashmap_free_with_destructor(network->nexthops_by_section, nexthop_free); + ordered_hashmap_free(network->addresses_by_section); + hashmap_free(network->routes_by_section); + ordered_hashmap_free(network->nexthops_by_section); hashmap_free_with_destructor(network->bridge_fdb_entries_by_section, bridge_fdb_free); hashmap_free_with_destructor(network->bridge_mdb_entries_by_section, bridge_mdb_free); - ordered_hashmap_free_with_destructor(network->neighbors_by_section, neighbor_free); + ordered_hashmap_free(network->neighbors_by_section); hashmap_free_with_destructor(network->address_labels_by_section, address_label_free); hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); - hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free); + hashmap_free_with_destructor(network->pref64_prefixes_by_section, prefix64_free); hashmap_free_with_destructor(network->rules_by_section, routing_policy_rule_free); hashmap_free_with_destructor(network->dhcp_static_leases_by_section, dhcp_static_lease_free); ordered_hashmap_free_with_destructor(network->sr_iov_by_section, sr_iov_free); @@ -905,288 +924,6 @@ int config_parse_stacked_netdev( return 0; } -int config_parse_domains( - 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) { - - Network *n = ASSERT_PTR(userdata); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - n->search_domains = ordered_set_free(n->search_domains); - n->route_domains = ordered_set_free(n->route_domains); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL, *normalized = NULL; - const char *domain; - bool is_route; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract search or route domain, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - is_route = w[0] == '~'; - domain = is_route ? w + 1 : w; - - if (dns_name_is_root(domain) || streq(domain, "*")) { - /* If the root domain appears as is, or the special token "*" is found, we'll - * consider this as routing domain, unconditionally. */ - is_route = true; - domain = "."; /* make sure we don't allow empty strings, thus write the root - * domain as "." */ - } else { - r = dns_name_normalize(domain, 0, &normalized); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "'%s' is not a valid domain name, ignoring.", domain); - continue; - } - - domain = normalized; - - if (is_localhost(domain)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "'localhost' domain may not be configured as search or route domain, ignoring assignment: %s", - domain); - continue; - } - } - - OrderedSet **set = is_route ? &n->route_domains : &n->search_domains; - r = ordered_set_put_strdup(set, domain); - if (r == -EEXIST) - continue; - if (r < 0) - return log_oom(); - } -} - -int config_parse_timezone( - 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 **tz = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *tz = mfree(*tz); - return 0; - } - - r = verify_timezone(rvalue, LOG_WARNING); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Timezone is not valid, ignoring assignment: %s", rvalue); - return 0; - } - - return free_and_strdup_warn(tz, rvalue); -} - -int config_parse_dns( - 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) { - - Network *n = ASSERT_PTR(userdata); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - for (unsigned i = 0; i < n->n_dns; i++) - in_addr_full_free(n->dns[i]); - n->dns = mfree(n->dns); - n->n_dns = 0; - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_(in_addr_full_freep) struct in_addr_full *dns = NULL; - _cleanup_free_ char *w = NULL; - struct in_addr_full **m; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid syntax, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = in_addr_full_new_from_string(w, &dns); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse dns server address, ignoring: %s", w); - continue; - } - - if (IN_SET(dns->port, 53, 853)) - dns->port = 0; - - m = reallocarray(n->dns, n->n_dns + 1, sizeof(struct in_addr_full*)); - if (!m) - return log_oom(); - - m[n->n_dns++] = TAKE_PTR(dns); - n->dns = m; - } -} - -int config_parse_dnssec_negative_trust_anchors( - 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) { - - Set **nta = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *nta = set_free_free(*nta); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract negative trust anchor domain, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = dns_name_is_valid(w); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "%s is not a valid domain name, ignoring.", w); - continue; - } - - r = set_ensure_consume(nta, &dns_name_hash_ops, TAKE_PTR(w)); - if (r < 0) - return log_oom(); - } -} - -int config_parse_ntp( - 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 = ASSERT_PTR(data); - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - *l = strv_free(*l); - return 0; - } - - for (const char *p = rvalue;;) { - _cleanup_free_ char *w = NULL; - - r = extract_first_word(&p, &w, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to extract NTP server name, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - return 0; - - r = dns_name_is_valid_or_address(w); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "%s is not a valid domain name or IP address, ignoring.", w); - continue; - } - - if (strv_length(*l) > MAX_NTP_SERVERS) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.", - MAX_NTP_SERVERS, w); - return 0; - } - - r = strv_consume(l, TAKE_PTR(w)); - if (r < 0) - return log_oom(); - } -} - int config_parse_required_for_online( const char *unit, const char *filename, @@ -1200,8 +937,6 @@ int config_parse_required_for_online( void *userdata) { Network *network = ASSERT_PTR(userdata); - LinkOperationalStateRange range; - bool required = true; int r; assert(filename); @@ -1210,11 +945,11 @@ int config_parse_required_for_online( if (isempty(rvalue)) { network->required_for_online = -1; - network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT; + network->required_operstate_for_online = LINK_OPERSTATE_RANGE_INVALID; return 0; } - r = parse_operational_state_range(rvalue, &range); + r = parse_operational_state_range(rvalue, &network->required_operstate_for_online); if (r < 0) { r = parse_boolean(rvalue); if (r < 0) { @@ -1224,13 +959,12 @@ int config_parse_required_for_online( return 0; } - required = r; - range = LINK_OPERSTATE_RANGE_DEFAULT; + network->required_for_online = r; + network->required_operstate_for_online = LINK_OPERSTATE_RANGE_DEFAULT; + return 0; } - network->required_for_online = required; - network->required_operstate_for_online = range; - + network->required_for_online = true; return 0; } diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index 03131b7..92d367e 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -20,6 +20,7 @@ #include "networkd-dhcp-common.h" #include "networkd-dhcp4.h" #include "networkd-dhcp6.h" +#include "networkd-dns.h" #include "networkd-ipv6ll.h" #include "networkd-lldp-rx.h" #include "networkd-ndisc.h" @@ -112,6 +113,14 @@ struct Network { bool default_route_on_device; AddressFamily ip_masquerade; + /* Protocol independent settings */ + UseDomains use_domains; + + /* For backward compatibility, only applied to DHCPv4 and DHCPv6. */ + UseDomains compat_dhcp_use_domains; + int compat_dhcp_use_dns; + int compat_dhcp_use_ntp; + /* DHCP Client Support */ AddressFamily dhcp; struct in_addr dhcp_request_address; @@ -132,6 +141,7 @@ struct Network { usec_t dhcp_fallback_lease_lifetime_usec; uint32_t dhcp_route_mtu; uint16_t dhcp_client_port; + uint16_t dhcp_port; int dhcp_critical; int dhcp_ip_service_type; int dhcp_socket_priority; @@ -142,11 +152,9 @@ struct Network { int dhcp_broadcast; int dhcp_ipv6_only_mode; int dhcp_use_rapid_commit; - bool dhcp_use_dns; - bool dhcp_use_dns_set; + int dhcp_use_dns; bool dhcp_routes_to_dns; - bool dhcp_use_ntp; - bool dhcp_use_ntp_set; + int dhcp_use_ntp; bool dhcp_routes_to_ntp; bool dhcp_use_sip; bool dhcp_use_captive_portal; @@ -161,8 +169,7 @@ struct Network { bool dhcp_use_6rd; bool dhcp_send_release; bool dhcp_send_decline; - DHCPUseDomains dhcp_use_domains; - bool dhcp_use_domains_set; + UseDomains dhcp_use_domains; Set *dhcp_deny_listed_ip; Set *dhcp_allow_listed_ip; Set *dhcp_request_options; @@ -176,15 +183,12 @@ struct Network { bool dhcp6_use_pd_prefix; bool dhcp6_send_hostname; bool dhcp6_send_hostname_set; - bool dhcp6_use_dns; - bool dhcp6_use_dns_set; + int dhcp6_use_dns; bool dhcp6_use_hostname; - bool dhcp6_use_ntp; - bool dhcp6_use_ntp_set; + int dhcp6_use_ntp; bool dhcp6_use_captive_portal; bool dhcp6_use_rapid_commit; - DHCPUseDomains dhcp6_use_domains; - bool dhcp6_use_domains_set; + UseDomains dhcp6_use_domains; uint32_t dhcp6_iaid; bool dhcp6_iaid_set; bool dhcp6_iaid_set_explicitly; @@ -229,6 +233,7 @@ struct Network { char *dhcp_server_boot_filename; usec_t dhcp_server_ipv6_only_preferred_usec; bool dhcp_server_rapid_commit; + int dhcp_server_persist_leases; /* link-local addressing support */ AddressFamily link_local; @@ -241,6 +246,7 @@ struct Network { RADVPrefixDelegation router_prefix_delegation; usec_t router_lifetime_usec; uint8_t router_preference; + usec_t router_reachable_usec; usec_t router_retransmit_usec; uint8_t router_hop_limit; bool router_managed; @@ -289,10 +295,9 @@ struct Network { MulticastRouter multicast_router; /* Bridge VLAN */ - bool use_br_vlan; - uint16_t pvid; - uint32_t br_vid_bitmap[BRIDGE_VLAN_BITMAP_LEN]; - uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + uint16_t bridge_vlan_pvid; + uint32_t bridge_vlan_bitmap[BRIDGE_VLAN_BITMAP_LEN]; + uint32_t bridge_vlan_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN]; /* CAN support */ uint32_t can_bitrate; @@ -320,41 +325,45 @@ struct Network { int ipoib_umcast; /* sysctl settings */ - AddressFamily ip_forward; + int ip_forwarding[2]; int ipv4_accept_local; int ipv4_route_localnet; int ipv6_dad_transmits; uint8_t ipv6_hop_limit; + usec_t ipv6_retransmission_time; int proxy_arp; + int proxy_arp_pvlan; uint32_t ipv6_mtu; IPv6PrivacyExtensions ipv6_privacy_extensions; IPReversePathFilter ipv4_rp_filter; int ipv6_proxy_ndp; Set *ipv6_proxy_ndp_addresses; - /* IPv6 accept RA */ - int ipv6_accept_ra; - bool ipv6_accept_ra_use_dns; - bool ipv6_accept_ra_use_gateway; - bool ipv6_accept_ra_use_route_prefix; - bool ipv6_accept_ra_use_autonomous_prefix; - bool ipv6_accept_ra_use_onlink_prefix; - bool ipv6_accept_ra_use_mtu; - bool ipv6_accept_ra_use_hop_limit; - bool ipv6_accept_ra_use_icmp6_ratelimit; - bool ipv6_accept_ra_quickack; - bool ipv6_accept_ra_use_captive_portal; - bool ipv6_accept_ra_use_pref64; + /* NDisc support */ + int ndisc; + bool ndisc_use_redirect; + int ndisc_use_dns; + bool ndisc_use_gateway; + bool ndisc_use_route_prefix; + bool ndisc_use_autonomous_prefix; + bool ndisc_use_onlink_prefix; + bool ndisc_use_mtu; + bool ndisc_use_hop_limit; + bool ndisc_use_reachable_time; + bool ndisc_use_retransmission_time; + bool ndisc_quickack; + bool ndisc_use_captive_portal; + bool ndisc_use_pref64; bool active_slave; bool primary_slave; - DHCPUseDomains ipv6_accept_ra_use_domains; - IPv6AcceptRAStartDHCP6Client ipv6_accept_ra_start_dhcp6_client; - uint32_t ipv6_accept_ra_route_table; - bool ipv6_accept_ra_route_table_set; - uint32_t ipv6_accept_ra_route_metric_high; - uint32_t ipv6_accept_ra_route_metric_medium; - uint32_t ipv6_accept_ra_route_metric_low; - bool ipv6_accept_ra_route_metric_set; + UseDomains ndisc_use_domains; + IPv6AcceptRAStartDHCP6Client ndisc_start_dhcp6_client; + uint32_t ndisc_route_table; + bool ndisc_route_table_set; + uint32_t ndisc_route_metric_high; + uint32_t ndisc_route_metric_medium; + uint32_t ndisc_route_metric_low; + bool ndisc_route_metric_set; Set *ndisc_deny_listed_router; Set *ndisc_allow_listed_router; Set *ndisc_deny_listed_prefix; @@ -372,7 +381,7 @@ struct Network { OrderedHashmap *addresses_by_section; Hashmap *routes_by_section; - Hashmap *nexthops_by_section; + OrderedHashmap *nexthops_by_section; Hashmap *bridge_fdb_entries_by_section; Hashmap *bridge_mdb_entries_by_section; OrderedHashmap *neighbors_by_section; @@ -419,11 +428,6 @@ bool network_has_static_ipv6_configurations(Network *network); CONFIG_PARSER_PROTOTYPE(config_parse_stacked_netdev); CONFIG_PARSER_PROTOTYPE(config_parse_tunnel); -CONFIG_PARSER_PROTOTYPE(config_parse_domains); -CONFIG_PARSER_PROTOTYPE(config_parse_dns); -CONFIG_PARSER_PROTOTYPE(config_parse_timezone); -CONFIG_PARSER_PROTOTYPE(config_parse_dnssec_negative_trust_anchors); -CONFIG_PARSER_PROTOTYPE(config_parse_ntp); CONFIG_PARSER_PROTOTYPE(config_parse_required_for_online); CONFIG_PARSER_PROTOTYPE(config_parse_required_family_for_online); CONFIG_PARSER_PROTOTYPE(config_parse_keep_configuration); diff --git a/src/network/networkd-nexthop.c b/src/network/networkd-nexthop.c index e2ded28..1b44ef3 100644 --- a/src/network/networkd-nexthop.c +++ b/src/network/networkd-nexthop.c @@ -2,6 +2,7 @@ * Copyright © 2019 VMware, Inc. */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include @@ -12,53 +13,125 @@ #include "networkd-network.h" #include "networkd-nexthop.h" #include "networkd-queue.h" +#include "networkd-route.h" #include "networkd-route-util.h" #include "parse-util.h" #include "set.h" #include "stdio-util.h" #include "string-util.h" -NextHop *nexthop_free(NextHop *nexthop) { - if (!nexthop) - return NULL; +static void nexthop_detach_from_group_members(NextHop *nexthop) { + assert(nexthop); + assert(nexthop->manager); + assert(nexthop->id > 0); - if (nexthop->network) { - assert(nexthop->section); - hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + struct nexthop_grp *nhg; + HASHMAP_FOREACH(nhg, nexthop->group) { + NextHop *nh; + + if (nexthop_get_by_id(nexthop->manager, nhg->id, &nh) < 0) + continue; + + set_remove(nh->nexthops, UINT32_TO_PTR(nexthop->id)); } +} - config_section_free(nexthop->section); +static void nexthop_attach_to_group_members(NextHop *nexthop) { + int r; - if (nexthop->link) { - set_remove(nexthop->link->nexthops, nexthop); + assert(nexthop); + assert(nexthop->manager); + assert(nexthop->id > 0); + + struct nexthop_grp *nhg; + HASHMAP_FOREACH(nhg, nexthop->group) { + NextHop *nh; + + r = nexthop_get_by_id(nexthop->manager, nhg->id, &nh); + if (r < 0) { + if (nexthop->manager->manage_foreign_nexthops) + log_debug_errno(r, "Nexthop (id=%"PRIu32") has unknown group member (%"PRIu32"), ignoring.", + nexthop->id, nhg->id); + continue; + } + + r = set_ensure_put(&nh->nexthops, NULL, UINT32_TO_PTR(nexthop->id)); + if (r < 0) + log_debug_errno(r, "Failed to save nexthop ID (%"PRIu32") to group member (%"PRIu32"), ignoring: %m", + nexthop->id, nhg->id); + } +} + +static NextHop* nexthop_detach_impl(NextHop *nexthop) { + assert(nexthop); + assert(!nexthop->manager || !nexthop->network); - if (nexthop->link->manager && nexthop->id > 0) - hashmap_remove(nexthop->link->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + if (nexthop->network) { + assert(nexthop->section); + ordered_hashmap_remove(nexthop->network->nexthops_by_section, nexthop->section); + nexthop->network = NULL; + return nexthop; } if (nexthop->manager) { - set_remove(nexthop->manager->nexthops, nexthop); + assert(nexthop->id > 0); - if (nexthop->id > 0) - hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + nexthop_detach_from_group_members(nexthop); + + hashmap_remove(nexthop->manager->nexthops_by_id, UINT32_TO_PTR(nexthop->id)); + nexthop->manager = NULL; + return nexthop; } + return NULL; +} + +static void nexthop_detach(NextHop *nexthop) { + nexthop_unref(nexthop_detach_impl(nexthop)); +} + +static NextHop* nexthop_free(NextHop *nexthop) { + if (!nexthop) + return NULL; + + nexthop_detach_impl(nexthop); + + config_section_free(nexthop->section); hashmap_free_free(nexthop->group); + set_free(nexthop->nexthops); + set_free(nexthop->routes); return mfree(nexthop); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_free); +DEFINE_TRIVIAL_REF_UNREF_FUNC(NextHop, nexthop, nexthop_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(NextHop, nexthop_unref); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + nexthop_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + NextHop, + nexthop_detach); + +DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + nexthop_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + NextHop, + nexthop_detach); static int nexthop_new(NextHop **ret) { - _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; nexthop = new(NextHop, 1); if (!nexthop) return -ENOMEM; *nexthop = (NextHop) { - .family = AF_UNSPEC, + .n_ref = 1, .onlink = -1, }; @@ -69,7 +142,7 @@ static int nexthop_new(NextHop **ret) { static int nexthop_new_static(Network *network, const char *filename, unsigned section_line, NextHop **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(nexthop_freep) NextHop *nexthop = NULL; + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; int r; assert(network); @@ -81,7 +154,7 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s if (r < 0) return r; - nexthop = hashmap_get(network->nexthops_by_section, n); + nexthop = ordered_hashmap_get(network->nexthops_by_section, n); if (nexthop) { *ret = TAKE_PTR(nexthop); return 0; @@ -96,7 +169,7 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s nexthop->section = TAKE_PTR(n); nexthop->source = NETWORK_CONFIG_SOURCE_STATIC; - r = hashmap_ensure_put(&network->nexthops_by_section, &config_section_hash_ops, nexthop->section, nexthop); + r = ordered_hashmap_ensure_put(&network->nexthops_by_section, &nexthop_section_hash_ops, nexthop->section, nexthop); if (r < 0) return r; @@ -106,35 +179,54 @@ static int nexthop_new_static(Network *network, const char *filename, unsigned s static void nexthop_hash_func(const NextHop *nexthop, struct siphash *state) { assert(nexthop); + assert(state); - siphash24_compress(&nexthop->protocol, sizeof(nexthop->protocol), state); - siphash24_compress(&nexthop->id, sizeof(nexthop->id), state); - siphash24_compress(&nexthop->blackhole, sizeof(nexthop->blackhole), state); - siphash24_compress(&nexthop->family, sizeof(nexthop->family), state); + siphash24_compress_typesafe(nexthop->id, state); +} - switch (nexthop->family) { - case AF_INET: - case AF_INET6: - siphash24_compress(&nexthop->gw, FAMILY_ADDRESS_SIZE(nexthop->family), state); +static int nexthop_compare_func(const NextHop *a, const NextHop *b) { + assert(a); + assert(b); - break; - default: - /* treat any other address family as AF_UNSPEC */ - break; - } + return CMP(a->id, b->id); } -static int nexthop_compare_func(const NextHop *a, const NextHop *b) { +static int nexthop_compare_full(const NextHop *a, const NextHop *b) { int r; + assert(a); + assert(b); + + /* This compares detailed configs, except for ID and ifindex. */ + r = CMP(a->protocol, b->protocol); if (r != 0) return r; - r = CMP(a->id, b->id); + r = CMP(a->flags, b->flags); + if (r != 0) + return r; + + r = CMP(hashmap_size(a->group), hashmap_size(b->group)); if (r != 0) return r; + if (!hashmap_isempty(a->group)) { + struct nexthop_grp *ga; + + HASHMAP_FOREACH(ga, a->group) { + struct nexthop_grp *gb; + + gb = hashmap_get(b->group, UINT32_TO_PTR(ga->id)); + if (!gb) + return CMP(ga, gb); + + r = CMP(ga->weight, gb->weight); + if (r != 0) + return r; + } + } + r = CMP(a->blackhole, b->blackhole); if (r != 0) return r; @@ -143,31 +235,17 @@ static int nexthop_compare_func(const NextHop *a, const NextHop *b) { if (r != 0) return r; - if (IN_SET(a->family, AF_INET, AF_INET6)) - return memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (IN_SET(a->family, AF_INET, AF_INET6)) { + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + } return 0; } -DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( - nexthop_hash_ops, - NextHop, - nexthop_hash_func, - nexthop_compare_func, - nexthop_free); - -static bool nexthop_equal(const NextHop *a, const NextHop *b) { - if (a == b) - return true; - - if (!a || !b) - return false; - - return nexthop_compare_func(a, b) == 0; -} - static int nexthop_dup(const NextHop *src, NextHop **ret) { - _cleanup_(nexthop_freep) NextHop *dest = NULL; + _cleanup_(nexthop_unrefp) NextHop *dest = NULL; struct nexthop_grp *nhg; int r; @@ -178,9 +256,9 @@ static int nexthop_dup(const NextHop *src, NextHop **ret) { if (!dest) return -ENOMEM; - /* unset all pointers */ + /* clear the reference counter and all pointers */ + dest->n_ref = 1; dest->manager = NULL; - dest->link = NULL; dest->network = NULL; dest->section = NULL; dest->group = NULL; @@ -203,7 +281,12 @@ static int nexthop_dup(const NextHop *src, NextHop **ret) { return 0; } -int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) { +static bool nexthop_bound_to_link(const NextHop *nexthop) { + assert(nexthop); + return !nexthop->blackhole && hashmap_isempty(nexthop->group); +} + +int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret) { NextHop *nh; assert(manager); @@ -220,143 +303,175 @@ int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret) { return 0; } -static bool nexthop_owned_by_link(const NextHop *nexthop) { - return !nexthop->blackhole && hashmap_isempty(nexthop->group); -} - -static int nexthop_get(Manager *manager, Link *link, NextHop *in, NextHop **ret) { +static int nexthop_get(Link *link, const NextHop *in, NextHop **ret) { NextHop *nexthop; - Set *nexthops; + int ifindex; + assert(link); + assert(link->manager); assert(in); - if (nexthop_owned_by_link(in)) { - if (!link) - return -ENOENT; + if (in->id > 0) + return nexthop_get_by_id(link->manager, in->id, ret); - nexthops = link->nexthops; - } else { - if (!manager) - return -ENOENT; + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(link->manager->manage_foreign_nexthops); - nexthops = manager->nexthops; - } + ifindex = nexthop_bound_to_link(in) ? link->ifindex : 0; + + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { + if (nexthop->ifindex != ifindex) + continue; + if (nexthop_compare_full(nexthop, in) != 0) + continue; + + /* Even if the configuration matches, it may be configured with another [NextHop] section + * that has an explicit ID. If so, the assigned nexthop is not the one we are looking for. */ + if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id))) + continue; - nexthop = set_get(nexthops, in); - if (nexthop) { if (ret) *ret = nexthop; return 0; } - if (in->id > 0) + return -ENOENT; +} + +static int nexthop_get_request_by_id(Manager *manager, uint32_t id, Request **ret) { + Request *req; + + assert(manager); + + if (id == 0) + return -EINVAL; + + req = ordered_set_get( + manager->request_queue, + &(Request) { + .type = REQUEST_TYPE_NEXTHOP, + .userdata = (void*) &(const NextHop) { .id = id }, + .hash_func = (hash_func_t) nexthop_hash_func, + .compare_func = (compare_func_t) nexthop_compare_func, + }); + if (!req) return -ENOENT; - /* Also find nexthop configured without ID. */ - SET_FOREACH(nexthop, nexthops) { - uint32_t id; - bool found; + if (ret) + *ret = req; + return 0; +} - id = nexthop->id; - nexthop->id = 0; - found = nexthop_equal(nexthop, in); - nexthop->id = id; +static int nexthop_get_request(Link *link, const NextHop *in, Request **ret) { + Request *req; + int ifindex; + + assert(link); + assert(link->manager); + assert(in); + + if (in->id > 0) + return nexthop_get_request_by_id(link->manager, in->id, ret); - if (!found) + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(link->manager->manage_foreign_nexthops); + + ifindex = nexthop_bound_to_link(in) ? link->ifindex : 0; + + ORDERED_SET_FOREACH(req, link->manager->request_queue) { + if (req->type != REQUEST_TYPE_NEXTHOP) + continue; + + NextHop *nexthop = ASSERT_PTR(req->userdata); + if (nexthop->ifindex != ifindex) + continue; + if (nexthop_compare_full(nexthop, in) != 0) + continue; + + /* Even if the configuration matches, it may be requested by another [NextHop] section + * that has an explicit ID. If so, the request is not the one we are looking for. */ + if (set_contains(link->manager->nexthop_ids, UINT32_TO_PTR(nexthop->id))) continue; if (ret) - *ret = nexthop; + *ret = req; return 0; } return -ENOENT; } -static int nexthop_add(Manager *manager, Link *link, NextHop *nexthop) { +static int nexthop_add_new(Manager *manager, uint32_t id, NextHop **ret) { + _cleanup_(nexthop_unrefp) NextHop *nexthop = NULL; int r; - assert(nexthop); - assert(nexthop->id > 0); - - if (nexthop_owned_by_link(nexthop)) { - assert(link); + assert(manager); + assert(id > 0); - r = set_ensure_put(&link->nexthops, &nexthop_hash_ops, nexthop); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + r = nexthop_new(&nexthop); + if (r < 0) + return r; - nexthop->link = link; + nexthop->id = id; - manager = link->manager; - } else { - assert(manager); + r = hashmap_ensure_put(&manager->nexthops_by_id, &nexthop_hash_ops, UINT32_TO_PTR(nexthop->id), nexthop); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; - r = set_ensure_put(&manager->nexthops, &nexthop_hash_ops, nexthop); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + nexthop->manager = manager; - nexthop->manager = manager; - } + if (ret) + *ret = nexthop; - return hashmap_ensure_put(&manager->nexthops_by_id, NULL, UINT32_TO_PTR(nexthop->id), nexthop); + TAKE_PTR(nexthop); + return 0; } static int nexthop_acquire_id(Manager *manager, NextHop *nexthop) { - _cleanup_set_free_ Set *ids = NULL; - Network *network; - uint32_t id; - int r; - assert(manager); assert(nexthop); if (nexthop->id > 0) return 0; - /* Find the lowest unused ID. */ - - ORDERED_HASHMAP_FOREACH(network, manager->networks) { - NextHop *tmp; - - HASHMAP_FOREACH(tmp, network->nexthops_by_section) { - if (tmp->id == 0) - continue; + /* If ManageForeignNextHops=no, nexthop with id == 0 should be already filtered by + * nexthop_section_verify(). */ + assert(manager->manage_foreign_nexthops); - r = set_ensure_put(&ids, NULL, UINT32_TO_PTR(tmp->id)); - if (r < 0) - return r; - } - } + /* Find the lowest unused ID. */ - for (id = 1; id < UINT32_MAX; id++) { - if (manager_get_nexthop_by_id(manager, id, NULL) >= 0) + for (uint32_t id = 1; id < UINT32_MAX; id++) { + if (nexthop_get_by_id(manager, id, NULL) >= 0) continue; - if (set_contains(ids, UINT32_TO_PTR(id))) + if (nexthop_get_request_by_id(manager, id, NULL) >= 0) continue; - break; + if (set_contains(manager->nexthop_ids, UINT32_TO_PTR(id))) + continue; + + nexthop->id = id; + return 0; } - nexthop->id = id; - return 0; + return -EBUSY; } -static void log_nexthop_debug(const NextHop *nexthop, const char *str, const Link *link) { +static void log_nexthop_debug(const NextHop *nexthop, const char *str, Manager *manager) { _cleanup_free_ char *state = NULL, *group = NULL, *flags = NULL; struct nexthop_grp *nhg; + Link *link = NULL; assert(nexthop); assert(str); - - /* link may be NULL. */ + assert(manager); if (!DEBUG_LOGGING) return; + (void) link_get_by_index(manager, nexthop->ifindex, &link); (void) network_config_state_to_string_alloc(nexthop->state, &state); (void) route_flags_to_string_alloc(nexthop->flags, &flags); @@ -370,42 +485,81 @@ static void log_nexthop_debug(const NextHop *nexthop, const char *str, const Lin yes_no(nexthop->blackhole), strna(group), strna(flags)); } -static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int nexthop_remove_dependents(NextHop *nexthop, Manager *manager) { + int r = 0; + + assert(nexthop); + assert(manager); + + /* If a nexthop is removed, the kernel silently removes nexthops and routes that depend on the + * removed nexthop. Let's remove them for safety (though, they are already removed in the kernel, + * hence that should fail), and forget them. */ + + void *id; + SET_FOREACH(id, nexthop->nexthops) { + NextHop *nh; + + if (nexthop_get_by_id(manager, PTR_TO_UINT32(id), &nh) < 0) + continue; + + RET_GATHER(r, nexthop_remove(nh, manager)); + } + + Route *route; + SET_FOREACH(route, nexthop->routes) + RET_GATHER(r, route_remove(route, manager)); + + return r; +} + +static int nexthop_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; assert(m); + assert(rreq); - /* link may be NULL. */ - - if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 1; + Manager *manager = ASSERT_PTR(rreq->manager); + NextHop *nexthop = ASSERT_PTR(rreq->userdata); r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ENOENT) - log_link_message_warning_errno(link, m, r, "Could not drop nexthop, ignoring"); + if (r < 0) { + log_message_full_errno(m, + (r == -ENOENT || !nexthop->manager) ? LOG_DEBUG : LOG_WARNING, + r, "Could not drop nexthop, ignoring"); + + (void) nexthop_remove_dependents(nexthop, manager); + + if (nexthop->manager) { + /* If the nexthop cannot be removed, then assume the nexthop is already removed. */ + log_nexthop_debug(nexthop, "Forgetting", manager); + + Request *req; + if (nexthop_get_request_by_id(manager, nexthop->id, &req) >= 0) + nexthop_enter_removed(req->userdata); + + nexthop_detach(nexthop); + } + } return 1; } -static int nexthop_remove(NextHop *nexthop) { +int nexthop_remove(NextHop *nexthop, Manager *manager) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - Manager *manager; - Link *link; + Link *link = NULL; int r; assert(nexthop); - assert(nexthop->manager || (nexthop->link && nexthop->link->manager)); + assert(nexthop->id > 0); + assert(manager); - /* link may be NULL. */ - link = nexthop->link; - manager = nexthop->manager ?: nexthop->link->manager; + /* If the nexthop is remembered, then use the remembered object. */ + (void) nexthop_get_by_id(manager, PTR_TO_UINT32(nexthop->id), &nexthop); - if (nexthop->id == 0) { - log_link_debug(link, "Cannot remove nexthop without valid ID, ignoring."); - return 0; - } + /* link may be NULL. */ + (void) link_get_by_index(manager, nexthop->ifindex, &link); - log_nexthop_debug(nexthop, "Removing", link); + log_nexthop_debug(nexthop, "Removing", manager); r = sd_rtnl_message_new_nexthop(manager->rtnl, &m, RTM_DELNEXTHOP, AF_UNSPEC, RTPROT_UNSPEC); if (r < 0) @@ -415,12 +569,9 @@ static int nexthop_remove(NextHop *nexthop) { if (r < 0) return log_link_error_errno(link, r, "Could not append NHA_ID attribute: %m"); - r = netlink_call_async(manager->rtnl, NULL, m, nexthop_remove_handler, - link ? link_netlink_destroy_callback : NULL, link); + r = manager_remove_request_add(manager, nexthop, nexthop, manager->rtnl, m, nexthop_remove_handler); if (r < 0) - return log_link_error_errno(link, r, "Could not send rtnetlink message: %m"); - - link_ref(link); /* link may be NULL, link_ref() is OK with that */ + return log_link_error_errno(link, r, "Could not queue rtnetlink message: %m"); nexthop_enter_removing(nexthop); return 0; @@ -431,6 +582,7 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { int r; assert(nexthop); + assert(nexthop->id > 0); assert(IN_SET(nexthop->family, AF_UNSPEC, AF_INET, AF_INET6)); assert(link); assert(link->manager); @@ -438,17 +590,15 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { assert(link->ifindex > 0); assert(req); - log_nexthop_debug(nexthop, "Configuring", link); + log_nexthop_debug(nexthop, "Configuring", link->manager); r = sd_rtnl_message_new_nexthop(link->manager->rtnl, &m, RTM_NEWNEXTHOP, nexthop->family, nexthop->protocol); if (r < 0) return r; - if (nexthop->id > 0) { - r = sd_netlink_message_append_u32(m, NHA_ID, nexthop->id); - if (r < 0) - return r; - } + r = sd_netlink_message_append_u32(m, NHA_ID, nexthop->id); + if (r < 0) + return r; if (!hashmap_isempty(nexthop->group)) { _cleanup_free_ struct nexthop_grp *group = NULL; @@ -471,7 +621,9 @@ static int nexthop_configure(NextHop *nexthop, Link *link, Request *req) { if (r < 0) return r; } else { - r = sd_netlink_message_append_u32(m, NHA_OIF, link->ifindex); + assert(nexthop->ifindex == link->ifindex); + + r = sd_netlink_message_append_u32(m, NHA_OIF, nexthop->ifindex); if (r < 0) return r; @@ -511,16 +663,49 @@ static int static_nexthop_handler(sd_netlink *rtnl, sd_netlink_message *m, Reque return 1; } +int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret) { + NextHop *nexthop; + + assert(manager); + + if (id == 0) + return -EINVAL; + + if (nexthop_get_request_by_id(manager, id, NULL) >= 0) + goto not_ready; + + if (nexthop_get_by_id(manager, id, &nexthop) < 0) + goto not_ready; + + if (!nexthop_exists(nexthop)) + goto not_ready; + + if (ret) + *ret = nexthop; + + return true; + +not_ready: + if (ret) + *ret = NULL; + + return false; +} + static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { struct nexthop_grp *nhg; + int r; assert(link); assert(nexthop); + assert(nexthop->id > 0); if (!link_is_ready_to_configure(link, false)) return false; - if (nexthop_owned_by_link(nexthop)) { + if (nexthop_bound_to_link(nexthop)) { + assert(nexthop->ifindex == link->ifindex); + /* TODO: fdb nexthop does not require IFF_UP. The conditions below needs to be updated * when fdb nexthop support is added. See rtm_to_nh_config() in net/ipv4/nexthop.c of * kernel. */ @@ -532,34 +717,21 @@ static bool nexthop_is_ready_to_configure(Link *link, const NextHop *nexthop) { /* All group members must be configured first. */ HASHMAP_FOREACH(nhg, nexthop->group) { - NextHop *g; - - if (manager_get_nexthop_by_id(link->manager, nhg->id, &g) < 0) - return false; - - if (!nexthop_exists(g)) - return false; - } - - if (nexthop->id == 0) { - Request *req; - - ORDERED_SET_FOREACH(req, link->manager->request_queue) { - if (req->type != REQUEST_TYPE_NEXTHOP) - continue; - if (((NextHop*) req->userdata)->id != 0) - return false; /* first configure nexthop with id. */ - } + r = nexthop_is_ready(link->manager, nhg->id, NULL); + if (r <= 0) + return r; } return gateway_is_ready(link, FLAGS_SET(nexthop->flags, RTNH_F_ONLINK), nexthop->family, &nexthop->gw); } static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { + NextHop *existing; int r; assert(req); assert(link); + assert(link->manager); assert(nexthop); if (!nexthop_is_ready_to_configure(link, nexthop)) @@ -570,39 +742,49 @@ static int nexthop_process_request(Request *req, Link *link, NextHop *nexthop) { return log_link_warning_errno(link, r, "Failed to configure nexthop"); nexthop_enter_configuring(nexthop); + if (nexthop_get_by_id(link->manager, nexthop->id, &existing) >= 0) + nexthop_enter_configuring(existing); + return 1; } -static int link_request_nexthop(Link *link, NextHop *nexthop) { - NextHop *existing; +static int link_request_nexthop(Link *link, const NextHop *nexthop) { + _cleanup_(nexthop_unrefp) NextHop *tmp = NULL; + NextHop *existing = NULL; int r; assert(link); + assert(link->manager); assert(nexthop); assert(nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN); - if (nexthop_get(link->manager, link, nexthop, &existing) < 0) { - _cleanup_(nexthop_freep) NextHop *tmp = NULL; + if (nexthop_get_request(link, nexthop, NULL) >= 0) + return 0; /* already requested, skipping. */ - r = nexthop_dup(nexthop, &tmp); - if (r < 0) - return r; + r = nexthop_dup(nexthop, &tmp); + if (r < 0) + return r; + if (nexthop_get(link, nexthop, &existing) < 0) { r = nexthop_acquire_id(link->manager, tmp); if (r < 0) return r; + } else { + /* Copy ID */ + assert(tmp->id == 0 || tmp->id == existing->id); + tmp->id = existing->id; - r = nexthop_add(link->manager, link, tmp); - if (r < 0) - return r; + /* Copy state for logging below. */ + tmp->state = existing->state; + } - existing = TAKE_PTR(tmp); - } else - existing->source = nexthop->source; + if (nexthop_bound_to_link(tmp)) + tmp->ifindex = link->ifindex; - log_nexthop_debug(existing, "Requesting", link); + log_nexthop_debug(tmp, "Requesting", link->manager); r = link_queue_request_safe(link, REQUEST_TYPE_NEXTHOP, - existing, NULL, + tmp, + nexthop_unref, nexthop_hash_func, nexthop_compare_func, nexthop_process_request, @@ -612,7 +794,11 @@ static int link_request_nexthop(Link *link, NextHop *nexthop) { if (r <= 0) return r; - nexthop_enter_requesting(existing); + nexthop_enter_requesting(tmp); + if (existing) + nexthop_enter_requesting(existing); + + TAKE_PTR(tmp); return 1; } @@ -625,7 +811,7 @@ int link_request_static_nexthops(Link *link, bool only_ipv4) { link->static_nexthops_configured = false; - HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { + ORDERED_HASHMAP_FOREACH(nh, link->network->nexthops_by_section) { if (only_ipv4 && nh->family != AF_INET) continue; @@ -645,162 +831,182 @@ int link_request_static_nexthops(Link *link, bool only_ipv4) { return 0; } -static void manager_mark_nexthops(Manager *manager, bool foreign, const Link *except) { +static bool nexthop_can_update(const NextHop *assigned_nexthop, const NextHop *requested_nexthop) { + assert(assigned_nexthop); + assert(assigned_nexthop->manager); + assert(requested_nexthop); + assert(requested_nexthop->network); + + /* A group nexthop cannot be replaced with a non-group nexthop, and vice versa. + * See replace_nexthop_grp() and replace_nexthop_single() in net/ipv4/nexthop.c of the kernel. */ + if (hashmap_isempty(assigned_nexthop->group) != hashmap_isempty(requested_nexthop->group)) + return false; + + /* There are several more conditions if we can replace a group nexthop, e.g. hash threshold and + * resilience. But, currently we do not support to modify that. Let's add checks for them in the + * future when we support to configure them.*/ + + /* When a nexthop is replaced with a blackhole nexthop, and a group nexthop has multiple nexthops + * including this nexthop, then the kernel refuses to replace the existing nexthop. + * So, here, for simplicity, let's unconditionally refuse to replace a non-blackhole nexthop with + * a blackhole nexthop. See replace_nexthop() in net/ipv4/nexthop.c of the kernel. */ + if (!assigned_nexthop->blackhole && requested_nexthop->blackhole) + return false; + + return true; +} + +static void link_mark_nexthops(Link *link, bool foreign) { NextHop *nexthop; - Link *link; + Link *other; - assert(manager); + assert(link); + assert(link->manager); /* First, mark all nexthops. */ - SET_FOREACH(nexthop, manager->nexthops) { + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { /* do not touch nexthop created by the kernel */ if (nexthop->protocol == RTPROT_KERNEL) continue; /* When 'foreign' is true, mark only foreign nexthops, and vice versa. */ - if (foreign != (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN)) + if (nexthop->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) continue; /* Ignore nexthops not assigned yet or already removed. */ if (!nexthop_exists(nexthop)) continue; + /* Ignore nexthops bound to other links. */ + if (nexthop->ifindex > 0 && nexthop->ifindex != link->ifindex) + continue; + nexthop_mark(nexthop); } /* Then, unmark all nexthops requested by active links. */ - HASHMAP_FOREACH(link, manager->links_by_index) { - if (link == except) + HASHMAP_FOREACH(other, link->manager->links_by_index) { + if (!foreign && other == link) continue; - if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + if (!IN_SET(other->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) continue; - HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) { + ORDERED_HASHMAP_FOREACH(nexthop, other->network->nexthops_by_section) { NextHop *existing; - if (nexthop_get(manager, NULL, nexthop, &existing) >= 0) - nexthop_unmark(existing); + if (nexthop_get(other, nexthop, &existing) < 0) + continue; + + if (!nexthop_can_update(existing, nexthop)) + continue; + + /* Found matching static configuration. Keep the existing nexthop. */ + nexthop_unmark(existing); } } } -static int manager_drop_marked_nexthops(Manager *manager) { +int link_drop_nexthops(Link *link, bool foreign) { NextHop *nexthop; int r = 0; - assert(manager); + assert(link); + assert(link->manager); - SET_FOREACH(nexthop, manager->nexthops) { + link_mark_nexthops(link, foreign); + + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { if (!nexthop_is_marked(nexthop)) continue; - RET_GATHER(r, nexthop_remove(nexthop)); + RET_GATHER(r, nexthop_remove(nexthop, link->manager)); } return r; } -int link_drop_foreign_nexthops(Link *link) { +void link_foreignize_nexthops(Link *link) { NextHop *nexthop; - int r = 0; assert(link); assert(link->manager); - assert(link->network); - /* First, mark all nexthops. */ - SET_FOREACH(nexthop, link->nexthops) { - /* do not touch nexthop created by the kernel */ - if (nexthop->protocol == RTPROT_KERNEL) - continue; - - /* Do not remove nexthops we configured. */ - if (nexthop->source != NETWORK_CONFIG_SOURCE_FOREIGN) - continue; + link_mark_nexthops(link, /* foreign = */ false); - /* Ignore nexthops not assigned yet or already removed. */ - if (!nexthop_exists(nexthop)) + HASHMAP_FOREACH(nexthop, link->manager->nexthops_by_id) { + if (!nexthop_is_marked(nexthop)) continue; - nexthop_mark(nexthop); + nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; } +} - /* Then, unmark all nexthops requested by active links. */ - HASHMAP_FOREACH(nexthop, link->network->nexthops_by_section) { - NextHop *existing; - - if (nexthop_get(NULL, link, nexthop, &existing) >= 0) - nexthop_unmark(existing); - } +static int nexthop_update_group(NextHop *nexthop, sd_netlink_message *message) { + _cleanup_hashmap_free_free_ Hashmap *h = NULL; + _cleanup_free_ struct nexthop_grp *group = NULL; + size_t size = 0, n_group; + int r; - /* Finally, remove all marked rules. */ - SET_FOREACH(nexthop, link->nexthops) { - if (!nexthop_is_marked(nexthop)) - continue; + assert(nexthop); + assert(message); - RET_GATHER(r, nexthop_remove(nexthop)); - } + r = sd_netlink_message_read_data(message, NHA_GROUP, &size, (void**) &group); + if (r < 0 && r != -ENODATA) + return log_debug_errno(r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m"); - manager_mark_nexthops(link->manager, /* foreign = */ true, NULL); + nexthop_detach_from_group_members(nexthop); - return RET_GATHER(r, manager_drop_marked_nexthops(link->manager)); -} + if (size % sizeof(struct nexthop_grp) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received nexthop message with invalid nexthop group size, ignoring."); -int link_drop_managed_nexthops(Link *link) { - NextHop *nexthop; - int r = 0; + if ((uintptr_t) group % alignof(struct nexthop_grp) != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "rtnl: received nexthop message with invalid alignment, ignoring."); - assert(link); - assert(link->manager); + n_group = size / sizeof(struct nexthop_grp); + for (size_t i = 0; i < n_group; i++) { + _cleanup_free_ struct nexthop_grp *nhg = NULL; - SET_FOREACH(nexthop, link->nexthops) { - /* do not touch nexthop created by the kernel */ - if (nexthop->protocol == RTPROT_KERNEL) + if (group[i].id == 0) { + log_debug("rtnl: received nexthop message with invalid ID in group, ignoring."); continue; + } - /* Do not touch addresses managed by kernel or other tools. */ - if (nexthop->source == NETWORK_CONFIG_SOURCE_FOREIGN) + if (group[i].weight > 254) { + log_debug("rtnl: received nexthop message with invalid weight in group, ignoring."); continue; + } - /* Ignore nexthops not assigned yet or already removing. */ - if (!nexthop_exists(nexthop)) - continue; + nhg = newdup(struct nexthop_grp, group + i, 1); + if (!nhg) + return log_oom(); - RET_GATHER(r, nexthop_remove(nexthop)); + r = hashmap_ensure_put(&h, NULL, UINT32_TO_PTR(nhg->id), nhg); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_debug_errno(r, "Failed to store nexthop group, ignoring: %m"); + continue; + } + if (r > 0) + TAKE_PTR(nhg); } - manager_mark_nexthops(link->manager, /* foreign = */ false, link); - - return RET_GATHER(r, manager_drop_marked_nexthops(link->manager)); -} - -void link_foreignize_nexthops(Link *link) { - NextHop *nexthop; - - assert(link); - - SET_FOREACH(nexthop, link->nexthops) - nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; - - manager_mark_nexthops(link->manager, /* foreign = */ false, link); - - SET_FOREACH(nexthop, link->manager->nexthops) { - if (!nexthop_is_marked(nexthop)) - continue; + hashmap_free_free(nexthop->group); + nexthop->group = TAKE_PTR(h); - nexthop->source = NETWORK_CONFIG_SOURCE_FOREIGN; - } + nexthop_attach_to_group_members(nexthop); + return 0; } int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(nexthop_freep) NextHop *tmp = NULL; - _cleanup_free_ void *raw_group = NULL; - NextHop *nexthop = NULL; - size_t raw_group_size; - uint32_t ifindex; uint16_t type; - Link *link = NULL; + uint32_t id, ifindex; + NextHop *nexthop = NULL; + Request *req = NULL; + bool is_new = false; int r; assert(rtnl); @@ -824,163 +1030,102 @@ int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, return 0; } - r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); - if (r < 0 && r != -ENODATA) { - log_warning_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + r = sd_netlink_message_read_u32(message, NHA_ID, &id); + if (r == -ENODATA) { + log_warning_errno(r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m"); return 0; - } else if (r >= 0) { - if (ifindex <= 0) { - log_warning("rtnl: received nexthop message with invalid ifindex %"PRIu32", ignoring.", ifindex); - return 0; - } - - r = link_get_by_index(m, ifindex, &link); - if (r < 0) { - if (!m->enumerating) - log_warning("rtnl: received nexthop message for link (%"PRIu32") we do not know about, ignoring", ifindex); - return 0; - } - } - - r = nexthop_new(&tmp); - if (r < 0) - return log_oom(); - - r = sd_rtnl_message_get_family(message, &tmp->family); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop family, ignoring: %m"); + } else if (r < 0) { + log_warning_errno(r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); return 0; - } else if (!IN_SET(tmp->family, AF_UNSPEC, AF_INET, AF_INET6)) { - log_link_debug(link, "rtnl: received nexthop message with invalid family %d, ignoring.", tmp->family); + } else if (id == 0) { + log_warning("rtnl: received nexthop message with invalid nexthop ID, ignoring: %m"); return 0; } - r = sd_rtnl_message_nexthop_get_protocol(message, &tmp->protocol); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop protocol, ignoring: %m"); - return 0; - } + (void) nexthop_get_by_id(m, id, &nexthop); + (void) nexthop_get_request_by_id(m, id, &req); - r = sd_rtnl_message_nexthop_get_flags(message, &tmp->flags); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get nexthop flags, ignoring: %m"); - return 0; - } + if (type == RTM_DELNEXTHOP) { + if (nexthop) { + nexthop_enter_removed(nexthop); + log_nexthop_debug(nexthop, "Forgetting removed", m); + (void) nexthop_remove_dependents(nexthop, m); + nexthop_detach(nexthop); + } else + log_nexthop_debug(&(const NextHop) { .id = id }, "Kernel removed unknown", m); + + if (req) + nexthop_enter_removed(req->userdata); - r = sd_netlink_message_read_data(message, NHA_GROUP, &raw_group_size, &raw_group); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_GROUP attribute, ignoring: %m"); return 0; - } else if (r >= 0) { - struct nexthop_grp *group = raw_group; - size_t n_group; + } - if (raw_group_size == 0 || raw_group_size % sizeof(struct nexthop_grp) != 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid nexthop group size, ignoring."); + /* If we did not know the nexthop, then save it. */ + if (!nexthop) { + r = nexthop_add_new(m, id, &nexthop); + if (r < 0) { + log_warning_errno(r, "Failed to add received nexthop, ignoring: %m"); return 0; } - assert((uintptr_t) group % alignof(struct nexthop_grp) == 0); + is_new = true; + } - n_group = raw_group_size / sizeof(struct nexthop_grp); - for (size_t i = 0; i < n_group; i++) { - _cleanup_free_ struct nexthop_grp *nhg = NULL; + /* Also update information that cannot be obtained through netlink notification. */ + if (req && req->waiting_reply) { + NextHop *n = ASSERT_PTR(req->userdata); - if (group[i].id == 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid ID in group, ignoring."); - return 0; - } - if (group[i].weight > 254) { - log_link_warning(link, "rtnl: received nexthop message with invalid weight in group, ignoring."); - return 0; - } + nexthop->source = n->source; + } - nhg = newdup(struct nexthop_grp, group + i, 1); - if (!nhg) - return log_oom(); + r = sd_rtnl_message_get_family(message, &nexthop->family); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop family, ignoring: %m"); - r = hashmap_ensure_put(&tmp->group, NULL, UINT32_TO_PTR(nhg->id), nhg); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to store nexthop group, ignoring: %m"); - return 0; - } - if (r > 0) - TAKE_PTR(nhg); - } - } + r = sd_rtnl_message_nexthop_get_protocol(message, &nexthop->protocol); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop protocol, ignoring: %m"); - if (tmp->family != AF_UNSPEC) { - r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, tmp->family, &tmp->gw); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); - return 0; - } + r = sd_rtnl_message_nexthop_get_flags(message, &nexthop->flags); + if (r < 0) + log_debug_errno(r, "rtnl: could not get nexthop flags, ignoring: %m"); + + (void) nexthop_update_group(nexthop, message); + + if (nexthop->family != AF_UNSPEC) { + r = netlink_message_read_in_addr_union(message, NHA_GATEWAY, nexthop->family, &nexthop->gw); + if (r == -ENODATA) + nexthop->gw = IN_ADDR_NULL; + else if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_GATEWAY attribute, ignoring: %m"); } r = sd_netlink_message_has_flag(message, NHA_BLACKHOLE); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m"); - return 0; - } - tmp->blackhole = r; + if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_BLACKHOLE attribute, ignoring: %m"); + else + nexthop->blackhole = r; - r = sd_netlink_message_read_u32(message, NHA_ID, &tmp->id); - if (r == -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received nexthop message without NHA_ID attribute, ignoring: %m"); - return 0; - } else if (r < 0) { - log_link_warning_errno(link, r, "rtnl: could not get NHA_ID attribute, ignoring: %m"); - return 0; - } else if (tmp->id == 0) { - log_link_warning(link, "rtnl: received nexthop message with invalid nexthop ID, ignoring: %m"); - return 0; - } + r = sd_netlink_message_read_u32(message, NHA_OIF, &ifindex); + if (r == -ENODATA) + nexthop->ifindex = 0; + else if (r < 0) + log_debug_errno(r, "rtnl: could not get NHA_OIF attribute, ignoring: %m"); + else if (ifindex > INT32_MAX) + log_debug_errno(r, "rtnl: received invalid NHA_OIF attribute, ignoring: %m"); + else + nexthop->ifindex = (int) ifindex; /* All blackhole or group nexthops are managed by Manager. Note that the linux kernel does not * set NHA_OID attribute when NHA_BLACKHOLE or NHA_GROUP is set. Just for safety. */ - if (!nexthop_owned_by_link(tmp)) - link = NULL; - - (void) nexthop_get(m, link, tmp, &nexthop); - - switch (type) { - case RTM_NEWNEXTHOP: - if (nexthop) { - nexthop->flags = tmp->flags; - nexthop_enter_configured(nexthop); - log_nexthop_debug(tmp, "Received remembered", link); - } else { - nexthop_enter_configured(tmp); - log_nexthop_debug(tmp, "Remembering", link); - - r = nexthop_add(m, link, tmp); - if (r < 0) { - log_link_warning_errno(link, r, "Could not remember foreign nexthop, ignoring: %m"); - return 0; - } - - TAKE_PTR(tmp); - } + if (!nexthop_bound_to_link(nexthop)) + nexthop->ifindex = 0; - break; - case RTM_DELNEXTHOP: - if (nexthop) { - nexthop_enter_removed(nexthop); - if (nexthop->state == 0) { - log_nexthop_debug(nexthop, "Forgetting", link); - nexthop_free(nexthop); - } else - log_nexthop_debug(nexthop, "Removed", link); - } else - log_nexthop_debug(tmp, "Kernel removed unknown", link); - break; - - default: - assert_not_reached(); - } + nexthop_enter_configured(nexthop); + if (req) + nexthop_enter_configured(req->userdata); + log_nexthop_debug(nexthop, is_new ? "Remembering" : "Received remembered", m); return 1; } @@ -988,6 +1133,13 @@ static int nexthop_section_verify(NextHop *nh) { if (section_is_invalid(nh->section)) return -EINVAL; + if (!nh->network->manager->manage_foreign_nexthops && nh->id == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: [NextHop] section without specifying Id= is not supported " + "if ManageForeignNextHops=no is set in networkd.conf. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + if (!hashmap_isempty(nh->group)) { if (in_addr_is_set(nh->family, &nh->gw)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1001,20 +1153,34 @@ static int nexthop_section_verify(NextHop *nh) { "Ignoring [NextHop] section from line %u.", nh->section->filename, nh->section->line); - if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) + if (nh->blackhole) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: nexthop group cannot be a blackhole. " "Ignoring [NextHop] section from line %u.", nh->section->filename, nh->section->line); + + if (nh->onlink > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: nexthop group cannot have on-link flag. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); } else if (nh->family == AF_UNSPEC) /* When neither Family=, Gateway=, nor Group= is specified, assume IPv4. */ nh->family = AF_INET; - if (nh->blackhole && in_addr_is_set(nh->family, &nh->gw)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: blackhole nexthop cannot have gateway address. " - "Ignoring [NextHop] section from line %u.", - nh->section->filename, nh->section->line); + if (nh->blackhole) { + if (in_addr_is_set(nh->family, &nh->gw)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: blackhole nexthop cannot have gateway address. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + + if (nh->onlink > 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: blackhole nexthop cannot have on-link flag. " + "Ignoring [NextHop] section from line %u.", + nh->section->filename, nh->section->line); + } if (nh->onlink < 0 && in_addr_is_set(nh->family, &nh->gw) && ordered_hashmap_isempty(nh->network->addresses_by_section)) { @@ -1032,14 +1198,68 @@ static int nexthop_section_verify(NextHop *nh) { return 0; } -void network_drop_invalid_nexthops(Network *network) { +int network_drop_invalid_nexthops(Network *network) { + _cleanup_hashmap_free_ Hashmap *nexthops = NULL; NextHop *nh; + int r; assert(network); - HASHMAP_FOREACH(nh, network->nexthops_by_section) - if (nexthop_section_verify(nh) < 0) - nexthop_free(nh); + ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) { + if (nexthop_section_verify(nh) < 0) { + nexthop_detach(nh); + continue; + } + + if (nh->id == 0) + continue; + + /* Always use the setting specified later. So, remove the previously assigned setting. */ + NextHop *dup = hashmap_remove(nexthops, UINT32_TO_PTR(nh->id)); + if (dup) { + log_warning("%s: Duplicated nexthop settings for ID %"PRIu32" is specified at line %u and %u, " + "dropping the nexthop setting specified at line %u.", + dup->section->filename, + nh->id, nh->section->line, + dup->section->line, dup->section->line); + /* nexthop_detach() will drop the nexthop from nexthops_by_section. */ + nexthop_detach(dup); + } + + r = hashmap_ensure_put(&nexthops, NULL, UINT32_TO_PTR(nh->id), nh); + if (r < 0) + return log_oom(); + assert(r > 0); + } + + return 0; +} + +int manager_build_nexthop_ids(Manager *manager) { + Network *network; + int r; + + assert(manager); + + if (!manager->manage_foreign_nexthops) + return 0; + + manager->nexthop_ids = set_free(manager->nexthop_ids); + + ORDERED_HASHMAP_FOREACH(network, manager->networks) { + NextHop *nh; + + ORDERED_HASHMAP_FOREACH(nh, network->nexthops_by_section) { + if (nh->id == 0) + continue; + + r = set_ensure_put(&manager->nexthop_ids, NULL, UINT32_TO_PTR(nh->id)); + if (r < 0) + return r; + } + } + + return 0; } int config_parse_nexthop_id( @@ -1054,7 +1274,7 @@ int config_parse_nexthop_id( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; uint32_t id; int r; @@ -1104,7 +1324,7 @@ int config_parse_nexthop_gateway( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1149,7 +1369,7 @@ int config_parse_nexthop_family( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; AddressFamily a; int r; @@ -1215,7 +1435,7 @@ int config_parse_nexthop_onlink( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1252,7 +1472,7 @@ int config_parse_nexthop_blackhole( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; @@ -1291,7 +1511,7 @@ int config_parse_nexthop_group( void *data, void *userdata) { - _cleanup_(nexthop_free_or_set_invalidp) NextHop *n = NULL; + _cleanup_(nexthop_unref_or_set_invalidp) NextHop *n = NULL; Network *network = userdata; int r; diff --git a/src/network/networkd-nexthop.h b/src/network/networkd-nexthop.h index 6f2aa6f..fbe330a 100644 --- a/src/network/networkd-nexthop.h +++ b/src/network/networkd-nexthop.h @@ -20,34 +20,54 @@ typedef struct Network Network; typedef struct NextHop { Network *network; Manager *manager; - Link *link; ConfigSection *section; NetworkConfigSource source; NetworkConfigState state; - uint8_t protocol; + unsigned n_ref; - uint32_t id; - bool blackhole; + /* struct nhmsg */ int family; - union in_addr_union gw; + uint8_t protocol; uint8_t flags; - int onlink; /* Only used in conf parser and nexthop_section_verify(). */ - Hashmap *group; + + /* attributes */ + uint32_t id; /* NHA_ID */ + Hashmap *group; /* NHA_GROUP */ + bool blackhole; /* NHA_BLACKHOLE */ + int ifindex; /* NHA_OIF */ + union in_addr_union gw; /* NHA_GATEWAY */ + + /* Only used in conf parser and nexthop_section_verify(). */ + int onlink; + + /* For managing routes and nexthops that depend on this nexthop. */ + Set *nexthops; + Set *routes; } NextHop; -NextHop *nexthop_free(NextHop *nexthop); +NextHop* nexthop_ref(NextHop *nexthop); +NextHop* nexthop_unref(NextHop *nexthop); + +int nexthop_remove(NextHop *nexthop, Manager *manager); -void network_drop_invalid_nexthops(Network *network); +int network_drop_invalid_nexthops(Network *network); -int link_drop_managed_nexthops(Link *link); -int link_drop_foreign_nexthops(Link *link); +int link_drop_nexthops(Link *link, bool foreign); +static inline int link_drop_foreign_nexthops(Link *link) { + return link_drop_nexthops(link, /* foreign = */ true); +} +static inline int link_drop_static_nexthops(Link *link) { + return link_drop_nexthops(link, /* foreign = */ false); +} void link_foreignize_nexthops(Link *link); int link_request_static_nexthops(Link *link, bool only_ipv4); -int manager_get_nexthop_by_id(Manager *manager, uint32_t id, NextHop **ret); +int nexthop_get_by_id(Manager *manager, uint32_t id, NextHop **ret); +int nexthop_is_ready(Manager *manager, uint32_t id, NextHop **ret); int manager_rtnl_process_nexthop(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); +int manager_build_nexthop_ids(Manager *manager); DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(NextHop, nexthop); diff --git a/src/network/networkd-ntp.c b/src/network/networkd-ntp.c new file mode 100644 index 0000000..e764fea --- /dev/null +++ b/src/network/networkd-ntp.c @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "dns-domain.h" +#include "networkd-network.h" +#include "networkd-ntp.h" +#include "parse-util.h" +#include "strv.h" + +/* Let's assume that anything above this number is a user misconfiguration. */ +#define MAX_NTP_SERVERS 128U + +bool link_get_use_ntp(Link *link, NetworkConfigSource proto) { + int n, c; + + assert(link); + + if (!link->network) + return false; + + switch (proto) { + case NETWORK_CONFIG_SOURCE_DHCP4: + n = link->network->dhcp_use_ntp; + c = link->network->compat_dhcp_use_ntp; + break; + case NETWORK_CONFIG_SOURCE_DHCP6: + n = link->network->dhcp6_use_ntp; + c = link->network->compat_dhcp_use_ntp; + break; + default: + assert_not_reached(); + } + + /* If per-network and per-protocol setting is specified, use it. */ + if (n >= 0) + return n; + + /* If compat setting is specified, use it. */ + if (c >= 0) + return c; + + /* Otherwise, defaults to yes. */ + return true; +} + +int config_parse_ntp( + 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 = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *l = strv_free(*l); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *w = NULL; + + r = extract_first_word(&p, &w, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to extract NTP server name, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = dns_name_is_valid_or_address(w); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "%s is not a valid domain name or IP address, ignoring.", w); + continue; + } + + if (strv_length(*l) > MAX_NTP_SERVERS) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "More than %u NTP servers specified, ignoring \"%s\" and any subsequent entries.", + MAX_NTP_SERVERS, w); + return 0; + } + + r = strv_consume(l, TAKE_PTR(w)); + if (r < 0) + return log_oom(); + } +} diff --git a/src/network/networkd-ntp.h b/src/network/networkd-ntp.h new file mode 100644 index 0000000..44e7678 --- /dev/null +++ b/src/network/networkd-ntp.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "networkd-util.h" + +typedef struct Link Link; + +bool link_get_use_ntp(Link *link, NetworkConfigSource proto); + +CONFIG_PARSER_PROTOTYPE(config_parse_ntp); diff --git a/src/network/networkd-queue.c b/src/network/networkd-queue.c index 1128987..98c629f 100644 --- a/src/network/networkd-queue.c +++ b/src/network/networkd-queue.c @@ -9,14 +9,28 @@ #define REPLY_CALLBACK_COUNT_THRESHOLD 128 +static Request* request_detach_impl(Request *req) { + assert(req); + + if (!req->manager) + return NULL; + + ordered_set_remove(req->manager->request_queue, req); + req->manager = NULL; + return req; +} + +void request_detach(Request *req) { + request_unref(request_detach_impl(req)); +} + static Request *request_free(Request *req) { if (!req) return NULL; /* To prevent from triggering assertions in the hash and compare functions, remove this request * from the set before freeing userdata below. */ - if (req->manager) - ordered_set_remove(req->manager->request_queue, req); + request_detach_impl(req); if (req->free_func) req->free_func(req->userdata); @@ -31,26 +45,10 @@ static Request *request_free(Request *req) { DEFINE_TRIVIAL_REF_UNREF_FUNC(Request, request, request_free); -void request_detach(Manager *manager, Request *req) { - assert(manager); - - if (!req) - return; - - req = ordered_set_remove(manager->request_queue, req); - if (!req) - return; - - req->manager = NULL; - request_unref(req); -} - static void request_destroy_callback(Request *req) { assert(req); - if (req->manager) - request_detach(req->manager, req); - + request_detach(req); request_unref(req); } @@ -58,14 +56,16 @@ static void request_hash_func(const Request *req, struct siphash *state) { assert(req); assert(state); - siphash24_compress_boolean(req->link, state); - if (req->link) - siphash24_compress(&req->link->ifindex, sizeof(req->link->ifindex), state); + siphash24_compress_typesafe(req->type, state); - siphash24_compress(&req->type, sizeof(req->type), state); + if (!IN_SET(req->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + siphash24_compress_boolean(req->link, state); + if (req->link) + siphash24_compress_typesafe(req->link->ifindex, state); + } - siphash24_compress(&req->hash_func, sizeof(req->hash_func), state); - siphash24_compress(&req->compare_func, sizeof(req->compare_func), state); + siphash24_compress_typesafe(req->hash_func, state); + siphash24_compress_typesafe(req->compare_func, state); if (req->hash_func) req->hash_func(req->userdata, state); @@ -77,19 +77,21 @@ static int request_compare_func(const struct Request *a, const struct Request *b assert(a); assert(b); - r = CMP(!!a->link, !!b->link); + r = CMP(a->type, b->type); if (r != 0) return r; - if (a->link) { - r = CMP(a->link->ifindex, b->link->ifindex); + if (!IN_SET(a->type, REQUEST_TYPE_NEXTHOP, REQUEST_TYPE_ROUTE)) { + r = CMP(!!a->link, !!b->link); if (r != 0) return r; - } - r = CMP(a->type, b->type); - if (r != 0) - return r; + if (a->link) { + r = CMP(a->link->ifindex, b->link->ifindex); + if (r != 0) + return r; + } + } r = CMP(PTR_TO_UINT64(a->hash_func), PTR_TO_UINT64(b->hash_func)); if (r != 0) @@ -110,7 +112,7 @@ DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( Request, request_hash_func, request_compare_func, - request_unref); + request_detach); static int request_new( Manager *manager, @@ -164,6 +166,10 @@ static int request_new( if (req->counter) (*req->counter)++; + /* If this is called in the ORDERED_SET_FOREACH() loop of manager_process_requests(), we need to + * exit from the loop, due to the limitation of the iteration on OrderedSet. */ + manager->request_queued = true; + if (ret) *ret = req; @@ -210,52 +216,70 @@ int link_queue_request_full( process, counter, netlink_handler, ret); } -int manager_process_requests(sd_event_source *s, void *userdata) { - Manager *manager = ASSERT_PTR(userdata); - int r; +int link_requeue_request(Link *link, Request *req, void *userdata, Request **ret) { + assert(link); + assert(req); - for (;;) { - bool processed = false; - Request *req; + return link_queue_request_full( + link, + req->type, + userdata, + req->free_func, + req->hash_func, + req->compare_func, + req->process, + req->counter, + req->netlink_handler, + ret); +} - ORDERED_SET_FOREACH(req, manager->request_queue) { - _cleanup_(link_unrefp) Link *link = link_ref(req->link); +int manager_process_requests(Manager *manager) { + Request *req; + int r; - assert(req->process); + assert(manager); - if (req->waiting_reply) - continue; /* Waiting for netlink reply. */ + /* Process only when no remove request is queued. */ + if (!ordered_set_isempty(manager->remove_request_queue)) + return 0; - /* Typically, requests send netlink message asynchronously. If there are many requests - * queued, then this event may make reply callback queue in sd-netlink full. */ - if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || - netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || - fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) - return 0; + manager->request_queued = false; - r = req->process(req, link, req->userdata); - if (r == 0) - continue; + ORDERED_SET_FOREACH(req, manager->request_queue) { + if (req->waiting_reply) + continue; /* Already processed, and waiting for netlink reply. */ - processed = true; + /* Typically, requests send netlink message asynchronously. If there are many requests + * queued, then this event may make reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(manager->rtnl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + netlink_get_reply_callback_count(manager->genl) >= REPLY_CALLBACK_COUNT_THRESHOLD || + fw_ctx_get_reply_callback_count(manager->fw_ctx) >= REPLY_CALLBACK_COUNT_THRESHOLD) + break; - /* If the request sends netlink message, e.g. for Address or so, the Request object - * is referenced by the netlink slot, and will be detached later by its destroy callback. - * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */ - if (!req->waiting_reply) - request_detach(manager, req); + /* Avoid the request and link freed by req->process() and request_detach(). */ + _unused_ _cleanup_(request_unrefp) Request *req_unref = request_ref(req); + _cleanup_(link_unrefp) Link *link = link_ref(req->link); - if (r < 0 && link) { + assert(req->process); + r = req->process(req, link, req->userdata); + if (r < 0) { + request_detach(req); + + if (link) { link_enter_failed(link); - /* link_enter_failed() may remove multiple requests, - * hence we need to exit from the loop. */ + /* link_enter_failed() may detach multiple requests from the queue. + * Hence, we need to exit from the loop. */ break; } } + if (r > 0 && !req->waiting_reply) + /* If the request sends netlink message, e.g. for Address or so, the Request object is + * referenced by the netlink slot, and will be detached later by its destroy callback. + * Otherwise, e.g. for DHCP client or so, detach the request from queue now. */ + request_detach(req); - /* When at least one request is processed, then another request may be ready now. */ - if (!processed) - break; + if (manager->request_queued) + break; /* New request is queued. Exit from the loop. */ } return 0; @@ -316,7 +340,8 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { [REQUEST_TYPE_SET_LINK_ADDRESS_GENERATION_MODE] = "IPv6LL address generation mode", [REQUEST_TYPE_SET_LINK_BOND] = "bond configurations", [REQUEST_TYPE_SET_LINK_BRIDGE] = "bridge configurations", - [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations", + [REQUEST_TYPE_SET_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 1)", + [REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN] = "bridge VLAN configurations (step 2)", [REQUEST_TYPE_SET_LINK_CAN] = "CAN interface configurations", [REQUEST_TYPE_SET_LINK_FLAGS] = "link flags", [REQUEST_TYPE_SET_LINK_GROUP] = "interface group", @@ -331,3 +356,110 @@ static const char *const request_type_table[_REQUEST_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP_TO_STRING(request_type, RequestType); + +static RemoveRequest* remove_request_free(RemoveRequest *req) { + if (!req) + return NULL; + + if (req->manager) + ordered_set_remove(req->manager->remove_request_queue, req); + + if (req->unref_func) + req->unref_func(req->userdata); + + link_unref(req->link); + sd_netlink_unref(req->netlink); + sd_netlink_message_unref(req->message); + + return mfree(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(RemoveRequest*, remove_request_free); +DEFINE_TRIVIAL_DESTRUCTOR(remove_request_destroy_callback, RemoveRequest, remove_request_free); +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + remove_request_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + remove_request_free); + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler) { + + _cleanup_(remove_request_freep) RemoveRequest *req = NULL; + int r; + + assert(manager); + assert(userdata); + assert(netlink); + assert(message); + + req = new(RemoveRequest, 1); + if (!req) + return -ENOMEM; + + *req = (RemoveRequest) { + .link = link_ref(link), /* link may be NULL, but link_ref() handles it gracefully. */ + .userdata = userdata, + .netlink = sd_netlink_ref(netlink), + .message = sd_netlink_message_ref(message), + .netlink_handler = netlink_handler, + }; + + r = ordered_set_ensure_put(&manager->remove_request_queue, &remove_request_hash_ops, req); + if (r < 0) + return r; + assert(r > 0); + + req->manager = manager; + req->unref_func = unref_func; + + TAKE_PTR(req); + return 0; +} + +int manager_process_remove_requests(Manager *manager) { + RemoveRequest *req; + int r; + + assert(manager); + + while ((req = ordered_set_first(manager->remove_request_queue))) { + + /* Do not make the reply callback queue in sd-netlink full. */ + if (netlink_get_reply_callback_count(req->netlink) >= REPLY_CALLBACK_COUNT_THRESHOLD) + return 0; + + r = netlink_call_async( + req->netlink, NULL, req->message, + req->netlink_handler, + remove_request_destroy_callback, + req); + if (r < 0) { + _cleanup_(link_unrefp) Link *link = link_ref(req->link); + + log_link_warning_errno(link, r, "Failed to call netlink message: %m"); + + /* First free the request. */ + remove_request_free(req); + + /* Then, make the link enter the failed state. */ + if (link) + link_enter_failed(link); + + } else { + /* On success, netlink needs to be unref()ed. Otherwise, the netlink and remove + * request may not freed on shutting down. */ + req->netlink = sd_netlink_unref(req->netlink); + ordered_set_remove(manager->remove_request_queue, req); + } + } + + return 0; +} diff --git a/src/network/networkd-queue.h b/src/network/networkd-queue.h index e58d1be..e35cd73 100644 --- a/src/network/networkd-queue.h +++ b/src/network/networkd-queue.h @@ -1,7 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -#include "sd-event.h" #include "sd-netlink.h" #include "alloc-util.h" @@ -37,6 +36,7 @@ typedef enum RequestType { REQUEST_TYPE_SET_LINK_BOND, /* Setting bond configs. */ REQUEST_TYPE_SET_LINK_BRIDGE, /* Setting bridge configs. */ REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, /* Setting bridge VLAN configs. */ + REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN, /* Removing bridge VLAN configs. */ REQUEST_TYPE_SET_LINK_CAN, /* Setting CAN interface configs. */ REQUEST_TYPE_SET_LINK_FLAGS, /* Setting IFF_NOARP or friends. */ REQUEST_TYPE_SET_LINK_GROUP, /* Setting interface group. */ @@ -88,7 +88,7 @@ Request *request_ref(Request *req); Request *request_unref(Request *req); DEFINE_TRIVIAL_CLEANUP_FUNC(Request*, request_unref); -void request_detach(Manager *manager, Request *req); +void request_detach(Request *req); int netdev_queue_request( NetDev *netdev, @@ -107,6 +107,8 @@ int link_queue_request_full( request_netlink_handler_t netlink_handler, Request **ret); +int link_requeue_request(Link *link, Request *req, void *userdata, Request **ret); + static inline int link_queue_request( Link *link, RequestType type, @@ -135,7 +137,56 @@ static inline int link_queue_request( ret); \ }) -int manager_process_requests(sd_event_source *s, void *userdata); +int manager_process_requests(Manager *manager); int request_call_netlink_async(sd_netlink *nl, sd_netlink_message *m, Request *req); const char* request_type_to_string(RequestType t) _const_; + +typedef struct RemoveRequest RemoveRequest; +typedef int (*remove_request_netlink_handler_t)(sd_netlink *nl, sd_netlink_message *m, RemoveRequest *req); + +struct RemoveRequest { + Manager *manager; + Link *link; + void *userdata; /* e.g. Address */ + mfree_func_t unref_func; /* e.g. address_unref() */ + sd_netlink *netlink; + sd_netlink_message *message; + remove_request_netlink_handler_t netlink_handler; +}; + +int remove_request_add( + Manager *manager, + Link *link, + void *userdata, /* This is unref()ed when the call failed. */ + mfree_func_t unref_func, + sd_netlink *netlink, + sd_netlink_message *message, + remove_request_netlink_handler_t netlink_handler); + +#define _remove_request_add(manager, link, data, name, nl, m, handler) \ + ({ \ + typeof(*data) *_data = (data); \ + int _r; \ + \ + _r = remove_request_add(manager, link, _data, \ + (mfree_func_t) name##_unref, \ + nl, m, handler); \ + if (_r >= 0) \ + name##_ref(_data); \ + _r; \ + }) + + +#define link_remove_request_add(link, data, name, nl, m, handler) \ + ({ \ + Link *_link = (link); \ + \ + _remove_request_add(_link->manager, _link, data, name, \ + nl, m, handler); \ + }) + +#define manager_remove_request_add(manager, data, name, nl, m, handler) \ + _remove_request_add(manager, NULL, data, name, nl, m, handler) + +int manager_process_remove_requests(Manager *manager); diff --git a/src/network/networkd-radv.c b/src/network/networkd-radv.c index fc36a00..652e784 100644 --- a/src/network/networkd-radv.c +++ b/src/network/networkd-radv.c @@ -7,6 +7,7 @@ #include #include "dns-domain.h" +#include "ndisc-router-internal.h" #include "networkd-address-generation.h" #include "networkd-address.h" #include "networkd-dhcp-prefix-delegation.h" @@ -22,36 +23,6 @@ #include "string-table.h" #include "strv.h" -void network_adjust_radv(Network *network) { - assert(network); - - /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ - - if (network->dhcp_pd < 0) - /* For backward compatibility. */ - network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - - if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { - if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) - log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " - "Disabling IPv6PrefixDelegation=.", network->filename); - - network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; - } - - if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { - network->n_router_dns = 0; - network->router_dns = mfree(network->router_dns); - network->router_search_domains = ordered_set_free(network->router_search_domains); - } - - if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { - network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); - network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); - network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, pref64_prefix_free); - } -} - bool link_radv_enabled(Link *link) { assert(link); @@ -64,7 +35,7 @@ bool link_radv_enabled(Link *link) { return link->network->router_prefix_delegation; } -Prefix *prefix_free(Prefix *prefix) { +Prefix* prefix_free(Prefix *prefix) { if (!prefix) return NULL; @@ -109,10 +80,11 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se .network = network, .section = TAKE_PTR(n), - .preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC, - .valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, - .onlink = true, - .address_auto_configuration = true, + .prefix.flags = ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO, + .prefix.valid_lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .prefix.preferred_lifetime = RADV_DEFAULT_PREFERRED_LIFETIME_USEC, + .prefix.valid_until = USEC_INFINITY, + .prefix.preferred_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -123,7 +95,7 @@ static int prefix_new_static(Network *network, const char *filename, unsigned se return 0; } -RoutePrefix *route_prefix_free(RoutePrefix *prefix) { +RoutePrefix* route_prefix_free(RoutePrefix *prefix) { if (!prefix) return NULL; @@ -167,7 +139,8 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig .network = network, .section = TAKE_PTR(n), - .lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .route.lifetime = RADV_DEFAULT_VALID_LIFETIME_USEC, + .route.valid_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->route_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -178,7 +151,7 @@ static int route_prefix_new_static(Network *network, const char *filename, unsig return 0; } -pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) { +Prefix64* prefix64_free(Prefix64 *prefix) { if (!prefix) return NULL; @@ -192,11 +165,11 @@ pref64Prefix *pref64_prefix_free(pref64Prefix *prefix) { return mfree(prefix); } -DEFINE_SECTION_CLEANUP_FUNCTIONS(pref64Prefix, pref64_prefix_free); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Prefix64, prefix64_free); -static int pref64_prefix_new_static(Network *network, const char *filename, unsigned section_line, pref64Prefix **ret) { +static int prefix64_new_static(Network *network, const char *filename, unsigned section_line, Prefix64 **ret) { _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(pref64_prefix_freep) pref64Prefix *prefix = NULL; + _cleanup_(prefix64_freep) Prefix64 *prefix = NULL; int r; assert(network); @@ -214,15 +187,16 @@ static int pref64_prefix_new_static(Network *network, const char *filename, unsi return 0; } - prefix = new(pref64Prefix, 1); + prefix = new(Prefix64, 1); if (!prefix) return -ENOMEM; - *prefix = (pref64Prefix) { + *prefix = (Prefix64) { .network = network, .section = TAKE_PTR(n), - .lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC, + .prefix64.lifetime = RADV_PREF64_DEFAULT_LIFETIME_USEC, + .prefix64.valid_until = USEC_INFINITY, }; r = hashmap_ensure_put(&network->pref64_prefixes_by_section, &config_section_hash_ops, prefix->section, prefix); @@ -243,22 +217,22 @@ int link_request_radv_addresses(Link *link) { return 0; HASHMAP_FOREACH(p, link->network->prefixes_by_section) { - _cleanup_set_free_ Set *addresses = NULL; - struct in6_addr *a; - if (!p->assign) continue; /* radv_generate_addresses() below requires the prefix length <= 64. */ - if (p->prefixlen > 64) + if (p->prefix.prefixlen > 64) continue; - r = radv_generate_addresses(link, p->tokens, &p->prefix, p->prefixlen, &addresses); + _cleanup_hashmap_free_ Hashmap *tokens_by_address = NULL; + r = radv_generate_addresses(link, p->tokens, &p->prefix.address, p->prefix.prefixlen, &tokens_by_address); if (r < 0) return r; - SET_FOREACH(a, addresses) { - _cleanup_(address_freep) Address *address = NULL; + IPv6Token *token; + struct in6_addr *a; + HASHMAP_FOREACH_KEY(token, a, tokens_by_address) { + _cleanup_(address_unrefp) Address *address = NULL; r = address_new(&address); if (r < 0) @@ -267,8 +241,9 @@ int link_request_radv_addresses(Link *link) { address->source = NETWORK_CONFIG_SOURCE_STATIC; address->family = AF_INET6; address->in_addr.in6 = *a; - address->prefixlen = p->prefixlen; + address->prefixlen = p->prefix.prefixlen; address->route_metric = p->route_metric; + address->token = ipv6_token_ref(token); r = link_request_static_address(link, address); if (r < 0) @@ -279,6 +254,29 @@ int link_request_radv_addresses(Link *link) { return 0; } +int link_reconfigure_radv_address(Address *address, Link *link) { + int r; + + assert(address); + assert(address->source == NETWORK_CONFIG_SOURCE_STATIC); + assert(link); + + r = regenerate_address(address, link); + if (r <= 0) + return r; + + r = link_request_static_address(link, address); + if (r < 0) + return r; + + if (link->static_address_messages != 0) { + link->static_addresses_configured = false; + link_set_state(link, LINK_STATE_CONFIGURING); + } + + return 0; +} + static int radv_set_prefix(Link *link, Prefix *prefix) { _cleanup_(sd_radv_prefix_unrefp) sd_radv_prefix *p = NULL; int r; @@ -291,23 +289,23 @@ static int radv_set_prefix(Link *link, Prefix *prefix) { if (r < 0) return r; - r = sd_radv_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + r = sd_radv_prefix_set_prefix(p, &prefix->prefix.address, prefix->prefix.prefixlen); if (r < 0) return r; - r = sd_radv_prefix_set_preferred_lifetime(p, prefix->preferred_lifetime, USEC_INFINITY); + r = sd_radv_prefix_set_preferred_lifetime(p, prefix->prefix.preferred_lifetime, prefix->prefix.preferred_until); if (r < 0) return r; - r = sd_radv_prefix_set_valid_lifetime(p, prefix->valid_lifetime, USEC_INFINITY); + r = sd_radv_prefix_set_valid_lifetime(p, prefix->prefix.valid_lifetime, prefix->prefix.valid_until); if (r < 0) return r; - r = sd_radv_prefix_set_onlink(p, prefix->onlink); + r = sd_radv_prefix_set_onlink(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_ONLINK)); if (r < 0) return r; - r = sd_radv_prefix_set_address_autoconfiguration(p, prefix->address_auto_configuration); + r = sd_radv_prefix_set_address_autoconfiguration(p, FLAGS_SET(prefix->prefix.flags, ND_OPT_PI_FLAG_AUTO)); if (r < 0) return r; @@ -326,18 +324,18 @@ static int radv_set_route_prefix(Link *link, RoutePrefix *prefix) { if (r < 0) return r; - r = sd_radv_route_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen); + r = sd_radv_route_prefix_set_prefix(p, &prefix->route.address, prefix->route.prefixlen); if (r < 0) return r; - r = sd_radv_route_prefix_set_lifetime(p, prefix->lifetime, USEC_INFINITY); + r = sd_radv_route_prefix_set_lifetime(p, prefix->route.lifetime, prefix->route.valid_until); if (r < 0) return r; return sd_radv_add_route_prefix(link->radv, p); } -static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) { +static int radv_set_pref64_prefix(Link *link, Prefix64 *prefix) { _cleanup_(sd_radv_pref64_prefix_unrefp) sd_radv_pref64_prefix *p = NULL; int r; @@ -349,7 +347,7 @@ static int radv_set_pref64_prefix(Link *link, pref64Prefix *prefix) { if (r < 0) return r; - r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix, prefix->prefixlen, prefix->lifetime); + r = sd_radv_pref64_prefix_set_prefix(p, &prefix->prefix64.prefix, prefix->prefix64.prefixlen, prefix->prefix64.lifetime); if (r < 0) return r; @@ -502,9 +500,6 @@ static int radv_find_uplink(Link *link, Link **ret) { static int radv_configure(Link *link) { Link *uplink = NULL; - RoutePrefix *q; - pref64Prefix *n; - Prefix *p; int r; assert(link); @@ -547,30 +542,33 @@ static int radv_configure(Link *link) { if (r < 0) return r; - if (link->network->router_lifetime_usec > 0) { - r = sd_radv_set_preference(link->radv, link->network->router_preference); - if (r < 0) - return r; - } + r = sd_radv_set_preference(link->radv, link->network->router_preference); + if (r < 0) + return r; - if (link->network->router_retransmit_usec > 0) { - r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec); - if (r < 0) - return r; - } + r = sd_radv_set_reachable_time(link->radv, link->network->router_reachable_usec); + if (r < 0) + return r; + r = sd_radv_set_retransmit(link->radv, link->network->router_retransmit_usec); + if (r < 0) + return r; + + Prefix *p; HASHMAP_FOREACH(p, link->network->prefixes_by_section) { r = radv_set_prefix(link, p); if (r < 0 && r != -EEXIST) return r; } + RoutePrefix *q; HASHMAP_FOREACH(q, link->network->route_prefixes_by_section) { r = radv_set_route_prefix(link, q); if (r < 0 && r != -EEXIST) return r; } + Prefix64 *n; HASHMAP_FOREACH(n, link->network->pref64_prefixes_by_section) { r = radv_set_pref64_prefix(link, n); if (r < 0 && r != -EEXIST) @@ -603,9 +601,6 @@ static int radv_configure(Link *link) { } int radv_update_mac(Link *link) { - bool restart; - int r; - assert(link); if (!link->radv) @@ -614,23 +609,7 @@ int radv_update_mac(Link *link) { if (link->hw_addr.length != ETH_ALEN) return 0; - restart = sd_radv_is_running(link->radv); - - r = sd_radv_stop(link->radv); - if (r < 0) - return r; - - r = sd_radv_set_mac(link->radv, &link->hw_addr.ether); - if (r < 0) - return r; - - if (restart) { - r = sd_radv_start(link->radv); - if (r < 0) - return r; - } - - return 0; + return sd_radv_set_mac(link->radv, &link->hw_addr.ether); } static int radv_is_ready_to_configure(Link *link) { @@ -745,6 +724,10 @@ int radv_start(Link *link) { return log_link_debug_errno(link, r, "Failed to request DHCP delegated subnet prefix: %m"); } + r = sd_radv_set_link_local_address(link->radv, &link->ipv6ll_address); + if (r < 0) + return r; + log_link_debug(link, "Starting IPv6 Router Advertisements"); return sd_radv_start(link->radv); } @@ -781,9 +764,18 @@ int radv_add_prefix( return r; r = sd_radv_add_prefix(link->radv, p); - if (r < 0 && r != -EEXIST) + if (r == -EEXIST) + return 0; + if (r < 0) return r; + if (sd_radv_is_running(link->radv)) { + /* Announce updated prefixe now. */ + r = sd_radv_send(link->radv); + if (r < 0) + return r; + } + return 0; } @@ -793,94 +785,112 @@ static int prefix_section_verify(Prefix *p) { if (section_is_invalid(p->section)) return -EINVAL; - if (in6_addr_is_null(&p->prefix)) + if (in6_addr_is_null(&p->prefix.address)) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: [IPv6Prefix] section without Prefix= field configured, " "or specified prefix is the null address. " "Ignoring [IPv6Prefix] section from line %u.", p->section->filename, p->section->line); - if (p->prefixlen < 3 || p->prefixlen > 128) + if (p->prefix.prefixlen < 3 || p->prefix.prefixlen > 128) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: Invalid prefix length %u is specified in [IPv6Prefix] section. " "Valid range is 3…128. Ignoring [IPv6Prefix] section from line %u.", - p->section->filename, p->prefixlen, p->section->line); + p->section->filename, p->prefix.prefixlen, p->section->line); - if (p->prefixlen > 64) { + if (p->prefix.prefixlen > 64) { log_info("%s:%u: Unusual prefix length %u (> 64) is specified in [IPv6Prefix] section from line %s%s.", p->section->filename, p->section->line, - p->prefixlen, + p->prefix.prefixlen, p->assign ? ", refusing to assign an address in " : "", - p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix, p->prefixlen) : ""); + p->assign ? IN6_ADDR_PREFIX_TO_STRING(&p->prefix.address, p->prefix.prefixlen) : ""); p->assign = false; } - if (p->valid_lifetime == 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: The valid lifetime of prefix cannot be zero. " - "Ignoring [IPv6Prefix] section from line %u.", - p->section->filename, p->section->line); - - if (p->preferred_lifetime > p->valid_lifetime) + if (p->prefix.preferred_lifetime > p->prefix.valid_lifetime) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: The preferred lifetime %s is longer than the valid lifetime %s. " "Ignoring [IPv6Prefix] section from line %u.", p->section->filename, - FORMAT_TIMESPAN(p->preferred_lifetime, USEC_PER_SEC), - FORMAT_TIMESPAN(p->valid_lifetime, USEC_PER_SEC), + FORMAT_TIMESPAN(p->prefix.preferred_lifetime, USEC_PER_SEC), + FORMAT_TIMESPAN(p->prefix.valid_lifetime, USEC_PER_SEC), p->section->line); return 0; } -void network_drop_invalid_prefixes(Network *network) { - Prefix *p; - - assert(network); - - HASHMAP_FOREACH(p, network->prefixes_by_section) - if (prefix_section_verify(p) < 0) - prefix_free(p); -} - static int route_prefix_section_verify(RoutePrefix *p) { if (section_is_invalid(p->section)) return -EINVAL; - if (p->prefixlen > 128) + if (p->route.prefixlen > 128) return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "%s: Invalid prefix length %u is specified in [IPv6RoutePrefix] section. " "Valid range is 0…128. Ignoring [IPv6RoutePrefix] section from line %u.", - p->section->filename, p->prefixlen, p->section->line); - - if (p->lifetime == 0) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: The lifetime of route cannot be zero. " - "Ignoring [IPv6RoutePrefix] section from line %u.", - p->section->filename, p->section->line); + p->section->filename, p->route.prefixlen, p->section->line); return 0; } -void network_drop_invalid_route_prefixes(Network *network) { - RoutePrefix *p; - +void network_adjust_radv(Network *network) { assert(network); - HASHMAP_FOREACH(p, network->route_prefixes_by_section) - if (route_prefix_section_verify(p) < 0) - route_prefix_free(p); -} + /* After this function is called, network->router_prefix_delegation can be treated as a boolean. */ -void network_drop_invalid_pref64_prefixes(Network *network) { - pref64Prefix *p; + if (network->dhcp_pd < 0) + /* For backward compatibility. */ + network->dhcp_pd = FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_DHCP6); - assert(network); + if (!FLAGS_SET(network->link_local, ADDRESS_FAMILY_IPV6)) { + if (network->router_prefix_delegation != RADV_PREFIX_DELEGATION_NONE) + log_warning("%s: IPv6PrefixDelegation= is enabled but IPv6 link-local addressing is disabled. " + "Disabling IPv6PrefixDelegation=.", network->filename); + + network->router_prefix_delegation = RADV_PREFIX_DELEGATION_NONE; + } - HASHMAP_FOREACH(p, network->pref64_prefixes_by_section) - if (section_is_invalid(p->section)) - pref64_prefix_free(p); + if (network->router_prefix_delegation == RADV_PREFIX_DELEGATION_NONE) { + network->n_router_dns = 0; + network->router_dns = mfree(network->router_dns); + network->router_search_domains = ordered_set_free(network->router_search_domains); + } + + if (!FLAGS_SET(network->router_prefix_delegation, RADV_PREFIX_DELEGATION_STATIC)) { + network->prefixes_by_section = hashmap_free_with_destructor(network->prefixes_by_section, prefix_free); + network->route_prefixes_by_section = hashmap_free_with_destructor(network->route_prefixes_by_section, route_prefix_free); + network->pref64_prefixes_by_section = hashmap_free_with_destructor(network->pref64_prefixes_by_section, prefix64_free); + } + + if (!network->router_prefix_delegation) + return; + + /* Below, let's verify router settings, if enabled. */ + + if (network->router_lifetime_usec == 0 && network->router_preference != SD_NDISC_PREFERENCE_MEDIUM) + /* RFC 4191, Section 2.2, + * If the Router Lifetime is zero, the preference value MUST be set to (00) by the sender. + * + * Note, radv_send_router() gracefully handle that. So, it is not necessary to refuse, but + * let's warn about that. */ + log_notice("%s: RouterPreference=%s specified with RouterLifetimeSec=0, ignoring RouterPreference= setting.", + network->filename, ndisc_router_preference_to_string(network->router_preference)); + + Prefix *prefix; + HASHMAP_FOREACH(prefix, network->prefixes_by_section) + if (prefix_section_verify(prefix) < 0) + prefix_free(prefix); + + + RoutePrefix *route; + HASHMAP_FOREACH(route, network->route_prefixes_by_section) + if (route_prefix_section_verify(route) < 0) + route_prefix_free(route); + + Prefix64 *pref64; + HASHMAP_FOREACH(pref64, network->pref64_prefixes_by_section) + if (section_is_invalid(pref64->section)) + prefix64_free(pref64); } int config_parse_prefix( @@ -909,15 +919,15 @@ int config_parse_prefix( if (r < 0) return log_oom(); - r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefix.prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Prefix is invalid, ignoring assignment: %s", rvalue); return 0; } - (void) in6_addr_mask(&a.in6, p->prefixlen); - p->prefix = a.in6; + (void) in6_addr_mask(&a.in6, p->prefix.prefixlen); + p->prefix.address = a.in6; TAKE_PTR(p); return 0; @@ -955,14 +965,12 @@ int config_parse_prefix_boolean( return 0; } - if (streq(lvalue, "OnLink")) - p->onlink = r; - else if (streq(lvalue, "AddressAutoconfiguration")) - p->address_auto_configuration = r; - else if (streq(lvalue, "Assign")) + if (ltype != 0) + SET_FLAG(p->prefix.flags, ltype, r); + else { + assert(streq(lvalue, "Assign")); p->assign = r; - else - assert_not_reached(); + } TAKE_PTR(p); return 0; @@ -1008,9 +1016,9 @@ int config_parse_prefix_lifetime( } if (streq(lvalue, "PreferredLifetimeSec")) - p->preferred_lifetime = usec; + p->prefix.preferred_lifetime = usec; else if (streq(lvalue, "ValidLifetimeSec")) - p->valid_lifetime = usec; + p->prefix.valid_lifetime = usec; else assert_not_reached(); @@ -1115,15 +1123,15 @@ int config_parse_route_prefix( if (r < 0) return log_oom(); - r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->prefixlen); + r = in_addr_prefix_from_string(rvalue, AF_INET6, &a, &p->route.prefixlen); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Route prefix is invalid, ignoring assignment: %s", rvalue); return 0; } - (void) in6_addr_mask(&a.in6, p->prefixlen); - p->prefix = a.in6; + (void) in6_addr_mask(&a.in6, p->route.prefixlen); + p->route.address = a.in6; TAKE_PTR(p); return 0; @@ -1168,7 +1176,7 @@ int config_parse_route_prefix_lifetime( return 0; } - p->lifetime = usec; + p->route.lifetime = usec; TAKE_PTR(p); return 0; @@ -1186,7 +1194,7 @@ int config_parse_pref64_prefix( void *data, void *userdata) { - _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + _cleanup_(prefix64_free_or_set_invalidp) Prefix64 *p = NULL; Network *network = ASSERT_PTR(userdata); union in_addr_union a; uint8_t prefixlen; @@ -1197,7 +1205,7 @@ int config_parse_pref64_prefix( assert(lvalue); assert(rvalue); - r = pref64_prefix_new_static(network, filename, section_line, &p); + r = prefix64_new_static(network, filename, section_line, &p); if (r < 0) return log_oom(); @@ -1214,9 +1222,9 @@ int config_parse_pref64_prefix( return 0; } - (void) in6_addr_mask(&a.in6,prefixlen); - p->prefix = a.in6; - p->prefixlen = prefixlen; + (void) in6_addr_mask(&a.in6, prefixlen); + p->prefix64.prefix = a.in6; + p->prefix64.prefixlen = prefixlen; TAKE_PTR(p); return 0; @@ -1234,7 +1242,7 @@ int config_parse_pref64_prefix_lifetime( void *data, void *userdata) { - _cleanup_(pref64_prefix_free_or_set_invalidp) pref64Prefix *p = NULL; + _cleanup_(prefix64_free_or_set_invalidp) Prefix64 *p = NULL; Network *network = ASSERT_PTR(userdata); usec_t usec; int r; @@ -1244,7 +1252,7 @@ int config_parse_pref64_prefix_lifetime( assert(lvalue); assert(rvalue); - r = pref64_prefix_new_static(network, filename, section_line, &p); + r = prefix64_new_static(network, filename, section_line, &p); if (r < 0) return log_oom(); @@ -1261,7 +1269,7 @@ int config_parse_pref64_prefix_lifetime( return 0; } - p->lifetime = usec; + p->prefix64.lifetime = usec; TAKE_PTR(p); return 0; @@ -1499,7 +1507,7 @@ int config_parse_router_lifetime( return 0; } -int config_parse_router_retransmit( +int config_parse_router_uint32_msec_usec( const char *unit, const char *filename, unsigned line, @@ -1511,7 +1519,7 @@ int config_parse_router_retransmit( void *data, void *userdata) { - usec_t usec, *router_retransmit_usec = ASSERT_PTR(data); + usec_t usec, *router_usec = ASSERT_PTR(data); int r; assert(filename); @@ -1520,7 +1528,7 @@ int config_parse_router_retransmit( assert(rvalue); if (isempty(rvalue)) { - *router_retransmit_usec = 0; + *router_usec = 0; return 0; } @@ -1532,13 +1540,13 @@ int config_parse_router_retransmit( } if (usec != USEC_INFINITY && - usec > RADV_MAX_RETRANSMIT_USEC) { + usec > RADV_MAX_UINT32_MSEC_USEC) { log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid [%s] %s=, ignoring assignment: %s", section, lvalue, rvalue); return 0; } - *router_retransmit_usec = usec; + *router_usec = usec; return 0; } diff --git a/src/network/networkd-radv.h b/src/network/networkd-radv.h index 48677b5..ccdd656 100644 --- a/src/network/networkd-radv.h +++ b/src/network/networkd-radv.h @@ -12,6 +12,7 @@ #include "in-addr-util.h" #include "conf-parser.h" +#include "ndisc-option.h" #include "networkd-util.h" typedef struct Link Link; @@ -30,13 +31,7 @@ typedef struct Prefix { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t preferred_lifetime; - usec_t valid_lifetime; - - bool onlink; - bool address_auto_configuration; + sd_ndisc_prefix prefix; bool assign; uint32_t route_metric; @@ -47,30 +42,24 @@ typedef struct RoutePrefix { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t lifetime; + sd_ndisc_route route; } RoutePrefix; -typedef struct pref64Prefix { +typedef struct Prefix64 { Network *network; ConfigSection *section; - struct in6_addr prefix; - uint8_t prefixlen; - usec_t lifetime; -} pref64Prefix; + sd_ndisc_prefix64 prefix64; +} Prefix64; -Prefix *prefix_free(Prefix *prefix); -RoutePrefix *route_prefix_free(RoutePrefix *prefix); -pref64Prefix *pref64_prefix_free(pref64Prefix *prefix); +Prefix* prefix_free(Prefix *prefix); +RoutePrefix* route_prefix_free(RoutePrefix *prefix); +Prefix64* prefix64_free(Prefix64 *prefix); -void network_drop_invalid_prefixes(Network *network); -void network_drop_invalid_route_prefixes(Network *network); -void network_drop_invalid_pref64_prefixes(Network *network); void network_adjust_radv(Network *network); int link_request_radv_addresses(Link *link); +int link_reconfigure_radv_address(Address *address, Link *link); bool link_radv_enabled(Link *link); int radv_start(Link *link); @@ -85,7 +74,7 @@ RADVPrefixDelegation radv_prefix_delegation_from_string(const char *s) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_router_prefix_delegation); CONFIG_PARSER_PROTOTYPE(config_parse_router_lifetime); -CONFIG_PARSER_PROTOTYPE(config_parse_router_retransmit); +CONFIG_PARSER_PROTOTYPE(config_parse_router_uint32_msec_usec); CONFIG_PARSER_PROTOTYPE(config_parse_router_preference); CONFIG_PARSER_PROTOTYPE(config_parse_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_prefix_boolean); diff --git a/src/network/networkd-route-metric.c b/src/network/networkd-route-metric.c new file mode 100644 index 0000000..31a2bde --- /dev/null +++ b/src/network/networkd-route-metric.c @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "netlink-util.h" +#include "networkd-route.h" +#include "networkd-route-metric.h" +#include "parse-util.h" +#include "string-util.h" + +void route_metric_done(RouteMetric *metric) { + assert(metric); + + free(metric->metrics); + free(metric->metrics_set); + free(metric->tcp_congestion_control_algo); +} + +int route_metric_copy(const RouteMetric *src, RouteMetric *dest) { + assert(src); + assert(dest); + + dest->n_metrics = src->n_metrics; + if (src->n_metrics > 0) { + assert(src->n_metrics != 1); + + dest->metrics = newdup(uint32_t, src->metrics, src->n_metrics); + if (!dest->metrics) + return -ENOMEM; + } else + dest->metrics = NULL; + + dest->n_metrics_set = src->n_metrics_set; + if (src->n_metrics_set > 0) { + assert(src->n_metrics_set != 1); + + dest->metrics_set = newdup(bool, src->metrics_set, src->n_metrics_set); + if (!dest->metrics_set) + return -ENOMEM; + } else + dest->metrics_set = NULL; + + return strdup_to(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo); +} + +void route_metric_hash_func(const RouteMetric *metric, struct siphash *state) { + assert(metric); + + siphash24_compress_typesafe(metric->n_metrics, state); + siphash24_compress_safe(metric->metrics, sizeof(uint32_t) * metric->n_metrics, state); + siphash24_compress_string(metric->tcp_congestion_control_algo, state); +} + +int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b) { + int r; + + assert(a); + assert(b); + + r = memcmp_nn(a->metrics, a->n_metrics * sizeof(uint32_t), b->metrics, b->n_metrics * sizeof(uint32_t)); + if (r != 0) + return r; + + return strcmp_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); +} + +bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel) { + assert(a); + assert(b); + + /* If the kernel has expiration timer for the route, then only MTU can be updated. */ + + if (!expiration_by_kernel) + return route_metric_compare_func(a, b) == 0; + + if (a->n_metrics != b->n_metrics) + return false; + + for (size_t i = 1; i < a->n_metrics; i++) { + if (i != RTAX_MTU) + continue; + if (a->metrics[i] != b->metrics[i]) + return false; + } + + return streq_ptr(a->tcp_congestion_control_algo, b->tcp_congestion_control_algo); +} + +int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force) { + assert(metric); + + if (force) { + if (!GREEDY_REALLOC0(metric->metrics_set, attr + 1)) + return -ENOMEM; + + metric->metrics_set[attr] = true; + metric->n_metrics_set = MAX(metric->n_metrics_set, (size_t) (attr + 1)); + } else { + /* Do not override the values specified in conf parsers. */ + if (metric->n_metrics_set > attr && metric->metrics_set[attr]) + return 0; + } + + if (value != 0) { + if (!GREEDY_REALLOC0(metric->metrics, attr + 1)) + return -ENOMEM; + + metric->metrics[attr] = value; + metric->n_metrics = MAX(metric->n_metrics, (size_t) (attr + 1)); + return 0; + } + + if (metric->n_metrics <= attr) + return 0; + + metric->metrics[attr] = 0; + + for (size_t i = metric->n_metrics; i > 0; i--) + if (metric->metrics[i-1] != 0) { + metric->n_metrics = i; + return 0; + } + + metric->n_metrics = 0; + return 0; +} + +static void route_metric_unset(RouteMetric *metric, uint16_t attr) { + assert(metric); + + if (metric->n_metrics_set > attr) + metric->metrics_set[attr] = false; + + assert_se(route_metric_set_full(metric, attr, 0, /* force = */ false) >= 0); +} + +uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr) { + assert(metric); + + if (metric->n_metrics <= attr) + return 0; + + return metric->metrics[attr]; +} + +int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m) { + int r; + + assert(metric); + assert(m); + + if (metric->n_metrics <= 0 && isempty(metric->tcp_congestion_control_algo)) + return 0; + + r = sd_netlink_message_open_container(m, RTA_METRICS); + if (r < 0) + return r; + + for (size_t i = 0; i < metric->n_metrics; i++) { + if (i == RTAX_CC_ALGO) + continue; + + if (metric->metrics[i] == 0) + continue; + + r = sd_netlink_message_append_u32(m, i, metric->metrics[i]); + if (r < 0) + return r; + } + + if (!isempty(metric->tcp_congestion_control_algo)) { + r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, metric->tcp_congestion_control_algo); + if (r < 0) + return r; + } + + r = sd_netlink_message_close_container(m); + if (r < 0) + return r; + + return 0; +} + +int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *m) { + _cleanup_free_ void *data = NULL; + size_t len; + int r; + + assert(metric); + assert(m); + + r = sd_netlink_message_read_data(m, RTA_METRICS, &len, &data); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_warning_errno(r, "rtnl: Could not read RTA_METRICS attribute, ignoring: %m"); + + for (struct rtattr *rta = data; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + size_t rta_type = RTA_TYPE(rta); + + if (rta_type == RTAX_CC_ALGO) { + char *p = memdup_suffix0(RTA_DATA(rta), RTA_PAYLOAD(rta)); + if (!p) + return log_oom(); + + free_and_replace(metric->tcp_congestion_control_algo, p); + + } else { + if (RTA_PAYLOAD(rta) != sizeof(uint32_t)) + continue; + + r = route_metric_set(metric, rta_type, *(uint32_t*) RTA_DATA(rta)); + if (r < 0) + return log_oom(); + } + } + + return 0; +} + +static int config_parse_route_metric_advmss_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + uint64_t u; + int r; + + assert(rvalue); + + r = parse_size(rvalue, 1024, &u); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (u == 0 || u > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = (uint32_t) u; + return 1; +} + +static int config_parse_route_metric_hop_limit_impl( + 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) { + + uint32_t k, *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + if (k == 0 || k > 255) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = k; + return 1; +} + +int config_parse_tcp_window( + 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) { + + uint32_t k, *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = safe_atou32(rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + if (k == 0 || k >= 1024) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = k; + return 1; +} + +static int config_parse_route_metric_tcp_rto_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + usec_t usec; + int r; + + assert(rvalue); + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + if (!timestamp_is_set(usec) || + DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = (uint32_t) DIV_ROUND_UP(usec, USEC_PER_MSEC); + return 1; +} + +static int config_parse_route_metric_boolean_impl( + 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) { + + uint32_t *val = ASSERT_PTR(data); + int r; + + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse %s=, ignoring assignment: %s", lvalue, rvalue); + return 0; + } + + *val = r; + return 1; +} + +#define DEFINE_CONFIG_PARSE_ROUTE_METRIC(name, parser) \ + int config_parse_route_metric_##name( \ + 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) { \ + \ + Network *network = ASSERT_PTR(userdata); \ + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; \ + uint16_t attr_type = ltype; \ + int r; \ + \ + assert(filename); \ + assert(section); \ + assert(lvalue); \ + assert(rvalue); \ + \ + r = route_new_static(network, filename, section_line, &route); \ + if (r == -ENOMEM) \ + return log_oom(); \ + if (r < 0) { \ + log_syntax(unit, LOG_WARNING, filename, line, r, \ + "Failed to allocate route, ignoring assignment: %m"); \ + return 0; \ + } \ + \ + if (isempty(rvalue)) { \ + route_metric_unset(&route->metric, attr_type); \ + TAKE_PTR(route); \ + return 0; \ + } \ + \ + uint32_t k; \ + r = parser(unit, filename, line, section, section_line, \ + lvalue, /* ltype = */ 0, rvalue, \ + &k, userdata); \ + if (r <= 0) \ + return r; \ + \ + if (route_metric_set_full( \ + &route->metric, \ + attr_type, \ + k, \ + /* force = */ true) < 0) \ + return log_oom(); \ + \ + TAKE_PTR(route); \ + return 0; \ + } + +DEFINE_CONFIG_PARSE_ROUTE_METRIC(mtu, config_parse_mtu); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(advmss, config_parse_route_metric_advmss_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(hop_limit, config_parse_route_metric_hop_limit_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_window, config_parse_tcp_window); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(tcp_rto, config_parse_route_metric_tcp_rto_impl); +DEFINE_CONFIG_PARSE_ROUTE_METRIC(boolean, config_parse_route_metric_boolean_impl); + +int config_parse_route_metric_tcp_congestion( + 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) { + + Network *network = ASSERT_PTR(userdata); + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(rvalue); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_string(unit, filename, line, section, section_line, lvalue, 0, + rvalue, &route->metric.tcp_congestion_control_algo, userdata); + if (r <= 0) + return r; + + TAKE_PTR(route); + return 0; +} diff --git a/src/network/networkd-route-metric.h b/src/network/networkd-route-metric.h new file mode 100644 index 0000000..212f907 --- /dev/null +++ b/src/network/networkd-route-metric.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "hash-funcs.h" + +typedef struct RouteMetric { + size_t n_metrics; /* maximum metric attr type with non-zero value */ + uint32_t *metrics; /* RTAX_*, except for RTAX_CC_ALGO */ + + size_t n_metrics_set; + bool *metrics_set; /* used by conf parsers */ + + char *tcp_congestion_control_algo; /* RTAX_CC_ALGO */ +} RouteMetric; + +#define ROUTE_METRIC_NULL ((const RouteMetric) {}) + +void route_metric_done(RouteMetric *metric); +int route_metric_copy(const RouteMetric *src, RouteMetric *dest); + +void route_metric_hash_func(const RouteMetric *metric, struct siphash *state); +int route_metric_compare_func(const RouteMetric *a, const RouteMetric *b); +bool route_metric_can_update(const RouteMetric *a, const RouteMetric *b, bool expiration_by_kernel); + +int route_metric_set_full(RouteMetric *metric, uint16_t attr, uint32_t value, bool force); +static inline int route_metric_set(RouteMetric *metric, uint16_t attr, uint32_t value) { + return route_metric_set_full(metric, attr, value, false); +} +uint32_t route_metric_get(const RouteMetric *metric, uint16_t attr); + +int route_metric_set_netlink_message(const RouteMetric *metric, sd_netlink_message *m); +int route_metric_read_netlink_message(RouteMetric *metric, sd_netlink_message *message); + +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_mtu); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_advmss); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_hop_limit); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_window); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_rto); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_boolean); +CONFIG_PARSER_PROTOTYPE(config_parse_route_metric_tcp_congestion); +CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window); diff --git a/src/network/networkd-route-nexthop.c b/src/network/networkd-route-nexthop.c new file mode 100644 index 0000000..11215c3 --- /dev/null +++ b/src/network/networkd-route-nexthop.c @@ -0,0 +1,1225 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "netlink-util.h" +#include "networkd-manager.h" +#include "networkd-network.h" +#include "networkd-nexthop.h" +#include "networkd-route.h" +#include "networkd-route-nexthop.h" +#include "networkd-route-util.h" +#include "parse-util.h" +#include "string-util.h" + +void route_detach_from_nexthop(Route *route) { + NextHop *nh; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + if (nexthop_get_by_id(route->manager, route->nexthop_id, &nh) < 0) + return; + + route_unref(set_remove(nh->routes, route)); +} + +void route_attach_to_nexthop(Route *route) { + NextHop *nh; + int r; + + assert(route); + assert(route->manager); + + if (route->nexthop_id == 0) + return; + + r = nexthop_get_by_id(route->manager, route->nexthop_id, &nh); + if (r < 0) { + if (route->manager->manage_foreign_nexthops) + log_debug_errno(r, "Route has unknown nexthop ID (%"PRIu32"), ignoring.", + route->nexthop_id); + return; + } + + r = set_ensure_put(&nh->routes, &route_hash_ops_unref, route); + if (r < 0) + return (void) log_debug_errno(r, "Failed to save route to nexthop, ignoring: %m"); + if (r == 0) + return (void) log_debug("Duplicated route assigned to nexthop, ignoring."); + + route_ref(route); +} + +static void route_nexthop_done(RouteNextHop *nh) { + assert(nh); + + free(nh->ifname); +} + +RouteNextHop* route_nexthop_free(RouteNextHop *nh) { + if (!nh) + return NULL; + + route_nexthop_done(nh); + + return mfree(nh); +} + +void route_nexthops_done(Route *route) { + assert(route); + + route_nexthop_done(&route->nexthop); + ordered_set_free(route->nexthops); +} + +static void route_nexthop_hash_func_full(const RouteNextHop *nh, struct siphash *state, bool with_weight) { + assert(nh); + assert(state); + + /* See nh_comp() in net/ipv4/fib_semantics.c of the kernel. */ + + siphash24_compress_typesafe(nh->family, state); + if (!IN_SET(nh->family, AF_INET, AF_INET6)) + return; + + in_addr_hash_func(&nh->gw, nh->family, state); + if (with_weight) + siphash24_compress_typesafe(nh->weight, state); + siphash24_compress_typesafe(nh->ifindex, state); + if (nh->ifindex == 0) + siphash24_compress_string(nh->ifname, state); /* For Network or Request object. */ +} + +static int route_nexthop_compare_func_full(const RouteNextHop *a, const RouteNextHop *b, bool with_weight) { + int r; + + assert(a); + assert(b); + + r = CMP(a->family, b->family); + if (r != 0) + return r; + + if (!IN_SET(a->family, AF_INET, AF_INET6)) + return 0; + + r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); + if (r != 0) + return r; + + if (with_weight) { + r = CMP(a->weight, b->weight); + if (r != 0) + return r; + } + + r = CMP(a->ifindex, b->ifindex); + if (r != 0) + return r; + + if (a->ifindex == 0) { + r = strcmp_ptr(a->ifname, b->ifname); + if (r != 0) + return r; + } + + return 0; +} + +static void route_nexthop_hash_func(const RouteNextHop *nh, struct siphash *state) { + route_nexthop_hash_func_full(nh, state, /* with_weight = */ true); +} + +static int route_nexthop_compare_func(const RouteNextHop *a, const RouteNextHop *b) { + return route_nexthop_compare_func_full(a, b, /* with_weight = */ true); +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_nexthop_hash_ops, + RouteNextHop, + route_nexthop_hash_func, + route_nexthop_compare_func, + route_nexthop_free); + +static size_t route_n_nexthops(const Route *route) { + if (route->nexthop_id != 0 || route_type_is_reject(route)) + return 0; + + if (ordered_set_isempty(route->nexthops)) + return 1; + + return ordered_set_size(route->nexthops); +} + +void route_nexthops_hash_func(const Route *route, struct siphash *state) { + assert(route); + + size_t nhs = route_n_nexthops(route); + siphash24_compress_typesafe(nhs, state); + + switch (nhs) { + case 0: + siphash24_compress_typesafe(route->nexthop_id, state); + return; + + case 1: + route_nexthop_hash_func_full(&route->nexthop, state, /* with_weight = */ false); + return; + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + route_nexthop_hash_func(nh, state); + }} +} + +int route_nexthops_compare_func(const Route *a, const Route *b) { + int r; + + assert(a); + assert(b); + + size_t a_nhs = route_n_nexthops(a); + size_t b_nhs = route_n_nexthops(b); + r = CMP(a_nhs, b_nhs); + if (r != 0) + return r; + + switch (a_nhs) { + case 0: + return CMP(a->nexthop_id, b->nexthop_id); + + case 1: + return route_nexthop_compare_func_full(&a->nexthop, &b->nexthop, /* with_weight = */ false); + + default: { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, a->nexthops) { + r = CMP(nh, (RouteNextHop*) ordered_set_get(a->nexthops, nh)); + if (r != 0) + return r; + } + return 0; + }} +} + +static int route_nexthop_copy(const RouteNextHop *src, RouteNextHop *dest) { + assert(src); + assert(dest); + + *dest = *src; + + /* unset pointer copied in the above. */ + dest->ifname = NULL; + + return strdup_to(&dest->ifname, src->ifindex > 0 ? NULL : src->ifname); +} + +static int route_nexthop_dup(const RouteNextHop *src, RouteNextHop **ret) { + _cleanup_(route_nexthop_freep) RouteNextHop *dest = NULL; + int r; + + assert(src); + assert(ret); + + dest = new(RouteNextHop, 1); + if (!dest) + return -ENOMEM; + + r = route_nexthop_copy(src, dest); + if (r < 0) + return r; + + *ret = TAKE_PTR(dest); + return 0; +} + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest) { + int r; + + assert(src); + assert(dest); + + if (src->nexthop_id != 0 || route_type_is_reject(src)) + return 0; + + if (nh) + return route_nexthop_copy(nh, &dest->nexthop); + + if (ordered_set_isempty(src->nexthops)) + return route_nexthop_copy(&src->nexthop, &dest->nexthop); + + ORDERED_SET_FOREACH(nh, src->nexthops) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh_dup = NULL; + + r = route_nexthop_dup(nh, &nh_dup); + if (r < 0) + return r; + + r = ordered_set_ensure_put(&dest->nexthops, &route_nexthop_hash_ops, nh_dup); + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh_dup); + } + + return 0; +} + +static bool multipath_routes_needs_adjust(const Route *route) { + assert(route); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (route->nexthop.ifindex == 0) + return true; + + return false; +} + +bool route_nexthops_needs_adjust(const Route *route) { + assert(route); + + if (route->nexthop_id != 0) + /* At this stage, the nexthop may not be configured, or may be under reconfiguring. + * Hence, we cannot know if the nexthop is blackhole or not. */ + return route->type != RTN_BLACKHOLE; + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route->nexthop.ifindex == 0; + + return multipath_routes_needs_adjust(route); +} + +static bool route_nexthop_set_ifindex(RouteNextHop *nh, Link *link) { + assert(nh); + assert(link); + assert(link->manager); + + if (nh->ifindex > 0) { + nh->ifname = mfree(nh->ifname); + return false; + } + + /* If an interface name is specified, use it. Otherwise, use the interface that requests this route. */ + if (nh->ifname && link_get_by_name(link->manager, nh->ifname, &link) < 0) + return false; + + nh->ifindex = link->ifindex; + nh->ifname = mfree(nh->ifname); + return true; /* updated */ +} + +int route_adjust_nexthops(Route *route, Link *link) { + int r; + + assert(route); + assert(link); + assert(link->manager); + + /* When an IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel sends + * RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type fib_rt_info::type + * may not be blackhole. Thus, we cannot know the internal value. Moreover, on route removal, the + * matching is done with the hidden value if we set non-zero type in RTM_DELROUTE message. So, + * here let's set route type to BLACKHOLE when the nexthop is blackhole. */ + if (route->nexthop_id != 0) { + NextHop *nexthop; + + r = nexthop_is_ready(link->manager, route->nexthop_id, &nexthop); + if (r <= 0) + return r; /* r == 0 means the nexthop is under (re-)configuring. + * We cannot use the currently remembered information. */ + + if (!nexthop->blackhole) + return false; + + if (route->type == RTN_BLACKHOLE) + return false; + + route->type = RTN_BLACKHOLE; + return true; /* updated */ + } + + if (route_type_is_reject(route)) + return false; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_set_ifindex(&route->nexthop, link); + + if (!multipath_routes_needs_adjust(route)) + return false; + + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + for (;;) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + nh = ordered_set_steal_first(route->nexthops); + if (!nh) + break; + + (void) route_nexthop_set_ifindex(nh, link); + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + if (r == -EEXIST) + continue; /* Duplicated? Let's drop the nexthop. */ + if (r < 0) + return r; + assert(r > 0); + + TAKE_PTR(nh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return true; /* updated */ +} + +int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret) { + assert(manager); + assert(nh); + + if (nh->ifindex > 0) + return link_get_by_index(manager, nh->ifindex, ret); + if (nh->ifname) + return link_get_by_name(manager, nh->ifname, ret); + + return -ENOENT; +} + +static bool route_nexthop_is_ready_to_configure(const RouteNextHop *nh, Manager *manager, bool onlink) { + Link *link; + + assert(nh); + assert(manager); + + if (route_nexthop_get_link(manager, nh, &link) < 0) + return false; + + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ true)) + return false; + + /* If the interface is not managed by us, we request that the interface has carrier. + * That is, ConfigureWithoutCarrier=no is the default even for unamanaged interfaces. */ + if (!link->network && !link_has_carrier(link)) + return false; + + return gateway_is_ready(link, onlink, nh->family, &nh->gw); +} + +int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager) { + int r; + + assert(route); + assert(manager); + + if (route->nexthop_id != 0) { + struct nexthop_grp *nhg; + NextHop *nh; + + r = nexthop_is_ready(manager, route->nexthop_id, &nh); + if (r <= 0) + return r; + + HASHMAP_FOREACH(nhg, nh->group) { + r = nexthop_is_ready(manager, nhg->id, NULL); + if (r <= 0) + return r; + } + + return true; + } + + if (route_type_is_reject(route)) + return true; + + if (ordered_set_isempty(route->nexthops)) + return route_nexthop_is_ready_to_configure(&route->nexthop, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK)); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (!route_nexthop_is_ready_to_configure(nh, manager, FLAGS_SET(route->flags, RTNH_F_ONLINK))) + return false; + + return true; +} + +int route_nexthops_to_string(const Route *route, char **ret) { + _cleanup_free_ char *buf = NULL; + int r; + + assert(route); + assert(ret); + + if (route->nexthop_id != 0) { + if (asprintf(&buf, "nexthop: %"PRIu32, route->nexthop_id) < 0) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (route_type_is_reject(route)) { + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + if (ordered_set_isempty(route->nexthops)) { + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) + buf = strjoin("gw: ", IN_ADDR_TO_STRING(route->nexthop.family, &route->nexthop.gw)); + else if (route->gateway_from_dhcp_or_ra) { + if (route->nexthop.family == AF_INET) + buf = strdup("gw: _dhcp4"); + else if (route->nexthop.family == AF_INET6) + buf = strdup("gw: _ipv6ra"); + else + buf = strdup("gw: _dhcp"); + } else + buf = strdup("gw: n/a"); + if (!buf) + return -ENOMEM; + + *ret = TAKE_PTR(buf); + return 0; + } + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + const char *s = in_addr_is_set(nh->family, &nh->gw) ? IN_ADDR_TO_STRING(nh->family, &nh->gw) : NULL; + + if (nh->ifindex > 0) + r = strextendf_with_separator(&buf, ",", "%s@%i:%"PRIu32, strempty(s), nh->ifindex, nh->weight + 1); + else if (nh->ifname) + r = strextendf_with_separator(&buf, ",", "%s@%s:%"PRIu32, strempty(s), nh->ifname, nh->weight + 1); + else + r = strextendf_with_separator(&buf, ",", "%s:%"PRIu32, strempty(s), nh->weight + 1); + if (r < 0) + return r; + } + + char *p = strjoin("gw: ", strna(buf)); + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static int append_nexthop_one(const Route *route, const RouteNextHop *nh, struct rtattr **rta, size_t offset) { + struct rtnexthop *rtnh; + struct rtattr *new_rta; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(nh); + assert(rta); + assert(*rta); + + new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); + if (!new_rta) + return -ENOMEM; + *rta = new_rta; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + *rtnh = (struct rtnexthop) { + .rtnh_len = sizeof(*rtnh), + .rtnh_ifindex = nh->ifindex, + .rtnh_hops = nh->weight, + }; + + (*rta)->rta_len += sizeof(struct rtnexthop); + + if (nh->family == route->family) { + r = rtattr_append_attribute(rta, RTA_GATEWAY, &nh->gw, FAMILY_ADDRESS_SIZE(nh->family)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(nh->family)); + + } else if (nh->family == AF_INET6) { + assert(route->family == AF_INET); + + r = rtattr_append_attribute(rta, RTA_VIA, + &(RouteVia) { + .family = nh->family, + .address = nh->gw, + }, sizeof(RouteVia)); + if (r < 0) + goto clear; + + rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); + rtnh->rtnh_len += RTA_SPACE(sizeof(RouteVia)); + + } else if (nh->family == AF_INET) + assert_not_reached(); + + return 0; + +clear: + (*rta)->rta_len -= sizeof(struct rtnexthop); + return r; +} + +static int netlink_message_append_multipath_route(const Route *route, sd_netlink_message *message) { + _cleanup_free_ struct rtattr *rta = NULL; + size_t offset; + int r; + + assert(route); + assert(message); + + rta = new(struct rtattr, 1); + if (!rta) + return -ENOMEM; + + *rta = (struct rtattr) { + .rta_type = RTA_MULTIPATH, + .rta_len = RTA_LENGTH(0), + }; + offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; + + if (ordered_set_isempty(route->nexthops)) { + r = append_nexthop_one(route, &route->nexthop, &rta, offset); + if (r < 0) + return r; + + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + struct rtnexthop *rtnh; + + r = append_nexthop_one(route, nh, &rta, offset); + if (r < 0) + return r; + + rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); + offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; + } + } + + return sd_netlink_message_append_data(message, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); +} + +int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(message); + + if (route->nexthop_id != 0) + return sd_netlink_message_append_u32(message, RTA_NH_ID, route->nexthop_id); + + if (route_type_is_reject(route)) + return 0; + + /* We request IPv6 multipath routes separately. Even though, if weight is non-zero, we need to use + * RTA_MULTIPATH, as we have no way to specify the weight of the nexthop. */ + if (ordered_set_isempty(route->nexthops) && route->nexthop.weight == 0) { + + if (in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) { + if (route->nexthop.family == route->family) + r = netlink_message_append_in_addr_union(message, RTA_GATEWAY, route->nexthop.family, &route->nexthop.gw); + else { + assert(route->family == AF_INET); + r = sd_netlink_message_append_data(message, RTA_VIA, + &(const RouteVia) { + .family = route->nexthop.family, + .address = route->nexthop.gw, + }, sizeof(RouteVia)); + } + if (r < 0) + return r; + } + + assert(route->nexthop.ifindex > 0); + return sd_netlink_message_append_u32(message, RTA_OIF, route->nexthop.ifindex); + } + + return netlink_message_append_multipath_route(route, message); +} + +static int route_parse_nexthops(Route *route, const struct rtnexthop *rtnh, size_t size) { + _cleanup_ordered_set_free_ OrderedSet *nexthops = NULL; + int r; + + assert(route); + assert(IN_SET(route->family, AF_INET, AF_INET6)); + assert(rtnh); + + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; + + for (; size >= sizeof(struct rtnexthop); ) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = NULL; + + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; + + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + nh = new(RouteNextHop, 1); + if (!nh) + return -ENOMEM; + + *nh = (RouteNextHop) { + .ifindex = rtnh->rtnh_ifindex, + .weight = rtnh->rtnh_hops, + }; + + if (rtnh->rtnh_len > sizeof(struct rtnexthop)) { + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + bool have_gw = false; + + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) { + + switch (attr->rta_type) { + case RTA_GATEWAY: + if (have_gw) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(route->family))) + return -EBADMSG; + + nh->family = route->family; + memcpy(&nh->gw, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(nh->family)); + have_gw = true; + break; + + case RTA_VIA: + if (have_gw) + return -EBADMSG; + + if (route->family != AF_INET) + return -EBADMSG; + + if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia))) + return -EBADMSG; + + RouteVia *via = RTA_DATA(attr); + if (via->family != AF_INET6) + return -EBADMSG; + + nh->family = via->family; + nh->gw = via->address; + have_gw = true; + break; + } + } + } + + r = ordered_set_ensure_put(&nexthops, &route_nexthop_hash_ops, nh); + assert(r != 0); + if (r > 0) + TAKE_PTR(nh); + else if (r != -EEXIST) + return r; + + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } + + ordered_set_free(route->nexthops); + route->nexthops = TAKE_PTR(nexthops); + return 0; +} + +int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message) { + int r; + + assert(route); + assert(message); + + r = sd_netlink_message_read_u32(message, RTA_NH_ID, &route->nexthop_id); + if (r < 0 && r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); + + if (route->nexthop_id != 0 || route_type_is_reject(route)) + /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's + * fib6_nh_init() in net/ipv6/route.c. However, we'd like to make it consistent with IPv4 + * routes. Hence, skip reading of RTA_OIF. */ + return 0; + + uint32_t ifindex; + r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); + if (r >= 0) + route->nexthop.ifindex = (int) ifindex; + else if (r != -ENODATA) + return log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); + + if (route->nexthop.ifindex > 0) { + r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, route->family, &route->nexthop.gw); + if (r >= 0) { + route->nexthop.family = route->family; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + if (route->family != AF_INET) + return 0; + + RouteVia via; + r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); + if (r >= 0) { + route->nexthop.family = via.family; + route->nexthop.gw = via.address; + return 0; + } + if (r != -ENODATA) + return log_warning_errno(r, "rtnl: received route message without valid gateway, ignoring: %m"); + + return 0; + } + + size_t rta_len; + _cleanup_free_ void *rta = NULL; + r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta); + if (r == -ENODATA) + return 0; + if (r < 0) + return log_warning_errno(r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); + + r = route_parse_nexthops(route, rta, rta_len); + if (r < 0) + return log_warning_errno(r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); + + return 0; +} + +int route_section_verify_nexthops(Route *route) { + assert(route); + assert(route->section); + + if (route->gateway_from_dhcp_or_ra) { + assert(route->network); + + if (route->nexthop.family == AF_UNSPEC) + /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ + switch (route->family) { + case AF_UNSPEC: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", + route->section->filename, route->section->line); + + route->nexthop.family = route->family = AF_INET; + break; + case AF_INET: + case AF_INET6: + log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " + "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", + route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); + + route->nexthop.family = route->family; + break; + default: + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Invalid route family. Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop.family == AF_INET && !FLAGS_SET(route->network->dhcp, ADDRESS_FAMILY_IPV4)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route->nexthop.family == AF_INET6 && route->network->ndisc == 0) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + /* When only Gateway= is specified, assume the route family based on the Gateway address. */ + if (route->family == AF_UNSPEC) + route->family = route->nexthop.family; + + if (route->family == AF_UNSPEC) { + assert(route->section); + + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Route section without Gateway=, Destination=, Source=, " + "or PreferredSource= field configured. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->gateway_onlink < 0 && in_addr_is_set(route->nexthop.family, &route->nexthop.gw) && + route->network && ordered_hashmap_isempty(route->network->addresses_by_section)) { + /* If no address is configured, in most cases the gateway cannot be reachable. + * TODO: we may need to improve the condition above. */ + log_warning("%s: Gateway= without static address configured. " + "Enabling GatewayOnLink= option.", + route->section->filename); + route->gateway_onlink = true; + } + + if (route->gateway_onlink >= 0) + SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); + + if (route->family == AF_INET6) { + if (route->nexthop.family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 gateway is configured for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) + if (nh->family == AF_INET) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: IPv4 multipath route is specified for IPv6 route. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + } + + if (route->nexthop_id != 0 && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (route_type_is_reject(route) && + (route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw) || + !ordered_set_isempty(route->nexthops))) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: reject type route cannot be specified with Gateway= or MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if ((route->gateway_from_dhcp_or_ra || + in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) && + !ordered_set_isempty(route->nexthops)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "%s: Gateway= cannot be specified with MultiPathRoute=. " + "Ignoring [Route] section from line %u.", + route->section->filename, route->section->line); + + if (ordered_set_size(route->nexthops) == 1) { + _cleanup_(route_nexthop_freep) RouteNextHop *nh = ordered_set_steal_first(route->nexthops); + + route_nexthop_done(&route->nexthop); + route->nexthop = TAKE_STRUCT(*nh); + + assert(ordered_set_isempty(route->nexthops)); + route->nexthops = ordered_set_free(route->nexthops); + } + + return 0; +} + +int config_parse_gateway( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(section, "Network")) { + /* we are not in an Route section, so use line number instead */ + r = route_new_static(network, filename, line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + } else { + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->gateway_from_dhcp_or_ra = false; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_UNSPEC; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_dhcp4")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + + if (streq(rvalue, "_ipv6ra")) { + route->gateway_from_dhcp_or_ra = true; + route->nexthop.family = AF_INET6; + route->nexthop.gw = IN_ADDR_NULL; + TAKE_PTR(route); + return 0; + } + } + + r = in_addr_from_string_auto(rvalue, &route->nexthop.family, &route->nexthop.gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } + + route->gateway_from_dhcp_or_ra = false; + TAKE_PTR(route); + return 0; +} + +int config_parse_route_gateway_onlink( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + r = config_parse_tristate(unit, filename, line, section, section_line, lvalue, ltype, rvalue, + &route->gateway_onlink, network); + if (r <= 0) + return r; + + TAKE_PTR(route); + return 0; +} + +int config_parse_route_nexthop( + 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) { + + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + uint32_t id; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthop_id = 0; + TAKE_PTR(route); + return 0; + } + + r = safe_atou32(rvalue, &id); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + if (id == 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue); + return 0; + } + + route->nexthop_id = id; + TAKE_PTR(route); + return 0; +} + +int config_parse_multipath_route( + 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_(route_nexthop_freep) RouteNextHop *nh = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + _cleanup_free_ char *word = NULL; + Network *network = userdata; + const char *p; + char *dev; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to allocate route, ignoring assignment: %m"); + return 0; + } + + if (isempty(rvalue)) { + route->nexthops = ordered_set_free(route->nexthops); + TAKE_PTR(route); + return 0; + } + + nh = new0(RouteNextHop, 1); + if (!nh) + return log_oom(); + + p = rvalue; + r = extract_first_word(&p, &word, NULL, 0); + if (r == -ENOMEM) + return log_oom(); + if (r <= 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route option, ignoring assignment: %s", rvalue); + return 0; + } + + dev = strchr(word, '@'); + if (dev) { + *dev++ = '\0'; + + r = parse_ifindex(dev); + if (r > 0) + nh->ifindex = r; + else { + if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue); + return 0; + } + + nh->ifname = strdup(dev); + if (!nh->ifname) + return log_oom(); + } + } + + r = in_addr_from_string_auto(word, &nh->family, &nh->gw); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + return 0; + } + + if (!isempty(p)) { + r = safe_atou32(p, &nh->weight); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + /* ip command takes weight in the range 1…255, while kernel takes the value in the + * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip + * command uses, then networkd decreases by one and stores it to match the range which + * kernel uses. */ + if (nh->weight == 0 || nh->weight > 256) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid multipath route weight, ignoring assignment: %s", p); + return 0; + } + nh->weight--; + } + + r = ordered_set_ensure_put(&route->nexthops, &route_nexthop_hash_ops, nh); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to store multipath route, ignoring assignment: %m"); + return 0; + } + + TAKE_PTR(nh); + TAKE_PTR(route); + return 0; +} diff --git a/src/network/networkd-route-nexthop.h b/src/network/networkd-route-nexthop.h new file mode 100644 index 0000000..f9a1478 --- /dev/null +++ b/src/network/networkd-route-nexthop.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +#include "sd-netlink.h" + +#include "conf-parser.h" +#include "in-addr-util.h" +#include "macro.h" +#include "siphash24.h" + +typedef struct Link Link; +typedef struct Manager Manager; +typedef struct Route Route; + +typedef struct RouteNextHop { + int family; /* used in RTA_VIA (IPv4 only) */ + union in_addr_union gw; /* RTA_GATEWAY or RTA_VIA (IPv4 only) */ + uint32_t weight; /* rtnh_hops */ + int ifindex; /* RTA_OIF(u32) or rtnh_ifindex */ + char *ifname; /* only used by Route object owned by Network object */ + /* unsupported attributes: RTA_FLOW (IPv4 only), RTA_ENCAP_TYPE, RTA_ENCAP. */ +} RouteNextHop; + +#define ROUTE_NEXTHOP_NULL ((const RouteNextHop) {}) + +void route_detach_from_nexthop(Route *route); +void route_attach_to_nexthop(Route *route); + +RouteNextHop* route_nexthop_free(RouteNextHop *nh); +DEFINE_TRIVIAL_CLEANUP_FUNC(RouteNextHop*, route_nexthop_free); + +void route_nexthops_done(Route *route); + +void route_nexthops_hash_func(const Route *route, struct siphash *state); +int route_nexthops_compare_func(const Route *a, const Route *b); + +int route_nexthops_copy(const Route *src, const RouteNextHop *nh, Route *dest); +bool route_nexthops_needs_adjust(const Route *route); +int route_adjust_nexthops(Route *route, Link *link); + +int route_nexthop_get_link(Manager *manager, const RouteNextHop *nh, Link **ret); +int route_nexthops_is_ready_to_configure(const Route *route, Manager *manager); + +int route_nexthops_to_string(const Route *route, char **ret); + +int route_nexthops_set_netlink_message(const Route *route, sd_netlink_message *message); +int route_nexthops_read_netlink_message(Route *route, sd_netlink_message *message); + +int route_section_verify_nexthops(Route *route); + +CONFIG_PARSER_PROTOTYPE(config_parse_gateway); +CONFIG_PARSER_PROTOTYPE(config_parse_route_gateway_onlink); +CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop); +CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); diff --git a/src/network/networkd-route-util.c b/src/network/networkd-route-util.c index d49a0b9..b7e07f4 100644 --- a/src/network/networkd-route-util.c +++ b/src/network/networkd-route-util.c @@ -39,6 +39,12 @@ unsigned routes_max(void) { return cached; } +bool route_type_is_reject(const Route *route) { + assert(route); + + return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); +} + static bool route_lifetime_is_valid(const Route *route) { assert(route); @@ -52,8 +58,11 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { Route *route; assert(link); + assert(link->manager); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; if (!route_exists(route)) continue; if (family != AF_UNSPEC && route->family != family) @@ -68,7 +77,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { continue; if (route->scope != RT_SCOPE_UNIVERSE) continue; - if (!in_addr_is_set(route->gw_family, &route->gw)) + if (!in_addr_is_set(route->nexthop.family, &route->nexthop.gw)) continue; /* Found a default gateway. */ @@ -77,7 +86,7 @@ bool link_find_default_gateway(Link *link, int family, Route **gw) { /* If we have already found another gw, then let's compare their weight and priority. */ if (*gw) { - if (route->gw_weight > (*gw)->gw_weight) + if (route->nexthop.weight > (*gw)->nexthop.weight) continue; if (route->priority >= (*gw)->priority) continue; @@ -113,12 +122,7 @@ int manager_find_uplink(Manager *m, int family, Link *exclude, Link **ret) { if (!gw) return -ENOENT; - if (ret) { - assert(gw->link); - *ret = gw->link; - } - - return 0; + return link_get_by_index(m, gw->nexthop.ifindex, ret); } bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_union *gw) { @@ -137,7 +141,9 @@ bool gateway_is_ready(Link *link, bool onlink, int family, const union in_addr_u if (family == AF_INET6 && in6_addr_is_link_local(&gw->in6)) return true; - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; if (!route_exists(route)) continue; if (!route_lifetime_is_valid(route)) @@ -181,10 +187,14 @@ static int link_address_is_reachable_internal( Route *route, *found = NULL; assert(link); + assert(link->manager); assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); - SET_FOREACH(route, link->routes) { + SET_FOREACH(route, link->manager->routes) { + if (route->nexthop.ifindex != link->ifindex) + continue; + if (!route_exists(route)) continue; @@ -298,7 +308,11 @@ int manager_address_is_reachable( return 0; } - r = link_get_address(found->link, found->family, &found->prefsrc, 0, &a); + r = link_get_by_index(manager, found->nexthop.ifindex, &link); + if (r < 0) + return r; + + r = link_get_address(link, found->family, &found->prefsrc, 0, &a); if (r < 0) return r; diff --git a/src/network/networkd-route-util.h b/src/network/networkd-route-util.h index f326888..eba823a 100644 --- a/src/network/networkd-route-util.h +++ b/src/network/networkd-route-util.h @@ -13,6 +13,8 @@ typedef struct Route Route; unsigned routes_max(void); +bool route_type_is_reject(const Route *route); + bool link_find_default_gateway(Link *link, int family, Route **gw); static inline bool link_has_default_gateway(Link *link, int family) { return link_find_default_gateway(link, family, NULL); diff --git a/src/network/networkd-route.c b/src/network/networkd-route.c index eb502ae..d596fd8 100644 --- a/src/network/networkd-route.c +++ b/src/network/networkd-route.c @@ -1,6 +1,5 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include #include #include @@ -21,133 +20,105 @@ #include "vrf.h" #include "wireguard.h" -int route_new(Route **ret) { - _cleanup_(route_freep) Route *route = NULL; - - route = new(Route, 1); - if (!route) - return -ENOMEM; - - *route = (Route) { - .family = AF_UNSPEC, - .scope = RT_SCOPE_UNIVERSE, - .protocol = RTPROT_UNSPEC, - .type = RTN_UNICAST, - .table = RT_TABLE_MAIN, - .lifetime_usec = USEC_INFINITY, - .quickack = -1, - .fast_open_no_cookie = -1, - .gateway_onlink = -1, - .ttl_propagate = -1, - }; - - *ret = TAKE_PTR(route); - - return 0; -} - -static int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { - _cleanup_(config_section_freep) ConfigSection *n = NULL; - _cleanup_(route_freep) Route *route = NULL; - int r; - - assert(network); - assert(ret); - assert(filename); - assert(section_line > 0); - - r = config_section_new(filename, section_line, &n); - if (r < 0) - return r; +static Route* route_detach_impl(Route *route) { + assert(route); + assert(!!route->network + !!route->manager + !!route->wireguard <= 1); - route = hashmap_get(network->routes_by_section, n); - if (route) { - *ret = TAKE_PTR(route); - return 0; + if (route->network) { + assert(route->section); + hashmap_remove(route->network->routes_by_section, route->section); + route->network = NULL; + return route; } - if (hashmap_size(network->routes_by_section) >= routes_max()) - return -E2BIG; - - r = route_new(&route); - if (r < 0) - return r; + if (route->manager) { + route_detach_from_nexthop(route); + set_remove(route->manager->routes, route); + route->manager = NULL; + return route; + } - route->protocol = RTPROT_STATIC; - route->network = network; - route->section = TAKE_PTR(n); - route->source = NETWORK_CONFIG_SOURCE_STATIC; + if (route->wireguard) { + set_remove(route->wireguard->routes, route); + route->wireguard = NULL; + return route; + } - r = hashmap_ensure_put(&network->routes_by_section, &config_section_hash_ops, route->section, route); - if (r < 0) - return r; + return NULL; +} - *ret = TAKE_PTR(route); - return 0; +static void route_detach(Route *route) { + route_unref(route_detach_impl(route)); } -Route *route_free(Route *route) { +static Route* route_free(Route *route) { if (!route) return NULL; - if (route->network) { - assert(route->section); - hashmap_remove(route->network->routes_by_section, route->section); - } + route_detach_impl(route); config_section_free(route->section); - - if (route->link) - set_remove(route->link->routes, route); - - if (route->manager) - set_remove(route->manager->routes, route); - - ordered_set_free_with_destructor(route->multipath_routes, multipath_route_free); - + route_nexthops_done(route); + route_metric_done(&route->metric); sd_event_source_disable_unref(route->expire); - free(route->tcp_congestion_control_algo); - return mfree(route); } +DEFINE_TRIVIAL_REF_UNREF_FUNC(Route, route, route_free); + static void route_hash_func(const Route *route, struct siphash *state) { assert(route); - siphash24_compress(&route->family, sizeof(route->family), state); + siphash24_compress_typesafe(route->family, state); switch (route->family) { case AF_INET: - case AF_INET6: - siphash24_compress(&route->dst_prefixlen, sizeof(route->dst_prefixlen), state); - siphash24_compress(&route->dst, FAMILY_ADDRESS_SIZE(route->family), state); - - siphash24_compress(&route->src_prefixlen, sizeof(route->src_prefixlen), state); - siphash24_compress(&route->src, FAMILY_ADDRESS_SIZE(route->family), state); - - siphash24_compress(&route->gw_family, sizeof(route->gw_family), state); - if (IN_SET(route->gw_family, AF_INET, AF_INET6)) { - siphash24_compress(&route->gw, FAMILY_ADDRESS_SIZE(route->gw_family), state); - siphash24_compress(&route->gw_weight, sizeof(route->gw_weight), state); - } + /* First, the table, destination prefix, priority, and tos (dscp), are used to find routes. + * See fib_table_insert(), fib_find_node(), and fib_find_alias() in net/ipv4/fib_trie.c of the kernel. */ + siphash24_compress_typesafe(route->table, state); + in_addr_hash_func(&route->dst, route->family, state); + siphash24_compress_typesafe(route->dst_prefixlen, state); + siphash24_compress_typesafe(route->priority, state); + siphash24_compress_typesafe(route->tos, state); + + /* Then, protocol, scope, type, flags, prefsrc, metrics (RTAX_* attributes), and nexthops (gateways) + * are used to find routes. See fib_find_info() in net/ipv4/fib_semantics.c of the kernel. */ + siphash24_compress_typesafe(route->protocol, state); + siphash24_compress_typesafe(route->scope, state); + siphash24_compress_typesafe(route->type, state); + unsigned flags = route->flags & ~RTNH_COMPARE_MASK; + siphash24_compress_typesafe(flags, state); + in_addr_hash_func(&route->prefsrc, route->family, state); + + /* nexthops (id, number of nexthops, nexthop) */ + route_nexthops_hash_func(route, state); + + /* metrics */ + route_metric_hash_func(&route->metric, state); + break; - siphash24_compress(&route->prefsrc, FAMILY_ADDRESS_SIZE(route->family), state); + case AF_INET6: + /* First, table and destination prefix are used for classifying routes. + * See fib6_add() and fib6_add_1() in net/ipv6/ip6_fib.c of the kernel. */ + siphash24_compress_typesafe(route->table, state); + in_addr_hash_func(&route->dst, route->family, state); + siphash24_compress_typesafe(route->dst_prefixlen, state); - siphash24_compress(&route->tos, sizeof(route->tos), state); - siphash24_compress(&route->priority, sizeof(route->priority), state); - siphash24_compress(&route->table, sizeof(route->table), state); - siphash24_compress(&route->protocol, sizeof(route->protocol), state); - siphash24_compress(&route->scope, sizeof(route->scope), state); - siphash24_compress(&route->type, sizeof(route->type), state); + /* Then, source prefix is used. See fib6_add(). */ + in_addr_hash_func(&route->src, route->family, state); + siphash24_compress_typesafe(route->src_prefixlen, state); - siphash24_compress(&route->initcwnd, sizeof(route->initcwnd), state); - siphash24_compress(&route->initrwnd, sizeof(route->initrwnd), state); + /* See fib6_add_rt2node(). */ + siphash24_compress_typesafe(route->priority, state); - siphash24_compress(&route->advmss, sizeof(route->advmss), state); - siphash24_compress(&route->nexthop_id, sizeof(route->nexthop_id), state); + /* See rt6_duplicate_nexthop() in include/net/ip6_route.h of the kernel. + * Here, we hash nexthop in a similar way as the one for IPv4. */ + route_nexthops_hash_func(route, state); + /* Unlike IPv4 routes, metrics are not taken into account. */ break; + default: /* treat any other address family as AF_UNSPEC */ break; @@ -163,8 +134,7 @@ static int route_compare_func(const Route *a, const Route *b) { switch (a->family) { case AF_INET: - case AF_INET6: - r = CMP(a->dst_prefixlen, b->dst_prefixlen); + r = CMP(a->table, b->table); if (r != 0) return r; @@ -172,73 +142,71 @@ static int route_compare_func(const Route *a, const Route *b) { if (r != 0) return r; - r = CMP(a->src_prefixlen, b->src_prefixlen); + r = CMP(a->dst_prefixlen, b->dst_prefixlen); if (r != 0) return r; - r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->priority, b->priority); if (r != 0) return r; - r = CMP(a->gw_family, b->gw_family); + r = CMP(a->tos, b->tos); if (r != 0) return r; - if (IN_SET(a->gw_family, AF_INET, AF_INET6)) { - r = memcmp(&a->gw, &b->gw, FAMILY_ADDRESS_SIZE(a->family)); - if (r != 0) - return r; - - r = CMP(a->gw_weight, b->gw_weight); - if (r != 0) - return r; - } + r = CMP(a->protocol, b->protocol); + if (r != 0) + return r; - r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); + r = CMP(a->scope, b->scope); if (r != 0) return r; - r = CMP(a->tos, b->tos); + r = CMP(a->type, b->type); if (r != 0) return r; - r = CMP(a->priority, b->priority); + r = CMP(a->flags & ~RTNH_COMPARE_MASK, b->flags & ~RTNH_COMPARE_MASK); if (r != 0) return r; - r = CMP(a->table, b->table); + r = memcmp(&a->prefsrc, &b->prefsrc, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->protocol, b->protocol); + r = route_nexthops_compare_func(a, b); if (r != 0) return r; - r = CMP(a->scope, b->scope); + return route_metric_compare_func(&a->metric, &b->metric); + + case AF_INET6: + r = CMP(a->table, b->table); if (r != 0) return r; - r = CMP(a->type, b->type); + r = memcmp(&a->dst, &b->dst, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->initcwnd, b->initcwnd); + r = CMP(a->dst_prefixlen, b->dst_prefixlen); if (r != 0) return r; - r = CMP(a->initrwnd, b->initrwnd); + r = memcmp(&a->src, &b->src, FAMILY_ADDRESS_SIZE(a->family)); if (r != 0) return r; - r = CMP(a->advmss, b->advmss); + r = CMP(a->src_prefixlen, b->src_prefixlen); if (r != 0) return r; - r = CMP(a->nexthop_id, b->nexthop_id); + r = CMP(a->priority, b->priority); if (r != 0) return r; - return 0; + return route_nexthops_compare_func(a, b); + default: /* treat any other address family as AF_UNSPEC */ return 0; @@ -250,81 +218,164 @@ DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( Route, route_hash_func, route_compare_func, - route_free); + route_detach); -static bool route_type_is_reject(const Route *route) { - assert(route); +DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( + route_hash_ops_unref, + Route, + route_hash_func, + route_compare_func, + route_unref); + +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + route_section_hash_ops, + ConfigSection, + config_section_hash_func, + config_section_compare_func, + Route, + route_detach); + +int route_new(Route **ret) { + _cleanup_(route_unrefp) Route *route = NULL; + + route = new(Route, 1); + if (!route) + return -ENOMEM; + + *route = (Route) { + .n_ref = 1, + .family = AF_UNSPEC, + .scope = RT_SCOPE_UNIVERSE, + .protocol = RTPROT_UNSPEC, + .type = RTN_UNICAST, + .table = RT_TABLE_MAIN, + .lifetime_usec = USEC_INFINITY, + .gateway_onlink = -1, + }; - return IN_SET(route->type, RTN_UNREACHABLE, RTN_PROHIBIT, RTN_BLACKHOLE, RTN_THROW); + *ret = TAKE_PTR(route); + + return 0; } -static bool route_needs_convert(const Route *route) { - assert(route); +int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret) { + _cleanup_(config_section_freep) ConfigSection *n = NULL; + _cleanup_(route_unrefp) Route *route = NULL; + int r; + + assert(network); + assert(ret); + assert(filename); + assert(section_line > 0); + + r = config_section_new(filename, section_line, &n); + if (r < 0) + return r; + + route = hashmap_get(network->routes_by_section, n); + if (route) { + *ret = TAKE_PTR(route); + return 0; + } + + if (hashmap_size(network->routes_by_section) >= routes_max()) + return -E2BIG; + + r = route_new(&route); + if (r < 0) + return r; - return route->nexthop_id > 0 || !ordered_set_isempty(route->multipath_routes); + route->protocol = RTPROT_STATIC; + route->network = network; + route->section = TAKE_PTR(n); + route->source = NETWORK_CONFIG_SOURCE_STATIC; + + r = hashmap_ensure_put(&network->routes_by_section, &route_section_hash_ops, route->section, route); + if (r < 0) + return r; + + *ret = TAKE_PTR(route); + return 0; } -static int route_add(Manager *manager, Link *link, Route *route) { +static int route_attach(Manager *manager, Route *route) { int r; + assert(manager); assert(route); + assert(!route->network); + assert(!route->wireguard); - if (route_type_is_reject(route)) { - assert(manager); + r = set_ensure_put(&manager->routes, &route_hash_ops, route); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; - r = set_ensure_put(&manager->routes, &route_hash_ops, route); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + route->manager = manager; + return 0; +} - route->manager = manager; - } else { - assert(link); +int route_get(Manager *manager, const Route *route, Route **ret) { + Route *existing; - r = set_ensure_put(&link->routes, &route_hash_ops, route); - if (r < 0) - return r; - if (r == 0) - return -EEXIST; + assert(manager); + assert(route); - route->link = link; - } + existing = set_get(manager->routes, route); + if (!existing) + return -ENOENT; + + if (ret) + *ret = existing; return 0; } -int route_get(Manager *manager, Link *link, const Route *in, Route **ret) { - Route *route; +static int route_get_link(Manager *manager, const Route *route, Link **ret) { + int r; - assert(in); + assert(manager); + assert(route); - if (route_type_is_reject(in)) { - if (!manager) - return -ENOENT; + if (route->nexthop_id != 0) { + NextHop *nh; - route = set_get(manager->routes, in); - } else { - if (!link) - return -ENOENT; + r = nexthop_get_by_id(manager, route->nexthop_id, &nh); + if (r < 0) + return r; - route = set_get(link->routes, in); + return link_get_by_index(manager, nh->ifindex, ret); } - if (!route) + + return route_nexthop_get_link(manager, &route->nexthop, ret); +} + +int route_get_request(Manager *manager, const Route *route, Request **ret) { + Request *req; + + assert(manager); + assert(route); + + req = ordered_set_get(manager->request_queue, + &(const Request) { + .type = REQUEST_TYPE_ROUTE, + .userdata = (void*) route, + .hash_func = (hash_func_t) route_hash_func, + .compare_func = (compare_func_t) route_compare_func, + }); + if (!req) return -ENOENT; if (ret) - *ret = route; - + *ret = req; return 0; } -int route_dup(const Route *src, Route **ret) { - _cleanup_(route_freep) Route *dest = NULL; +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret) { + _cleanup_(route_unrefp) Route *dest = NULL; int r; - /* This does not copy mulipath routes. */ - assert(src); assert(ret); @@ -332,16 +383,22 @@ int route_dup(const Route *src, Route **ret) { if (!dest) return -ENOMEM; - /* Unset all pointers */ + /* Unset number of reference and all pointers */ + dest->n_ref = 1; + dest->manager = NULL; dest->network = NULL; + dest->wireguard = NULL; dest->section = NULL; - dest->link = NULL; - dest->manager = NULL; - dest->multipath_routes = NULL; + dest->nexthop = ROUTE_NEXTHOP_NULL; + dest->nexthops = NULL; + dest->metric = ROUTE_METRIC_NULL; dest->expire = NULL; - dest->tcp_congestion_control_algo = NULL; - r = free_and_strdup(&dest->tcp_congestion_control_algo, src->tcp_congestion_control_algo); + r = route_nexthops_copy(src, nh, dest); + if (r < 0) + return r; + + r = route_metric_copy(&src->metric, &dest->metric); if (r < 0) return r; @@ -349,2201 +406,1152 @@ int route_dup(const Route *src, Route **ret) { return 0; } -static void route_apply_nexthop(Route *route, const NextHop *nh, uint8_t nh_weight) { +static void log_route_debug(const Route *route, const char *str, Manager *manager) { + _cleanup_free_ char *state = NULL, *nexthop = NULL, *prefsrc = NULL, + *table = NULL, *scope = NULL, *proto = NULL, *flags = NULL; + const char *dst, *src; + Link *link = NULL; + assert(route); - assert(nh); - assert(hashmap_isempty(nh->group)); + assert(str); + assert(manager); - route->gw_family = nh->family; - route->gw = nh->gw; + if (!DEBUG_LOGGING) + return; - if (nh_weight != UINT8_MAX) - route->gw_weight = nh_weight; + (void) route_get_link(manager, route, &link); - if (nh->blackhole) - route->type = RTN_BLACKHOLE; -} + (void) network_config_state_to_string_alloc(route->state, &state); -static void route_apply_multipath_route(Route *route, const MultipathRoute *m) { - assert(route); - assert(m); + dst = in_addr_is_set(route->family, &route->dst) || route->dst_prefixlen > 0 ? + IN_ADDR_PREFIX_TO_STRING(route->family, &route->dst, route->dst_prefixlen) : NULL; + src = in_addr_is_set(route->family, &route->src) || route->src_prefixlen > 0 ? + IN_ADDR_PREFIX_TO_STRING(route->family, &route->src, route->src_prefixlen) : NULL; + + (void) route_nexthops_to_string(route, &nexthop); + + if (in_addr_is_set(route->family, &route->prefsrc)) + (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); + (void) route_scope_to_string_alloc(route->scope, &scope); + (void) manager_get_route_table_to_string(manager, route->table, /* append_num = */ true, &table); + (void) route_protocol_full_to_string_alloc(route->protocol, &proto); + (void) route_flags_to_string_alloc(route->flags, &flags); - route->gw_family = m->gateway.family; - route->gw = m->gateway.address; - route->gw_weight = m->weight; + log_link_debug(link, + "%s %s route (%s): dst: %s, src: %s, %s, prefsrc: %s, " + "table: %s, priority: %"PRIu32", " + "proto: %s, scope: %s, type: %s, flags: %s", + str, strna(network_config_source_to_string(route->source)), strna(state), + strna(dst), strna(src), strna(nexthop), strna(prefsrc), + strna(table), route->priority, + strna(proto), strna(scope), strna(route_type_to_string(route->type)), strna(flags)); } -static int multipath_route_get_link(Manager *manager, const MultipathRoute *m, Link **ret) { +static int route_set_netlink_message(const Route *route, sd_netlink_message *m) { int r; - assert(manager); + assert(route); assert(m); - if (m->ifname) { - r = link_get_by_name(manager, m->ifname, ret); - return r < 0 ? r : 1; + /* rtmsg header (and relevant attributes) */ + if (route->dst_prefixlen > 0) { + r = netlink_message_append_in_addr_union(m, RTA_DST, route->family, &route->dst); + if (r < 0) + return r; - } else if (m->ifindex > 0) { /* Always ignore ifindex if ifname is set. */ - r = link_get_by_index(manager, m->ifindex, ret); - return r < 0 ? r : 1; + r = sd_rtnl_message_route_set_dst_prefixlen(m, route->dst_prefixlen); + if (r < 0) + return r; } - if (ret) - *ret = NULL; - return 0; -} + if (route->src_prefixlen > 0) { + r = netlink_message_append_in_addr_union(m, RTA_SRC, route->family, &route->src); + if (r < 0) + return r; -typedef struct ConvertedRoutes { - size_t n; - Route **routes; - Link **links; -} ConvertedRoutes; + r = sd_rtnl_message_route_set_src_prefixlen(m, route->src_prefixlen); + if (r < 0) + return r; + } -static ConvertedRoutes *converted_routes_free(ConvertedRoutes *c) { - if (!c) - return NULL; + r = sd_rtnl_message_route_set_tos(m, route->tos); + if (r < 0) + return r; - for (size_t i = 0; i < c->n; i++) - route_free(c->routes[i]); + r = sd_rtnl_message_route_set_scope(m, route->scope); + if (r < 0) + return r; - free(c->routes); - free(c->links); + r = sd_rtnl_message_route_set_type(m, route->type); + if (r < 0) + return r; - return mfree(c); -} + r = sd_rtnl_message_route_set_flags(m, route->flags & ~RTNH_COMPARE_MASK); + if (r < 0) + return r; -DEFINE_TRIVIAL_CLEANUP_FUNC(ConvertedRoutes*, converted_routes_free); + /* attributes */ + r = sd_netlink_message_append_u32(m, RTA_PRIORITY, route->priority); + if (r < 0) + return r; -static int converted_routes_new(size_t n, ConvertedRoutes **ret) { - _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; - _cleanup_free_ Route **routes = NULL; - _cleanup_free_ Link **links = NULL; + if (in_addr_is_set(route->family, &route->prefsrc)) { + r = netlink_message_append_in_addr_union(m, RTA_PREFSRC, route->family, &route->prefsrc); + if (r < 0) + return r; + } - assert(n > 0); - assert(ret); + if (route->table < 256) { + r = sd_rtnl_message_route_set_table(m, route->table); + if (r < 0) + return r; + } else { + r = sd_rtnl_message_route_set_table(m, RT_TABLE_UNSPEC); + if (r < 0) + return r; - routes = new0(Route*, n); - if (!routes) - return -ENOMEM; + /* Table attribute to allow more than 256. */ + r = sd_netlink_message_append_u32(m, RTA_TABLE, route->table); + if (r < 0) + return r; + } - links = new0(Link*, n); - if (!links) - return -ENOMEM; + r = sd_netlink_message_append_u8(m, RTA_PREF, route->pref); + if (r < 0) + return r; - c = new(ConvertedRoutes, 1); - if (!c) - return -ENOMEM; + /* nexthops */ + r = route_nexthops_set_netlink_message(route, m); + if (r < 0) + return r; - *c = (ConvertedRoutes) { - .n = n, - .routes = TAKE_PTR(routes), - .links = TAKE_PTR(links), - }; + /* metrics */ + r = route_metric_set_netlink_message(&route->metric, m); + if (r < 0) + return r; - *ret = TAKE_PTR(c); return 0; } -static int route_convert(Manager *manager, const Route *route, ConvertedRoutes **ret) { - _cleanup_(converted_routes_freep) ConvertedRoutes *c = NULL; +static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, RemoveRequest *rreq) { int r; - assert(manager); - assert(route); - assert(ret); + assert(m); + assert(rreq); - if (!route_needs_convert(route)) { - *ret = NULL; - return 0; + Manager *manager = ASSERT_PTR(rreq->manager); + Route *route = ASSERT_PTR(rreq->userdata); + + r = sd_netlink_message_get_errno(m); + if (r < 0) { + log_message_full_errno(m, + (r == -ESRCH || /* the route is already removed? */ + (r == -EINVAL && route->nexthop_id != 0) || /* The nexthop is already removed? */ + !route->manager) ? /* already detached? */ + LOG_DEBUG : LOG_WARNING, + r, "Could not drop route, ignoring"); + + if (route->manager) { + /* If the route cannot be removed, then assume the route is already removed. */ + log_route_debug(route, "Forgetting", manager); + + Request *req; + if (route_get_request(manager, route, &req) >= 0) + route_enter_removed(req->userdata); + + route_detach(route); + } } - if (route->nexthop_id > 0) { - struct nexthop_grp *nhg; - NextHop *nh; + return 1; +} - r = manager_get_nexthop_by_id(manager, route->nexthop_id, &nh); - if (r < 0) - return r; +int route_remove(Route *route, Manager *manager) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + Link *link = NULL; + int r; - if (hashmap_isempty(nh->group)) { - r = converted_routes_new(1, &c); - if (r < 0) - return r; + assert(route); + assert(manager); - r = route_dup(route, &c->routes[0]); - if (r < 0) - return r; + /* If the route is remembered, then use the remembered object. */ + (void) route_get(manager, route, &route); - route_apply_nexthop(c->routes[0], nh, UINT8_MAX); - c->links[0] = nh->link; + log_route_debug(route, "Removing", manager); - *ret = TAKE_PTR(c); - return 1; - } + /* For logging. */ + (void) route_get_link(manager, route, &link); - r = converted_routes_new(hashmap_size(nh->group), &c); - if (r < 0) - return r; + r = sd_rtnl_message_new_route(manager->rtnl, &m, RTM_DELROUTE, route->family, route->protocol); + if (r < 0) + return log_link_warning_errno(link, r, "Could not create netlink message: %m"); - size_t i = 0; - HASHMAP_FOREACH(nhg, nh->group) { - NextHop *h; + r = route_set_netlink_message(route, m); + if (r < 0) + return log_link_warning_errno(link, r, "Could not fill netlink message: %m"); - r = manager_get_nexthop_by_id(manager, nhg->id, &h); - if (r < 0) - return r; + r = manager_remove_request_add(manager, route, route, manager->rtnl, m, route_remove_handler); + if (r < 0) + return log_link_warning_errno(link, r, "Could not queue rtnetlink message: %m"); - r = route_dup(route, &c->routes[i]); - if (r < 0) - return r; + route_enter_removing(route); + return 0; +} - route_apply_nexthop(c->routes[i], h, nhg->weight); - c->links[i] = h->link; +int route_remove_and_cancel(Route *route, Manager *manager) { + _cleanup_(request_unrefp) Request *req = NULL; + bool waiting = false; - i++; - } + assert(route); + assert(manager); - *ret = TAKE_PTR(c); - return 1; + /* If the route is remembered by the manager, then use the remembered object. */ + (void) route_get(manager, route, &route); + /* Cancel the request for the route. If the request is already called but we have not received the + * notification about the request, then explicitly remove the route. */ + if (route_get_request(manager, route, &req) >= 0) { + request_ref(req); /* avoid the request freed by request_detach() */ + waiting = req->waiting_reply; + request_detach(req); + route_cancel_requesting(route); } - assert(!ordered_set_isempty(route->multipath_routes)); - - r = converted_routes_new(ordered_set_size(route->multipath_routes), &c); - if (r < 0) - return r; + /* If we know that the route will come or already exists, remove it. */ + if (waiting || (route->manager && route_exists(route))) + return route_remove(route, manager); - size_t i = 0; - MultipathRoute *m; - ORDERED_SET_FOREACH(m, route->multipath_routes) { - r = route_dup(route, &c->routes[i]); - if (r < 0) - return r; + return 0; +} - route_apply_multipath_route(c->routes[i], m); +static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { + Route *route = ASSERT_PTR(userdata); + int r; - r = multipath_route_get_link(manager, m, &c->links[i]); - if (r < 0) - return r; + if (!route->manager) + return 0; /* already detached. */ - i++; + r = route_remove(route, route->manager); + if (r < 0) { + Link *link = NULL; + (void) route_get_link(route->manager, route, &link); + log_link_warning_errno(link, r, "Could not remove route: %m"); + if (link) + link_enter_failed(link); } - *ret = TAKE_PTR(c); return 1; } -void link_mark_routes(Link *link, NetworkConfigSource source) { - Route *route; +static int route_setup_timer(Route *route, const struct rta_cacheinfo *cacheinfo) { + int r; - assert(link); + assert(route); - SET_FOREACH(route, link->routes) { - if (route->source != source) - continue; + if (cacheinfo && cacheinfo->rta_expires != 0) + route->expiration_managed_by_kernel = true; - route_mark(route); + if (route->lifetime_usec == USEC_INFINITY || /* We do not request expiration for the route. */ + route->expiration_managed_by_kernel) { /* We have received nonzero expiration previously. The expiration is managed by the kernel. */ + route->expire = sd_event_source_disable_unref(route->expire); + return 0; } -} -static void log_route_debug(const Route *route, const char *str, const Link *link, const Manager *manager) { - _cleanup_free_ char *state = NULL, *gw_alloc = NULL, *prefsrc = NULL, - *table = NULL, *scope = NULL, *proto = NULL, *flags = NULL; - const char *gw = NULL, *dst, *src; - - assert(route); - assert(str); - assert(manager); + Manager *manager = ASSERT_PTR(route->manager); + r = event_reset_time(manager->event, &route->expire, CLOCK_BOOTTIME, + route->lifetime_usec, 0, route_expire_handler, route, 0, "route-expiration", true); + if (r < 0) { + Link *link = NULL; + (void) route_get_link(manager, route, &link); + return log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m"); + } - /* link may be NULL. */ + log_route_debug(route, "Configured expiration timer for", manager); + return 1; +} - if (!DEBUG_LOGGING) - return; +int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg) { + int r; - (void) network_config_state_to_string_alloc(route->state, &state); + assert(m); + assert(link); + assert(link->manager); + assert(route); + assert(error_msg); - dst = in_addr_is_set(route->family, &route->dst) || route->dst_prefixlen > 0 ? - IN_ADDR_PREFIX_TO_STRING(route->family, &route->dst, route->dst_prefixlen) : NULL; - src = in_addr_is_set(route->family, &route->src) || route->src_prefixlen > 0 ? - IN_ADDR_PREFIX_TO_STRING(route->family, &route->src, route->src_prefixlen) : NULL; + r = sd_netlink_message_get_errno(m); + if (r == -EEXIST) { + Route *existing; - if (in_addr_is_set(route->gw_family, &route->gw)) { - (void) in_addr_to_string(route->gw_family, &route->gw, &gw_alloc); - gw = gw_alloc; - } else if (route->gateway_from_dhcp_or_ra) { - if (route->gw_family == AF_INET) - gw = "_dhcp4"; - else if (route->gw_family == AF_INET6) - gw = "_ipv6ra"; - } else { - MultipathRoute *m; - - ORDERED_SET_FOREACH(m, route->multipath_routes) { - _cleanup_free_ char *buf = NULL; - union in_addr_union a = m->gateway.address; - - (void) in_addr_to_string(m->gateway.family, &a, &buf); - (void) strextend_with_separator(&gw_alloc, ",", strna(buf)); - if (m->ifname) - (void) strextend(&gw_alloc, "@", m->ifname); - else if (m->ifindex > 0) - (void) strextendf(&gw_alloc, "@%i", m->ifindex); - /* See comments in config_parse_multipath_route(). */ - (void) strextendf(&gw_alloc, ":%"PRIu32, m->weight + 1); + if (route_get(link->manager, route, &existing) >= 0) { + /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE + * notification, so we need to update the timer here. */ + existing->lifetime_usec = route->lifetime_usec; + (void) route_setup_timer(existing, NULL); + + /* This may be a bug in the kernel, but the MTU of an IPv6 route can be updated only + * when the route has an expiration timer managed by the kernel (not by us). + * See fib6_add_rt2node() in net/ipv6/ip6_fib.c of the kernel. */ + if (existing->family == AF_INET6 && + existing->expiration_managed_by_kernel) { + r = route_metric_set(&existing->metric, RTAX_MTU, route_metric_get(&route->metric, RTAX_MTU)); + if (r < 0) { + log_oom(); + link_enter_failed(link); + return 0; + } + } } - gw = gw_alloc; + + } else if (r < 0) { + log_link_message_warning_errno(link, m, r, error_msg); + link_enter_failed(link); + return 0; } - if (in_addr_is_set(route->family, &route->prefsrc)) - (void) in_addr_to_string(route->family, &route->prefsrc, &prefsrc); - (void) route_scope_to_string_alloc(route->scope, &scope); - (void) manager_get_route_table_to_string(manager, route->table, /* append_num = */ true, &table); - (void) route_protocol_full_to_string_alloc(route->protocol, &proto); - (void) route_flags_to_string_alloc(route->flags, &flags); - log_link_debug(link, - "%s %s route (%s): dst: %s, src: %s, gw: %s, prefsrc: %s, scope: %s, table: %s, " - "proto: %s, type: %s, nexthop: %"PRIu32", priority: %"PRIu32", flags: %s", - str, strna(network_config_source_to_string(route->source)), strna(state), - strna(dst), strna(src), strna(gw), strna(prefsrc), - strna(scope), strna(table), strna(proto), - strna(route_type_to_string(route->type)), - route->nexthop_id, route->priority, strna(flags)); + return 1; } -static int route_set_netlink_message(const Route *route, sd_netlink_message *req, Link *link) { +static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link, Request *req) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; int r; assert(route); + assert(link); + assert(link->manager); assert(req); - /* link may be NULL */ - - if (in_addr_is_set(route->gw_family, &route->gw) && route->nexthop_id == 0) { - if (route->gw_family == route->family) { - r = netlink_message_append_in_addr_union(req, RTA_GATEWAY, route->gw_family, &route->gw); - if (r < 0) - return r; - } else { - RouteVia rtvia = { - .family = route->gw_family, - .address = route->gw, - }; - - r = sd_netlink_message_append_data(req, RTA_VIA, &rtvia, sizeof(rtvia)); - if (r < 0) - return r; - } - } - - if (route->dst_prefixlen > 0) { - r = netlink_message_append_in_addr_union(req, RTA_DST, route->family, &route->dst); - if (r < 0) - return r; - - r = sd_rtnl_message_route_set_dst_prefixlen(req, route->dst_prefixlen); - if (r < 0) - return r; - } - - if (route->src_prefixlen > 0) { - r = netlink_message_append_in_addr_union(req, RTA_SRC, route->family, &route->src); - if (r < 0) - return r; - - r = sd_rtnl_message_route_set_src_prefixlen(req, route->src_prefixlen); - if (r < 0) - return r; - } - - if (in_addr_is_set(route->family, &route->prefsrc)) { - r = netlink_message_append_in_addr_union(req, RTA_PREFSRC, route->family, &route->prefsrc); - if (r < 0) - return r; - } + log_route_debug(route, "Configuring", link->manager); - r = sd_rtnl_message_route_set_scope(req, route->scope); + r = sd_rtnl_message_new_route(link->manager->rtnl, &m, RTM_NEWROUTE, route->family, route->protocol); if (r < 0) return r; - r = sd_rtnl_message_route_set_flags(req, route->flags & RTNH_F_ONLINK); + r = route_set_netlink_message(route, m); if (r < 0) return r; - if (route->table < 256) { - r = sd_rtnl_message_route_set_table(req, route->table); - if (r < 0) - return r; - } else { - r = sd_rtnl_message_route_set_table(req, RT_TABLE_UNSPEC); - if (r < 0) - return r; - - /* Table attribute to allow more than 256. */ - r = sd_netlink_message_append_u32(req, RTA_TABLE, route->table); + if (lifetime_sec != UINT32_MAX) { + r = sd_netlink_message_append_u32(m, RTA_EXPIRES, lifetime_sec); if (r < 0) return r; } - if (!route_type_is_reject(route) && - route->nexthop_id == 0 && - ordered_set_isempty(route->multipath_routes)) { - assert(link); /* Those routes must be attached to a specific link */ + return request_call_netlink_async(link->manager->rtnl, m, req); +} - r = sd_netlink_message_append_u32(req, RTA_OIF, link->ifindex); - if (r < 0) - return r; - } +static int route_requeue_request(Request *req, Link *link, const Route *route) { + _unused_ _cleanup_(request_unrefp) Request *req_unref = NULL; + _cleanup_(route_unrefp) Route *tmp = NULL; + int r; - if (route->nexthop_id > 0) { - r = sd_netlink_message_append_u32(req, RTA_NH_ID, route->nexthop_id); - if (r < 0) - return r; - } + assert(req); + assert(link); + assert(link->manager); + assert(route); - r = sd_netlink_message_append_u8(req, RTA_PREF, route->pref); + /* It is not possible to adjust the Route object owned by Request, as it is used as a key to manage + * Request objects in the queue. Hence, we need to re-request with the updated Route object. */ + + if (!route_nexthops_needs_adjust(route)) + return 0; /* The Route object does not need the adjustment. Continue with it. */ + + r = route_dup(route, NULL, &tmp); if (r < 0) return r; - r = sd_netlink_message_append_u32(req, RTA_PRIORITY, route->priority); + r = route_adjust_nexthops(tmp, link); + if (r <= 0) + return r; + + if (route_compare_func(route, tmp) == 0 && route->type == tmp->type) + return 0; /* No effective change?? That's OK. */ + + /* Avoid the request to be freed by request_detach(). */ + req_unref = request_ref(req); + + /* Detach the request from the queue, to make not the new request is deduped. + * Why this is necessary? IPv6 routes with different type may be handled as the same, + * As commented in route_adjust_nexthops(), we need to configure the adjusted type, + * otherwise we cannot remove the route on reconfigure or so. If we request the new Route object + * without detaching the current request, the new request is deduped, and the route is configured + * with unmodified type. */ + request_detach(req); + + /* Request the route with the adjusted Route object combined with the same other parameters. */ + r = link_requeue_request(link, req, tmp, NULL); if (r < 0) return r; + if (r == 0) + return 1; /* Already queued?? That's OK. Maybe, [Route] section is effectively duplicated. */ - return 0; + TAKE_PTR(tmp); + return 1; /* New request is queued. Finish to process this request. */ } -static int route_remove_handler(sd_netlink *rtnl, sd_netlink_message *m, Link *link) { +static int route_is_ready_to_configure(const Route *route, Link *link) { int r; - assert(m); - - /* link may be NULL. */ + assert(route); + assert(link); - if (link && IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER)) - return 0; + if (!link_is_ready_to_configure(link, /* allow_unmanaged = */ false)) + return false; - r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -ESRCH) - log_link_message_warning_errno(link, m, r, "Could not drop route, ignoring"); + if (in_addr_is_set(route->family, &route->prefsrc) > 0) { + r = manager_has_address(link->manager, route->family, &route->prefsrc); + if (r <= 0) + return r; + } - return 1; + return route_nexthops_is_ready_to_configure(route, link->manager); } -int route_remove(Route *route) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - unsigned char type; - Manager *manager; - Link *link; +static int route_process_request(Request *req, Link *link, Route *route) { + Route *existing; int r; + assert(req); + assert(link); + assert(link->manager); assert(route); - assert(route->manager || (route->link && route->link->manager)); - assert(IN_SET(route->family, AF_INET, AF_INET6)); - - link = route->link; - manager = route->manager ?: link->manager; - log_route_debug(route, "Removing", link, manager); - - r = sd_rtnl_message_new_route(manager->rtnl, &req, - RTM_DELROUTE, route->family, - route->protocol); + r = route_is_ready_to_configure(route, link); if (r < 0) - return log_link_error_errno(link, r, "Could not create netlink message: %m"); - - if (route->family == AF_INET && route->nexthop_id > 0 && route->type == RTN_BLACKHOLE) - /* When IPv4 route has nexthop id and the nexthop type is blackhole, even though kernel - * sends RTM_NEWROUTE netlink message with blackhole type, kernel's internal route type - * fib_rt_info::type may not be blackhole. Thus, we cannot know the internal value. - * Moreover, on route removal, the matching is done with the hidden value if we set - * non-zero type in RTM_DELROUTE message. Note, sd_rtnl_message_new_route() sets - * RTN_UNICAST by default. So, we need to clear the type here. */ - type = RTN_UNSPEC; - else - type = route->type; + return log_link_warning_errno(link, r, "Failed to check if route is ready to configure: %m"); + if (r == 0) + return 0; - r = sd_rtnl_message_route_set_type(req, type); - if (r < 0) - return log_link_error_errno(link, r, "Could not set route type: %m"); + usec_t now_usec; + assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); + uint32_t sec = usec_to_sec(route->lifetime_usec, now_usec); + if (sec == 0) { + log_link_debug(link, "Refuse to configure %s route with zero lifetime.", + network_config_source_to_string(route->source)); - r = route_set_netlink_message(route, req, link); - if (r < 0) - return log_error_errno(r, "Could not fill netlink message: %m"); + route_cancel_requesting(route); + if (route_get(link->manager, route, &existing) >= 0) + route_cancel_requesting(existing); + return 1; + } - r = netlink_call_async(manager->rtnl, NULL, req, route_remove_handler, - link ? link_netlink_destroy_callback : NULL, link); - if (r < 0) - return log_link_error_errno(link, r, "Could not send netlink message: %m"); + r = route_requeue_request(req, link, route); + if (r != 0) + return r; - link_ref(link); + r = route_configure(route, sec, link, req); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to configure route: %m"); - route_enter_removing(route); - return 0; + route_enter_configuring(route); + if (route_get(link->manager, route, &existing) >= 0) + route_enter_configuring(existing); + return 1; } -int route_remove_and_drop(Route *route) { - if (!route) - return 0; - - route_cancel_request(route, NULL); +static int link_request_route_one( + Link *link, + const Route *route, + const RouteNextHop *nh, + unsigned *message_counter, + route_netlink_handler_t netlink_handler) { - if (route_exists(route)) - return route_remove(route); + _cleanup_(route_unrefp) Route *tmp = NULL; + Route *existing = NULL; + int r; - if (route->state == 0) - route_free(route); + assert(link); + assert(link->manager); + assert(route); - return 0; -} + r = route_dup(route, nh, &tmp); + if (r < 0) + return r; -static void manager_mark_routes(Manager *manager, bool foreign, const Link *except) { - Route *route; - Link *link; - int r; + r = route_adjust_nexthops(tmp, link); + if (r < 0) + return r; - assert(manager); + if (route_get(link->manager, tmp, &existing) >= 0) + /* Copy state for logging below. */ + tmp->state = existing->state; - /* First, mark all routes. */ - SET_FOREACH(route, manager->routes) { - /* Do not touch routes managed by the kernel. */ - if (route->protocol == RTPROT_KERNEL) - continue; - - /* When 'foreign' is true, mark only foreign routes, and vice versa. */ - if (foreign != (route->source == NETWORK_CONFIG_SOURCE_FOREIGN)) - continue; - - /* Do not touch dynamic routes. They will removed by dhcp_pd_prefix_lost() */ - if (IN_SET(route->source, NETWORK_CONFIG_SOURCE_DHCP4, NETWORK_CONFIG_SOURCE_DHCP6)) - continue; - - /* Ignore routes not assigned yet or already removed. */ - if (!route_exists(route)) - continue; + log_route_debug(tmp, "Requesting", link->manager); + r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE, + tmp, + route_unref, + route_hash_func, + route_compare_func, + route_process_request, + message_counter, + netlink_handler, + NULL); + if (r <= 0) + return r; - route_mark(route); - } + route_enter_requesting(tmp); + if (existing) + route_enter_requesting(existing); - /* Then, unmark all routes requested by active links. */ - HASHMAP_FOREACH(link, manager->links_by_index) { - if (link == except) - continue; + TAKE_PTR(tmp); + return 1; +} - if (!link->network) - continue; +int link_request_route( + Link *link, + const Route *route, + unsigned *message_counter, + route_netlink_handler_t netlink_handler) { - if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) - continue; + int r; - HASHMAP_FOREACH(route, link->network->routes_by_section) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - Route *existing; + assert(link); + assert(link->manager); + assert(route); + assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); - r = route_convert(manager, route, &converted); - if (r < 0) - continue; - if (r == 0) { - if (route_get(manager, NULL, route, &existing) >= 0) - route_unmark(existing); - continue; - } + if (route->family == AF_INET || route_type_is_reject(route) || ordered_set_isempty(route->nexthops)) + return link_request_route_one(link, route, NULL, message_counter, netlink_handler); - for (size_t i = 0; i < converted->n; i++) - if (route_get(manager, NULL, converted->routes[i], &existing) >= 0) - route_unmark(existing); - } + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + r = link_request_route_one(link, route, nh, message_counter, netlink_handler); + if (r < 0) + return r; } + + return 0; } -static int manager_drop_marked_routes(Manager *manager) { - Route *route; - int r = 0; +static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) { + int r; - assert(manager); + assert(link); - SET_FOREACH(route, manager->routes) { - if (!route_is_marked(route)) - continue; + r = route_configure_handler_internal(rtnl, m, link, route, "Could not set route"); + if (r <= 0) + return r; - RET_GATHER(r, route_remove(route)); + if (link->static_route_messages == 0) { + log_link_debug(link, "Routes set"); + link->static_routes_configured = true; + link_check_ready(link); } - return r; + return 1; } -static bool route_by_kernel(const Route *route) { - assert(route); +static int link_request_wireguard_routes(Link *link, bool only_ipv4) { + NetDev *netdev; + Route *route; + int r; - if (route->protocol == RTPROT_KERNEL) - return true; + assert(link); - /* The kernels older than a826b04303a40d52439aa141035fca5654ccaccd (v5.11) create the IPv6 - * multicast with RTPROT_BOOT. Do not touch it. */ - if (route->protocol == RTPROT_BOOT && - route->family == AF_INET6 && - route->dst_prefixlen == 8 && - in6_addr_equal(&route->dst.in6, & (struct in6_addr) {{{ 0xff,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }}})) - return true; + if (!streq_ptr(link->kind, "wireguard")) + return 0; - return false; -} + if (netdev_get(link->manager, link->ifname, &netdev) < 0) + return 0; -static void link_unmark_wireguard_routes(Link *link) { - assert(link); + Wireguard *w = WIREGUARD(netdev); - if (!link->netdev || link->netdev->kind != NETDEV_KIND_WIREGUARD) - return; + SET_FOREACH(route, w->routes) { + if (only_ipv4 && route->family != AF_INET) + continue; - Route *route, *existing; - Wireguard *w = WIREGUARD(link->netdev); + r = link_request_route(link, route, &link->static_route_messages, static_route_handler); + if (r < 0) + return r; + } - SET_FOREACH(route, w->routes) - if (route_get(NULL, link, route, &existing) >= 0) - route_unmark(existing); + return 0; } -int link_drop_foreign_routes(Link *link) { +int link_request_static_routes(Link *link, bool only_ipv4) { Route *route; int r; assert(link); - assert(link->manager); assert(link->network); - SET_FOREACH(route, link->routes) { - /* do not touch routes managed by the kernel */ - if (route_by_kernel(route)) - continue; - - /* Do not remove routes we configured. */ - if (route->source != NETWORK_CONFIG_SOURCE_FOREIGN) - continue; - - /* Ignore routes not assigned yet or already removed. */ - if (!route_exists(route)) - continue; + link->static_routes_configured = false; - if (route->protocol == RTPROT_STATIC && - FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) + HASHMAP_FOREACH(route, link->network->routes_by_section) { + if (route->gateway_from_dhcp_or_ra) continue; - if (route->protocol == RTPROT_DHCP && - FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) + if (only_ipv4 && route->family != AF_INET) continue; - route_mark(route); - } - - HASHMAP_FOREACH(route, link->network->routes_by_section) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - Route *existing; - - r = route_convert(link->manager, route, &converted); + r = link_request_route(link, route, &link->static_route_messages, static_route_handler); if (r < 0) - continue; - if (r == 0) { - if (route_get(NULL, link, route, &existing) >= 0) - route_unmark(existing); - continue; - } - - for (size_t i = 0; i < converted->n; i++) - if (route_get(NULL, link, converted->routes[i], &existing) >= 0) - route_unmark(existing); + return r; } - link_unmark_wireguard_routes(link); - - r = 0; - SET_FOREACH(route, link->routes) { - if (!route_is_marked(route)) - continue; + r = link_request_wireguard_routes(link, only_ipv4); + if (r < 0) + return r; - RET_GATHER(r, route_remove(route)); + if (link->static_route_messages == 0) { + link->static_routes_configured = true; + link_check_ready(link); + } else { + log_link_debug(link, "Requesting routes"); + link_set_state(link, LINK_STATE_CONFIGURING); } - manager_mark_routes(link->manager, /* foreign = */ true, NULL); - - return RET_GATHER(r, manager_drop_marked_routes(link->manager)); + return 0; } -int link_drop_managed_routes(Link *link) { - Route *route; - int r = 0; +static int process_route_one( + Manager *manager, + uint16_t type, + Route *tmp, + const struct rta_cacheinfo *cacheinfo) { - assert(link); + Request *req = NULL; + Route *route = NULL; + Link *link = NULL; + bool is_new = false, update_dhcp4; + int r; - SET_FOREACH(route, link->routes) { - /* do not touch routes managed by the kernel */ - if (route_by_kernel(route)) - continue; + assert(manager); + assert(tmp); + assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); - /* Do not touch routes managed by kernel or other tools. */ - if (route->source == NETWORK_CONFIG_SOURCE_FOREIGN) - continue; + (void) route_get(manager, tmp, &route); + (void) route_get_request(manager, tmp, &req); + (void) route_get_link(manager, tmp, &link); - if (!route_exists(route)) - continue; + update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0; - RET_GATHER(r, route_remove(route)); - } + switch (type) { + case RTM_NEWROUTE: + if (!route) { + if (!manager->manage_foreign_routes && !(req && req->waiting_reply)) { + route_enter_configured(tmp); + log_route_debug(tmp, "Ignoring received", manager); + return 0; + } - manager_mark_routes(link->manager, /* foreign = */ false, link); + /* If we do not know the route, then save it. */ + r = route_attach(manager, tmp); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); + return 0; + } - return RET_GATHER(r, manager_drop_marked_routes(link->manager)); -} + route = route_ref(tmp); + is_new = true; -void link_foreignize_routes(Link *link) { - Route *route; + } else + /* Update remembered route with the received notification. */ + route->nexthop.weight = tmp->nexthop.weight; - assert(link); + /* Also update information that cannot be obtained through netlink notification. */ + if (req && req->waiting_reply) { + Route *rt = ASSERT_PTR(req->userdata); - SET_FOREACH(route, link->routes) - route->source = NETWORK_CONFIG_SOURCE_FOREIGN; + route->source = rt->source; + route->provider = rt->provider; + route->lifetime_usec = rt->lifetime_usec; + } - manager_mark_routes(link->manager, /* foreign = */ false, link); + route_enter_configured(route); + log_route_debug(route, is_new ? "Received new" : "Received remembered", manager); - SET_FOREACH(route, link->manager->routes) { - if (!route_is_marked(route)) - continue; + (void) route_setup_timer(route, cacheinfo); - route->source = NETWORK_CONFIG_SOURCE_FOREIGN; - } -} + break; -static int route_expire_handler(sd_event_source *s, uint64_t usec, void *userdata) { - Route *route = ASSERT_PTR(userdata); - Link *link; - int r; + case RTM_DELROUTE: + if (route) { + route_enter_removed(route); + log_route_debug(route, "Forgetting removed", manager); + route_detach(route); + } else + log_route_debug(tmp, + manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received", + manager); - assert(route->manager || (route->link && route->link->manager)); + if (req) + route_enter_removed(req->userdata); - link = route->link; /* This may be NULL. */ + break; - r = route_remove(route); - if (r < 0) { - log_link_warning_errno(link, r, "Could not remove route: %m"); - if (link) + default: + assert_not_reached(); + } + + if (update_dhcp4) { + r = dhcp4_update_ipv6_connectivity(link); + if (r < 0) { + log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m"); link_enter_failed(link); + } } return 1; } -static int route_setup_timer(Route *route, const struct rta_cacheinfo *cacheinfo) { - Manager *manager; +int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { + _cleanup_(route_unrefp) Route *tmp = NULL; int r; - assert(route); - assert(route->manager || (route->link && route->link->manager)); + assert(rtnl); + assert(message); + assert(m); - manager = route->manager ?: route->link->manager; + if (sd_netlink_message_is_error(message)) { + r = sd_netlink_message_get_errno(message); + if (r < 0) + log_message_warning_errno(message, r, "rtnl: failed to receive route message, ignoring"); - if (route->lifetime_usec == USEC_INFINITY) return 0; + } - if (cacheinfo && cacheinfo->rta_expires != 0) - /* Assume that non-zero rta_expires means kernel will handle the route expiration. */ + uint16_t type; + r = sd_netlink_message_get_type(message, &type); + if (r < 0) { + log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); + return 0; + } else if (!IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)) { + log_warning("rtnl: received unexpected message type %u when processing route, ignoring.", type); return 0; + } - r = event_reset_time(manager->event, &route->expire, CLOCK_BOOTTIME, - route->lifetime_usec, 0, route_expire_handler, route, 0, "route-expiration", true); + r = route_new(&tmp); if (r < 0) - return r; + return log_oom(); - return 1; -} + /* rtmsg header */ + r = sd_rtnl_message_route_get_family(message, &tmp->family); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message without family, ignoring: %m"); + return 0; + } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { + log_debug("rtnl: received route message with invalid family '%i', ignoring.", tmp->family); + return 0; + } -static int append_nexthop_one(const Link *link, const Route *route, const MultipathRoute *m, struct rtattr **rta, size_t offset) { - struct rtnexthop *rtnh; - struct rtattr *new_rta; - int r; + r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); + return 0; + } - assert(route); - assert(m); - assert(rta); - assert(*rta); + r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); + return 0; + } - new_rta = realloc(*rta, RTA_ALIGN((*rta)->rta_len) + RTA_SPACE(sizeof(struct rtnexthop))); - if (!new_rta) - return -ENOMEM; - *rta = new_rta; + r = sd_rtnl_message_route_get_tos(message, &tmp->tos); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid tos, ignoring: %m"); + return 0; + } - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - *rtnh = (struct rtnexthop) { - .rtnh_len = sizeof(*rtnh), - .rtnh_ifindex = m->ifindex > 0 ? m->ifindex : link->ifindex, - .rtnh_hops = m->weight, - }; + r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m"); + return 0; + } - (*rta)->rta_len += sizeof(struct rtnexthop); + r = sd_rtnl_message_route_get_scope(message, &tmp->scope); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid scope, ignoring: %m"); + return 0; + } - if (route->family == m->gateway.family) { - r = rtattr_append_attribute(rta, RTA_GATEWAY, &m->gateway.address, FAMILY_ADDRESS_SIZE(m->gateway.family)); - if (r < 0) - goto clear; - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family)); - } else { - r = rtattr_append_attribute(rta, RTA_VIA, &m->gateway, FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); - if (r < 0) - goto clear; - rtnh = (struct rtnexthop *)((uint8_t *) *rta + offset); - rtnh->rtnh_len += RTA_SPACE(FAMILY_ADDRESS_SIZE(m->gateway.family) + sizeof(m->gateway.family)); + r = sd_rtnl_message_route_get_type(message, &tmp->type); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid type, ignoring: %m"); + return 0; } - return 0; - -clear: - (*rta)->rta_len -= sizeof(struct rtnexthop); - return r; -} - -static int append_nexthops(const Link *link, const Route *route, sd_netlink_message *req) { - _cleanup_free_ struct rtattr *rta = NULL; - struct rtnexthop *rtnh; - MultipathRoute *m; - size_t offset; - int r; - - assert(link); - assert(route); - assert(req); - - if (ordered_set_isempty(route->multipath_routes)) + r = sd_rtnl_message_route_get_flags(message, &tmp->flags); + if (r < 0) { + log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m"); return 0; - - rta = new(struct rtattr, 1); - if (!rta) - return -ENOMEM; - - *rta = (struct rtattr) { - .rta_type = RTA_MULTIPATH, - .rta_len = RTA_LENGTH(0), - }; - offset = (uint8_t *) RTA_DATA(rta) - (uint8_t *) rta; - - ORDERED_SET_FOREACH(m, route->multipath_routes) { - r = append_nexthop_one(link, route, m, &rta, offset); - if (r < 0) - return r; - - rtnh = (struct rtnexthop *)((uint8_t *) rta + offset); - offset = (uint8_t *) RTNH_NEXT(rtnh) - (uint8_t *) rta; } - r = sd_netlink_message_append_data(req, RTA_MULTIPATH, RTA_DATA(rta), RTA_PAYLOAD(rta)); - if (r < 0) - return r; - - return 0; -} - -int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg) { - int r; - - assert(m); - assert(link); - assert(error_msg); + /* attributes */ + r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid destination, ignoring: %m"); + return 0; + } - r = sd_netlink_message_get_errno(m); - if (r < 0 && r != -EEXIST) { - log_link_message_warning_errno(link, m, r, "Could not set route"); - link_enter_failed(link); + r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid source, ignoring: %m"); return 0; } - return 1; -} + r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message with invalid priority, ignoring: %m"); + return 0; + } -static int route_configure(const Route *route, uint32_t lifetime_sec, Link *link, Request *req) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; + r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message without valid preferred source, ignoring: %m"); + return 0; + } - assert(route); - assert(IN_SET(route->family, AF_INET, AF_INET6)); - assert(link); - assert(link->manager); - assert(link->manager->rtnl); - assert(link->ifindex > 0); - assert(req); + r = sd_netlink_message_read_u32(message, RTA_TABLE, &tmp->table); + if (r == -ENODATA) { + unsigned char table; - log_route_debug(route, "Configuring", link, link->manager); + r = sd_rtnl_message_route_get_table(message, &table); + if (r >= 0) + tmp->table = table; + } + if (r < 0) { + log_warning_errno(r, "rtnl: received route message with invalid table, ignoring: %m"); + return 0; + } - r = sd_rtnl_message_new_route(link->manager->rtnl, &m, RTM_NEWROUTE, route->family, route->protocol); - if (r < 0) - return r; + r = sd_netlink_message_read_u8(message, RTA_PREF, &tmp->pref); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: received route message with invalid preference, ignoring: %m"); + return 0; + } - r = sd_rtnl_message_route_set_type(m, route->type); - if (r < 0) - return r; + /* nexthops */ + if (route_nexthops_read_netlink_message(tmp, message) < 0) + return 0; - r = route_set_netlink_message(route, m, link); - if (r < 0) - return r; + /* metrics */ + if (route_metric_read_netlink_message(&tmp->metric, message) < 0) + return 0; - if (lifetime_sec != UINT32_MAX) { - r = sd_netlink_message_append_u32(m, RTA_EXPIRES, lifetime_sec); - if (r < 0) - return r; + bool has_cacheinfo; + struct rta_cacheinfo cacheinfo; + r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo); + if (r < 0 && r != -ENODATA) { + log_warning_errno(r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m"); + return 0; } + has_cacheinfo = r >= 0; - if (route->ttl_propagate >= 0) { - r = sd_netlink_message_append_u8(m, RTA_TTL_PROPAGATE, route->ttl_propagate); - if (r < 0) - return r; - } + if (tmp->family == AF_INET || ordered_set_isempty(tmp->nexthops)) + return process_route_one(m, type, tmp, has_cacheinfo ? &cacheinfo : NULL); - r = sd_netlink_message_open_container(m, RTA_METRICS); - if (r < 0) - return r; + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, tmp->nexthops) { + _cleanup_(route_unrefp) Route *dup = NULL; - if (route->mtu > 0) { - r = sd_netlink_message_append_u32(m, RTAX_MTU, route->mtu); + r = route_dup(tmp, nh, &dup); if (r < 0) - return r; - } + return log_oom(); - if (route->initcwnd > 0) { - r = sd_netlink_message_append_u32(m, RTAX_INITCWND, route->initcwnd); + r = process_route_one(m, type, dup, has_cacheinfo ? &cacheinfo : NULL); if (r < 0) return r; } - if (route->initrwnd > 0) { - r = sd_netlink_message_append_u32(m, RTAX_INITRWND, route->initrwnd); - if (r < 0) - return r; - } + return 1; +} - if (route->quickack >= 0) { - r = sd_netlink_message_append_u32(m, RTAX_QUICKACK, route->quickack); - if (r < 0) - return r; - } +void manager_mark_routes(Manager *manager, Link *link, NetworkConfigSource source) { + Route *route; - if (route->fast_open_no_cookie >= 0) { - r = sd_netlink_message_append_u32(m, RTAX_FASTOPEN_NO_COOKIE, route->fast_open_no_cookie); - if (r < 0) - return r; - } + assert(manager); - if (route->advmss > 0) { - r = sd_netlink_message_append_u32(m, RTAX_ADVMSS, route->advmss); - if (r < 0) - return r; - } + SET_FOREACH(route, manager->routes) { + if (route->source != source) + continue; - if (!isempty(route->tcp_congestion_control_algo)) { - r = sd_netlink_message_append_string(m, RTAX_CC_ALGO, route->tcp_congestion_control_algo); - if (r < 0) - return r; - } + if (link) { + Link *route_link; - if (route->hop_limit > 0) { - r = sd_netlink_message_append_u32(m, RTAX_HOPLIMIT, route->hop_limit); - if (r < 0) - return r; - } + if (route_get_link(manager, route, &route_link) < 0) + continue; + if (route_link != link) + continue; + } - if (route->tcp_rto_usec > 0) { - r = sd_netlink_message_append_u32(m, RTAX_RTO_MIN, DIV_ROUND_UP(route->tcp_rto_usec, USEC_PER_MSEC)); - if (r < 0) - return r; + route_mark(route); } +} - r = sd_netlink_message_close_container(m); - if (r < 0) - return r; +static bool route_by_kernel(const Route *route) { + assert(route); - if (!ordered_set_isempty(route->multipath_routes)) { - assert(route->nexthop_id == 0); - assert(!in_addr_is_set(route->gw_family, &route->gw)); + if (route->protocol == RTPROT_KERNEL) + return true; - r = append_nexthops(link, route, m); - if (r < 0) - return r; - } + /* The kernels older than a826b04303a40d52439aa141035fca5654ccaccd (v5.11) create the IPv6 + * multicast with RTPROT_BOOT. Do not touch it. */ + if (route->protocol == RTPROT_BOOT && + route->family == AF_INET6 && + route->dst_prefixlen == 8 && + in6_addr_equal(&route->dst.in6, & (struct in6_addr) {{{ 0xff,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 }}})) + return true; - return request_call_netlink_async(link->manager->rtnl, m, req); + return false; } -static int route_is_ready_to_configure(const Route *route, Link *link) { - int r; - - assert(route); - assert(link); - - if (!link_is_ready_to_configure(link, false)) - return false; +bool route_can_update(const Route *existing, const Route *requesting) { + assert(existing); + assert(requesting); - if (set_size(link->routes) >= routes_max()) + if (route_compare_func(existing, requesting) != 0) return false; - if (route->nexthop_id > 0) { - struct nexthop_grp *nhg; - NextHop *nh; - - if (manager_get_nexthop_by_id(link->manager, route->nexthop_id, &nh) < 0) + switch (existing->family) { + case AF_INET: + if (existing->nexthop.weight != requesting->nexthop.weight) return false; + return true; - if (!nexthop_exists(nh)) + case AF_INET6: + if (existing->protocol != requesting->protocol) return false; + if (existing->type != requesting->type) + return false; + if (existing->flags != requesting->flags) + return false; + if (!in6_addr_equal(&existing->prefsrc.in6, &requesting->prefsrc.in6)) + return false; + if (existing->pref != requesting->pref) + return false; + if (existing->expiration_managed_by_kernel && requesting->lifetime_usec == USEC_INFINITY) + return false; /* We cannot disable expiration timer in the kernel. */ + if (!route_metric_can_update(&existing->metric, &requesting->metric, existing->expiration_managed_by_kernel)) + return false; + if (existing->nexthop.weight != requesting->nexthop.weight) + return false; + return true; - HASHMAP_FOREACH(nhg, nh->group) { - NextHop *g; - - if (manager_get_nexthop_by_id(link->manager, nhg->id, &g) < 0) - return false; - - if (!nexthop_exists(g)) - return false; - } + default: + assert_not_reached(); } +} - if (in_addr_is_set(route->family, &route->prefsrc) > 0) { - r = manager_has_address(link->manager, route->family, &route->prefsrc, route->family == AF_INET6); - if (r <= 0) - return r; - } +static int link_unmark_route(Link *link, const Route *route, const RouteNextHop *nh) { + _cleanup_(route_unrefp) Route *tmp = NULL; + Route *existing; + int r; - if (!gateway_is_ready(link, FLAGS_SET(route->flags, RTNH_F_ONLINK), route->gw_family, &route->gw)) - return false; + assert(link); + assert(route); - MultipathRoute *m; - ORDERED_SET_FOREACH(m, route->multipath_routes) { - union in_addr_union a = m->gateway.address; - Link *l = NULL; + r = route_dup(route, nh, &tmp); + if (r < 0) + return r; - r = multipath_route_get_link(link->manager, m, &l); - if (r < 0) - return false; - if (r > 0) { - if (!link_is_ready_to_configure(l, /* allow_unmanaged = */ true) || - !link_has_carrier(l)) - return false; + r = route_adjust_nexthops(tmp, link); + if (r < 0) + return r; - m->ifindex = l->ifindex; - } + if (route_get(link->manager, tmp, &existing) < 0) + return 0; - if (!gateway_is_ready(l ?: link, FLAGS_SET(route->flags, RTNH_F_ONLINK), m->gateway.family, &a)) - return false; - } + if (!route_can_update(existing, tmp)) + return 0; - return true; + route_unmark(existing); + return 1; } -static int route_process_request(Request *req, Link *link, Route *route) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; +static int link_mark_routes(Link *link, bool foreign) { + Route *route; + Link *other; int r; - assert(req); assert(link); assert(link->manager); - assert(route); - - r = route_is_ready_to_configure(route, link); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to check if route is ready to configure: %m"); - if (r == 0) - return 0; - if (route_needs_convert(route)) { - r = route_convert(link->manager, route, &converted); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to convert route: %m"); + /* First, mark all routes. */ + SET_FOREACH(route, link->manager->routes) { + /* Do not touch routes managed by the kernel. */ + if (route_by_kernel(route)) + continue; - assert(r > 0); - assert(converted); + /* When 'foreign' is true, mark only foreign routes, and vice versa. + * Note, do not touch dynamic routes. They will removed by when e.g. lease is lost. */ + if (route->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) + continue; - for (size_t i = 0; i < converted->n; i++) { - Route *existing; + /* Ignore routes not assigned yet or already removed. */ + if (!route_exists(route)) + continue; - if (route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) < 0) { - _cleanup_(route_freep) Route *tmp = NULL; + if (link->network) { + if (route->protocol == RTPROT_STATIC && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_STATIC)) + continue; - r = route_dup(converted->routes[i], &tmp); - if (r < 0) - return log_oom(); + if (route->protocol == RTPROT_DHCP && + FLAGS_SET(link->network->keep_configuration, KEEP_CONFIGURATION_DHCP)) + continue; + } - r = route_add(link->manager, converted->links[i] ?: link, tmp); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to add route: %m"); + /* When we mark foreign routes, do not mark routes assigned to other interfaces. + * Otherwise, routes assigned to unmanaged interfaces will be dropped. + * Note, route_get_link() does not provide assigned link for routes with an unreachable type + * or IPv4 multipath routes. So, the current implementation does not support managing such + * routes by other daemon or so, unless ManageForeignRoutes=no. */ + if (foreign) { + Link *route_link; - TAKE_PTR(tmp); - } else { - existing->source = converted->routes[i]->source; - existing->provider = converted->routes[i]->provider; - } + if (route_get_link(link->manager, route, &route_link) >= 0 && route_link != link) + continue; } - } - usec_t now_usec; - assert_se(sd_event_now(link->manager->event, CLOCK_BOOTTIME, &now_usec) >= 0); - uint32_t sec = usec_to_sec(route->lifetime_usec, now_usec); - if (sec == 0) { - log_link_debug(link, "Refuse to configure %s route with zero lifetime.", - network_config_source_to_string(route->source)); + route_mark(route); + } - if (converted) - for (size_t i = 0; i < converted->n; i++) { - Route *existing; + /* Then, unmark all routes requested by active links. */ + HASHMAP_FOREACH(other, link->manager->links_by_index) { + if (!foreign && other == link) + continue; - assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0); - route_cancel_requesting(existing); - } - else - route_cancel_requesting(route); + if (!IN_SET(other->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) + continue; - return 1; - } + HASHMAP_FOREACH(route, other->network->routes_by_section) { + if (route->family == AF_INET || ordered_set_isempty(route->nexthops)) { + r = link_unmark_route(other, route, NULL); + if (r < 0) + return r; - r = route_configure(route, sec, link, req); - if (r < 0) - return log_link_warning_errno(link, r, "Failed to configure route: %m"); + } else { + RouteNextHop *nh; + ORDERED_SET_FOREACH(nh, route->nexthops) { + r = link_unmark_route(other, route, nh); + if (r < 0) + return r; + } + } + } - if (converted) - for (size_t i = 0; i < converted->n; i++) { - Route *existing; + /* Also unmark routes requested in .netdev file. */ + if (other->netdev && other->netdev->kind == NETDEV_KIND_WIREGUARD) { + Wireguard *w = WIREGUARD(other->netdev); - assert_se(route_get(link->manager, converted->links[i] ?: link, converted->routes[i], &existing) >= 0); - route_enter_configuring(existing); + SET_FOREACH(route, w->routes) { + r = link_unmark_route(other, route, NULL); + if (r < 0) + return r; + } } - else - route_enter_configuring(route); + } - return 1; + return 0; } -int link_request_route( - Link *link, - Route *route, - bool consume_object, - unsigned *message_counter, - route_netlink_handler_t netlink_handler, - Request **ret) { - - Route *existing = NULL; +int link_drop_routes(Link *link, bool foreign) { + Route *route; int r; assert(link); assert(link->manager); - assert(route); - assert(route->source != NETWORK_CONFIG_SOURCE_FOREIGN); - assert(!route_needs_convert(route)); - - (void) route_get(link->manager, link, route, &existing); - - if (route->lifetime_usec == 0) { - if (consume_object) - route_free(route); - - /* The requested route is outdated. Let's remove it. */ - return route_remove_and_drop(existing); - } - - if (!existing) { - _cleanup_(route_freep) Route *tmp = NULL; - - if (consume_object) - tmp = route; - else { - r = route_dup(route, &tmp); - if (r < 0) - return r; - } - - r = route_add(link->manager, link, tmp); - if (r < 0) - return r; - - existing = TAKE_PTR(tmp); - } else { - existing->source = route->source; - existing->provider = route->provider; - existing->lifetime_usec = route->lifetime_usec; - if (consume_object) - route_free(route); - - if (existing->expire) { - /* When re-configuring an existing route, kernel does not send RTM_NEWROUTE - * message, so we need to update the timer here. */ - r = route_setup_timer(existing, NULL); - if (r < 0) - log_link_warning_errno(link, r, "Failed to update expiration timer for route, ignoring: %m"); - if (r > 0) - log_route_debug(existing, "Updated expiration timer for", link, link->manager); - } - } - - log_route_debug(existing, "Requesting", link, link->manager); - r = link_queue_request_safe(link, REQUEST_TYPE_ROUTE, - existing, NULL, - route_hash_func, - route_compare_func, - route_process_request, - message_counter, netlink_handler, ret); - if (r <= 0) - return r; - - route_enter_requesting(existing); - return 1; -} - -static int static_route_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, Route *route) { - int r; - - assert(link); - r = route_configure_handler_internal(rtnl, m, link, "Could not set route"); - if (r <= 0) + r = link_mark_routes(link, foreign); + if (r < 0) return r; - if (link->static_route_messages == 0) { - log_link_debug(link, "Routes set"); - link->static_routes_configured = true; - link_check_ready(link); - } - - return 1; -} - -static int link_request_static_route(Link *link, Route *route) { - assert(link); - assert(link->manager); - assert(route); - - if (!route_needs_convert(route)) - return link_request_route(link, route, false, &link->static_route_messages, - static_route_handler, NULL); - - log_route_debug(route, "Requesting", link, link->manager); - return link_queue_request_safe(link, REQUEST_TYPE_ROUTE, - route, NULL, route_hash_func, route_compare_func, - route_process_request, - &link->static_route_messages, static_route_handler, NULL); -} - -static int link_request_wireguard_routes(Link *link, bool only_ipv4) { - NetDev *netdev; - Route *route; - int r; - - assert(link); - - if (!streq_ptr(link->kind, "wireguard")) - return 0; - - if (netdev_get(link->manager, link->ifname, &netdev) < 0) - return 0; - - Wireguard *w = WIREGUARD(netdev); - - SET_FOREACH(route, w->routes) { - if (only_ipv4 && route->family != AF_INET) + SET_FOREACH(route, link->manager->routes) { + if (!route_is_marked(route)) continue; - r = link_request_static_route(link, route); - if (r < 0) - return r; + RET_GATHER(r, route_remove(route, link->manager)); } - return 0; + return r; } -int link_request_static_routes(Link *link, bool only_ipv4) { +int link_foreignize_routes(Link *link) { Route *route; int r; assert(link); - assert(link->network); - - link->static_routes_configured = false; - - HASHMAP_FOREACH(route, link->network->routes_by_section) { - if (route->gateway_from_dhcp_or_ra) - continue; - - if (only_ipv4 && route->family != AF_INET) - continue; - - r = link_request_static_route(link, route); - if (r < 0) - return r; - } - - r = link_request_wireguard_routes(link, only_ipv4); - if (r < 0) - return r; - - if (link->static_route_messages == 0) { - link->static_routes_configured = true; - link_check_ready(link); - } else { - log_link_debug(link, "Requesting routes"); - link_set_state(link, LINK_STATE_CONFIGURING); - } - - return 0; -} - -void route_cancel_request(Route *route, Link *link) { - Request req; - - assert(route); - - link = route->link ?: link; - - assert(link); - - if (!route_is_requesting(route)) - return; - - req = (Request) { - .link = link, - .type = REQUEST_TYPE_ROUTE, - .userdata = route, - .hash_func = (hash_func_t) route_hash_func, - .compare_func = (compare_func_t) route_compare_func, - }; - - request_detach(link->manager, &req); - route_cancel_requesting(route); -} - -static int process_route_one( - Manager *manager, - Link *link, - uint16_t type, - Route *in, - const struct rta_cacheinfo *cacheinfo) { - - _cleanup_(route_freep) Route *tmp = in; - Route *route = NULL; - bool update_dhcp4; - int r; - - assert(manager); - assert(tmp); - assert(IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)); - - /* link may be NULL. This consumes 'in'. */ - - update_dhcp4 = link && tmp->family == AF_INET6 && tmp->dst_prefixlen == 0; - - (void) route_get(manager, link, tmp, &route); - - switch (type) { - case RTM_NEWROUTE: - if (route) { - route->flags = tmp->flags; - route_enter_configured(route); - log_route_debug(route, "Received remembered", link, manager); - - r = route_setup_timer(route, cacheinfo); - if (r < 0) - log_link_warning_errno(link, r, "Failed to configure expiration timer for route, ignoring: %m"); - if (r > 0) - log_route_debug(route, "Configured expiration timer for", link, manager); - - } else if (!manager->manage_foreign_routes) { - route_enter_configured(tmp); - log_route_debug(tmp, "Ignoring received", link, manager); - - } else { - /* A route appeared that we did not request */ - route_enter_configured(tmp); - log_route_debug(tmp, "Received new", link, manager); - r = route_add(manager, link, tmp); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to remember foreign route, ignoring: %m"); - return 0; - } - TAKE_PTR(tmp); - } - - break; - - case RTM_DELROUTE: - if (route) { - route_enter_removed(route); - if (route->state == 0) { - log_route_debug(route, "Forgetting", link, manager); - route_free(route); - } else - log_route_debug(route, "Removed", link, manager); - } else - log_route_debug(tmp, - manager->manage_foreign_routes ? "Kernel removed unknown" : "Ignoring received", - link, manager); - - break; - - default: - assert_not_reached(); - } - - if (update_dhcp4) { - r = dhcp4_update_ipv6_connectivity(link); - if (r < 0) { - log_link_warning_errno(link, r, "Failed to notify IPv6 connectivity to DHCPv4 client: %m"); - link_enter_failed(link); - } - } - - return 1; -} - -int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m) { - _cleanup_(converted_routes_freep) ConvertedRoutes *converted = NULL; - _cleanup_(route_freep) Route *tmp = NULL; - _cleanup_free_ void *rta_multipath = NULL; - struct rta_cacheinfo cacheinfo; - bool has_cacheinfo; - Link *link = NULL; - uint32_t ifindex; - uint16_t type; - size_t rta_len; - int r; - - assert(rtnl); - assert(message); - assert(m); - - if (sd_netlink_message_is_error(message)) { - r = sd_netlink_message_get_errno(message); - if (r < 0) - log_message_warning_errno(message, r, "rtnl: failed to receive route message, ignoring"); - - return 0; - } - - r = sd_netlink_message_get_type(message, &type); - if (r < 0) { - log_warning_errno(r, "rtnl: could not get message type, ignoring: %m"); - return 0; - } else if (!IN_SET(type, RTM_NEWROUTE, RTM_DELROUTE)) { - log_warning("rtnl: received unexpected message type %u when processing route, ignoring.", type); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTA_OIF, &ifindex); - if (r < 0 && r != -ENODATA) { - log_warning_errno(r, "rtnl: could not get ifindex from route message, ignoring: %m"); - return 0; - } else if (r >= 0) { - if (ifindex <= 0) { - log_warning("rtnl: received route message with invalid ifindex %u, ignoring.", ifindex); - return 0; - } - - r = link_get_by_index(m, ifindex, &link); - if (r < 0) { - /* when enumerating we might be out of sync, but we will - * get the route again, so just ignore it */ - if (!m->enumerating) - log_warning("rtnl: received route message for link (%u) we do not know about, ignoring", ifindex); - return 0; - } - } - - r = route_new(&tmp); - if (r < 0) - return log_oom(); - - r = sd_rtnl_message_route_get_family(message, &tmp->family); - if (r < 0) { - log_link_warning(link, "rtnl: received route message without family, ignoring"); - return 0; - } else if (!IN_SET(tmp->family, AF_INET, AF_INET6)) { - log_link_debug(link, "rtnl: received route message with invalid family '%i', ignoring", tmp->family); - return 0; - } - - r = sd_rtnl_message_route_get_protocol(message, &tmp->protocol); - if (r < 0) { - log_warning_errno(r, "rtnl: received route message without route protocol, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_flags(message, &tmp->flags); - if (r < 0) { - log_warning_errno(r, "rtnl: received route message without route flags, ignoring: %m"); - return 0; - } - - r = netlink_message_read_in_addr_union(message, RTA_DST, tmp->family, &tmp->dst); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid destination, ignoring: %m"); - return 0; - } - - r = netlink_message_read_in_addr_union(message, RTA_GATEWAY, tmp->family, &tmp->gw); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); - return 0; - } else if (r >= 0) - tmp->gw_family = tmp->family; - else if (tmp->family == AF_INET) { - RouteVia via; - - r = sd_netlink_message_read(message, RTA_VIA, sizeof(via), &via); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid gateway, ignoring: %m"); - return 0; - } else if (r >= 0) { - tmp->gw_family = via.family; - tmp->gw = via.address; - } - } - - r = netlink_message_read_in_addr_union(message, RTA_SRC, tmp->family, &tmp->src); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid source, ignoring: %m"); - return 0; - } - - r = netlink_message_read_in_addr_union(message, RTA_PREFSRC, tmp->family, &tmp->prefsrc); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message without valid preferred source, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_dst_prefixlen(message, &tmp->dst_prefixlen); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid destination prefixlen, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_src_prefixlen(message, &tmp->src_prefixlen); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid source prefixlen, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_scope(message, &tmp->scope); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid scope, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_tos(message, &tmp->tos); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid tos, ignoring: %m"); - return 0; - } - - r = sd_rtnl_message_route_get_type(message, &tmp->type); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid type, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTA_TABLE, &tmp->table); - if (r == -ENODATA) { - unsigned char table; - - r = sd_rtnl_message_route_get_table(message, &table); - if (r >= 0) - tmp->table = table; - } - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid table, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTA_PRIORITY, &tmp->priority); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid priority, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTA_NH_ID, &tmp->nexthop_id); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid nexthop id, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_enter_container(message, RTA_METRICS); - if (r < 0 && r != -ENODATA) { - log_link_error_errno(link, r, "rtnl: Could not enter RTA_METRICS container, ignoring: %m"); - return 0; - } - if (r >= 0) { - r = sd_netlink_message_read_u32(message, RTAX_INITCWND, &tmp->initcwnd); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid initcwnd, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTAX_INITRWND, &tmp->initrwnd); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid initrwnd, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_read_u32(message, RTAX_ADVMSS, &tmp->advmss); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: received route message with invalid advmss, ignoring: %m"); - return 0; - } - - r = sd_netlink_message_exit_container(message); - if (r < 0) { - log_link_error_errno(link, r, "rtnl: Could not exit from RTA_METRICS container, ignoring: %m"); - return 0; - } - } - - r = sd_netlink_message_read_data(message, RTA_MULTIPATH, &rta_len, &rta_multipath); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: failed to read RTA_MULTIPATH attribute, ignoring: %m"); - return 0; - } else if (r >= 0) { - r = rtattr_read_nexthop(rta_multipath, rta_len, tmp->family, &tmp->multipath_routes); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: failed to parse RTA_MULTIPATH attribute, ignoring: %m"); - return 0; - } - } - - r = sd_netlink_message_read(message, RTA_CACHEINFO, sizeof(cacheinfo), &cacheinfo); - if (r < 0 && r != -ENODATA) { - log_link_warning_errno(link, r, "rtnl: failed to read RTA_CACHEINFO attribute, ignoring: %m"); - return 0; - } - has_cacheinfo = r >= 0; - - /* IPv6 routes with reject type are always assigned to the loopback interface. See kernel's - * fib6_nh_init() in net/ipv6/route.c. However, we'd like to manage them by Manager. Hence, set - * link to NULL here. */ - if (route_type_is_reject(tmp)) - link = NULL; - - if (!route_needs_convert(tmp)) - return process_route_one(m, link, type, TAKE_PTR(tmp), has_cacheinfo ? &cacheinfo : NULL); - - r = route_convert(m, tmp, &converted); - if (r < 0) { - log_link_warning_errno(link, r, "rtnl: failed to convert received route, ignoring: %m"); - return 0; - } - - assert(r > 0); - assert(converted); - - for (size_t i = 0; i < converted->n; i++) - (void) process_route_one(m, - converted->links[i] ?: link, - type, - TAKE_PTR(converted->routes[i]), - has_cacheinfo ? &cacheinfo : NULL); - - return 1; -} - -int network_add_ipv4ll_route(Network *network) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - unsigned section_line; - int r; - - assert(network); - - if (!network->ipv4ll_route) - return 0; - - r = hashmap_by_section_find_unused_line(network->routes_by_section, network->filename, §ion_line); - if (r < 0) - return r; - - /* IPv4LLRoute= is in [Network] section. */ - r = route_new_static(network, network->filename, section_line, &n); - if (r < 0) - return r; - - r = in_addr_from_string(AF_INET, "169.254.0.0", &n->dst); - if (r < 0) - return r; - - n->family = AF_INET; - n->dst_prefixlen = 16; - n->scope = RT_SCOPE_LINK; - n->scope_set = true; - n->table_set = true; - n->priority = IPV4LL_ROUTE_METRIC; - n->protocol = RTPROT_STATIC; - - TAKE_PTR(n); - return 0; -} - -int network_add_default_route_on_device(Network *network) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - unsigned section_line; - int r; - - assert(network); - - if (!network->default_route_on_device) - return 0; - - r = hashmap_by_section_find_unused_line(network->routes_by_section, network->filename, §ion_line); - if (r < 0) - return r; - - /* DefaultRouteOnDevice= is in [Network] section. */ - r = route_new_static(network, network->filename, section_line, &n); - if (r < 0) - return r; - - n->family = AF_INET; - n->scope = RT_SCOPE_LINK; - n->scope_set = true; - n->protocol = RTPROT_STATIC; - - TAKE_PTR(n); - return 0; -} - -int config_parse_gateway( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(section, "Network")) { - /* we are not in an Route section, so use line number instead */ - r = route_new_static(network, filename, line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - } else { - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->gateway_from_dhcp_or_ra = false; - n->gw_family = AF_UNSPEC; - n->gw = IN_ADDR_NULL; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_dhcp")) { - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_dhcp4")) { - n->gw_family = AF_INET; - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - - if (streq(rvalue, "_ipv6ra")) { - n->gw_family = AF_INET6; - n->gateway_from_dhcp_or_ra = true; - TAKE_PTR(n); - return 0; - } - } - - r = in_addr_from_string_auto(rvalue, &n->gw_family, &n->gw); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - n->gateway_from_dhcp_or_ra = false; - TAKE_PTR(n); - return 0; -} - -int config_parse_preferred_src( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (n->family == AF_UNSPEC) - r = in_addr_from_string_auto(rvalue, &n->family, &n->prefsrc); - else - r = in_addr_from_string(n->family, rvalue, &n->prefsrc); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, EINVAL, - "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - TAKE_PTR(n); - return 0; -} - -int config_parse_destination( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - union in_addr_union *buffer; - unsigned char *prefixlen; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (streq(lvalue, "Destination")) { - buffer = &n->dst; - prefixlen = &n->dst_prefixlen; - } else if (streq(lvalue, "Source")) { - buffer = &n->src; - prefixlen = &n->src_prefixlen; - } else - assert_not_reached(); - - if (n->family == AF_UNSPEC) - r = in_addr_prefix_from_string_auto(rvalue, &n->family, buffer, prefixlen); - else - r = in_addr_prefix_from_string(rvalue, n->family, buffer, prefixlen); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, EINVAL, - "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - (void) in_addr_mask(n->family, buffer, *prefixlen); - - TAKE_PTR(n); - return 0; -} - -int config_parse_route_priority( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = safe_atou32(rvalue, &n->priority); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - n->priority_set = true; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_scope( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = route_scope_from_string(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Unknown route scope: %s", rvalue); - return 0; - } - - n->scope = r; - n->scope_set = true; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_nexthop( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - uint32_t id; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - if (isempty(rvalue)) { - n->nexthop_id = 0; - TAKE_PTR(n); - return 0; - } - - r = safe_atou32(rvalue, &id); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse nexthop ID, ignoring assignment: %s", rvalue); - return 0; - } - if (id == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid nexthop ID, ignoring assignment: %s", rvalue); - return 0; - } - - n->nexthop_id = id; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_table( - 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_(route_free_or_set_invalidp) Route *n = NULL; - Network *network = userdata; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = manager_get_route_table_from_string(network->manager, rvalue, &n->table); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse route table \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - n->table_set = true; - TAKE_PTR(n); - return 0; -} - -int config_parse_route_boolean( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse %s=\"%s\", ignoring assignment: %m", lvalue, rvalue); - return 0; - } - - if (STR_IN_SET(lvalue, "GatewayOnLink", "GatewayOnlink")) - n->gateway_onlink = r; - else if (streq(lvalue, "QuickAck")) - n->quickack = r; - else if (streq(lvalue, "FastOpenNoCookie")) - n->fast_open_no_cookie = r; - else if (streq(lvalue, "TTLPropagate")) - n->ttl_propagate = r; - else - assert_not_reached(); - - TAKE_PTR(n); - return 0; -} - -int config_parse_ipv6_route_preference( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int r; + assert(link->manager); - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } + r = link_mark_routes(link, /* foreign = */ false); + if (r < 0) + return r; - if (streq(rvalue, "low")) - n->pref = ICMPV6_ROUTER_PREF_LOW; - else if (streq(rvalue, "medium")) - n->pref = ICMPV6_ROUTER_PREF_MEDIUM; - else if (streq(rvalue, "high")) - n->pref = ICMPV6_ROUTER_PREF_HIGH; - else { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue); - return 0; + SET_FOREACH(route, link->manager->routes) { + if (!route_is_marked(route)) + continue; + + route->source = NETWORK_CONFIG_SOURCE_FOREIGN; } - n->pref_set = true; - TAKE_PTR(n); return 0; } -int config_parse_route_protocol( - 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) { - - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; +int network_add_ipv4ll_route(Network *network) { + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + unsigned section_line; int r; - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); - return 0; - } + assert(network); - r = route_protocol_from_string(rvalue); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse route protocol \"%s\", ignoring assignment: %m", rvalue); + if (!network->ipv4ll_route) return 0; - } - n->protocol = r; + r = hashmap_by_section_find_unused_line(network->routes_by_section, network->filename, §ion_line); + if (r < 0) + return r; + + /* IPv4LLRoute= is in [Network] section. */ + r = route_new_static(network, network->filename, section_line, &route); + if (r < 0) + return r; + + r = in_addr_from_string(AF_INET, "169.254.0.0", &route->dst); + if (r < 0) + return r; + + route->family = AF_INET; + route->dst_prefixlen = 16; + route->scope = RT_SCOPE_LINK; + route->scope_set = true; + route->table_set = true; + route->priority = IPV4LL_ROUTE_METRIC; + route->protocol = RTPROT_STATIC; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -int config_parse_route_type( - 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) { +int network_add_default_route_on_device(Network *network) { + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + unsigned section_line; + int r; - Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - int t, r; + assert(network); - r = route_new_static(network, filename, section_line, &n); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to allocate route, ignoring assignment: %m"); + if (!network->default_route_on_device) return 0; - } - t = route_type_from_string(rvalue); - if (t < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse route type \"%s\", ignoring assignment: %m", rvalue); - return 0; - } + r = hashmap_by_section_find_unused_line(network->routes_by_section, network->filename, §ion_line); + if (r < 0) + return r; + + /* DefaultRouteOnDevice= is in [Network] section. */ + r = route_new_static(network, network->filename, section_line, &route); + if (r < 0) + return r; - n->type = (unsigned char) t; + route->family = AF_INET; + route->scope = RT_SCOPE_LINK; + route->scope_set = true; + route->protocol = RTPROT_STATIC; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -int config_parse_route_hop_limit( +int config_parse_preferred_src( const char *unit, const char *filename, unsigned line, @@ -2555,9 +1563,8 @@ int config_parse_route_hop_limit( void *data, void *userdata) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; Network *network = userdata; - uint32_t k; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2566,7 +1573,7 @@ int config_parse_route_hop_limit( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2575,36 +1582,21 @@ int config_parse_route_hop_limit( return 0; } - if (isempty(rvalue)) { - n->hop_limit = 0; - TAKE_PTR(n); - return 0; - } - - r = safe_atou32(rvalue, &k); + if (route->family == AF_UNSPEC) + r = in_addr_from_string_auto(rvalue, &route->family, &route->prefsrc); + else + r = in_addr_from_string(route->family, rvalue, &route->prefsrc); if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse per route hop limit, ignoring assignment: %s", rvalue); - return 0; - } - if (k > 255) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Specified per route hop limit \"%s\" is too large, ignoring assignment: %m", rvalue); - return 0; - } - if (k == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid per route hop limit \"%s\", ignoring assignment: %m", rvalue); + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); return 0; } - n->hop_limit = k; - - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -int config_parse_tcp_congestion( +int config_parse_destination( const char *unit, const char *filename, unsigned line, @@ -2617,7 +1609,9 @@ int config_parse_tcp_congestion( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + union in_addr_union *buffer; + unsigned char *prefixlen; int r; assert(filename); @@ -2626,7 +1620,7 @@ int config_parse_tcp_congestion( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2635,16 +1629,32 @@ int config_parse_tcp_congestion( return 0; } - r = config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, - rvalue, &n->tcp_congestion_control_algo, userdata); - if (r < 0) - return r; + if (streq(lvalue, "Destination")) { + buffer = &route->dst; + prefixlen = &route->dst_prefixlen; + } else if (streq(lvalue, "Source")) { + buffer = &route->src; + prefixlen = &route->src_prefixlen; + } else + assert_not_reached(); + + if (route->family == AF_UNSPEC) + r = in_addr_prefix_from_string_auto(rvalue, &route->family, buffer, prefixlen); + else + r = in_addr_prefix_from_string(rvalue, route->family, buffer, prefixlen); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, + "Invalid %s='%s', ignoring assignment: %m", lvalue, rvalue); + return 0; + } - TAKE_PTR(n); + (void) in_addr_mask(route->family, buffer, *prefixlen); + + TAKE_PTR(route); return 0; } -int config_parse_tcp_advmss( +int config_parse_route_priority( const char *unit, const char *filename, unsigned line, @@ -2656,9 +1666,8 @@ int config_parse_tcp_advmss( void *data, void *userdata) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; Network *network = userdata; - uint64_t u; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2667,7 +1676,7 @@ int config_parse_tcp_advmss( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2676,32 +1685,19 @@ int config_parse_tcp_advmss( return 0; } - if (isempty(rvalue)) { - n->advmss = 0; - TAKE_PTR(n); - return 0; - } - - r = parse_size(rvalue, 1024, &u); + r = safe_atou32(rvalue, &route->priority); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue); - return 0; - } - - if (u == 0 || u > UINT32_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid TCPAdvertisedMaximumSegmentSize= \"%s\", ignoring assignment: %m", rvalue); + "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue); return 0; } - n->advmss = u; - - TAKE_PTR(n); + route->priority_set = true; + TAKE_PTR(route); return 0; } -int config_parse_tcp_window( +int config_parse_route_scope( const char *unit, const char *filename, unsigned line, @@ -2713,8 +1709,8 @@ int config_parse_tcp_window( void *data, void *userdata) { - uint32_t *window = ASSERT_PTR(data); - uint32_t k; + Network *network = userdata; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; assert(filename); @@ -2723,28 +1719,28 @@ int config_parse_tcp_window( assert(rvalue); assert(data); - r = safe_atou32(rvalue, &k); + r = route_new_static(network, filename, section_line, &route); + if (r == -ENOMEM) + return log_oom(); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Could not parse TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue); - return 0; - } - if (k >= 1024) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Specified TCP %s \"%s\" is too large, ignoring assignment: %m", lvalue, rvalue); + "Failed to allocate route, ignoring assignment: %m"); return 0; } - if (k == 0) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid TCP %s \"%s\", ignoring assignment: %m", lvalue, rvalue); + + r = route_scope_from_string(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Unknown route scope: %s", rvalue); return 0; } - *window = k; + route->scope = r; + route->scope_set = true; + TAKE_PTR(route); return 0; } -int config_parse_route_tcp_window( +int config_parse_route_table( const char *unit, const char *filename, unsigned line, @@ -2756,9 +1752,8 @@ int config_parse_route_tcp_window( void *data, void *userdata) { - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; Network *network = userdata; - uint32_t *d; int r; assert(filename); @@ -2767,7 +1762,7 @@ int config_parse_route_tcp_window( assert(rvalue); assert(data); - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2776,22 +1771,19 @@ int config_parse_route_tcp_window( return 0; } - if (streq(lvalue, "InitialCongestionWindow")) - d = &n->initcwnd; - else if (streq(lvalue, "InitialAdvertisedReceiveWindow")) - d = &n->initrwnd; - else - assert_not_reached(); - - r = config_parse_tcp_window(unit, filename, line, section, section_line, lvalue, ltype, rvalue, d, userdata); - if (r < 0) - return r; + r = manager_get_route_table_from_string(network->manager, rvalue, &route->table); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Could not parse route table \"%s\", ignoring assignment: %m", rvalue); + return 0; + } - TAKE_PTR(n); + route->table_set = true; + TAKE_PTR(route); return 0; } -int config_parse_route_mtu( +int config_parse_ipv6_route_preference( const char *unit, const char *filename, unsigned line, @@ -2804,16 +1796,10 @@ int config_parse_route_mtu( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2822,15 +1808,23 @@ int config_parse_route_mtu( return 0; } - r = config_parse_mtu(unit, filename, line, section, section_line, lvalue, ltype, rvalue, &n->mtu, userdata); - if (r < 0) - return r; + if (streq(rvalue, "low")) + route->pref = SD_NDISC_PREFERENCE_LOW; + else if (streq(rvalue, "medium")) + route->pref = SD_NDISC_PREFERENCE_MEDIUM; + else if (streq(rvalue, "high")) + route->pref = SD_NDISC_PREFERENCE_HIGH; + else { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown route preference: %s", rvalue); + return 0; + } - TAKE_PTR(n); + route->pref_set = true; + TAKE_PTR(route); return 0; } -int config_parse_route_tcp_rto( +int config_parse_route_protocol( const char *unit, const char *filename, unsigned line, @@ -2843,17 +1837,10 @@ int config_parse_route_tcp_rto( void *userdata) { Network *network = userdata; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - usec_t usec; + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; int r; - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); - - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2862,27 +1849,20 @@ int config_parse_route_tcp_rto( return 0; } - r = parse_sec(rvalue, &usec); + r = route_protocol_from_string(rvalue); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to parse route TCP retransmission timeout (RTO), ignoring assignment: %s", rvalue); - return 0; - } - - if (IN_SET(usec, 0, USEC_INFINITY) || - DIV_ROUND_UP(usec, USEC_PER_MSEC) > UINT32_MAX) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Route TCP retransmission timeout (RTO) must be in the range 0…%"PRIu32"ms, ignoring assignment: %s", UINT32_MAX, rvalue); + "Failed to parse route protocol \"%s\", ignoring assignment: %m", rvalue); return 0; } - n->tcp_rto_usec = usec; + route->protocol = r; - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -int config_parse_multipath_route( +int config_parse_route_type( const char *unit, const char *filename, unsigned line, @@ -2894,22 +1874,11 @@ int config_parse_multipath_route( void *data, void *userdata) { - _cleanup_(multipath_route_freep) MultipathRoute *m = NULL; - _cleanup_(route_free_or_set_invalidp) Route *n = NULL; - _cleanup_free_ char *word = NULL; Network *network = userdata; - union in_addr_union a; - int family, r; - const char *p; - char *dev; - - assert(filename); - assert(section); - assert(lvalue); - assert(rvalue); - assert(data); + _cleanup_(route_unref_or_set_invalidp) Route *route = NULL; + int t, r; - r = route_new_static(network, filename, section_line, &n); + r = route_new_static(network, filename, section_line, &route); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -2918,222 +1887,69 @@ int config_parse_multipath_route( return 0; } - if (isempty(rvalue)) { - n->multipath_routes = ordered_set_free_with_destructor(n->multipath_routes, multipath_route_free); - TAKE_PTR(n); - return 0; - } - - m = new0(MultipathRoute, 1); - if (!m) - return log_oom(); - - p = rvalue; - r = extract_first_word(&p, &word, NULL, 0); - if (r == -ENOMEM) - return log_oom(); - if (r <= 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route option, ignoring assignment: %s", rvalue); - return 0; - } - - dev = strchr(word, '@'); - if (dev) { - *dev++ = '\0'; - - r = parse_ifindex(dev); - if (r > 0) - m->ifindex = r; - else { - if (!ifname_valid_full(dev, IFNAME_VALID_ALTERNATIVE)) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid interface name '%s' in %s=, ignoring: %s", dev, lvalue, rvalue); - return 0; - } - - m->ifname = strdup(dev); - if (!m->ifname) - return log_oom(); - } - } - - r = in_addr_from_string_auto(word, &family, &a); - if (r < 0) { + t = route_type_from_string(rvalue); + if (t < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route gateway '%s', ignoring assignment: %m", rvalue); + "Could not parse route type \"%s\", ignoring assignment: %m", rvalue); return 0; } - m->gateway.address = a; - m->gateway.family = family; - - if (!isempty(p)) { - r = safe_atou32(p, &m->weight); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Invalid multipath route weight, ignoring assignment: %s", p); - return 0; - } - /* ip command takes weight in the range 1…255, while kernel takes the value in the - * range 0…254. MultiPathRoute= setting also takes weight in the same range which ip - * command uses, then networkd decreases by one and stores it to match the range which - * kernel uses. */ - if (m->weight == 0 || m->weight > 256) { - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Invalid multipath route weight, ignoring assignment: %s", p); - return 0; - } - m->weight--; - } - r = ordered_set_ensure_put(&n->multipath_routes, NULL, m); - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_WARNING, filename, line, r, - "Failed to store multipath route, ignoring assignment: %m"); - return 0; - } + route->type = (unsigned char) t; - TAKE_PTR(m); - TAKE_PTR(n); + TAKE_PTR(route); return 0; } -static int route_section_verify(Route *route, Network *network) { +int route_section_verify(Route *route) { + int r; + + assert(route); + assert(route->section); + if (section_is_invalid(route->section)) return -EINVAL; /* Currently, we do not support static route with finite lifetime. */ assert(route->lifetime_usec == USEC_INFINITY); - if (route->gateway_from_dhcp_or_ra) { - if (route->gw_family == AF_UNSPEC) { - /* When deprecated Gateway=_dhcp is set, then assume gateway family based on other settings. */ - switch (route->family) { - case AF_UNSPEC: - log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " - "Please use \"_dhcp4\" or \"_ipv6ra\" instead. Assuming \"_dhcp4\".", - route->section->filename, route->section->line); - route->family = AF_INET; - break; - case AF_INET: - case AF_INET6: - log_warning("%s: Deprecated value \"_dhcp\" is specified for Gateway= in [Route] section from line %u. " - "Assuming \"%s\" based on Destination=, Source=, or PreferredSource= setting.", - route->section->filename, route->section->line, route->family == AF_INET ? "_dhcp4" : "_ipv6ra"); - break; - default: - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Invalid route family. Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - route->gw_family = route->family; - } - - if (route->gw_family == AF_INET && !FLAGS_SET(network->dhcp, ADDRESS_FAMILY_IPV4)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway=\"_dhcp4\" is specified but DHCPv4 client is disabled. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - - if (route->gw_family == AF_INET6 && !network->ipv6_accept_ra) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway=\"_ipv6ra\" is specified but IPv6AcceptRA= is disabled. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - /* When only Gateway= is specified, assume the route family based on the Gateway address. */ - if (route->family == AF_UNSPEC) - route->family = route->gw_family; - - if (route->family == AF_UNSPEC) { - assert(route->section); - - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Route section without Gateway=, Destination=, Source=, " - "or PreferredSource= field configured. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - if (route->family == AF_INET6 && route->gw_family == AF_INET) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: IPv4 gateway is configured for IPv6 route. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); + r = route_section_verify_nexthops(route); + if (r < 0) + return r; - if (!route->table_set && network->vrf) { - route->table = VRF(network->vrf)->table; + /* table */ + if (!route->table_set && route->network && route->network->vrf) { + route->table = VRF(route->network->vrf)->table; route->table_set = true; } if (!route->table_set && IN_SET(route->type, RTN_LOCAL, RTN_BROADCAST, RTN_ANYCAST, RTN_NAT)) route->table = RT_TABLE_LOCAL; - if (!route->scope_set && route->family != AF_INET6) { + /* scope */ + if (!route->scope_set && route->family == AF_INET) { if (IN_SET(route->type, RTN_LOCAL, RTN_NAT)) route->scope = RT_SCOPE_HOST; else if (IN_SET(route->type, RTN_BROADCAST, RTN_ANYCAST, RTN_MULTICAST)) route->scope = RT_SCOPE_LINK; else if (IN_SET(route->type, RTN_UNICAST, RTN_UNSPEC) && !route->gateway_from_dhcp_or_ra && - !in_addr_is_set(route->gw_family, &route->gw) && - ordered_set_isempty(route->multipath_routes) && + !in_addr_is_set(route->nexthop.family, &route->nexthop.gw) && + ordered_set_isempty(route->nexthops) && route->nexthop_id == 0) route->scope = RT_SCOPE_LINK; } - if (route->scope != RT_SCOPE_UNIVERSE && route->family == AF_INET6) { - log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); - route->scope = RT_SCOPE_UNIVERSE; - } - - if (route->family == AF_INET6 && route->priority == 0) - route->priority = IP6_RT_PRIO_USER; + /* IPv6 route */ + if (route->family == AF_INET6) { + if (route->scope != RT_SCOPE_UNIVERSE) { + log_warning("%s: Scope= is specified for IPv6 route. It will be ignored.", route->section->filename); + route->scope = RT_SCOPE_UNIVERSE; + } - if (route->gateway_onlink < 0 && in_addr_is_set(route->gw_family, &route->gw) && - ordered_hashmap_isempty(network->addresses_by_section)) { - /* If no address is configured, in most cases the gateway cannot be reachable. - * TODO: we may need to improve the condition above. */ - log_warning("%s: Gateway= without static address configured. " - "Enabling GatewayOnLink= option.", - network->filename); - route->gateway_onlink = true; + if (route->priority == 0) + route->priority = IP6_RT_PRIO_USER; } - if (route->gateway_onlink >= 0) - SET_FLAG(route->flags, RTNH_F_ONLINK, route->gateway_onlink); - - if (route->family == AF_INET6) { - MultipathRoute *m; - - ORDERED_SET_FOREACH(m, route->multipath_routes) - if (m->gateway.family == AF_INET) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: IPv4 multipath route is specified for IPv6 route. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - } - - if ((route->gateway_from_dhcp_or_ra || - in_addr_is_set(route->gw_family, &route->gw)) && - !ordered_set_isempty(route->multipath_routes)) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: Gateway= cannot be specified with MultiPathRoute=. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - - if (route->nexthop_id > 0 && - (route->gateway_from_dhcp_or_ra || - in_addr_is_set(route->gw_family, &route->gw) || - !ordered_set_isempty(route->multipath_routes))) - return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), - "%s: NextHopId= cannot be specified with Gateway= or MultiPathRoute=. " - "Ignoring [Route] section from line %u.", - route->section->filename, route->section->line); - return 0; } @@ -3143,6 +1959,6 @@ void network_drop_invalid_routes(Network *network) { assert(network); HASHMAP_FOREACH(route, network->routes_by_section) - if (route_section_verify(route, network) < 0) - route_free(route); + if (route_section_verify(route) < 0) + route_detach(route); } diff --git a/src/network/networkd-route.h b/src/network/networkd-route.h index 3d85889..912c6e5 100644 --- a/src/network/networkd-route.h +++ b/src/network/networkd-route.h @@ -9,12 +9,16 @@ #include "conf-parser.h" #include "in-addr-util.h" #include "networkd-link.h" +#include "networkd-route-metric.h" +#include "networkd-route-nexthop.h" #include "networkd-util.h" typedef struct Manager Manager; typedef struct Network Network; typedef struct Request Request; typedef struct Route Route; +typedef struct Wireguard Wireguard; + typedef int (*route_netlink_handler_t)( sd_netlink *rtnl, sd_netlink_message *m, @@ -23,86 +27,93 @@ typedef int (*route_netlink_handler_t)( Route *route); struct Route { - Link *link; Manager *manager; Network *network; + Wireguard *wireguard; ConfigSection *section; NetworkConfigSource source; NetworkConfigState state; union in_addr_union provider; /* DHCP server or router address */ - int family; - int gw_family; - uint32_t gw_weight; - int quickack; - int fast_open_no_cookie; - int ttl_propagate; + unsigned n_ref; + /* rtmsg header */ + int family; unsigned char dst_prefixlen; - unsigned char src_prefixlen; - unsigned char scope; + unsigned char src_prefixlen; /* IPv6 only */ + unsigned char tos; /* IPv4 only */ unsigned char protocol; /* RTPROT_* */ - unsigned char type; /* RTN_* */ - unsigned char tos; - uint32_t priority; /* note that ip(8) calls this 'metric' */ - uint32_t table; - uint32_t mtu; - uint32_t initcwnd; - uint32_t initrwnd; - uint32_t advmss; - uint32_t hop_limit; - char *tcp_congestion_control_algo; - unsigned char pref; - unsigned flags; - int gateway_onlink; /* Only used in conf parser and route_section_verify(). */ - uint32_t nexthop_id; - usec_t tcp_rto_usec; + unsigned char scope; /* IPv4 only */ + unsigned char type; /* RTN_*, e.g. RTN_LOCAL, RTN_UNREACHABLE */ + unsigned flags; /* e.g. RTNH_F_ONLINK */ + + /* attributes */ + union in_addr_union dst; /* RTA_DST */ + union in_addr_union src; /* RTA_SRC (IPv6 only) */ + uint32_t priority; /* RTA_PRIORITY, note that ip(8) calls this 'metric' */ + union in_addr_union prefsrc; /* RTA_PREFSRC */ + uint32_t table; /* RTA_TABLE, also used in rtmsg header */ + uint8_t pref; /* RTA_PREF (IPv6 only) */ + + /* nexthops */ + RouteNextHop nexthop; /* RTA_OIF, and RTA_GATEWAY or RTA_VIA (IPv4 only) */ + OrderedSet *nexthops; /* RTA_MULTIPATH */ + uint32_t nexthop_id; /* RTA_NH_ID */ + + /* metrics (RTA_METRICS) */ + RouteMetric metric; + + /* This is an absolute point in time, and NOT a timespan/duration. + * Must be specified with clock_boottime_or_monotonic(). */ + usec_t lifetime_usec; /* RTA_EXPIRES (IPv6 only) */ + /* Used when kernel does not support RTA_EXPIRES attribute. */ + sd_event_source *expire; + bool expiration_managed_by_kernel:1; /* RTA_CACHEINFO has nonzero rta_expires */ + /* Only used by conf persers and route_section_verify(). */ bool scope_set:1; bool table_set:1; bool priority_set:1; bool protocol_set:1; bool pref_set:1; bool gateway_from_dhcp_or_ra:1; - - union in_addr_union gw; - union in_addr_union dst; - union in_addr_union src; - union in_addr_union prefsrc; - OrderedSet *multipath_routes; - - /* This is an absolute point in time, and NOT a timespan/duration. - * Must be specified with clock_boottime_or_monotonic(). */ - usec_t lifetime_usec; - /* Used when kernel does not support RTA_EXPIRES attribute. */ - sd_event_source *expire; + int gateway_onlink; }; extern const struct hash_ops route_hash_ops; +extern const struct hash_ops route_hash_ops_unref; + +Route* route_ref(Route *route); +Route* route_unref(Route *route); +DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_unref); int route_new(Route **ret); -Route *route_free(Route *route); -DEFINE_SECTION_CLEANUP_FUNCTIONS(Route, route_free); -int route_dup(const Route *src, Route **ret); +int route_new_static(Network *network, const char *filename, unsigned section_line, Route **ret); +int route_dup(const Route *src, const RouteNextHop *nh, Route **ret); + +int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, Route *route, const char *error_msg); +int route_remove(Route *route, Manager *manager); +int route_remove_and_cancel(Route *route, Manager *manager); -int route_configure_handler_internal(sd_netlink *rtnl, sd_netlink_message *m, Link *link, const char *error_msg); -int route_remove(Route *route); -int route_remove_and_drop(Route *route); +int route_get(Manager *manager, const Route *route, Route **ret); +int route_get_request(Manager *manager, const Route *route, Request **ret); -int route_get(Manager *manager, Link *link, const Route *in, Route **ret); +bool route_can_update(const Route *existing, const Route *requesting); -int link_drop_managed_routes(Link *link); -int link_drop_foreign_routes(Link *link); -void link_foreignize_routes(Link *link); +int link_drop_routes(Link *link, bool foreign); +static inline int link_drop_static_routes(Link *link) { + return link_drop_routes(link, false); +} +static inline int link_drop_foreign_routes(Link *link) { + return link_drop_routes(link, true); +} +int link_foreignize_routes(Link *link); -void route_cancel_request(Route *route, Link *link); int link_request_route( Link *link, - Route *route, - bool consume_object, + const Route *route, unsigned *message_counter, - route_netlink_handler_t netlink_handler, - Request **ret); + route_netlink_handler_t netlink_handler); int link_request_static_routes(Link *link, bool only_ipv4); int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Manager *m); @@ -110,26 +121,16 @@ int manager_rtnl_process_route(sd_netlink *rtnl, sd_netlink_message *message, Ma int network_add_ipv4ll_route(Network *network); int network_add_default_route_on_device(Network *network); void network_drop_invalid_routes(Network *network); +int route_section_verify(Route *route); DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(Route, route); -void link_mark_routes(Link *link, NetworkConfigSource source); +void manager_mark_routes(Manager *manager, Link *link, NetworkConfigSource source); -CONFIG_PARSER_PROTOTYPE(config_parse_gateway); CONFIG_PARSER_PROTOTYPE(config_parse_preferred_src); CONFIG_PARSER_PROTOTYPE(config_parse_destination); CONFIG_PARSER_PROTOTYPE(config_parse_route_priority); CONFIG_PARSER_PROTOTYPE(config_parse_route_scope); CONFIG_PARSER_PROTOTYPE(config_parse_route_table); -CONFIG_PARSER_PROTOTYPE(config_parse_route_boolean); CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_route_preference); CONFIG_PARSER_PROTOTYPE(config_parse_route_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_route_type); -CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_window); -CONFIG_PARSER_PROTOTYPE(config_parse_route_hop_limit); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_window); -CONFIG_PARSER_PROTOTYPE(config_parse_route_tcp_rto); -CONFIG_PARSER_PROTOTYPE(config_parse_route_mtu); -CONFIG_PARSER_PROTOTYPE(config_parse_multipath_route); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_congestion); -CONFIG_PARSER_PROTOTYPE(config_parse_tcp_advmss); -CONFIG_PARSER_PROTOTYPE(config_parse_route_nexthop); diff --git a/src/network/networkd-routing-policy-rule.c b/src/network/networkd-routing-policy-rule.c index 0cb5831..886b4da 100644 --- a/src/network/networkd-routing-policy-rule.c +++ b/src/network/networkd-routing-policy-rule.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include @@ -156,33 +157,35 @@ static int routing_policy_rule_dup(const RoutingPolicyRule *src, RoutingPolicyRu static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct siphash *state) { assert(rule); - siphash24_compress(&rule->family, sizeof(rule->family), state); + siphash24_compress_typesafe(rule->family, state); switch (rule->family) { case AF_INET: case AF_INET6: - siphash24_compress(&rule->from, FAMILY_ADDRESS_SIZE(rule->family), state); - siphash24_compress(&rule->from_prefixlen, sizeof(rule->from_prefixlen), state); + in_addr_hash_func(&rule->from, rule->family, state); + siphash24_compress_typesafe(rule->from_prefixlen, state); - siphash24_compress(&rule->to, FAMILY_ADDRESS_SIZE(rule->family), state); - siphash24_compress(&rule->to_prefixlen, sizeof(rule->to_prefixlen), state); + siphash24_compress_boolean(rule->l3mdev, state); + + in_addr_hash_func(&rule->to, rule->family, state); + siphash24_compress_typesafe(rule->to_prefixlen, state); siphash24_compress_boolean(rule->invert_rule, state); - siphash24_compress(&rule->tos, sizeof(rule->tos), state); - siphash24_compress(&rule->type, sizeof(rule->type), state); - siphash24_compress(&rule->fwmark, sizeof(rule->fwmark), state); - siphash24_compress(&rule->fwmask, sizeof(rule->fwmask), state); - siphash24_compress(&rule->priority, sizeof(rule->priority), state); - siphash24_compress(&rule->table, sizeof(rule->table), state); - siphash24_compress(&rule->suppress_prefixlen, sizeof(rule->suppress_prefixlen), state); - siphash24_compress(&rule->suppress_ifgroup, sizeof(rule->suppress_ifgroup), state); - - siphash24_compress(&rule->ipproto, sizeof(rule->ipproto), state); - siphash24_compress(&rule->protocol, sizeof(rule->protocol), state); - siphash24_compress(&rule->sport, sizeof(rule->sport), state); - siphash24_compress(&rule->dport, sizeof(rule->dport), state); - siphash24_compress(&rule->uid_range, sizeof(rule->uid_range), state); + siphash24_compress_typesafe(rule->tos, state); + siphash24_compress_typesafe(rule->type, state); + siphash24_compress_typesafe(rule->fwmark, state); + siphash24_compress_typesafe(rule->fwmask, state); + siphash24_compress_typesafe(rule->priority, state); + siphash24_compress_typesafe(rule->table, state); + siphash24_compress_typesafe(rule->suppress_prefixlen, state); + siphash24_compress_typesafe(rule->suppress_ifgroup, state); + + siphash24_compress_typesafe(rule->ipproto, state); + siphash24_compress_typesafe(rule->protocol, state); + siphash24_compress_typesafe(rule->sport, state); + siphash24_compress_typesafe(rule->dport, state); + siphash24_compress_typesafe(rule->uid_range, state); siphash24_compress_string(rule->iif, state); siphash24_compress_string(rule->oif, state); @@ -212,6 +215,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro if (r != 0) return r; + r = CMP(a->l3mdev, b->l3mdev); + if (r != 0) + return r; + r = CMP(a->to_prefixlen, b->to_prefixlen); if (r != 0) return r; @@ -476,19 +483,19 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } - if (rule->table < 256) { + if (rule->l3mdev) + r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); + else if (rule->table < 256) r = sd_rtnl_message_routing_policy_rule_set_table(m, rule->table); - if (r < 0) - return r; - } else { + else { r = sd_rtnl_message_routing_policy_rule_set_table(m, RT_TABLE_UNSPEC); if (r < 0) return r; r = sd_netlink_message_append_u32(m, FRA_TABLE, rule->table); - if (r < 0) - return r; } + if (r < 0) + return r; if (rule->fwmark > 0) { r = sd_netlink_message_append_u32(m, FRA_FWMARK, rule->fwmark); @@ -544,6 +551,12 @@ static int routing_policy_rule_set_netlink_message(const RoutingPolicyRule *rule return r; } + if (rule->l3mdev) { + r = sd_netlink_message_append_u8(m, FRA_L3MDEV, 1); + if (r < 0) + return r; + } + if (rule->suppress_prefixlen >= 0) { r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen); if (r < 0) @@ -642,7 +655,7 @@ static void manager_mark_routing_policy_rules(Manager *m, bool foreign, const Li continue; /* When 'foreign' is true, mark only foreign rules, and vice versa. */ - if (foreign != (rule->source == NETWORK_CONFIG_SOURCE_FOREIGN)) + if (rule->source != (foreign ? NETWORK_CONFIG_SOURCE_FOREIGN : NETWORK_CONFIG_SOURCE_STATIC)) continue; /* Ignore rules not assigned yet or already removing. */ @@ -853,20 +866,17 @@ int link_request_static_routing_policy_rules(Link *link) { static const RoutingPolicyRule kernel_rules[] = { { .family = AF_INET, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET, .priority_set = true, .priority = 32767, .table = RT_TABLE_DEFAULT, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, { .family = AF_INET6, .priority_set = true, .priority = 0, .table = RT_TABLE_LOCAL, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, + { .family = AF_INET6, .priority_set = true, .priority = 1000, .table = RT_TABLE_UNSPEC, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, .l3mdev = true }, { .family = AF_INET6, .priority_set = true, .priority = 32766, .table = RT_TABLE_MAIN, .type = FR_ACT_TO_TBL, .uid_range.start = UID_INVALID, .uid_range.end = UID_INVALID, .suppress_prefixlen = -1, .suppress_ifgroup = -1, }, }; static bool routing_policy_rule_is_created_by_kernel(const RoutingPolicyRule *rule) { assert(rule); - if (rule->l3mdev > 0) - /* Currently, [RoutingPolicyRule] does not explicitly set FRA_L3MDEV. So, if the flag - * is set, it is safe to treat the rule as created by kernel. */ - return true; - for (size_t i = 0; i < ELEMENTSOF(kernel_rules); i++) if (routing_policy_rule_equal(rule, &kernel_rules[i])) return true; @@ -1016,11 +1026,13 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, Man return 0; } - r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &tmp->l3mdev); + uint8_t l3mdev = 0; + r = sd_netlink_message_read_u8(message, FRA_L3MDEV, &l3mdev); if (r < 0 && r != -ENODATA) { log_warning_errno(r, "rtnl: could not get FRA_L3MDEV attribute, ignoring: %m"); return 0; } + tmp->l3mdev = l3mdev != 0; r = sd_netlink_message_read(message, FRA_SPORT_RANGE, sizeof(tmp->sport), &tmp->sport); if (r < 0 && r != -ENODATA) { @@ -1408,7 +1420,7 @@ int config_parse_routing_policy_rule_port_range( if (r < 0) return log_oom(); - r = parse_ip_port_range(rvalue, &low, &high); + r = parse_ip_port_range(rvalue, &low, &high, /* allow_zero = */ false); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse routing policy rule port range '%s'", rvalue); return 0; @@ -1502,6 +1514,44 @@ int config_parse_routing_policy_rule_invert( return 0; } +int config_parse_routing_policy_rule_l3mdev( + 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_(routing_policy_rule_free_or_set_invalidp) RoutingPolicyRule *n = NULL; + Network *network = userdata; + int r; + + assert(filename); + assert(section); + assert(lvalue); + assert(rvalue); + assert(data); + + r = routing_policy_rule_new_static(network, filename, section_line, &n); + if (r < 0) + return log_oom(); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse RPDB rule l3mdev, ignoring: %s", rvalue); + return 0; + } + + n->l3mdev = r; + + TAKE_PTR(n); + return 0; +} + int config_parse_routing_policy_rule_family( const char *unit, const char *filename, @@ -1734,12 +1784,6 @@ static int routing_policy_rule_section_verify(RoutingPolicyRule *rule) { /* rule->family can be AF_UNSPEC only when Family=both. */ } - /* Currently, [RoutingPolicyRule] does not have a setting to set FRA_L3MDEV flag. Please also - * update routing_policy_rule_is_created_by_kernel() when a new setting which sets the flag is - * added in the future. */ - if (rule->l3mdev > 0) - assert_not_reached(); - return 0; } diff --git a/src/network/networkd-routing-policy-rule.h b/src/network/networkd-routing-policy-rule.h index b6ce2fa..42a575c 100644 --- a/src/network/networkd-routing-policy-rule.h +++ b/src/network/networkd-routing-policy-rule.h @@ -22,6 +22,7 @@ typedef struct RoutingPolicyRule { bool invert_rule; bool priority_set; + bool l3mdev; /* FRA_L3MDEV */ uint8_t tos; uint8_t type; @@ -29,7 +30,6 @@ typedef struct RoutingPolicyRule { uint8_t protocol; /* FRA_PROTOCOL */ uint8_t to_prefixlen; uint8_t from_prefixlen; - uint8_t l3mdev; /* FRA_L3MDEV */ uint32_t table; uint32_t fwmark; @@ -66,7 +66,7 @@ int manager_drop_routing_policy_rules_internal(Manager *m, bool foreign, const L static inline int manager_drop_foreign_routing_policy_rules(Manager *m) { return manager_drop_routing_policy_rules_internal(m, true, NULL); } -static inline int link_drop_managed_routing_policy_rules(Link *link) { +static inline int link_drop_static_routing_policy_rules(Link *link) { assert(link); return manager_drop_routing_policy_rules_internal(link->manager, false, link); } @@ -80,6 +80,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_fwmark_mask); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_prefix); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_priority); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_device); +CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_l3mdev); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_port_range); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_ip_protocol); CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_invert); diff --git a/src/network/networkd-setlink.c b/src/network/networkd-setlink.c index 011ea1f..058bc00 100644 --- a/src/network/networkd-setlink.c +++ b/src/network/networkd-setlink.c @@ -103,6 +103,19 @@ static int link_set_bridge_handler(sd_netlink *rtnl, sd_netlink_message *m, Requ } static int link_set_bridge_vlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { + int r; + + assert(link); + + r = set_link_handler_internal(rtnl, m, req, link, /* ignore = */ false, NULL); + if (r <= 0) + return r; + + link->bridge_vlan_set = true; + return 0; +} + +static int link_del_bridge_vlan_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ false, NULL); } @@ -160,19 +173,7 @@ static int link_unset_master_handler(sd_netlink *rtnl, sd_netlink_message *m, Re } static int link_set_mtu_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, void *userdata) { - int r; - - r = set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); - if (r <= 0) - return r; - - /* The kernel resets ipv6 mtu after changing device mtu; - * we must set this here, after we've set device mtu */ - r = link_set_ipv6_mtu(link); - if (r < 0) - log_link_warning_errno(link, r, "Failed to set IPv6 MTU, ignoring: %m"); - - return 0; + return set_link_handler_internal(rtnl, m, req, link, /* ignore = */ true, get_link_default_handler); } static int link_configure_fill_message( @@ -326,29 +327,14 @@ static int link_configure_fill_message( return r; break; case REQUEST_TYPE_SET_LINK_BRIDGE_VLAN: - r = sd_rtnl_message_link_set_family(req, AF_BRIDGE); - if (r < 0) - return r; - - r = sd_netlink_message_open_container(req, IFLA_AF_SPEC); + r = bridge_vlan_set_message(link, req, /* is_set = */ true); if (r < 0) return r; - - if (link->master_ifindex <= 0) { - /* master needs BRIDGE_FLAGS_SELF flag */ - r = sd_netlink_message_append_u16(req, IFLA_BRIDGE_FLAGS, BRIDGE_FLAGS_SELF); - if (r < 0) - return r; - } - - r = bridge_vlan_append_info(link, req, link->network->pvid, link->network->br_vid_bitmap, link->network->br_untagged_bitmap); - if (r < 0) - return r; - - r = sd_netlink_message_close_container(req); + break; + case REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN: + r = bridge_vlan_set_message(link, req, /* is_set = */ false); if (r < 0) return r; - break; case REQUEST_TYPE_SET_LINK_CAN: r = can_set_netlink_message(link, req); @@ -430,6 +416,8 @@ static int link_configure(Link *link, Request *req) { r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->master_ifindex); else if (IN_SET(req->type, REQUEST_TYPE_SET_LINK_CAN, REQUEST_TYPE_SET_LINK_IPOIB)) r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_NEWLINK, link->ifindex); + else if (req->type == REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN) + r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_DELLINK, link->ifindex); else r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_SETLINK, link->ifindex); if (r < 0) @@ -453,6 +441,43 @@ static bool netdev_is_ready(NetDev *netdev) { return true; } +static uint32_t link_adjust_mtu(Link *link, uint32_t mtu) { + const char *origin; + uint32_t min_mtu; + + assert(link); + assert(link->network); + + min_mtu = link->min_mtu; + origin = "the minimum MTU of the interface"; + if (link_ipv6_enabled(link)) { + /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up + * MTU bytes to IPV6_MTU_MIN. */ + if (min_mtu < IPV6_MIN_MTU) { + min_mtu = IPV6_MIN_MTU; + origin = "the minimum IPv6 MTU"; + } + if (min_mtu < link->network->ipv6_mtu) { + min_mtu = link->network->ipv6_mtu; + origin = "the requested IPv6 MTU in IPv6MTUBytes="; + } + } + + if (mtu < min_mtu) { + log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", + mtu, origin, min_mtu); + mtu = min_mtu; + } + + if (mtu > link->max_mtu) { + log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->max_mtu); + mtu = link->max_mtu; + } + + return mtu; +} + static int link_is_ready_to_set_link(Link *link, Request *req) { int r; @@ -480,9 +505,11 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { if (link->network->keep_master && link->master_ifindex <= 0 && !streq_ptr(link->kind, "bridge")) return false; - break; + case REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN: + return link->bridge_vlan_set; + case REQUEST_TYPE_SET_LINK_CAN: /* Do not check link->set_flags_messages here, as it is ok even if link->flags * is outdated, and checking the counter causes a deadlock. */ @@ -568,13 +595,24 @@ static int link_is_ready_to_set_link(Link *link, Request *req) { })) return false; - /* Changing FD mode may affect MTU. */ + /* Changing FD mode may affect MTU. + * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support + * MTU = 16 (CAN_MTU) => Classical CAN device + * MTU = 72 (CANFD_MTU) => CAN FD capable device */ if (ordered_set_contains(link->manager->request_queue, &(const Request) { .link = link, .type = REQUEST_TYPE_SET_LINK_CAN, })) return false; + + /* Now, it is ready to set MTU, but before setting, adjust requested MTU. */ + uint32_t mtu = link_adjust_mtu(link, PTR_TO_UINT32(req->userdata)); + if (mtu == link->mtu) + return -EALREADY; /* Not necessary to set the same value. */ + + req->userdata = UINT32_TO_PTR(mtu); + return true; } default: break; @@ -712,10 +750,14 @@ int link_request_to_set_bridge(Link *link) { } int link_request_to_set_bridge_vlan(Link *link) { + int r; + assert(link); assert(link->network); - if (!link->network->use_br_vlan) + /* If nothing configured, use the default vlan ID. */ + if (memeqzero(link->network->bridge_vlan_bitmap, BRIDGE_VLAN_BITMAP_LEN * sizeof(uint32_t)) && + link->network->bridge_vlan_pvid == BRIDGE_VLAN_KEEP_PVID) return 0; if (!link->network->bridge && !streq_ptr(link->kind, "bridge")) { @@ -731,9 +773,21 @@ int link_request_to_set_bridge_vlan(Link *link) { return 0; } - return link_request_set_link(link, REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, - link_set_bridge_vlan_handler, - NULL); + link->bridge_vlan_set = false; + + r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_BRIDGE_VLAN, + link_set_bridge_vlan_handler, + NULL); + if (r < 0) + return r; + + r = link_request_set_link(link, REQUEST_TYPE_DEL_LINK_BRIDGE_VLAN, + link_del_bridge_vlan_handler, + NULL); + if (r < 0) + return r; + + return 0; } int link_request_to_set_can(Link *link) { @@ -847,51 +901,12 @@ int link_request_to_set_master(Link *link) { } int link_request_to_set_mtu(Link *link, uint32_t mtu) { - const char *origin; - uint32_t min_mtu, max_mtu; Request *req; int r; assert(link); - assert(link->network); - - min_mtu = link->min_mtu; - origin = "the minimum MTU of the interface"; - if (link_ipv6_enabled(link)) { - /* IPv6 protocol requires a minimum MTU of IPV6_MTU_MIN(1280) bytes on the interface. Bump up - * MTU bytes to IPV6_MTU_MIN. */ - if (min_mtu < IPV6_MIN_MTU) { - min_mtu = IPV6_MIN_MTU; - origin = "the minimum IPv6 MTU"; - } - if (min_mtu < link->network->ipv6_mtu) { - min_mtu = link->network->ipv6_mtu; - origin = "the requested IPv6 MTU in IPv6MTUBytes="; - } - } - - if (mtu < min_mtu) { - log_link_warning(link, "Bumping the requested MTU %"PRIu32" to %s (%"PRIu32")", - mtu, origin, min_mtu); - mtu = min_mtu; - } - - max_mtu = link->max_mtu; - if (link->iftype == ARPHRD_CAN) - /* The maximum MTU may be changed when FD mode is changed. - * See https://docs.kernel.org/networking/can.html#can-fd-flexible-data-rate-driver-support - * MTU = 16 (CAN_MTU) => Classical CAN device - * MTU = 72 (CANFD_MTU) => CAN FD capable device - * So, even if the current maximum is 16, we should not reduce the requested value now. */ - max_mtu = MAX(max_mtu, 72u); - - if (mtu > max_mtu) { - log_link_warning(link, "Reducing the requested MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, max_mtu); - mtu = max_mtu; - } - if (link->mtu == mtu) + if (mtu == 0) return 0; r = link_request_set_link(link, REQUEST_TYPE_SET_LINK_MTU, diff --git a/src/network/networkd-state-file.c b/src/network/networkd-state-file.c index bba84bb..fbe4fee 100644 --- a/src/network/networkd-state-file.c +++ b/src/network/networkd-state-file.c @@ -15,6 +15,7 @@ #include "networkd-manager-bus.h" #include "networkd-manager.h" #include "networkd-network.h" +#include "networkd-ntp.h" #include "networkd-state-file.h" #include "ordered-set.h" #include "set.h" @@ -101,7 +102,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_dns) { + if (link->dhcp_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *addresses; r = sd_dhcp_lease_get_dns(link->dhcp_lease, &addresses); @@ -112,7 +113,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_dns) { + if (link->dhcp6_lease && link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *addresses; r = sd_dhcp6_lease_get_dns(link->dhcp6_lease, &addresses); @@ -123,7 +124,7 @@ static int link_put_dns(Link *link, OrderedSet **s) { } } - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *a; SET_FOREACH(a, link->ndisc_rdnss) { @@ -150,7 +151,7 @@ static int link_put_ntp(Link *link, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_ntp) { + if (link->dhcp_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4)) { const struct in_addr *addresses; r = sd_dhcp_lease_get_ntp(link->dhcp_lease, &addresses); @@ -161,7 +162,7 @@ static int link_put_ntp(Link *link, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_ntp) { + if (link->dhcp6_lease && link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6)) { const struct in6_addr *addresses; char **fqdn; @@ -206,7 +207,7 @@ static int link_put_sip(Link *link, OrderedSet **s) { static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { OrderedSet *link_domains, *network_domains; - DHCPUseDomains use_domains; + UseDomains use_domains; int r; assert(link); @@ -215,7 +216,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { link_domains = is_route ? link->route_domains : link->search_domains; network_domains = is_route ? link->network->route_domains : link->network->search_domains; - use_domains = is_route ? DHCP_USE_DOMAINS_ROUTE : DHCP_USE_DOMAINS_YES; + use_domains = is_route ? USE_DOMAINS_ROUTE : USE_DOMAINS_YES; if (link_domains) return ordered_set_put_string_set(s, link_domains); @@ -224,7 +225,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { if (r < 0) return r; - if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { const char *domainname; char **domains; @@ -243,7 +244,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { } } - if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { char **domains; r = sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains); @@ -254,7 +255,7 @@ static int link_put_domains(Link *link, bool is_route, OrderedSet **s) { } } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *a; SET_FOREACH(a, link->ndisc_dnssl) { @@ -528,7 +529,7 @@ static void serialize_addresses( fputc('\n', f); } -static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, DHCPUseDomains use_domains) { +static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, UseDomains use_domains) { bool space = false; const char *p; @@ -537,33 +538,33 @@ static void link_save_domains(Link *link, FILE *f, OrderedSet *static_domains, D assert(f); ORDERED_SET_FOREACH(p, static_domains) - fputs_with_space(f, p, NULL, &space); + fputs_with_separator(f, p, NULL, &space); - if (use_domains == DHCP_USE_DOMAINS_NO) + if (use_domains == USE_DOMAINS_NO) return; - if (link->dhcp_lease && link->network->dhcp_use_domains == use_domains) { + if (link->dhcp_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP4) == use_domains) { const char *domainname; char **domains; if (sd_dhcp_lease_get_domainname(link->dhcp_lease, &domainname) >= 0) - fputs_with_space(f, domainname, NULL, &space); + fputs_with_separator(f, domainname, NULL, &space); if (sd_dhcp_lease_get_search_domains(link->dhcp_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } - if (link->dhcp6_lease && link->network->dhcp6_use_domains == use_domains) { + if (link->dhcp6_lease && link_get_use_domains(link, NETWORK_CONFIG_SOURCE_DHCP6) == use_domains) { char **domains; if (sd_dhcp6_lease_get_domains(link->dhcp6_lease, &domains) >= 0) fputstrv(f, domains, NULL, &space); } - if (link->network->ipv6_accept_ra_use_domains == use_domains) { + if (link_get_use_domains(link, NETWORK_CONFIG_SOURCE_NDISC) == use_domains) { NDiscDNSSL *dd; SET_FOREACH(dd, link->ndisc_dnssl) - fputs_with_space(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); + fputs_with_separator(f, NDISC_DNSSL_DOMAIN(dd), NULL, &space); } } @@ -583,8 +584,6 @@ static int link_save(Link *link) { if (link->state == LINK_STATE_LINGER) return 0; - link_lldp_save(link); - admin_state = link_state_to_string(link->state); assert(admin_state); @@ -630,14 +629,14 @@ static int link_save(Link *link) { fprintf(f, "REQUIRED_FOR_ONLINE=%s\n", yes_no(link->network->required_for_online)); - LinkOperationalStateRange st = link->network->required_operstate_for_online; - fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s%s%s\n", - strempty(link_operstate_to_string(st.min)), - st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? ":" : "", - st.max != LINK_OPERSTATE_RANGE_DEFAULT.max ? strempty(link_operstate_to_string(st.max)) : ""); + LinkOperationalStateRange st; + link_required_operstate_for_online(link, &st); + + fprintf(f, "REQUIRED_OPER_STATE_FOR_ONLINE=%s:%s\n", + link_operstate_to_string(st.min), link_operstate_to_string(st.max)); fprintf(f, "REQUIRED_FAMILY_FOR_ONLINE=%s\n", - link_required_address_family_to_string(link->network->required_family_for_online)); + link_required_address_family_to_string(link_required_family_for_online(link))); fprintf(f, "ACTIVATION_POLICY=%s\n", activation_policy_to_string(link->network->activation_policy)); @@ -652,7 +651,7 @@ static int link_save(Link *link) { if (!escaped) return -ENOMEM; - fputs_with_space(f, escaped, ":", &space); + fputs_with_separator(f, escaped, ":", &space); } fputs("\"\n", f); @@ -668,14 +667,14 @@ static int link_save(Link *link) { serialize_addresses(f, NULL, &space, NULL, link->dhcp_lease, - link->network->dhcp_use_dns, + link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP4), SD_DHCP_LEASE_DNS, link->dhcp6_lease, - link->network->dhcp6_use_dns, + link_get_use_dns(link, NETWORK_CONFIG_SOURCE_DHCP6), sd_dhcp6_lease_get_dns, NULL); - if (link->network->ipv6_accept_ra_use_dns) { + if (link_get_use_dns(link, NETWORK_CONFIG_SOURCE_NDISC)) { NDiscRDNSS *dd; SET_FOREACH(dd, link->ndisc_rdnss) @@ -695,10 +694,10 @@ static int link_save(Link *link) { serialize_addresses(f, "NTP", NULL, link->network->ntp, link->dhcp_lease, - link->network->dhcp_use_ntp, + link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP4), SD_DHCP_LEASE_NTP, link->dhcp6_lease, - link->network->dhcp6_use_ntp, + link_get_use_ntp(link, NETWORK_CONFIG_SOURCE_DHCP6), sd_dhcp6_lease_get_ntp_addrs, sd_dhcp6_lease_get_ntp_fqdn); @@ -722,18 +721,18 @@ static int link_save(Link *link) { fputs("DOMAINS=", f); if (link->search_domains) - link_save_domains(link, f, link->search_domains, DHCP_USE_DOMAINS_NO); + link_save_domains(link, f, link->search_domains, USE_DOMAINS_NO); else - link_save_domains(link, f, link->network->search_domains, DHCP_USE_DOMAINS_YES); + link_save_domains(link, f, link->network->search_domains, USE_DOMAINS_YES); fputc('\n', f); /************************************************************/ fputs("ROUTE_DOMAINS=", f); if (link->route_domains) - link_save_domains(link, f, link->route_domains, DHCP_USE_DOMAINS_NO); + link_save_domains(link, f, link->route_domains, USE_DOMAINS_NO); else - link_save_domains(link, f, link->network->route_domains, DHCP_USE_DOMAINS_ROUTE); + link_save_domains(link, f, link->network->route_domains, USE_DOMAINS_ROUTE); fputc('\n', f); /************************************************************/ @@ -782,7 +781,7 @@ static int link_save(Link *link) { fputs("DNSSEC_NTA=", f); space = false; SET_FOREACH(n, nta_anchors) - fputs_with_space(f, n, NULL, &space); + fputs_with_separator(f, n, NULL, &space); fputc('\n', f); } } @@ -861,3 +860,26 @@ int link_save_and_clean_full(Link *link, bool also_save_manager) { link_clean(link); return k; } + +int manager_clean_all(Manager *manager) { + int r, ret = 0; + + assert(manager); + + if (manager->dirty) { + r = manager_save(manager); + if (r < 0) + log_warning_errno(r, "Failed to update state file %s, ignoring: %m", manager->state_file); + RET_GATHER(ret, r); + } + + Link *link; + SET_FOREACH(link, manager->dirty_links) { + r = link_save_and_clean(link); + if (r < 0) + log_link_warning_errno(link, r, "Failed to update link state file %s, ignoring: %m", link->state_file); + RET_GATHER(ret, r); + } + + return ret; +} diff --git a/src/network/networkd-state-file.h b/src/network/networkd-state-file.h index 684f0d1..7efd157 100644 --- a/src/network/networkd-state-file.h +++ b/src/network/networkd-state-file.h @@ -12,3 +12,4 @@ static inline int link_save_and_clean(Link *link) { } int manager_save(Manager *m); +int manager_clean_all(Manager *manager); diff --git a/src/network/networkd-sysctl.c b/src/network/networkd-sysctl.c index 2b226b2..68c23e0 100644 --- a/src/network/networkd-sysctl.c +++ b/src/network/networkd-sysctl.c @@ -4,6 +4,7 @@ #include #include +#include "af-list.h" #include "missing_network.h" #include "networkd-link.h" #include "networkd-manager.h" @@ -13,6 +14,40 @@ #include "string-table.h" #include "sysctl-util.h" +static void manager_set_ip_forwarding(Manager *manager, int family) { + int r, t; + + assert(manager); + assert(IN_SET(family, AF_INET, AF_INET6)); + + if (family == AF_INET6 && !socket_ipv6_is_supported()) + return; + + t = manager->ip_forwarding[family == AF_INET6]; + if (t < 0) + return; /* keep */ + + /* First, set the default value. */ + r = sysctl_write_ip_property_boolean(family, "default", "forwarding", t); + if (r < 0) + log_warning_errno(r, "Failed to %s the default %s forwarding: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); + + /* Then, set the value to all interfaces. */ + r = sysctl_write_ip_property_boolean(family, "all", "forwarding", t); + if (r < 0) + log_warning_errno(r, "Failed to %s %s forwarding for all interfaces: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); +} + +void manager_set_sysctl(Manager *manager) { + assert(manager); + assert(!manager->test_mode); + + manager_set_ip_forwarding(manager, AF_INET); + manager_set_ip_forwarding(manager, AF_INET6); +} + static bool link_is_configured_for_family(Link *link, int family) { assert(link); @@ -58,48 +93,62 @@ static int link_set_proxy_arp(Link *link) { return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp", link->network->proxy_arp > 0); } -static bool link_ip_forward_enabled(Link *link, int family) { +static int link_set_proxy_arp_pvlan(Link *link) { assert(link); - assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_is_configured_for_family(link, family)) - return false; + if (!link_is_configured_for_family(link, AF_INET)) + return 0; - return link->network->ip_forward & (family == AF_INET ? ADDRESS_FAMILY_IPV4 : ADDRESS_FAMILY_IPV6); + if (link->network->proxy_arp_pvlan < 0) + return 0; + + return sysctl_write_ip_property_boolean(AF_INET, link->ifname, "proxy_arp_pvlan", link->network->proxy_arp_pvlan > 0); } -static int link_set_ipv4_forward(Link *link) { +int link_get_ip_forwarding(Link *link, int family) { assert(link); + assert(link->manager); + assert(link->network); + assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_ip_forward_enabled(link, AF_INET)) - return 0; + /* If it is explicitly specified, then honor the setting. */ + int t = link->network->ip_forwarding[family == AF_INET6]; + if (t >= 0) + return t; + + /* If IPMasquerade= is enabled, also enable IP forwarding. */ + if (family == AF_INET && FLAGS_SET(link->network->ip_masquerade, ADDRESS_FAMILY_IPV4)) + return true; + if (family == AF_INET6 && FLAGS_SET(link->network->ip_masquerade, ADDRESS_FAMILY_IPV6)) + return true; - /* We propagate the forwarding flag from one interface to the - * global setting one way. This means: as long as at least one - * interface was configured at any time that had IP forwarding - * enabled the setting will stay on for good. We do this - * primarily to keep IPv4 and IPv6 packet forwarding behaviour - * somewhat in sync (see below). */ + /* If IPv6SendRA= is enabled, also enable IPv6 forwarding. */ + if (family == AF_INET6 && link_radv_enabled(link)) + return true; - return sysctl_write_ip_property(AF_INET, NULL, "ip_forward", "1"); + /* Otherwise, use the global setting. */ + return link->manager->ip_forwarding[family == AF_INET6]; } -static int link_set_ipv6_forward(Link *link) { +static int link_set_ip_forwarding(Link *link, int family) { + int r, t; + assert(link); + assert(IN_SET(family, AF_INET, AF_INET6)); - if (!link_ip_forward_enabled(link, AF_INET6)) + if (!link_is_configured_for_family(link, family)) return 0; - /* On Linux, the IPv6 stack does not know a per-interface - * packet forwarding setting: either packet forwarding is on - * for all, or off for all. We hence don't bother with a - * per-interface setting, but simply propagate the interface - * flag, if it is set, to the global flag, one-way. Note that - * while IPv4 would allow a per-interface flag, we expose the - * same behaviour there and also propagate the setting from - * one to all, to keep things simple (see above). */ + t = link_get_ip_forwarding(link, family); + if (t < 0) + return 0; /* keep */ - return sysctl_write_ip_property(AF_INET6, "all", "forwarding", "1"); + r = sysctl_write_ip_property_boolean(family, link->ifname, "forwarding", t); + if (r < 0) + return log_link_warning_errno(link, r, "Failed to %s %s forwarding, ignoring: %m", + enable_disable(t), af_to_ipv4_ipv6(family)); + + return 0; } static int link_set_ipv4_rp_filter(Link *link) { @@ -167,6 +216,24 @@ static int link_set_ipv6_hop_limit(Link *link) { return sysctl_write_ip_property_int(AF_INET6, link->ifname, "hop_limit", link->network->ipv6_hop_limit); } +static int link_set_ipv6_retransmission_time(Link *link) { + usec_t retrans_time_ms; + + assert(link); + + if (!link_is_configured_for_family(link, AF_INET6)) + return 0; + + if (!timestamp_is_set(link->network->ipv6_retransmission_time)) + return 0; + + retrans_time_ms = DIV_ROUND_UP(link->network->ipv6_retransmission_time, USEC_PER_MSEC); + if (retrans_time_ms <= 0 || retrans_time_ms > UINT32_MAX) + return 0; + + return sysctl_write_ip_neighbor_property_uint32(AF_INET6, link->ifname, "retrans_time_ms", retrans_time_ms); +} + static int link_set_ipv6_proxy_ndp(Link *link) { bool v; @@ -183,22 +250,28 @@ static int link_set_ipv6_proxy_ndp(Link *link) { return sysctl_write_ip_property_boolean(AF_INET6, link->ifname, "proxy_ndp", v); } -int link_set_ipv6_mtu(Link *link) { - uint32_t mtu; +int link_set_ipv6_mtu(Link *link, int log_level) { + uint32_t mtu = 0; assert(link); if (!link_is_configured_for_family(link, AF_INET6)) return 0; - if (link->network->ipv6_mtu == 0) + assert(link->network); + + if (link->network->ndisc_use_mtu) + mtu = link->ndisc_mtu; + if (mtu == 0) + mtu = link->network->ipv6_mtu; + if (mtu == 0) return 0; - mtu = link->network->ipv6_mtu; - if (mtu > link->max_mtu) { - log_link_warning(link, "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", - mtu, link->max_mtu); - mtu = link->max_mtu; + if (mtu > link->mtu) { + log_link_full(link, log_level, + "Reducing requested IPv6 MTU %"PRIu32" to the interface's maximum MTU %"PRIu32".", + mtu, link->mtu); + mtu = link->mtu; } return sysctl_write_ip_property_uint32(AF_INET6, link->ifname, "mtu", mtu); @@ -257,13 +330,12 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot configure proxy ARP for interface, ignoring: %m"); - r = link_set_ipv4_forward(link); + r = link_set_proxy_arp_pvlan(link); if (r < 0) - log_link_warning_errno(link, r, "Cannot turn on IPv4 packet forwarding, ignoring: %m"); + log_link_warning_errno(link, r, "Cannot configure proxy ARP private VLAN for interface, ignoring: %m"); - r = link_set_ipv6_forward(link); - if (r < 0) - log_link_warning_errno(link, r, "Cannot configure IPv6 packet forwarding, ignoring: %m"); + (void) link_set_ip_forwarding(link, AF_INET); + (void) link_set_ip_forwarding(link, AF_INET6); r = link_set_ipv6_privacy_extensions(link); if (r < 0) @@ -281,11 +353,15 @@ int link_set_sysctl(Link *link) { if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 hop limit for interface, ignoring: %m"); + r = link_set_ipv6_retransmission_time(link); + if (r < 0) + log_link_warning_errno(link, r, "Cannot set IPv6 retransmission time for interface, ignoring: %m"); + r = link_set_ipv6_proxy_ndp(link); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 proxy NDP, ignoring: %m"); - r = link_set_ipv6_mtu(link); + r = link_set_ipv6_mtu(link, LOG_INFO); if (r < 0) log_link_warning_errno(link, r, "Cannot set IPv6 MTU, ignoring: %m"); @@ -333,3 +409,24 @@ static const char* const ip_reverse_path_filter_table[_IP_REVERSE_PATH_FILTER_MA DEFINE_STRING_TABLE_LOOKUP(ip_reverse_path_filter, IPReversePathFilter); DEFINE_CONFIG_PARSE_ENUM(config_parse_ip_reverse_path_filter, ip_reverse_path_filter, IPReversePathFilter, "Failed to parse IP reverse path filter option"); + +int config_parse_ip_forward_deprecated( + 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) { + + assert(filename); + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "IPForward= setting is deprecated. " + "Please use IPv4Forwarding= and/or IPv6Forwarding= in networkd.conf for global setting, " + "and the same settings in .network files for per-interface setting."); + return 0; +} diff --git a/src/network/networkd-sysctl.h b/src/network/networkd-sysctl.h index 0644384..d7a9b1f 100644 --- a/src/network/networkd-sysctl.h +++ b/src/network/networkd-sysctl.h @@ -6,6 +6,7 @@ #include "conf-parser.h" typedef struct Link Link; +typedef struct Manager Manager; typedef enum IPv6PrivacyExtensions { /* These values map to the kernel's /proc/sys/net/ipv6/conf/xxx/use_tempaddr values. Do not reorder! */ @@ -26,8 +27,11 @@ typedef enum IPReversePathFilter { _IP_REVERSE_PATH_FILTER_INVALID = -EINVAL, } IPReversePathFilter; +void manager_set_sysctl(Manager *manager); + +int link_get_ip_forwarding(Link *link, int family); int link_set_sysctl(Link *link); -int link_set_ipv6_mtu(Link *link); +int link_set_ipv6_mtu(Link *link, int log_level); const char* ipv6_privacy_extensions_to_string(IPv6PrivacyExtensions i) _const_; IPv6PrivacyExtensions ipv6_privacy_extensions_from_string(const char *s) _pure_; @@ -37,3 +41,4 @@ IPReversePathFilter ip_reverse_path_filter_from_string(const char *s) _pure_; CONFIG_PARSER_PROTOTYPE(config_parse_ipv6_privacy_extensions); CONFIG_PARSER_PROTOTYPE(config_parse_ip_reverse_path_filter); +CONFIG_PARSER_PROTOTYPE(config_parse_ip_forward_deprecated); diff --git a/src/network/networkd-util.c b/src/network/networkd-util.c index 33352ba..46f9008 100644 --- a/src/network/networkd-util.c +++ b/src/network/networkd-util.c @@ -116,48 +116,6 @@ DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(dhcp_deprecated_address_family, AddressFa DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(ip_masquerade_address_family, AddressFamily); DEFINE_STRING_TABLE_LOOKUP(dhcp_lease_server_type, sd_dhcp_lease_server_type_t); -int config_parse_address_family_with_kernel( - 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) { - - AddressFamily *fwd = data, s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* This function is mostly obsolete now. It simply redirects - * "kernel" to "no". In older networkd versions we used to - * distinguish IPForward=off from IPForward=kernel, where the - * former would explicitly turn off forwarding while the - * latter would simply not touch the setting. But that logic - * is gone, hence silently accept the old setting, but turn it - * to "no". */ - - s = address_family_from_string(rvalue); - if (s < 0) { - if (streq(rvalue, "kernel")) - s = ADDRESS_FAMILY_NO; - else { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Failed to parse IPForward= option, ignoring: %s", rvalue); - return 0; - } - } - - *fwd = s; - - return 0; -} - int config_parse_ip_masquerade( const char *unit, const char *filename, diff --git a/src/network/networkd-util.h b/src/network/networkd-util.h index 9c360f5..c3b4586 100644 --- a/src/network/networkd-util.h +++ b/src/network/networkd-util.h @@ -52,7 +52,6 @@ static inline uint32_t usec_to_sec(usec_t usec, usec_t now_usec) { } CONFIG_PARSER_PROTOTYPE(config_parse_link_local_address_family); -CONFIG_PARSER_PROTOTYPE(config_parse_address_family_with_kernel); CONFIG_PARSER_PROTOTYPE(config_parse_ip_masquerade); CONFIG_PARSER_PROTOTYPE(config_parse_mud_url); diff --git a/src/network/networkd-wifi.c b/src/network/networkd-wifi.c index 98e7a72..ee63c3e 100644 --- a/src/network/networkd-wifi.c +++ b/src/network/networkd-wifi.c @@ -128,7 +128,7 @@ int manager_genl_process_nl80211_config(sd_netlink *genl, sd_netlink_message *me return 0; } - r = sd_netlink_message_read_data_suffix0(message, NL80211_ATTR_SSID, &len, (void**) &ssid); + r = sd_netlink_message_read_data(message, NL80211_ATTR_SSID, &len, (void**) &ssid); if (r < 0 && r != -ENODATA) { log_link_debug_errno(link, r, "nl80211: received %s(%u) message without valid SSID, ignoring: %m", strna(nl80211_cmd_to_string(cmd)), cmd); diff --git a/src/network/networkd-wiphy.c b/src/network/networkd-wiphy.c index 13f2d72..441713f 100644 --- a/src/network/networkd-wiphy.c +++ b/src/network/networkd-wiphy.c @@ -118,11 +118,7 @@ static int link_get_wiphy(Link *link, Wiphy **ret) { if (!link->dev) return -ENODEV; - r = sd_device_get_devtype(link->dev, &s); - if (r < 0) - return r; - - if (!streq_ptr(s, "wlan")) + if (!device_is_devtype(link->dev, "wlan")) return -EOPNOTSUPP; r = sd_device_new_child(&phy, link->dev, "phy80211"); diff --git a/src/network/networkd.c b/src/network/networkd.c index 46c2c74..69a2864 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -18,6 +18,7 @@ #include "networkd-manager.h" #include "service-util.h" #include "signal-util.h" +#include "strv.h" #include "user-util.h" static int run(int argc, char *argv[]) { @@ -69,17 +70,13 @@ static int run(int argc, char *argv[]) { /* Always create the directories people can create inotify watches in. * It is necessary to create the following subdirectories after drop_privileges() * to support old kernels not supporting AmbientCapabilities=. */ - r = mkdir_safe_label("/run/systemd/netif/links", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'links': %m"); - - r = mkdir_safe_label("/run/systemd/netif/leases", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'leases': %m"); - - r = mkdir_safe_label("/run/systemd/netif/lldp", 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); - if (r < 0) - log_warning_errno(r, "Could not create runtime directory 'lldp': %m"); + FOREACH_STRING(p, + "/run/systemd/netif/links/", + "/run/systemd/netif/leases/") { + r = mkdir_safe_label(p, 0755, UID_INVALID, GID_INVALID, MKDIR_WARN_MODE); + if (r < 0) + log_warning_errno(r, "Could not create directory '%s': %m", p); + } r = manager_new(&m, /* test_mode = */ false); if (r < 0) diff --git a/src/network/networkd.conf b/src/network/networkd.conf index e5a5e88..06d4362 100644 --- a/src/network/networkd.conf +++ b/src/network/networkd.conf @@ -21,13 +21,23 @@ #SpeedMeterIntervalSec=10sec #ManageForeignRoutingPolicyRules=yes #ManageForeignRoutes=yes +#ManageForeignNextHops=yes #RouteTable= #IPv6PrivacyExtensions=no +#UseDomains=no + +[IPv6AcceptRA] +#UseDomains= [DHCPv4] #DUIDType=vendor #DUIDRawData= +#UseDomains= [DHCPv6] #DUIDType=vendor #DUIDRawData= +#UseDomains= + +[DHCPServer] +#PersistLeases=yes diff --git a/src/network/org.freedesktop.network1.policy b/src/network/org.freedesktop.network1.policy index 1e2d8d7..eed3169 100644 --- a/src/network/org.freedesktop.network1.policy +++ b/src/network/org.freedesktop.network1.policy @@ -183,4 +183,15 @@ unix-user:systemd-network + + Specify whether persistent storage for systemd-networkd is available + Authentication is required to specify whether persistent storage for systemd-networkd is available. + + auth_admin + auth_admin + auth_admin_keep + + unix-user:systemd-network + + diff --git a/src/network/tc/qdisc.c b/src/network/tc/qdisc.c index f9b9437..38dee2c 100644 --- a/src/network/tc/qdisc.c +++ b/src/network/tc/qdisc.c @@ -155,8 +155,8 @@ static void qdisc_hash_func(const QDisc *qdisc, struct siphash *state) { assert(qdisc); assert(state); - siphash24_compress(&qdisc->handle, sizeof(qdisc->handle), state); - siphash24_compress(&qdisc->parent, sizeof(qdisc->parent), state); + siphash24_compress_typesafe(qdisc->handle, state); + siphash24_compress_typesafe(qdisc->parent, state); siphash24_compress_string(qdisc_get_tca_kind(qdisc), state); } @@ -285,37 +285,57 @@ int link_find_qdisc(Link *link, uint32_t handle, const char *kind, QDisc **ret) return -ENOENT; } -QDisc* qdisc_drop(QDisc *qdisc) { +void qdisc_mark_recursive(QDisc *qdisc) { TClass *tclass; - Link *link; assert(qdisc); + assert(qdisc->link); - link = ASSERT_PTR(qdisc->link); + if (qdisc_is_marked(qdisc)) + return; - qdisc_mark(qdisc); /* To avoid stack overflow. */ + qdisc_mark(qdisc); - /* also drop all child classes assigned to the qdisc. */ - SET_FOREACH(tclass, link->tclasses) { - if (tclass_is_marked(tclass)) + /* also mark all child classes assigned to the qdisc. */ + SET_FOREACH(tclass, qdisc->link->tclasses) { + if (TC_H_MAJ(tclass->classid) != qdisc->handle) continue; - if (TC_H_MAJ(tclass->classid) != qdisc->handle) + tclass_mark_recursive(tclass); + } +} + +void link_qdisc_drop_marked(Link *link) { + QDisc *qdisc; + + assert(link); + + SET_FOREACH(qdisc, link->qdiscs) { + if (!qdisc_is_marked(qdisc)) continue; - tclass_drop(tclass); + qdisc_unmark(qdisc); + qdisc_enter_removed(qdisc); + + if (qdisc->state == 0) { + log_qdisc_debug(qdisc, link, "Forgetting"); + qdisc_free(qdisc); + } else + log_qdisc_debug(qdisc, link, "Removed"); } +} - qdisc_unmark(qdisc); - qdisc_enter_removed(qdisc); +QDisc* qdisc_drop(QDisc *qdisc) { + assert(qdisc); + assert(qdisc->link); - if (qdisc->state == 0) { - log_qdisc_debug(qdisc, link, "Forgetting"); - qdisc = qdisc_free(qdisc); - } else - log_qdisc_debug(qdisc, link, "Removed"); + qdisc_mark_recursive(qdisc); + + /* link_qdisc_drop_marked() may invalidate qdisc, so run link_tclass_drop_marked() first. */ + link_tclass_drop_marked(qdisc->link); + link_qdisc_drop_marked(qdisc->link); - return qdisc; + return NULL; } static int qdisc_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, QDisc *qdisc) { diff --git a/src/network/tc/qdisc.h b/src/network/tc/qdisc.h index a62b941..cbba1be 100644 --- a/src/network/tc/qdisc.h +++ b/src/network/tc/qdisc.h @@ -77,7 +77,9 @@ DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(QDisc, qdisc); QDisc* qdisc_free(QDisc *qdisc); int qdisc_new_static(QDiscKind kind, Network *network, const char *filename, unsigned section_line, QDisc **ret); +void qdisc_mark_recursive(QDisc *qdisc); QDisc* qdisc_drop(QDisc *qdisc); +void link_qdisc_drop_marked(Link *link); int link_find_qdisc(Link *link, uint32_t handle, const char *kind, QDisc **qdisc); diff --git a/src/network/tc/tclass.c b/src/network/tc/tclass.c index 394e06d..fcbe8cb 100644 --- a/src/network/tc/tclass.c +++ b/src/network/tc/tclass.c @@ -125,8 +125,8 @@ static void tclass_hash_func(const TClass *tclass, struct siphash *state) { assert(tclass); assert(state); - siphash24_compress(&tclass->classid, sizeof(tclass->classid), state); - siphash24_compress(&tclass->parent, sizeof(tclass->parent), state); + siphash24_compress_typesafe(tclass->classid, state); + siphash24_compress_typesafe(tclass->parent, state); siphash24_compress_string(tclass_get_tca_kind(tclass), state); } @@ -252,37 +252,56 @@ static void log_tclass_debug(TClass *tclass, Link *link, const char *str) { strna(tclass_get_tca_kind(tclass))); } -TClass* tclass_drop(TClass *tclass) { +void tclass_mark_recursive(TClass *tclass) { QDisc *qdisc; - Link *link; assert(tclass); + assert(tclass->link); - link = ASSERT_PTR(tclass->link); + if (tclass_is_marked(tclass)) + return; - tclass_mark(tclass); /* To avoid stack overflow. */ + tclass_mark(tclass); - /* Also drop all child qdiscs assigned to the class. */ - SET_FOREACH(qdisc, link->qdiscs) { - if (qdisc_is_marked(qdisc)) + /* Also mark all child qdiscs assigned to the class. */ + SET_FOREACH(qdisc, tclass->link->qdiscs) { + if (qdisc->parent != tclass->classid) continue; - if (qdisc->parent != tclass->classid) + qdisc_mark_recursive(qdisc); + } +} + +void link_tclass_drop_marked(Link *link) { + TClass *tclass; + + assert(link); + + SET_FOREACH(tclass, link->tclasses) { + if (!tclass_is_marked(tclass)) continue; - qdisc_drop(qdisc); + tclass_unmark(tclass); + tclass_enter_removed(tclass); + + if (tclass->state == 0) { + log_tclass_debug(tclass, link, "Forgetting"); + tclass_free(tclass); + } else + log_tclass_debug(tclass, link, "Removed"); } +} - tclass_unmark(tclass); - tclass_enter_removed(tclass); +TClass* tclass_drop(TClass *tclass) { + assert(tclass); - if (tclass->state == 0) { - log_tclass_debug(tclass, link, "Forgetting"); - tclass = tclass_free(tclass); - } else - log_tclass_debug(tclass, link, "Removed"); + tclass_mark_recursive(tclass); + + /* link_tclass_drop_marked() may invalidate tclass, so run link_qdisc_drop_marked() first. */ + link_qdisc_drop_marked(tclass->link); + link_tclass_drop_marked(tclass->link); - return tclass; + return NULL; } static int tclass_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, TClass *tclass) { diff --git a/src/network/tc/tclass.h b/src/network/tc/tclass.h index e73e23c..85df57d 100644 --- a/src/network/tc/tclass.h +++ b/src/network/tc/tclass.h @@ -58,7 +58,9 @@ DEFINE_NETWORK_CONFIG_STATE_FUNCTIONS(TClass, tclass); TClass* tclass_free(TClass *tclass); int tclass_new_static(TClassKind kind, Network *network, const char *filename, unsigned section_line, TClass **ret); +void tclass_mark_recursive(TClass *tclass); TClass* tclass_drop(TClass *tclass); +void link_tclass_drop_marked(Link *link); int link_find_tclass(Link *link, uint32_t classid, TClass **ret); diff --git a/src/network/test-network-tables.c b/src/network/test-network-tables.c index 564ca09..f4e14c6 100644 --- a/src/network/test-network-tables.c +++ b/src/network/test-network-tables.c @@ -1,5 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include +#include + #include "bond.h" #include "dhcp6-internal.h" #include "dhcp6-protocol.h" @@ -28,7 +32,7 @@ int main(int argc, char **argv) { test_table(bond_xmit_hash_policy, NETDEV_BOND_XMIT_HASH_POLICY); test_table(dhcp6_message_status, DHCP6_STATUS); test_table_sparse(dhcp6_message_type, DHCP6_MESSAGE_TYPE); /* enum starts from 1 */ - test_table(dhcp_use_domains, DHCP_USE_DOMAINS); + test_table(use_domains, USE_DOMAINS); test_table(duplex, DUP); test_table(ip6tnl_mode, NETDEV_IP6_TNL_MODE); test_table(ipv6_privacy_extensions, IPV6_PRIVACY_EXTENSIONS); diff --git a/src/network/test-networkd-conf.c b/src/network/test-networkd-conf.c index 808db99..3581524 100644 --- a/src/network/test-networkd-conf.c +++ b/src/network/test-networkd-conf.c @@ -80,7 +80,7 @@ static void test_config_parse_ether_addrs_one(const char *rvalue, const struct e assert_se(q = set_remove(s, &list[m])); } - assert_se(set_size(s) == 0); + assert_se(set_isempty(s)); } #define STR_OK \ diff --git a/src/network/wait-online/manager.c b/src/network/wait-online/manager.c index 40a9fba..7838350 100644 --- a/src/network/wait-online/manager.c +++ b/src/network/wait-online/manager.c @@ -52,13 +52,27 @@ static bool manager_ignore_link(Manager *m, Link *link) { return false; } -static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange s) { +static const LinkOperationalStateRange* get_state_range(Manager *m, Link *l, const LinkOperationalStateRange *from_cmdline) { + assert(m); + assert(l); + + const LinkOperationalStateRange *range; + FOREACH_ARGUMENT(range, from_cmdline, &m->required_operstate, &l->required_operstate) + if (operational_state_range_is_valid(range)) + return range; + + /* l->requred_operstate should be always valid. */ + assert_not_reached(); +} + +static int manager_link_is_online(Manager *m, Link *l, const LinkOperationalStateRange *range) { AddressFamily required_family; bool needs_ipv4; bool needs_ipv6; assert(m); assert(l); + assert(range); /* This returns the following: * -EAGAIN : not processed by udev @@ -91,25 +105,17 @@ static int manager_link_is_online(Manager *m, Link *l, LinkOperationalStateRange "link is being processed by networkd: setup state is %s.", l->state); - if (s.min < 0) - s.min = m->required_operstate.min >= 0 ? m->required_operstate.min - : l->required_operstate.min; - - if (s.max < 0) - s.max = m->required_operstate.max >= 0 ? m->required_operstate.max - : l->required_operstate.max; - - if (l->operational_state < s.min || l->operational_state > s.max) + if (!operational_state_is_in_range(l->operational_state, range)) return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Operational state '%s' is not in range ['%s':'%s']", link_operstate_to_string(l->operational_state), - link_operstate_to_string(s.min), link_operstate_to_string(s.max)); + link_operstate_to_string(range->min), link_operstate_to_string(range->max)); required_family = m->required_family > 0 ? m->required_family : l->required_family; needs_ipv4 = required_family & ADDRESS_FAMILY_IPV4; needs_ipv6 = required_family & ADDRESS_FAMILY_IPV6; - if (s.min < LINK_OPERSTATE_ROUTABLE) { + if (range->min < LINK_OPERSTATE_ROUTABLE) { if (needs_ipv4 && l->ipv4_address_state < LINK_ADDRESS_STATE_DEGRADED) return log_link_debug_errno(l, SYNTHETIC_ERRNO(EADDRNOTAVAIL), "No routable or link-local IPv4 address is configured."); @@ -136,7 +142,7 @@ bool manager_configured(Manager *m) { int r; if (!hashmap_isempty(m->command_line_interfaces_by_name)) { - LinkOperationalStateRange *range; + const LinkOperationalStateRange *range; const char *ifname; /* wait for all the links given on the command line to appear */ @@ -155,7 +161,9 @@ bool manager_configured(Manager *m) { continue; } - r = manager_link_is_online(m, l, *range); + range = get_state_range(m, l, range); + + r = manager_link_is_online(m, l, range); if (r <= 0 && !m->any) return false; if (r > 0 && m->any) @@ -170,14 +178,16 @@ bool manager_configured(Manager *m) { /* wait for all links networkd manages */ bool has_online = false; HASHMAP_FOREACH(l, m->links_by_index) { + const LinkOperationalStateRange *range; + if (manager_ignore_link(m, l)) { log_link_debug(l, "link is ignored"); continue; } - r = manager_link_is_online(m, l, - (LinkOperationalStateRange) { _LINK_OPERSTATE_INVALID, - _LINK_OPERSTATE_INVALID }); + range = get_state_range(m, l, /* from_cmdline = */ NULL); + + r = manager_link_is_online(m, l, range); /* Unlike the above loop, unmanaged interfaces are ignored here. Also, Configured but offline * interfaces are ignored. See issue #29506. */ if (r < 0 && r != -EADDRNOTAVAIL && !m->any) diff --git a/src/network/wait-online/wait-online.c b/src/network/wait-online/wait-online.c index 5328bba..4f8270d 100644 --- a/src/network/wait-online/wait-online.c +++ b/src/network/wait-online/wait-online.c @@ -19,7 +19,7 @@ static bool arg_quiet = false; static usec_t arg_timeout = 120 * USEC_PER_SEC; static Hashmap *arg_interfaces = NULL; static char **arg_ignore = NULL; -static LinkOperationalStateRange arg_required_operstate = { _LINK_OPERSTATE_INVALID, _LINK_OPERSTATE_INVALID }; +static LinkOperationalStateRange arg_required_operstate = LINK_OPERSTATE_RANGE_INVALID; static AddressFamily arg_required_family = ADDRESS_FAMILY_NO; static bool arg_any = false; @@ -71,12 +71,11 @@ static int parse_interface_with_operstate_range(const char *str) { if (p) { r = parse_operational_state_range(p + 1, range); if (r < 0) - log_error_errno(r, "Invalid operational state range '%s'", p + 1); + return log_error_errno(r, "Invalid operational state range: %s", p + 1); ifname = strndup(optarg, p - optarg); } else { - range->min = _LINK_OPERSTATE_INVALID; - range->max = _LINK_OPERSTATE_INVALID; + *range = LINK_OPERSTATE_RANGE_INVALID; ifname = strdup(str); } if (!ifname) @@ -84,18 +83,19 @@ static int parse_interface_with_operstate_range(const char *str) { if (!ifname_valid(ifname)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Invalid interface name '%s'", ifname); + "Invalid interface name: %s", ifname); - r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, TAKE_PTR(range)); + r = hashmap_ensure_put(&arg_interfaces, &string_hash_ops, ifname, range); if (r == -ENOMEM) return log_oom(); if (r < 0) return log_error_errno(r, "Failed to store interface name: %m"); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Interface name %s is already specified", ifname); + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), + "Interface name %s is already specified.", ifname); TAKE_PTR(ifname); + TAKE_PTR(range); return 0; } @@ -154,17 +154,11 @@ static int parse_argv(int argc, char *argv[]) { break; - case 'o': { - LinkOperationalStateRange range; - - r = parse_operational_state_range(optarg, &range); + case 'o': + r = parse_operational_state_range(optarg, &arg_required_operstate); if (r < 0) return log_error_errno(r, "Invalid operational state range '%s'", optarg); - - arg_required_operstate = range; - break; - } case '4': arg_required_family |= ADDRESS_FAMILY_IPV4; @@ -210,7 +204,7 @@ static int run(int argc, char *argv[]) { if (arg_quiet) log_set_max_level(LOG_ERR); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT) >= 0); r = manager_new(&m, arg_interfaces, arg_ignore, arg_required_operstate, arg_required_family, arg_any, arg_timeout); if (r < 0) diff --git a/src/notify/notify.c b/src/notify/notify.c index f63ec8b..32bd6e6 100644 --- a/src/notify/notify.c +++ b/src/notify/notify.c @@ -99,6 +99,18 @@ static pid_t manager_pid(void) { return pid; } +static pid_t pid_parent_if_possible(void) { + pid_t parent_pid = getppid(); + + /* Don't send from PID 1 or the service manager's PID (which might be distinct from 1, if we are a + * --user service). That'd just be confusing for the service manager. */ + if (parent_pid <= 1 || + parent_pid == manager_pid()) + return getpid_cached(); + + return parent_pid; +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -135,7 +147,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_fdset_free_ FDSet *passed = NULL; bool do_exec = false; - int c, r, n_env; + int c, r; assert(argc >= 0); assert(argv); @@ -163,16 +175,9 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_PID: - if (isempty(optarg) || streq(optarg, "auto")) { - arg_pid = getppid(); - - if (arg_pid <= 1 || - arg_pid == manager_pid()) /* Don't send from PID 1 or the service - * manager's PID (which might be distinct from - * 1, if we are a --user instance), that'd just - * be confusing for the service manager */ - arg_pid = getpid_cached(); - } else if (streq(optarg, "parent")) + if (isempty(optarg) || streq(optarg, "auto")) + arg_pid = pid_parent_if_possible(); + else if (streq(optarg, "parent")) arg_pid = getppid(); else if (streq(optarg, "self")) arg_pid = getpid_cached(); @@ -268,21 +273,12 @@ static int parse_argv(int argc, char *argv[]) { } } - if (optind >= argc && - !arg_ready && - !arg_stopping && - !arg_reloading && - !arg_status && - !arg_pid && - !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."); + bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || arg_pid > 0 || !fdset_isempty(arg_fds); + size_t n_arg_env; + if (do_exec) { int i; @@ -293,18 +289,32 @@ static int parse_argv(int argc, char *argv[]) { if (i >= argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing."); if (i+1 == argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing."); arg_exec = strv_copy_n(argv + i + 1, argc - i - 1); if (!arg_exec) return log_oom(); - n_env = i - optind; + n_arg_env = i - optind; } else - n_env = argc - optind; + n_arg_env = argc - optind; + + have_env = have_env || n_arg_env > 0; + + if (!have_env && !arg_booted) { + if (do_exec) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No notify message specified while --exec, refusing."); - if (n_env > 0) { - arg_env = strv_copy_n(argv + optind, n_env); + /* No argument at all? */ + help(); + return -EINVAL; + } + + if (have_env && arg_booted) + log_warning("Notify message specified along with --booted, ignoring."); + + if (n_arg_env > 0) { + arg_env = strv_copy_n(argv + optind, n_arg_env); if (!arg_env) return log_oom(); } @@ -316,16 +326,13 @@ static int parse_argv(int argc, char *argv[]) { } static int run(int argc, char* argv[]) { - _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL, *fdn = NULL; + _cleanup_free_ char *status = NULL, *cpid = NULL, *msg = NULL, *monotonic_usec = NULL, *fdn = NULL; _cleanup_strv_free_ char **final_env = NULL; - char* our_env[9]; + const char *our_env[9]; size_t i = 0; - pid_t source_pid; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -342,7 +349,7 @@ static int run(int argc, char* argv[]) { } if (arg_reloading) { - our_env[i++] = (char*) "RELOADING=1"; + our_env[i++] = "RELOADING=1"; if (asprintf(&monotonic_usec, "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)) < 0) return log_oom(); @@ -351,10 +358,10 @@ static int run(int argc, char* argv[]) { } if (arg_ready) - our_env[i++] = (char*) "READY=1"; + our_env[i++] = "READY=1"; if (arg_stopping) - our_env[i++] = (char*) "STOPPING=1"; + our_env[i++] = "STOPPING=1"; if (arg_status) { status = strjoin("STATUS=", arg_status); @@ -372,7 +379,7 @@ static int run(int argc, char* argv[]) { } if (!fdset_isempty(arg_fds)) { - our_env[i++] = (char*) "FDSTORE=1"; + our_env[i++] = "FDSTORE=1"; if (arg_fdname) { fdn = strjoin("FDNAME=", arg_fdname); @@ -385,15 +392,13 @@ static int run(int argc, char* argv[]) { our_env[i++] = NULL; - final_env = strv_env_merge(our_env, arg_env); + final_env = strv_env_merge((char**) our_env, arg_env); if (!final_env) return log_oom(); + assert(!strv_isempty(final_env)); - if (strv_isempty(final_env)) - return 0; - - n = strv_join(final_env, "\n"); - if (!n) + msg = strv_join(final_env, "\n"); + if (!msg) return log_oom(); /* If this is requested change to the requested UID/GID. Note that we only change the real UID here, and leave @@ -408,20 +413,13 @@ static int run(int argc, char* argv[]) { setreuid(arg_uid, UID_INVALID) < 0) return log_error_errno(errno, "Failed to change UID: %m"); - if (arg_pid > 0) - source_pid = arg_pid; - else { - /* Pretend the message originates from our parent, given that we are typically called from a - * shell script, i.e. we are not the main process of a service but only a child of it. */ - source_pid = getppid(); - if (source_pid <= 1 || - source_pid == manager_pid()) /* safety check: don't claim we'd send anything from PID 1 - * or the service manager itself */ - source_pid = 0; - } + /* If --pid= is explicitly specified, use it as source pid. Otherwise, pretend the message originates + * from our parent, i.e. --pid=auto */ + if (arg_pid <= 0) + arg_pid = pid_parent_if_possible(); if (fdset_isempty(arg_fds)) - r = sd_pid_notify(source_pid, /* unset_environment= */ false, n); + r = sd_pid_notify(arg_pid, /* unset_environment= */ false, msg); else { _cleanup_free_ int *a = NULL; int k; @@ -430,7 +428,7 @@ static int run(int argc, char* argv[]) { 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); + r = sd_pid_notify_with_fds(arg_pid, /* unset_environment= */ false, msg, a, k); } if (r < 0) @@ -442,7 +440,7 @@ static int run(int argc, char* argv[]) { arg_fds = fdset_free(arg_fds); /* Close before we execute anything */ if (!arg_no_block) { - r = sd_pid_notify_barrier(source_pid, /* unset_environment= */ false, 5 * USEC_PER_SEC); + r = sd_pid_notify_barrier(arg_pid, /* unset_environment= */ false, 5 * USEC_PER_SEC); if (r < 0) return log_error_errno(r, "Failed to invoke barrier: %m"); if (r == 0) @@ -451,15 +449,10 @@ static int run(int argc, char* argv[]) { } if (arg_exec) { - _cleanup_free_ char *cmdline = NULL; - execvp(arg_exec[0], arg_exec); - cmdline = strv_join(arg_exec, " "); - if (!cmdline) - return log_oom(); - - return log_error_errno(errno, "Failed to execute command line: %s", cmdline); + _cleanup_free_ char *cmdline = strv_join(arg_exec, " "); + return log_error_errno(errno, "Failed to execute command line: %s", strnull(cmdline)); } /* The DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE() boilerplate will send the exit status via diff --git a/src/nspawn/nspawn-bind-user.c b/src/nspawn/nspawn-bind-user.c index 61d8d30..018e7a3 100644 --- a/src/nspawn/nspawn-bind-user.c +++ b/src/nspawn/nspawn-bind-user.c @@ -153,7 +153,7 @@ static int find_free_uid(const char *directory, uid_t max_uid, uid_t *current_ui assert(directory); assert(current_uid); - for (;; (*current_uid) ++) { + for (;; (*current_uid)++) { if (*current_uid > MAP_UID_MAX || *current_uid > max_uid) return log_error_errno( SYNTHETIC_ERRNO(EBUSY), @@ -388,9 +388,9 @@ int bind_user_setup( if (!c || c->n_data == 0) return 0; - r = userns_mkdir(root, "/run/host", 0755, 0, 0); + r = make_run_host(root); if (r < 0) - return log_error_errno(r, "Failed to create /run/host: %m"); + return r; r = userns_mkdir(root, "/run/host/home", 0755, 0, 0); if (r < 0) diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c index a500243..4f28b4a 100644 --- a/src/nspawn/nspawn-cgroup.c +++ b/src/nspawn/nspawn-cgroup.c @@ -13,6 +13,7 @@ #include "mountpoint-util.h" #include "nspawn-cgroup.h" #include "nspawn-mount.h" +#include "nsresource.h" #include "path-util.h" #include "rm-rf.h" #include "string-util.h" @@ -46,38 +47,6 @@ static int chown_cgroup_path(const char *path, uid_t uid_shift) { return 0; } -int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) { - _cleanup_free_ char *path = NULL, *fs = NULL; - int r; - - r = cg_pid_get_path(NULL, pid, &path); - if (r < 0) - return log_error_errno(r, "Failed to get container cgroup path: %m"); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - r = chown_cgroup_path(fs, uid_shift); - if (r < 0) - return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs); - - if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) { - _cleanup_free_ char *lfs = NULL; - /* Always propagate access rights from unified to legacy controller */ - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER_LEGACY, path, NULL, &lfs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - r = chown_cgroup_path(lfs, uid_shift); - if (r < 0) - return log_error_errno(r, "Failed to chown() cgroup %s: %m", lfs); - } - - return 0; -} - int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift) { _cleanup_free_ char *cgroup = NULL; char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; @@ -142,7 +111,14 @@ finish: return r; } -int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested) { +int create_subcgroup( + pid_t pid, + bool keep_unit, + CGroupUnified unified_requested, + uid_t uid_shift, + int userns_fd, + bool privileged) { + _cleanup_free_ char *cgroup = NULL, *payload = NULL; CGroupMask supported; char *e; @@ -185,13 +161,54 @@ int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested) if (!payload) return log_oom(); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid); + if (privileged) + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, payload, pid); + else + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, payload); if (r < 0) return log_error_errno(r, "Failed to create %s subcgroup: %m", payload); + if (privileged) { + _cleanup_free_ char *fs = NULL; + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, payload, NULL, &fs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + r = chown_cgroup_path(fs, uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to chown() cgroup %s: %m", fs); + + } else if (userns_fd >= 0) { + _cleanup_close_ int cgroup_fd = -EBADF; + + cgroup_fd = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, payload); + if (cgroup_fd < 0) + return log_error_errno(cgroup_fd, "Failed to open cgroup %s: %m", payload); + + r = cg_fd_attach(cgroup_fd, pid); + if (r < 0) + return log_error_errno(r, "Failed to add process " PID_FMT " to cgroup %s: %m", pid, payload); + + r = nsresource_add_cgroup(userns_fd, cgroup_fd); + if (r < 0) + return log_error_errno(r, "Failed to add cgroup %s to userns: %m", payload); + } + + if (unified_requested == CGROUP_UNIFIED_SYSTEMD || (unified_requested == CGROUP_UNIFIED_NONE && cg_unified_controller(SYSTEMD_CGROUP_CONTROLLER) > 0)) { + _cleanup_free_ char *lfs = NULL; + /* Always propagate access rights from unified to legacy controller */ + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER_LEGACY, payload, NULL, &lfs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + r = chown_cgroup_path(lfs, uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to chown() cgroup %s: %m", lfs); + } + if (keep_unit) { _cleanup_free_ char *supervisor = NULL; - supervisor = path_join(cgroup, "supervisor"); if (!supervisor) return log_oom(); @@ -265,7 +282,7 @@ static int mount_legacy_cgroup_hierarchy( to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); - r = path_is_mount_point(to, dest, 0); + r = path_is_mount_point_full(to, dest, /* flags = */ 0); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); if (r > 0) @@ -317,7 +334,7 @@ static int mount_legacy_cgns_supported( (void) mkdir_p(cgroup_root, 0755); /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(cgroup_root, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); if (r == 0) { @@ -427,7 +444,7 @@ static int mount_legacy_cgns_unsupported( (void) mkdir_p(cgroup_root, 0755); /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(cgroup_root, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); if (r == 0) { @@ -529,7 +546,7 @@ static int mount_unified_cgroups(const char *dest) { (void) mkdir_p(p, 0755); - r = path_is_mount_point(p, dest, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(p, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); if (r > 0) { diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h index 3f5ba62..7e2cd53 100644 --- a/src/nspawn/nspawn-cgroup.h +++ b/src/nspawn/nspawn-cgroup.h @@ -6,9 +6,8 @@ #include "cgroup-util.h" -int chown_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift); int sync_cgroup(pid_t pid, CGroupUnified unified_requested, uid_t uid_shift); -int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested); +int create_subcgroup(pid_t pid, bool keep_unit, CGroupUnified unified_requested, uid_t uid_shift, int userns_fd, bool privileged); int mount_cgroups(const char *dest, CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context, bool use_cgns); int mount_systemd_cgroup_writable(const char *dest, CGroupUnified unified_requested); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf index 9e1210f..123ef0c 100644 --- a/src/nspawn/nspawn-gperf.gperf +++ b/src/nspawn/nspawn-gperf.gperf @@ -58,7 +58,7 @@ Exec.OOMScoreAdjust, config_parse_oom_score_adjust, 0, Exec.CPUAffinity, config_parse_cpu_affinity, 0, 0 Exec.ResolvConf, config_parse_resolv_conf, 0, offsetof(Settings, resolv_conf) Exec.LinkJournal, config_parse_link_journal, 0, 0 -Exec.Timezone, config_parse_timezone, 0, offsetof(Settings, timezone) +Exec.Timezone, config_parse_timezone_mode, 0, offsetof(Settings, timezone) Exec.SuppressSync, config_parse_tristate, 0, offsetof(Settings, suppress_sync) Files.ReadOnly, config_parse_tristate, 0, offsetof(Settings, read_only) Files.Volatile, config_parse_volatile_mode, 0, offsetof(Settings, volatile_mode) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 470f477..c2bd4f6 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -245,7 +245,7 @@ int bind_mount_parse(CustomMount **l, size_t *n, const char *s, bool read_only) assert(l); assert(n); - r = extract_many_words(&s, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + r = extract_many_words(&s, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination); if (r < 0) return r; if (r == 0) @@ -444,22 +444,38 @@ int tmpfs_patch_options( } int mount_sysfs(const char *dest, MountSettingsMask mount_settings) { - const char *full, *top; - int r; + _cleanup_free_ char *top = NULL, *full = NULL;; unsigned long extra_flags = 0; + int r; - top = prefix_roota(dest, "/sys"); - r = path_is_fs_type(top, SYSFS_MAGIC); + top = path_join(dest, "/sys"); + if (!top) + return log_oom(); + + r = path_is_mount_point(top); if (r < 0) - return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); - /* /sys might already be mounted as sysfs by the outer child in the - * !netns case. In this case, it's all good. Don't touch it because we - * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555. - */ - if (r > 0) - return 0; + return log_error_errno(r, "Failed to determine if '%s' is a mountpoint: %m", top); + if (r == 0) { + /* If this is not a mount point yet, then mount a tmpfs there */ + r = mount_nofollow_verbose(LOG_ERR, "tmpfs", top, "tmpfs", MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0555" TMPFS_LIMITS_SYS); + if (r < 0) + return r; + } else { + r = path_is_fs_type(top, SYSFS_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); + + /* /sys/ might already be mounted as sysfs by the outer child in the !netns case. In this case, it's + * all good. Don't touch it because we don't have the right to do so, see + * https://github.com/systemd/systemd/issues/1555. + */ + if (r > 0) + return 0; + } - full = prefix_roota(top, "/full"); + full = path_join(top, "/full"); + if (!full) + return log_oom(); (void) mkdir(full, 0755); @@ -501,10 +517,11 @@ int mount_sysfs(const char *dest, MountSettingsMask mount_settings) { if (rmdir(full) < 0) return log_error_errno(errno, "Failed to remove %s: %m", full); - /* Create mountpoint for cgroups. Otherwise we are not allowed since we - * remount /sys read-only. - */ - const char *x = prefix_roota(top, "/fs/cgroup"); + /* Create mountpoint for cgroups. Otherwise we are not allowed since we remount /sys/ read-only. */ + _cleanup_free_ char *x = path_join(top, "/fs/cgroup"); + if (!x) + return log_oom(); + (void) mkdir_p(x, 0755); return mount_nofollow_verbose(LOG_ERR, NULL, top, NULL, @@ -541,7 +558,7 @@ int mount_all(const char *dest, } MountPoint; static const MountPoint mount_table[] = { - /* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing) */ + /* First we list inner child mounts (i.e. mounts applied *after* entering user namespacing when we are privileged) */ { "proc", "/proc", "proc", NULL, PROC_DEFAULT_MOUNT_FLAGS, MOUNT_FATAL|MOUNT_IN_USERNS|MOUNT_MKDIR|MOUNT_FOLLOW_SYMLINKS }, /* we follow symlinks here since not following them requires /proc/ already being mounted, which we don't have here. */ @@ -575,15 +592,15 @@ int mount_all(const char *dest, { "mqueue", "/dev/mqueue", "mqueue", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, MOUNT_IN_USERNS|MOUNT_MKDIR }, - /* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing) */ + /* Then we list outer child mounts (i.e. mounts applied *before* entering user namespacing when we are privileged) */ { "tmpfs", "/tmp", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL|MOUNT_APPLY_TMPFS_TMP|MOUNT_MKDIR }, { "tmpfs", "/sys", "tmpfs", "mode=0555" TMPFS_LIMITS_SYS, MS_NOSUID|MS_NOEXEC|MS_NODEV, - MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR }, + MOUNT_FATAL|MOUNT_APPLY_APIVFS_NETNS|MOUNT_MKDIR|MOUNT_PRIVILEGED }, { "sysfs", "/sys", "sysfs", NULL, SYS_DEFAULT_MOUNT_FLAGS, - MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR }, /* skipped if above was mounted */ + MOUNT_FATAL|MOUNT_APPLY_APIVFS_RO|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */ { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - MOUNT_FATAL|MOUNT_MKDIR }, /* skipped if above was mounted */ + MOUNT_FATAL|MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* skipped if above was mounted */ { "tmpfs", "/dev", "tmpfs", "mode=0755" TMPFS_LIMITS_PRIVATE_DEV, MS_NOSUID|MS_STRICTATIME, MOUNT_FATAL|MOUNT_MKDIR }, { "tmpfs", "/dev/shm", "tmpfs", "mode=01777" NESTED_TMPFS_LIMITS, MS_NOSUID|MS_NODEV|MS_STRICTATIME, @@ -604,11 +621,11 @@ int mount_all(const char *dest, MOUNT_FATAL|MOUNT_IN_USERNS }, #if HAVE_SELINUX { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, - MOUNT_MKDIR }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */ + MOUNT_MKDIR|MOUNT_PRIVILEGED }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */ { NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - 0 }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */ + MOUNT_PRIVILEGED }, /* Then, make it r/o (don't mkdir/chown the mount point here, the previous entry already did that) */ { NULL, "/sys/fs/selinux", NULL, NULL, MS_PRIVATE, - 0 }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */ + MOUNT_PRIVILEGED }, /* Turn off propagation (we only want that for the mount propagation tunnel dir) */ #endif }; @@ -617,6 +634,7 @@ int mount_all(const char *dest, bool ro = FLAGS_SET(mount_settings, MOUNT_APPLY_APIVFS_RO); bool in_userns = FLAGS_SET(mount_settings, MOUNT_IN_USERNS); bool tmpfs_tmp = FLAGS_SET(mount_settings, MOUNT_APPLY_TMPFS_TMP); + bool privileged = FLAGS_SET(mount_settings, MOUNT_PRIVILEGED); int r; for (size_t k = 0; k < ELEMENTSOF(mount_table); k++) { @@ -624,6 +642,10 @@ int mount_all(const char *dest, bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL); const char *o; + /* If we are not privileged but the entry is marked as privileged and to be mounted outside the user namespace, then skip it */ + if (!privileged && FLAGS_SET(mount_table[k].mount_settings, MOUNT_PRIVILEGED) && !FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS)) + continue; + if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS)) continue; @@ -642,7 +664,7 @@ int mount_all(const char *dest, /* Skip this entry if it is not a remount. */ if (mount_table[k].what) { - r = path_is_mount_point(where, NULL, 0); + r = path_is_mount_point(where); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); if (r > 0) @@ -742,6 +764,8 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl new_idmapping = REMOUNT_IDMAPPING_NONE; else if (streq(word, "rootidmap")) new_idmapping = REMOUNT_IDMAPPING_HOST_OWNER; + else if (streq(word, "owneridmap")) + new_idmapping = REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER; else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid bind mount option: %s", word); @@ -759,6 +783,7 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u _cleanup_free_ char *mount_opts = NULL, *where = NULL; unsigned long mount_flags = MS_BIND | MS_REC; struct stat source_st, dest_st; + uid_t dest_uid = UID_INVALID; int r; RemountIdmapping idmapping = REMOUNT_IDMAPPING_NONE; @@ -787,6 +812,8 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u if (stat(where, &dest_st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", where); + dest_uid = dest_st.st_uid; + if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot bind mount directory %s on file %s.", @@ -815,6 +842,8 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u if (chown(where, uid_shift, uid_shift) < 0) return log_error_errno(errno, "Failed to chown %s: %m", where); + + dest_uid = uid_shift; } r = mount_nofollow_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts); @@ -828,7 +857,7 @@ static int mount_bind(const char *dest, CustomMount *m, uid_t uid_shift, uid_t u } if (idmapping != REMOUNT_IDMAPPING_NONE) { - r = remount_idmap(STRV_MAKE(where), uid_shift, uid_range, source_st.st_uid, idmapping); + r = remount_idmap(STRV_MAKE(where), uid_shift, uid_range, source_st.st_uid, dest_uid, idmapping); if (r < 0) return log_error_errno(r, "Failed to map ids for bind mount %s: %m", where); } @@ -1388,17 +1417,30 @@ int wipe_fully_visible_fs(int mntns_fd) { _cleanup_close_ int orig_mntns_fd = -EBADF; int r, rr; - r = namespace_open(0, NULL, &orig_mntns_fd, NULL, NULL, NULL); + r = namespace_open(0, + /* ret_pidns_fd = */ NULL, + &orig_mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); if (r < 0) return log_error_errno(r, "Failed to pin originating mount namespace: %m"); - r = namespace_enter(-EBADF, mntns_fd, -EBADF, -EBADF, -EBADF); + r = namespace_enter(/* pidns_fd = */ -EBADF, + mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) return log_error_errno(r, "Failed to enter mount namespace: %m"); rr = do_wipe_fully_visible_fs(); - r = namespace_enter(-EBADF, orig_mntns_fd, -EBADF, -EBADF, -EBADF); + r = namespace_enter(/* pidns_fd = */ -EBADF, + orig_mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) return log_error_errno(r, "Failed to enter original mount namespace: %m"); diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index bf5e47d..54dafa7 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -20,6 +20,7 @@ typedef enum MountSettingsMask { MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */ MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */ MOUNT_FOLLOW_SYMLINKS = 1 << 11,/* if set, we'll follow symlinks for the mount target */ + MOUNT_PRIVILEGED = 1 << 12,/* if set, we'll only mount this in the outer child if we are running in privileged mode */ } MountSettingsMask; typedef enum CustomMountType { diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c index c661f1d..ec5d396 100644 --- a/src/nspawn/nspawn-network.c +++ b/src/nspawn/nspawn-network.c @@ -1,23 +1,34 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ #include #include +#include #include #include +#include #include "sd-device.h" #include "sd-id128.h" #include "sd-netlink.h" #include "alloc-util.h" +#include "device-private.h" +#include "device-util.h" #include "ether-addr-util.h" +#include "fd-util.h" #include "hexdecoct.h" #include "lock-util.h" #include "missing_network.h" +#include "mkdir.h" +#include "mount-util.h" +#include "namespace-util.h" #include "netif-naming-scheme.h" +#include "netif-util.h" #include "netlink-util.h" #include "nspawn-network.h" #include "parse-util.h" +#include "process-util.h" #include "siphash24.h" #include "socket-netlink.h" #include "socket-util.h" @@ -31,7 +42,6 @@ #define VETH_EXTRA_HOST_HASH_KEY SD_ID128_MAKE(48,c7,f6,b7,ea,9d,4c,9e,b7,28,d4,de,91,d5,bf,66) #define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59) #define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) -#define SHORTEN_IFNAME_HASH_KEY SD_ID128_MAKE(e1,90,a4,04,a8,ef,4b,51,8c,cc,c3,3a,9f,11,fc,a2) static int remove_one_link(sd_netlink *rtnl, const char *name) { _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; @@ -57,51 +67,6 @@ static int remove_one_link(sd_netlink *rtnl, const char *name) { return 1; } -static int generate_mac( - const char *machine_name, - struct ether_addr *mac, - sd_id128_t hash_key, - uint64_t idx) { - - uint64_t result; - size_t l, sz; - uint8_t *v, *i; - int r; - - l = strlen(machine_name); - sz = sizeof(sd_id128_t) + l; - if (idx > 0) - sz += sizeof(idx); - - v = newa(uint8_t, sz); - - /* fetch some persistent data unique to the host */ - r = sd_id128_get_machine((sd_id128_t*) v); - if (r < 0) - return r; - - /* combine with some data unique (on this host) to this - * container instance */ - i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); - if (idx > 0) { - idx = htole64(idx); - memcpy(i, &idx, sizeof(idx)); - } - - /* Let's hash the host machine ID plus the container name. We - * use a fixed, but originally randomly created hash key here. */ - result = htole64(siphash24(v, sz, hash_key.bytes)); - - assert_cc(ETH_ALEN <= sizeof(result)); - memcpy(mac->ether_addr_octet, &result, ETH_ALEN); - - /* see eth_random_addr in the kernel */ - mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ - mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ - - return 0; -} - static int set_alternative_ifname(sd_netlink *rtnl, const char *ifname, const char *altifname) { int r; @@ -200,39 +165,6 @@ static int add_veth( return 0; } -static int shorten_ifname(char *ifname) { - char new_ifname[IFNAMSIZ]; - - assert(ifname); - - if (strlen(ifname) < IFNAMSIZ) /* Name is short enough */ - return 0; - - if (naming_scheme_has(NAMING_NSPAWN_LONG_HASH)) { - uint64_t h; - - /* Calculate 64-bit hash value */ - h = siphash24(ifname, strlen(ifname), SHORTEN_IFNAME_HASH_KEY.bytes); - - /* Set the final four bytes (i.e. 32-bit) to the lower 24bit of the hash, encoded in url-safe base64 */ - memcpy(new_ifname, ifname, IFNAMSIZ - 5); - new_ifname[IFNAMSIZ - 5] = urlsafe_base64char(h >> 18); - new_ifname[IFNAMSIZ - 4] = urlsafe_base64char(h >> 12); - new_ifname[IFNAMSIZ - 3] = urlsafe_base64char(h >> 6); - new_ifname[IFNAMSIZ - 2] = urlsafe_base64char(h); - } else - /* On old nspawn versions we just truncated the name, provide compatibility */ - memcpy(new_ifname, ifname, IFNAMSIZ-1); - - new_ifname[IFNAMSIZ - 1] = 0; - - /* Log the incident to make it more discoverable */ - log_warning("Network interface name '%s' has been changed to '%s' to fit length constraints.", ifname, new_ifname); - - strcpy(ifname, new_ifname); - return 1; -} - int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], @@ -252,18 +184,18 @@ int setup_veth(const char *machine_name, /* Use two different interface name prefixes depending whether * we are in bridge mode or not. */ n = strjoina(bridge ? "vb-" : "ve-", machine_name); - r = shorten_ifname(n); + r = net_shorten_ifname(n, /* check_naming_scheme= */ true); if (r > 0) a = strjoina(bridge ? "vb-" : "ve-", machine_name); if (ether_addr_is_null(provided_mac)){ - r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); + r = net_generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); } else mac_container = *provided_mac; - r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); + r = net_generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); @@ -306,11 +238,11 @@ int setup_veth_extra( STRV_FOREACH_PAIR(a, b, pairs) { struct ether_addr mac_host, mac_container; - r = generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); + r = net_generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); - r = generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); + r = net_generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); if (r < 0) return log_error_errno(r, "Failed to generate predictable MAC address for host side of extra veth link: %m"); @@ -480,7 +412,7 @@ static int test_network_interface_initialized(const char *name) { if (r < 0) return log_error_errno(r, "Failed to get device %s: %m", name); - r = sd_device_get_is_initialized(d); + r = device_is_processed(d); if (r < 0) return log_error_errno(r, "Failed to determine whether interface %s is initialized: %m", name); if (r == 0) @@ -505,42 +437,302 @@ int test_network_interfaces_initialized(char **iface_pairs) { return 0; } -int move_network_interfaces(int netns_fd, char **iface_pairs) { +int resolve_network_interface_names(char **iface_pairs) { _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; int r; - if (strv_isempty(iface_pairs)) - return 0; + /* Due to a bug in kernel fixed by 8e15aee621618a3ee3abecaf1fd8c1428098b7ef (v6.6, backported to + * 6.1.60 and 6.5.9), an interface with alternative names cannot be resolved by the alternative name + * if the interface is moved to another network namespace. Hence, we need to adjust the provided + * names before moving interfaces to container namespace. */ - r = sd_netlink_open(&rtnl); + STRV_FOREACH_PAIR(from, to, iface_pairs) { + _cleanup_free_ char *name = NULL; + _cleanup_strv_free_ char **altnames = NULL; + + r = rtnl_resolve_ifname_full(&rtnl, _RESOLVE_IFNAME_ALL, *from, &name, &altnames); + if (r < 0) + return r; + + /* Always use the resolved name for 'from'. */ + free_and_replace(*from, name); + + /* If the name 'to' is assigned as an alternative name, we cannot rename the interface. + * Hence, use the assigned interface name (including the alternative names) as is, and + * use the resolved name for 'to'. */ + if (strv_contains(altnames, *to)) { + r = free_and_strdup_warn(to, *from); + if (r < 0) + return r; + } + } + return 0; +} + +static int netns_child_begin(int netns_fd, int *ret_original_netns_fd) { + _cleanup_close_ int original_netns_fd = -EBADF; + int r; + + assert(netns_fd >= 0); + + if (ret_original_netns_fd) { + r = namespace_open(0, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + &original_netns_fd, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to open original network namespace: %m"); + } + + r = namespace_enter(/* pidns_fd = */ -EBADF, + /* mntns_fd = */ -EBADF, + netns_fd, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); + return log_error_errno(r, "Failed to enter child network namespace: %m"); - STRV_FOREACH_PAIR(i, b, iface_pairs) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int ifi; + r = umount_recursive("/sys/", /* flags = */ 0); + if (r < 0) + log_debug_errno(r, "Failed to unmount directories below /sys/, ignoring: %m"); - ifi = rtnl_resolve_interface_or_warn(&rtnl, *i); - if (ifi < 0) - return ifi; + (void) mkdir_p("/sys/", 0755); + + /* Populate new sysfs instance associated with the client netns, to make sd_device usable. */ + r = mount_nofollow_verbose(LOG_ERR, "sysfs", "/sys/", "sysfs", + MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, /* opts = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to mount sysfs on /sys/: %m"); + + /* udev_avaliable() might be called previously and the result may be cached. + * Now, we (re-)mount sysfs. Hence, we need to reset the cache. */ + reset_cached_udev_availability(); + + if (ret_original_netns_fd) + *ret_original_netns_fd = TAKE_FD(original_netns_fd); + + return 0; +} + +static int netns_fork_and_wait(int netns_fd, int *ret_original_netns_fd) { + int r; + + assert(netns_fd >= 0); + + r = safe_fork("(sd-netns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, NULL); + if (r < 0) + return log_error_errno(r, "Failed to fork process (sd-netns): %m"); + if (r == 0) { + if (netns_child_begin(netns_fd, ret_original_netns_fd) < 0) + _exit(EXIT_FAILURE); + + return 0; + } + + if (ret_original_netns_fd) + *ret_original_netns_fd = -EBADF; - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); + return 1; +} + +static int move_wlan_interface_impl(sd_netlink **genl, int netns_fd, sd_device *dev) { + _cleanup_(sd_netlink_unrefp) sd_netlink *our_genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(netns_fd >= 0); + assert(dev); + + if (!genl) + genl = &our_genl; + if (!*genl) { + r = sd_genl_socket_open(genl); if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); + return log_error_errno(r, "Failed to connect to generic netlink: %m"); + } + + r = sd_genl_message_new(*genl, NL80211_GENL_NAME, NL80211_CMD_SET_WIPHY_NETNS, &m); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m"); + + uint32_t phy_index; + r = device_get_sysattr_u32(dev, "phy80211/index", &phy_index); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get phy index: %m"); + + r = sd_netlink_message_append_u32(m, NL80211_ATTR_WIPHY, phy_index); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to append phy index to netlink message: %m"); + + r = sd_netlink_message_append_u32(m, NL80211_ATTR_NETNS_FD, netns_fd); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m"); + + r = sd_netlink_call(*genl, m, 0, NULL); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m"); + + return 0; +} + +static int move_wlan_interface_one( + sd_netlink **rtnl, + sd_netlink **genl, + int *temp_netns_fd, + int netns_fd, + sd_device *dev, + const char *name) { + + int r; + + assert(rtnl); + assert(genl); + assert(temp_netns_fd); + assert(netns_fd >= 0); + assert(dev); - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd); + if (!name) + return move_wlan_interface_impl(genl, netns_fd, dev); + + /* The command NL80211_CMD_SET_WIPHY_NETNS takes phy instead of network interface, and does not take + * an interface name in the passed network namespace. Hence, we need to move the phy and interface to + * a temporary network namespace, rename the interface in it, and move them to the requested netns. */ + + if (*temp_netns_fd < 0) { + r = netns_acquire(); if (r < 0) - return log_error_errno(r, "Failed to append namespace fd to netlink message: %m"); + return log_error_errno(r, "Failed to acquire new network namespace: %m"); + *temp_netns_fd = r; + } - 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 = move_wlan_interface_impl(genl, *temp_netns_fd, dev); + if (r < 0) + return r; + + const char *sysname; + r = sd_device_get_sysname(dev, &sysname); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get interface name: %m"); + + r = netns_fork_and_wait(*temp_netns_fd, NULL); + if (r < 0) + return log_error_errno(r, "Failed to fork process (nspawn-rename-wlan): %m"); + if (r == 0) { + _cleanup_(sd_device_unrefp) sd_device *temp_dev = NULL; + + r = rtnl_rename_link(NULL, sysname, name); + if (r < 0) { + log_error_errno(r, "Failed to rename network interface '%s' to '%s': %m", sysname, name); + goto finalize; } - r = sd_netlink_call(rtnl, m, 0, NULL); + r = sd_device_new_from_ifname(&temp_dev, name); + if (r < 0) { + log_error_errno(r, "Failed to acquire device '%s': %m", name); + goto finalize; + } + + r = move_wlan_interface_impl(NULL, netns_fd, temp_dev); + + finalize: + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + return 0; +} + +static int move_network_interface_one(sd_netlink **rtnl, int netns_fd, sd_device *dev, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + assert(netns_fd >= 0); + assert(dev); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); if (r < 0) - return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); + return log_error_errno(r, "Failed to connect to rtnetlink: %m"); + } + + int ifindex; + r = sd_device_get_ifindex(dev, &ifindex); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get ifindex: %m"); + + r = sd_rtnl_message_new_link(*rtnl, &m, RTM_SETLINK, ifindex); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to append namespace fd to netlink message: %m"); + + if (name) { + r = sd_netlink_message_append_string(m, IFLA_IFNAME, name); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to add netlink interface name: %m"); + } + + r = sd_netlink_call(*rtnl, m, 0, NULL); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to move interface to namespace: %m"); + + return 0; +} + +int move_network_interfaces(int netns_fd, char **iface_pairs) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL, *genl = NULL; + _cleanup_close_ int temp_netns_fd = -EBADF; + int r; + + assert(netns_fd >= 0); + + if (strv_isempty(iface_pairs)) + return 0; + + STRV_FOREACH_PAIR(from, to, iface_pairs) { + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + const char *name; + + name = streq(*from, *to) ? NULL : *to; + + r = sd_device_new_from_ifname(&dev, *from); + if (r < 0) + return log_error_errno(r, "Unknown interface name %s: %m", *from); + + if (device_is_devtype(dev, "wlan")) + r = move_wlan_interface_one(&rtnl, &genl, &temp_netns_fd, netns_fd, dev, name); + else + r = move_network_interface_one(&rtnl, netns_fd, dev, name); + if (r < 0) + return r; + } + + return 0; +} + +int move_back_network_interfaces(int child_netns_fd, char **interface_pairs) { + _cleanup_close_ int parent_netns_fd = -EBADF; + int r; + + assert(child_netns_fd >= 0); + + if (strv_isempty(interface_pairs)) + return 0; + + r = netns_fork_and_wait(child_netns_fd, &parent_netns_fd); + if (r < 0) + return r; + if (r == 0) { + /* 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. */ + interface_pairs = strv_reverse(interface_pairs); + + r = move_network_interfaces(parent_netns_fd, interface_pairs); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); } return 0; @@ -568,7 +760,7 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs) { if (ifi < 0) return ifi; - r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); + r = net_generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); if (r < 0) return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); @@ -584,7 +776,7 @@ int setup_macvlan(const char *machine_name, pid_t pid, char **iface_pairs) { if (!n) return log_oom(); - shortened = shorten_ifname(n); + shortened = net_shorten_ifname(n, /* check_naming_scheme= */ true); r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); if (r < 0) @@ -661,7 +853,7 @@ int setup_ipvlan(const char *machine_name, pid_t pid, char **iface_pairs) { if (!n) return log_oom(); - shortened = shorten_ifname(n); + shortened = net_shorten_ifname(n, /* check_naming_scheme= */ true); r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); if (r < 0) diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h index a785f8e..840fe15 100644 --- a/src/nspawn/nspawn-network.h +++ b/src/nspawn/nspawn-network.h @@ -8,6 +8,7 @@ #include "ether-addr-util.h" int test_network_interfaces_initialized(char **iface_pairs); +int resolve_network_interface_names(char **iface_pairs); int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge, const struct ether_addr *provided_mac); int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs); @@ -19,6 +20,7 @@ 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 **iface_pairs); +int move_back_network_interfaces(int child_netns_fd, char **interface_pairs); int veth_extra_parse(char ***l, const char *p); diff --git a/src/nspawn/nspawn-oci.c b/src/nspawn/nspawn-oci.c index 8f1ac7c..a00934c 100644 --- a/src/nspawn/nspawn-oci.c +++ b/src/nspawn/nspawn-oci.c @@ -409,18 +409,18 @@ static int oci_user(const char *name, JsonVariant *v, JsonDispatchFlags flags, v static int oci_process(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch table[] = { - { "terminal", JSON_VARIANT_BOOLEAN, oci_terminal, 0, 0 }, - { "consoleSize", JSON_VARIANT_OBJECT, oci_console_size, 0, 0 }, - { "cwd", JSON_VARIANT_STRING, oci_absolute_path, offsetof(Settings, working_directory), 0 }, - { "env", JSON_VARIANT_ARRAY, oci_env, offsetof(Settings, environment), 0 }, - { "args", JSON_VARIANT_ARRAY, oci_args, offsetof(Settings, parameters), 0 }, - { "rlimits", JSON_VARIANT_ARRAY, oci_rlimits, 0, 0 }, - { "apparmorProfile", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE }, - { "capabilities", JSON_VARIANT_OBJECT, oci_capabilities, 0, 0 }, - { "noNewPrivileges", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(Settings, no_new_privileges), 0 }, - { "oomScoreAdj", JSON_VARIANT_INTEGER, oci_oom_score_adj, 0, 0 }, - { "selinuxLabel", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE }, - { "user", JSON_VARIANT_OBJECT, oci_user, 0, 0 }, + { "terminal", JSON_VARIANT_BOOLEAN, oci_terminal, 0, 0 }, + { "consoleSize", JSON_VARIANT_OBJECT, oci_console_size, 0, 0 }, + { "cwd", JSON_VARIANT_STRING, oci_absolute_path, offsetof(Settings, working_directory), 0 }, + { "env", JSON_VARIANT_ARRAY, oci_env, offsetof(Settings, environment), 0 }, + { "args", JSON_VARIANT_ARRAY, oci_args, offsetof(Settings, parameters), 0 }, + { "rlimits", JSON_VARIANT_ARRAY, oci_rlimits, 0, 0 }, + { "apparmorProfile", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE }, + { "capabilities", JSON_VARIANT_OBJECT, oci_capabilities, 0, 0 }, + { "noNewPrivileges", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(Settings, no_new_privileges), 0 }, + { "oomScoreAdj", JSON_VARIANT_INTEGER, oci_oom_score_adj, 0, 0 }, + { "selinuxLabel", JSON_VARIANT_STRING, oci_unsupported, 0, JSON_PERMISSIVE }, + { "user", JSON_VARIANT_OBJECT, oci_user, 0, 0 }, {} }; @@ -432,8 +432,8 @@ static int oci_root(const char *name, JsonVariant *v, JsonDispatchFlags flags, v int r; static const JsonDispatch table[] = { - { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Settings, root) }, - { "readonly", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(Settings, read_only) }, + { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Settings, root) }, + { "readonly", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(Settings, read_only) }, {} }; @@ -863,7 +863,7 @@ static int oci_devices(const char *name, JsonVariant *v, JsonDispatchFlags flags if (node->major == UINT_MAX || node->minor == UINT_MAX) { r = json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), - "Major/minor required when device node is device node"); + "Major/minor required when device node is device node."); goto fail_element; } @@ -1148,7 +1148,7 @@ static int oci_cgroup_memory_limit(const char *name, JsonVariant *v, JsonDispatc if (!json_variant_is_unsigned(v)) return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), - "Memory limit is not an unsigned integer"); + "Memory limit is not an unsigned integer."); k = json_variant_unsigned(v); if (k >= UINT64_MAX) @@ -1588,7 +1588,7 @@ static int oci_sysctl(const char *name, JsonVariant *v, JsonDispatchFlags flags, return json_log(v, flags, SYNTHETIC_ERRNO(EINVAL), "sysctl key invalid, refusing: %s", k); - r = strv_extend_strv(&s->sysctl, STRV_MAKE(k, m), false); + r = strv_extend_many(&s->sysctl, k, m); if (r < 0) return log_oom(); } @@ -1716,7 +1716,7 @@ static int oci_seccomp_archs(const char *name, JsonVariant *v, JsonDispatchFlags if (!json_variant_is_string(e)) return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), - "Architecture entry is not a string"); + "Architecture entry is not a string."); r = oci_seccomp_arch_from_string(json_variant_string(e), &a); if (r < 0) @@ -1837,10 +1837,8 @@ static int oci_seccomp_syscalls(const char *name, JsonVariant *v, JsonDispatchFl if (r < 0) return r; - if (strv_isempty(rule.names)) { - json_log(e, flags, 0, "System call name list is empty."); - return -EINVAL; - } + if (strv_isempty(rule.names)) + return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "System call name list is empty."); STRV_FOREACH(i, rule.names) { int nr; @@ -2082,7 +2080,7 @@ static int oci_hooks_array(const char *name, JsonVariant *v, JsonDispatchFlags f return r; } - (*n_array) ++; + (*n_array)++; } return 0; diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c index 66962d7..b63516d 100644 --- a/src/nspawn/nspawn-register.c +++ b/src/nspawn/nspawn-register.c @@ -297,15 +297,12 @@ int allocate_scope( description = strjoina("Container ", machine_name); - if (allow_pidfd) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = pidref_set_pid(&pidref, pid); - if (r < 0) - return log_error_errno(r, "Failed to allocate PID reference: %m"); + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_set_pid(&pidref, pid); + if (r < 0) + return log_error_errno(r, "Failed to allocate PID reference: %m"); - r = bus_append_scope_pidref(m, &pidref); - } else - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + r = bus_append_scope_pidref(m, &pidref, allow_pidfd); if (r < 0) return bus_log_create_error(r); @@ -368,7 +365,11 @@ int allocate_scope( if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, false, NULL); + r = bus_wait_for_jobs_one( + w, + object, + BUS_WAIT_JOBS_LOG_ERROR, + /* extra_args= */ NULL); if (r < 0) return r; diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index 161b1c1..132a543 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -835,21 +835,21 @@ int config_parse_cpu_affinity( DEFINE_CONFIG_PARSE_ENUM(config_parse_resolv_conf, resolv_conf_mode, ResolvConfMode, "Failed to parse resolv.conf mode"); static const char *const resolv_conf_mode_table[_RESOLV_CONF_MODE_MAX] = { - [RESOLV_CONF_OFF] = "off", - [RESOLV_CONF_COPY_HOST] = "copy-host", - [RESOLV_CONF_COPY_STATIC] = "copy-static", - [RESOLV_CONF_COPY_UPLINK] = "copy-uplink", - [RESOLV_CONF_COPY_STUB] = "copy-stub", - [RESOLV_CONF_REPLACE_HOST] = "replace-host", + [RESOLV_CONF_OFF] = "off", + [RESOLV_CONF_COPY_HOST] = "copy-host", + [RESOLV_CONF_COPY_STATIC] = "copy-static", + [RESOLV_CONF_COPY_UPLINK] = "copy-uplink", + [RESOLV_CONF_COPY_STUB] = "copy-stub", + [RESOLV_CONF_REPLACE_HOST] = "replace-host", [RESOLV_CONF_REPLACE_STATIC] = "replace-static", [RESOLV_CONF_REPLACE_UPLINK] = "replace-uplink", - [RESOLV_CONF_REPLACE_STUB] = "replace-stub", - [RESOLV_CONF_BIND_HOST] = "bind-host", - [RESOLV_CONF_BIND_STATIC] = "bind-static", - [RESOLV_CONF_BIND_UPLINK] = "bind-uplink", - [RESOLV_CONF_BIND_STUB] = "bind-stub", - [RESOLV_CONF_DELETE] = "delete", - [RESOLV_CONF_AUTO] = "auto", + [RESOLV_CONF_REPLACE_STUB] = "replace-stub", + [RESOLV_CONF_BIND_HOST] = "bind-host", + [RESOLV_CONF_BIND_STATIC] = "bind-static", + [RESOLV_CONF_BIND_UPLINK] = "bind-uplink", + [RESOLV_CONF_BIND_STUB] = "bind-stub", + [RESOLV_CONF_DELETE] = "delete", + [RESOLV_CONF_AUTO] = "auto", }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolv_conf_mode, ResolvConfMode, RESOLV_CONF_AUTO); @@ -914,15 +914,15 @@ int config_parse_link_journal( return 0; } -DEFINE_CONFIG_PARSE_ENUM(config_parse_timezone, timezone_mode, TimezoneMode, "Failed to parse timezone mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_timezone_mode, timezone_mode, TimezoneMode, "Failed to parse timezone mode"); static const char *const timezone_mode_table[_TIMEZONE_MODE_MAX] = { - [TIMEZONE_OFF] = "off", - [TIMEZONE_COPY] = "copy", - [TIMEZONE_BIND] = "bind", + [TIMEZONE_OFF] = "off", + [TIMEZONE_COPY] = "copy", + [TIMEZONE_BIND] = "bind", [TIMEZONE_SYMLINK] = "symlink", - [TIMEZONE_DELETE] = "delete", - [TIMEZONE_AUTO] = "auto", + [TIMEZONE_DELETE] = "delete", + [TIMEZONE_AUTO] = "auto", }; DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AUTO); @@ -930,10 +930,10 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(timezone_mode, TimezoneMode, TIMEZONE_AU DEFINE_CONFIG_PARSE_ENUM(config_parse_userns_ownership, user_namespace_ownership, UserNamespaceOwnership, "Failed to parse user namespace ownership mode"); static const char *const user_namespace_ownership_table[_USER_NAMESPACE_OWNERSHIP_MAX] = { - [USER_NAMESPACE_OWNERSHIP_OFF] = "off", + [USER_NAMESPACE_OWNERSHIP_OFF] = "off", [USER_NAMESPACE_OWNERSHIP_CHOWN] = "chown", - [USER_NAMESPACE_OWNERSHIP_MAP] = "map", - [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", + [USER_NAMESPACE_OWNERSHIP_MAP] = "map", + [USER_NAMESPACE_OWNERSHIP_AUTO] = "auto", }; DEFINE_STRING_TABLE_LOOKUP(user_namespace_ownership, UserNamespaceOwnership); diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 8edf8a3..0bcb285 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -268,7 +268,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_oom_score_adjust); CONFIG_PARSER_PROTOTYPE(config_parse_cpu_affinity); CONFIG_PARSER_PROTOTYPE(config_parse_resolv_conf); CONFIG_PARSER_PROTOTYPE(config_parse_link_journal); -CONFIG_PARSER_PROTOTYPE(config_parse_timezone); +CONFIG_PARSER_PROTOTYPE(config_parse_timezone_mode); CONFIG_PARSER_PROTOTYPE(config_parse_userns_chown); CONFIG_PARSER_PROTOTYPE(config_parse_userns_ownership); CONFIG_PARSER_PROTOTYPE(config_parse_bind_user); diff --git a/src/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c index 2d67c3d..e350b22 100644 --- a/src/nspawn/nspawn-setuid.c +++ b/src/nspawn/nspawn-setuid.c @@ -56,6 +56,8 @@ int change_uid_gid_raw( size_t n_supplementary_gids, bool chown_stdio) { + int r; + if (!uid_is_valid(uid)) uid = 0; if (!gid_is_valid(gid)) @@ -67,14 +69,9 @@ int change_uid_gid_raw( (void) fchown(STDERR_FILENO, uid, gid); } - if (setgroups(n_supplementary_gids, supplementary_gids) < 0) - return log_error_errno(errno, "Failed to set auxiliary groups: %m"); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setresgid() failed: %m"); - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setresuid() failed: %m"); + r = fully_set_uid_gid(uid, gid, supplementary_gids, n_supplementary_gids); + if (r < 0) + return log_error_errno(r, "Changing privileges failed: %m"); return 0; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index e46cc1c..5842d3b 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1,10 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#if HAVE_BLKID -#endif #include #include -#include #include #if HAVE_SELINUX #include @@ -12,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +17,8 @@ #include #include +#include /* Must be included after */ + #include "sd-bus.h" #include "sd-daemon.h" #include "sd-id128.h" @@ -84,6 +84,7 @@ #include "nspawn-stub-pid1.h" #include "nspawn-util.h" #include "nspawn.h" +#include "nsresource.h" #include "nulstr-util.h" #include "os-util.h" #include "pager.h" @@ -112,6 +113,7 @@ #include "umask-util.h" #include "unit-name.h" #include "user-util.h" +#include "vpick.h" /* The notify socket inside the container it can use to talk to nspawn using the sd_notify(3) protocol */ #define NSPAWN_NOTIFY_SOCKET_PATH "/run/host/notify" @@ -229,13 +231,14 @@ static DeviceNode* arg_extra_nodes = NULL; static size_t arg_n_extra_nodes = 0; static char **arg_sysctl = NULL; static ConsoleMode arg_console_mode = _CONSOLE_MODE_INVALID; -static MachineCredential *arg_credentials = NULL; -static size_t arg_n_credentials = 0; +static MachineCredentialContext arg_credentials = {}; 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 char *arg_background = NULL; +static bool arg_privileged = false; STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_template, freep); @@ -266,11 +269,13 @@ STATIC_DESTRUCTOR_REGISTER(arg_syscall_deny_list, strv_freep); #if HAVE_SECCOMP STATIC_DESTRUCTOR_REGISTER(arg_seccomp, seccomp_releasep); #endif +STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); 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_DESTRUCTOR_REGISTER(arg_background, freep); static int handle_arg_console(const char *arg) { if (streq(arg, "help")) { @@ -289,7 +294,7 @@ static int handle_arg_console(const char *arg) { else if (streq(arg, "passive")) arg_console_mode = CONSOLE_PASSIVE; else if (streq(arg, "pipe")) { - if (isatty(STDIN_FILENO) > 0 && isatty(STDOUT_FILENO) > 0) + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) log_full(arg_quiet ? LOG_DEBUG : LOG_NOTICE, "Console mode 'pipe' selected, but standard input/output are connected to an interactive TTY. " "Most likely you want to use 'interactive' console mode for proper interactivity and shell job control. " @@ -297,7 +302,7 @@ static int handle_arg_console(const char *arg) { arg_console_mode = CONSOLE_PIPE; } else if (streq(arg, "autopipe")) { - if (isatty(STDIN_FILENO) > 0 && isatty(STDOUT_FILENO) > 0) + if (isatty(STDIN_FILENO) && isatty(STDOUT_FILENO)) arg_console_mode = CONSOLE_INTERACTIVE; else arg_console_mode = CONSOLE_PIPE; @@ -324,8 +329,8 @@ static int help(void) { " --version Print version string\n" " -q --quiet Do not show status information\n" " --no-pager Do not pipe output into a pager\n" - " --settings=BOOLEAN Load additional settings from .nspawn file\n\n" - "%3$sImage:%4$s\n" + " --settings=BOOLEAN Load additional settings from .nspawn file\n" + "\n%3$sImage:%4$s\n" " -D --directory=PATH Root directory for the container\n" " --template=PATH Initialize root directory from template directory,\n" " if missing\n" @@ -344,8 +349,8 @@ static int help(void) { " 'base64:'\n" " --verity-data=PATH Specify hash device for verity\n" " --pivot-root=PATH[:PATH]\n" - " Pivot root to given directory in the container\n\n" - "%3$sExecution:%4$s\n" + " Pivot root to given directory in the container\n" + "\n%3$sExecution:%4$s\n" " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" " -b --boot Boot up full system (i.e. invoke init)\n" " --chdir=PATH Set working directory in the container\n" @@ -354,18 +359,18 @@ static int help(void) { " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" " --notify-ready=BOOLEAN Receive notifications from the child init process\n" " --suppress-sync=BOOLEAN\n" - " Suppress any form of disk data synchronization\n\n" - "%3$sSystem Identity:%4$s\n" + " Suppress any form of disk data synchronization\n" + "\n%3$sSystem Identity:%4$s\n" " -M --machine=NAME Set the machine name for the container\n" " --hostname=NAME Override the hostname for the container\n" - " --uuid=UUID Set a specific machine UUID for the container\n\n" - "%3$sProperties:%4$s\n" + " --uuid=UUID Set a specific machine UUID for the container\n" + "\n%3$sProperties:%4$s\n" " -S --slice=SLICE Place the container in the specified slice\n" " --property=NAME=VALUE Set scope unit property\n" " --register=BOOLEAN Register container as machine\n" " --keep-unit Do not register a scope for the machine, reuse\n" - " the service unit nspawn is running in\n\n" - "%3$sUser Namespacing:%4$s\n" + " the service unit nspawn is running in\n" + "\n%3$sUser Namespacing:%4$s\n" " --private-users=no Run without user namespacing\n" " --private-users=yes|pick|identity\n" " Run within user namespace, autoselect UID/GID range\n" @@ -375,8 +380,8 @@ static int help(void) { " Adjust ('chown') or map ('map') OS tree ownership\n" " to private UID/GID range\n" " -U Equivalent to --private-users=pick and\n" - " --private-users-ownership=auto\n\n" - "%3$sNetworking:%4$s\n" + " --private-users-ownership=auto\n" + "\n%3$sNetworking:%4$s\n" " --private-network Disable network in container\n" " --network-interface=HOSTIF[:CONTAINERIF]\n" " Assign an existing network interface to the\n" @@ -401,8 +406,8 @@ static int help(void) { " Set network namespace to the one represented by\n" " the specified kernel namespace file node\n" " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" - " Expose a container IP port on the host\n\n" - "%3$sSecurity:%4$s\n" + " Expose a container IP port on the host\n" + "\n%3$sSecurity:%4$s\n" " --capability=CAP In addition to the default, retain specified\n" " capability\n" " --drop-capability=CAP Drop the specified capability from the default set\n" @@ -417,20 +422,20 @@ static int help(void) { " processes in the container\n" " -L --selinux-apifs-context=SECLABEL\n" " Set the SELinux security context to be used by\n" - " API/tmpfs file systems in the container\n\n" - "%3$sResources:%4$s\n" + " API/tmpfs file systems in the container\n" + "\n%3$sResources:%4$s\n" " --rlimit=NAME=LIMIT Set a resource limit for the payload\n" " --oom-score-adjust=VALUE\n" " Adjust the OOM score value for the payload\n" " --cpu-affinity=CPUS Adjust the CPU affinity of the container\n" - " --personality=ARCH Pick personality for this container\n\n" - "%3$sIntegration:%4$s\n" + " --personality=ARCH Pick personality for this container\n" + "\n%3$sIntegration:%4$s\n" " --resolv-conf=MODE Select mode of /etc/resolv.conf initialization\n" " --timezone=MODE Select mode of /etc/localtime initialization\n" " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" " host, try-guest, try-host\n" - " -j Equivalent to --link-journal=try-guest\n\n" - "%3$sMounts:%4$s\n" + " -j Equivalent to --link-journal=try-guest\n" + "\n%3$sMounts:%4$s\n" " --bind=PATH[:PATH[:OPTIONS]]\n" " Bind mount a file or directory from the host into\n" " the container\n" @@ -444,12 +449,13 @@ static int help(void) { " the container\n" " --overlay-ro=PATH[:PATH...]:PATH\n" " Similar, but creates a read-only overlay mount\n" - " --bind-user=NAME Bind user from host to container\n\n" - "%3$sInput/Output:%4$s\n" + " --bind-user=NAME Bind user from host to container\n" + "\n%3$sInput/Output:%4$s\n" " --console=MODE Select how stdin/stdout/stderr and /dev/console are\n" " set up for the container.\n" - " -P --pipe Equivalent to --console=pipe\n\n" - "%3$sCredentials:%4$s\n" + " -P --pipe Equivalent to --console=pipe\n" + " --background=COLOR Set ANSI color for background\n" + "\n%3$sCredentials:%4$s\n" " --set-credential=ID:VALUE\n" " Pass a credential with literal value to container.\n" " --load-credential=ID:PATH\n" @@ -514,6 +520,12 @@ static int detect_unified_cgroup_hierarchy_from_environment(void) { static int detect_unified_cgroup_hierarchy_from_image(const char *directory) { int r; + if (!arg_privileged) { + /* We only support the unified mode when running unprivileged */ + arg_unified_cgroup_hierarchy = CGROUP_UNIFIED_ALL; + return 0; + } + /* Let's inherit the mode to use from the host system, but let's take into consideration what systemd * in the image actually supports. */ r = cg_all_unified(); @@ -615,7 +627,6 @@ static int parse_mount_settings_env(void) { e = getenv("SYSTEMD_NSPAWN_API_VFS_WRITABLE"); if (streq_ptr(e, "network")) arg_mount_settings |= MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_APIVFS_NETNS; - else if (e) { r = parse_boolean(e); if (r < 0) @@ -744,6 +755,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_BIND_USER, ARG_SUPPRESS_SYNC, ARG_IMAGE_POLICY, + ARG_BACKGROUND, }; static const struct option options[] = { @@ -818,6 +830,7 @@ static int parse_argv(int argc, char *argv[]) { { "bind-user", required_argument, NULL, ARG_BIND_USER }, { "suppress-sync", required_argument, NULL, ARG_SUPPRESS_SYNC }, { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, + { "background", required_argument, NULL, ARG_BACKGROUND }, {} }; @@ -1249,33 +1262,11 @@ static int parse_argv(int argc, char *argv[]) { arg_uid_shift = 0; arg_uid_range = UINT32_C(0x10000); } else { - _cleanup_free_ char *buffer = NULL; - const char *range, *shift; - /* anything else: User namespacing on, UID range is explicitly configured */ - - range = strchr(optarg, ':'); - if (range) { - buffer = strndup(optarg, range - optarg); - if (!buffer) - return log_oom(); - shift = buffer; - - range++; - r = safe_atou32(range, &arg_uid_range); - if (r < 0) - return log_error_errno(r, "Failed to parse UID range \"%s\": %m", range); - } else - shift = optarg; - - r = parse_uid(shift, &arg_uid_shift); + r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); if (r < 0) - return log_error_errno(r, "Failed to parse UID \"%s\": %m", optarg); - + return r; arg_userns_mode = USER_NAMESPACE_FIXED; - - if (!userns_shift_range_valid(arg_uid_shift, arg_uid_range)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID range cannot be empty or go beyond " UID_FMT ".", UID_INVALID); } arg_settings_mask |= SETTING_USERNS; @@ -1362,17 +1353,27 @@ static int parse_argv(int argc, char *argv[]) { break; - case ARG_CHDIR: + case ARG_CHDIR: { + _cleanup_free_ char *wd = NULL; + if (!path_is_absolute(optarg)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory %s is not an absolute path.", optarg); - r = free_and_strdup(&arg_chdir, optarg); + r = path_simplify_alloc(optarg, &wd); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to simplify path %s: %m", optarg); + + if (!path_is_normalized(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory path is not normalized: %s", wd); + + if (path_below_api_vfs(wd)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Working directory is below API VFS, refusing: %s", wd); + free_and_replace(arg_chdir, wd); arg_settings_mask |= SETTING_WORKING_DIRECTORY; break; + } case ARG_PIVOT_ROOT: r = pivot_root_parse(&arg_pivot_root_new, &arg_pivot_root_old, optarg); @@ -1395,7 +1396,7 @@ static int parse_argv(int argc, char *argv[]) { _cleanup_free_ void *k = NULL; size_t l; - r = unhexmem(optarg, strlen(optarg), &k, &l); + r = unhexmem(optarg, &k, &l); if (r < 0) return log_error_errno(r, "Failed to parse root hash: %s", optarg); if (l < sizeof(sd_id128_t)) @@ -1412,7 +1413,7 @@ static int parse_argv(int argc, char *argv[]) { void *p; if ((value = startswith(optarg, "base64:"))) { - r = unbase64mem(value, strlen(value), &p, &l); + r = unbase64mem(value, &p, &l); if (r < 0) return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); @@ -1568,7 +1569,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_SET_CREDENTIAL: - r = machine_credential_set(&arg_credentials, &arg_n_credentials, optarg); + r = machine_credential_set(&arg_credentials, optarg); if (r < 0) return r; @@ -1576,7 +1577,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_LOAD_CREDENTIAL: - r = machine_credential_load(&arg_credentials, &arg_n_credentials, optarg); + r = machine_credential_load(&arg_credentials, optarg); if (r < 0) return r; @@ -1607,6 +1608,12 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -1653,6 +1660,21 @@ static int parse_argv(int argc, char *argv[]) { static int verify_arguments(void) { int r; + SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); + + if (!arg_privileged) { + /* machined is not accessible to unpriv clients */ + if (arg_register) { + log_notice("Automatically implying --register=no, since machined is not accessible to unprivileged clients."); + arg_register = false; + } + + if (!arg_private_network) { + log_notice("Automatically implying --private-network, since mounting /sys/ in an unprivileged user namespaces requires network namespacing."); + arg_private_network = true; + } + } + if (arg_start_mode == START_PID2 && arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) { /* If we are running the stub init in the container, we don't need to look at what the init * in the container supports, because we are not using it. Let's immediately pick the right @@ -2184,7 +2206,7 @@ static int copy_devnodes(const char *dest) { if (mknod(to, st.st_mode, st.st_rdev) < 0) { /* Explicitly warn the user when /dev is already populated. */ if (errno == EEXIST) - log_notice("%s/dev is pre-mounted and pre-populated. If a pre-mounted /dev is provided it needs to be an unpopulated file system.", dest); + log_notice("%s/dev/ is pre-mounted and pre-populated. If a pre-mounted /dev/ is provided it needs to be an unpopulated file system.", dest); if (errno != EPERM) return log_error_errno(errno, "mknod(%s) failed: %m", to); @@ -2365,18 +2387,44 @@ static int setup_keyring(void) { return 0; } +int make_run_host(const char *root) { + int r; + + assert(root); + + r = userns_mkdir(root, "/run/host", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/host/: %m"); + + return 0; +} + static int setup_credentials(const char *root) { + bool world_readable = false; const char *q; int r; - if (arg_n_credentials <= 0) + if (arg_credentials.n_credentials == 0) return 0; - r = userns_mkdir(root, "/run/host", 0755, 0, 0); + /* If starting a single-process container as a non-root user, the uid will only be resolved after we + * are inside the inner child, when credential directories and files are already read-only, so they + * are unusable as the single process won't have access to them. We also don't have access to the + * uid that will actually be used from here, as we are setting credentials up from the outer child. + * In order to make them usable as requested by the configuration, make them world readable in that + * case, as by definition there are no other processes in that case besides the one being started, + * which is being configured to be able to access credentials, and any of its children which will + * inherit its privileges anyway. To ensure this, also enforce (and document) that + * --no-new-privileges is necessary for this combination to work. */ + if (arg_no_new_privileges && !isempty(arg_user) && !STR_IN_SET(arg_user, "root", "0") && + arg_start_mode == START_PID1) + world_readable = true; + + r = make_run_host(root); if (r < 0) - return log_error_errno(r, "Failed to create /run/host: %m"); + return r; - r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0); + r = userns_mkdir(root, "/run/host/credentials", world_readable ? 0777 : 0700, 0, 0); if (r < 0) return log_error_errno(r, "Failed to create /run/host/credentials: %m"); @@ -2385,23 +2433,23 @@ static int setup_credentials(const char *root) { if (r < 0) return r; - for (size_t i = 0; i < arg_n_credentials; i++) { + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { _cleanup_free_ char *j = NULL; _cleanup_close_ int fd = -EBADF; - j = path_join(q, arg_credentials[i].id); + j = path_join(q, cred->id); if (!j) return log_oom(); - fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, 0600); + fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, world_readable ? 0666 : 0600); if (fd < 0) return log_error_errno(errno, "Failed to create credential file %s: %m", j); - r = loop_write(fd, arg_credentials[i].data, arg_credentials[i].size); + r = loop_write(fd, cred->data, cred->size); if (r < 0) return log_error_errno(r, "Failed to write credential to file %s: %m", j); - if (fchmod(fd, 0400) < 0) + if (fchmod(fd, world_readable ? 0444 : 0400) < 0) return log_error_errno(errno, "Failed to adjust access mode of %s: %m", j); if (arg_userns_mode != USER_NAMESPACE_NO) { @@ -2410,7 +2458,7 @@ static int setup_credentials(const char *root) { } } - if (chmod(q, 0500) < 0) + if (chmod(q, world_readable ? 0555 : 0500) < 0) return log_error_errno(errno, "Failed to adjust access mode of %s: %m", q); r = userns_lchown(q, 0, 0); @@ -2536,7 +2584,7 @@ static int setup_journal(const char *directory) { p = strjoina("/var/log/journal/", SD_ID128_TO_STRING(arg_uuid)); q = prefix_roota(directory, p); - if (path_is_mount_point(p, NULL, 0) > 0) { + if (path_is_mount_point(p) > 0) { if (try) return 0; @@ -2544,7 +2592,7 @@ static int setup_journal(const char *directory) { "%s: already a mount point, refusing to use for journal", p); } - if (path_is_mount_point(q, NULL, 0) > 0) { + if (path_is_mount_point(q) > 0) { if (try) return 0; @@ -2620,7 +2668,7 @@ static int setup_journal(const char *directory) { r = mount_nofollow_verbose(LOG_DEBUG, p, q, NULL, MS_BIND, NULL); if (r < 0) - return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); + return log_error_errno(r, "Failed to bind mount journal from host into guest: %m"); return 0; } @@ -2680,6 +2728,9 @@ static int reset_audit_loginuid(void) { if ((arg_clone_ns_flags & CLONE_NEWPID) == 0) return 0; + if (!arg_privileged) + return 0; + r = read_one_line_file("/proc/self/loginuid", &p); if (r == -ENOENT) return 0; @@ -2709,14 +2760,19 @@ static int mount_tunnel_dig(const char *root) { const char *p, *q; int r; + if (!arg_privileged) { + log_debug("Not digging mount tunnel, because running unprivileged."); + return 0; + } + (void) mkdir_p("/run/systemd/nspawn/", 0755); (void) mkdir_p("/run/systemd/nspawn/propagate", 0600); p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); (void) mkdir_p(p, 0600); - r = userns_mkdir(root, "/run/host", 0755, 0, 0); + r = make_run_host(root); if (r < 0) - return log_error_errno(r, "Failed to create /run/host: %m"); + return r; r = userns_mkdir(root, NSPAWN_MOUNT_TUNNEL, 0600, 0, 0); if (r < 0) @@ -2737,6 +2793,11 @@ static int mount_tunnel_dig(const char *root) { static int mount_tunnel_open(void) { int r; + if (!arg_privileged) { + log_debug("Not opening up mount tunnel, because running unprivileged."); + return 0; + } + r = mount_follow_verbose(LOG_ERR, NULL, NSPAWN_MOUNT_TUNNEL, NULL, MS_SLAVE, NULL); if (r < 0) return r; @@ -2913,14 +2974,72 @@ static int on_request_stop(sd_bus_message *m, void *userdata, sd_bus_error *erro return 0; } +static int pick_paths(void) { + int r; + + if (arg_directory) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + PickFilter filter = pick_filter_image_dir; + + filter.architecture = arg_architecture; + + r = path_pick_update_warn( + &arg_directory, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) { + /* Accept ENOENT here so that the --template= logic can work */ + if (r != -ENOENT) + return r; + } else + arg_architecture = result.architecture; + } + + if (arg_image) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + PickFilter filter = pick_filter_image_raw; + + filter.architecture = arg_architecture; + + r = path_pick_update_warn( + &arg_image, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) + return r; + + arg_architecture = result.architecture; + } + + if (arg_template) { + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + PickFilter filter = pick_filter_image_dir; + + filter.architecture = arg_architecture; + + r = path_pick_update_warn( + &arg_template, + &filter, + PICK_ARCHITECTURE, + &result); + if (r < 0) + return r; + + arg_architecture = result.architecture; + } + + return 0; +} + static int determine_names(void) { int r; if (arg_template && !arg_directory && arg_machine) { - /* If --template= was specified then we should not - * search for a machine, but instead create a new one - * in /var/lib/machine. */ + /* If --template= was specified then we should not search for a machine, but instead create a + * new one in /var/lib/machine. */ arg_directory = path_join("/var/lib/machines", arg_machine); if (!arg_directory) @@ -2957,9 +3076,11 @@ static int determine_names(void) { } if (!arg_machine) { - if (arg_directory && path_equal(arg_directory, "/")) + if (arg_directory && path_equal(arg_directory, "/")) { arg_machine = gethostname_malloc(); - else if (arg_image) { + if (!arg_machine) + return log_oom(); + } else if (arg_image) { char *e; r = path_extract_filename(arg_image, &arg_machine); @@ -3198,20 +3319,32 @@ static int inner_child( return r; if (!arg_network_namespace_path && arg_private_network) { - r = unshare(CLONE_NEWNET); + _cleanup_close_ int netns_fd = -EBADF; + + if (arg_privileged) { + if (unshare(CLONE_NEWNET) < 0) + return log_error_errno(errno, "Failed to unshare network namespace: %m"); + } + + netns_fd = namespace_open_by_type(NAMESPACE_NET); + if (netns_fd < 0) + return log_error_errno(netns_fd, "Failed to open newly allocate network namespace: %m"); + + r = send_one_fd(fd_inner_socket, netns_fd, 0); if (r < 0) - return log_error_errno(errno, "Failed to unshare network namespace: %m"); + return log_error_errno(r, "Failed to send network namespace to supervisor: %m"); /* Tell the parent that it can setup network interfaces. */ (void) barrier_place(barrier); /* #3 */ } - r = mount_sysfs(NULL, arg_mount_settings); - if (r < 0) - return r; + if (arg_privileged) { + r = mount_sysfs(NULL, arg_mount_settings); + if (r < 0) + return r; + } - /* Wait until we are cgroup-ified, so that we - * can mount the right cgroup path writable */ + /* Wait until we are cgroup-ified, so that we can mount the right cgroup path writable */ if (!barrier_place_and_sync(barrier)) /* #4 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent died too early"); @@ -3396,7 +3529,7 @@ static int inner_child( if (asprintf(envp + n_env++, "container_uuid=%s", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) return log_oom(); - if (fdset_size(fds) > 0) { + if (!fdset_isempty(fds)) { r = fdset_cloexec(fds, false); if (r < 0) return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); @@ -3408,7 +3541,7 @@ static int inner_child( if (asprintf(envp + n_env++, "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0) return log_oom(); - if (arg_n_credentials > 0) { + if (arg_credentials.n_credentials > 0) { envp[n_env] = strdup("CREDENTIALS_DIRECTORY=/run/host/credentials"); if (!envp[n_env]) return log_oom(); @@ -3430,6 +3563,9 @@ static int inner_child( if (!barrier_place_and_sync(barrier)) /* #5 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent died too early"); + /* Note, this should be done this late (💣 and not moved earlier! 💣), so that all namespacing + * changes are already in effect by now, so that any resolved paths here definitely reference + * resources inside the container, and not outside of them. */ if (arg_chdir) if (chdir(arg_chdir) < 0) return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); @@ -3509,11 +3645,11 @@ static int inner_child( return log_error_errno(errno, "execv(%s) failed: %m", exec_target); } -static int setup_notify_child(void) { +static int setup_notify_child(const void *directory) { _cleanup_close_ int fd = -EBADF; - static const union sockaddr_union sa = { + _cleanup_free_ char *j = NULL; + union sockaddr_union sa = { .un.sun_family = AF_UNIX, - .un.sun_path = NSPAWN_NOTIFY_SOCKET_PATH, }; int r; @@ -3521,14 +3657,26 @@ static int setup_notify_child(void) { if (fd < 0) return log_error_errno(errno, "Failed to allocate notification socket: %m"); - (void) mkdir_parents(NSPAWN_NOTIFY_SOCKET_PATH, 0755); - (void) sockaddr_un_unlink(&sa.un); + if (directory) { + j = path_join(directory, NSPAWN_NOTIFY_SOCKET_PATH); + if (!j) + return log_oom(); + } - r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + r = sockaddr_un_set_path(&sa.un, j ?: NSPAWN_NOTIFY_SOCKET_PATH); if (r < 0) - return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m"); + return log_error_errno(r, "Failed to set AF_UNIX path to %s: %m", j ?: NSPAWN_NOTIFY_SOCKET_PATH); - r = userns_lchown(NSPAWN_NOTIFY_SOCKET_PATH, 0, 0); + (void) mkdir_parents(sa.un.sun_path, 0755); + (void) sockaddr_un_unlink(&sa.un); + + WITH_UMASK(0577) { /* only set "w" bit, which is all that's necessary for connecting from the container */ + r = bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)); + if (r < 0) + return log_error_errno(errno, "bind(" NSPAWN_NOTIFY_SOCKET_PATH ") failed: %m"); + } + + r = userns_lchown(sa.un.sun_path, 0, 0); if (r < 0) return log_error_errno(r, "Failed to chown " NSPAWN_NOTIFY_SOCKET_PATH ": %m"); @@ -3539,6 +3687,125 @@ static int setup_notify_child(void) { return TAKE_FD(fd); } +static int setup_unix_export_dir_outside(char **ret) { + int r; + + assert(ret); + + if (!arg_privileged) { + log_debug("Not digging socket tunnel, because running unprivileged."); + return 0; + } + + _cleanup_free_ char *p = NULL; + p = path_join("/run/systemd/nspawn/unix-export", arg_machine); + if (!p) + return log_oom(); + + r = path_is_mount_point(p); + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Mount point '%s' exists already, refusing.", p); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to detect if '%s' is a mount point: %m", p); + + r = mkdir_p(p, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create '%s': %m", p); + + _cleanup_(rmdir_and_freep) char *q = TAKE_PTR(p); + + /* Mount the "unix export" directory really tiny, just 64 inodes. We mark the superblock writable + * (since the container shall bind sockets into it). */ + r = mount_nofollow_verbose( + LOG_ERR, + "tmpfs", + q, + "tmpfs", + MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(), + "size=4M,nr_inodes=64,mode=0755"); + if (r < 0) + return r; + + _cleanup_(umount_and_rmdir_and_freep) char *w = TAKE_PTR(q); + + /* After creating the superblock we change the bind mount to be read-only. This means that the fs + * itself is writable, but not through the mount accessible from the host. */ + r = mount_nofollow_verbose( + LOG_ERR, + /* source= */ NULL, + w, + /* fstype= */ NULL, + MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(), + /* options= */ NULL); + if (r < 0) + return r; + + *ret = TAKE_PTR(w); + return 0; +} + +static int setup_unix_export_host_inside(const char *directory, const char *unix_export_path) { + int r; + + assert(directory); + + if (!arg_privileged) + return 0; + + assert(unix_export_path); + + r = make_run_host(directory); + if (r < 0) + return r; + + _cleanup_free_ char *p = path_join(directory, "run/host/unix-export"); + if (!p) + return log_oom(); + + if (mkdir(p, 0755) < 0) + return log_error_errno(errno, "Failed to create '%s': %m", p); + + r = mount_nofollow_verbose( + LOG_ERR, + unix_export_path, + p, + /* fstype= */ NULL, + MS_BIND, + /* options= */ NULL); + if (r < 0) + return r; + + r = mount_nofollow_verbose( + LOG_ERR, + /* source= */ NULL, + p, + /* fstype= */ NULL, + MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported(), + /* options= */ NULL); + if (r < 0) + return r; + + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown '%s': %m", p); + + return 0; +} + +static DissectImageFlags determine_dissect_image_flags(void) { + return + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_DISCARD_ON_LOOP | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS) | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY | + (arg_console_mode == CONSOLE_INTERACTIVE ? DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH : 0); +} + static int outer_child( Barrier *barrier, const char *directory, @@ -3546,7 +3813,8 @@ static int outer_child( int fd_outer_socket, int fd_inner_socket, FDSet *fds, - int netns_fd) { + int netns_fd, + const char *unix_export_path) { _cleanup_(bind_user_context_freep) BindUserContext *bind_user_context = NULL; _cleanup_strv_free_ char **os_release_pairs = NULL; @@ -3599,10 +3867,8 @@ static int outer_child( arg_uid_shift, arg_uid_range, /* userns_fd= */ -EBADF, + determine_dissect_image_flags()| DISSECT_IMAGE_MOUNT_ROOT_ONLY| - DISSECT_IMAGE_DISCARD_ON_LOOP| - DISSECT_IMAGE_USR_NO_ROOT| - (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS)| (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0)); if (r < 0) return r; @@ -3613,7 +3879,12 @@ static int outer_child( return r; if (arg_userns_mode != USER_NAMESPACE_NO) { - r = namespace_open(0, NULL, &mntns_fd, NULL, NULL, NULL); + r = namespace_open(0, + /* ret_pidns_fd = */ NULL, + &mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); if (r < 0) return log_error_errno(r, "Failed to pin outer mount namespace: %m"); @@ -3752,7 +4023,7 @@ static int outer_child( dirs[i] = NULL; - r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + r = remount_idmap(dirs, arg_uid_shift, arg_uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); if (r == -EINVAL || ERRNO_IS_NEG_NOT_SUPPORTED(r)) { /* This might fail because the kernel or file system doesn't support idmapping. We * can't really distinguish this nicely, nor do we have any guarantees about the @@ -3773,21 +4044,17 @@ static int outer_child( if (dissected_image) { /* Now we know the uid shift, let's now mount everything else that might be in the image. */ - r = dissected_image_mount( + r = dissected_image_mount_and_warn( dissected_image, directory, arg_uid_shift, arg_uid_range, /* userns_fd= */ -EBADF, + determine_dissect_image_flags()| DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY| - DISSECT_IMAGE_DISCARD_ON_LOOP| - DISSECT_IMAGE_USR_NO_ROOT| - (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK|DISSECT_IMAGE_GROWFS)| (idmap ? DISSECT_IMAGE_MOUNT_IDMAPPED : 0)); - if (r == -EUCLEAN) - return log_error_errno(r, "File system check for image failed: %m"); if (r < 0) - return log_error_errno(r, "Failed to mount image file system: %m"); + return r; } if (arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) { @@ -3840,6 +4107,10 @@ static int outer_child( p = prefix_roota(directory, "/run/host"); (void) make_inaccessible_nodes(p, arg_uid_shift, arg_uid_shift); + r = setup_unix_export_host_inside(directory, unix_export_path); + if (r < 0) + return r; + r = setup_pts(directory); if (r < 0) return r; @@ -3889,11 +4160,11 @@ static int outer_child( /* The same stuff as the $container env var, but nicely readable for the entire payload */ p = prefix_roota(directory, "/run/host/container-manager"); - (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE); + (void) write_string_file(p, arg_container_service_name, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444); /* The same stuff as the $container_uuid env var */ p = prefix_roota(directory, "/run/host/container-uuid"); - (void) write_string_filef(p, WRITE_STRING_FILE_CREATE, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid)); + (void) write_string_filef(p, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0444, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(arg_uuid)); if (!arg_use_cgns) { r = mount_cgroups( @@ -3908,47 +4179,59 @@ static int outer_child( return r; } - /* Mark everything as shared so our mounts get propagated down. This is required to make new bind - * mounts available in systemd services inside the container that create a new mount namespace. See - * https://github.com/systemd/systemd/issues/3860 Further submounts (such as /dev) done after this - * will inherit the shared propagation mode. - * - * IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root - * directory mount to root later on. - * https://github.com/systemd/systemd/issues/3847#issuecomment-562735251 - */ - r = mount_switch_root(directory, MS_SHARED); - if (r < 0) - return log_error_errno(r, "Failed to move root directory: %m"); + /* We have different codepaths here for privileged and non-privileged mode. In privileged mode we'll + * now switch into the target directory, and then do the final setup from there. If a user namespace + * is then allocated for the container, the root mount and everything else will be out of reach for + * it. For unprivileged containers we cannot do that however, since we couldn't mount a sysfs and + * procfs then anymore, since that only works if there's an unobstructed instance currently + * visible. Hence there we do it the other way round: we first allocate a new set of namespaces + * (and fork for it) for which we then mount sysfs/procfs, and only then switch root. */ - /* We finished setting up the rootfs which is a shared mount. The mount tunnel needs to be a - * dependent mount otherwise we can't MS_MOVE mounts that were propagated from the host into - * the container. */ - r = mount_tunnel_open(); - if (r < 0) - return r; + if (arg_privileged) { + /* Mark everything as shared so our mounts get propagated down. This is required to make new + * bind mounts available in systemd services inside the container that create a new mount + * namespace. See https://github.com/systemd/systemd/issues/3860 Further submounts (such as + * /dev/) done after this will inherit the shared propagation mode. + * + * IMPORTANT: Do not overmount the root directory anymore from now on to enable moving the root + * directory mount to root later on. + * https://github.com/systemd/systemd/issues/3847#issuecomment-562735251 + */ + r = mount_switch_root(directory, MS_SHARED); + if (r < 0) + return log_error_errno(r, "Failed to move root directory: %m"); - if (arg_userns_mode != USER_NAMESPACE_NO) { - /* In order to mount procfs and sysfs in an unprivileged container the kernel - * requires that a fully visible instance is already present in the target mount - * namespace. Mount one here so the inner child can mount its own instances. Later - * we umount the temporary instances created here before we actually exec the - * payload. Since the rootfs is shared the umount will propagate into the container. - * Note, the inner child wouldn't be able to unmount the instances on its own since - * it doesn't own the originating mount namespace. IOW, the outer child needs to do - * this. */ - r = pin_fully_visible_fs(); + /* We finished setting up the rootfs which is a shared mount. The mount tunnel needs to be a + * dependent mount otherwise we can't MS_MOVE mounts that were propagated from the host into + * the container. */ + r = mount_tunnel_open(); if (r < 0) return r; - } - fd = setup_notify_child(); + if (arg_userns_mode != USER_NAMESPACE_NO) { + /* In order to mount procfs and sysfs in an unprivileged container the kernel + * requires that a fully visible instance is already present in the target mount + * namespace. Mount one here so the inner child can mount its own instances. Later + * we umount the temporary instances created here before we actually exec the + * payload. Since the rootfs is shared the umount will propagate into the container. + * Note, the inner child wouldn't be able to unmount the instances on its own since + * it doesn't own the originating mount namespace. IOW, the outer child needs to do + * this. */ + r = pin_fully_visible_fs(); + if (r < 0) + return r; + } + + fd = setup_notify_child(NULL); + } else + fd = setup_notify_child(directory); if (fd < 0) return fd; pid = raw_clone(SIGCHLD|CLONE_NEWNS| arg_clone_ns_flags | - (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0)); + (arg_userns_mode != USER_NAMESPACE_NO ? CLONE_NEWUSER : 0) | + ((arg_private_network && !arg_privileged) ? CLONE_NEWNET : 0)); if (pid < 0) return log_error_errno(errno, "Failed to fork inner child: %m"); if (pid == 0) { @@ -3958,11 +4241,35 @@ static int outer_child( * user if user namespaces are turned on. */ if (arg_network_namespace_path) { - r = namespace_enter(-1, -1, netns_fd, -1, -1); + r = namespace_enter(/* pidns_fd = */ -EBADF, + /* mntns_fd = */ -EBADF, + netns_fd, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) return log_error_errno(r, "Failed to join network namespace: %m"); } + if (!arg_privileged) { + /* In unprivileged operation, sysfs + procfs are special, we'll have to mount them + * inside the inner namespaces, but before we switch root. Hence do so here. */ + _cleanup_free_ char *j = path_join(directory, "/proc"); + if (!j) + return log_oom(); + + r = mount_follow_verbose(LOG_ERR, "proc", j, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL); + if (r < 0) + return r; + + r = mount_sysfs(directory, arg_mount_settings); + if (r < 0) + return r; + + r = mount_switch_root(directory, MS_SHARED); + if (r < 0) + return log_error_errno(r, "Failed to move root directory: %m"); + } + r = inner_child(barrier, fd_inner_socket, fds, os_release_pairs); if (r < 0) _exit(EXIT_FAILURE); @@ -4030,13 +4337,13 @@ static int uid_shift_pick(uid_t *shift, LockFile *ret_lock_file) { return r; /* Make some superficial checks whether the range is currently known in the user database */ - if (getpwuid(candidate)) + if (getpwuid_malloc(candidate, /* ret= */ NULL) >= 0) goto next; - if (getpwuid(candidate + UINT32_C(0xFFFE))) + if (getpwuid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0) goto next; - if (getgrgid(candidate)) + if (getgrgid_malloc(candidate, /* ret= */ NULL) >= 0) goto next; - if (getgrgid(candidate + UINT32_C(0xFFFE))) + if (getgrgid_malloc(candidate + UINT32_C(0xFFFE), /* ret= */ NULL) >= 0) goto next; *ret_lock_file = lf; @@ -4217,6 +4524,17 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r if (!tags) return log_oom(); + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = strv_join(tags, " "); + + if (joined) { + _cleanup_free_ char *j = cescape(joined); + free_and_replace(joined, j); + } + + log_debug("Got sd_notify() message: %s", strnull(joined)); + } + if (strv_contains(tags, "READY=1")) { r = sd_notify(false, "READY=1\n"); if (r < 0) @@ -4233,6 +4551,9 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r static int setup_notify_parent(sd_event *event, int fd, pid_t *inner_child_pid, sd_event_source **notify_event_source) { int r; + if (fd < 0) + return 0; + r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, nspawn_dispatch_notify_fd, inner_child_pid); if (r < 0) return log_error_errno(r, "Failed to allocate notify event source: %m"); @@ -4242,6 +4563,25 @@ static int setup_notify_parent(sd_event *event, int fd, pid_t *inner_child_pid, return 0; } +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *dot = NULL; + + assert(f); + + (void) gethostname_strict(&hn); + + if (emoji_enabled()) + dot = strjoin(special_glyph(SPECIAL_GLYPH_BLUE_CIRCLE), " "); + + if (hn) + (void) pty_forward_set_titlef(f, "%sContainer %s on %s", strempty(dot), arg_machine, hn); + else + (void) pty_forward_set_titlef(f, "%sContainer %s", strempty(dot), arg_machine); + + if (dot) + (void) pty_forward_set_title_prefix(f, dot); +} + static int merge_settings(Settings *settings, const char *path) { int rl; @@ -4457,7 +4797,7 @@ static int merge_settings(Settings *settings, const char *path) { #endif } - for (rl = 0; rl < _RLIMIT_MAX; rl ++) { + for (rl = 0; rl < _RLIMIT_MAX; rl++) { if ((arg_settings_mask & (SETTING_RLIMIT_FIRST << rl))) continue; @@ -4593,26 +4933,28 @@ static int load_settings(void) { return 0; /* We first look in the admin's directories in /etc and /run */ - FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { - _cleanup_free_ char *j = NULL; + if (arg_privileged) { + FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { + _cleanup_free_ char *j = NULL; - j = path_join(i, arg_settings_filename); - if (!j) - return log_oom(); + j = path_join(i, arg_settings_filename); + if (!j) + return log_oom(); - f = fopen(j, "re"); - if (f) { - p = TAKE_PTR(j); + f = fopen(j, "re"); + if (f) { + p = TAKE_PTR(j); - /* By default, we trust configuration from /etc and /run */ - if (arg_settings_trusted < 0) - arg_settings_trusted = true; + /* By default, we trust configuration from /etc and /run */ + if (arg_settings_trusted < 0) + arg_settings_trusted = true; - break; - } + break; + } - if (errno != ENOENT) - return log_error_errno(errno, "Failed to open %s: %m", j); + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open %s: %m", j); + } } if (!f) { @@ -4672,10 +5014,14 @@ static int load_oci_bundle(void) { static int run_container( DissectedImage *dissected_image, + int userns_fd, FDSet *fds, - char veth_name[IFNAMSIZ], bool *veth_created, + char veth_name[IFNAMSIZ], + bool *veth_created, struct ExposeArgs *expose_args, - int *master, pid_t *pid, int *ret) { + int *master, + pid_t *pid, + int *ret) { static const struct sigaction sa = { .sa_handler = nop_signal_handler, @@ -4691,6 +5037,7 @@ static int run_container( _cleanup_close_ int notify_socket = -EBADF, mntns_fd = -EBADF, fd_kmsg_fifo = -EBADF; _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; _cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unix_export_host_dir = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; _cleanup_(pty_forward_freep) PTYForward *forward = NULL; _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; @@ -4706,6 +5053,11 @@ static int run_container( assert_se(sigemptyset(&mask_chld) == 0); assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); + /* Set up the unix export host directory on the host first */ + r = setup_unix_export_dir_outside(&unix_export_host_dir); + if (r < 0) + return r; + if (arg_userns_mode == USER_NAMESPACE_PICK) { /* When we shall pick the UID/GID range, let's first lock /etc/passwd, so that we can safely * check with getpwuid() if the specific user already exists. Note that /etc might be @@ -4754,11 +5106,44 @@ static int run_container( "Path %s doesn't refer to a network namespace, refusing.", arg_network_namespace_path); } - *pid = raw_clone(SIGCHLD|CLONE_NEWNS); - if (*pid < 0) - return log_error_errno(errno, "clone() failed%s: %m", - errno == EINVAL ? - ", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : ""); + if (arg_privileged) { + assert(userns_fd < 0); + + /* If we have no user namespace then we'll clone and create a new mount namespace right-away. */ + + *pid = raw_clone(SIGCHLD|CLONE_NEWNS); + if (*pid < 0) + return log_error_errno(errno, "clone() failed%s: %m", + errno == EINVAL ? + ", do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in)" : ""); + } else { + assert(userns_fd >= 0); + + /* If we have a user namespace then we'll clone() first, and then join the user namespace, + * and then open the mount namespace, so that it is owned by the user namespace */ + + *pid = raw_clone(SIGCHLD); + if (*pid < 0) + return log_error_errno(errno, "clone() failed: %m"); + + if (*pid == 0) { + if (setns(userns_fd, CLONE_NEWUSER) < 0) { + log_error_errno(errno, "Failed to join allocate user namespace: %m"); + _exit(EXIT_FAILURE); + } + + r = reset_uid_gid(); + if (r < 0) { + log_error_errno(r, "Failed to reset UID/GID to root: %m"); + _exit(EXIT_FAILURE); + } + + if (unshare(CLONE_NEWNS) < 0) { + log_error_errno(errno, "Failed to unshare file system namespace: %m"); + _exit(EXIT_FAILURE); + } + } + } if (*pid == 0) { /* The outer child only has a file system namespace. */ @@ -4776,7 +5161,8 @@ static int run_container( fd_outer_socket_pair[1], fd_inner_socket_pair[1], fds, - child_netns_fd); + child_netns_fd, + unix_export_host_dir); if (r < 0) _exit(EXIT_FAILURE); @@ -4894,14 +5280,13 @@ static int run_container( /* Wait until the child has unshared its network namespace. */ if (!barrier_place_and_sync(&barrier)) /* #3 */ return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Child died too early"); - } - if (child_netns_fd < 0) { - /* Make sure we have an open file descriptor to the child's network - * namespace so it stays alive even if the child exits. */ - r = namespace_open(*pid, NULL, NULL, &child_netns_fd, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to open child network namespace: %m"); + /* Make sure we have an open file descriptor to the child's network namespace so it + * stays alive even if the child exits. */ + assert(child_netns_fd < 0); + child_netns_fd = receive_one_fd(fd_inner_socket_pair[0], 0); + if (child_netns_fd < 0) + return log_error_errno(r, "Failed to receive child network namespace: %m"); } r = move_network_interfaces(child_netns_fd, arg_network_interfaces); @@ -4909,12 +5294,29 @@ static int run_container( return r; if (arg_network_veth) { - r = setup_veth(arg_machine, *pid, veth_name, - arg_network_bridge || arg_network_zone, &arg_network_provided_mac); - if (r < 0) - return r; - else if (r > 0) - ifi = r; + if (arg_privileged) { + r = setup_veth(arg_machine, *pid, veth_name, + arg_network_bridge || arg_network_zone, &arg_network_provided_mac); + if (r < 0) + return r; + else if (r > 0) + ifi = r; + } else { + _cleanup_free_ char *host_ifname = NULL; + + r = nsresource_add_netif(userns_fd, child_netns_fd, /* namespace_ifname= */ NULL, &host_ifname, /* ret_namespace_ifname= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to add network interface to container: %m"); + + ifi = if_nametoindex(host_ifname); + if (ifi == 0) + return log_error_errno(errno, "Failed to resolve interface '%s': %m", host_ifname); + + if (strlen(host_ifname) >= IFNAMSIZ) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Host interface name too long?"); + + strcpy(veth_name, host_ifname); + } if (arg_network_bridge) { /* Add the interface to a bridge */ @@ -4953,9 +5355,12 @@ static int run_container( } if (arg_register || !arg_keep_unit) { - r = sd_bus_default_system(&bus); + if (arg_privileged) + r = sd_bus_default_system(&bus); + else + r = sd_bus_default_user(&bus); if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); + return log_error_errno(r, "Failed to open bus: %m"); r = sd_bus_set_close_on_exit(bus, false); if (r < 0) @@ -5016,7 +5421,13 @@ static int run_container( } else if (arg_slice || arg_property) log_notice("Machine and scope registration turned off, --slice= and --property= settings will have no effect."); - r = create_subcgroup(*pid, arg_keep_unit, arg_unified_cgroup_hierarchy); + r = create_subcgroup( + *pid, + arg_keep_unit, + arg_unified_cgroup_hierarchy, + arg_uid_shift, + userns_fd, + arg_privileged); if (r < 0) return r; @@ -5024,14 +5435,8 @@ static int run_container( if (r < 0) return r; - r = chown_cgroup(*pid, arg_unified_cgroup_hierarchy, arg_uid_shift); - if (r < 0) - return r; - - /* Notify the child that the parent is ready with all - * its setup (including cgroup-ification), and that - * the child can now hand over control to the code to - * run inside the container. */ + /* Notify the child that the parent is ready with all its setup (including cgroup-ification), and + * that the child can now hand over control to the code to run inside the container. */ (void) barrier_place(&barrier); /* #4 */ /* Block SIGCHLD here, before notifying child. @@ -5146,9 +5551,23 @@ static int run_container( return log_error_errno(r, "Failed to create PTY forwarder: %m"); if (arg_console_width != UINT_MAX || arg_console_height != UINT_MAX) - (void) pty_forward_set_width_height(forward, - arg_console_width, - arg_console_height); + (void) pty_forward_set_width_height( + forward, + arg_console_width, + arg_console_height); + + if (!arg_background && shall_tint_background()) { + _cleanup_free_ char *bg = NULL; + + r = terminal_tint_color(220 /* blue */, &bg); + if (r < 0) + log_debug_errno(r, "Failed to determine terminal background color, not tinting."); + else + (void) pty_forward_set_background_color(forward, bg); + } else if (!isempty(arg_background)) + (void) pty_forward_set_background_color(forward, arg_background); + + set_window_title(forward); break; default: @@ -5183,38 +5602,10 @@ static int run_container( fd_kmsg_fifo = safe_close(fd_kmsg_fifo); - if (arg_private_network) { - /* Move network interfaces back to the parent network namespace. We use `safe_fork` - * to avoid having to move the parent to the child network namespace. */ - r = safe_fork(NULL, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); + if (arg_private_network && arg_privileged) { + r = move_back_network_interfaces(child_netns_fd, arg_network_interfaces); if (r < 0) return r; - - if (r == 0) { - _cleanup_close_ int parent_netns_fd = -EBADF; - - r = namespace_open(getpid_cached(), NULL, NULL, &parent_netns_fd, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to open parent network namespace: %m"); - _exit(EXIT_FAILURE); - } - - r = namespace_enter(-1, -1, child_netns_fd, -1, -1); - if (r < 0) { - log_error_errno(r, "Failed to enter child network namespace: %m"); - _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"); - - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } } r = wait_for_container(TAKE_PID(*pid), &container_status); @@ -5288,7 +5679,7 @@ static int initialize_rlimits(void) { * don't read the other limits from PID 1 but prefer the static table above. */ }; - int rl; + int rl, r; for (rl = 0; rl < _RLIMIT_MAX; rl++) { /* Let's only fill in what the user hasn't explicitly configured anyway */ @@ -5299,8 +5690,9 @@ static int initialize_rlimits(void) { if (IN_SET(rl, RLIMIT_NPROC, RLIMIT_SIGPENDING)) { /* For these two let's read the limits off PID 1. See above for an explanation. */ - if (prlimit(1, rl, NULL, &buffer) < 0) - return log_error_errno(errno, "Failed to read resource limit RLIMIT_%s of PID 1: %m", rlimit_to_string(rl)); + r = pid_getrlimit(1, rl, &buffer); + if (r < 0) + return log_error_errno(r, "Failed to read resource limit RLIMIT_%s of PID 1: %m", rlimit_to_string(rl)); v = &buffer; } else if (rl == RLIMIT_NOFILE) { @@ -5351,6 +5743,10 @@ static int cant_be_in_netns(void) { if (r == -ENOENT || ERRNO_IS_NEG_DISCONNECT(r)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Sorry, but --image= requires access to the host's /run/ hierarchy, since we need access to udev."); + if (ERRNO_IS_NEG_PRIVILEGE(r)) { + log_debug_errno(r, "Can't connect to udev control socket, assuming we are in same netns."); + return 0; + } if (r < 0) return log_error_errno(r, "Failed to connect socket to udev control socket: %m"); @@ -5369,7 +5765,7 @@ static int cant_be_in_netns(void) { static int run(int argc, char *argv[]) { bool remove_directory = false, remove_image = false, veth_created = false, remove_tmprootdir = false; - _cleanup_close_ int master = -EBADF; + _cleanup_close_ int master = -EBADF, userns_fd = -EBADF; _cleanup_fdset_free_ FDSet *fds = NULL; int r, n_fd_passed, ret = EXIT_SUCCESS; char veth_name[IFNAMSIZ] = ""; @@ -5381,20 +5777,14 @@ static int run(int argc, char *argv[]) { _cleanup_(fw_ctx_freep) FirewallContext *fw_ctx = NULL; pid_t pid = 0; - log_parse_environment(); - log_open(); + log_setup(); + + arg_privileged = getuid() == 0; r = parse_argv(argc, argv); if (r <= 0) goto finish; - if (geteuid() != 0) { - r = log_warning_errno(SYNTHETIC_ERRNO(EPERM), - argc >= 2 ? "Need to be root." : - "Need to be root (and some arguments are usually required).\nHint: try --help"); - goto finish; - } - r = cant_be_in_netns(); if (r < 0) goto finish; @@ -5407,6 +5797,10 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + r = pick_paths(); + if (r < 0) + goto finish; + r = determine_names(); if (r < 0) goto finish; @@ -5421,7 +5815,7 @@ static int run(int argc, char *argv[]) { if (!arg_private_network && arg_userns_mode != USER_NAMESPACE_NO && arg_uid_shift > 0) arg_caps_retain &= ~(UINT64_C(1) << CAP_NET_BIND_SERVICE); - r = cg_unified(); + r = cg_unified(); /* initialize cache early */ if (r < 0) { log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); goto finish; @@ -5431,6 +5825,10 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; + r = resolve_network_interface_names(arg_network_interfaces); + if (r < 0) + goto finish; + r = verify_network_interfaces_initialized(); if (r < 0) goto finish; @@ -5438,6 +5836,16 @@ static int run(int argc, char *argv[]) { /* Reapply environment settings. */ (void) detect_unified_cgroup_hierarchy_from_environment(); + if (!arg_privileged) { + r = cg_all_unified(); + if (r < 0) { + log_error_errno(r, "Failed to determine if we are in unified cgroupv2 mode: %m"); + goto finish; + } + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unprivileged operation only supported in unified cgroupv2 mode."); + } + /* Ignore SIGPIPE here, because we use splice() on the ptyfwd stuff and that will generate SIGPIPE if * the result is closed. Note that the container payload child will reset signal mask+handler anyway, * so just turning this off here means we only turn it off in nspawn itself, not any children. */ @@ -5457,9 +5865,21 @@ static int run(int argc, char *argv[]) { * the child. Functions like copy_devnodes() change the umask temporarily. */ umask(0022); + if (arg_console_mode < 0) + arg_console_mode = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) ? + CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY; + + if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */ + arg_quiet = true; + if (arg_directory) { assert(!arg_image); + if (!arg_privileged) { + r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invoking container from plain directory tree is currently not supported if called without privileges."); + goto finish; + } + /* Safety precaution: let's not allow running images from the live host OS image, as long as * /var from the host will propagate into container dynamically (because bad things happen if * two systems write to the same /var). Let's allow it for the special cases where /var is @@ -5480,7 +5900,7 @@ static int run(int argc, char *argv[]) { /* If the specified path is a mount point we generate the new snapshot immediately * inside it under a random name. However if the specified is not a mount point we * create the new snapshot in the parent directory, just next to it. */ - r = path_is_mount_point(arg_directory, NULL, 0); + r = path_is_mount_point(arg_directory); if (r < 0) { log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory); goto finish; @@ -5496,7 +5916,11 @@ static int run(int argc, char *argv[]) { /* We take an exclusive lock on this image, since it's our private, ephemeral copy * only owned by us and no one else. */ - r = image_path_lock(np, LOCK_EX|LOCK_NB, &tree_global_lock, &tree_local_lock); + r = image_path_lock( + np, + LOCK_EX|LOCK_NB, + arg_privileged ? &tree_global_lock : NULL, + &tree_local_lock); if (r < 0) { log_error_errno(r, "Failed to lock %s: %m", np); goto finish; @@ -5528,7 +5952,11 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; - r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + r = image_path_lock( + arg_directory, + (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, + arg_privileged ? &tree_global_lock : NULL, + &tree_local_lock); if (r == -EBUSY) { log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); goto finish; @@ -5620,15 +6048,12 @@ static int run(int argc, char *argv[]) { } else { DissectImageFlags dissect_image_flags = - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES; + determine_dissect_image_flags(); + assert(arg_image); assert(!arg_template); + r = chase_and_update(&arg_image, 0); if (r < 0) goto finish; @@ -5643,9 +6068,13 @@ static int run(int argc, char *argv[]) { } /* Always take an exclusive lock on our own ephemeral copy. */ - r = image_path_lock(np, LOCK_EX|LOCK_NB, &tree_global_lock, &tree_local_lock); + r = image_path_lock( + np, + LOCK_EX|LOCK_NB, + arg_privileged ? &tree_global_lock : NULL, + &tree_local_lock); if (r < 0) { - r = log_error_errno(r, "Failed to create image lock: %m"); + log_error_errno(r, "Failed to create image lock: %m"); goto finish; } @@ -5668,13 +6097,17 @@ static int run(int argc, char *argv[]) { free_and_replace(arg_image, np); remove_image = true; } else { - r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + r = image_path_lock( + arg_image, + (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, + arg_privileged ? &tree_global_lock : NULL, + &tree_local_lock); if (r == -EBUSY) { - r = log_error_errno(r, "Disk image %s is currently busy.", arg_image); + log_error_errno(r, "Disk image %s is currently busy.", arg_image); goto finish; } if (r < 0) { - r = log_error_errno(r, "Failed to create image lock: %m"); + log_error_errno(r, "Failed to create image lock: %m"); goto finish; } @@ -5703,56 +6136,80 @@ static int run(int argc, char *argv[]) { goto finish; } - r = loop_device_make_by_path( - arg_image, - arg_read_only ? O_RDONLY : O_RDWR, - /* sector_size= */ UINT32_MAX, - FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, - LOCK_SH, - &loop); - if (r < 0) { - log_error_errno(r, "Failed to set up loopback block device: %m"); - goto finish; - } + if (arg_privileged) { + r = loop_device_make_by_path( + arg_image, + arg_read_only ? O_RDONLY : O_RDWR, + /* sector_size= */ UINT32_MAX, + FLAGS_SET(dissect_image_flags, DISSECT_IMAGE_NO_PARTITION_TABLE) ? 0 : LO_FLAGS_PARTSCAN, + LOCK_SH, + &loop); + if (r < 0) { + log_error_errno(r, "Failed to set up loopback block device: %m"); + goto finish; + } - r = dissect_loop_device_and_warn( - loop, - &arg_verity_settings, - /* mount_options=*/ NULL, - arg_image_policy ?: &image_policy_container, - dissect_image_flags, - &dissected_image); - if (r == -ENOPKG) { - /* dissected_image_and_warn() already printed a brief error message. Extend on that with more details */ - log_notice("Note that the disk image needs to\n" - " a) either contain only a single MBR partition of type 0x83 that is marked bootable\n" - " b) or contain a single GPT partition of type 0FC63DAF-8483-4772-8E79-3D69D8477DE4\n" - " c) or follow https://uapi-group.org/specifications/specs/discoverable_partitions_specification\n" - " d) or contain a file system without a partition table\n" - "in order to be bootable with systemd-nspawn."); - goto finish; - } - if (r < 0) - goto finish; + r = dissect_loop_device_and_warn( + loop, + &arg_verity_settings, + /* mount_options=*/ NULL, + arg_image_policy ?: &image_policy_container, + dissect_image_flags, + &dissected_image); + if (r == -ENOPKG) { + /* dissected_image_and_warn() already printed a brief error message. Extend on that with more details */ + log_notice("Note that the disk image needs to\n" + " a) either contain only a single MBR partition of type 0x83 that is marked bootable\n" + " b) or contain a single GPT partition of type 0FC63DAF-8483-4772-8E79-3D69D8477DE4\n" + " c) or follow https://uapi-group.org/specifications/specs/discoverable_partitions_specification\n" + " d) or contain a file system without a partition table\n" + "in order to be bootable with systemd-nspawn."); + goto finish; + } + if (r < 0) + goto finish; - r = dissected_image_load_verity_sig_partition( - dissected_image, - loop->fd, - &arg_verity_settings); - if (r < 0) - goto finish; + r = dissected_image_load_verity_sig_partition( + dissected_image, + loop->fd, + &arg_verity_settings); + if (r < 0) + goto finish; - if (dissected_image->has_verity && !arg_verity_settings.root_hash && !dissected_image->has_verity_sig) - log_notice("Note: image %s contains verity information, but no root hash specified and no embedded " - "root hash signature found! Proceeding without integrity checking.", arg_image); + if (dissected_image->has_verity && !arg_verity_settings.root_hash && !dissected_image->has_verity_sig) + log_notice("Note: image %s contains verity information, but no root hash specified and no embedded " + "root hash signature found! Proceeding without integrity checking.", arg_image); - r = dissected_image_decrypt_interactively( - dissected_image, - NULL, - &arg_verity_settings, - 0); - if (r < 0) - goto finish; + r = dissected_image_decrypt_interactively( + dissected_image, + NULL, + &arg_verity_settings, + dissect_image_flags); + if (r < 0) + goto finish; + } else { + _cleanup_free_ char *userns_name = strjoin("nspawn-", arg_machine); + if (!userns_name) { + r = log_oom(); + goto finish; + } + + /* if we are unprivileged, let's allocate a 64K userns first */ + userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000)); + if (userns_fd < 0) { + r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); + goto finish; + } + + r = mountfsd_mount_image( + arg_image, + userns_fd, + arg_image_policy, + dissect_image_flags, + &dissected_image); + if (r < 0) + goto finish; + } /* Now that we mounted the image, let's try to remove it again, if it is ephemeral */ if (remove_image && unlink(arg_image) >= 0) @@ -5766,19 +6223,20 @@ static int run(int argc, char *argv[]) { if (r < 0) goto finish; - if (arg_console_mode < 0) - arg_console_mode = - isatty(STDIN_FILENO) > 0 && - isatty(STDOUT_FILENO) > 0 ? CONSOLE_INTERACTIVE : CONSOLE_READ_ONLY; + if (!arg_quiet) { + const char *t = arg_image ?: arg_directory; + _cleanup_free_ char *u = NULL; + (void) terminal_urlify_path(t, t, &u); - if (arg_console_mode == CONSOLE_PIPE) /* if we pass STDERR on to the container, don't add our own logs into it too */ - arg_quiet = true; + log_info("%s %sSpawning container %s on %s.%s", + special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: t, ansi_normal()); - if (!arg_quiet) - log_info("Spawning container %s on %s.\nPress Ctrl-] three times within 1s to kill container.", - arg_machine, arg_image ?: arg_directory); + if (arg_console_mode == CONSOLE_INTERACTIVE) + log_info("%s %sPress %sCtrl-]%s three times within 1s to kill container.%s", + special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal()); + } - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, SIGRTMIN+18) >= 0); r = make_reaper_process(true); if (r < 0) { @@ -5795,11 +6253,13 @@ static int run(int argc, char *argv[]) { expose_args.fw_ctx = fw_ctx; } for (;;) { - r = run_container(dissected_image, - fds, - veth_name, &veth_created, - &expose_args, &master, - &pid, &ret); + r = run_container( + dissected_image, + userns_fd, + fds, + veth_name, &veth_created, + &expose_args, &master, + &pid, &ret); if (r <= 0) break; } @@ -5841,25 +6301,30 @@ finish: log_debug_errno(errno, "Can't remove temporary root directory '%s', ignoring: %m", tmprootdir); } - if (arg_machine) { + if (arg_machine && arg_privileged) { const char *p; p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); (void) rm_rf(p, REMOVE_ROOT); + + p = strjoina("/run/systemd/nspawn/unix-export/", arg_machine); + (void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW); + (void) rmdir(p); } expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET, &expose_args.address4); expose_port_flush(&fw_ctx, arg_expose_ports, AF_INET6, &expose_args.address6); - if (veth_created) - (void) remove_veth_links(veth_name, arg_network_veth_extra); - (void) remove_bridge(arg_network_zone); + if (arg_privileged) { + if (veth_created) + (void) remove_veth_links(veth_name, arg_network_veth_extra); + (void) remove_bridge(arg_network_zone); + } custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); expose_port_free_all(arg_expose_ports); rlimit_free_all(arg_rlimit); device_node_array_free(arg_extra_nodes, arg_n_extra_nodes); - machine_credential_free_all(arg_credentials, arg_n_credentials); if (r < 0) return r; diff --git a/src/nspawn/nspawn.h b/src/nspawn/nspawn.h index 27fb0b4..556f8ee 100644 --- a/src/nspawn/nspawn.h +++ b/src/nspawn/nspawn.h @@ -5,3 +5,4 @@ int userns_lchown(const char *p, uid_t uid, gid_t gid); int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid); +int make_run_host(const char *root); diff --git a/src/nspawn/test-nspawn-util.c b/src/nspawn/test-nspawn-util.c index 08c8050..533edde 100644 --- a/src/nspawn/test-nspawn-util.c +++ b/src/nspawn/test-nspawn-util.c @@ -8,7 +8,7 @@ TEST(systemd_installation_has_version) { int r; - FOREACH_STRING(version, "0", "231", STRINGIFY(PROJECT_VERSION), "999") { + FOREACH_STRING(version, "0", "231", PROJECT_VERSION_FULL, "999") { r = systemd_installation_has_version(saved_argv[1], version); assert_se(r >= 0); log_info("%s has systemd >= %s: %s", diff --git a/src/nsresourced/bpf/userns_restrict/meson.build b/src/nsresourced/bpf/userns_restrict/meson.build new file mode 100644 index 0000000..d773c75 --- /dev/null +++ b/src/nsresourced/bpf/userns_restrict/meson.build @@ -0,0 +1,25 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +if conf.get('HAVE_VMLINUX_H') != 1 + subdir_done() +endif + +userns_restrict_bpf_o_unstripped = custom_target( + 'userns-restrict.bpf.unstripped.o', + input : 'userns-restrict.bpf.c', + output : 'userns-restrict.bpf.unstripped.o', + command : bpf_o_unstripped_cmd, + depends : vmlinux_h_dependency) + +userns_restrict_bpf_o = custom_target( + 'userns-restrict.bpf.o', + input : userns_restrict_bpf_o_unstripped, + output : 'userns-restrict.bpf.o', + command : bpf_o_cmd) + +userns_restrict_skel_h = custom_target( + 'userns-restrict.skel.h', + input : userns_restrict_bpf_o, + output : 'userns-restrict.skel.h', + command : skel_h_cmd, + capture : true) diff --git a/src/nsresourced/bpf/userns_restrict/userns-restrict-skel.h b/src/nsresourced/bpf/userns_restrict/userns-restrict-skel.h new file mode 100644 index 0000000..271caf4 --- /dev/null +++ b/src/nsresourced/bpf/userns_restrict/userns-restrict-skel.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* The SPDX header above is actually correct in claiming this was + * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that + * compatible with GPL we will claim this to be GPL however, which should be + * fine given that LGPL-2.1-or-later downgrades to GPL if needed. + */ + +#include "bpf-dlopen.h" + +/* libbpf is used via dlopen(), so rename symbols */ +#define bpf_object__attach_skeleton sym_bpf_object__attach_skeleton +#define bpf_object__destroy_skeleton sym_bpf_object__destroy_skeleton +#define bpf_object__load_skeleton sym_bpf_object__load_skeleton +#define bpf_object__open_skeleton sym_bpf_object__open_skeleton + +#include "bpf/userns_restrict/userns-restrict.skel.h" diff --git a/src/nsresourced/bpf/userns_restrict/userns-restrict.bpf.c b/src/nsresourced/bpf/userns_restrict/userns-restrict.bpf.c new file mode 100644 index 0000000..126422b --- /dev/null +++ b/src/nsresourced/bpf/userns_restrict/userns-restrict.bpf.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +/* The SPDX header above is actually correct in claiming this was + * LGPL-2.1-or-later, because it is. Since the kernel doesn't consider that + * compatible with GPL we will claim this to be GPL however, which should be + * fine given that LGPL-2.1-or-later downgrades to GPL if needed. + */ + +/* If offsetof() is implemented via __builtin_offset() then it doesn't work on current compilers, since the + * built-ins do not understand CO-RE. Let's undefine any such macros here, to force bpf_helpers.h to define + * its own definitions for this. (In new versions it will do so automatically, but at least in libbpf 1.1.0 + * it does not.) */ +#undef offsetof +#undef container_of + +#include "vmlinux.h" + +#include +#include +#include +#include + +#ifndef bpf_core_cast +/* bpf_rdonly_cast() was introduced in libbpf commit 688879f together with + * the definition of a bpf_core_cast macro. So use that one to avoid + * defining a prototype for bpf_rdonly_cast */ +void *bpf_rdonly_cast(void *, __u32) __ksym; +#endif + +/* BPF module that implements an allowlist of mounts (identified by mount ID) for user namespaces (identified + * by their inode number in nsfs) that restricts creation of inodes (which would inherit the callers UID/GID) + * or changing of ownership (similar). + * + * This hooks into the various path-based LSM entrypoints that control inode creation as well as chmod(), and + * then looks up the calling process' user namespace in a global map of namespaces, which points us to + * another map that is simply a list of allowed mnt_ids. */ + +// FIXME: ACL adjustments are currently not blocked. There's no path-based LSM hook available in the kernel +// for setting xattrs or ACLs, hence we cannot easily block them, even though we want that. We can get away +// with ignoring this for now, as ACLs never define ownership, but purely access: i.e. ACLs never allow +// taking possession of an object, but only control access to it. Thus, things like suid access modes should +// not be reachable through it. It still sucks though that a user can persistently add an ACL entry to a file +// with their transient UIDs/GIDs. + +/* kernel currently enforces a maximum usernamespace nesting depth of 32, see create_user_ns() in the kernel sources */ +#define USER_NAMESPACE_DEPTH_MAX 32U + +struct mnt_id_map { + __uint(type, BPF_MAP_TYPE_HASH); + __uint(max_entries, 1); /* placeholder, configured otherwise by nsresourced */ + __type(key, int); + __type(value, int); +}; + +struct { + __uint(type, BPF_MAP_TYPE_HASH_OF_MAPS); + __uint(max_entries, 1); /* placeholder, configured otherwise by nsresourced */ + __type(key, unsigned); /* userns inode */ + __array(values, struct mnt_id_map); +} userns_mnt_id_hash SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 4096); +} userns_ringbuf SEC(".maps"); + +static inline struct mount *real_mount(struct vfsmount *mnt) { + return container_of(mnt, struct mount, mnt); +} + +static int validate_inode_on_mount(struct inode *inode, struct vfsmount *v) { + struct user_namespace *mount_userns, *task_userns, *p; + unsigned task_userns_inode; + struct task_struct *task; + void *mnt_id_map; + struct mount *m; + int mnt_id; + + /* Get user namespace from vfsmount */ + m = bpf_rdonly_cast(real_mount(v), bpf_core_type_id_kernel(struct mount)); + mount_userns = m->mnt_ns->user_ns; + + /* Get user namespace from task */ + task = (struct task_struct*) bpf_get_current_task_btf(); + task_userns = task->cred->user_ns; + + /* Is the file on a mount that belongs to our own user namespace or a child of it? If so, say + * yes immediately. */ + p = mount_userns; + for (unsigned i = 0; i < USER_NAMESPACE_DEPTH_MAX; i++) { + if (p == task_userns) + return 0; /* our task's user namespace (or a child thereof) owns this superblock: allow! */ + + p = p->parent; + if (!p) + break; + } + + /* Hmm, something is fishy if there's more than 32 levels of namespaces involved. Let's better be + * safe than sorry, and refuse. */ + if (p) + return -EPERM; + + /* This is a mount foreign to our task's user namespace, let's consult our allow list */ + task_userns_inode = task_userns->ns.inum; + + mnt_id_map = bpf_map_lookup_elem(&userns_mnt_id_hash, &task_userns_inode); + if (!mnt_id_map) /* No rules installed for this userns? Then say yes, too! */ + return 0; + + mnt_id = m->mnt_id; + + /* Otherwise, say yes if the mount ID is allowlisted */ + if (bpf_map_lookup_elem(mnt_id_map, &mnt_id)) + return 0; + + return -EPERM; +} + +static int validate_path(const struct path *path, int ret) { + struct inode *inode; + struct vfsmount *v; + + if (ret != 0) /* propagate earlier error */ + return ret; + + inode = path->dentry->d_inode; + v = path->mnt; + + return validate_inode_on_mount(inode, v); +} + +SEC("lsm/path_chown") +int BPF_PROG(userns_restrict_path_chown, struct path *path, void* uid, void *gid, int ret) { + return validate_path(path, ret); +} + +SEC("lsm/path_mkdir") +int BPF_PROG(userns_restrict_path_mkdir, struct path *dir, struct dentry *dentry, umode_t mode, int ret) { + return validate_path(dir, ret); +} + +SEC("lsm/path_mknod") +int BPF_PROG(userns_restrict_path_mknod, const struct path *dir, struct dentry *dentry, umode_t mode, unsigned int dev, int ret) { + return validate_path(dir, ret); +} + +SEC("lsm/path_symlink") +int BPF_PROG(userns_restrict_path_symlink, const struct path *dir, struct dentry *dentry, const char *old_name, int ret) { + return validate_path(dir, ret); +} + +SEC("lsm/path_link") +int BPF_PROG(userns_restrict_path_link, struct dentry *old_dentry, const struct path *new_dir, struct dentry *new_dentry, int ret) { + return validate_path(new_dir, ret); +} + +SEC("kprobe/free_user_ns") +void BPF_KPROBE(userns_restrict_free_user_ns, struct work_struct *work) { + struct user_namespace *userns; + unsigned inode; + void *mnt_id_map; + + /* Inform userspace that a user namespace just went away. I wish there was a nicer way to hook into + * user namespaces being deleted than using kprobes, but couldn't find any. */ + + userns = bpf_rdonly_cast(container_of(work, struct user_namespace, work), + bpf_core_type_id_kernel(struct user_namespace)); + + inode = userns->ns.inum; + + mnt_id_map = bpf_map_lookup_elem(&userns_mnt_id_hash, &inode); + if (!mnt_id_map) /* No rules installed for this userns? Then send no notification. */ + return; + + bpf_ringbuf_output(&userns_ringbuf, &inode, sizeof(inode), 0); +} + +static const char _license[] SEC("license") = "GPL"; diff --git a/src/nsresourced/meson.build b/src/nsresourced/meson.build new file mode 100644 index 0000000..cb131f0 --- /dev/null +++ b/src/nsresourced/meson.build @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +subdir('bpf/userns_restrict') + +systemd_nsresourcework_sources = files( + 'nsresourcework.c', + 'userns-restrict.c', + 'userns-registry.c', +) + +systemd_nsresourced_sources = files( + 'nsresourced-manager.c', + 'nsresourced.c', + 'userns-restrict.c', + 'userns-registry.c', +) + +userns_restrict_include = include_directories('.') + +if conf.get('HAVE_VMLINUX_H') == 1 + systemd_nsresourcework_sources += userns_restrict_skel_h + systemd_nsresourced_sources += userns_restrict_skel_h + + executables += [ + test_template + { + 'sources' : files('test-userns-restrict.c', 'userns-restrict.c') + userns_restrict_skel_h, + 'conditions' : ['ENABLE_NSRESOURCED', 'HAVE_VMLINUX_H'], + 'include_directories' : [ includes, userns_restrict_include ], + }, + ] +endif + +executables += [ + libexec_template + { + 'name' : 'systemd-nsresourcework', + 'conditions' : ['ENABLE_NSRESOURCED'], + 'sources' : systemd_nsresourcework_sources, + 'dependencies' : threads, + 'include_directories' : [ includes, userns_restrict_include ], + }, + libexec_template + { + 'name' : 'systemd-nsresourced', + 'conditions' : ['ENABLE_NSRESOURCED'], + 'sources' : systemd_nsresourced_sources, + 'dependencies' : threads, + 'include_directories' : [ includes, userns_restrict_include ], + }, +] diff --git a/src/nsresourced/nsresourced-manager.c b/src/nsresourced/nsresourced-manager.c new file mode 100644 index 0000000..d87da58 --- /dev/null +++ b/src/nsresourced/nsresourced-manager.c @@ -0,0 +1,647 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "sd-daemon.h" + +#include "bpf-dlopen.h" +#include "build-path.h" +#include "common-signal.h" +#include "env-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "nsresourced-manager.h" +#include "parse-util.h" +#include "process-util.h" +#include "recurse-dir.h" +#include "set.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "umask-util.h" +#include "unaligned.h" +#include "user-util.h" +#include "userns-registry.h" +#include "userns-restrict.h" + +#define LISTEN_TIMEOUT_USEC (25 * USEC_PER_SEC) + +static int start_workers(Manager *m, bool explicit_request); + +static int on_worker_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(s); + + assert_se(!set_remove(m->workers_dynamic, s) != !set_remove(m->workers_fixed, s)); + sd_event_source_disable_unref(s); + + if (si->si_code == CLD_EXITED) { + if (si->si_status == EXIT_SUCCESS) + log_debug("Worker " PID_FMT " exited successfully.", si->si_pid); + else + log_warning("Worker " PID_FMT " died with a failure exit status %i, ignoring.", si->si_pid, si->si_status); + } else if (si->si_code == CLD_KILLED) + log_warning("Worker " PID_FMT " was killed by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status)); + else if (si->si_code == CLD_DUMPED) + log_warning("Worker " PID_FMT " dumped core by signal %s, ignoring.", si->si_pid, signal_to_string(si->si_status)); + else + log_warning("Got unexpected exit code via SIGCHLD, ignoring."); + + (void) start_workers(m, /* explicit_request= */ false); /* Fill up workers again if we fell below the low watermark */ + return 0; +} + +static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(s); + + (void) start_workers(m, /* explicit_request=*/ true); /* Workers told us there's more work, let's add one more worker as long as we are below the high watermark */ + return 0; +} + +static int on_deferred_start_worker(sd_event_source *s, uint64_t usec, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + + assert(s); + + m->deferred_start_worker_event_source = sd_event_source_unref(m->deferred_start_worker_event_source); + + (void) start_workers(m, /* explicit_request=*/ false); + return 0; +} + +DEFINE_PRIVATE_HASH_OPS_WITH_KEY_DESTRUCTOR( + event_source_hash_ops, + sd_event_source, + (void (*)(const sd_event_source*, struct siphash*)) trivial_hash_func, + (int (*)(const sd_event_source*, const sd_event_source*)) trivial_compare_func, + sd_event_source_disable_unref); + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + m = new(Manager, 1); + if (!m) + return -ENOMEM; + + *m = (Manager) { + .listen_fd = -EBADF, + .worker_ratelimit = { + .interval = 2 * USEC_PER_SEC, + .burst = 250, + }, + .registry_fd = -EBADF, + }; + + r = sd_event_new(&m->event); + if (r < 0) + return r; + + r = sd_event_set_signal_exit(m->event, true); + if (r < 0) + return r; + + r = sd_event_add_signal(m->event, NULL, (SIGRTMIN+18)|SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); + if (r < 0) + return r; + + r = sd_event_add_memory_pressure(m->event, NULL, NULL, NULL); + if (r < 0) + log_debug_errno(r, "Failed allocate memory pressure event source, ignoring: %m"); + + r = sd_event_set_watchdog(m->event, true); + if (r < 0) + log_debug_errno(r, "Failed to enable watchdog handling, ignoring: %m"); + + r = sd_event_add_signal(m->event, NULL, SIGUSR2|SD_EVENT_SIGNAL_PROCMASK, on_sigusr2, m); + if (r < 0) + return r; + + *ret = TAKE_PTR(m); + return 0; +} + +Manager* manager_free(Manager *m) { + if (!m) + return NULL; + + set_free(m->workers_fixed); + set_free(m->workers_dynamic); + + m->deferred_start_worker_event_source = sd_event_source_unref(m->deferred_start_worker_event_source); + + safe_close(m->listen_fd); + +#if HAVE_VMLINUX_H + sd_event_source_disable_unref(m->userns_restrict_bpf_ring_buffer_event_source); + if (m->userns_restrict_bpf_ring_buffer) + sym_ring_buffer__free(m->userns_restrict_bpf_ring_buffer); + userns_restrict_bpf_free(m->userns_restrict_bpf); +#endif + + safe_close(m->registry_fd); + + sd_event_unref(m->event); + + return mfree(m); +} + +static size_t manager_current_workers(Manager *m) { + assert(m); + + return set_size(m->workers_fixed) + set_size(m->workers_dynamic); +} + +static int start_one_worker(Manager *m) { + _cleanup_(sd_event_source_disable_unrefp) sd_event_source *source = NULL; + bool fixed; + pid_t pid; + int r; + + assert(m); + + fixed = set_size(m->workers_fixed) < NSRESOURCE_WORKERS_MIN; + + r = safe_fork_full( + "(sd-worker)", + /* stdio_fds= */ NULL, + &m->listen_fd, 1, + FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_CLOSE_ALL_FDS, + &pid); + if (r < 0) + return log_error_errno(r, "Failed to fork new worker child: %m"); + if (r == 0) { + char pids[DECIMAL_STR_MAX(pid_t)]; + /* Child */ + + if (m->listen_fd == 3) { + r = fd_cloexec(3, false); + if (r < 0) { + log_error_errno(r, "Failed to turn off O_CLOEXEC for fd 3: %m"); + _exit(EXIT_FAILURE); + } + } else { + if (dup2(m->listen_fd, 3) < 0) { /* dup2() creates with O_CLOEXEC off */ + log_error_errno(errno, "Failed to move listen fd to 3: %m"); + _exit(EXIT_FAILURE); + } + + safe_close(m->listen_fd); + } + + xsprintf(pids, PID_FMT, pid); + if (setenv("LISTEN_PID", pids, 1) < 0) { + log_error_errno(errno, "Failed to set $LISTEN_PID: %m"); + _exit(EXIT_FAILURE); + } + + if (setenv("LISTEN_FDS", "1", 1) < 0) { + log_error_errno(errno, "Failed to set $LISTEN_FDS: %m"); + _exit(EXIT_FAILURE); + } + + if (setenv("NSRESOURCE_FIXED_WORKER", one_zero(fixed), 1) < 0) { + log_error_errno(errno, "Failed to set $NSRESOURCE_FIXED_WORKER: %m"); + _exit(EXIT_FAILURE); + } + +#if HAVE_VMLINUX_H + bool supported = m->userns_restrict_bpf; +#else + bool supported = false; +#endif + + /* Tell the workers whether to enable the userns API */ + if (setenv("NSRESOURCE_API", one_zero(supported), 1) < 0) { + log_error_errno(errno, "Failed to set $NSRESOURCE_API: %m"); + _exit(EXIT_FAILURE); + } + + r = setenv_systemd_log_level(); + if (r < 0) { + log_error_errno(r, "Failed to set $SYSTEMD_LOG_LEVEL: %m"); + _exit(EXIT_FAILURE); + } + + r = invoke_callout_binary(SYSTEMD_NSRESOURCEWORK_PATH, STRV_MAKE("systemd-nsresourcework", "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */ + log_error_errno(r, "Failed start worker process: %m"); + _exit(EXIT_FAILURE); + } + + r = sd_event_add_child(m->event, &source, pid, WEXITED, on_worker_exit, m); + if (r < 0) + return log_error_errno(r, "Failed to watch child " PID_FMT ": %m", pid); + + r = set_ensure_put( + fixed ? &m->workers_fixed : &m->workers_dynamic, + &event_source_hash_ops, + source); + if (r < 0) + return log_error_errno(r, "Failed to add child process to set: %m"); + + TAKE_PTR(source); + + return 0; +} + +static int start_workers(Manager *m, bool explicit_request) { + int r; + + assert(m); + + for (;;) { + size_t n; + + n = manager_current_workers(m); + if (n >= NSRESOURCE_WORKERS_MIN && (!explicit_request || n >= NSRESOURCE_WORKERS_MAX)) + break; + + if (!ratelimit_below(&m->worker_ratelimit)) { + + /* If we keep starting workers too often but none sticks, let's fail the whole + * daemon, something is wrong */ + if (n == 0) { + sd_event_exit(m->event, EXIT_FAILURE); + return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "Worker threads requested too frequently, but worker count is zero, something is wrong."); + } + + /* Otherwise, let's stop spawning more for a while. */ + log_warning("Worker threads requested too frequently, not starting new ones for a while."); + + if (!m->deferred_start_worker_event_source) { + r = sd_event_add_time( + m->event, + &m->deferred_start_worker_event_source, + CLOCK_MONOTONIC, + ratelimit_end(&m->worker_ratelimit), + /* accuracy_usec= */ 0, + on_deferred_start_worker, + m); + if (r < 0) + return log_error_errno(r, "Failed to allocate deferred start worker event source: %m"); + } + + break; + } + + r = start_one_worker(m); + if (r < 0) + return r; + + explicit_request = false; + } + + return 0; +} + +static void manager_release_userns_bpf(Manager *m, uint64_t inode) { +#if HAVE_VMLINUX_H + int r; + + assert(m); + + if (inode == 0) + return; + + assert(m->userns_restrict_bpf); + + r = userns_restrict_reset_by_inode(m->userns_restrict_bpf, inode); + if (r < 0) + return (void) log_warning_errno(r, "Failed to remove namespace inode from BPF map, ignoring: %m"); +#endif +} + +static void manager_release_userns_fds(Manager *m, uint64_t inode) { + int r; + + assert(m); + assert(inode != 0); + + r = sd_notifyf(/* unset_environment= */ false, + "FDSTOREREMOVE=1\n" + "FDNAME=userns-%" PRIu64 "\n", inode); + if (r < 0) + log_warning_errno(r, "Failed to send fd store removal message, ignoring: %m"); +} + +static void manager_release_userns_by_inode(Manager *m, uint64_t inode) { + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_close_ int lock_fd = -EBADF; + int r; + + assert(m); + assert(inode != 0); + + lock_fd = userns_registry_lock(m->registry_fd); + if (lock_fd < 0) + return (void) log_error_errno(lock_fd, "Failed to lock registry: %m"); + + r = userns_registry_load_by_userns_inode(m->registry_fd, inode, &userns_info); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to find userns for inode %" PRIu64 ", ignoring: %m", inode); + + if (userns_info && uid_is_valid(userns_info->start)) + log_debug("Removing user namespace mapping %" PRIu64 " for UID " UID_FMT ".", inode, userns_info->start); + else + log_debug("Removing user namespace mapping %" PRIu64 ".", inode); + + /* Remove the BPF rules */ + manager_release_userns_bpf(m, inode); + + /* Remove the resources from the fdstore */ + manager_release_userns_fds(m, inode); + + /* And finally remove the resources file from disk */ + if (userns_info) { + /* Remove the cgroups of this userns */ + r = userns_info_remove_cgroups(userns_info); + if (r < 0) + log_warning_errno(r, "Failed to remove cgroups of user namespace: %m"); + + r = userns_registry_remove(m->registry_fd, userns_info); + if (r < 0) + log_warning_errno(r, "Failed to remove user namespace '%s', ignoring.", userns_info->name); + } +} + +static int manager_scan_registry(Manager *m, Set **registry_inodes) { + _cleanup_free_ DirectoryEntries *de = NULL; + int r; + + assert(m); + assert(registry_inodes); + assert(m->registry_fd >= 0); + + r = readdir_all(m->registry_fd, RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate registry."); + + for (size_t i = 0; i < de->n_entries; i++) { + struct dirent *dentry = de->entries[i]; + _cleanup_free_ char *u = NULL; + const char *e, *p; + uint64_t inode; + + p = startswith(dentry->d_name, "i"); + if (!p) + continue; + + e = endswith(p, ".userns"); + if (!e) + continue; + + u = strndup(p, e - p); + if (!u) + return log_oom(); + + r = safe_atou64(u, &inode); + if (r < 0) { + log_warning_errno(r, "Failed to parse userns inode number from '%s', skipping: %m", dentry->d_name); + continue; + } + + if (inode > UINT32_MAX) { /* namespace inode numbers are 23bit only right now */ + log_warning("userns inode number outside of 32bit range, skipping."); + continue; + } + + if (set_ensure_put(registry_inodes, NULL, UINT32_TO_PTR(inode)) < 0) + return log_oom(); + + log_debug("Found user namespace %" PRIu64 " in registry directory", inode); + } + + return 0; +} + +static int manager_make_listen_socket(Manager *m) { + static const union sockaddr_union sockaddr = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/io.systemd.NamespaceResource", + }; + int r; + + assert(m); + + if (m->listen_fd >= 0) + return 0; + + m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (m->listen_fd < 0) + return log_error_errno(errno, "Failed to bind on socket: %m"); + + (void) sockaddr_un_unlink(&sockaddr.un); + + WITH_UMASK(0000) + if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0) + return log_error_errno(errno, "Failed to bind socket: %m"); + + r = mkdir_p("/run/systemd/userdb", 0755); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/userdb: %m"); + + r = symlink_idempotent("../io.systemd.NamespaceResource", "/run/systemd/userdb/io.systemd.NamespaceResource", /* make_relative= */ false); + if (r < 0) + return log_error_errno(r, "Failed to symlink userdb socket: %m"); + + if (listen(m->listen_fd, SOMAXCONN) < 0) + return log_error_errno(errno, "Failed to listen on socket: %m"); + + return 1; +} + +static int manager_scan_listen_fds(Manager *m, Set **fdstore_inodes) { + _cleanup_strv_free_ char **names = NULL; + int n, r; + + assert(m); + assert(fdstore_inodes); + + n = sd_listen_fds_with_names(/* unset_environment= */ true, &names); + if (n < 0) + return log_error_errno(n, "Failed to determine number of passed file descriptors: %m"); + + for (int i = 0; i < n; i++) { + _cleanup_close_ int fd = SD_LISTEN_FDS_START + i; /* Take possession */ + const char *e; + + /* If this is a BPF allowlist related fd, just close it, but remember which start UIDs this covers */ + e = startswith(names[i], "userns-"); + if (e) { + uint64_t inode; + + r = safe_atou64(e, &inode); + if (r < 0) { + log_warning_errno(r, "Failed to parse UID from fd name '%s', ignoring: %m", e); + continue; + } + + if (inode > UINT32_MAX) { + log_warning("Inode number outside of 32bit range, ignoring"); + continue; + } + + if (set_ensure_put(fdstore_inodes, NULL, UINT32_TO_PTR(inode)) < 0) + return log_oom(); + + continue; + } + + /* We don't check the name for the stream socket, for compatibility with older versions */ + r = sd_is_socket(fd, AF_UNIX, SOCK_STREAM, 1); + if (r < 0) + return log_error_errno(r, "Failed to detect if passed file descriptor is a socket: %m"); + if (r > 0) { + if (m->listen_fd >= 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Passed more than one AF_UNIX/SOCK_STREAM socket, refusing."); + + m->listen_fd = TAKE_FD(fd); + continue; + } + + log_warning("Closing passed file descriptor %i (%s) we don't recognize.", fd, names[i]); + } + + return 0; +} + +#if HAVE_VMLINUX_H +static int ringbuf_event(void *userdata, void *data, size_t size) { + Manager *m = ASSERT_PTR(userdata); + size_t n; + + if ((size % sizeof(unsigned int)) != 0) /* Not multiples of "unsigned int"? */ + return -EIO; + + n = size / sizeof(unsigned int); + for (size_t i = 0; i < n; i++) { + const void *d; + uint64_t inode; + + d = (const uint8_t*) data + i * sizeof(unsigned int); + inode = unaligned_read_ne32(d); + + log_debug("Got BPF ring buffer notification that user namespace %" PRIu64 " is now dead.", inode); + manager_release_userns_by_inode(m, inode); + } + + return 0; +} + +static int on_ringbuf_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + r = sym_ring_buffer__poll(m->userns_restrict_bpf_ring_buffer, 0); + if (r < 0) + return log_error_errno(r, "Got failure reading from BPF ring buffer: %m"); + + return 0; +} + +static int manager_setup_bpf(Manager *m) { + int rb_fd = -EBADF, poll_fd = -EBADF, r; + + assert(m); + assert(!m->userns_restrict_bpf); + assert(!m->userns_restrict_bpf_ring_buffer); + assert(!m->userns_restrict_bpf_ring_buffer_event_source); + + r = userns_restrict_install(/* pin= */ true, &m->userns_restrict_bpf); + if (r < 0) { + log_notice_errno(r, "Proceeding with user namespace interfaces disabled."); + return 0; + } + + rb_fd = sym_bpf_map__fd(m->userns_restrict_bpf->maps.userns_ringbuf); + if (rb_fd < 0) + return log_error_errno(rb_fd, "Failed to get fd of ring buffer: %m"); + + m->userns_restrict_bpf_ring_buffer = sym_ring_buffer__new(rb_fd, ringbuf_event, m, NULL); + if (!m->userns_restrict_bpf_ring_buffer) + return log_error_errno(errno, "Failed to allocate BPF ring buffer object: %m"); + + poll_fd = sym_ring_buffer__epoll_fd(m->userns_restrict_bpf_ring_buffer); + if (poll_fd < 0) + return log_error_errno(poll_fd, "Failed to get poll fd of ring buffer: %m"); + + r = sd_event_add_io( + m->event, + &m->userns_restrict_bpf_ring_buffer_event_source, + poll_fd, + EPOLLIN, + on_ringbuf_io, + m); + if (r < 0) + return log_error_errno(r, "Failed to allocate event source for BPF ring buffer: %m"); + + return 0; +} +#else +static int manager_setup_bpf(Manager *m) { + log_notice("Not setting up BPF subsystem, as functionality has been disabled at compile time."); + return 0; +} +#endif + +int manager_startup(Manager *m) { + _cleanup_(set_freep) Set *fdstore_inodes = NULL, *registry_inodes = NULL; + void *p; + int r; + + assert(m); + assert(m->registry_fd < 0); + assert(m->listen_fd < 0); + + m->registry_fd = userns_registry_open_fd(); + if (m->registry_fd < 0) + return log_error_errno(m->registry_fd, "Failed to open registry directory: %m"); + + r = manager_setup_bpf(m); + if (r < 0) + return r; + + r = manager_scan_listen_fds(m, &fdstore_inodes); + if (r < 0) + return r; + + r = manager_scan_registry(m, ®istry_inodes); + if (r < 0) + return r; + + /* If there are resources tied to UIDs not found in the registry, then release them */ + SET_FOREACH(p, fdstore_inodes) { + uint64_t inode; + + if (set_contains(registry_inodes, p)) + continue; + + inode = PTR_TO_UINT32(p); + + log_debug("Found stale fd store entry for user namespace %" PRIu64 ", removing.", inode); + manager_release_userns_by_inode(m, inode); + } + + r = manager_make_listen_socket(m); + if (r < 0) + return r; + + /* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be + * GC'ed on idle */ + if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0) + return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m"); + + r = start_workers(m, /* explicit_request= */ false); + if (r < 0) + return r; + + return 0; +} diff --git a/src/nsresourced/nsresourced-manager.h b/src/nsresourced/nsresourced-manager.h new file mode 100644 index 0000000..5ecf378 --- /dev/null +++ b/src/nsresourced/nsresourced-manager.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-bus.h" +#include "sd-event.h" + +typedef struct Manager Manager; + +#include "hashmap.h" +#include "ratelimit.h" + +#define NSRESOURCE_WORKERS_MIN 5 +#define NSRESOURCE_WORKERS_MAX 4096 + +struct Manager { + sd_event *event; + + Set *workers_fixed; /* Workers 0…NSRESOURCE_WORKERS_MIN */ + Set *workers_dynamic; /* Workers NSRESOURCES_WORKERS_MIN+1…NSRESOURCES_WORKERS_MAX */ + + int listen_fd; + + RateLimit worker_ratelimit; + + sd_event_source *deferred_start_worker_event_source; + +#if HAVE_VMLINUX_H + struct userns_restrict_bpf *userns_restrict_bpf; + struct ring_buffer *userns_restrict_bpf_ring_buffer; + sd_event_source *userns_restrict_bpf_ring_buffer_event_source; +#endif + + int registry_fd; +}; + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +int manager_startup(Manager *m); diff --git a/src/nsresourced/nsresourced.c b/src/nsresourced/nsresourced.c new file mode 100644 index 0000000..7056897 --- /dev/null +++ b/src/nsresourced/nsresourced.c @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "daemon-util.h" +#include "nsresourced-manager.h" +#include "log.h" +#include "main-func.h" +#include "signal-util.h" + +static int run(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + log_setup(); + + umask(0022); + + if (argc != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + + if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NamespaceResource", 1) < 0) + return log_error_errno(errno, "Failed to set $SYSTEMD_BYPASS_USERDB: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); + + r = manager_new(&m); + if (r < 0) + return log_error_errno(r, "Could not create manager: %m"); + + r = manager_startup(m); + if (r < 0) + return log_error_errno(r, "Failed to start up daemon: %m"); + + _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL; + notify_stop = notify_start(NOTIFY_READY, NOTIFY_STOPPING); + + r = sd_event_loop(m->event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/nsresourced/nsresourcework.c b/src/nsresourced/nsresourcework.c new file mode 100644 index 0000000..6bd2fed --- /dev/null +++ b/src/nsresourced/nsresourcework.c @@ -0,0 +1,1782 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include +#include + +#include "sd-daemon.h" +#include "sd-netlink.h" + +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "group-record.h" +#include "io-util.h" +#include "lock-util.h" +#include "main-func.h" +#include "missing_magic.h" +#include "missing_mount.h" +#include "missing_syscall.h" +#include "mount-util.h" +#include "mountpoint-util.h" +#include "namespace-util.h" +#include "netlink-util.h" +#include "process-util.h" +#include "random-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "strv.h" +#include "time-util.h" +#include "uid-classification.h" +#include "uid-range.h" +#include "user-record-nss.h" +#include "user-record.h" +#include "user-util.h" +#include "userdb.h" +#include "userns-registry.h" +#include "userns-restrict.h" +#include "varlink-io.systemd.NamespaceResource.h" +#include "varlink-io.systemd.UserDatabase.h" +#include "varlink.h" + +#define ITERATIONS_MAX 64U +#define RUNTIME_MAX_USEC (5 * USEC_PER_MINUTE) +#define PRESSURE_SLEEP_TIME_USEC (50 * USEC_PER_MSEC) +#define CONNECTION_IDLE_USEC (15 * USEC_PER_SEC) +#define LISTEN_IDLE_USEC (90 * USEC_PER_SEC) +#define USERNS_PER_UID 256 + +typedef struct LookupParameters { + const char *user_name; + const char *group_name; + union { + uid_t uid; + gid_t gid; + }; + const char *service; +} LookupParameters; + +static int build_user_json(UserNamespaceInfo *userns_info, uid_t offset, JsonVariant **ret) { + _cleanup_free_ char *name = NULL, *realname = NULL; + UserDisposition disposition; + int r; + + assert(userns_info); + assert(offset < userns_info->size); + + if (asprintf(&name, "ns-%s-" UID_FMT, userns_info->name, offset) < 0) + return -ENOMEM; + + if (userns_info->size > 1) { + disposition = USER_CONTAINER; + r = asprintf(&realname, "User " UID_FMT " of Allocated Namespace %s", offset, userns_info->name); + } else { + disposition = USER_DYNAMIC; + r = asprintf(&realname, "Allocated Namespace %s", userns_info->name); + } + if (r < 0) + return -ENOMEM; + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(userns_info->start + offset)), + JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(GID_NOBODY)), + JSON_BUILD_PAIR("realName", JSON_BUILD_STRING(realname)), + JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_CONST_STRING("/")), + JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)), + JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)), + JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NamespaceResource")), + JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING(user_disposition_to_string(disposition))))); +} + +static int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, uid), 0 }, + { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + {} + }; + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + LookupParameters p = { + .uid = UID_INVALID, + }; + uid_t offset; + int r; + + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!streq_ptr(p.service, "io.systemd.NamespaceResource")) + return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); + + if (p.user_name) { + _cleanup_free_ char *n = NULL; + const char *e, *f; + + e = startswith(p.user_name, "ns-"); + if (!e) + goto not_found; + + f = strrchr(e, '-'); + if (!f) + goto not_found; + + if (parse_uid(f+1, &offset) < 0) + goto not_found; + + n = strndup(e, f - e); + if (!n) + return log_oom(); + + r = userns_registry_load_by_name( + /* registry_fd= */ -EBADF, + n, + &userns_info); + if (r == -ENOENT) + goto not_found; + if (r < 0) + return r; + + if (offset >= userns_info->size) /* Outside of range? */ + goto not_found; + + if (uid_is_valid(p.uid) && p.uid != userns_info->start + offset) + return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); + + } else if (uid_is_valid(p.uid)) { + uid_t start, uidmask; + + if (uid_is_container(p.uid)) + uidmask = (uid_t) UINT32_C(0xFFFF0000); + else if (uid_is_dynamic(p.uid)) + uidmask = (uid_t) UINT32_C(0xFFFFFFFF); + else + goto not_found; + + start = p.uid & uidmask; + offset = p.uid - start; + + r = userns_registry_load_by_start_uid( + /* registry_fd= */ -EBADF, + start, + &userns_info); + if (r == -ENOENT) + goto not_found; + if (r < 0) + return r; + + if (offset >= userns_info->size) /* Outside of range? */ + goto not_found; + } else + return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL); + + r = build_user_json(userns_info, offset, &v); + if (r < 0) + return r; + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)))); + +not_found: + return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); +} + +static int build_group_json(UserNamespaceInfo *userns_info, gid_t offset, JsonVariant **ret) { + _cleanup_free_ char *name = NULL, *description = NULL; + UserDisposition disposition; + int r; + + assert(userns_info); + assert(offset < userns_info->size); + + if (asprintf(&name, "ns-%s-" GID_FMT, userns_info->name, offset) < 0) + return -ENOMEM; + + if (userns_info->size > 1) { + disposition = USER_CONTAINER; + r = asprintf(&description, "Group " GID_FMT " of Allocated Namespace %s", offset, userns_info->name); + } else { + disposition = USER_DYNAMIC; + r = asprintf(&description, "Allocated Namespace %s", userns_info->name); + } + if (r < 0) + return -ENOMEM; + + return json_build(ret, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(userns_info->start + offset)), + JSON_BUILD_PAIR("description", JSON_BUILD_STRING(description)), + JSON_BUILD_PAIR("service", JSON_BUILD_CONST_STRING("io.systemd.NamespaceResource")), + JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING(user_disposition_to_string(disposition))))); +} + +static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(LookupParameters, gid), 0 }, + { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + {} + }; + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + LookupParameters p = { + .gid = GID_INVALID, + }; + gid_t offset; + int r; + + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!streq_ptr(p.service, "io.systemd.NamespaceResource")) + return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); + + if (p.group_name) { + _cleanup_free_ char *n = NULL; + const char *e, *f; + + e = startswith(p.group_name, "ns-"); + if (!e) + goto not_found; + + f = strrchr(e, '-'); + if (!f) + goto not_found; + + if (parse_gid(f+1, &offset) < 0) + goto not_found; + + n = strndup(e, f - e); + if (!n) + return log_oom(); + + r = userns_registry_load_by_name( + /* registry_fd= */ -EBADF, + n, + &userns_info); + if (r == -ENOENT) + goto not_found; + if (r < 0) + return r; + + if (offset >= userns_info->size) /* Outside of range? */ + goto not_found; + + if (gid_is_valid(p.gid) && p.uid != userns_info->start + offset) + return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL); + + } else if (gid_is_valid(p.gid)) { + gid_t start, gidmask; + + if (gid_is_container(p.gid)) + gidmask = (gid_t) UINT32_C(0xFFFF0000); + else if (gid_is_dynamic(p.gid)) + gidmask = (gid_t) UINT32_C(0xFFFFFFFF); + else + goto not_found; + + start = p.gid & gidmask; + offset = p.gid - start; + + r = userns_registry_load_by_start_uid( + /* registry_fd= */ -EBADF, + (uid_t) start, + &userns_info); + if (r == -ENOENT) + goto not_found; + if (r < 0) + return r; + + if (offset >= userns_info->size) /* Outside of range? */ + goto not_found; + } else + return varlink_error(link, "io.systemd.UserDatabase.EnumerationNotSupported", NULL); + + r = build_group_json(userns_info, offset, &v); + if (r < 0) + return r; + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(v)))); + +not_found: + return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); +} + +static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 }, + { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + {} + }; + + LookupParameters p = {}; + int r; + + assert(parameters); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (!streq_ptr(p.service, "io.systemd.NamespaceResource")) + return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL); + + /* We don't support auxiliary groups for namespace allocations */ + return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); +} + +static int uid_is_available( + int registry_dir_fd, + uid_t candidate) { + + int r; + + assert(registry_dir_fd >= 0); + + log_debug("Checking if UID " UID_FMT " is available.", candidate); + + r = userns_registry_uid_exists(registry_dir_fd, candidate); + if (r < 0) + return r; + if (r > 0) + return false; + + r = userdb_by_uid(candidate, USERDB_AVOID_MULTIPLEXER, NULL); + if (r >= 0) + return false; + if (r != -ESRCH) + return r; + + r = groupdb_by_gid(candidate, USERDB_AVOID_MULTIPLEXER, NULL); + if (r >= 0) + return false; + if (r != -ESRCH) + return r; + + log_debug("UID " UID_FMT " is available.", candidate); + + return true; +} + +static int name_is_available( + int registry_dir_fd, + const char *name) { + + _cleanup_free_ char *user_name = NULL; + int r; + + assert(registry_dir_fd >= 0); + assert(name); + + r = userns_registry_name_exists(registry_dir_fd, name); + if (r < 0) + return r; + if (r > 0) + return false; + + user_name = strjoin("ns-", name, "-0"); + if (!user_name) + return -ENOMEM; + + r = userdb_by_name(user_name, USERDB_AVOID_MULTIPLEXER, NULL); + if (r >= 0) + return false; + if (r != -ESRCH) + return r; + + r = groupdb_by_name(user_name, USERDB_AVOID_MULTIPLEXER, NULL); + if (r >= 0) + return false; + if (r != -ESRCH) + return r; + + log_debug("Namespace name '%s' is available.", name); + + return true; +} + +static int allocate_now( + int registry_dir_fd, + UserNamespaceInfo *info, + int *ret_lock_fd) { + + static const uint8_t hash_key[16] = { + 0xd4, 0xd7, 0x33, 0xa7, 0x4d, 0xd3, 0x42, 0xcd, + 0xaa, 0xe9, 0x45, 0xd0, 0xfb, 0xec, 0x79, 0xee, + }; + + _cleanup_(uid_range_freep) UIDRange *valid_range = NULL; + uid_t candidate, uidmin, uidmax, uidmask; + unsigned n_tries = 100; + int r; + + /* Returns the following error codes: + * + * EBUSY → all UID candidates we checked are already taken + * EEXIST → the name for the userns already exists + * EDEADLK → the userns is already registered in the registry + */ + + assert(registry_dir_fd >= 0); + assert(info); + + switch (info->size) { + + case 0x10000U: + uidmin = CONTAINER_UID_BASE_MIN; + uidmax = CONTAINER_UID_BASE_MAX; + uidmask = (uid_t) UINT32_C(0xFFFF0000); + break; + + case 1U: + uidmin = DYNAMIC_UID_MIN; + uidmax = DYNAMIC_UID_MAX; + uidmask = (uid_t) UINT32_C(0xFFFFFFFF); + break; + + default: + assert_not_reached(); + } + + r = uid_range_load_userns(/* path= */ NULL, UID_RANGE_USERNS_INSIDE, &valid_range); + if (r < 0) + return r; + + /* Check early whether we have any chance at all given our own uid range */ + if (!uid_range_overlaps(valid_range, uidmin, uidmax)) + return log_debug_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Relevant UID range not delegated, can't allocate."); + + _cleanup_close_ int lock_fd = -EBADF; + lock_fd = userns_registry_lock(registry_dir_fd); + if (lock_fd < 0) + return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); + + /* Enforce limit on user namespaces per UID */ + r = userns_registry_per_uid(registry_dir_fd, info->owner); + if (r < 0) + return log_debug_errno(r, "Failed to determine number of currently registered user namespaces per UID " UID_FMT ": %m", info->owner); + if (r >= USERNS_PER_UID) + return log_debug_errno(SYNTHETIC_ERRNO(EUSERS), "User already registered %i user namespaces, refusing.", r); + + r = userns_registry_inode_exists(registry_dir_fd, info->userns_inode); + if (r < 0) + return r; + if (r > 0) + return -EDEADLK; + + r = name_is_available(registry_dir_fd, info->name); + if (r < 0) + return r; + if (r == 0) + return -EEXIST; + + for (candidate = siphash24_string(info->name, hash_key) & UINT32_MAX;; /* Start from a hash of the input name */ + candidate = random_u32()) { /* Use random values afterwards */ + + if (--n_tries <= 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Try limit hit, no UIDs available."); + + candidate = (candidate % (uidmax - uidmin)) + uidmin; + candidate &= uidmask; + + if (!uid_range_covers(valid_range, candidate, info->size)) + continue; + + /* We only check the base UID for each range (!) */ + r = uid_is_available(registry_dir_fd, candidate); + if (r < 0) + return log_debug_errno(r, "Can't determine if UID range " UID_FMT " is available: %m", candidate); + if (r > 0) { + info->start = candidate; + + log_debug("Allocating UID range " UID_FMT "…" UID_FMT, candidate, candidate + info->size - 1); + + if (ret_lock_fd) + *ret_lock_fd = TAKE_FD(lock_fd); + + return 0; + } + + log_debug("UID range " UID_FMT " already taken.", candidate); + } +} + +static int write_userns(int usernsfd, const UserNamespaceInfo *userns_info) { + _cleanup_(sigkill_waitp) pid_t pid = 0; + _cleanup_close_ int efd = -EBADF; + uint64_t u; + int r; + + assert(usernsfd >= 0); + assert(userns_info); + assert(uid_is_valid(userns_info->target)); + assert(uid_is_valid(userns_info->start)); + assert(userns_info->size > 0); + assert(userns_info->size <= UINT32_MAX - userns_info->start); + + efd = eventfd(0, EFD_CLOEXEC); + if (efd < 0) + return log_error_errno(errno, "Failed to allocate eventfd(): %m"); + + r = safe_fork("(sd-userns)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG, &pid); + if (r < 0) + return r; + if (r == 0) { + /* child */ + + if (setns(usernsfd, CLONE_NEWUSER) < 0) { + log_error_errno(errno, "Failed to join user namespace: %m"); + goto child_fail; + } + + if (eventfd_write(efd, 1) < 0) { + log_error_errno(errno, "Failed to ping event fd: %m"); + goto child_fail; + } + + freeze(); + + child_fail: + _exit(EXIT_FAILURE); + } + + /* Wait until child joined the user namespace */ + if (eventfd_read(efd, &u) < 0) + return log_error_errno(errno, "Failed to wait for event fd: %m"); + + /* Now write mapping */ + + _cleanup_free_ char *pmap = NULL; + + if (asprintf(&pmap, "/proc/" PID_FMT "/uid_map", pid) < 0) + return log_oom(); + + r = write_string_filef(pmap, 0, UID_FMT " " UID_FMT " " UID_FMT "\n", userns_info->target, userns_info->start, userns_info->size); + if (r < 0) + return log_error_errno(r, "Failed to write 'uid_map' file of user namespace: %m"); + + pmap = mfree(pmap); + if (asprintf(&pmap, "/proc/" PID_FMT "/gid_map", pid) < 0) + return log_oom(); + + r = write_string_filef(pmap, 0, GID_FMT " " GID_FMT " " GID_FMT "\n", (gid_t) userns_info->target, (gid_t) userns_info->start, (gid_t) userns_info->size); + if (r < 0) + return log_error_errno(r, "Failed to write 'gid_map' file of user namespace: %m"); + + /* We are done! */ + + log_debug("Successfully configured user namespace."); + return 0; +} + +static int test_userns_api_support(Varlink *link) { + int r; + + assert(link); + + /* We only expose the userns API if our manager daemon told us this OK to do. It will set this + * boolean only if it managed to set up BPF correctly for itself (i.e. watches for userns going away + * via BPF APIs). This should make very sure we don't accidentally allow any of the userns stuff to + * go through without the BPF LSM in effect. */ + + r = getenv_bool("NSRESOURCE_API"); + if (r < 0) + return log_error_errno(r, "Failed to parse $NSRESOURCE_API: %m"); + if (r == 0) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceInterfaceNotSupported", NULL); + + return 0; +} + +static int validate_name(Varlink *link, const char *name, char **ret) { + _cleanup_free_ char *un = NULL; + int r; + + assert(link); + assert(name); + assert(ret); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (peer_uid == 0) { + if (!userns_name_is_valid(name)) + return varlink_error_invalid_parameter_name(link, "name"); + + un = strdup(name); + if (!un) + return -ENOMEM; + } else { + /* The the client is not root then prefix the name with the UID of the peer, so that they + * live in separate namespaces and cannot steal each other's names. */ + + if (asprintf(&un, UID_FMT "-%s", peer_uid, name) < 0) + return -ENOMEM; + + if (!userns_name_is_valid(un)) + return varlink_error_invalid_parameter_name(link, "name"); + } + + *ret = TAKE_PTR(un); + return 0; +} + +static int validate_target_and_size(Varlink *link, unsigned target, unsigned size) { + assert(link); + + if (!IN_SET(size, 1U, 0x10000)) + return varlink_error_invalid_parameter_name(link, "size"); + + if (!uid_is_valid(target) || target > UINT32_MAX - size) + return varlink_error_invalid_parameter_name(link, "target"); + + return 0; +} + +static int validate_userns(Varlink *link, int userns_fd) { + int r; + + assert(link); + assert(userns_fd >= 0); + + r = fd_verify_safe_flags(userns_fd); + if (r < 0) + return log_debug_errno(r, "User namespace file descriptor has unsafe flags set: %m"); + + /* Validate this is actually a valid user namespace fd */ + r = fd_is_ns(userns_fd, CLONE_NEWUSER); + if (r < 0) + return log_debug_errno(r, "Failed to check if user namespace fd is actually a user namespace: %m"); + if (r == 0) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + /* And refuse the thing if it is our own */ + r = is_our_namespace(userns_fd, NAMESPACE_USER); + if (r < 0) + return log_debug_errno(r, "Failed to check if user namespace fd refers to our own user namespace: %m"); + if (r > 0) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return log_debug_errno(r, "Failed to acquire peer UID: %m"); + + if (peer_uid != 0) { + /* Refuse if the userns is not actually owned by our client. */ + uid_t owner_uid; + if (ioctl(userns_fd, NS_GET_OWNER_UID, &owner_uid) < 0) + return log_debug_errno(errno, "Failed to get owner UID of user namespace: %m"); + + if (owner_uid != peer_uid) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + } + + return 0; +} + +static int validate_userns_is_empty(Varlink *link, int userns_fd) { + int r; + + assert(link); + assert(userns_fd >= 0); + + _cleanup_(uid_range_freep) UIDRange *range = NULL; + r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &range); + if (r < 0) + return log_debug_errno(r, "Failed to read userns UID range: %m"); + + if (!uid_range_is_empty(range)) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + range = uid_range_free(range); + r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &range); + if (r < 0) + return log_debug_errno(r, "Failed to read userns GID range: %m"); + + if (!uid_range_is_empty(range)) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + return 0; +} + +typedef struct AllocateParameters { + const char *name; + unsigned size; + unsigned target; + unsigned userns_fd_idx; +} AllocateParameters; + +static int vl_method_allocate_user_range(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(AllocateParameters, name), JSON_MANDATORY }, + { "size", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AllocateParameters, size), JSON_MANDATORY }, + { "target", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AllocateParameters, target), 0 }, + { "userNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AllocateParameters, userns_fd_idx), JSON_MANDATORY }, + {} + }; + + struct userns_restrict_bpf **bpf = ASSERT_PTR(userdata); + _cleanup_close_ int userns_fd = -EBADF, registry_dir_fd = -EBADF, lock_fd = -EBADF; + _cleanup_free_ char *userns_name = NULL; + uid_t peer_uid; + struct stat userns_st; + AllocateParameters p = { + .size = UINT_MAX, + .userns_fd_idx = UINT_MAX, + }; + int r; + + assert(link); + assert(parameters); + + r = test_userns_api_support(link); + if (r != 0) + return r; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = validate_name(link, p.name, &userns_name); + if (r != 0) + return r; + + r = validate_target_and_size(link, p.target, p.size); + if (r != 0) + return r; + + userns_fd = varlink_take_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to take user namespace fd from Varlink connection: %m"); + + r = validate_userns(link, userns_fd); + if (r != 0) + return r; + + r = validate_userns_is_empty(link, userns_fd); + if (r != 0) + return r; + + if (fstat(userns_fd, &userns_st) < 0) + return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m"); + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (!*bpf) { + r = userns_restrict_install(/* pin= */ true, bpf); + if (r < 0) + return r; + } + + registry_dir_fd = userns_registry_open_fd(); + if (registry_dir_fd < 0) + return registry_dir_fd; + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = userns_info_new(); + if (!userns_info) + return -ENOMEM; + + userns_info->name = TAKE_PTR(userns_name); + if (!userns_info->name) + return -ENOMEM; + + userns_info->owner = peer_uid; + userns_info->userns_inode = userns_st.st_ino; + userns_info->size = p.size; + userns_info->target = p.target; + + r = allocate_now(registry_dir_fd, userns_info, &lock_fd); + if (r == -EHOSTDOWN) /* The needed UID range is not delegated to us */ + return varlink_error(link, "io.systemd.NamespaceResource.DynamicRangeUnavailable", NULL); + if (r == -EBUSY) /* All used up */ + return varlink_error(link, "io.systemd.NamespaceResource.NoDynamicRange", NULL); + if (r == -EDEADLK) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL); + if (r == -EEXIST) + return varlink_error(link, "io.systemd.NamespaceResource.NameExists", NULL); + if (r < 0) + return r; + + r = userns_registry_store(registry_dir_fd, userns_info); + if (r < 0) + return r; + + /* Register the userns in the BPF map with an empty allowlist */ + r = userns_restrict_put_by_fd( + *bpf, + userns_fd, + /* replace= */ true, + /* mount_fds= */ NULL, + /* n_mount_fds= */ 0); + if (r < 0) + goto fail; + + r = write_userns(userns_fd, userns_info); + if (r < 0) + goto fail; + + lock_fd = safe_close(lock_fd); + + /* Send user namespace and process fd to our manager process, which will watch the process and user namespace */ + r = sd_pid_notifyf_with_fds( + /* pid= */ 0, + /* unset_environment= */ false, + &userns_fd, 1, + "FDSTORE=1\n" + "FDNAME=userns-" INO_FMT "\n", userns_info->userns_inode); + if (r < 0) + goto fail; + + /* Note, we'll not return UID values from the host, since the child might not run in the same + * user namespace as us. If they want to know the ranges they should read them off the userns fd, so + * that they are translated into their PoV */ + return varlink_replyb(link, JSON_BUILD_EMPTY_OBJECT); + +fail: + /* Note: we don't have to clean-up the BPF maps in the error path: the bpf map type used will + * automatically do that once the userns inode goes away */ + userns_registry_remove(registry_dir_fd, userns_info); + return r; +} + +static int validate_userns_is_safe(Varlink *link, int userns_fd) { + int r; + + assert(link); + assert(userns_fd >= 0); + + /* Read the outside UID range and verify it isn't empty */ + _cleanup_(uid_range_freep) UIDRange *outside_range = NULL; + r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_OUTSIDE, &outside_range); + if (r < 0) + return log_debug_errno(r, "Failed to read userns UID range: %m"); + if (uid_range_is_empty(outside_range)) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + /* Read the outside GID range and check it is the same as the UID range */ + _cleanup_(uid_range_freep) UIDRange *outside_range_gid = NULL; + r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_OUTSIDE, &outside_range_gid); + if (r < 0) + return log_debug_errno(r, "Failed to read userns GID range: %m"); + if (!uid_range_equal(outside_range, outside_range_gid)) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + /* Read the inside UID range, and verify it matches the size of the outside UID range */ + _cleanup_(uid_range_freep) UIDRange *inside_range = NULL; + r = uid_range_load_userns_by_fd(userns_fd, UID_RANGE_USERNS_INSIDE, &inside_range); + if (r < 0) + return log_debug_errno(r, "Failed to read userns contents: %m"); + if (uid_range_size(outside_range) != uid_range_size(inside_range)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Uh, inside and outside UID range sizes don't match."); + + /* Read the inside GID range, and verify it matches the inside UID range */ + _cleanup_(uid_range_freep) UIDRange *inside_range_gid = NULL; + r = uid_range_load_userns_by_fd(userns_fd, GID_RANGE_USERNS_INSIDE, &inside_range_gid); + if (r < 0) + return log_debug_errno(r, "Failed to read userns contents: %m"); + if (!uid_range_equal(inside_range, inside_range_gid)) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + uid_t peer_gid; + r = varlink_get_peer_gid(link, &peer_gid); + if (r < 0) + return r; + + /* Insist that the first UID/GID in the range matches the client's UID/GID */ + if (outside_range->entries[0].start != peer_uid || + outside_range_gid->entries[0].start != peer_gid) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + /* If there are more than one UID in the range, then also insist that the first UID maps to root inside the userns */ + if (uid_range_size(outside_range) > 1 && inside_range->entries[0].start != 0) + return varlink_error_invalid_parameter_name(link, "userNamespaceFileDescriptor"); + + return 0; +} + +typedef struct RegisterParameters { + const char *name; + unsigned userns_fd_idx; +} RegisterParameters; + +static int vl_method_register_user_namespace(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(RegisterParameters, name), JSON_MANDATORY }, + { "userNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(RegisterParameters, userns_fd_idx), JSON_MANDATORY }, + {} + }; + + struct userns_restrict_bpf **bpf = ASSERT_PTR(userdata); + _cleanup_close_ int userns_fd = -EBADF, registry_dir_fd = -EBADF; + _cleanup_free_ char *userns_name = NULL; + uid_t peer_uid; + struct stat userns_st; + RegisterParameters p = { + .userns_fd_idx = UINT_MAX, + }; + int r; + + assert(link); + assert(parameters); + + r = test_userns_api_support(link); + if (r != 0) + return r; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = validate_name(link, p.name, &userns_name); + if (r != 0) + return r; + + userns_fd = varlink_take_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return userns_fd; + + r = validate_userns(link, userns_fd); + if (r != 0) + return r; + + r = validate_userns_is_safe(link, userns_fd); + if (r != 0) + return r; + + if (fstat(userns_fd, &userns_st) < 0) + return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m"); + + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (!*bpf) { + r = userns_restrict_install(/* pin= */ true, bpf); + if (r < 0) + return r; + } + + registry_dir_fd = userns_registry_open_fd(); + if (registry_dir_fd < 0) + return registry_dir_fd; + + _cleanup_close_ int lock_fd = -EBADF; + lock_fd = userns_registry_lock(registry_dir_fd); + if (lock_fd < 0) + return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); + + r = userns_registry_inode_exists(registry_dir_fd, userns_st.st_ino); + if (r < 0) + return r; + if (r > 0) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceExists", NULL); + + r = name_is_available(registry_dir_fd, userns_name); + if (r < 0) + return r; + if (r == 0) + return varlink_error(link, "io.systemd.NamespaceResource.NameExists", NULL); + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = userns_info_new(); + if (!userns_info) + return -ENOMEM; + + userns_info->name = TAKE_PTR(userns_name); + if (!userns_info->name) + return -ENOMEM; + + userns_info->owner = peer_uid; + userns_info->userns_inode = userns_st.st_ino; + + r = userns_registry_store(registry_dir_fd, userns_info); + if (r < 0) + return log_debug_errno(r, "Failed to update userns registry: %m"); + + /* Register the userns in the BPF map with an empty allowlist */ + r = userns_restrict_put_by_fd( + *bpf, + userns_fd, + /* replace= */ true, + /* mount_fds= */ NULL, + /* n_mount_fds= */ 0); + if (r < 0) + goto fail; + + /* Send user namespace and process fd to our manager process, which will watch the process and user namespace */ + r = sd_pid_notifyf_with_fds( + /* pid= */ 0, + /* unset_environment= */ false, + &userns_fd, 1, + "FDSTORE=1\n" + "FDNAME=userns-" INO_FMT "\n", userns_info->userns_inode); + if (r < 0) + goto fail; + + return varlink_replyb(link, JSON_BUILD_EMPTY_OBJECT); + +fail: + userns_registry_remove(registry_dir_fd, userns_info); + return r; +} + +typedef struct AddMountParameters { + unsigned userns_fd_idx; + unsigned mount_fd_idx; +} AddMountParameters; + +static int vl_method_add_mount_to_user_namespace(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + + static const JsonDispatch parameter_dispatch_table[] = { + { "userNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddMountParameters, userns_fd_idx), JSON_MANDATORY }, + { "mountFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddMountParameters, mount_fd_idx), JSON_MANDATORY }, + {} + }; + + _cleanup_close_ int userns_fd = -EBADF, mount_fd = -EBADF, registry_dir_fd = -EBADF; + struct userns_restrict_bpf **bpf = ASSERT_PTR(userdata); + AddMountParameters p = { + .userns_fd_idx = UINT_MAX, + .mount_fd_idx = UINT_MAX, + }; + int r, mnt_id = 0; + struct stat userns_st; + uid_t peer_uid; + + assert(link); + assert(parameters); + + r = test_userns_api_support(link); + if (r != 0) + return r; + + /* Allowlisting arbitrary mounts is a privileged operation */ + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + if (peer_uid != 0) + return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + + r = varlink_dispatch(link, parameters, parameter_dispatch_table, &p); + if (r != 0) + return r; + + userns_fd = varlink_take_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return userns_fd; + + r = validate_userns(link, userns_fd); + if (r != 0) + return r; + + if (fstat(userns_fd, &userns_st) < 0) + return -errno; + + mount_fd = varlink_take_fd(link, p.mount_fd_idx); + if (mount_fd < 0) + return mount_fd; + + r = fd_verify_safe_flags_full(mount_fd, O_PATH|O_DIRECTORY); + if (r < 0) + return log_debug_errno(r, "Mount file descriptor has unsafe flags set: %m"); + + r = fd_verify_directory(mount_fd); + if (r < 0) + return r; + + r = path_get_mnt_id_at(mount_fd, NULL, &mnt_id); + if (r < 0) + return r; + + registry_dir_fd = userns_registry_open_fd(); + if (registry_dir_fd < 0) + return registry_dir_fd; + + _cleanup_close_ int lock_fd = -EBADF; + lock_fd = userns_registry_lock(registry_dir_fd); + if (lock_fd < 0) + return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + r = userns_registry_load_by_userns_inode( + registry_dir_fd, + userns_st.st_ino, + &userns_info); + if (r == -ENOENT) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return r; + + if (!*bpf) { + r = userns_restrict_install(/* pin= */ true, bpf); + if (r < 0) + return r; + } + + /* Pin the mount fd */ + r = sd_pid_notifyf_with_fds( + /* pid= */ 0, + /* unset_environment= */ false, + &mount_fd, 1, + "FDSTORE=1\n" + "FDNAME=userns-" INO_FMT "\n", userns_st.st_ino); + if (r < 0) + return r; + + /* Add this mount to the user namespace's BPF map allowlist entry. */ + r = userns_restrict_put_by_fd( + *bpf, + userns_fd, + /* replace= */ false, + &mount_fd, + 1); + if (r < 0) + return r; + + if (userns_info->size > 0) + log_debug("Granting access to mount %i to user namespace " INO_FMT " ('%s' @ UID " UID_FMT ")", + mnt_id, userns_st.st_ino, userns_info->name, userns_info->start); + else + log_debug("Granting access to mount %i to user namespace " INO_FMT " ('%s')", + mnt_id, userns_st.st_ino, userns_info->name); + + return varlink_replyb(link, JSON_BUILD_EMPTY_OBJECT); +} + +static int validate_cgroup(Varlink *link, int fd, uint64_t *ret_cgroup_id) { + int r; + + assert(link); + assert(fd >= 0); + assert(ret_cgroup_id); + + r = fd_verify_safe_flags_full(fd, O_DIRECTORY); + if (r < 0) + return log_debug_errno(r, "Control group file descriptor has unsafe flags set: %m"); + + r = fd_verify_directory(fd); + if (r < 0) + return log_debug_errno(r, "Verification that cgroup fd refers to directory failed: %m"); + + r = fd_is_fs_type(fd, CGROUP2_SUPER_MAGIC); + if (r < 0) + return log_debug_errno(r, "Failed to check if cgroup fd actually refers to cgroupfs: %m"); + if (r == 0) + return varlink_error_invalid_parameter_name(link, "controlGroupFileDescriptor"); + + r = cg_fd_get_cgroupid(fd, ret_cgroup_id); + if (r < 0) + return log_debug_errno(r, "Failed to read cgroup ID from cgroupfs: %m"); + + return 0; +} + +typedef struct AddCGroupParameters { + unsigned userns_fd_idx; + unsigned cgroup_fd_idx; +} AddCGroupParameters; + +static int vl_method_add_cgroup_to_user_namespace(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch parameter_dispatch_table[] = { + { "userNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddCGroupParameters, userns_fd_idx), JSON_MANDATORY }, + { "controlGroupFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddCGroupParameters, cgroup_fd_idx), JSON_MANDATORY }, + {} + }; + + _cleanup_close_ int userns_fd = -EBADF, cgroup_fd = -EBADF, registry_dir_fd = -EBADF; + AddCGroupParameters p = { + .userns_fd_idx = UINT_MAX, + .cgroup_fd_idx = UINT_MAX, + }; + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + struct stat userns_st, cgroup_st; + uid_t peer_uid; + int r; + + assert(link); + assert(parameters); + + r = test_userns_api_support(link); + if (r != 0) + return r; + + r = varlink_dispatch(link, parameters, parameter_dispatch_table, &p); + if (r != 0) + return r; + + userns_fd = varlink_take_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to take user namespace fd from Varlink connection: %m"); + + r = validate_userns(link, userns_fd); + if (r != 0) + return r; + + if (fstat(userns_fd, &userns_st) < 0) + return log_debug_errno(errno, "Failed to fstat() user namespace fd: %m"); + + cgroup_fd = varlink_take_fd(link, p.cgroup_fd_idx); + if (cgroup_fd < 0) + return log_debug_errno(cgroup_fd, "Failed to take cgroup fd from Varlink connection: %m"); + + uint64_t cgroup_id; + r = validate_cgroup(link, cgroup_fd, &cgroup_id); + if (r != 0) + return r; + + if (fstat(cgroup_fd, &cgroup_st) < 0) + return log_debug_errno(errno, "Failed to fstat() cgroup fd: %m"); + + registry_dir_fd = userns_registry_open_fd(); + if (registry_dir_fd < 0) + return registry_dir_fd; + + _cleanup_close_ int lock_fd = -EBADF; + lock_fd = userns_registry_lock(registry_dir_fd); + if (lock_fd < 0) + return lock_fd; + + r = userns_registry_load_by_userns_inode( + registry_dir_fd, + userns_st.st_ino, + &userns_info); + if (r == -ENOENT) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return r; + + /* The user namespace must have a user assigned */ + if (userns_info->size == 0) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceWithoutUserRange", NULL); + if (userns_info_has_cgroup(userns_info, cgroup_id)) + return varlink_error(link, "io.systemd.NamespaceResource.ControlGroupAlreadyAdded", NULL); + if (userns_info->n_cgroups > USER_NAMESPACE_CGROUPS_DELEGATE_MAX) + return varlink_error(link, "io.systemd.NamespaceResource.TooManyControlGroups", NULL); + + /* Registering a cgroup for this client is only allowed for the root or the owner of a userns */ + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return log_debug_errno(r, "Failed to get connection peer: %m"); + if (peer_uid != 0) { + if (peer_uid != userns_info->owner) + return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + + /* The cgroup must be owned by the owner of the userns */ + if (cgroup_st.st_uid != userns_info->owner) + return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + } + + r = userns_info_add_cgroup(userns_info, cgroup_id); + if (r < 0) + return r; + + r = userns_registry_store(registry_dir_fd, userns_info); + if (r < 0) + return r; + + if (fchown(cgroup_fd, userns_info->start, userns_info->start) < 0) + return log_debug_errno(errno, "Failed to change ownership of cgroup: %m"); + + if (fchmod(cgroup_fd, 0755) < 0) + return log_debug_errno(errno, "Failed to change access mode of cgroup: %m"); + + FOREACH_STRING(attr, "cgroup.procs", "cgroup.subtree_control", "cgroup.threads") { + (void) fchmodat(cgroup_fd, attr, 0644, AT_SYMLINK_NOFOLLOW); + (void) fchownat(cgroup_fd, attr, userns_info->start, userns_info->start, AT_SYMLINK_NOFOLLOW); + } + + log_debug("Granting ownership to cgroup %" PRIu64 " to userns " INO_FMT " ('%s' @ UID " UID_FMT ")", + cgroup_id, userns_st.st_ino, userns_info->name, userns_info->start); + + return varlink_replyb(link, JSON_BUILD_EMPTY_OBJECT); +} + +static uint64_t hash_ifname_id(UserNamespaceInfo *userns_info, const char *ifname) { + struct siphash state; + + assert(userns_info); + + siphash24_init(&state, (const uint8_t[]) { 0xc4, 0x6c, 0x96, 0xe8, 0xad, 0x37, 0x4d, 0x5f, 0xa1, 0xae, 0xfe, 0x70, 0x40, 0xed, 0x41, 0x5f }); + siphash24_compress_string(userns_info->name, &state); + siphash24_compress_byte(0, &state); /* separator */ + siphash24_compress_string(strempty(ifname), &state); + + return siphash24_finalize(&state); +} + +static void hash_ether_addr(UserNamespaceInfo *userns_info, const char *ifname, uint64_t n, struct ether_addr *ret) { + struct siphash state; + uint64_t h; + + assert(userns_info); + assert(ret); + + siphash24_init(&state, (const uint8_t[]) { 0x36, 0xaa, 0xd1, 0x69, 0xc7, 0xe5, 0x4c, 0xaa, 0x1e, 0xb2, 0x9e, 0xb3, 0x3a, 0x6b, 0xd4, 0x71 }); + siphash24_compress_string(userns_info->name, &state); + siphash24_compress_byte(0, &state); /* separator */ + siphash24_compress_string(strempty(ifname), &state); + siphash24_compress_byte(0, &state); /* separator */ + n = htole64(n); /* add the 'index' to the mix in an endianess-independent fashion */ + siphash24_compress(&n, sizeof(n), &state); + + h = htole64(siphash24_finalize(&state)); + + assert(sizeof(h) >= sizeof_field(struct ether_addr, ether_addr_octet)); + + memcpy(ret->ether_addr_octet, &h, sizeof_field(struct ether_addr, ether_addr_octet)); + ether_addr_mark_random(ret); +} + +static int create_veth( + int netns_fd, + const char *ifname_host, + const char *altifname_host, + struct ether_addr *mac_host, + const char *ifname_namespace, + struct ether_addr *mac_namespace) { + + int r; + + assert(netns_fd >= 0); + assert(ifname_host); + assert(mac_host); + assert(ifname_namespace); + assert(mac_namespace); + + log_debug("Creating veth link on host %s (%s) with address %s to container as %s with address %s", + ifname_host, strna(altifname_host), ETHER_ADDR_TO_STR(mac_host), + ifname_namespace, ETHER_ADDR_TO_STR(mac_namespace)); + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container(m, VETH_INFO_PEER); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_namespace); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_namespace); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_FD, netns_fd); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_namespace); + + r = rtnl_set_link_alternative_names_by_ifname(&rtnl, ifname_host, STRV_MAKE(altifname_host)); + if (r < 0) + log_warning_errno(r, "Failed to set alternative interface name to '%s', ignoring: %m", altifname_host); + + return 0; +} + +static int validate_netns(Varlink *link, int userns_fd, int netns_fd) { + int r; + + assert(link); + assert(userns_fd >= 0); + assert(netns_fd >= 0); + + r = fd_verify_safe_flags(netns_fd); + if (r < 0) + return log_debug_errno(r, "Network namespace file descriptor has unsafe flags set: %m"); + + /* Validate this is actually a valid network namespace fd */ + r = fd_is_ns(netns_fd, CLONE_NEWNET); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor"); + + /* And refuse the thing if it is our own */ + r = is_our_namespace(netns_fd, NAMESPACE_NET); + if (r < 0) + return r; + if (r > 0) + return varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor"); + + /* Check if the netns actually belongs to the userns */ + _cleanup_close_ int owner_userns_fd = -EBADF; + owner_userns_fd = ioctl(netns_fd, NS_GET_USERNS); + if (owner_userns_fd < 0) + return -errno; + + r = inode_same_at(owner_userns_fd, /* path_a= */ NULL, userns_fd, /* path_b= */ NULL, AT_EMPTY_PATH); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor"); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + if (peer_uid != 0) { + /* Refuse if the netns is not actually owned by our client. */ + + uid_t owner_uid; + if (ioctl(owner_userns_fd, NS_GET_OWNER_UID, &owner_uid) < 0) + return -errno; + + if (owner_uid != peer_uid) + return varlink_error_invalid_parameter_name(link, "networkNamespaceFileDescriptor"); + } + + return 0; +} + +typedef struct AddNetworkParameters { + unsigned userns_fd_idx; + unsigned netns_fd_idx; + const char *ifname; + const char *mode; +} AddNetworkParameters; + +static int vl_method_add_netif_to_user_namespace(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch parameter_dispatch_table[] = { + { "userNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddNetworkParameters, userns_fd_idx), JSON_MANDATORY }, + { "networkNamespaceFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(AddNetworkParameters, netns_fd_idx), JSON_MANDATORY }, + { "namespaceInterfaceName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(AddNetworkParameters, ifname), 0 }, + { "mode", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(AddNetworkParameters, mode), JSON_MANDATORY }, + {} + }; + + _cleanup_close_ int userns_fd = -EBADF, netns_fd = -EBADF, registry_dir_fd = -EBADF; + AddNetworkParameters p = { + .userns_fd_idx = UINT_MAX, + }; + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + struct stat userns_st; + uid_t peer_uid; + int r; + + assert(link); + assert(parameters); + + r = test_userns_api_support(link); + if (r != 0) + return r; + + r = varlink_dispatch(link, parameters, parameter_dispatch_table, &p); + if (r != 0) + return r; + + userns_fd = varlink_take_fd(link, p.userns_fd_idx); + if (userns_fd < 0) + return userns_fd; + + r = validate_userns(link, userns_fd); + if (r != 0) + return r; + + if (fstat(userns_fd, &userns_st) < 0) + return -errno; + + netns_fd = varlink_take_fd(link, p.netns_fd_idx); + if (netns_fd < 0) + return netns_fd; + + r = validate_netns(link, userns_fd, netns_fd); + if (r != 0) + return r; + + if (!streq_ptr(p.mode, "veth")) + return varlink_error_invalid_parameter_name(link, "mode"); + + if (p.ifname && !ifname_valid(p.ifname)) + return varlink_error_invalid_parameter_name(link, "interfaceName"); + + registry_dir_fd = userns_registry_open_fd(); + if (registry_dir_fd < 0) + return registry_dir_fd; + + _cleanup_close_ int lock_fd = -EBADF; + lock_fd = userns_registry_lock(registry_dir_fd); + if (lock_fd < 0) + return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); + + r = userns_registry_load_by_userns_inode( + registry_dir_fd, + userns_st.st_ino, + &userns_info); + if (r == -ENOENT) + return varlink_error(link, "io.systemd.NamespaceResource.UserNamespaceNotRegistered", NULL); + if (r < 0) + return r; + + /* Registering a network interface for this client is only allowed for the root or the owner of a userns */ + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + if (peer_uid != 0 && peer_uid != userns_info->owner) + return varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + + _cleanup_free_ char *ifname_host = NULL, *altifname_host = NULL; + const char *ifname_namespace = p.ifname ?: "host0"; + + /* The short ifname is just too short to generate readable and unique names where unprivileged users + * can't take each others names. Hence just hash it. The alternative name however contains more useful + * information. */ + if (asprintf(&ifname_host, "ns-%08" PRIx64, hash_ifname_id(userns_info, p.ifname)) < 0) + return -ENOMEM; + strshorten(ifname_host, IFNAMSIZ-1); + + if (p.ifname) + r = asprintf(&altifname_host, "ns-" UID_FMT "-%s-%s", userns_info->owner, userns_info->name, p.ifname); + else + r = asprintf(&altifname_host, "ns-" UID_FMT "-%s", userns_info->owner, userns_info->name); + if (r < 0) + return -ENOMEM; + + struct ether_addr ether_addr_host, ether_addr_namespace; + + hash_ether_addr(userns_info, p.ifname, 0, ðer_addr_host); + hash_ether_addr(userns_info, p.ifname, 1, ðer_addr_namespace); + + r = create_veth(netns_fd, + ifname_host, altifname_host, ðer_addr_host, + ifname_namespace, ðer_addr_namespace); + if (r < 0) + return r; + + log_debug("Adding veth tunnel %s from host to userns " INO_FMT " ('%s' @ UID " UID_FMT ", interface %s).", + ifname_host, userns_st.st_ino, userns_info->name, userns_info->start, ifname_namespace); + + return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("hostInterfaceName", JSON_BUILD_STRING(ifname_host)), + JSON_BUILD_PAIR("namespaceInterfaceName", JSON_BUILD_STRING(ifname_namespace)))); +} + +static int process_connection(VarlinkServer *server, int _fd) { + _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ + _cleanup_(varlink_close_unrefp) Varlink *vl = NULL; + int r; + + r = varlink_server_add_connection(server, fd, &vl); + if (r < 0) + return log_error_errno(r, "Failed to add connection: %m"); + + TAKE_FD(fd); + vl = varlink_ref(vl); + + r = varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing for read: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable fd passing for write: %m"); + + for (;;) { + r = varlink_process(vl); + if (r == -ENOTCONN) { + log_debug("Connection terminated."); + break; + } + if (r < 0) + return log_error_errno(r, "Failed to process connection: %m"); + if (r > 0) + continue; + + r = varlink_wait(vl, CONNECTION_IDLE_USEC); + if (r < 0) + return log_error_errno(r, "Failed to wait for connection events: %m"); + if (r == 0) + break; + } + + return 0; +} + +static int run(int argc, char *argv[]) { + _cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *bpf = NULL; + usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY; + _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; + _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; + unsigned n_iterations = 0; + int m, listen_fd, r; + + log_setup(); + + m = sd_listen_fds(false); + if (m < 0) + return log_error_errno(m, "Failed to determine number of listening fds: %m"); + if (m == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No socket to listen on received."); + if (m > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Worker can only listen on a single socket at a time."); + + listen_fd = SD_LISTEN_FDS_START; + + r = fd_nonblock(listen_fd, false); + if (r < 0) + return log_error_errno(r, "Failed to turn off non-blocking mode for listening socket: %m"); + + r = varlink_server_new(&server, VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate server: %m"); + + r = varlink_server_add_interface_many( + server, + &vl_interface_io_systemd_NamespaceResource, + &vl_interface_io_systemd_UserDatabase); + if (r < 0) + return log_error_errno(r, "Failed to add UserDatabase and NamespaceResource interface to varlink server: %m"); + + r = varlink_server_bind_method_many( + server, + "io.systemd.NamespaceResource.AllocateUserRange", vl_method_allocate_user_range, + "io.systemd.NamespaceResource.RegisterUserNamespace", vl_method_register_user_namespace, + "io.systemd.NamespaceResource.AddMountToUserNamespace", vl_method_add_mount_to_user_namespace, + "io.systemd.NamespaceResource.AddControlGroupToUserNamespace", vl_method_add_cgroup_to_user_namespace, + "io.systemd.NamespaceResource.AddNetworkToUserNamespace", vl_method_add_netif_to_user_namespace, + "io.systemd.UserDatabase.GetUserRecord", vl_method_get_user_record, + "io.systemd.UserDatabase.GetGroupRecord", vl_method_get_group_record, + "io.systemd.UserDatabase.GetMemberships", vl_method_get_memberships); + if (r < 0) + return log_error_errno(r, "Failed to bind methods: %m"); + + varlink_server_set_userdata(server, &bpf); + + r = getenv_bool("NSRESOURCE_FIXED_WORKER"); + if (r < 0) + return log_error_errno(r, "Failed to parse NSRESOURCE_FIXED_WORKER: %m"); + listen_idle_usec = r ? USEC_INFINITY : LISTEN_IDLE_USEC; + + r = pidref_set_parent(&parent); + if (r < 0) + return log_error_errno(r, "Failed to acquire pidfd of parent process: %m"); + + start_time = now(CLOCK_MONOTONIC); + + for (;;) { + _cleanup_close_ int fd = -EBADF; + usec_t n; + + /* Exit the worker in regular intervals, to flush out all memory use */ + if (n_iterations++ > ITERATIONS_MAX) { + log_debug("Exiting worker, processed %u iterations, that's enough.", n_iterations); + break; + } + + n = now(CLOCK_MONOTONIC); + if (n >= usec_add(start_time, RUNTIME_MAX_USEC)) { + log_debug("Exiting worker, ran for %s, that's enough.", + FORMAT_TIMESPAN(usec_sub_unsigned(n, start_time), 0)); + break; + } + + if (last_busy_usec == USEC_INFINITY) + last_busy_usec = n; + else if (listen_idle_usec != USEC_INFINITY && n >= usec_add(last_busy_usec, listen_idle_usec)) { + log_debug("Exiting worker, been idle for %s.", + FORMAT_TIMESPAN(usec_sub_unsigned(n, last_busy_usec), 0)); + break; + } + + (void) rename_process("systemd-nsresourcework: waiting..."); + fd = RET_NERRNO(accept4(listen_fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC)); + (void) rename_process("systemd-nsresourcework: processing..."); + + if (fd == -EAGAIN) + continue; /* The listening socket has SO_RECVTIMEO set, hence a timeout is expected + * after a while, let's check if it's time to exit though. */ + if (fd == -EINTR) + continue; /* Might be that somebody attached via strace, let's just continue in that + * case */ + if (fd < 0) + return log_error_errno(fd, "Failed to accept() from listening socket: %m"); + + if (now(CLOCK_MONOTONIC) <= usec_add(n, PRESSURE_SLEEP_TIME_USEC)) { + /* We only slept a very short time? If so, let's see if there are more sockets + * pending, and if so, let's ask our parent for more workers */ + + r = fd_wait_for_event(listen_fd, POLLIN, 0); + if (r < 0) + return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m"); + + if (FLAGS_SET(r, POLLIN)) { + r = pidref_kill(&parent, SIGUSR2); + if (r == -ESRCH) + return log_error_errno(r, "Parent already died?"); + if (r < 0) + return log_error_errno(r, "Failed to send SIGUSR2 signal to parent. %m"); + } + } + + (void) process_connection(server, TAKE_FD(fd)); + last_busy_usec = USEC_INFINITY; + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/nsresourced/test-userns-restrict.c b/src/nsresourced/test-userns-restrict.c new file mode 100644 index 0000000..f509321 --- /dev/null +++ b/src/nsresourced/test-userns-restrict.c @@ -0,0 +1,182 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "fd-util.h" +#include "main-func.h" +#include "missing_mount.h" +#include "missing_syscall.h" +#include "namespace-util.h" +#include "process-util.h" +#include "rm-rf.h" +#include "tmpfile-util.h" +#include "userns-restrict.h" + +static int make_tmpfs_fsmount(void) { + _cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF; + + fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC); + assert_se(fsfd >= 0); + assert_se(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) >= 0); + + mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0); + assert_se(mntfd >= 0); + + return TAKE_FD(mntfd); +} + +static void test_works_reg(int parent_fd, const char *fname) { + _cleanup_close_ int fd = -EBADF; + + fd = openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666); + assert_se(fd >= 0); +} + +static void test_fails_reg(int parent_fd, const char *fname) { + errno = 0; + assert_se(openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666) < 0); + assert_se(errno == EPERM); +} + +static void test_works_dir(int parent_fd, const char *fname) { + assert_se(mkdirat(parent_fd, fname, 0666) >= 0); +} + +static void test_fails_dir(int parent_fd, const char *fname) { + errno = 0; + assert_se(mkdirat(parent_fd, fname, 0666) < 0); + assert_se(errno == EPERM); +} + +static int run(int argc, char *argv[]) { + _cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL; + _cleanup_close_ int userns_fd = -EBADF, host_fd1 = -EBADF, host_tmpfs = -EBADF, afd = -EBADF, bfd = -EBADF; + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + log_set_max_level(LOG_DEBUG); + log_setup(); + + r = userns_restrict_install(/* pin= */ false, &obj); + if (ERRNO_IS_NOT_SUPPORTED(r)) { + log_notice("Skipping test, LSM-BPF logic not supported."); + return EXIT_TEST_SKIP; + } + if (ERRNO_IS_PRIVILEGE(r)) { + log_notice("Skipping test, lacking privileges."); + return EXIT_TEST_SKIP; + } + if (r < 0) + return r; + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + host_fd1 = open(t, O_DIRECTORY|O_CLOEXEC); + assert_se(host_fd1 >= 0); + + host_tmpfs = make_tmpfs_fsmount(); + assert_se(host_tmpfs >= 0); + + userns_fd = userns_acquire("0 0 1", "0 0 1"); + if (userns_fd < 0) + return log_error_errno(userns_fd, "Failed to make user namespace: %m"); + + r = userns_restrict_put_by_fd( + obj, + userns_fd, + /* replace= */ true, + /* mount_fds= */ NULL, + /* n_mount_fds= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to restrict user namespace: %m"); + + afd = eventfd(0, EFD_CLOEXEC); + bfd = eventfd(0, EFD_CLOEXEC); + + assert_se(afd >= 0 && bfd >= 0); + + r = safe_fork("(test)", FORK_DEATHSIG_SIGKILL, &pid); + assert_se(r >= 0); + if (r == 0) { + _cleanup_close_ int private_tmpfs = -EBADF; + + assert_se(setns(userns_fd, CLONE_NEWUSER) >= 0); + assert_se(unshare(CLONE_NEWNS) >= 0); + + /* Allocate tmpfs locally */ + private_tmpfs = make_tmpfs_fsmount(); + + /* These two host mounts should be inaccessible */ + test_fails_reg(host_fd1, "test"); + test_fails_reg(host_tmpfs, "xxx"); + test_fails_dir(host_fd1, "test2"); + test_fails_dir(host_tmpfs, "xxx2"); + + /* But this mount created locally should be fine */ + test_works_reg(private_tmpfs, "yyy"); + test_works_dir(private_tmpfs, "yyy2"); + + /* Let's sync with the parent, so that it allowlists more stuff for us */ + assert_se(eventfd_write(afd, 1) >= 0); + uint64_t x; + assert_se(eventfd_read(bfd, &x) >= 0); + + /* And now we should also have access to the host tmpfs */ + test_works_reg(host_tmpfs, "zzz"); + test_works_reg(private_tmpfs, "aaa"); + test_works_dir(host_tmpfs, "zzz2"); + test_works_dir(private_tmpfs, "aaa2"); + + /* But this one should still fail */ + test_fails_reg(host_fd1, "bbb"); + test_fails_dir(host_fd1, "bbb2"); + + /* Sync again, to get more stuff allowlisted */ + assert_se(eventfd_write(afd, 1) >= 0); + assert_se(eventfd_read(bfd, &x) >= 0); + + /* Everything should now be allowed */ + test_works_reg(host_tmpfs, "ccc"); + test_works_reg(host_fd1, "ddd"); + test_works_reg(private_tmpfs, "eee"); + test_works_dir(host_tmpfs, "ccc2"); + test_works_reg(host_fd1, "ddd2"); + test_works_dir(private_tmpfs, "eee2"); + + _exit(EXIT_SUCCESS); + } + + uint64_t x; + assert_se(eventfd_read(afd, &x) >= 0); + + r = userns_restrict_put_by_fd( + obj, + userns_fd, + /* replace= */ false, + &host_tmpfs, + 1); + if (r < 0) + return log_error_errno(r, "Failed to loosen user namespace: %m"); + + assert_se(eventfd_write(bfd, 1) >= 0); + + assert_se(eventfd_read(afd, &x) >= 0); + + r = userns_restrict_put_by_fd( + obj, + userns_fd, + /* replace= */ false, + &host_fd1, + 1); + if (r < 0) + return log_error_errno(r, "Failed to loosen user namespace: %m"); + + assert_se(eventfd_write(bfd, 1) >= 0); + + assert_se(wait_for_terminate_and_check("(test)", pid, WAIT_LOG) >= 0); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/nsresourced/userns-registry.c b/src/nsresourced/userns-registry.c new file mode 100644 index 0000000..2cc1b1f --- /dev/null +++ b/src/nsresourced/userns-registry.c @@ -0,0 +1,646 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "chase.h" +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "json.h" +#include "missing_magic.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "rm-rf.h" +#include "user-util.h" +#include "userns-registry.h" + +int userns_registry_open_fd(void) { + int fd; + + fd = chase_and_open( + "/run/systemd/nsresource/registry", + /* root= */ NULL, + CHASE_MKDIR_0755, + O_CLOEXEC|O_DIRECTORY|O_CREAT, + /* ret_path= */ NULL); + if (fd < 0) + return log_debug_errno(fd, "Failed to open registry dir: %m"); + + return fd; +} + +int userns_registry_lock(int dir_fd) { + _cleanup_close_ int registry_fd = -EBADF, lock_fd = -EBADF; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + lock_fd = xopenat_lock_full(dir_fd, "lock", O_CREAT|O_RDWR|O_CLOEXEC, /* xopen_flags= */ 0, 0600, LOCK_BSD, LOCK_EX); + if (lock_fd < 0) + return log_debug_errno(lock_fd, "Failed to open nsresource registry lock file: %m"); + + return TAKE_FD(lock_fd); +} + +UserNamespaceInfo* userns_info_new(void) { + UserNamespaceInfo *info = new(UserNamespaceInfo, 1); + if (!info) + return NULL; + + *info = (UserNamespaceInfo) { + .owner = UID_INVALID, + .start = UID_INVALID, + .target = UID_INVALID, + }; + + return info; +} + +UserNamespaceInfo *userns_info_free(UserNamespaceInfo *userns) { + if (!userns) + return NULL; + + free(userns->cgroups); + free(userns->name); + + return mfree(userns); +} + +static int dispatch_cgroups_array(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + UserNamespaceInfo *info = ASSERT_PTR(userdata); + _cleanup_free_ uint64_t *cgroups = NULL; + size_t n_cgroups = 0; + + if (json_variant_is_null(variant)) { + info->cgroups = mfree(info->cgroups); + info->n_cgroups = 0; + return 0; + } + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + cgroups = new(uint64_t, json_variant_elements(variant)); + if (!cgroups) + return json_log_oom(variant, flags); + + JsonVariant *e; + JSON_VARIANT_ARRAY_FOREACH(e, variant) { + bool found = false; + + if (!json_variant_is_unsigned(e)) + return json_log(e, flags, SYNTHETIC_ERRNO(EINVAL), "JSON array element is not a number."); + + FOREACH_ARRAY(cg, cgroups, n_cgroups) + if (*cg == json_variant_unsigned(e)) { + found = true; + break; + } + if (found) /* suppress duplicate */ + continue; + + cgroups[n_cgroups++] = json_variant_unsigned(e); + } + + assert(n_cgroups <= json_variant_elements(variant)); + + free_and_replace(info->cgroups, cgroups); + info->n_cgroups = n_cgroups; + + return 0; +} + +static int userns_registry_load(int dir_fd, const char *fn, UserNamespaceInfo **ret) { + + static const JsonDispatch dispatch_table[] = { + { "owner", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, owner), JSON_MANDATORY }, + { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserNamespaceInfo, name), JSON_MANDATORY }, + { "userns", JSON_VARIANT_UNSIGNED, json_dispatch_uint64, offsetof(UserNamespaceInfo, userns_inode), JSON_MANDATORY }, + { "start", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, start), 0 }, + { "size", JSON_VARIANT_UNSIGNED, json_dispatch_uint32, offsetof(UserNamespaceInfo, size), 0 }, + { "target", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserNamespaceInfo, target), 0 }, + { "cgroups", JSON_VARIANT_ARRAY, dispatch_cgroups_array, 0, 0 }, + {} + }; + + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_close_ int registry_fd = -EBADF; + int r; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + r = json_parse_file_at(NULL, dir_fd, fn, 0, &v, NULL, NULL); + if (r < 0) + return r; + + userns_info = userns_info_new(); + if (!userns_info) + return -ENOMEM; + + r = json_dispatch(v, dispatch_table, 0, userns_info); + if (r < 0) + return r; + + if (userns_info->userns_inode == 0) + return -EBADMSG; + if (userns_info->start == 0) + return -EBADMSG; + if (userns_info->size == 0) { + if (uid_is_valid(userns_info->start) || uid_is_valid(userns_info->target)) + return -EBADMSG; + } else { + if (!uid_is_valid(userns_info->start) || !uid_is_valid(userns_info->target)) + return -EBADMSG; + + if (userns_info->size > UINT32_MAX - userns_info->start || + userns_info->size > UINT32_MAX - userns_info->target) + return -EBADMSG; + } + + if (ret) + *ret = TAKE_PTR(userns_info); + return 0; +} + +int userns_registry_uid_exists(int dir_fd, uid_t start) { + _cleanup_free_ char *fn = NULL; + + assert(dir_fd >= 0); + + if (!uid_is_valid(start)) + return -ENOENT; + + if (start == 0) + return true; + + if (asprintf(&fn, "u" UID_FMT ".userns", start) < 0) + return -ENOMEM; + + if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + return errno == ENOENT ? false : -errno; + + return true; +} + +int userns_registry_name_exists(int dir_fd, const char *name) { + _cleanup_free_ char *fn = NULL; + + assert(dir_fd >= 0); + + if (!userns_name_is_valid(name)) + return -EINVAL; + + fn = strjoin("n", name, ".userns"); + if (!fn) + return -ENOMEM; + + if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + return errno == ENOENT ? false : -errno; + + return true; +} + +int userns_registry_inode_exists(int dir_fd, uint64_t inode) { + _cleanup_free_ char *fn = NULL; + + assert(dir_fd >= 0); + + if (inode <= 0) + return -EINVAL; + + if (asprintf(&fn, "i%" PRIu64 ".userns", inode) < 0) + return -ENOMEM; + + if (faccessat(dir_fd, fn, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + return errno == ENOENT ? false : -errno; + + return true; +} + +int userns_registry_load_by_start_uid(int dir_fd, uid_t start, UserNamespaceInfo **ret) { + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_close_ int registry_fd = -EBADF; + _cleanup_free_ char *fn = NULL; + int r; + + if (!uid_is_valid(start)) + return -ENOENT; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + if (asprintf(&fn, "u" UID_FMT ".userns", start) < 0) + return -ENOMEM; + + r = userns_registry_load(dir_fd, fn, &userns_info); + if (r < 0) + return r; + + if (userns_info->start != start) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(userns_info); + + return 0; +} + +int userns_registry_load_by_userns_inode(int dir_fd, uint64_t inode, UserNamespaceInfo **ret) { + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_close_ int registry_fd = -EBADF; + _cleanup_free_ char *fn = NULL; + int r; + + if (inode == 0) + return -ENOENT; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + if (asprintf(&fn, "i%" PRIu64 ".userns", inode) < 0) + return -ENOMEM; + + r = userns_registry_load(dir_fd, fn, &userns_info); + if (r < 0) + return r; + + if (userns_info->userns_inode != inode) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(userns_info); + + return 0; +} + +int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret) { + _cleanup_(userns_info_freep) UserNamespaceInfo *userns_info = NULL; + _cleanup_close_ int registry_fd = -EBADF; + _cleanup_free_ char *fn = NULL; + int r; + + assert(name); + + if (!userns_name_is_valid(name)) /* Invalid names never exist */ + return -ENOENT; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + fn = strjoin("n", name, ".userns"); + if (!fn) + return -ENOMEM; + + r = userns_registry_load(dir_fd, fn, &userns_info); + if (r < 0) + return r; + + if (!streq_ptr(userns_info->name, name)) + return -EBADMSG; + + if (ret) + *ret = TAKE_PTR(userns_info); + + return 0; +} + +int userns_registry_store(int dir_fd, UserNamespaceInfo *info) { + _cleanup_close_ int registry_fd = -EBADF; + int r; + + assert(info); + + if (!uid_is_valid(info->owner) || + !info->name || + info->userns_inode == 0) + return -EINVAL; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + _cleanup_(json_variant_unrefp) JsonVariant *cgroup_array = NULL; + FOREACH_ARRAY(cg, info->cgroups, info->n_cgroups) { + r = json_variant_append_arrayb( + &cgroup_array, + JSON_BUILD_UNSIGNED(*cg)); + if (r < 0) + return r; + } + + _cleanup_(json_variant_unrefp) JsonVariant *def = NULL; + r = json_build(&def, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("owner", JSON_BUILD_UNSIGNED(info->owner)), + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(info->name)), + JSON_BUILD_PAIR("userns", JSON_BUILD_UNSIGNED(info->userns_inode)), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "start", JSON_BUILD_UNSIGNED(info->start)), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "size", JSON_BUILD_UNSIGNED(info->size)), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(info->start), "target", JSON_BUILD_UNSIGNED(info->target)), + JSON_BUILD_PAIR_CONDITION(cgroup_array, "cgroups", JSON_BUILD_VARIANT(cgroup_array)))); + if (r < 0) + return r; + + _cleanup_free_ char *def_buf = NULL; + r = json_variant_format(def, 0, &def_buf); + if (r < 0) + return log_debug_errno(r, "Failed to format userns JSON object: %m"); + + _cleanup_free_ char *reg_fn = NULL, *link1_fn = NULL, *link2_fn = NULL, *owner_fn = NULL, *uid_fn = NULL; + if (asprintf(®_fn, "i%" PRIu64 ".userns", info->userns_inode) < 0) + return log_oom_debug(); + + r = write_string_file_at(dir_fd, reg_fn, def_buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_debug_errno(r, "Failed to write userns data to '%s' in registry: %m", reg_fn); + + link1_fn = strjoin("n", info->name, ".userns"); + if (!link1_fn) { + r = log_oom_debug(); + goto fail; + } + + r = linkat_replace(dir_fd, reg_fn, dir_fd, link1_fn); + if (r < 0) { + log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", link1_fn); + goto fail; + } + + if (uid_is_valid(info->start)) { + if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0) { + r = log_oom_debug(); + goto fail; + } + + r = linkat_replace(dir_fd, reg_fn, dir_fd, link2_fn); + if (r < 0) { + log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", link2_fn); + goto fail; + } + } + + if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0) { + r = log_oom_debug(); + goto fail; + } + + if (mkdirat(dir_fd, uid_fn, 0755) < 0 && errno != EEXIST) { + r = log_debug_errno(errno, "Failed to create per-UID subdir '%s' of registry: %m", uid_fn); + goto fail; + } + + if (asprintf(&owner_fn, "%s/i%" PRIu64 ".userns", uid_fn, info->userns_inode) < 0) { + r = log_oom_debug(); + goto fail; + } + + r = linkat_replace(dir_fd, reg_fn, dir_fd, owner_fn); + if (r < 0) { + log_debug_errno(r, "Failed to link userns data to '%s' in registry: %m", owner_fn); + goto fail; + } + + return 0; + +fail: + if (reg_fn) + (void) unlinkat(dir_fd, reg_fn, /* flags= */ 0); + if (link1_fn) + (void) unlinkat(dir_fd, link1_fn, /* flags= */ 0); + if (link2_fn) + (void) unlinkat(dir_fd, link2_fn, /* flags= */ 0); + if (owner_fn) + (void) unlinkat(dir_fd, owner_fn, /* flags= */ 0); + if (uid_fn) + (void) unlinkat(dir_fd, uid_fn, AT_REMOVEDIR); + + return r; +} + +int userns_registry_remove(int dir_fd, UserNamespaceInfo *info) { + _cleanup_close_ int registry_fd = -EBADF; + int ret = 0, r; + + assert(info); + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + _cleanup_free_ char *reg_fn = NULL; + if (asprintf(®_fn, "i%" PRIu64 ".userns", info->userns_inode) < 0) + return log_oom_debug(); + + ret = RET_NERRNO(unlinkat(dir_fd, reg_fn, 0)); + + _cleanup_free_ char *link1_fn = NULL; + link1_fn = strjoin("n", info->name, ".userns"); + if (!link1_fn) + return log_oom_debug(); + + RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link1_fn, 0))); + + if (uid_is_valid(info->start)) { + _cleanup_free_ char *link2_fn = NULL; + + if (asprintf(&link2_fn, "u" UID_FMT ".userns", info->start) < 0) + return log_oom_debug(); + + RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, link2_fn, 0))); + } + + _cleanup_free_ char *uid_fn = NULL; + if (asprintf(&uid_fn, "o" UID_FMT ".owns", info->owner) < 0) + return log_oom_debug(); + + _cleanup_free_ char *owner_fn = NULL; + if (asprintf(&owner_fn, "%s/i%" PRIu64 ".userns", uid_fn, info->userns_inode) < 0) + return log_oom_debug(); + + RET_GATHER(ret, RET_NERRNO(unlinkat(dir_fd, owner_fn, 0))); + + r = RET_NERRNO(unlinkat(dir_fd, uid_fn, AT_REMOVEDIR)); + if (r != -ENOTEMPTY) + RET_GATHER(ret, r); + + return ret; +} + +bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) { + assert(userns); + + FOREACH_ARRAY(i, userns->cgroups, userns->n_cgroups) + if (*i == cgroup_id) + return true; + + return false; +} + +int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id) { + + if (userns_info_has_cgroup(userns, cgroup_id)) + return 0; + + if (!GREEDY_REALLOC(userns->cgroups, userns->n_cgroups+1)) + return -ENOMEM; + + userns->cgroups[userns->n_cgroups++] = cgroup_id; + return 1; +} + +static int userns_destroy_cgroup(uint64_t cgroup_id) { + _cleanup_close_ int cgroup_fd = -EBADF, parent_fd = -EBADF; + int r; + + cgroup_fd = cg_cgroupid_open(/* cgroupfsfd= */ -EBADF, cgroup_id); + if (cgroup_fd == -ESTALE) { + log_debug_errno(cgroup_fd, "Control group %" PRIu64 " already gone, ignoring: %m", cgroup_id); + return 0; + } + if (cgroup_fd < 0) + return log_debug_errno(errno, "Failed to open cgroup %" PRIu64 ", ignoring: %m", cgroup_id); + + _cleanup_free_ char *path = NULL; + r = fd_get_path(cgroup_fd, &path); + if (r < 0) + return log_debug_errno(r, "Failed to get path of cgroup %" PRIu64 ", ignoring: %m", cgroup_id); + + const char *e = path_startswith(path, "/sys/fs/cgroup/"); + if (!e) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Got cgroup path that doesn't start with /sys/fs/cgroup/, refusing: %s", path); + if (isempty(e)) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Got root cgroup path, which can't be right, refusing."); + + log_debug("Path of cgroup %" PRIu64 " is: %s", cgroup_id, path); + + _cleanup_free_ char *fname = NULL; + r = path_extract_filename(path, &fname); + if (r < 0) + return log_debug_errno(r, "Failed to extract name of cgroup %" PRIu64 ", ignoring: %m", cgroup_id); + + parent_fd = openat(cgroup_fd, "..", O_CLOEXEC|O_DIRECTORY); + if (parent_fd < 0) + return log_debug_errno(errno, "Failed to open parent cgroup of %" PRIu64 ", ignoring: %m", cgroup_id); + + /* Safety check, never leave cgroupfs */ + r = fd_is_fs_type(parent_fd, CGROUP2_SUPER_MAGIC); + if (r < 0) + return log_debug_errno(r, "Failed to determine if parent directory of cgroup %" PRIu64 " is still a cgroup, ignoring: %m", cgroup_id); + if (!r) + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Parent directory of cgroup %" PRIu64 " is not a cgroup, refusing.", cgroup_id); + + cgroup_fd = safe_close(cgroup_fd); + + r = rm_rf_child(parent_fd, fname, REMOVE_ONLY_DIRECTORIES|REMOVE_PHYSICAL|REMOVE_CHMOD); + if (r < 0) + log_debug_errno(r, "Failed to remove delegated cgroup %" PRIu64 ", ignoring: %m", cgroup_id); + + return 0; +} + +int userns_info_remove_cgroups(UserNamespaceInfo *userns) { + int ret = 0; + + assert(userns); + + FOREACH_ARRAY(c, userns->cgroups, userns->n_cgroups) + RET_GATHER(ret, userns_destroy_cgroup(*c)); + + userns->cgroups = mfree(userns->cgroups); + userns->n_cgroups = 0; + + return ret; +} + +bool userns_name_is_valid(const char *name) { + + /* Checks if the specified string is suitable as user namespace name. */ + + if (strlen(name) > NAME_MAX) /* before we use alloca(), let's check for size */ + return false; + + const char *f = strjoina("n", name, ".userns"); /* Make sure we can name our lookup symlink with this name */ + if (!filename_is_valid(f)) + return false; + + const char *u = strjoina("ns-", name, "-65535"); /* Make sure we can turn this into valid user names */ + if (!valid_user_group_name(u, 0)) + return false; + + return true; +} + +int userns_registry_per_uid(int dir_fd, uid_t owner) { + _cleanup_close_ int registry_fd = -EBADF; + int n = 0, r; + + if (dir_fd < 0) { + registry_fd = userns_registry_open_fd(); + if (registry_fd < 0) + return registry_fd; + + dir_fd = registry_fd; + } + + _cleanup_free_ char *uid_fn = NULL; + if (asprintf(&uid_fn, "o" UID_FMT ".owns", owner) < 0) + return log_oom_debug(); + + _cleanup_free_ DirectoryEntries *de = NULL; + + r = readdir_all_at(dir_fd, uid_fn, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &de); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to enumerate contents of '%s' sub-directory: %m", uid_fn); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + struct dirent *e = *i; + + if (e->d_type != DT_REG) + continue; + + if (!startswith(e->d_name, "i") || !endswith(e->d_name, ".userns")) + continue; + + n++; + + if (n == INT_MAX) /* overflow safety check, just in case */ + break; + } + + return n; +} diff --git a/src/nsresourced/userns-registry.h b/src/nsresourced/userns-registry.h new file mode 100644 index 0000000..9e66a6f --- /dev/null +++ b/src/nsresourced/userns-registry.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#define USER_NAMESPACE_CGROUPS_DELEGATE_MAX 16 + +typedef struct UserNamespaceInfo { + uid_t owner; + char *name; + uint64_t userns_inode; + uid_t start; + uint32_t size; + uid_t target; + uint64_t *cgroups; + size_t n_cgroups; +} UserNamespaceInfo; + +UserNamespaceInfo* userns_info_new(void); +UserNamespaceInfo* userns_info_free(UserNamespaceInfo *userns); + +DEFINE_TRIVIAL_CLEANUP_FUNC(UserNamespaceInfo*, userns_info_free); + +bool userns_info_has_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id); +int userns_info_add_cgroup(UserNamespaceInfo *userns, uint64_t cgroup_id); +int userns_info_remove_cgroups(UserNamespaceInfo *userns); + +bool userns_name_is_valid(const char *name); + +int userns_registry_open_fd(void); +int userns_registry_lock(int dir_fd); + +int userns_registry_load_by_start_uid(int dir_fd, uid_t start, UserNamespaceInfo **ret); +int userns_registry_load_by_userns_inode(int dir_fd, uint64_t userns, UserNamespaceInfo **ret); +int userns_registry_load_by_name(int dir_fd, const char *name, UserNamespaceInfo **ret); + +int userns_registry_store(int dir_fd, UserNamespaceInfo *info); +int userns_registry_remove(int dir_fd, UserNamespaceInfo *info); + +int userns_registry_inode_exists(int dir_fd, uint64_t inode); +int userns_registry_name_exists(int dir_fd, const char *name); +int userns_registry_uid_exists(int dir_fd, uid_t start); + +int userns_registry_per_uid(int dir_fd, uid_t owner); diff --git a/src/nsresourced/userns-restrict.c b/src/nsresourced/userns-restrict.c new file mode 100644 index 0000000..be33f49 --- /dev/null +++ b/src/nsresourced/userns-restrict.c @@ -0,0 +1,346 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "userns-restrict.h" + +#if HAVE_VMLINUX_H + +#include + +#include "bpf-dlopen.h" +#include "bpf-link.h" +#include "fd-util.h" +#include "fs-util.h" +#include "lsm-util.h" +#include "missing_mount.h" +#include "mkdir.h" +#include "mount-util.h" +#include "mountpoint-util.h" +#include "namespace-util.h" +#include "path-util.h" + +#define USERNS_MAX (16U*1024U) +#define MOUNTS_MAX 4096U + +#define PROGRAM_LINK_PREFIX "/sys/fs/bpf/systemd/userns-restrict/programs" +#define MAP_LINK_PREFIX "/sys/fs/bpf/systemd/userns-restrict/maps" + +struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj) { + (void) userns_restrict_bpf__destroy(obj); /* this call is fine with NULL */ + return NULL; +} + +static int make_inner_hash_map(void) { + int fd; + + fd = compat_bpf_map_create( + BPF_MAP_TYPE_HASH, + NULL, + sizeof(int), + sizeof(uint32_t), + MOUNTS_MAX, + NULL); + if (fd < 0) + return log_debug_errno(errno, "Failed allocate inner BPF map: %m"); + + return fd; +} + +int userns_restrict_install( + bool pin, + struct userns_restrict_bpf **ret) { + + _cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL; + _cleanup_close_ int dummy_mnt_id_hash_fd = -EBADF; + int r; + + r = lsm_supported("bpf"); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "bpf-lsm not supported, can't lock down user namespace."); + + r = dlopen_bpf(); + if (r < 0) + return r; + + /* bpf_object__next_map() is not available in libbpf pre-0.7.0, and we want to use it. */ + if (!sym_bpf_object__next_map) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libbpf too old for locking down user namespace."); + + obj = userns_restrict_bpf__open(); + if (!obj) + return log_error_errno(errno, "Failed to open userns_restrict BPF object: %m"); + + if (pin) { + struct bpf_map *map; + + /* libbpf will only create one level of dirs. Let's create the rest */ + (void) mkdir_p(MAP_LINK_PREFIX, 0755); + (void) mkdir_p(PROGRAM_LINK_PREFIX, 0755); + + map = sym_bpf_object__next_map(obj->obj, NULL); + while (map) { + _cleanup_free_ char *fn = NULL; + + fn = path_join(MAP_LINK_PREFIX, sym_bpf_map__name(map)); + if (!fn) + return log_oom(); + + r = sym_bpf_map__set_pin_path(map, fn); + if (r < 0) + return log_error_errno(r, "Failed to set pin path to '%s': %m", fn); + + map = sym_bpf_object__next_map(obj->obj, map); + } + } + + r = sym_bpf_map__set_max_entries(obj->maps.userns_mnt_id_hash, USERNS_MAX); + if (r < 0) + return log_error_errno(r, "Failed to size userns/mnt_id hash table: %m"); + + r = sym_bpf_map__set_max_entries(obj->maps.userns_ringbuf, USERNS_MAX * sizeof(unsigned int)); + if (r < 0) + return log_error_errno(r, "Failed to size userns ring buffer: %m"); + + /* Dummy map to satisfy the verifier */ + dummy_mnt_id_hash_fd = make_inner_hash_map(); + if (dummy_mnt_id_hash_fd < 0) + return dummy_mnt_id_hash_fd; + + r = sym_bpf_map__set_inner_map_fd(obj->maps.userns_mnt_id_hash, dummy_mnt_id_hash_fd); + if (r < 0) + return log_error_errno(r, "Failed to set inner BPF map: %m"); + + r = userns_restrict_bpf__load(obj); + if (r < 0) + return log_error_errno(r, "Failed to load BPF object: %m"); + + for (int i = 0; i < obj->skeleton->prog_cnt; i++) { + _cleanup_(bpf_link_freep) struct bpf_link *link = NULL; + struct bpf_prog_skeleton *ps = obj->skeleton->progs + i; + _cleanup_free_ char *fn = NULL; + bool linked = false; + const char *e; + + e = startswith(ps->name, "userns_restrict_"); + assert(e); + + if (pin) { + fn = path_join(PROGRAM_LINK_PREFIX, e); + if (!fn) + return log_oom(); + + link = sym_bpf_link__open(fn); + r = bpf_get_error_translated(link); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Unable to open pinned program link: %m"); + link = NULL; + } else { + linked = true; + log_info("userns-restrict BPF-LSM program %s already attached.", ps->name); + } + } + + if (!link) { + link = sym_bpf_program__attach(*ps->prog); + r = bpf_get_error_translated(link); + if (r < 0) + return log_error_errno(r, "Failed to attach LSM BPF program: %m"); + + log_info("userns-restrict BPF-LSM program %s now attached.", ps->name); + } + + if (pin && !linked) { + assert(fn); + + r = sym_bpf_link__pin(link, fn); + if (r < 0) + return log_error_errno(r, "Failed to pin LSM attachment: %m"); + } + + *ps->link = TAKE_PTR(link); + } + + if (pin) { + r = sym_bpf_object__pin_maps(obj->obj, NULL); + if (r < 0) + return log_error_errno(r, "Failed to pin BPF maps: %m"); + } + + if (ret) + *ret = TAKE_PTR(obj); + + return 0; +} + +int userns_restrict_put_by_inode( + struct userns_restrict_bpf *obj, + uint64_t userns_inode, + bool replace, + const int mount_fds[], + size_t n_mount_fds) { + + _cleanup_close_ int inner_map_fd = -EBADF; + _cleanup_free_ int *mnt_ids = NULL; + uint64_t ino = userns_inode; + int r, outer_map_fd; + + assert(obj); + assert(userns_inode != 0); + assert(n_mount_fds == 0 || mount_fds); + + /* The BPF map type BPF_MAP_TYPE_HASH_OF_MAPS only supports 32bit keys, and user namespace inode + * numbers are 32bit too, even though ino_t is 64bit these days. Should we ever run into a 64bit + * inode let's refuse early, we can't support this with the current BPF code for now. */ + if (userns_inode > UINT32_MAX) + return -EINVAL; + + mnt_ids = new(int, n_mount_fds); + if (!mnt_ids) + return -ENOMEM; + + for (size_t i = 0; i < n_mount_fds; i++) { + r = path_get_mnt_id_at(mount_fds[i], "", mnt_ids + i); + if (r < 0) + return log_debug_errno(r, "Failed to get mount ID: %m"); + } + + outer_map_fd = sym_bpf_map__fd(obj->maps.userns_mnt_id_hash); + if (outer_map_fd < 0) + return log_debug_errno(outer_map_fd, "Failed to get outer BPF map fd: %m"); + + if (replace) { + /* Add if missing, replace if already exists */ + inner_map_fd = make_inner_hash_map(); + if (inner_map_fd < 0) + return inner_map_fd; + + r = sym_bpf_map_update_elem(outer_map_fd, &ino, &inner_map_fd, BPF_ANY); + if (r < 0) + return log_debug_errno(r, "Failed to replace map in inode hash: %m"); + } else { + /* Let's add an entry for this userns inode if missing. If it exists just extend the existing map. We + * might race against each other, hence we try a couple of times */ + for (size_t n_try = 10;; n_try--) { + uint32_t innermap_id; + + if (n_try == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), + "Stillcan't create inode entry in BPF map after 10 tries."); + + r = sym_bpf_map_lookup_elem(outer_map_fd, &ino, &innermap_id); + if (r >= 0) { + inner_map_fd = sym_bpf_map_get_fd_by_id(innermap_id); + if (inner_map_fd < 0) + return log_debug_errno(inner_map_fd, "Failed to get file descriptor for inner map: %m"); + + break; + } + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to look up inode hash entry: %m"); + + /* No entry for this user namespace yet. Let's create one */ + inner_map_fd = make_inner_hash_map(); + if (inner_map_fd < 0) + return inner_map_fd; + + r = sym_bpf_map_update_elem(outer_map_fd, &ino, &inner_map_fd, BPF_NOEXIST); + if (r >= 0) + break; + if (errno != EEXIST) + return log_debug_errno(errno, "Failed to add mount ID list to inode hash: %m"); + } + } + + FOREACH_ARRAY(mntid, mnt_ids, n_mount_fds) { + uint32_t dummy_value = 1; + + r = sym_bpf_map_update_elem(inner_map_fd, mntid, &dummy_value, BPF_ANY); + if (r < 0) + return log_debug_errno(r, "Failed to add mount ID to map: %m"); + + log_debug("Allowing mount %i on userns inode %" PRIu64, *mntid, ino); + } + + return 0; +} + +int userns_restrict_put_by_fd( + struct userns_restrict_bpf *obj, + int userns_fd, + bool replace, + const int mount_fds[], + size_t n_mount_fds) { + + struct stat st; + int r; + + assert(obj); + assert(userns_fd >= 0); + assert(n_mount_fds == 0 || mount_fds); + + r = fd_is_ns(userns_fd, CLONE_NEWUSER); + if (r < 0) + return log_debug_errno(r, "Failed to determine if file descriptor is user namespace: %m"); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EBADF), "User namespace fd is not actually a user namespace fd."); + + if (fstat(userns_fd, &st) < 0) + return log_debug_errno(errno, "Failed to fstat() user namespace: %m"); + + return userns_restrict_put_by_inode( + obj, + st.st_ino, + replace, + mount_fds, + n_mount_fds); +} + +int userns_restrict_reset_by_inode( + struct userns_restrict_bpf *obj, + uint64_t ino) { + + int r, outer_map_fd; + unsigned u; + + assert(obj); + assert(ino != 0); + + if (ino > UINT32_MAX) /* inodes larger than 32bit are definitely not included in our map, exit early */ + return 0; + + outer_map_fd = sym_bpf_map__fd(obj->maps.userns_mnt_id_hash); + if (outer_map_fd < 0) + return log_debug_errno(outer_map_fd, "Failed to get outer BPF map fd: %m"); + + u = (uint32_t) ino; + + r = sym_bpf_map_delete_elem(outer_map_fd, &u); + if (r < 0) + return log_debug_errno(r, "Failed to remove entry for inode %" PRIu64 " from outer map: %m", ino); + + return 0; +} + +#else +int userns_restrict_install(bool pin, struct userns_restrict_bpf **ret) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled."); +} + +struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj) { + return NULL; +} + +int userns_restrict_put_by_fd(struct userns_restrict_bpf *obj, int userns_fd, bool replace, const int mount_fds[], size_t n_mount_fds) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled."); +} + +int userns_restrict_put_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode, bool replace, const int mount_fds[], size_t n_mount_fds) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled."); +} + +int userns_restrict_reset_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode) { + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "User Namespace Restriction BPF support disabled."); +} +#endif diff --git a/src/nsresourced/userns-restrict.h b/src/nsresourced/userns-restrict.h new file mode 100644 index 0000000..37aed7b --- /dev/null +++ b/src/nsresourced/userns-restrict.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +#if HAVE_VMLINUX_H +#include "bpf/userns_restrict/userns-restrict-skel.h" +#else +struct userns_restrict_bpf; +#endif + +int userns_restrict_install(bool pin, struct userns_restrict_bpf **ret); +struct userns_restrict_bpf *userns_restrict_bpf_free(struct userns_restrict_bpf *obj); + +int userns_restrict_put_by_fd(struct userns_restrict_bpf *obj, int userns_fd, bool replace, const int mount_fds[], size_t n_mount_fds); +int userns_restrict_put_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode, bool replace, const int mount_fds[], size_t n_mount_fds); + +int userns_restrict_reset_by_inode(struct userns_restrict_bpf *obj, uint64_t userns_inode); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct userns_restrict_bpf*, userns_restrict_bpf_free); diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c index c4e02bc..3cefb63 100644 --- a/src/nss-resolve/nss-resolve.c +++ b/src/nss-resolve/nss-resolve.c @@ -20,7 +20,7 @@ #include "strv.h" #include "varlink.h" -static JsonDispatchFlags json_dispatch_flags = 0; +static JsonDispatchFlags json_dispatch_flags = JSON_ALLOW_EXTENSIONS; static void setup_logging(void) { log_parse_environment_variables(); @@ -202,9 +202,10 @@ static uint64_t query_flag( const char *name, const int value, uint64_t flag) { + int r; - r = getenv_bool_secure(name); + r = secure_getenv_bool(name); if (r >= 0) return r == value ? flag : 0; if (r != -ENXIO) @@ -261,7 +262,7 @@ enum nss_status _nss_resolve_gethostbyname4_r( * configuration can distinguish such executed but negative replies from complete failure to * talk to resolved). */ const char *error_id; - r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL); + r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id); if (r < 0) goto fail; if (!isempty(error_id)) { @@ -423,7 +424,7 @@ enum nss_status _nss_resolve_gethostbyname3_r( goto fail; const char *error_id; - r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id, NULL); + r = varlink_call(link, "io.systemd.Resolve.ResolveHostname", cparams, &rparams, &error_id); if (r < 0) goto fail; if (!isempty(error_id)) { @@ -641,7 +642,7 @@ enum nss_status _nss_resolve_gethostbyaddr2_r( goto fail; const char* error_id; - r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id, NULL); + r = varlink_call(link, "io.systemd.Resolve.ResolveAddress", cparams, &rparams, &error_id); if (r < 0) goto fail; if (!isempty(error_id)) { diff --git a/src/nss-systemd/nss-systemd.c b/src/nss-systemd/nss-systemd.c index 1d6e253..8e8d4cf 100644 --- a/src/nss-systemd/nss-systemd.c +++ b/src/nss-systemd/nss-systemd.c @@ -306,7 +306,7 @@ enum nss_status _nss_systemd_getpwnam_r( return NSS_STATUS_NOTFOUND; /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (streq(name, root_passwd.pw_name)) return copy_synthesized_passwd(pwd, &root_passwd, @@ -354,7 +354,7 @@ enum nss_status _nss_systemd_getpwuid_r( return NSS_STATUS_NOTFOUND; /* Synthesize data for the root user and for nobody in case they are missing from /etc/passwd */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (uid == root_passwd.pw_uid) return copy_synthesized_passwd(pwd, &root_passwd, @@ -403,7 +403,7 @@ enum nss_status _nss_systemd_getspnam_r( return NSS_STATUS_NOTFOUND; /* Synthesize entries for the root and nobody users, in case they are missing in /etc/passwd */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (streq(name, root_spwd.sp_namp)) return copy_synthesized_spwd(spwd, &root_spwd, buffer, buflen, errnop); @@ -450,7 +450,7 @@ enum nss_status _nss_systemd_getgrnam_r( return NSS_STATUS_NOTFOUND; /* Synthesize records for root and nobody, in case they are missing from /etc/group */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (streq(name, root_group.gr_name)) return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop); @@ -494,7 +494,7 @@ enum nss_status _nss_systemd_getgrgid_r( return NSS_STATUS_NOTFOUND; /* Synthesize records for root and nobody, in case they are missing from /etc/group */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (gid == root_group.gr_gid) return copy_synthesized_group(gr, &root_group, buffer, buflen, errnop); @@ -539,7 +539,7 @@ enum nss_status _nss_systemd_getsgnam_r( return NSS_STATUS_NOTFOUND; /* Synthesize records for root and nobody, in case they are missing from /etc/group */ - if (getenv_bool_secure("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { + if (secure_getenv_bool("SYSTEMD_NSS_BYPASS_SYNTHETIC") <= 0) { if (streq(name, root_sgrp.sg_namp)) return copy_synthesized_sgrp(sgrp, &root_sgrp, buffer, buflen, errnop); diff --git a/src/nss-systemd/userdb-glue.c b/src/nss-systemd/userdb-glue.c index c69667d..b02d89a 100644 --- a/src/nss-systemd/userdb-glue.c +++ b/src/nss-systemd/userdb-glue.c @@ -14,7 +14,7 @@ UserDBFlags nss_glue_userdb_flags(void) { UserDBFlags flags = USERDB_EXCLUDE_NSS; /* Make sure that we don't go in circles when allocating a dynamic UID by checking our own database */ - if (getenv_bool_secure("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) + if (secure_getenv_bool("SYSTEMD_NSS_DYNAMIC_BYPASS") > 0) flags |= USERDB_EXCLUDE_DYNAMIC_USER; return flags; diff --git a/src/oom/oomctl.c b/src/oom/oomctl.c index eb15f50..00914f3 100644 --- a/src/oom/oomctl.c +++ b/src/oom/oomctl.c @@ -119,9 +119,7 @@ static int run(int argc, char* argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/oom/oomd-manager-bus.c b/src/oom/oomd-manager-bus.c index 0581d58..7d2edb5 100644 --- a/src/oom/oomd-manager-bus.c +++ b/src/oom/oomd-manager-bus.c @@ -22,7 +22,7 @@ static int bus_method_dump_by_fd(sd_bus_message *message, void *userdata, sd_bus if (r < 0) return r; - fd = acquire_data_fd(dump, strlen(dump), 0); + fd = acquire_data_fd(dump); if (fd < 0) return fd; diff --git a/src/oom/oomd-manager.c b/src/oom/oomd-manager.c index 6081254..839936c 100644 --- a/src/oom/oomd-manager.c +++ b/src/oom/oomd-manager.c @@ -30,30 +30,17 @@ static void managed_oom_message_destroy(ManagedOOMMessage *message) { free(message->property); } -static int managed_oom_mode(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { - ManagedOOMMode *mode = userdata, m; - const char *s; - - assert(mode); - assert_se(s = json_variant_string(v)); - - m = managed_oom_mode_from_string(s); - if (m < 0) - return json_log(v, flags, m, "%s is not a valid ManagedOOMMode", s); - - *mode = m; - return 0; -} +static JSON_DISPATCH_ENUM_DEFINE(dispatch_managed_oom_mode, ManagedOOMMode, managed_oom_mode_from_string); static int process_managed_oom_message(Manager *m, uid_t uid, JsonVariant *parameters) { JsonVariant *c, *cgroups; int r; static const JsonDispatch dispatch_table[] = { - { "mode", JSON_VARIANT_STRING, managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY }, - { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY }, - { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY }, - { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 }, + { "mode", JSON_VARIANT_STRING, dispatch_managed_oom_mode, offsetof(ManagedOOMMessage, mode), JSON_MANDATORY }, + { "path", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, path), JSON_MANDATORY }, + { "property", JSON_VARIANT_STRING, json_dispatch_string, offsetof(ManagedOOMMessage, property), JSON_MANDATORY }, + { "limit", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(ManagedOOMMessage, limit), 0 }, {}, }; @@ -642,7 +629,7 @@ Manager* manager_free(Manager *m) { sd_event_source_unref(m->mem_pressure_context_event_source); sd_event_unref(m->event); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); hashmap_free(m->monitored_swap_cgroup_contexts); diff --git a/src/oom/oomd-util.c b/src/oom/oomd-util.c index f9f0af2..69d103e 100644 --- a/src/oom/oomd-util.c +++ b/src/oom/oomd-util.c @@ -276,7 +276,7 @@ int oomd_cgroup_kill(const char *path, bool recurse, bool dry_run) { if (r < 0) log_debug_errno(r, "Failed to set user.oomd_kill on kill: %m"); - return set_size(pids_killed) != 0; + return !set_isempty(pids_killed); } typedef void (*dump_candidate_func)(const OomdCGroupContext *ctx, FILE *f, const char *prefix); @@ -321,17 +321,16 @@ int oomd_kill_by_pgscan_rate(Hashmap *h, const char *prefix, bool dry_run, char if (r == -ENOMEM) return r; /* Treat oom as a hard error */ if (r < 0) { - if (ret == 0) - ret = r; + RET_GATHER(ret, r); continue; /* Try to find something else to kill */ } dump_until = MAX(dump_until, i + 1); - char *selected = strdup(sorted[i]->path); - if (!selected) - return -ENOMEM; - *ret_selected = selected; + ret = r; + r = strdup_to(ret_selected, sorted[i]->path); + if (r < 0) + return r; break; } @@ -365,17 +364,16 @@ int oomd_kill_by_swap_usage(Hashmap *h, uint64_t threshold_usage, bool dry_run, if (r == -ENOMEM) return r; /* Treat oom as a hard error */ if (r < 0) { - if (ret == 0) - ret = r; + RET_GATHER(ret, r); continue; /* Try to find something else to kill */ } dump_until = MAX(dump_until, i + 1); - char *selected = strdup(sorted[i]->path); - if (!selected) - return -ENOMEM; - *ret_selected = selected; + ret = r; + r = strdup_to(ret_selected, sorted[i]->path); + if (r < 0) + return r; break; } @@ -442,9 +440,9 @@ int oomd_cgroup_context_acquire(const char *path, OomdCGroupContext **ret) { return log_debug_errno(r, "Error converting pgscan value to uint64_t: %m"); } - ctx->path = strdup(empty_to_root(path)); - if (!ctx->path) - return -ENOMEM; + r = strdup_to(&ctx->path, empty_to_root(path)); + if (r < 0) + return r; *ret = TAKE_PTR(ctx); return 0; diff --git a/src/oom/oomd.c b/src/oom/oomd.c index ecc2eda..0d80310 100644 --- a/src/oom/oomd.c +++ b/src/oom/oomd.c @@ -31,9 +31,12 @@ static int parse_config(void) { {} }; - return config_parse_config_file("oomd.conf", "OOM\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + return config_parse_standard_file_with_dropins( + "systemd/oomd.conf", + "OOM\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); } static int help(void) { @@ -164,7 +167,7 @@ static int run(int argc, char *argv[]) { if (!FLAGS_SET(mask, CGROUP_MASK_MEMORY)) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Requires the cgroup memory controller."); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT) >= 0); if (arg_mem_pressure_usec > 0 && arg_mem_pressure_usec < 1 * USEC_PER_SEC) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "DefaultMemoryPressureDurationSec= must be 0 or at least 1s"); diff --git a/src/partition/growfs.c b/src/partition/growfs.c index 62f3ee6..c1ee18a 100644 --- a/src/partition/growfs.c +++ b/src/partition/growfs.c @@ -55,8 +55,9 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_ return log_error_errno(r, "Failed to open main block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(main_devno)); - if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to query size of \"%s\" (before resize): %m", + r = blockdev_get_device_size(main_devfd, &size); + if (r < 0) + return log_error_errno(r, "Failed to query size of \"%s\" (before resize): %m", main_devpath); log_debug("%s is %"PRIu64" bytes", main_devpath, size); @@ -83,9 +84,9 @@ static int resize_crypt_luks_device(dev_t devno, const char *fstype, dev_t main_ if (r < 0) return log_error_errno(r, "crypt_resize() of %s failed: %m", devpath); - if (ioctl(main_devfd, BLKGETSIZE64, &size) != 0) - log_warning_errno(errno, "Failed to query size of \"%s\" (after resize): %m", - devpath); + r = blockdev_get_device_size(main_devfd, &size); + if (r < 0) + log_warning_errno(r, "Failed to query size of \"%s\" (after resize): %m", devpath); else log_debug("%s is now %"PRIu64" bytes", main_devpath, size); @@ -223,11 +224,11 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = path_is_mount_point(arg_target, NULL, 0); + r = path_is_mount_point(arg_target); if (r < 0) return log_error_errno(r, "Failed to check if \"%s\" is a mount point: %m", arg_target); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a mount point: %m", arg_target); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a mount point.", arg_target); mountfd = open(arg_target, O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (mountfd < 0) @@ -250,8 +251,9 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to open block device " DEVNUM_FORMAT_STR ": %m", DEVNUM_FORMAT_VAL(devno)); - if (ioctl(devfd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to query size of \"%s\": %m", devpath); + r = blockdev_get_device_size(devfd, &size); + if (r < 0) + return log_error_errno(r, "Failed to query size of \"%s\": %m", devpath); log_debug("Resizing \"%s\" to %"PRIu64" bytes...", arg_target, size); diff --git a/src/partition/meson.build b/src/partition/meson.build index 78cde2f..52e1368 100644 --- a/src/partition/meson.build +++ b/src/partition/meson.build @@ -33,7 +33,6 @@ executables += [ 'c_args' : '-DSTANDALONE', 'link_with' : [ libbasic, - libbasic_gcrypt, libshared_fdisk, libshared_static, libsystemd_static, diff --git a/src/partition/repart.c b/src/partition/repart.c index 4fabe1b..6f67d46 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -128,7 +128,7 @@ typedef enum FilterPartitionType { static EmptyMode arg_empty = EMPTY_UNSET; static bool arg_dry_run = true; -static const char *arg_node = NULL; +static char *arg_node = NULL; static char *arg_root = NULL; static char *arg_image = NULL; static char **arg_definitions = NULL; @@ -146,6 +146,8 @@ static bool arg_legend = true; static void *arg_key = NULL; static size_t arg_key_size = 0; static EVP_PKEY *arg_private_key = NULL; +static KeySourceType arg_private_key_source_type = OPENSSL_KEY_SOURCE_FILE; +static char *arg_private_key_source = NULL; static X509 *arg_certificate = NULL; static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_seal_key_handle = 0; @@ -168,12 +170,16 @@ static int arg_offline = -1; static char **arg_copy_from = NULL; static char *arg_copy_source = NULL; static char *arg_make_ddi = NULL; +static char *arg_generate_fstab = NULL; +static char *arg_generate_crypttab = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_definitions, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep); +STATIC_DESTRUCTOR_REGISTER(arg_private_key_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); @@ -185,6 +191,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_from, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); typedef struct FreeArea FreeArea; @@ -214,6 +222,39 @@ typedef enum MinimizeMode { _MINIMIZE_MODE_INVALID = -EINVAL, } MinimizeMode; +typedef struct PartitionMountPoint { + char *where; + char *options; +} PartitionMountPoint; + +static void partition_mountpoint_free_many(PartitionMountPoint *f, size_t n) { + assert(f || n == 0); + + FOREACH_ARRAY(i, f, n) { + free(i->where); + free(i->options); + } + + free(f); +} + +typedef struct PartitionEncryptedVolume { + char *name; + char *keyfile; + char *options; +} PartitionEncryptedVolume; + +static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryptedVolume *c) { + if (!c) + return NULL; + + free(c->name); + free(c->keyfile); + free(c->options); + + return mfree(c); +} + typedef struct Partition { char *definition_path; char **drop_in_files; @@ -259,6 +300,7 @@ typedef struct Partition { char **exclude_files_target; char **make_directories; char **subvolumes; + char *default_subvolume; EncryptMode encrypt; VerityMode verity; char *verity_match_key; @@ -276,6 +318,11 @@ typedef struct Partition { char *split_name_format; char *split_path; + PartitionMountPoint *mountpoints; + size_t n_mountpoints; + + PartitionEncryptedVolume *encrypted_volume; + struct Partition *siblings[_VERITY_MODE_MAX]; LIST_FIELDS(struct Partition, partitions); @@ -425,6 +472,12 @@ static Partition* partition_free(Partition *p) { free(p->split_name_format); unlink_and_free(p->split_path); + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + partition_encrypted_volume_free(p->encrypted_volume); + return mfree(p); } @@ -460,6 +513,12 @@ static void partition_foreignize(Partition *p) { p->read_only = -1; p->growfs = -1; p->verity = VERITY_OFF; + + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + p->encrypted_volume = partition_encrypted_volume_free(p->encrypted_volume); } static bool partition_type_exclude(const GptPartitionType *type) { @@ -1610,6 +1669,41 @@ static int config_parse_make_dirs( } } +static int config_parse_default_subvolume( + 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 **subvol = ASSERT_PTR(data); + _cleanup_free_ char *p = NULL; + int r; + + if (isempty(rvalue)) { + *subvol = mfree(*subvol); + return 0; + } + + r = specifier_printf(rvalue, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &p); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to expand specifiers in DefaultSubvolume= parameter, ignoring: %s", rvalue); + return 0; + } + + r = path_simplify_and_warn(p, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + return free_and_replace(*subvol, p); +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_encrypt, encrypt_mode, EncryptMode, ENCRYPT_OFF, "Invalid encryption mode"); static int config_parse_gpt_flags( @@ -1677,41 +1771,164 @@ static int config_parse_uuid( return 0; } +static int config_parse_mountpoint( + 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_free_ char *where = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &where, &options); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + r = path_simplify_and_warn(where, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + if (!GREEDY_REALLOC(p->mountpoints, p->n_mountpoints + 1)) + return log_oom(); + + p->mountpoints[p->n_mountpoints++] = (PartitionMountPoint) { + .where = TAKE_PTR(where), + .options = TAKE_PTR(options), + }; + + return 0; +} + +static int config_parse_encrypted_volume( + 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_free_ char *volume = NULL, *keyfile = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + p->encrypted_volume = mfree(p->encrypted_volume); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &volume, &keyfile, &options); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (!filename_is_valid(volume)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Volume name %s is not valid, ignoring", volume); + return 0; + } + + partition_encrypted_volume_free(p->encrypted_volume); + + p->encrypted_volume = new(PartitionEncryptedVolume, 1); + if (!p->encrypted_volume) + return log_oom(); + + *p->encrypted_volume = (PartitionEncryptedVolume) { + .name = TAKE_PTR(volume), + .keyfile = TAKE_PTR(keyfile), + .options = TAKE_PTR(options), + }; + + return 0; +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode"); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF, "Invalid minimize mode"); static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) { ConfigTableItem table[] = { - { "Partition", "Type", config_parse_type, 0, &p->type }, - { "Partition", "Label", config_parse_label, 0, &p->new_label }, - { "Partition", "UUID", config_parse_uuid, 0, p }, - { "Partition", "Priority", config_parse_int32, 0, &p->priority }, - { "Partition", "Weight", config_parse_weight, 0, &p->weight }, - { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, - { "Partition", "SizeMinBytes", config_parse_size4096, -1, &p->size_min }, - { "Partition", "SizeMaxBytes", config_parse_size4096, 1, &p->size_max }, - { "Partition", "PaddingMinBytes", config_parse_size4096, -1, &p->padding_min }, - { "Partition", "PaddingMaxBytes", config_parse_size4096, 1, &p->padding_max }, - { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, - { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, - { "Partition", "Format", config_parse_fstype, 0, &p->format }, - { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, - { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, - { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, - { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, - { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, - { "Partition", "Verity", config_parse_verity, 0, &p->verity }, - { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, - { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, - { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, - { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, - { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, - { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, - { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, - { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, - { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, - { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "Type", config_parse_type, 0, &p->type }, + { "Partition", "Label", config_parse_label, 0, &p->new_label }, + { "Partition", "UUID", config_parse_uuid, 0, p }, + { "Partition", "Priority", config_parse_int32, 0, &p->priority }, + { "Partition", "Weight", config_parse_weight, 0, &p->weight }, + { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, + { "Partition", "SizeMinBytes", config_parse_size4096, -1, &p->size_min }, + { "Partition", "SizeMaxBytes", config_parse_size4096, 1, &p->size_max }, + { "Partition", "PaddingMinBytes", config_parse_size4096, -1, &p->padding_min }, + { "Partition", "PaddingMaxBytes", config_parse_size4096, 1, &p->padding_max }, + { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, + { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, + { "Partition", "Format", config_parse_fstype, 0, &p->format }, + { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, + { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, + { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, + { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, + { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, + { "Partition", "Verity", config_parse_verity, 0, &p->verity }, + { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, + { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, + { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, + { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, + { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, + { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, + { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, + { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, + { "Partition", "DefaultSubvolume", config_parse_default_subvolume, 0, &p->default_subvolume }, + { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, + { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "MountPoint", config_parse_mountpoint, 0, p }, + { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, {} }; int r; @@ -1780,20 +1997,20 @@ static int partition_read_definition(Partition *p, const char *path, const char if (p->minimize != MINIMIZE_OFF && !p->format && p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize= can only be enabled if Format= or Verity=hash are set"); + "Minimize= can only be enabled if Format= or Verity=hash are set."); if (p->minimize == MINIMIZE_BEST && (p->format && !fstype_is_ro(p->format)) && p->verity != VERITY_HASH) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize=best can only be used with read-only filesystems or Verity=hash"); + "Minimize=best can only be used with read-only filesystems or Verity=hash."); if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && !mkfs_supports_root_option(p->format) && geteuid() != 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM), - "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=", + "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=.", p->format); if (p->format && fstype_is_ro(p->format) && strv_isempty(p->copy_files) && strv_isempty(p->make_directories)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Cannot format %s filesystem without source files, refusing", p->format); + "Cannot format %s filesystem without source files, refusing.", p->format); if (p->verity != VERITY_OFF || p->encrypt != ENCRYPT_OFF) { r = dlopen_cryptsetup(); @@ -1804,39 +2021,47 @@ static int partition_read_definition(Partition *p, const char *path, const char if (p->verity != VERITY_OFF && !p->verity_match_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "VerityMatchKey= must be set if Verity=%s", verity_mode_to_string(p->verity)); + "VerityMatchKey= must be set if Verity=%s.", verity_mode_to_string(p->verity)); if (p->verity == VERITY_OFF && p->verity_match_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "VerityMatchKey= can only be set if Verity= is not \"%s\"", + "VerityMatchKey= can only be set if Verity= is not \"%s\".", verity_mode_to_string(p->verity)); if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_files || p->copy_blocks_path || p->copy_blocks_auto || p->format || p->make_directories)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s", + "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Encrypting verity hash/data partitions is not supported"); + "Encrypting verity hash/data partitions is not supported."); if (p->verity == VERITY_SIG && !arg_private_key) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Verity signature partition requested but no private key provided (--private-key=)"); + "Verity signature partition requested but no private key provided (--private-key=)."); if (p->verity == VERITY_SIG && !arg_certificate) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "Verity signature partition requested but no PEM certificate provided (--certificate=)"); + "Verity signature partition requested but no PEM certificate provided (--certificate=)."); if (p->verity == VERITY_SIG && (p->size_min != UINT64_MAX || p->size_max != UINT64_MAX)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), - "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s", + "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); if (!strv_isempty(p->subvolumes) && arg_offline > 0) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), - "Subvolumes= cannot be used with --offline=yes"); + "Subvolumes= cannot be used with --offline=yes."); + + if (p->default_subvolume && arg_offline > 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), + "DefaultSubvolume= cannot be used with --offline=yes."); + + if (p->default_subvolume && !path_strv_contains(p->subvolumes, p->default_subvolume)) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "DefaultSubvolume= must be one of the paths in Subvolumes=."); /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((IN_SET(p->type.designator, @@ -1940,7 +2165,7 @@ static int determine_current_padding( assert(ret); if (!fdisk_partition_has_end(p)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end."); offset = fdisk_partition_get_end(p); assert(offset < UINT64_MAX); @@ -1955,7 +2180,7 @@ static int determine_current_padding( q = fdisk_table_get_partition(t, i); if (!q) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(q) <= 0) continue; @@ -2041,7 +2266,7 @@ static int context_copy_from_one(Context *context, const char *src) { p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) continue; @@ -2191,21 +2416,21 @@ static int context_read_definitions(Context *context) { if (r == -ENXIO) { if (mode != VERITY_SIG) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Missing verity %s partition for verity %s partition with VerityMatchKey=%s", + "Missing verity %s partition for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); } else if (r == -ENOTUNIQ) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Multiple verity %s partitions found for verity %s partition with VerityMatchKey=%s", + "Multiple verity %s partitions found for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); else if (r < 0) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, r, - "Failed to find verity %s partition for verity %s partition with VerityMatchKey=%s", + "Failed to find verity %s partition for verity %s partition with VerityMatchKey=%s.", verity_mode_to_string(mode), verity_mode_to_string(p->verity), p->verity_match_key); if (q) { if (q->priority != p->priority) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s", + "Priority mismatch (%i != %i) for verity sibling partitions with VerityMatchKey=%s.", p->priority, q->priority, p->verity_match_key); p->siblings[mode] = q; @@ -2226,8 +2451,7 @@ static int context_read_definitions(Context *context) { if (dp->minimize == MINIMIZE_OFF && !(dp->copy_blocks_path || dp->copy_blocks_auto)) return log_syntax(NULL, LOG_ERR, p->definition_path, 1, SYNTHETIC_ERRNO(EINVAL), - "Minimize= set for verity hash partition but data partition does " - "not set CopyBlocks= or Minimize="); + "Minimize= set for verity hash partition but data partition does not set CopyBlocks= or Minimize=."); } @@ -2336,7 +2560,7 @@ static int context_load_partition_table(Context *context) { return log_error_errno(errno, "Failed to stat %s: %m", context->node); if (IN_SET(arg_empty, EMPTY_REQUIRE, EMPTY_FORCE, EMPTY_CREATE) && S_ISREG(st.st_mode)) - /* Don't probe sector size from partition table if we are supposed to strat from an empty disk */ + /* Don't probe sector size from partition table if we are supposed to start from an empty disk */ fs_secsz = ssz = 512; else { /* Auto-detect sector size if not specified. */ @@ -2506,7 +2730,7 @@ static int context_load_partition_table(Context *context) { p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) continue; @@ -3149,8 +3373,8 @@ static int context_dump(Context *context, bool late) { if (r < 0) return r; - /* Make sure we only write the partition bar once, even if we're writing the partition table twice to - * communicate roothashes. */ + /* Only write the partition bar once, even if we're writing the partition table twice to communicate + * roothashes. */ if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) && !late) { putc('\n', stdout); @@ -3556,6 +3780,11 @@ static int prepare_temporary_file(PartitionTarget *t, uint64_t size) { return 0; } +static bool loop_device_error_is_fatal(const Partition *p, int r) { + assert(p); + return arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes) || p->default_subvolume; +} + static int partition_target_prepare( Context *context, Partition *p, @@ -3595,7 +3824,7 @@ static int partition_target_prepare( if (arg_offline <= 0) { r = loop_device_make(whole_fd, O_RDWR, p->offset, size, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && (arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes))) + if (r < 0 && loop_device_error_is_fatal(p, r)) return log_error_errno(r, "Failed to make loopback device of future partition %" PRIu64 ": %m", p->partno); if (r >= 0) { t->loop = TAKE_PTR(d); @@ -3669,7 +3898,7 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget if (st.st_size > (off_t) p->new_size) return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), - "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s)", + "Partition %" PRIu64 "'s contents (%s) don't fit in the partition (%s).", p->partno, FORMAT_BYTES(st.st_size), FORMAT_BYTES(p->new_size)); r = copy_bytes(t->fd, whole_fd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_FSYNC); @@ -3700,7 +3929,9 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta .flags = CRYPT_REENCRYPT_INITIALIZE_ONLY|CRYPT_REENCRYPT_MOVE_FIRST_SEGMENT, }; _cleanup_(sym_crypt_freep) struct crypt_device *cd = NULL; +#if HAVE_TPM2 _cleanup_(erase_and_freep) char *base64_encoded = NULL; +#endif _cleanup_fclose_ FILE *h = NULL; _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; @@ -3792,17 +4023,15 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta if (IN_SET(p->encrypt, ENCRYPT_TPM2, ENCRYPT_KEY_FILE_TPM2)) { #if HAVE_TPM2 + _cleanup_(iovec_done) struct iovec pubkey = {}, blob = {}, srk = {}; + _cleanup_(iovec_done_erase) struct iovec secret = {}; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - _cleanup_(erase_and_freep) void *secret = NULL; - _cleanup_free_ void *pubkey = NULL; - _cleanup_free_ void *blob = NULL, *srk_buf = NULL; - size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; ssize_t base64_encoded_size; int keyslot; TPM2Flags flags = 0; if (arg_tpm2_public_key_pcr_mask != 0) { - r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey, &pubkey_size); + r = tpm2_load_pcr_public_key(arg_tpm2_public_key, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { if (arg_tpm2_public_key || r != -ENOENT) return log_error_errno(r, "Failed to read TPM PCR public key: %m"); @@ -3813,8 +4042,8 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta } TPM2B_PUBLIC public; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (iovec_is_set(&pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); if (r < 0) return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); } @@ -3871,7 +4100,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta r = tpm2_calculate_sealing_policy( arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, arg_tpm2_pcrlock ? &pcrlock_policy : NULL, &policy); @@ -3883,25 +4112,25 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta arg_tpm2_seal_key_handle, &device_key_public, /* attributes= */ NULL, - /* secret= */ NULL, /* secret_size= */ 0, + /* secret= */ NULL, &policy, /* pin= */ NULL, - &secret, &secret_size, - &blob, &blob_size, - &srk_buf, &srk_buf_size); + &secret, + &blob, + &srk); else r = tpm2_seal(tpm2_context, arg_tpm2_seal_key_handle, &policy, /* pin= */ NULL, - &secret, &secret_size, - &blob, &blob_size, + &secret, + &blob, /* ret_primary_alg= */ NULL, - &srk_buf, &srk_buf_size); + &srk); if (r < 0) return log_error_errno(r, "Failed to seal to TPM2: %m"); - base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); + base64_encoded_size = base64mem(secret.iov_base, secret.iov_len, &base64_encoded); if (base64_encoded_size < 0) return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); @@ -3923,13 +4152,14 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta keyslot, hash_pcr_mask, hash_pcr_bank, - pubkey, pubkey_size, + &pubkey, arg_tpm2_public_key_pcr_mask, /* primary_alg= */ 0, - blob, blob_size, - policy.buffer, policy.size, - NULL, 0, /* no salt because tpm2_seal has no pin */ - srk_buf, srk_buf_size, + &blob, + &IOVEC_MAKE(policy.buffer, policy.size), + /* salt= */ NULL, /* no salt because tpm2_seal has no pin */ + &srk, + &pcrlock_policy.nv_handle, flags, &v); if (r < 0) @@ -4032,7 +4262,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta return 0; #else return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "libcryptsetup is not supported or is missing required symbols, cannot encrypt: %m"); + "libcryptsetup is not supported or is missing required symbols, cannot encrypt."); #endif } @@ -4060,7 +4290,7 @@ static int partition_format_verity_hash( if (PARTITION_EXISTS(p)) /* Never format existing partitions */ return 0; - /* Minimized partitions will use the copy blocks logic so let's make sure to skip those here. */ + /* Minimized partitions will use the copy blocks logic so skip those here. */ if (p->copy_blocks_fd >= 0) return 0; @@ -4153,7 +4383,7 @@ static int partition_format_verity_hash( return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "libcryptsetup is not supported, cannot setup verity hashes."); #endif } @@ -4190,12 +4420,11 @@ static int sign_verity_roothash( return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert PKCS7 signature to DER: %s", ERR_error_string(ERR_get_error(), NULL)); - ret_signature->iov_base = TAKE_PTR(sig); - ret_signature->iov_len = sigsz; + *ret_signature = IOVEC_MAKE(TAKE_PTR(sig), sigsz); return 0; #else - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot setup verity signature: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot setup verity signature."); #endif } @@ -4250,7 +4479,7 @@ static int partition_format_verity_sig(Context *context, Partition *p) { return log_error_errno(r, "Failed to format verity signature JSON object: %m"); if (strlen(text)+1 > p->new_size) - return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Verity signature too long for partition: %m"); + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Verity signature too long for partition."); r = strgrowpad0(&text, p->new_size); if (r < 0) @@ -4547,6 +4776,30 @@ static int make_subvolumes_set( return 0; } +static usec_t epoch_or_infinity(void) { + static usec_t cache; + static bool cached = false; + uint64_t epoch; + int r; + + if (cached) + return cache; + + r = secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch); + if (r >= 0) { + if (epoch <= UINT64_MAX / USEC_PER_SEC) { /* Overflow check */ + cached = true; + return (cache = epoch * USEC_PER_SEC); + } + r = -ERANGE; + } + if (r != -ENXIO) + log_debug_errno(r, "Failed to parse $SOURCE_DATE_EPOCH, ignoring: %m"); + + cached = true; + return (cache = USEC_INFINITY); +} + static int do_copy_files(Context *context, Partition *p, const char *root) { int r; @@ -4582,6 +4835,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; + usec_t ts = epoch_or_infinity(); r = make_copy_files_denylist(context, p, *source, *target, &denylist); if (r < 0) @@ -4620,7 +4874,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory '%s': %m", dn); @@ -4660,7 +4914,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory: %m"); @@ -4679,6 +4933,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { (void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS); (void) copy_access(sfd, tfd); (void) copy_times(sfd, tfd, 0); + + if (ts != USEC_INFINITY) { + struct timespec tspec; + timespec_store(&tspec, ts); + + if (futimens(pfd, (const struct timespec[2]) { TIMESPEC_OMIT, tspec }) < 0) + return -errno; + } } } @@ -4692,7 +4954,7 @@ static int do_make_directories(Partition *p, const char *root) { assert(root); STRV_FOREACH(d, p->make_directories) { - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, p->subvolumes); + r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); } @@ -4700,6 +4962,27 @@ static int do_make_directories(Partition *p, const char *root) { return 0; } +static int set_default_subvolume(Partition *p, const char *root) { + _cleanup_free_ char *path = NULL; + int r; + + assert(p); + assert(root); + + if (!p->default_subvolume) + return 0; + + path = path_join(root, p->default_subvolume); + if (!path) + return log_oom(); + + r = btrfs_subvol_make_default(path); + if (r < 0) + return log_error_errno(r, "Failed to make '%s' the default subvolume: %m", p->default_subvolume); + + return 0; +} + static bool partition_needs_populate(Partition *p) { assert(p); return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories); @@ -4774,6 +5057,9 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c if (do_make_directories(p, fs) < 0) _exit(EXIT_FAILURE); + if (set_default_subvolume(p, fs) < 0) + _exit(EXIT_FAILURE); + r = syncfs_path(AT_FDCWD, fs); if (r < 0) { log_error_errno(r, "Failed to synchronize written files: %m"); @@ -4808,7 +5094,7 @@ static int context_mkfs(Context *context) { if (!p->format) continue; - /* Minimized partitions will use the copy blocks logic so let's make sure to skip those here. */ + /* Minimized partitions will use the copy blocks logic so skip those here. */ if (p->copy_blocks_fd >= 0) continue; @@ -4819,7 +5105,7 @@ static int context_mkfs(Context *context) { assert(p->new_size != UINT64_MAX); assert(p->new_size >= (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0)); - /* If we're doing encryption, we make sure we keep free space at the end which is required + /* If we're doing encryption, keep free space at the end which is required * for cryptsetup's offline encryption. */ r = partition_target_prepare(context, p, p->new_size - (p->encrypt != ENCRYPT_OFF ? LUKS2_METADATA_KEEP_FREE : 0), @@ -4868,8 +5154,8 @@ static int context_mkfs(Context *context) { return r; /* The mkfs binary we invoked might have removed our temporary file when we're not operating - * on a loop device, so let's make sure we open the file again to make sure our file - * descriptor points to any potential new file. */ + * on a loop device, so open the file again to make sure our file descriptor points to actual + * new file. */ if (t->fd >= 0 && t->path && !t->loop) { safe_close(t->fd); @@ -5469,7 +5755,7 @@ static int split_name_resolve(Context *context) { continue; return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "%s and %s have the same resolved split name \"%s\", refusing", + "%s and %s have the same resolved split name \"%s\", refusing.", p->definition_path, q->definition_path, p->split_path); } } @@ -5829,7 +6115,7 @@ static int find_backing_devno( if (r < 0) return r; - r = path_is_mount_point(resolved, NULL, 0); + r = path_is_mount_point(resolved); if (r < 0) return r; if (r == 0) /* Not a mount point, then it's not a partition of its own, let's not automatically use it. */ @@ -6060,10 +6346,11 @@ static int context_open_copy_block_paths( if (S_ISREG(st.st_mode)) size = st.st_size; else if (S_ISBLK(st.st_mode)) { - if (ioctl(source_fd, BLKGETSIZE64, &size) != 0) - return log_error_errno(errno, "Failed to determine size of block device to copy from: %m"); + r = blockdev_get_device_size(source_fd, &size); + if (r < 0) + return log_error_errno(r, "Failed to determine size of block device to copy from: %m"); } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing: %m", opened); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing.", opened); if (size <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened); @@ -6130,6 +6417,177 @@ static int fd_apparent_size(int fd, uint64_t *ret) { return 0; } +static bool need_fstab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (!p->format) + return false; + + if (p->n_mountpoints == 0) + return false; + + return true; +} + +static bool need_fstab(const Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_fstab_one(p)) + return true; + + return false; +} + +static int context_fstab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_fstab) + return false; + + if (!need_fstab(context)) { + log_notice("MountPoint= is not specified for any eligible partitions, not generating %s", + arg_generate_fstab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_fstab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *what = NULL, *options = NULL; + + if (!need_fstab_one(p)) + continue; + + what = strjoin("UUID=", SD_ID128_TO_UUID_STRING(p->fs_uuid)); + if (!what) + return log_oom(); + + FOREACH_ARRAY(mountpoint, p->mountpoints, p->n_mountpoints) { + r = partition_pick_mount_options( + p->type.designator, + p->format, + /* rw= */ true, + /* discard= */ !IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR), + &options, + NULL); + if (r < 0) + return r; + + if (!strextend_with_separator(&options, ",", mountpoint->options)) + return log_oom(); + + fprintf(f, "%s %s %s %s 0 %i\n", + what, + mountpoint->where, + p->format, + options, + p->type.designator == PARTITION_ROOT ? 1 : 2); + } + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + +static bool need_crypttab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (p->encrypt == ENCRYPT_OFF) + return false; + + if (!p->encrypted_volume) + return false; + + return true; +} + +static bool need_crypttab(Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_crypttab_one(p)) + return true; + + return false; +} + +static int context_crypttab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_crypttab) + return false; + + if (!need_crypttab(context)) { + log_notice("EncryptedVolume= is not specified for any eligible partitions, not generating %s", + arg_generate_crypttab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_crypttab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *volume = NULL; + + if (!need_crypttab_one(p)) + continue; + + if (!p->encrypted_volume->name && asprintf(&volume, "luks-%s", SD_ID128_TO_UUID_STRING(p->luks_uuid)) < 0) + return log_oom(); + + fprintf(f, "%s UUID=%s %s %s\n", + p->encrypted_volume->name ?: volume, + SD_ID128_TO_UUID_STRING(p->luks_uuid), + isempty(p->encrypted_volume->keyfile) ? "-" : p->encrypted_volume->keyfile, + strempty(p->encrypted_volume->options)); + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + static int context_minimize(Context *context) { const char *vt = NULL; int r; @@ -6197,7 +6655,7 @@ static int context_minimize(Context *context) { if (arg_offline <= 0) { r = loop_device_make(fd, O_RDWR, 0, UINT64_MAX, context->sector_size, 0, LOCK_EX, &d); - if (r < 0 && (arg_offline == 0 || (r != -ENOENT && !ERRNO_IS_PRIVILEGE(r)) || !strv_isempty(p->subvolumes))) + if (r < 0 && loop_device_error_is_fatal(p, r)) return log_error_errno(r, "Failed to make loopback device of %s: %m", temp); } @@ -6211,7 +6669,7 @@ static int context_minimize(Context *context) { if (!d || fstype_is_ro(p->format)) { if (!mkfs_supports_root_option(p->format)) return log_error_errno(SYNTHETIC_ERRNO(ENODEV), - "Loop device access is required to populate %s filesystems", + "Loop device access is required to populate %s filesystems.", p->format); r = partition_populate_directory(context, p, &root); @@ -6293,10 +6751,10 @@ static int context_minimize(Context *context) { d = loop_device_unref(d); /* Erase the previous filesystem first. */ - if (ftruncate(fd, 0)) + if (ftruncate(fd, 0) < 0) return log_error_errno(errno, "Failed to erase temporary file: %m"); - if (ftruncate(fd, fsz)) + if (ftruncate(fd, fsz) < 0) return log_error_errno(errno, "Failed to truncate temporary file to %s: %m", FORMAT_BYTES(fsz)); if (arg_offline <= 0) { @@ -6459,8 +6917,14 @@ static int help(void) { " 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" - " signatures\n" + " --private-key=PATH|URI\n" + " Private key to use when generating verity roothash\n" + " signatures, or an engine or provider specific\n" + " designation if --private-key-source= is used\n" + " --private-key-source=file|provider:PROVIDER|engine:ENGINE\n" + " Specify how to use KEY for --private-key=. Allows\n" + " an OpenSSL engine/provider to be used when generating\n" + " verity roothash signatures\n" " --certificate=PATH PEM certificate to use when generating verity\n" " roothash signatures\n" " --tpm2-device=PATH Path to TPM2 device node to use\n" @@ -6496,6 +6960,10 @@ static int help(void) { " -S --make-ddi=sysext Make a system extension DDI\n" " -C --make-ddi=confext Make a configuration extension DDI\n" " -P --make-ddi=portable Make a portable service DDI\n" + " --generate-fstab=PATH\n" + " Write fstab configuration to the given path\n" + " --generate-crypttab=PATH\n" + " Write crypttab configuration to the given path\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -6506,6 +6974,7 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { + _cleanup_free_ char *private_key = NULL; enum { ARG_VERSION = 0x100, @@ -6526,6 +6995,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_JSON, ARG_KEY_FILE, ARG_PRIVATE_KEY, + ARG_PRIVATE_KEY_SOURCE, ARG_CERTIFICATE, ARG_TPM2_DEVICE, ARG_TPM2_DEVICE_KEY, @@ -6544,6 +7014,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_OFFLINE, ARG_COPY_FROM, ARG_MAKE_DDI, + ARG_GENERATE_FSTAB, + ARG_GENERATE_CRYPTTAB, }; static const struct option options[] = { @@ -6566,6 +7038,7 @@ static int parse_argv(int argc, char *argv[]) { { "json", required_argument, NULL, ARG_JSON }, { "key-file", required_argument, NULL, ARG_KEY_FILE }, { "private-key", required_argument, NULL, ARG_PRIVATE_KEY }, + { "private-key-source", required_argument, NULL, ARG_PRIVATE_KEY_SOURCE }, { "certificate", required_argument, NULL, ARG_CERTIFICATE }, { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, @@ -6584,6 +7057,8 @@ static int parse_argv(int argc, char *argv[]) { { "copy-from", required_argument, NULL, ARG_COPY_FROM }, { "copy-source", required_argument, NULL, 's' }, { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, + { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, + { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, {} }; @@ -6751,24 +7226,20 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_PRIVATE_KEY: { - _cleanup_(erase_and_freep) char *k = NULL; - size_t n = 0; - - r = read_full_file_full( - AT_FDCWD, optarg, UINT64_MAX, SIZE_MAX, - READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, - NULL, - &k, &n); + r = free_and_strdup_warn(&private_key, optarg); if (r < 0) - return log_error_errno(r, "Failed to read key file '%s': %m", optarg); + return r; + break; + } - EVP_PKEY_free(arg_private_key); - arg_private_key = NULL; - r = parse_private_key(k, n, &arg_private_key); + case ARG_PRIVATE_KEY_SOURCE: + r = parse_openssl_key_source_argument( + optarg, + &arg_private_key_source, + &arg_private_key_source_type); if (r < 0) return r; break; - } case ARG_CERTIFICATE: { _cleanup_free_ char *cert = NULL; @@ -6903,7 +7374,7 @@ static int parse_argv(int argc, char *argv[]) { case ARG_ARCHITECTURE: r = architecture_from_string(optarg); if (r < 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'", optarg); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid architecture '%s'.", optarg); arg_architecture = r; break; @@ -6967,6 +7438,18 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_GENERATE_FSTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + if (r < 0) + return r; + break; + + case ARG_GENERATE_CRYPTTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -7037,7 +7520,7 @@ static int parse_argv(int argc, char *argv[]) { /* By default operate on /sysusr/ or /sysroot/ when invoked in the initrd. We prefer the * former, if it is mounted, so that we have deterministic behaviour on systems where /usr/ * is vendor-supplied but the root fs formatted on first boot. */ - r = path_is_mount_point("/sysusr/usr", NULL, 0); + r = path_is_mount_point("/sysusr/usr"); if (r <= 0) { if (r < 0 && r != -ENOENT) log_debug_errno(r, "Unable to determine whether /sysusr/usr is a mount point, assuming it is not: %m"); @@ -7049,7 +7532,11 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } - arg_node = argc > optind ? argv[optind] : NULL; + if (argc > optind) { + arg_node = strdup(argv[optind]); + if (!arg_node) + return log_oom(); + } if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -7097,6 +7584,39 @@ static int parse_argv(int argc, char *argv[]) { *p = gpt_partition_type_override_architecture(*p, arg_architecture); } + if (private_key && arg_private_key_source_type == OPENSSL_KEY_SOURCE_FILE) { + _cleanup_(erase_and_freep) char *k = NULL; + size_t n = 0; + + r = read_full_file_full( + AT_FDCWD, private_key, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &k, &n); + if (r < 0) + return log_error_errno(r, "Failed to read key file '%s': %m", private_key); + + r = parse_private_key(k, n, &arg_private_key); + if (r < 0) + return r; + } else if (private_key && + IN_SET(arg_private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)) { + /* This must happen after parse_x509_certificate() is called above, otherwise + * signing later will get stuck as the parsed private key won't have the + * certificate, so this block cannot be inline in ARG_PRIVATE_KEY. */ + r = openssl_load_key_from_token( + arg_private_key_source_type, + arg_private_key_source, + private_key, + &arg_private_key); + if (r < 0) + return log_error_errno( + r, + "Failed to load key '%s' from OpenSSL private key source %s: %m", + private_key, + arg_private_key_source); + } + return 1; } @@ -7301,7 +7821,7 @@ static int find_root(Context *context) { if (r == -EUCLEAN) return btrfs_log_dev_root(LOG_ERR, r, p); if (r != -ENODEV) - return log_error_errno(r, "Failed to determine backing device of %s: %m", p); + return log_error_errno(r, "Failed to determine backing device of %s%s: %m", strempty(arg_root), p); } else return 0; } @@ -7383,8 +7903,9 @@ static int resize_backing_fd( assert(loop_device); - if (ioctl(*fd, BLKGETSIZE64, ¤t_size) < 0) - return log_error_errno(errno, "Failed to determine size of block device %s: %m", node); + r = blockdev_get_device_size(*fd, ¤t_size); + if (r < 0) + return log_error_errno(r, "Failed to determine size of block device %s: %m", node); } else { r = stat_verify_regular(&st); if (r < 0) @@ -7518,9 +8039,7 @@ static int run(int argc, char *argv[]) { bool node_is_our_loop = false; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -7551,7 +8070,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_GPT_ONLY | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -7596,7 +8116,7 @@ static int run(int argc, char *argv[]) { if (!d) return log_oom(); - r = search_and_access(d, F_OK, arg_root, CONF_PATHS_USR_STRV("systemd/repart/definitions"), &dp); + r = search_and_access(d, F_OK, NULL, CONF_PATHS_STRV("systemd/repart/definitions"), &dp); if (r < 0) return log_error_errno(r, "DDI type '%s' is not defined: %m", arg_make_ddi); @@ -7679,6 +8199,14 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_fstab(context); + if (r < 0) + return r; + + r = context_crypttab(context); + if (r < 0) + return r; + r = context_minimize(context); if (r < 0) return r; diff --git a/src/path/path.c b/src/path/path.c index 1e69c6a..3ab0934 100644 --- a/src/path/path.c +++ b/src/path/path.c @@ -218,9 +218,7 @@ static int parse_argv(int argc, char *argv[]) { static int run(int argc, char* argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/pcrextend/pcrextend.c b/src/pcrextend/pcrextend.c index 394c258..ba2b171 100644 --- a/src/pcrextend/pcrextend.c +++ b/src/pcrextend/pcrextend.c @@ -276,18 +276,18 @@ static int vl_method_extend(Varlink *link, JsonVariant *parameters, VarlinkMetho return r; if (!TPM2_PCR_INDEX_VALID(p.pcr)) - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "pcr"))); + return varlink_error_invalid_parameter_name(link, "pcr"); if (p.text) { /* Specifying both the text string and the binary data is not allowed */ if (p.data.iov_base) - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "data"))); + return varlink_error_invalid_parameter_name(link, "data"); r = extend_now(p.pcr, p.text, strlen(p.text), _TPM2_USERSPACE_EVENT_TYPE_INVALID); } else if (p.data.iov_base) r = extend_now(p.pcr, p.data.iov_base, p.data.iov_len, _TPM2_USERSPACE_EVENT_TYPE_INVALID); else - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "text"))); + return varlink_error_invalid_parameter_name(link, "text"); if (r < 0) return r; diff --git a/src/pcrlock/pcrlock-firmware.c b/src/pcrlock/pcrlock-firmware.c index 73c68c2..6fd7363 100644 --- a/src/pcrlock/pcrlock-firmware.c +++ b/src/pcrlock/pcrlock-firmware.c @@ -100,12 +100,12 @@ int validate_firmware_header( if (size < (uint64_t) offsetof(TCG_PCClientPCREvent, event) + (uint64_t) h->eventDataSize) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log too short for TCG_PCClientPCREvent events data."); - if (h->pcrIndex != 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32, h->pcrIndex); if (h->eventType != EV_NO_ACTION) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%" PRIx32, h->eventType); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected event type 0x%08" PRIx32 ". (Probably not a TPM2 event log?)", h->eventType); + if (h->pcrIndex != 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected PCR index %" PRIu32 ". (Probably not a TPM2 event log?)", h->pcrIndex); if (!memeqzero(h->digest, sizeof(h->digest))) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest."); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header has unexpected non-zero digest. (Probably not a TPM2 event log?)"); if (h->eventDataSize < offsetof(TCG_EfiSpecIDEvent, digestSizes)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Event log header too short for TCG_EfiSpecIdEvent."); diff --git a/src/pcrlock/pcrlock.c b/src/pcrlock/pcrlock.c index dde4dd9..1716fb3 100644 --- a/src/pcrlock/pcrlock.c +++ b/src/pcrlock/pcrlock.c @@ -9,14 +9,18 @@ #include "ask-password-api.h" #include "blockdev-util.h" +#include "boot-entry.h" #include "build.h" #include "chase.h" +#include "color-util.h" #include "conf-files.h" +#include "creds-util.h" #include "efi-api.h" #include "env-util.h" #include "escape.h" #include "fd-util.h" #include "fileio.h" +#include "find-esp.h" #include "format-table.h" #include "format-util.h" #include "fs-util.h" @@ -39,13 +43,24 @@ #include "random-util.h" #include "recovery-key.h" #include "sort-util.h" +#include "string-table.h" #include "terminal-util.h" #include "tpm2-util.h" #include "unaligned.h" #include "unit-name.h" #include "utf8.h" +#include "varlink.h" +#include "varlink-io.systemd.PCRLock.h" #include "verbs.h" +typedef enum RecoveryPinMode { + RECOVERY_PIN_HIDE, /* generate a recovery PIN automatically, but don't show it (alias: "no") */ + RECOVERY_PIN_SHOW, /* generate a recovery PIN automatically, and display it to the user */ + RECOVERY_PIN_QUERY, /* asks the user for a PIN to use interactively (alias: "yes") */ + _RECOVERY_PIN_MODE_MAX, + _RECOVERY_PIN_MODE_INVALID = -EINVAL, +} RecoveryPinMode; + static PagerFlags arg_pager_flags = 0; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF|JSON_FORMAT_NEWLINE; static char **arg_components = NULL; @@ -56,15 +71,19 @@ static bool arg_raw_description = false; static char *arg_location_start = NULL; static char *arg_location_end = NULL; static TPM2_HANDLE arg_nv_index = 0; -static bool arg_recovery_pin = false; +static RecoveryPinMode arg_recovery_pin = RECOVERY_PIN_HIDE; static char *arg_policy_path = NULL; static bool arg_force = false; +static BootEntryTokenType arg_entry_token_type = BOOT_ENTRY_TOKEN_AUTO; +static char *arg_entry_token = NULL; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_components, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_pcrlock_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_location_start, freep); STATIC_DESTRUCTOR_REGISTER(arg_location_end, freep); STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); #define PCRLOCK_SECUREBOOT_POLICY_PATH "/var/lib/pcrlock.d/240-secureboot-policy.pcrlock.d/generated.pcrlock" #define PCRLOCK_FIRMWARE_CODE_EARLY_PATH "/var/lib/pcrlock.d/250-firmware-code-early.pcrlock.d/generated.pcrlock" @@ -94,6 +113,14 @@ STATIC_DESTRUCTOR_REGISTER(arg_policy_path, freep); (UINT32_C(1) << TPM2_PCR_SHIM_POLICY) | \ (UINT32_C(1) << TPM2_PCR_SYSTEM_IDENTITY)) +static const char* recovery_pin_mode_table[_RECOVERY_PIN_MODE_MAX] = { + [RECOVERY_PIN_HIDE] = "hide", + [RECOVERY_PIN_SHOW] = "show", + [RECOVERY_PIN_QUERY] = "query", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(recovery_pin_mode, RecoveryPinMode, RECOVERY_PIN_QUERY); + typedef struct EventLogRecordBank EventLogRecordBank; typedef struct EventLogRecord EventLogRecord; typedef struct EventLogRegisterBank EventLogRegisterBank; @@ -549,7 +576,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_SEPARATOR: { if (rec->firmware_payload_size != sizeof(uint32_t)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "EFI separator field has wrong size, ignoring."); + log_warning("EFI separator field has wrong size, ignoring."); goto invalid; } @@ -567,7 +594,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { break; default: - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected separator payload %" PRIu32 ".", val); + log_warning("Unexpected separator payload %" PRIu32 ", ignoring.", val); goto invalid; } @@ -585,7 +612,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { return log_error_errno(r, "Failed to make C string from EFI action string: %m"); if (!string_is_safe(d)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI action string in record, ignoring."); + log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } @@ -597,14 +624,14 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_EFI_GPT_EVENT: { if (rec->firmware_payload_size < sizeof(GptHeader)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "GPT measurement too short, ignoring."); + log_warning("GPT measurement too short, ignoring."); goto invalid; } const GptHeader *h = rec->firmware_payload; if (!gpt_header_has_signature(h)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "GPT measurement does not cover a GPT partition table header, ignoring."); + log_warning("GPT measurement does not cover a GPT partition table header, ignoring."); goto invalid; } @@ -628,7 +655,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { return log_oom(); if (string_has_cc(d, NULL)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Unsafe EFI action string in record, ignoring."); + log_warning("Unsafe EFI action string in record, ignoring."); goto invalid; } @@ -644,7 +671,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { size_t left = rec->firmware_payload_size; if (left == 0) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Empty tagged PC client event, ignoring."); + log_warning("Empty tagged PC client event, ignoring."); goto invalid; } @@ -652,13 +679,13 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { uint64_t m; if (left < offsetof(TCG_PCClientTaggedEvent, taggedEventData)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Tagged PC client event too short, ignoring."); + log_warning("Tagged PC client event too short, ignoring."); goto invalid; } m = offsetof(TCG_PCClientTaggedEvent, taggedEventData) + (uint64_t) tag->taggedEventDataSize; if (left < m) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Tagged PC client event data too short, ignoring."); + log_warning("Tagged PC client event data too short, ignoring."); goto invalid; } @@ -728,7 +755,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { case EV_EFI_PLATFORM_FIRMWARE_BLOB: { const UEFI_PLATFORM_FIRMWARE_BLOB *blob; if (rec->firmware_payload_size != sizeof(UEFI_PLATFORM_FIRMWARE_BLOB)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "EV_EFI_PLATFORM_FIRMWARE_BLOB of wrong size, ignoring."); + log_warning("EV_EFI_PLATFORM_FIRMWARE_BLOB of wrong size, ignoring."); goto invalid; } @@ -747,14 +774,14 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { bool end = false; if (rec->firmware_payload_size < offsetof(UEFI_IMAGE_LOAD_EVENT, devicePath)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path too short, ignoring."); + log_warning("Device path too short, ignoring."); goto invalid; } load = rec->firmware_payload; if (load->lengthOfDevicePath != rec->firmware_payload_size - offsetof(UEFI_IMAGE_LOAD_EVENT, devicePath)) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path size does not match, ignoring."); + log_warning("Device path size does not match, ignoring."); goto invalid; } @@ -764,7 +791,7 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { for (;;) { if (left == 0) { if (!end) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Garbage after device path end, ignoring."); + log_warning("Garbage after device path end, ignoring."); goto invalid; } @@ -772,12 +799,12 @@ static int event_log_record_extract_firmware_description(EventLogRecord *rec) { } if (end) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Garbage after device path end, ignoring."); + log_warning("Garbage after device path end, ignoring."); goto invalid; } if (left < offsetof(packed_EFI_DEVICE_PATH, path) || left < dp->length) { - log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Device path element too short, ignoring."); + log_warning("Device path element too short, ignoring."); goto invalid; } @@ -896,7 +923,7 @@ static int event_log_load_firmware(EventLog *el) { payload_size == 17 && memcmp(payload, "StartupLocality", sizeof("StartupLocality")) == 0) { if (el->startup_locality_found) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "StartupLocality event found twice!"); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "StartupLocality event found twice."); el->startup_locality = ((const uint8_t*) payload)[sizeof("StartupLocality")]; el->startup_locality_found = true; @@ -926,23 +953,30 @@ static int event_log_load_firmware(EventLog *el) { assert(event->digests.count == n_algorithms); for (size_t i = 0; i < n_algorithms; i++, ha = ha_next) { - ha_next = (const uint8_t*) ha + offsetof(TPMT_HA, digest) + algorithms[i].digestSize; - /* The TPMT_HA is not aligned in the record, hence read the hashAlg field via an unaligned read */ assert_cc(__builtin_types_compatible_p(uint16_t, typeof(TPMI_ALG_HASH))); uint16_t hash_alg = unaligned_read_ne16((const uint8_t*) ha + offsetof(TPMT_HA, hashAlg)); - if (hash_alg != algorithms[i].algorithmId) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Hash algorithms in event log record don't match log."); + /* On some systems (some HyperV?) the order of hash algorithms announced in the + * header does not match the order in the records. Let's hence search for the right + * mapping */ + size_t j; + for (j = 0; j < n_algorithms; j++) + if (hash_alg == algorithms[j].algorithmId) + break; + if (j >= n_algorithms) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Hash algorithms in event log record not among those advertised by log header."); - if (!tpm2_hash_alg_to_string(algorithms[i].algorithmId)) + ha_next = (const uint8_t*) ha + offsetof(TPMT_HA, digest) + algorithms[j].digestSize; + + if (!tpm2_hash_alg_to_string(hash_alg)) continue; r = event_log_record_add_bank( record, - algorithms[i].algorithmId, + hash_alg, (const uint8_t*) ha + offsetof(TPMT_HA, digest), - algorithms[i].digestSize, + algorithms[j].digestSize, /* ret= */ NULL); if (r < 0) return log_error_errno(r, "Failed to add bank to event log record: %m"); @@ -1010,7 +1044,7 @@ static int event_log_record_parse_json(EventLogRecord *record, JsonVariant *j) { h = json_variant_by_key(k, "digest"); if (!h) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field lacks 'digest' field"); + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "'digests' field lacks 'digest' field."); r = json_variant_unhex(h, &hash, &hash_size); if (r < 0) @@ -1294,7 +1328,7 @@ static int event_log_calculate_pcrs(EventLog *el) { rec_b = event_log_record_find_bank(*rr, el->algorithms[i]); if (!rec_b) { - log_warning_errno(SYNTHETIC_ERRNO(ENXIO), "Record with missing bank '%s', ignoring.", n); + log_warning("Record with missing bank '%s', ignoring.", n); continue; } @@ -1932,40 +1966,6 @@ static int event_log_map_components(EventLog *el) { return event_log_validate_fully_recognized(el); } -static void hsv_to_rgb( - double h, double s, double v, - uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) { - - double c, x, m, r, g, b; - - assert(s >= 0 && s <= 100); - assert(v >= 0 && v <= 100); - assert(ret_r); - assert(ret_g); - assert(ret_b); - - c = (s / 100.0) * (v / 100.0); - x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); - m = (v / 100) - c; - - if (h >= 0 && h < 60) - r = c, g = x, b = 0.0; - else if (h >= 60 && h < 120) - r = x, g = c, b = 0.0; - else if (h >= 120 && h < 180) - r = 0.0, g = c, b = x; - else if (h >= 180 && h < 240) - r = 0.0, g = x, b = c; - else if (h >= 240 && h < 300) - r = x, g = 0.0, b = c; - else - r = c, g = 0.0, b = x; - - *ret_r = (uint8_t) ((r + m) * 255); - *ret_g = (uint8_t) ((g + m) * 255); - *ret_b = (uint8_t) ((b + m) * 255); -} - #define ANSI_TRUE_COLOR_MAX (7U + 3U + 1U + 3U + 1U + 3U + 2U) static const char *ansi_true_color(uint8_t r, uint8_t g, uint8_t b, char ret[static ANSI_TRUE_COLOR_MAX]) { @@ -2341,7 +2341,7 @@ static int event_determine_primary_algorithm(EventLog *el) { } FOREACH_ARRAY(alg, el->algorithms, el->n_algorithms) { - /* If we have SHA256, focus on that that */ + /* If we have SHA256, focus on that */ if (*alg == TPM2_ALG_SHA256) { el->primary_algorithm = *alg; @@ -2439,6 +2439,75 @@ static int verb_show_log(int argc, char *argv[], void *userdata) { return 0; } +static int event_log_record_to_cel(EventLogRecord *record, uint64_t *recnum, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL; + JsonVariant *cd = NULL; + const char *ct = NULL; + int r; + + assert(record); + assert(recnum); + assert(ret); + + LIST_FOREACH(banks, bank, record->banks) { + r = json_variant_append_arrayb( + &ja, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), + JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size))); + if (r < 0) + return log_error_errno(r, "Failed to append CEL digest entry: %m"); + } + + if (!ja) { + r = json_variant_new_array(&ja, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate JSON array: %m"); + } + + if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) { + _cleanup_free_ char *et = NULL; + const char *z; + + z = tpm2_log_event_type_to_string(record->firmware_event_type); + if (z) { + _cleanup_free_ char *b = NULL; + + b = strreplace(z, "-", "_"); + if (!b) + return log_oom(); + + et = strjoin("EV_", ascii_strupper(b)); + if (!et) + return log_oom(); + } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0) + return log_oom(); + + r = json_build(&fj, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("event_type", et), + JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size))); + if (r < 0) + return log_error_errno(r, "Failed to build firmware event data: %m"); + + cd = fj; + ct = "pcclient_std"; + } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) { + cd = record->userspace_content; + ct = "systemd"; + } + + r = json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr), + JSON_BUILD_PAIR_UNSIGNED("recnum", ++(*recnum)), + JSON_BUILD_PAIR_VARIANT("digests", ja), + JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)), + JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd)))); + if (r < 0) + return log_error_errno(r, "Failed to make CEL record: %m"); + + return 0; +} + static int verb_show_cel(int argc, char *argv[], void *userdata) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; _cleanup_(event_log_freep) EventLog *el = NULL; @@ -2456,64 +2525,13 @@ static int verb_show_cel(int argc, char *argv[], void *userdata) { /* Output the event log in TCG CEL-JSON. */ FOREACH_ARRAY(rr, el->records, el->n_records) { - _cleanup_(json_variant_unrefp) JsonVariant *ja = NULL, *fj = NULL; - EventLogRecord *record = *rr; - JsonVariant *cd = NULL; - const char *ct = NULL; - - LIST_FOREACH(banks, bank, record->banks) { - r = json_variant_append_arrayb( - &ja, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING("hashAlg", tpm2_hash_alg_to_string(bank->algorithm)), - JSON_BUILD_PAIR_HEX("digest", bank->hash.buffer, bank->hash.size))); - if (r < 0) - return log_error_errno(r, "Failed to append CEL digest entry: %m"); - } - - if (!ja) { - r = json_variant_new_array(&ja, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate JSON array: %m"); - } - - if (EVENT_LOG_RECORD_IS_FIRMWARE(record)) { - _cleanup_free_ char *et = NULL; - const char *z; - - z = tpm2_log_event_type_to_string(record->firmware_event_type); - if (z) { - _cleanup_free_ char *b = NULL; - - b = strreplace(z, "-", "_"); - if (!b) - return log_oom(); - - et = strjoin("EV_", ascii_strupper(b)); - if (!et) - return log_oom(); - } else if (asprintf(&et, "%" PRIu32, record->firmware_event_type) < 0) - return log_oom(); + _cleanup_(json_variant_unrefp) JsonVariant *cel = NULL; - r = json_build(&fj, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING("event_type", et), - JSON_BUILD_PAIR_HEX("event_data", record->firmware_payload, record->firmware_payload_size))); - if (r < 0) - return log_error_errno(r, "Failed to build firmware event data: %m"); - - cd = fj; - ct = "pcclient_std"; - } else if (EVENT_LOG_RECORD_IS_USERSPACE(record)) { - cd = record->userspace_content; - ct = "systemd"; - } + r = event_log_record_to_cel(*rr, &recnum, &cel); + if (r < 0) + return r; - r = json_variant_append_arrayb(&array, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_UNSIGNED("pcr", record->pcr), - JSON_BUILD_PAIR_UNSIGNED("recnum", ++recnum), - JSON_BUILD_PAIR_VARIANT("digests", ja), - JSON_BUILD_PAIR_CONDITION(ct, "content_type", JSON_BUILD_STRING(ct)), - JSON_BUILD_PAIR_CONDITION(cd, "content", JSON_BUILD_VARIANT(cd)))); + r = json_variant_append_array(&array, cel); if (r < 0) return log_error_errno(r, "Failed to append CEL record: %m"); } @@ -2603,17 +2621,17 @@ static int verb_list_components(int argc, char *argv[], void *userdata) { } } - if (table_get_rows(table) > 1 || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + if (!table_isempty(table) || !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { 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"); } if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { - if (table_get_rows(table) > 1) - printf("\n%zu components listed.\n", table_get_rows(table) - 1); - else + if (table_isempty(table)) printf("No components defined.\n"); + else + printf("\n%zu components listed.\n", table_get_rows(table) - 1); } return 0; @@ -2661,7 +2679,7 @@ static int make_pcrlock_record( assert_se(a = tpm2_hash_alg_to_string(*pa)); assert_se(md = EVP_get_digestbyname(a)); hash_ssize = EVP_MD_size(md); - assert_se(hash_ssize > 0); + assert(hash_ssize > 0); hash_usize = hash_ssize; hash = malloc(hash_usize); @@ -2690,6 +2708,101 @@ static int make_pcrlock_record( return 0; } +static void evp_md_ctx_free_all(EVP_MD_CTX *(*md)[TPM2_N_HASH_ALGORITHMS]) { + assert(md); + FOREACH_ARRAY(alg, *md, TPM2_N_HASH_ALGORITHMS) + if (*alg) + EVP_MD_CTX_free(*alg); +} + +static int make_pcrlock_record_from_stream( + uint32_t pcr_mask, + FILE *f, + JsonVariant **ret_records) { + + _cleanup_(json_variant_unrefp) JsonVariant *digests = NULL; + _cleanup_(evp_md_ctx_free_all) EVP_MD_CTX *mdctx[TPM2_N_HASH_ALGORITHMS] = {}; + int r; + + assert(f); + assert(ret_records); + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + const EVP_MD *md; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + assert_se(md = EVP_get_digestbyname(a)); + + mdctx[i] = EVP_MD_CTX_new(); + if (!mdctx[i]) + return log_oom(); + + if (EVP_DigestInit_ex(mdctx[i], md, NULL) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize message digest for %s.", a); + } + + for (;;) { + uint8_t buffer[64*1024]; + size_t n; + + n = fread(buffer, 1, sizeof(buffer), f); + if (ferror(f)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to read file."); + if (n == 0 && feof(f)) + break; + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) + if (EVP_DigestUpdate(mdctx[i], buffer, n) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to hash data."); + } + + for (size_t i = 0; i < TPM2_N_HASH_ALGORITHMS; i++) { + const char *a; + int hash_ssize; + unsigned hash_usize; + + assert_se(a = tpm2_hash_alg_to_string(tpm2_hash_algorithms[i])); + hash_ssize = EVP_MD_CTX_size(mdctx[i]); + assert(hash_ssize > 0 && hash_ssize <= EVP_MAX_MD_SIZE); + hash_usize = hash_ssize; + unsigned char hash[hash_usize]; + + if (EVP_DigestFinal_ex(mdctx[i], hash, &hash_usize) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to finalize hash context for algorithn '%s'.", a); + + r = json_variant_append_arrayb( + &digests, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("hashAlg", JSON_BUILD_STRING(a)), + JSON_BUILD_PAIR("digest", JSON_BUILD_HEX(hash, hash_usize)))); + if (r < 0) + return log_error_errno(r, "Failed to build JSON digest object: %m"); + } + + for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { + _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; + + if (!FLAGS_SET(pcr_mask, UINT32_C(1) << i)) + continue; + + r = json_build(&record, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("pcr", JSON_BUILD_UNSIGNED(i)), + JSON_BUILD_PAIR("digests", JSON_BUILD_VARIANT(digests)))); + if (r < 0) + return log_error_errno(r, "Failed to build record object: %m"); + + r = json_variant_append_array(ret_records, record); + if (r < 0) + return log_error_errno(r, "Failed to append to JSON array: %m"); + } + + return 0; +} + static const char *pcrlock_path(const char *default_pcrlock_path) { return arg_pcrlock_path ?: arg_pcrlock_auto ? default_pcrlock_path : NULL; } @@ -2753,10 +2866,8 @@ static int unlink_pcrlock(const char *default_pcrlock_path) { } static int verb_lock_raw(int argc, char *argv[], void *userdata) { - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - _cleanup_free_ char *data = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *records = NULL; _cleanup_fclose_ FILE *f = NULL; - size_t size; int r; if (arg_pcr_mask == 0) @@ -2768,26 +2879,11 @@ static int verb_lock_raw(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - r = read_full_stream(f ?: stdin, &data, &size); + r = make_pcrlock_record_from_stream(arg_pcr_mask, f ?: stdin, &records); if (r < 0) - return log_error_errno(r, "Failed to read data from stdin: %m"); - - for (uint32_t i = 0; i < TPM2_PCRS_MAX; i++) { - _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; - - if (!FLAGS_SET(arg_pcr_mask, UINT32_C(1) << i)) - continue; - - r = make_pcrlock_record(i, data, size, &record); - if (r < 0) - return r; - - r = json_variant_append_array(&array, record); - if (r < 0) - return log_error_errno(r, "Failed to append to JSON array: %m"); - } + return r; - return write_pcrlock(array, NULL); + return write_pcrlock(records, NULL); } static int verb_unlock_simple(int argc, char *argv[], void *userdata) { @@ -2815,7 +2911,7 @@ static int verb_lock_secureboot_policy(int argc, char *argv[], void *userdata) { /* Generates expected records from the current SecureBoot state, as readable in the EFI variables * right now. */ - FOREACH_ARRAY(vv, variables, ELEMENTSOF(variables)) { + FOREACH_ELEMENT(vv, variables) { _cleanup_(json_variant_unrefp) JsonVariant *record = NULL; _cleanup_free_ char *name = NULL; @@ -3099,7 +3195,7 @@ static int verb_lock_gpt(int argc, char *argv[], void *userdata) { if (n < 0) return log_error_errno(errno, "Failed to read GPT header of block device: %m"); if ((size_t) n != sizeof(h)) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read trying to read GPT header."); /* Try a couple of sector sizes */ for (size_t sz = 512; sz <= 4096; sz <<= 1) { @@ -3152,7 +3248,7 @@ static int verb_lock_gpt(int argc, char *argv[], void *userdata) { if (n < 0) return log_error_errno(errno, "Failed to read GPT partition table entries: %m"); if ((size_t) n != member_bufsz) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading GPT partition table entries."); size_t vdata_size = le32toh(p->header_size) + sizeof(le64_t) + member_size * n_members; _cleanup_free_ void *vdata = malloc0(vdata_size); @@ -3795,10 +3891,9 @@ static int verb_unlock_kernel_cmdline(int argc, char *argv[], void *userdata) { } static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { - _cleanup_(json_variant_unrefp) JsonVariant *record = NULL, *array = NULL; - _cleanup_free_ void *data = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *records = NULL; _cleanup_fclose_ FILE *f = NULL; - size_t size; + uint32_t pcr_mask = UINT32_C(1) << TPM2_PCR_KERNEL_INITRD; int r; if (argc >= 2) { @@ -3807,19 +3902,11 @@ static int verb_lock_kernel_initrd(int argc, char *argv[], void *userdata) { return log_error_errno(errno, "Failed to open '%s': %m", argv[1]); } - r = read_full_stream(f ?: stdin, (char**) &data, &size); - if (r < 0) - return log_error_errno(r, "Failed to read data from stdin: %m"); - - r = make_pcrlock_record(TPM2_PCR_KERNEL_INITRD /* = 9 */, data, size, &record); + r = make_pcrlock_record_from_stream(pcr_mask, f ?: stdin, &records); if (r < 0) return r; - r = json_variant_new_array(&array, &record, 1); - if (r < 0) - return log_error_errno(r, "Failed to create record array: %m"); - - r = write_pcrlock(array, PCRLOCK_KERNEL_INITRD_PATH); + r = write_pcrlock(records, PCRLOCK_KERNEL_INITRD_PATH); if (r < 0) return r; @@ -4197,7 +4284,129 @@ static int remove_policy_file(const char *path) { return 1; } -static int verb_make_policy(int argc, char *argv[], void *userdata) { +static int determine_boot_policy_file(char **ret) { + _cleanup_free_ char *path = NULL, *fn = NULL, *joined = NULL; + sd_id128_t machine_id; + int r; + + assert(ret); + + r = find_xbootldr_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &path, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) { + if (r != -ENOKEY) + return log_error_errno(r, "Failed to find XBOOTLDR partition: %m"); + + r = find_esp_and_warn( + /* root= */ NULL, + /* path= */ NULL, + /* unprivileged_mode= */ false, + &path, + /* ret_part= */ NULL, + /* ret_pstart= */ NULL, + /* ret_psize= */ NULL, + /* ret_uuid= */ NULL, + /* ret_devid= */ NULL); + if (r < 0) { + if (r != -ENOKEY) + return log_error_errno(r, "Failed to find ESP partition: %m"); + + *ret = NULL; + return 0; /* not found! */ + } + } + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); + + r = boot_entry_token_ensure( + /* root= */ NULL, + "/etc/kernel", + machine_id, + /* machine_id_is_random = */ false, + &arg_entry_token_type, + &arg_entry_token); + if (r < 0) + return r; + + fn = strjoin("pcrlock.", arg_entry_token, ".cred"); + if (!fn) + return log_oom(); + + if (!filename_is_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name '%s' would not be a valid file name, refusing.", fn); + + joined = path_join(path, "loader/credentials", fn); + if (!joined) + return log_oom(); + + *ret = TAKE_PTR(joined); + return 1; /* found! */ +} + +static int write_boot_policy_file(const char *json_text) { + _cleanup_free_ char *boot_policy_file = NULL; + int r; + + assert(json_text); + + r = determine_boot_policy_file(&boot_policy_file); + if (r < 0) + return r; + if (r == 0) { + log_info("Did not find XBOOTLDR/ESP partition, not writing boot policy file."); + return 0; + } + + _cleanup_free_ char *c = NULL; + r = path_extract_filename(boot_policy_file, &c); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from %s: %m", boot_policy_file); + + ascii_strlower(c); /* lowercase this file, no matter what, since stored on VFAT, and we don't want to + * run into case change incompatibilities */ + + _cleanup_(iovec_done) struct iovec encoded = {}; + r = encrypt_credential_and_warn( + CRED_AES256_GCM_BY_NULL, + c, + now(CLOCK_REALTIME), + /* not_after= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_path_mask= */ 0, + UID_INVALID, + &IOVEC_MAKE_STRING(json_text), + CREDENTIAL_ALLOW_NULL, + &encoded); + if (r < 0) + return log_error_errno(r, "Failed to encode policy as credential: %m"); + + _cleanup_free_ char *base64_buf = NULL; + ssize_t base64_size; + base64_size = base64mem_full(encoded.iov_base, encoded.iov_len, 79, &base64_buf); + if (base64_size < 0) + return base64_size; + + r = write_string_file( + boot_policy_file, + base64_buf, + WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) + return log_error_errno(r, "Failed to write boot policy file to '%s': %m", boot_policy_file); + + log_info("Written new boot policy to '%s'.", boot_policy_file); + return 1; +} + +static int make_policy(bool force, RecoveryPinMode recovery_pin_mode) { int r; /* Here's how this all works: after predicting all possible PCR values for next boot (with @@ -4207,7 +4416,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { * policies). * * Whenever we want to lock an encrypted object (for example FDE) against this policy, we'll use a - * PolicyAuthorizeNV epxression that pins the NV index in the policy, and permits access to any + * PolicyAuthorizeNV expression that pins the NV index in the policy, and permits access to any * policies matching the current NV index contents. * * We grant world-readable read access to the NV index. Write access is controlled by a PIN (which we @@ -4272,11 +4481,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { if (arg_nv_index != 0 && old_policy.nv_index != arg_nv_index) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Stored policy references different NV index (0x%x) than specified (0x%x), refusing.", old_policy.nv_index, arg_nv_index); - if (!arg_force && + if (!force && old_policy.algorithm == el->primary_algorithm && tpm2_pcr_prediction_equal(&old_policy.prediction, &new_prediction, el->primary_algorithm)) { log_info("Prediction is identical to current policy, skipping update."); - return EXIT_SUCCESS; + return 0; /* NOP */ } } @@ -4321,19 +4530,21 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { /* Acquire a recovery PIN, either from the user, or create a randomized one */ _cleanup_(erase_and_freep) char *pin = NULL; - if (arg_recovery_pin) { + if (recovery_pin_mode == RECOVERY_PIN_QUERY) { r = getenv_steal_erase("PIN", &pin); if (r < 0) return log_error_errno(r, "Failed to acquire PIN from environment: %m"); if (r == 0) { _cleanup_(strv_free_erasep) char **l = NULL; + AskPasswordRequest req = { + .message = "Recovery PIN", + .id = "pcrlock-recovery-pin", + .credential = "pcrlock.recovery-pin", + }; + r = ask_password_auto( - "Recovery PIN", - /* icon= */ NULL, - /* id= */ "pcrlock-recovery-pin", - /* key_name= */ NULL, - /* credential_name= */ "systemd-pcrlock.recovery-pin", + &req, /* until= */ 0, /* flags= */ 0, &l); @@ -4348,16 +4559,16 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { } } else if (!have_old_policy) { - char rnd[256]; - - r = crypto_random_bytes(rnd, sizeof(rnd)); + r = make_recovery_key(&pin); if (r < 0) return log_error_errno(r, "Failed to generate a randomized recovery PIN: %m"); - (void) base64mem(rnd, sizeof(rnd), &pin); - explicit_bzero_safe(rnd, sizeof(rnd)); - if (!pin) - return log_oom(); + if (recovery_pin_mode == RECOVERY_PIN_SHOW) + printf("%s Selected recovery PIN is: %s%s%s\n", + special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), + ansi_highlight_cyan(), + pin, + ansi_normal()); } _cleanup_(tpm2_handle_freep) Tpm2Handle *nv_handle = NULL; @@ -4375,7 +4586,7 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { CLEANUP_ERASE(auth); if (pin) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); if (r < 0) return log_error_errno(r, "Failed to hash PIN: %m"); } else { @@ -4442,15 +4653,28 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { log_info("Retrieved PIN from TPM2 in %s.", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), pin_start_usec), 1)); } - TPM2B_NV_PUBLIC nv_public = {}; + /* Now convert the PIN into an HMAC-SHA256 key that we can use in PolicySigned to protect access to the nvindex with */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *pin_handle = NULL; + r = tpm2_hmac_key_from_pin(tc, encryption_session, &auth, &pin_handle); + if (r < 0) + return r; + TPM2B_NV_PUBLIC nv_public = {}; usec_t nv_index_start_usec = now(CLOCK_MONOTONIC); if (!iovec_is_set(&nv_blob)) { + _cleanup_(Esys_Freep) TPM2B_NAME *pin_name = NULL; + r = tpm2_get_name( + tc, + pin_handle, + &pin_name); + if (r < 0) + return log_error_errno(r, "Failed to get name of PIN from TPM2: %m"); + TPM2B_DIGEST recovery_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); - r = tpm2_calculate_policy_auth_value(&recovery_policy_digest); + r = tpm2_calculate_policy_signed(&recovery_policy_digest, pin_name); if (r < 0) - return log_error_errno(r, "Failed to calculate authentication value policy: %m"); + return log_error_errno(r, "Failed to calculate PolicySigned policy: %m"); log_debug("Allocating NV index to write PCR policy to..."); r = tpm2_define_policy_nv_index( @@ -4458,8 +4682,6 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { encryption_session, arg_nv_index, &recovery_policy_digest, - pin, - &auth, &nv_index, &nv_handle, &nv_public); @@ -4469,10 +4691,6 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { return log_error_errno(r, "Failed to allocate NV index: %m"); } - r = tpm2_set_auth_binary(tc, nv_handle, &auth); - if (r < 0) - return log_error_errno(r, "Failed to set authentication value on NV index: %m"); - _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; r = tpm2_make_policy_session( tc, @@ -4482,9 +4700,11 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to allocate policy session: %m"); - r = tpm2_policy_auth_value( + r = tpm2_policy_signed_hmac_sha256( tc, policy_session, + pin_handle, + &IOVEC_MAKE(auth.buffer, auth.size), /* ret_policy_digest= */ NULL); if (r < 0) return log_error_errno(r, "Failed to submit authentication value policy: %m"); @@ -4595,9 +4815,15 @@ static int verb_make_policy(int argc, char *argv[], void *userdata) { log_info("Written new policy to '%s' and digest to TPM2 NV index 0x%x.", path, nv_index); + (void) write_boot_policy_file(text); + log_info("Overall time spent: %s", FORMAT_TIMESPAN(usec_sub_unsigned(now(CLOCK_MONOTONIC), start_usec), 1)); - return 0; + return 1; /* installed new policy */ +} + +static int verb_make_policy(int argc, char *argv[], void *userdata) { + return make_policy(arg_force, arg_recovery_pin); } static int undefine_policy_nv_index( @@ -4653,8 +4879,8 @@ static int undefine_policy_nv_index( return 0; } -static int verb_remove_policy(int argc, char *argv[], void *userdata) { - int r; +static int remove_policy(void) { + int ret = 0, r; _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; r = tpm2_pcrlock_policy_load(arg_policy_path, &policy); @@ -4669,22 +4895,31 @@ static int verb_remove_policy(int argc, char *argv[], void *userdata) { r = undefine_policy_nv_index(policy.nv_index, &policy.nv_handle, &policy.srk_handle); if (r < 0) log_notice("Failed to remove NV index, assuming data out of date, removing policy file."); - } - if (arg_policy_path) { - r = remove_policy_file(arg_policy_path); - if (r < 0) - return r; - - return 0; - } else { - int ret = 0; + RET_GATHER(ret, r); + } + if (arg_policy_path) + RET_GATHER(ret, remove_policy_file(arg_policy_path)); + else { RET_GATHER(ret, remove_policy_file("/var/lib/systemd/pcrlock.json")); RET_GATHER(ret, remove_policy_file("/run/systemd/pcrlock.json")); - - return ret; } + + _cleanup_free_ char *boot_policy_file = NULL; + r = determine_boot_policy_file(&boot_policy_file); + if (r == 0) + log_info("Did not find XBOOTLDR/ESP partition, not removing boot policy file."); + else if (r > 0) { + RET_GATHER(ret, remove_policy_file(boot_policy_file)); + } else + RET_GATHER(ret, r); + + return ret; +} + +static int verb_remove_policy(int argc, char *argv[], void *userdata) { + return remove_policy(); } static int help(int argc, char *argv[], void *userdata) { @@ -4744,6 +4979,8 @@ static int help(int argc, char *argv[], void *userdata) { " --pcrlock=PATH .pcrlock file to write expected PCR measurement to\n" " --policy=PATH JSON file to write policy output to\n" " --force Write policy even if it matches existing policy\n" + " --entry-token=machine-id|os-id|os-image-id|auto|literal:…\n" + " Boot entry token to use for this installation\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -4769,6 +5006,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_PCRLOCK, ARG_POLICY, ARG_FORCE, + ARG_ENTRY_TOKEN, }; static const struct option options[] = { @@ -4785,6 +5023,7 @@ static int parse_argv(int argc, char *argv[]) { { "pcrlock", required_argument, NULL, ARG_PCRLOCK }, { "policy", required_argument, NULL, ARG_POLICY }, { "force", no_argument, NULL, ARG_FORCE }, + { "entry-token", required_argument, NULL, ARG_ENTRY_TOKEN }, {} }; @@ -4900,13 +5139,13 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_RECOVERY_PIN: - r = parse_boolean_argument("--recovery-pin", optarg, &arg_recovery_pin); - if (r < 0) - return r; + arg_recovery_pin = recovery_pin_mode_from_string(optarg); + if (arg_recovery_pin < 0) + return log_error_errno(arg_recovery_pin, "Failed to parse --recovery-pin= mode: %s", optarg); break; case ARG_PCRLOCK: - if (isempty(optarg) || streq(optarg, "-")) + if (empty_or_dash(optarg)) arg_pcrlock_path = mfree(arg_pcrlock_path); else { r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_pcrlock_path); @@ -4918,7 +5157,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_POLICY: - if (isempty(optarg) || streq(optarg, "-")) + if (empty_or_dash(optarg)) arg_policy_path = mfree(arg_policy_path); else { r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_policy_path); @@ -4932,6 +5171,12 @@ static int parse_argv(int argc, char *argv[]) { arg_force = true; break; + 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 '?': return -EINVAL; @@ -4952,6 +5197,14 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); } + r = varlink_invocation(VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) { + arg_varlink = true; + arg_pager_flags |= PAGER_DISABLE; + } + return 1; } @@ -4994,17 +5247,125 @@ static int pcrlock_main(int argc, char *argv[]) { return dispatch_verb(argc, argv, verbs, NULL); } +static int vl_method_read_event_log(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + _cleanup_(event_log_freep) EventLog *el = NULL; + uint64_t recnum = 0; + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + el = event_log_new(); + if (!el) + return log_oom(); + + r = event_log_load(el); + if (r < 0) + return r; + + _cleanup_(json_variant_unrefp) JsonVariant *rec_cel = NULL; + + FOREACH_ARRAY(rr, el->records, el->n_records) { + + if (rec_cel) { + r = varlink_notifyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_VARIANT("record", rec_cel))); + if (r < 0) + return r; + + rec_cel = json_variant_unref(rec_cel); + } + + r = event_log_record_to_cel(*rr, &recnum, &rec_cel); + if (r < 0) + return r; + } + + return varlink_replyb(link, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(rec_cel, "record", JSON_BUILD_VARIANT(rec_cel)))); +} + +typedef struct MethodMakePolicyParameters { + bool force; +} MethodMakePolicyParameters; + +static int vl_method_make_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMakePolicyParameters, force), 0 }, + {} + }; + MethodMakePolicyParameters p = {}; + int r; + + assert(link); + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + r = make_policy(p.force, /* recovery_key= */ RECOVERY_PIN_HIDE); + if (r < 0) + return r; + if (r == 0) + return varlink_error(link, "io.systemd.PCRLock.NoChange", NULL); + + return varlink_reply(link, NULL); +} + +static int vl_method_remove_policy(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + int r; + + assert(link); + + if (json_variant_elements(parameters) > 0) + return varlink_error_invalid_parameter(link, parameters); + + r = remove_policy(); + if (r < 0) + return r; + + return varlink_reply(link, NULL); +} + static int run(int argc, char *argv[]) { int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) return r; + if (arg_varlink) { + _cleanup_(varlink_server_unrefp) VarlinkServer *varlink_server = NULL; + + /* Invocation as Varlink service */ + + r = varlink_server_new(&varlink_server, VARLINK_SERVER_ROOT_ONLY); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_PCRLock); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = varlink_server_bind_method_many( + varlink_server, + "io.systemd.PCRLock.ReadEventLog", vl_method_read_event_log, + "io.systemd.PCRLock.MakePolicy", vl_method_make_policy, + "io.systemd.PCRLock.RemovePolicy", vl_method_remove_policy); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink methods: %m"); + + r = varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return EXIT_SUCCESS; + } + return pcrlock_main(argc, argv); } diff --git a/src/portable/portable.c b/src/portable/portable.c index 3b2a379..53418c4 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -33,6 +33,7 @@ #include "path-lookup.h" #include "portable.h" #include "process-util.h" +#include "rm-rf.h" #include "selinux-util.h" #include "set.h" #include "signal-util.h" @@ -42,6 +43,7 @@ #include "strv.h" #include "tmpfile-util.h" #include "user-util.h" +#include "vpick.h" /* Markers used in the first line of our 20-portable.conf unit file drop-in to determine, that a) the unit file was * dropped there by the portable service logic and b) for which image it was dropped there. */ @@ -181,7 +183,7 @@ static int extract_now( _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; - _cleanup_(lookup_paths_free) LookupPaths paths = {}; + _cleanup_(lookup_paths_done) LookupPaths paths = {}; _cleanup_close_ int os_release_fd = -EBADF; _cleanup_free_ char *os_release_path = NULL; const char *os_release_id; @@ -361,7 +363,13 @@ static int portable_extract_by_path( assert(path); - r = loop_device_make_by_path(path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d); + r = loop_device_make_by_path( + path, + O_RDONLY, + /* sector_size= */ UINT32_MAX, + LO_FLAGS_PARTSCAN, + LOCK_SH, + &d); if (r == -EISDIR) { _cleanup_free_ char *image_name = NULL; @@ -383,6 +391,21 @@ static int portable_extract_by_path( _cleanup_(rmdir_and_freep) char *tmpdir = NULL; _cleanup_close_pair_ int seq[2] = EBADF_PAIR; _cleanup_(sigkill_waitp) pid_t child = 0; + DissectImageFlags flags = + DISSECT_IMAGE_READ_ONLY | + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_DISCARD_ON_LOOP | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + + if (path_is_extension) + flags |= DISSECT_IMAGE_VALIDATE_OS_EXT | (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0); + else + flags |= DISSECT_IMAGE_VALIDATE_OS; /* We now have a loopback block device, let's fork off a child in its own mount namespace, mount it * there, and extract the metadata we need. The metadata is sent from the child back to us. */ @@ -398,14 +421,7 @@ static int portable_extract_by_path( /* verity= */ NULL, /* mount_options= */ NULL, image_policy, - DISSECT_IMAGE_READ_ONLY | - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_DISCARD_ON_LOOP | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES, + flags, &m); if (r == -ENOPKG) sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path); @@ -427,15 +443,8 @@ static int portable_extract_by_path( if (r < 0) return r; if (r == 0) { - DissectImageFlags flags = DISSECT_IMAGE_READ_ONLY; - seq[0] = safe_close(seq[0]); - if (path_is_extension) - flags |= DISSECT_IMAGE_VALIDATE_OS_EXT | (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0); - else - flags |= DISSECT_IMAGE_VALIDATE_OS; - r = dissected_image_mount( m, tmpdir, @@ -556,6 +565,7 @@ static int extract_image_and_extensions( _cleanup_free_ char *id = NULL, *version_id = NULL, *sysext_level = NULL, *confext_level = NULL; _cleanup_(portable_metadata_unrefp) PortableMetadata *os_release = NULL; _cleanup_ordered_hashmap_free_ OrderedHashmap *extension_images = NULL, *extension_releases = NULL; + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; _cleanup_hashmap_free_ Hashmap *unit_files = NULL; _cleanup_strv_free_ char **valid_prefixes = NULL; _cleanup_(image_unrefp) Image *image = NULL; @@ -564,7 +574,27 @@ static int extract_image_and_extensions( assert(name_or_path); - r = image_find_harder(IMAGE_PORTABLE, name_or_path, NULL, &image); + /* If we get a path, then check if it can be resolved with vpick. We need this as we might just + * get a simple image name, which would make vpick error out. */ + if (path_is_absolute(name_or_path)) { + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + name_or_path, + &pick_filter_image_any, + PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE, + &result); + if (r < 0) + return r; + if (!result.path) + return log_debug_errno( + SYNTHETIC_ERRNO(ENOENT), + "No matching entry in .v/ directory %s found.", + name_or_path); + + name_or_path = result.path; + } + + r = image_find_harder(IMAGE_PORTABLE, name_or_path, /* root= */ NULL, &image); if (r < 0) return r; @@ -580,9 +610,29 @@ static int extract_image_and_extensions( } STRV_FOREACH(p, extension_image_paths) { + _cleanup_(pick_result_done) PickResult ext_result = PICK_RESULT_NULL; _cleanup_(image_unrefp) Image *new = NULL; + const char *path = *p; + + if (path_is_absolute(*p)) { + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + *p, + &pick_filter_image_any, + PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE, + &ext_result); + if (r < 0) + return r; + if (!ext_result.path) + return log_debug_errno( + SYNTHETIC_ERRNO(ENOENT), + "No matching entry in .v/ directory %s found.", + *p); - r = image_find_harder(IMAGE_PORTABLE, *p, NULL, &new); + path = ext_result.path; + } + + r = image_find_harder(IMAGE_PORTABLE, path, NULL, &new); if (r < 0) return r; @@ -1341,7 +1391,7 @@ static int attach_unit_file( return 0; } -static int image_symlink( +static int image_target_path( const char *image_path, PortableFlags flags, char **ret) { @@ -1367,37 +1417,66 @@ static int image_symlink( return 0; } -static int install_image_symlink( +static int install_image( const char *image_path, PortableFlags flags, PortableChange **changes, size_t *n_changes) { - _cleanup_free_ char *sl = NULL; + _cleanup_free_ char *target = NULL; int r; assert(image_path); - /* If the image is outside of the image search also link it into it, so that it can be found with short image - * names and is listed among the images. */ + /* If the image is outside of the image search also link it into it, so that it can be found with + * short image names and is listed among the images. If we are operating in mixed mode, the image is + * copied instead. */ if (image_in_search_path(IMAGE_PORTABLE, NULL, image_path)) return 0; - r = image_symlink(image_path, flags, &sl); + r = image_target_path(image_path, flags, &target); if (r < 0) return log_debug_errno(r, "Failed to generate image symlink path: %m"); - (void) mkdir_parents(sl, 0755); + (void) mkdir_parents(target, 0755); + + if (flags & PORTABLE_MIXED_COPY_LINK) { + r = copy_tree(image_path, + target, + UID_INVALID, + GID_INVALID, + COPY_REFLINK | COPY_FSYNC | COPY_FSYNC_FULL | COPY_SYNCFS, + /* denylist= */ NULL, + /* subvolumes= */ NULL); + if (r < 0) + return log_debug_errno( + r, + "Failed to copy %s %s %s: %m", + image_path, + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + target); - if (symlink(image_path, sl) < 0) - return log_debug_errno(errno, "Failed to link %s %s %s: %m", image_path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), sl); + } else { + if (symlink(image_path, target) < 0) + return log_debug_errno( + errno, + "Failed to link %s %s %s: %m", + image_path, + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), + target); + } - (void) portable_changes_add(changes, n_changes, PORTABLE_SYMLINK, sl, image_path); + (void) portable_changes_add( + changes, + n_changes, + (flags & PORTABLE_MIXED_COPY_LINK) ? PORTABLE_COPY : PORTABLE_SYMLINK, + target, + image_path); return 0; } -static int install_image_and_extensions_symlinks( +static int install_image_and_extensions( const Image *image, OrderedHashmap *extension_images, PortableFlags flags, @@ -1410,12 +1489,12 @@ static int install_image_and_extensions_symlinks( assert(image); ORDERED_HASHMAP_FOREACH(ext, extension_images) { - r = install_image_symlink(ext->path, flags, changes, n_changes); + r = install_image(ext->path, flags, changes, n_changes); if (r < 0) return r; } - r = install_image_symlink(image->path, flags, changes, n_changes); + r = install_image(image->path, flags, changes, n_changes); if (r < 0) return r; @@ -1436,6 +1515,7 @@ static void log_portable_verb( const char *verb, const char *message_id, const char *image_path, + const char *profile, OrderedHashmap *extension_images, char **extension_image_paths, PortableFlags flags) { @@ -1494,12 +1574,14 @@ static void log_portable_verb( LOG_CONTEXT_PUSH_STRV(extension_base_names); log_struct(LOG_INFO, - LOG_MESSAGE("Successfully %s%s '%s%s%s'", + LOG_MESSAGE("Successfully %s%s '%s%s%s%s%s'", verb, FLAGS_SET(flags, PORTABLE_RUNTIME) ? " ephemeral" : "", image_path, isempty(extensions_joined) ? "" : "' and its extension(s) '", - strempty(extensions_joined)), + strempty(extensions_joined), + isempty(profile) ? "" : "' using profile '", + strempty(profile)), message_id, "PORTABLE_ROOT=%s", strna(root_base_name)); } @@ -1519,7 +1601,7 @@ int portable_attach( _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_(lookup_paths_done) LookupPaths paths = {}; _cleanup_strv_free_ char **valid_prefixes = NULL; _cleanup_(image_unrefp) Image *image = NULL; PortableMetadata *item; @@ -1608,14 +1690,15 @@ int portable_attach( return sd_bus_error_set_errnof(error, r, "Failed to attach unit '%s': %m", item->name); } - /* We don't care too much for the image symlink, it's just a convenience thing, it's not necessary for proper - * operation otherwise. */ - (void) install_image_and_extensions_symlinks(image, extension_images, flags, changes, n_changes); + /* We don't care too much for the image symlink/copy, it's just a convenience thing, it's not necessary for + * proper operation otherwise. */ + (void) install_image_and_extensions(image, extension_images, flags, changes, n_changes); log_portable_verb( "attached", "MESSAGE_ID=" SD_MESSAGE_PORTABLE_ATTACHED_STR, image->path, + profile, extension_images, /* extension_image_paths= */ NULL, flags); @@ -1650,6 +1733,7 @@ static bool marker_matches_images(const char *marker, const char *name_or_path, while (!isempty(marker)) STRV_FOREACH(image_name_or_path, root_and_extensions) { _cleanup_free_ char *image = NULL, *base_image = NULL, *base_image_name_or_path = NULL; + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; r = extract_first_word(&marker, &image, ":", EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); if (r < 0) @@ -1661,9 +1745,23 @@ static bool marker_matches_images(const char *marker, const char *name_or_path, if (r < 0) return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", image); - r = path_extract_image_name(*image_name_or_path, &base_image_name_or_path); + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + *image_name_or_path, + &pick_filter_image_any, + PICK_ARCHITECTURE|PICK_TRIES|PICK_RESOLVE, + &result); + if (r < 0) + return r; + if (!result.path) + return log_debug_errno( + SYNTHETIC_ERRNO(ENOENT), + "No matching entry in .v/ directory %s found.", + *image_name_or_path); + + r = path_extract_image_name(result.path, &base_image_name_or_path); if (r < 0) - return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", *image_name_or_path); + return log_debug_errno(r, "Failed to extract image name from %s, ignoring: %m", result.path); if (!streq(base_image, base_image_name_or_path)) { if (match_all) @@ -1746,7 +1844,7 @@ int portable_detach( size_t *n_changes, sd_bus_error *error) { - _cleanup_(lookup_paths_free) LookupPaths paths = {}; + _cleanup_(lookup_paths_done) LookupPaths paths = {}; _cleanup_set_free_ Set *unit_files = NULL, *markers = NULL; _cleanup_free_ char *extensions = NULL; _cleanup_closedir_ DIR *d = NULL; @@ -1875,34 +1973,24 @@ int portable_detach( portable_changes_add_with_prefix(changes, n_changes, PORTABLE_UNLINK, where, md, NULL); } - /* Now, also drop any image symlink, for images outside of the sarch path */ + /* Now, also drop any image symlink or copy, for images outside of the sarch path */ SET_FOREACH(item, markers) { - _cleanup_free_ char *sl = NULL; - struct stat st; + _cleanup_free_ char *target = NULL; - r = image_symlink(item, flags, &sl); + r = image_target_path(item, flags, &target); if (r < 0) { - log_debug_errno(r, "Failed to determine image symlink for '%s', ignoring: %m", item); + log_debug_errno(r, "Failed to determine image path for '%s', ignoring: %m", item); continue; } - if (lstat(sl, &st) < 0) { - log_debug_errno(errno, "Failed to stat '%s', ignoring: %m", sl); - continue; - } - - if (!S_ISLNK(st.st_mode)) { - log_debug("Image '%s' is not a symlink, ignoring.", sl); - continue; - } - - if (unlink(sl) < 0) { - log_debug_errno(errno, "Can't remove image symlink '%s': %m", sl); + r = rm_rf(target, REMOVE_ROOT | REMOVE_PHYSICAL | REMOVE_MISSING_OK | REMOVE_SYNCFS); + if (r < 0) { + log_debug_errno(r, "Can't remove image '%s': %m", target); - if (errno != ENOENT && ret >= 0) - ret = -errno; + if (r != -ENOENT) + RET_GATHER(ret, r); } else - portable_changes_add(changes, n_changes, PORTABLE_UNLINK, sl, NULL); + portable_changes_add(changes, n_changes, PORTABLE_UNLINK, target, NULL); } /* Try to remove the unit file directory, if we can */ @@ -1913,6 +2001,7 @@ int portable_detach( "detached", "MESSAGE_ID=" SD_MESSAGE_PORTABLE_DETACHED_STR, name_or_path, + /* profile= */ NULL, /* extension_images= */ NULL, extension_image_paths, flags); @@ -1941,7 +2030,7 @@ static int portable_get_state_internal( PortableState *ret, sd_bus_error *error) { - _cleanup_(lookup_paths_free) LookupPaths paths = {}; + _cleanup_(lookup_paths_done) LookupPaths paths = {}; bool found_enabled = false, found_running = false; _cleanup_set_free_ Set *unit_files = NULL; _cleanup_closedir_ DIR *d = NULL; diff --git a/src/portable/portable.h b/src/portable/portable.h index c4a9d51..f5bb190 100644 --- a/src/portable/portable.h +++ b/src/portable/portable.h @@ -27,7 +27,8 @@ typedef enum PortableFlags { PORTABLE_FORCE_EXTENSION = 1 << 2, /* Public API via DBUS, do not change */ PORTABLE_PREFER_COPY = 1 << 3, PORTABLE_PREFER_SYMLINK = 1 << 4, - PORTABLE_REATTACH = 1 << 5, + PORTABLE_MIXED_COPY_LINK = 1 << 5, + PORTABLE_REATTACH = 1 << 6, _PORTABLE_MASK_PUBLIC = PORTABLE_RUNTIME | PORTABLE_FORCE_ATTACH | PORTABLE_FORCE_EXTENSION, _PORTABLE_TYPE_MAX, _PORTABLE_TYPE_INVALID = -EINVAL, diff --git a/src/portable/portablectl.c b/src/portable/portablectl.c index 1588b17..57b930d 100644 --- a/src/portable/portablectl.c +++ b/src/portable/portablectl.c @@ -50,6 +50,7 @@ static bool arg_now = false; static bool arg_no_block = false; static char **arg_extension_images = NULL; static bool arg_force = false; +static bool arg_clean = false; STATIC_DESTRUCTOR_REGISTER(arg_extension_images, strv_freep); @@ -769,13 +770,49 @@ static int maybe_stop_enable_restart(sd_bus *bus, sd_bus_message *reply) { return 0; } -static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { +static int maybe_clean_units(sd_bus *bus, char **units) { + int r; + + assert(bus); + + if (!arg_clean) + return 0; + + STRV_FOREACH(name, units) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = bus_message_new_method_call(bus, &m, bus_systemd_mgr, "CleanUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", *name); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, STRV_MAKE("all", "fdstore")); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno( + r, + "Failed to call CleanUnit on portable service %s: %s", + *name, + bus_error_message(&error, r)); + } + + return 0; +} + +static int maybe_stop_disable_clean(sd_bus *bus, char *image, char *argv[]) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *wait = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **matches = NULL; + _cleanup_strv_free_ char **matches = NULL, **units = NULL; int r; - if (!arg_enable && !arg_now) + if (!arg_enable && !arg_now && !arg_clean) return 0; r = determine_matches(argv[1], argv + 2, true, &matches); @@ -829,6 +866,10 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { (void) maybe_start_stop_restart(bus, name, "StopUnit", wait); (void) maybe_enable_disable(bus, name, false); + + r = strv_extend(&units, name); + if (r < 0) + return log_oom(); } r = sd_bus_message_exit_container(reply); @@ -840,6 +881,9 @@ static int maybe_stop_disable(sd_bus *bus, char *image, char *argv[]) { if (r < 0) return r; + /* Need to ensure all units are stopped before calling CleanUnit, as files might be in use. */ + (void) maybe_clean_units(bus, units); + return 0; } @@ -942,7 +986,7 @@ static int detach_image(int argc, char *argv[], void *userdata) { (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password); - (void) maybe_stop_disable(bus, image, argv); + (void) maybe_stop_disable_clean(bus, image, argv); method = strv_isempty(arg_extension_images) && !arg_force ? "DetachImage" : "DetachImageWithExtensions"; @@ -1030,7 +1074,7 @@ static int list_images(int argc, char *argv[], void *userdata) { if (r < 0) return bus_log_parse_error(r); - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { r = table_set_sort(table, (size_t) 0); if (r < 0) return table_log_sort_error(r); @@ -1043,10 +1087,10 @@ static int list_images(int argc, char *argv[], void *userdata) { } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu images listed.\n", table_get_rows(table) - 1); - else + if (table_isempty(table)) printf("No images.\n"); + else + printf("\n%zu images listed.\n", table_get_rows(table) - 1); } return 0; @@ -1251,7 +1295,8 @@ static int help(int argc, char *argv[], void *userdata) { " -M --machine=CONTAINER Operate on local container\n" " -q --quiet Suppress informational messages\n" " -p --profile=PROFILE Pick security profile for portable service\n" - " --copy=copy|auto|symlink Prefer copying or symlinks if possible\n" + " --copy=copy|auto|symlink|mixed\n" + " Pick copying or symlinking of resources\n" " --runtime Attach portable service until next reboot only\n" " --no-reload Don't reload the system and service manager\n" " --cat When inspecting include unit and os-release file\n" @@ -1264,6 +1309,9 @@ static int help(int argc, char *argv[], void *userdata) { " --extension=PATH Extend the image with an overlay\n" " --force Skip 'already active' check when attaching or\n" " detaching an image (with extensions)\n" + " --clean When detaching, also remove configuration, state,\n" + " cache, logs or runtime data of the portable\n" + " service(s)\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -1290,6 +1338,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NO_BLOCK, ARG_EXTENSION, ARG_FORCE, + ARG_CLEAN, }; static const struct option options[] = { @@ -1311,6 +1360,7 @@ static int parse_argv(int argc, char *argv[]) { { "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "extension", required_argument, NULL, ARG_EXTENSION }, { "force", no_argument, NULL, ARG_FORCE }, + { "clean", no_argument, NULL, ARG_CLEAN }, {} }; @@ -1372,12 +1422,13 @@ static int parse_argv(int argc, char *argv[]) { case ARG_COPY: if (streq(optarg, "auto")) arg_copy_mode = NULL; - else if (STR_IN_SET(optarg, "copy", "symlink")) + else if (STR_IN_SET(optarg, "copy", "symlink", "mixed")) arg_copy_mode = optarg; else if (streq(optarg, "help")) { puts("auto\n" "copy\n" - "symlink"); + "symlink\n" + "mixed\n"); return 0; } else return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1419,6 +1470,10 @@ static int parse_argv(int argc, char *argv[]) { arg_force = true; break; + case ARG_CLEAN: + arg_clean = true; + break; + case '?': return -EINVAL; diff --git a/src/portable/portabled-bus.c b/src/portable/portabled-bus.c index 0d55180..4f239e2 100644 --- a/src/portable/portabled-bus.c +++ b/src/portable/portabled-bus.c @@ -320,11 +320,8 @@ static int method_detach_image(sd_bus_message *message, void *userdata, sd_bus_e r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.portable1.attach-images", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -377,11 +374,8 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.portable1.manage-images", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) diff --git a/src/portable/portabled-image-bus.c b/src/portable/portabled-image-bus.c index 1f61c3b..8db5574 100644 --- a/src/portable/portabled-image-bus.c +++ b/src/portable/portabled-image-bus.c @@ -366,6 +366,8 @@ int bus_image_common_attach( flags |= PORTABLE_PREFER_SYMLINK; else if (streq(copy_mode, "copy")) flags |= PORTABLE_PREFER_COPY; + else if (streq(copy_mode, "mixed")) + flags |= PORTABLE_MIXED_COPY_LINK; else if (!isempty(copy_mode)) return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode); @@ -451,11 +453,8 @@ static int bus_image_method_detach( r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, "org.freedesktop.portable1.attach-images", - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -698,6 +697,8 @@ int bus_image_common_reattach( flags |= PORTABLE_PREFER_SYMLINK; else if (streq(copy_mode, "copy")) flags |= PORTABLE_PREFER_COPY; + else if (streq(copy_mode, "mixed")) + flags |= PORTABLE_MIXED_COPY_LINK; else if (!isempty(copy_mode)) return sd_bus_reply_method_errorf(message, SD_BUS_ERROR_INVALID_ARGS, "Unknown copy mode '%s'", copy_mode); @@ -1010,11 +1011,8 @@ int bus_image_acquire( if (mode == BUS_IMAGE_AUTHENTICATE_ALL) { r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, polkit_action, - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) @@ -1064,11 +1062,8 @@ int bus_image_acquire( if (mode == BUS_IMAGE_AUTHENTICATE_BY_PATH) { r = bus_verify_polkit_async( message, - CAP_SYS_ADMIN, polkit_action, - NULL, - false, - UID_INVALID, + /* details= */ NULL, &m->polkit_registry, error); if (r < 0) diff --git a/src/portable/portabled.c b/src/portable/portabled.c index 136c5fa..d46ac01 100644 --- a/src/portable/portabled.c +++ b/src/portable/portabled.c @@ -65,7 +65,7 @@ static Manager* manager_unref(Manager *m) { sd_event_source_unref(m->image_cache_defer_event); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); sd_event_unref(m->event); @@ -122,17 +122,6 @@ static bool check_idle(void *userdata) { return !m->operations; } -static int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.portable1", - DEFAULT_EXIT_USEC, - check_idle, m); -} - static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; int r; @@ -152,7 +141,7 @@ static int run(int argc, char *argv[]) { if (argc != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) @@ -162,16 +151,20 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to fully start up daemon: %m"); - log_debug("systemd-portabled running as pid " PID_FMT, getpid_cached()); r = sd_notify(false, NOTIFY_READY); if (r < 0) log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); - r = manager_run(m); + r = bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.portable1", + DEFAULT_EXIT_USEC, + check_idle, m); + if (r < 0) + return log_error_errno(r, "Failed to run main loop: %m"); - (void) sd_notify(false, NOTIFY_STOPPING); - log_debug("systemd-portabled stopped as pid " PID_FMT, getpid_cached()); - return r; + return 0; } DEFINE_MAIN_FUNCTION(run); diff --git a/src/portable/profile/default/service.conf b/src/portable/profile/default/service.conf index 230aa60..5c447d6 100644 --- a/src/portable/profile/default/service.conf +++ b/src/portable/profile/default/service.conf @@ -4,7 +4,7 @@ MountAPIVFS=yes BindReadOnlyPaths=/dev/log /run/systemd/journal/socket /run/systemd/journal/stdout BindReadOnlyPaths=/etc/machine-id -BindReadOnlyPaths=/etc/resolv.conf +BindReadOnlyPaths=-/etc/resolv.conf BindReadOnlyPaths=/run/dbus/system_bus_socket DynamicUser=yes RemoveIPC=yes diff --git a/src/portable/profile/trusted/service.conf b/src/portable/profile/trusted/service.conf index 04deeb2..144d4f6 100644 --- a/src/portable/profile/trusted/service.conf +++ b/src/portable/profile/trusted/service.conf @@ -5,4 +5,4 @@ MountAPIVFS=yes PrivateTmp=yes BindPaths=/run BindReadOnlyPaths=/etc/machine-id -BindReadOnlyPaths=/etc/resolv.conf +BindReadOnlyPaths=-/etc/resolv.conf diff --git a/src/pstore/meson.build b/src/pstore/meson.build index b6fda87..27c2855 100644 --- a/src/pstore/meson.build +++ b/src/pstore/meson.build @@ -7,9 +7,9 @@ executables += [ 'sources' : files('pstore.c'), 'dependencies' : [ libacl, - liblz4, - libxz, - libzstd, + liblz4_cflags, + libxz_cflags, + libzstd_cflags, threads, ], }, diff --git a/src/pstore/pstore.c b/src/pstore/pstore.c index 8f32a0a..e2dfc4d 100644 --- a/src/pstore/pstore.c +++ b/src/pstore/pstore.c @@ -56,9 +56,9 @@ typedef enum PStoreStorage { } PStoreStorage; static const char* const pstore_storage_table[_PSTORE_STORAGE_MAX] = { - [PSTORE_STORAGE_NONE] = "none", + [PSTORE_STORAGE_NONE] = "none", [PSTORE_STORAGE_EXTERNAL] = "external", - [PSTORE_STORAGE_JOURNAL] = "journal", + [PSTORE_STORAGE_JOURNAL] = "journal", }; DEFINE_PRIVATE_STRING_TABLE_LOOKUP(pstore_storage, PStoreStorage); @@ -77,9 +77,12 @@ static int parse_config(void) { {} }; - return config_parse_config_file("pstore.conf", "PStore\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + return config_parse_standard_file_with_dropins( + "systemd/pstore.conf", + "PStore\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); } /* File list handling - PStoreEntry is the struct and @@ -279,6 +282,7 @@ static int process_dmesg_files(PStoreList *list) { } else log_debug("Unknown backend, ignoring \"%s\".", pe->dirent.d_name); } + return 0; } diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 27a914d..d46f280 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -12,11 +12,15 @@ #include "proc-cmdline.h" #include "process-util.h" #include "signal-util.h" +#include "static-destruct.h" #include "string-util.h" +static char *arg_path = NULL; static bool arg_skip = false; static bool arg_force = false; +STATIC_DESTRUCTOR_REGISTER(arg_path, freep); + static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { if (streq(key, "quotacheck.mode")) { @@ -31,12 +35,12 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else if (streq(value, "skip")) arg_skip = true; else - log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value); + log_warning("Invalid quotacheck.mode= value, ignoring: %s", value); } #if HAVE_SYSV_COMPAT else if (streq(key, "forcequotacheck") && !value) { - log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line."); + log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line. Proceeding anyway."); arg_force = true; } #endif @@ -48,7 +52,7 @@ static void test_files(void) { #if HAVE_SYSV_COMPAT if (access("/forcequotacheck", F_OK) >= 0) { - log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system."); + log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system. Proceeding anyway."); arg_force = true; } #endif @@ -59,9 +63,9 @@ static int run(int argc, char *argv[]) { log_setup(); - if (argc > 1) + if (argc > 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This program takes no arguments."); + "This program expects one or no arguments."); umask(0022); @@ -75,17 +79,31 @@ static int run(int argc, char *argv[]) { if (arg_skip) return 0; - if (access("/run/systemd/quotacheck", F_OK) < 0) + /* This is created by systemd-fsck when fsck detected and corrected errors. In normal + * operations quotacheck is not needed. */ + if (access("/run/systemd/quotacheck", F_OK) < 0) { + if (errno != ENOENT) + log_warning_errno(errno, + "Failed to check whether /run/systemd/quotacheck exists, ignoring: %m"); + return 0; + } + } + + if (argc == 2) { + arg_path = strdup(argv[1]); + if (!arg_path) + return log_oom(); } r = safe_fork("(quotacheck)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_WAIT|FORK_LOG, NULL); if (r < 0) return r; if (r == 0) { - static const char * const cmdline[] = { + const char *cmdline[] = { QUOTACHECK, - "-anug", + arg_path ? "-nug" : "-anug", /* Check all file systems if path isn't specified */ + arg_path, NULL }; diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c index da68b41..1af370d 100644 --- a/src/resolve/dns-type.c +++ b/src/resolve/dns-type.c @@ -80,7 +80,7 @@ bool dns_type_is_valid_query(uint16_t type) { DNS_TYPE_RRSIG); } -bool dns_type_is_zone_transer(uint16_t type) { +bool dns_type_is_zone_transfer(uint16_t type) { /* Zone transfers, either normal or incremental */ diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h index c6be190..404256c 100644 --- a/src/resolve/dns-type.h +++ b/src/resolve/dns-type.h @@ -136,7 +136,7 @@ bool dns_type_is_obsolete(uint16_t type); bool dns_type_may_wildcard(uint16_t type); bool dns_type_apex_only(uint16_t type); bool dns_type_needs_authentication(uint16_t type); -bool dns_type_is_zone_transer(uint16_t type); +bool dns_type_is_zone_transfer(uint16_t type); int dns_type_to_af(uint16_t type); bool dns_class_is_pseudo(uint16_t class); diff --git a/src/resolve/fuzz-resource-record.c b/src/resolve/fuzz-resource-record.c index 358a5c7..f792167 100644 --- a/src/resolve/fuzz-resource-record.c +++ b/src/resolve/fuzz-resource-record.c @@ -26,12 +26,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(f = memstream_init(&m)); (void) fprintf(f, "%s", strna(dns_resource_record_to_string(rr))); - if (dns_resource_record_to_json(rr, &v) < 0) - return 0; - - (void) json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, f, NULL); - (void) dns_resource_record_to_wire_format(rr, false); - (void) dns_resource_record_to_wire_format(rr, true); + assert_se(dns_resource_record_to_json(rr, &v) >= 0); + assert_se(json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR|JSON_FORMAT_SOURCE, f, NULL) >= 0); + assert_se(dns_resource_record_to_wire_format(rr, false) >= 0); + assert_se(dns_resource_record_to_wire_format(rr, true) >= 0); return 0; } diff --git a/src/resolve/meson.build b/src/resolve/meson.build index e7867e2..d336b2c 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -118,7 +118,6 @@ if conf.get('ENABLE_DNS_OVER_TLS') == 1 endif link_with = [ - libbasic_gcrypt, libshared, libsystemd_resolve_core, ] @@ -196,6 +195,20 @@ executables += [ ], 'include_directories' : resolve_includes, }, + test_template + { + 'sources' : [ + files('test-resolved-dummy-server.c'), + basic_dns_sources, + systemd_resolved_sources, + ], + 'dependencies' : [ + lib_openssl_or_gcrypt, + libm, + systemd_resolved_dependencies, + ], + 'include_directories' : resolve_includes, + 'type' : 'manual', + }, resolve_fuzz_template + { 'sources' : files('fuzz-dns-packet.c'), }, diff --git a/src/resolve/resolvectl.c b/src/resolve/resolvectl.c index afa537f..f2e9e7a 100644 --- a/src/resolve/resolvectl.c +++ b/src/resolve/resolvectl.c @@ -184,6 +184,9 @@ static void print_source(uint64_t flags, usec_t rtt) { if (!arg_legend) return; + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return; + if (flags == 0) return; @@ -250,6 +253,9 @@ static int resolve_host(sd_bus *bus, const char *name) { assert(name); + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type=A or --type=AAAA to acquire address record information in JSON format."); + log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(arg_ifname) ? "*" : arg_ifname); r = bus_message_new_method_call(bus, &req, bus_resolve_mgr, "ResolveHostname"); @@ -348,6 +354,9 @@ static int resolve_address(sd_bus *bus, int family, const union in_addr_union *a assert(IN_SET(family, AF_INET, AF_INET6)); assert(address); + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + if (ifindex <= 0) ifindex = arg_ifindex; @@ -433,11 +442,26 @@ static int output_rr_packet(const void *d, size_t l, int ifindex) { _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; int r; + assert(d || l == 0); + r = dns_resource_record_new_from_raw(&rr, d, l); if (r < 0) return log_error_errno(r, "Failed to parse RR: %m"); - if (arg_raw == RAW_PAYLOAD) { + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { + _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; + r = dns_resource_record_to_json(rr, &j); + if (r < 0) + return log_error_errno(r, "Failed to convert RR to JSON: %m"); + + if (!j) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "JSON formatting for records of type %s (%u) not available.", dns_type_to_string(rr->key->type), rr->key->type); + + r = json_variant_dump(j, arg_json_format_flags, NULL, NULL); + if (r < 0) + return r; + + } else if (arg_raw == RAW_PAYLOAD) { void *data; ssize_t k; @@ -946,6 +970,9 @@ static int resolve_service(sd_bus *bus, const char *name, const char *type, cons static int verb_service(int argc, char **argv, void *userdata) { sd_bus *bus = userdata; + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + if (argc == 2) return resolve_service(bus, NULL, NULL, argv[1]); else if (argc == 3) @@ -1005,6 +1032,9 @@ static int verb_openpgp(int argc, char **argv, void *userdata) { sd_bus *bus = userdata; int q, r = 0; + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + STRV_FOREACH(p, argv + 1) { q = resolve_openpgp(bus, *p); if (q < 0) @@ -1056,6 +1086,9 @@ static int verb_tlsa(int argc, char **argv, void *userdata) { const char *family = "tcp"; int q, r = 0; + if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Use --json=pretty with --type= to acquire resource record information in JSON format."); + if (service_family_is_valid(argv[1])) { family = argv[1]; args++; @@ -1080,9 +1113,9 @@ static int show_statistics(int argc, char **argv, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpStatistics", NULL, &reply, NULL, 0); + r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpStatistics", /* parameters= */ NULL, &reply); if (r < 0) - return log_error_errno(r, "Failed to issue DumpStatistics() varlink call: %m"); + return r; if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) return json_variant_dump(reply, arg_json_format_flags, NULL, NULL); @@ -1238,9 +1271,9 @@ static int reset_statistics(int argc, char **argv, void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = varlink_call(vl, "io.systemd.Resolve.Monitor.ResetStatistics", NULL, &reply, NULL, 0); + r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.ResetStatistics", /* parameters= */ NULL, &reply); if (r < 0) - return log_error_errno(r, "Failed to issue ResetStatistics() varlink call: %m"); + return r; if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) return json_variant_dump(reply, arg_json_format_flags, NULL, NULL); @@ -2715,24 +2748,26 @@ static int print_answer(JsonVariant *answer) { static void monitor_query_dump(JsonVariant *v) { _cleanup_(json_variant_unrefp) JsonVariant *question = NULL, *answer = NULL, *collected_questions = NULL; - int rcode = -1, error = 0, r; - const char *state = NULL; + int rcode = -1, error = 0, ede_code = -1; + const char *state = NULL, *result = NULL, *ede_msg = NULL; assert(v); JsonDispatch dispatch_table[] = { - { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY }, - { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 }, - { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 }, - { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY }, - { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 }, - { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 }, + { "question", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&question), JSON_MANDATORY }, + { "answer", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&answer), 0 }, + { "collectedQuestions", JSON_VARIANT_ARRAY, json_dispatch_variant, PTR_TO_SIZE(&collected_questions), 0 }, + { "state", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&state), JSON_MANDATORY }, + { "result", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&result), 0 }, + { "rcode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&rcode), 0 }, + { "errno", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&error), 0 }, + { "extendedDNSErrorCode", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, PTR_TO_SIZE(&ede_code), 0 }, + { "extendedDNSErrorMessage", JSON_VARIANT_STRING, json_dispatch_const_string, PTR_TO_SIZE(&ede_msg), 0 }, {} }; - r = json_dispatch(v, dispatch_table, 0, NULL); - if (r < 0) - return (void) log_warning("Received malformed monitor message, ignoring."); + if (json_dispatch(v, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, NULL) < 0) + return; /* First show the current question */ print_question('Q', ansi_highlight_cyan(), question); @@ -2740,7 +2775,7 @@ static void monitor_query_dump(JsonVariant *v) { /* And then show the questions that led to this one in case this was a CNAME chain */ print_question('C', ansi_highlight_grey(), collected_questions); - printf("%s%s S%s: %s\n", + printf("%s%s S%s: %s", streq_ptr(state, "success") ? ansi_highlight_green() : ansi_highlight_red(), special_glyph(SPECIAL_GLYPH_ARROW_LEFT), ansi_normal(), @@ -2748,6 +2783,17 @@ static void monitor_query_dump(JsonVariant *v) { streq_ptr(state, "rcode-failure") ? dns_rcode_to_string(rcode) : state)); + if (!isempty(result)) + printf(": %s", result); + + if (ede_code >= 0) + printf(" (%s%s%s)", + FORMAT_DNS_EDE_RCODE(ede_code), + !isempty(ede_msg) ? ": " : "", + strempty(ede_msg)); + + puts(""); + print_answer(answer); } @@ -2856,7 +2902,7 @@ static int dump_cache_item(JsonVariant *item) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; int r, c = 0; - r = json_dispatch(item, dispatch_table, JSON_LOG, &item_info); + r = json_dispatch(item, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &item_info); if (r < 0) return r; @@ -2918,7 +2964,7 @@ static int dump_cache_scope(JsonVariant *scope) { {}, }; - r = json_dispatch(scope, dispatch_table, JSON_LOG, &scope_info); + r = json_dispatch(scope, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &scope_info); if (r < 0) return r; @@ -2959,9 +3005,9 @@ static int verb_show_cache(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpCache", NULL, &reply, NULL, 0); + r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpCache", /* parameters= */ NULL, &reply); if (r < 0) - return log_error_errno(r, "Failed to issue DumpCache() varlink call: %m"); + return r; d = json_variant_by_key(reply, "dump"); if (!d) @@ -3034,7 +3080,7 @@ static int dump_server_state(JsonVariant *server) { {}, }; - r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_PERMISSIVE, &server_state); + r = json_dispatch(server, dispatch_table, JSON_LOG|JSON_ALLOW_EXTENSIONS, &server_state); if (r < 0) return r; @@ -3133,9 +3179,9 @@ static int verb_show_server_state(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to connect to query monitoring service /run/systemd/resolve/io.systemd.Resolve.Monitor: %m"); - r = varlink_call(vl, "io.systemd.Resolve.Monitor.DumpServerState", NULL, &reply, NULL, 0); + r = varlink_call_and_log(vl, "io.systemd.Resolve.Monitor.DumpServerState", /* parameters= */ NULL, &reply); if (r < 0) - return log_error_errno(r, "Failed to issue DumpServerState() varlink call: %m"); + return r; d = json_variant_by_key(reply, "dump"); if (!d) @@ -3252,11 +3298,11 @@ static int native_help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND ...\n" + printf("%1$s [OPTIONS...] COMMAND ...\n" "\n" - "%sSend control commands to the network name resolution manager, or%s\n" - "%sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%s\n" - "\nCommands:\n" + "%5$sSend control commands to the network name resolution manager, or%6$s\n" + "%5$sresolve domain names, IPv4 and IPv6 addresses, DNS records, and services.%6$s\n" + "\n%3$sCommands:%4$s\n" " query HOSTNAME|ADDRESS... Resolve domain names, IPv4 and IPv6 addresses\n" " service [[NAME] TYPE] DOMAIN Resolve service (SRV)\n" " openpgp EMAIL@DOMAIN... Query OpenPGP public key\n" @@ -3279,7 +3325,7 @@ static int native_help(void) { " nta [LINK [DOMAIN...]] Get/set per-interface DNSSEC NTA\n" " revert LINK Revert per-interface configuration\n" " log-level [LEVEL] Get/set logging threshold for systemd-resolved\n" - "\nOptions:\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" @@ -3296,6 +3342,7 @@ static int native_help(void) { " --synthesize=BOOL Allow synthetic response (default: yes)\n" " --cache=BOOL Allow response from cache (default: yes)\n" " --stale-data=BOOL Allow response from cache with stale data (default: yes)\n" + " --relax-single-label=BOOL Allow single label lookups to go upstream (default: no)\n" " --zone=BOOL Allow response from locally registered mDNS/LLMNR\n" " records (default: yes)\n" " --trust-anchor=BOOL Allow response from local trust anchor (default:\n" @@ -3308,13 +3355,13 @@ static int native_help(void) { " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short\n" " otherwise\n" - "\nSee the %s for details.\n", + "\nSee the %2$s for details.\n", program_invocation_short_name, - ansi_highlight(), + link, + ansi_underline(), ansi_normal(), ansi_highlight(), - ansi_normal(), - link); + ansi_normal()); return 0; } @@ -3655,7 +3702,8 @@ static int native_parse_argv(int argc, char *argv[]) { ARG_SEARCH, ARG_NO_PAGER, ARG_JSON, - ARG_STALE_DATA + ARG_STALE_DATA, + ARG_RELAX_SINGLE_LABEL, }; static const struct option options[] = { @@ -3680,6 +3728,7 @@ static int native_parse_argv(int argc, char *argv[]) { { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "json", required_argument, NULL, ARG_JSON }, { "stale-data", required_argument, NULL, ARG_STALE_DATA }, + { "relax-single-label", required_argument, NULL, ARG_RELAX_SINGLE_LABEL }, {} }; @@ -3866,6 +3915,13 @@ static int native_parse_argv(int argc, char *argv[]) { SET_FLAG(arg_flags, SD_RESOLVED_NO_SEARCH, r == 0); break; + case ARG_RELAX_SINGLE_LABEL: + r = parse_boolean_argument("--relax-single-label=", optarg, NULL); + if (r < 0) + return r; + SET_FLAG(arg_flags, SD_RESOLVED_RELAX_SINGLE_LABEL, r > 0); + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c index 75ba29c..d6d2273 100644 --- a/src/resolve/resolved-bus.c +++ b/src/resolve/resolved-bus.c @@ -11,6 +11,7 @@ #include "format-util.h" #include "memory-util.h" #include "missing_capability.h" +#include "path-util.h" #include "resolved-bus.h" #include "resolved-def.h" #include "resolved-dns-stream.h" @@ -146,8 +147,13 @@ static int reply_query_state(DnsQuery *q) { return reply_method_errorf(q, BUS_ERROR_ABORTED, "Query aborted"); case DNS_TRANSACTION_DNSSEC_FAILED: - return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", - dnssec_result_to_string(q->answer_dnssec_result)); + return reply_method_errorf(q, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s%s%s%s%s%s", + dnssec_result_to_string(q->answer_dnssec_result), + q->answer_ede_rcode >= 0 ? " (" : "", + q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "", + (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "", + q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "", + q->answer_ede_rcode >= 0 ? ")" : ""); case DNS_TRANSACTION_NO_TRUST_ANCHOR: return reply_method_errorf(q, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); @@ -184,7 +190,13 @@ static int reply_query_state(DnsQuery *q) { rc = FORMAT_DNS_RCODE(q->answer_rcode); n = strjoina(_BUS_ERROR_DNS, rc); - sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error: %s%s%s%s%s%s", + dns_query_string(q), rc, + q->answer_ede_rcode >= 0 ? " (" : "", + q->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(q->answer_ede_rcode) : "", + (q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg)) ? ": " : "", + q->answer_ede_rcode >= 0 ? strempty(q->answer_ede_msg) : "", + q->answer_ede_rcode >= 0 ? ")" : ""); } return sd_bus_reply_method_error(req, &error); @@ -362,6 +374,7 @@ static int validate_and_mangle_flags( SD_RESOLVED_NO_TRUST_ANCHOR| SD_RESOLVED_NO_NETWORK| SD_RESOLVED_NO_STALE| + SD_RESOLVED_RELAX_SINGLE_LABEL| ok)) return sd_bus_error_set(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); @@ -807,7 +820,7 @@ static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd if (!dns_type_is_valid_query(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified resource record type %" PRIu16 " may not be used in a query.", type); - if (dns_type_is_zone_transer(type)) + if (dns_type_is_zone_transfer(type)) return sd_bus_error_set(error, SD_BUS_ERROR_NOT_SUPPORTED, "Zone transfers not permitted via this programming interface."); if (dns_type_is_obsolete(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); @@ -1854,7 +1867,7 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; _cleanup_(dnssd_service_freep) DnssdService *service = NULL; _cleanup_(sd_bus_track_unrefp) sd_bus_track *bus_track = NULL; - const char *name, *name_template, *type; + const char *id, *name_template, *type; _cleanup_free_ char *path = NULL; DnssdService *s = NULL; Manager *m = ASSERT_PTR(userdata); @@ -1878,22 +1891,26 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, if (r < 0) return r; service->originator = euid; + service->config_source = RESOLVE_CONFIG_SOURCE_DBUS; - r = sd_bus_message_read(message, "sssqqq", &name, &name_template, &type, + r = sd_bus_message_read(message, "sssqqq", &id, &name_template, &type, &service->port, &service->priority, &service->weight); if (r < 0) return r; - s = hashmap_get(m->dnssd_services, name); - if (s) - return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", name); + if (!filename_part_is_valid(id)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service identifier '%s' is invalid", id); if (!dnssd_srv_type_is_valid(type)) return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "DNS-SD service type '%s' is invalid", type); - service->name = strdup(name); - if (!service->name) + s = hashmap_get(m->dnssd_services, id); + if (s) + return sd_bus_error_setf(error, BUS_ERROR_DNSSD_SERVICE_EXISTS, "DNS-SD service '%s' exists already", id); + + service->id = strdup(id); + if (!service->id) return log_oom(); service->name_template = strdup(name_template); @@ -1986,20 +2003,22 @@ static int bus_method_register_service(sd_bus_message *message, void *userdata, txt_data = NULL; } - r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &path); + r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &path); if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_SYS_ADMIN, - "org.freedesktop.resolve1.register-service", - NULL, false, UID_INVALID, - &m->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.register-service", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) return 1; /* Polkit will call us back */ - r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->name, service); + r = hashmap_ensure_put(&m->dnssd_services, &string_hash_ops, service->id, service); if (r < 0) return r; @@ -2163,7 +2182,7 @@ static const sd_bus_vtable resolve_vtable[] = { bus_method_revert_link, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD_WITH_ARGS("RegisterService", - SD_BUS_ARGS("s", name, + SD_BUS_ARGS("s", id, "s", name_template, "s", type, "q", service_port, diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index 2f08ed0..4441ee2 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -55,7 +55,7 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons return 0; } - return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name); + return dns_server_new(m, NULL, type, NULL, family, &address, port, ifindex, server_name, RESOLVE_CONFIG_SOURCE_FILE); } int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { @@ -299,6 +299,37 @@ int config_parse_dnssd_service_type( return 0; } +int config_parse_dnssd_service_subtype( + 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) { + + DnssdService *s = ASSERT_PTR(userdata); + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + s->subtype = mfree(s->subtype); + return 0; + } + + if (!dns_subtype_name_is_valid(rvalue)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Service subtype is invalid. Ignoring."); + return 0; + } + + return free_and_strdup_warn(&s->subtype, rvalue); +} + int config_parse_dnssd_txt( const char *unit, const char *filename, @@ -362,7 +393,7 @@ int config_parse_dnssd_txt( case DNS_TXT_ITEM_DATA: if (value) { - r = unbase64mem(value, strlen(value), &decoded, &length); + r = unbase64mem(value, &decoded, &length); if (r == -ENOMEM) return log_oom(); if (r < 0) { @@ -570,9 +601,12 @@ int manager_parse_config_file(Manager *m) { assert(m); - r = config_parse_config_file("resolved.conf", "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - CONFIG_PARSE_WARN, m); + r = config_parse_standard_file_with_dropins( + "systemd/resolved.conf", + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ m); if (r < 0) return r; diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h index 07ce259..5eea6bd 100644 --- a/src/resolve/resolved-conf.h +++ b/src/resolve/resolved-conf.h @@ -3,6 +3,14 @@ #include "conf-parser.h" +typedef enum ResolveConfigSource { + RESOLVE_CONFIG_SOURCE_FILE, + RESOLVE_CONFIG_SOURCE_NETWORKD, + RESOLVE_CONFIG_SOURCE_DBUS, + _RESOLVE_CONFIG_SOURCE_MAX, + _RESOLVE_CONFIG_SOURCE_INVALID = -EINVAL, +} ResolveConfigSource; + #include "resolved-dns-server.h" int manager_parse_config_file(Manager *m); @@ -17,6 +25,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_dns_servers); CONFIG_PARSER_PROTOTYPE(config_parse_search_domains); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_mode); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_name); +CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_subtype); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_service_type); CONFIG_PARSER_PROTOTYPE(config_parse_dnssd_txt); CONFIG_PARSER_PROTOTYPE(config_parse_dns_stub_listener_extra); diff --git a/src/resolve/resolved-def.h b/src/resolve/resolved-def.h index b7a44f9..f702a0a 100644 --- a/src/resolve/resolved-def.h +++ b/src/resolve/resolved-def.h @@ -73,6 +73,10 @@ /* Input: Don't answer request with stale data */ #define SD_RESOLVED_NO_STALE (UINT64_C(1) << 24) +/* Input: Allow single-label lookups to Internet DNS servers */ +#define SD_RESOLVED_RELAX_SINGLE_LABEL \ + (UINT64_C(1) << 25) + #define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) #define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) #define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c index bf023a7..254f836 100644 --- a/src/resolve/resolved-dns-answer.c +++ b/src/resolve/resolved-dns-answer.c @@ -26,7 +26,7 @@ static void dns_answer_item_hash_func(const DnsAnswerItem *a, struct siphash *st assert(a); assert(state); - siphash24_compress(&a->ifindex, sizeof(a->ifindex), state); + siphash24_compress_typesafe(a->ifindex, state); dns_resource_record_hash_func(a->rr, state); } diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c index e90915e..9061908 100644 --- a/src/resolve/resolved-dns-cache.c +++ b/src/resolve/resolved-dns-cache.c @@ -164,8 +164,8 @@ void dns_cache_flush(DnsCache *c) { while ((key = hashmap_first_key(c->by_key))) dns_cache_remove_by_key(c, key); - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); + assert(hashmap_isempty(c->by_key)); + assert(prioq_isempty(c->by_expiry)); c->by_key = hashmap_free(c->by_key); c->by_expiry = prioq_free(c->by_expiry); @@ -186,7 +186,7 @@ static void dns_cache_make_space(DnsCache *c, unsigned add) { _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; DnsCacheItem *i; - if (prioq_size(c->by_expiry) <= 0) + if (prioq_isempty(c->by_expiry)) break; if (prioq_size(c->by_expiry) + add < CACHE_MAX) @@ -243,6 +243,22 @@ void dns_cache_prune(DnsCache *c) { } } +bool dns_cache_expiry_in_one_second(DnsCache *c, usec_t t) { + DnsCacheItem *i; + + assert(c); + + /* Check if any items expire within the next second */ + i = prioq_peek(c->by_expiry); + if (!i) + return false; + + if (i->until <= usec_add(t, USEC_PER_SEC)) + return true; + + return false; +} + static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { const DnsCacheItem *x = a, *y = b; diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h index d078ae9..6a45b95 100644 --- a/src/resolve/resolved-dns-cache.h +++ b/src/resolve/resolved-dns-cache.h @@ -58,3 +58,5 @@ bool dns_cache_is_empty(DnsCache *cache); unsigned dns_cache_size(DnsCache *cache); int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p, usec_t ts, unsigned max_rr); + +bool dns_cache_expiry_in_one_second(DnsCache *c, usec_t t); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c index a192d82..233418a 100644 --- a/src/resolve/resolved-dns-dnssec.c +++ b/src/resolve/resolved-dns-dnssec.c @@ -890,8 +890,11 @@ static int dnssec_rrset_verify_sig( _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; void *hash; size_t hash_size; + int r; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; #endif switch (rrsig->rrsig.algorithm) { @@ -1334,6 +1337,7 @@ static hash_md_t digest_to_hash_md(uint8_t algorithm) { int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { uint8_t wire_format[DNS_WIRE_FORMAT_HOSTNAME_MAX]; + size_t encoded_length; int r; assert(dnskey); @@ -1360,6 +1364,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, r = dns_name_to_wire_format(dns_resource_key_name(dnskey->key), wire_format, sizeof wire_format, true); if (r < 0) return r; + encoded_length = r; hash_md_t md_algorithm = digest_to_hash_md(ds->ds.digest_type); @@ -1383,7 +1388,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, if (EVP_DigestInit_ex(ctx, md_algorithm, NULL) <= 0) return -EIO; - if (EVP_DigestUpdate(ctx, wire_format, r) <= 0) + if (EVP_DigestUpdate(ctx, wire_format, encoded_length) <= 0) return -EIO; if (mask_revoke) @@ -1407,7 +1412,9 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, if (md_algorithm < 0) return -EOPNOTSUPP; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; @@ -1421,7 +1428,7 @@ int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) return -EIO; - gcry_md_write(md, wire_format, r); + gcry_md_write(md, wire_format, encoded_length); if (mask_revoke) md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); else @@ -1552,8 +1559,11 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { if (algorithm < 0) return algorithm; - initialize_libgcrypt(false); + r = initialize_libgcrypt(false); + if (r < 0) + return r; + size_t encoded_length; unsigned hash_size = gcry_md_get_algo_dlen(algorithm); assert(hash_size > 0); @@ -1563,13 +1573,14 @@ int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); if (r < 0) return r; + encoded_length = r; _cleanup_(gcry_md_closep) gcry_md_hd_t md = NULL; gcry_error_t err = gcry_md_open(&md, algorithm, 0); if (gcry_err_code(err) != GPG_ERR_NO_ERROR || !md) return -EIO; - gcry_md_write(md, wire_format, r); + gcry_md_write(md, wire_format, encoded_length); gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); void *result = gcry_md_read(md, 0); @@ -1963,7 +1974,7 @@ found_closest_encloser: } static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; const char *n; int r; @@ -2576,6 +2587,7 @@ static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", + [DNSSEC_UPSTREAM_FAILURE] = "upstream-failure", [DNSSEC_TOO_MANY_VALIDATIONS] = "too-many-validations", }; DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h index 29b9013..d8ff3ba 100644 --- a/src/resolve/resolved-dns-dnssec.h +++ b/src/resolve/resolved-dns-dnssec.h @@ -21,11 +21,12 @@ enum DnssecResult { DNSSEC_NO_SIGNATURE, DNSSEC_MISSING_KEY, - /* These two are added by the DnsTransaction logic */ + /* These five are added by the DnsTransaction logic */ DNSSEC_UNSIGNED, DNSSEC_FAILED_AUXILIARY, DNSSEC_NSEC_MISMATCH, DNSSEC_INCOMPATIBLE_SERVER, + DNSSEC_UPSTREAM_FAILURE, _DNSSEC_RESULT_MAX, _DNSSEC_RESULT_INVALID = -EINVAL, diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c index 426711b..e626740 100644 --- a/src/resolve/resolved-dns-packet.c +++ b/src/resolve/resolved-dns-packet.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "dns-domain.h" +#include "escape.h" #include "memory-util.h" #include "resolved-dns-packet.h" #include "set.h" @@ -569,7 +570,7 @@ int dns_packet_append_name( while (!dns_name_is_root(name)) { const char *z = name; - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; size_t n = 0; if (allow_compression) @@ -792,7 +793,7 @@ int dns_packet_append_opt( static const uint8_t rfc6975[] = { - 0, 5, /* OPTION_CODE: DAU */ + 0, DNS_EDNS_OPT_DAU, /* OPTION_CODE */ #if PREFER_OPENSSL || (HAVE_GCRYPT && GCRYPT_VERSION_NUMBER >= 0x010600) 0, 7, /* LIST_LENGTH */ #else @@ -808,13 +809,13 @@ int dns_packet_append_opt( DNSSEC_ALGORITHM_ED25519, #endif - 0, 6, /* OPTION_CODE: DHU */ + 0, DNS_EDNS_OPT_DHU, /* OPTION_CODE */ 0, 3, /* LIST_LENGTH */ DNSSEC_DIGEST_SHA1, DNSSEC_DIGEST_SHA256, DNSSEC_DIGEST_SHA384, - 0, 7, /* OPTION_CODE: N3U */ + 0, DNS_EDNS_OPT_N3U, /* OPTION_CODE */ 0, 1, /* LIST_LENGTH */ NSEC3_ALGORITHM_SHA1, }; @@ -1182,6 +1183,31 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + r = dns_packet_append_uint16(p, rr->svcb.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->svcb.target_name, false, false, NULL); + if (r < 0) + goto fail; + + LIST_FOREACH(params, i, rr->svcb.params) { + r = dns_packet_append_uint16(p, i->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, i->length, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, i->value, i->length, NULL); + if (r < 0) + goto fail; + } + break; + case DNS_TYPE_CAA: r = dns_packet_append_uint8(p, rr->caa.flags, NULL); if (r < 0) @@ -1194,6 +1220,30 @@ int dns_packet_append_rr(DnsPacket *p, const DnsResourceRecord *rr, const DnsAns r = dns_packet_append_blob(p, rr->caa.value, rr->caa.value_size, NULL); break; + case DNS_TYPE_NAPTR: + r = dns_packet_append_uint16(p, rr->naptr.order, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->naptr.preference, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.services, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->naptr.regexp, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->naptr.replacement, /* allow_compression= */ false, /* canonical_candidate= */ true, NULL); + break; + case DNS_TYPE_OPT: case DNS_TYPE_OPENPGPKEY: case _DNS_TYPE_INVALID: /* unparsable */ @@ -1688,6 +1738,41 @@ static bool loc_size_ok(uint8_t size) { return m <= 9 && e <= 9 && (m > 0 || e == 0); } +static bool dns_svc_param_is_valid(DnsSvcParam *i) { + if (!i) + return false; + + switch (i->key) { + /* RFC 9460, section 7.1.1: alpn-ids must exactly fill SvcParamValue */ + case DNS_SVC_PARAM_KEY_ALPN: { + size_t sz = 0; + if (i->length <= 0) + return false; + while (sz < i->length) + sz += 1 + i->value[sz]; /* N.B. will not overflow */ + return sz == i->length; + } + + /* RFC 9460, section 7.1.1: value must be empty */ + case DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN: + return i->length == 0; + + /* RFC 9460, section 7.2 */ + case DNS_SVC_PARAM_KEY_PORT: + return i->length == 2; + + /* RFC 9460, section 7.3: addrs must exactly fill SvcParamValue */ + case DNS_SVC_PARAM_KEY_IPV4HINT: + return i->length % (sizeof (struct in_addr)) == 0; + case DNS_SVC_PARAM_KEY_IPV6HINT: + return i->length % (sizeof (struct in6_addr)) == 0; + + /* Otherwise, permit any value */ + default: + return true; + } +} + int dns_packet_read_rr( DnsPacket *p, DnsResourceRecord **ret, @@ -2122,6 +2207,52 @@ int dns_packet_read_rr( break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + r = dns_packet_read_uint16(p, &rr->svcb.priority, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->svcb.target_name, false /* uncompressed */, NULL); + if (r < 0) + return r; + + DnsSvcParam *last = NULL; + while (p->rindex - offset < rdlength) { + _cleanup_free_ DnsSvcParam *i = NULL; + uint16_t svc_param_key; + uint16_t sz; + + r = dns_packet_read_uint16(p, &svc_param_key, NULL); + if (r < 0) + return r; + /* RFC 9460, section 2.2 says we must consider an RR malformed if SvcParamKeys are + * not in strictly increasing order */ + if (last && last->key >= svc_param_key) + return -EBADMSG; + + r = dns_packet_read_uint16(p, &sz, NULL); + if (r < 0) + return r; + + i = malloc0(offsetof(DnsSvcParam, value) + sz); + if (!i) + return -ENOMEM; + + i->key = svc_param_key; + i->length = sz; + r = dns_packet_read_blob(p, &i->value, sz, NULL); + if (r < 0) + return r; + if (!dns_svc_param_is_valid(i)) + return -EBADMSG; + + LIST_INSERT_AFTER(params, rr->svcb.params, last, i); + last = TAKE_PTR(i); + } + + break; + case DNS_TYPE_CAA: r = dns_packet_read_uint8(p, &rr->caa.flags, NULL); if (r < 0) @@ -2140,6 +2271,30 @@ int dns_packet_read_rr( break; + case DNS_TYPE_NAPTR: + r = dns_packet_read_uint16(p, &rr->naptr.order, NULL); + if (r < 0) + return r; + + r = dns_packet_read_uint16(p, &rr->naptr.preference, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.flags, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.services, NULL); + if (r < 0) + return r; + + r = dns_packet_read_string(p, &rr->naptr.regexp, NULL); + if (r < 0) + return r; + + r = dns_packet_read_name(p, &rr->naptr.replacement, /* allow_compressed= */ false, NULL); + break; + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ case DNS_TYPE_OPENPGPKEY: default: @@ -2197,7 +2352,7 @@ static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { return false; /* RFC 6975 DAU, DHU or N3U fields found. */ - if (IN_SET(option_code, 5, 6, 7)) + if (IN_SET(option_code, DNS_EDNS_OPT_DAU, DNS_EDNS_OPT_DHU, DNS_EDNS_OPT_N3U)) found_dau_dhu_n3u = true; p += option_length + 4U; @@ -2567,7 +2722,7 @@ int dns_packet_patch_ttls(DnsPacket *p, usec_t timestamp) { static void dns_packet_hash_func(const DnsPacket *s, struct siphash *state) { assert(s); - siphash24_compress(&s->size, sizeof(s->size), state); + siphash24_compress_typesafe(s->size, state); siphash24_compress(DNS_PACKET_DATA((DnsPacket*) s), s->size, state); } @@ -2587,6 +2742,85 @@ bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b) { return dns_packet_compare_func(a, b) == 0; } +int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg) { + const uint8_t *d; + size_t l; + int r; + + assert(p); + + if (!p->opt) + return -ENOENT; + + d = p->opt->opt.data; + l = p->opt->opt.data_size; + + while (l > 0) { + uint16_t code, length; + + if (l < 4U) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "EDNS0 variable part has invalid size."); + + code = unaligned_read_be16(d); + length = unaligned_read_be16(d + 2); + + if (l < 4U + length) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "Truncated option in EDNS0 variable part."); + + if (code == DNS_EDNS_OPT_EXT_ERROR) { + _cleanup_free_ char *msg = NULL; + + if (length < 2U) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), + "EDNS0 truncated EDE info code."); + + r = make_cstring((char *) d + 6, length - 2U, MAKE_CSTRING_ALLOW_TRAILING_NUL, &msg); + if (r < 0) + return log_debug_errno(r, "Invalid EDE text in opt."); + + if (ret_ede_msg) { + if (!utf8_is_valid(msg)) { + _cleanup_free_ char *msg_escaped = NULL; + + msg_escaped = cescape(msg); + if (!msg_escaped) + return log_oom_debug(); + + *ret_ede_msg = TAKE_PTR(msg_escaped); + } else + *ret_ede_msg = TAKE_PTR(msg); + } + + if (ret_ede_rcode) + *ret_ede_rcode = unaligned_read_be16(d + 4); + + return 0; + } + + d += 4U + length; + l -= 4U + length; + } + + return -ENOENT; +} + +bool dns_ede_rcode_is_dnssec(int ede_rcode) { + return IN_SET(ede_rcode, + DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG, + DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST, + DNS_EDE_RCODE_DNSSEC_INDETERMINATE, + DNS_EDE_RCODE_DNSSEC_BOGUS, + DNS_EDE_RCODE_SIG_EXPIRED, + DNS_EDE_RCODE_SIG_NOT_YET_VALID, + DNS_EDE_RCODE_DNSKEY_MISSING, + DNS_EDE_RCODE_RRSIG_MISSING, + DNS_EDE_RCODE_NO_ZONE_KEY_BIT, + DNS_EDE_RCODE_NSEC_MISSING + ); +} + int dns_packet_has_nsid_request(DnsPacket *p) { bool has_nsid = false; const uint8_t *d; @@ -2614,7 +2848,7 @@ int dns_packet_has_nsid_request(DnsPacket *p) { return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Truncated option in EDNS0 variable part."); - if (code == 3) { + if (code == DNS_EDNS_OPT_NSID) { if (has_nsid) return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate NSID option in EDNS0 variable part."); @@ -2659,6 +2893,7 @@ static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { [DNS_RCODE_NXRRSET] = "NXRRSET", [DNS_RCODE_NOTAUTH] = "NOTAUTH", [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_DSOTYPENI] = "DSOTYPENI", [DNS_RCODE_BADVERS] = "BADVERS", [DNS_RCODE_BADKEY] = "BADKEY", [DNS_RCODE_BADTIME] = "BADTIME", @@ -2678,6 +2913,69 @@ const char *format_dns_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) { return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i); } +static const char* const dns_ede_rcode_table[_DNS_EDE_RCODE_MAX_DEFINED] = { + [DNS_EDE_RCODE_OTHER] = "Other", + [DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG] = "Unsupported DNSKEY Algorithm", + [DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST] = "Unsupported DS Digest Type", + [DNS_EDE_RCODE_STALE_ANSWER] = "Stale Answer", + [DNS_EDE_RCODE_FORGED_ANSWER] = "Forged Answer", + [DNS_EDE_RCODE_DNSSEC_INDETERMINATE] = "DNSSEC Indeterminate", + [DNS_EDE_RCODE_DNSSEC_BOGUS] = "DNSSEC Bogus", + [DNS_EDE_RCODE_SIG_EXPIRED] = "Signature Expired", + [DNS_EDE_RCODE_SIG_NOT_YET_VALID] = "Signature Not Yet Valid", + [DNS_EDE_RCODE_DNSKEY_MISSING] = "DNSKEY Missing", + [DNS_EDE_RCODE_RRSIG_MISSING] = "RRSIG Missing", + [DNS_EDE_RCODE_NO_ZONE_KEY_BIT] = "No Zone Key Bit Set", + [DNS_EDE_RCODE_NSEC_MISSING] = "NSEC Missing", + [DNS_EDE_RCODE_CACHED_ERROR] = "Cached Error", + [DNS_EDE_RCODE_NOT_READY] = "Not Ready", + [DNS_EDE_RCODE_BLOCKED] = "Blocked", + [DNS_EDE_RCODE_CENSORED] = "Censored", + [DNS_EDE_RCODE_FILTERED] = "Filtered", + [DNS_EDE_RCODE_PROHIBITIED] = "Prohibited", + [DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER] = "Stale NXDOMAIN Answer", + [DNS_EDE_RCODE_NOT_AUTHORITATIVE] = "Not Authoritative", + [DNS_EDE_RCODE_NOT_SUPPORTED] = "Not Supported", + [DNS_EDE_RCODE_UNREACH_AUTHORITY] = "No Reachable Authority", + [DNS_EDE_RCODE_NET_ERROR] = "Network Error", + [DNS_EDE_RCODE_INVALID_DATA] = "Invalid Data", + [DNS_EDE_RCODE_SIG_NEVER] = "Signature Never Valid", + [DNS_EDE_RCODE_TOO_EARLY] = "Too Early", + [DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER] = "Unsupported NSEC3 Iterations", + [DNS_EDE_RCODE_TRANSPORT_POLICY] = "Impossible Transport Policy", + [DNS_EDE_RCODE_SYNTHESIZED] = "Synthesized", +}; +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_ede_rcode, int); + +const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]) { + const char *p = dns_ede_rcode_to_string(i); + if (p) + return p; + + return snprintf_ok(buf, DECIMAL_STR_MAX(int), "%i", i); +} + +static const char* const dns_svc_param_key_table[_DNS_SVC_PARAM_KEY_MAX_DEFINED] = { + [DNS_SVC_PARAM_KEY_MANDATORY] = "mandatory", + [DNS_SVC_PARAM_KEY_ALPN] = "alpn", + [DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN] = "no-default-alpn", + [DNS_SVC_PARAM_KEY_PORT] = "port", + [DNS_SVC_PARAM_KEY_IPV4HINT] = "ipv4hint", + [DNS_SVC_PARAM_KEY_ECH] = "ech", + [DNS_SVC_PARAM_KEY_IPV6HINT] = "ipv6hint", + [DNS_SVC_PARAM_KEY_DOHPATH] = "dohpath", + [DNS_SVC_PARAM_KEY_OHTTP] = "ohttp", +}; +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(dns_svc_param_key, int); + +const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]) { + const char *p = dns_svc_param_key_to_string(i); + if (p) + return p; + + return snprintf_ok(buf, DECIMAL_STR_MAX(uint16_t)+3, "key%i", i); +} + static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { [DNS_PROTOCOL_DNS] = "dns", [DNS_PROTOCOL_MDNS] = "mdns", diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h index a6af44c..393b7b2 100644 --- a/src/resolve/resolved-dns-packet.h +++ b/src/resolve/resolved-dns-packet.h @@ -253,32 +253,100 @@ int dns_packet_extract(DnsPacket *p); bool dns_packet_equal(const DnsPacket *a, const DnsPacket *b); +int dns_packet_ede_rcode(DnsPacket *p, int *ret_ede_rcode, char **ret_ede_msg); +bool dns_ede_rcode_is_dnssec(int ede_rcode); int dns_packet_has_nsid_request(DnsPacket *p); /* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ enum { - DNS_RCODE_SUCCESS = 0, - DNS_RCODE_FORMERR = 1, - DNS_RCODE_SERVFAIL = 2, - DNS_RCODE_NXDOMAIN = 3, - DNS_RCODE_NOTIMP = 4, - DNS_RCODE_REFUSED = 5, - DNS_RCODE_YXDOMAIN = 6, - DNS_RCODE_YXRRSET = 7, - DNS_RCODE_NXRRSET = 8, - DNS_RCODE_NOTAUTH = 9, - DNS_RCODE_NOTZONE = 10, - DNS_RCODE_BADVERS = 16, - DNS_RCODE_BADSIG = 16, /* duplicate value! */ - DNS_RCODE_BADKEY = 17, - DNS_RCODE_BADTIME = 18, - DNS_RCODE_BADMODE = 19, - DNS_RCODE_BADNAME = 20, - DNS_RCODE_BADALG = 21, - DNS_RCODE_BADTRUNC = 22, - DNS_RCODE_BADCOOKIE = 23, + DNS_RCODE_SUCCESS = 0, + DNS_RCODE_FORMERR = 1, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_DSOTYPENI = 11, + /* 12-15 are unassigned. */ + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + DNS_RCODE_BADCOOKIE = 23, + /* 24-3840 are unassigned. */ + /* 3841-4095 are for private use. */ + /* 4096-65534 are unassigned. */ _DNS_RCODE_MAX_DEFINED, - _DNS_RCODE_MAX = 4095 /* 4 bit rcode in the header plus 8 bit rcode in OPT, makes 12 bit */ + _DNS_RCODE_MAX = 65535, /* reserved */ + _DNS_RCODE_INVALID = -EINVAL, +}; + +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-11 */ +enum { + DNS_EDNS_OPT_RESERVED = 0, /* RFC 6891 */ + DNS_EDNS_OPT_LLQ = 1, /* RFC 8764 */ + DNS_EDNS_OPT_UL = 2, + DNS_EDNS_OPT_NSID = 3, /* RFC 5001 */ + /* DNS_EDNS_OPT_RESERVED = 4 */ + DNS_EDNS_OPT_DAU = 5, /* RFC 6975 */ + DNS_EDNS_OPT_DHU = 6, /* RFC 6975 */ + DNS_EDNS_OPT_N3U = 7, /* RFC 6975 */ + DNS_EDNS_OPT_CLIENT_SUBNET = 8, /* RFC 7871 */ + DNS_EDNS_OPT_EXPIRE = 9, /* RFC 7314 */ + DNS_EDNS_OPT_COOKIE = 10, /* RFC 7873 */ + DNS_EDNS_OPT_TCP_KEEPALIVE = 11, /* RFC 7828 */ + DNS_EDNS_OPT_PADDING = 12, /* RFC 7830 */ + DNS_EDNS_OPT_CHAIN = 13, /* RFC 7901 */ + DNS_EDNS_OPT_KEY_TAG = 14, /* RFC 8145 */ + DNS_EDNS_OPT_EXT_ERROR = 15, /* RFC 8914 */ + DNS_EDNS_OPT_CLIENT_TAG = 16, + DNS_EDNS_OPT_SERVER_TAG = 17, + _DNS_EDNS_OPT_MAX_DEFINED, + _DNS_EDNS_OPT_INVALID = -EINVAL, +}; + +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#extended-dns-error-codes */ +enum { + DNS_EDE_RCODE_OTHER = 0, /* RFC 8914, Section 4.1 */ + DNS_EDE_RCODE_UNSUPPORTED_DNSKEY_ALG = 1, /* RFC 8914, Section 4.2 */ + DNS_EDE_RCODE_UNSUPPORTED_DS_DIGEST = 2, /* RFC 8914, Section 4.3 */ + DNS_EDE_RCODE_STALE_ANSWER = 3, /* RFC 8914, Section 4.4 */ + DNS_EDE_RCODE_FORGED_ANSWER = 4, /* RFC 8914, Section 4.5 */ + DNS_EDE_RCODE_DNSSEC_INDETERMINATE = 5, /* RFC 8914, Section 4.6 */ + DNS_EDE_RCODE_DNSSEC_BOGUS = 6, /* RFC 8914, Section 4.7 */ + DNS_EDE_RCODE_SIG_EXPIRED = 7, /* RFC 8914, Section 4.8 */ + DNS_EDE_RCODE_SIG_NOT_YET_VALID = 8, /* RFC 8914, Section 4.9 */ + DNS_EDE_RCODE_DNSKEY_MISSING = 9, /* RFC 8914, Section 4.10 */ + DNS_EDE_RCODE_RRSIG_MISSING = 10, /* RFC 8914, Section 4.11 */ + DNS_EDE_RCODE_NO_ZONE_KEY_BIT = 11, /* RFC 8914, Section 4.12 */ + DNS_EDE_RCODE_NSEC_MISSING = 12, /* RFC 8914, Section 4.13 */ + DNS_EDE_RCODE_CACHED_ERROR = 13, /* RFC 8914, Section 4.14 */ + DNS_EDE_RCODE_NOT_READY = 14, /* RFC 8914, Section 4.15 */ + DNS_EDE_RCODE_BLOCKED = 15, /* RFC 8914, Section 4.16 */ + DNS_EDE_RCODE_CENSORED = 16, /* RFC 8914, Section 4.17 */ + DNS_EDE_RCODE_FILTERED = 17, /* RFC 8914, Section 4.18 */ + DNS_EDE_RCODE_PROHIBITIED = 18, /* RFC 8914, Section 4.19 */ + DNS_EDE_RCODE_STALE_NXDOMAIN_ANSWER = 19, /* RFC 8914, Section 4.20 */ + DNS_EDE_RCODE_NOT_AUTHORITATIVE = 20, /* RFC 8914, Section 4.21 */ + DNS_EDE_RCODE_NOT_SUPPORTED = 21, /* RFC 8914, Section 4.22 */ + DNS_EDE_RCODE_UNREACH_AUTHORITY = 22, /* RFC 8914, Section 4.23 */ + DNS_EDE_RCODE_NET_ERROR = 23, /* RFC 8914, Section 4.24 */ + DNS_EDE_RCODE_INVALID_DATA = 24, /* RFC 8914, Section 4.25 */ + DNS_EDE_RCODE_SIG_NEVER = 25, + DNS_EDE_RCODE_TOO_EARLY = 26, /* RFC 9250 */ + DNS_EDE_RCODE_UNSUPPORTED_NSEC3_ITER = 27, /* RFC 9276 */ + DNS_EDE_RCODE_TRANSPORT_POLICY = 28, + DNS_EDE_RCODE_SYNTHESIZED = 29, + _DNS_EDE_RCODE_MAX_DEFINED, + _DNS_EDE_RCODE_INVALID = -EINVAL, }; const char* dns_rcode_to_string(int i) _const_; @@ -286,9 +354,32 @@ int dns_rcode_from_string(const char *s) _pure_; const char *format_dns_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]); #define FORMAT_DNS_RCODE(i) format_dns_rcode(i, (char [DECIMAL_STR_MAX(int)]) {}) +const char* dns_ede_rcode_to_string(int i) _const_; +const char *format_dns_ede_rcode(int i, char buf[static DECIMAL_STR_MAX(int)]); +#define FORMAT_DNS_EDE_RCODE(i) format_dns_ede_rcode(i, (char [DECIMAL_STR_MAX(int)]) {}) + const char* dns_protocol_to_string(DnsProtocol p) _const_; DnsProtocol dns_protocol_from_string(const char *s) _pure_; +/* https://www.iana.org/assignments/dns-svcb/dns-svcb.xhtml#dns-svcparamkeys */ +enum { + DNS_SVC_PARAM_KEY_MANDATORY = 0, /* RFC 9460 section 8 */ + DNS_SVC_PARAM_KEY_ALPN = 1, /* RFC 9460 section 7.1 */ + DNS_SVC_PARAM_KEY_NO_DEFAULT_ALPN = 2, /* RFC 9460 Section 7.1 */ + DNS_SVC_PARAM_KEY_PORT = 3, /* RFC 9460 section 7.2 */ + DNS_SVC_PARAM_KEY_IPV4HINT = 4, /* RFC 9460 section 7.3 */ + DNS_SVC_PARAM_KEY_ECH = 5, /* RFC 9460 */ + DNS_SVC_PARAM_KEY_IPV6HINT = 6, /* RFC 9460 section 7.3 */ + DNS_SVC_PARAM_KEY_DOHPATH = 7, /* RFC 9461 */ + DNS_SVC_PARAM_KEY_OHTTP = 8, + _DNS_SVC_PARAM_KEY_MAX_DEFINED, + DNS_SVC_PARAM_KEY_INVALID = 65535 /* RFC 9460 */ +}; + +const char* dns_svc_param_key_to_string(int i) _const_; +const char *format_dns_svc_param_key(uint16_t i, char buf[static DECIMAL_STR_MAX(uint16_t)+3]); +#define FORMAT_DNS_SVC_PARAM_KEY(i) format_dns_svc_param_key(i, (char [DECIMAL_STR_MAX(uint16_t)+3]) {}) + #define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) #define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c index 16334c6..14adb90 100644 --- a/src/resolve/resolved-dns-query.c +++ b/src/resolve/resolved-dns-query.c @@ -393,6 +393,8 @@ static void dns_query_reset_answer(DnsQuery *q) { q->answer = dns_answer_unref(q->answer); q->answer_rcode = 0; + q->answer_ede_rcode = _DNS_EDE_RCODE_INVALID; + q->answer_ede_msg = mfree(q->answer_ede_msg); q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; q->answer_errno = 0; q->answer_query_flags = 0; @@ -539,6 +541,7 @@ int dns_query_new( .question_bypass = dns_packet_ref(question_bypass), .ifindex = ifindex, .flags = flags, + .answer_ede_rcode = _DNS_EDE_RCODE_INVALID, .answer_dnssec_result = _DNSSEC_RESULT_INVALID, .answer_protocol = _DNS_PROTOCOL_INVALID, .answer_family = AF_UNSPEC, @@ -611,7 +614,7 @@ void dns_query_complete(DnsQuery *q, DnsTransactionState state) { q->state = state; - (void) manager_monitor_send(q->manager, q->state, q->answer_rcode, q->answer_errno, q->question_idna, q->question_utf8, q->question_bypass, q->collected_questions, q->answer); + (void) manager_monitor_send(q->manager, q); dns_query_abandon(q); if (q->complete) @@ -694,6 +697,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { q->answer_query_flags = SD_RESOLVED_AUTHENTICATED|SD_RESOLVED_CONFIDENTIAL|SD_RESOLVED_SYNTHETIC; *state = DNS_TRANSACTION_RCODE_FAILURE; + log_debug("Found synthetic NXDOMAIN response."); + return 0; } if (r <= 0) @@ -709,6 +714,8 @@ static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { *state = DNS_TRANSACTION_SUCCESS; + log_debug("Found synthetic success response."); + return 1; } @@ -763,7 +770,7 @@ int dns_query_go(DnsQuery *q) { LIST_FOREACH(scopes, s, q->manager->dns_scopes) { DnsScopeMatch match; - match = dns_scope_good_domain(s, q); + match = dns_scope_good_domain(s, q, q->flags); assert(match >= 0); if (match > found) { /* Does this match better? If so, remember how well it matched, and the first one * that matches this well */ @@ -790,7 +797,7 @@ int dns_query_go(DnsQuery *q) { LIST_FOREACH(scopes, s, first->scopes_next) { DnsScopeMatch match; - match = dns_scope_good_domain(s, q); + match = dns_scope_good_domain(s, q, q->flags); assert(match >= 0); if (match < found) continue; @@ -923,6 +930,10 @@ static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { DNS_ANSWER_REPLACE(q->answer, dns_answer_ref(t->answer)); q->answer_rcode = t->answer_rcode; + q->answer_ede_rcode = t->answer_ede_rcode; + r = free_and_strdup_warn(&q->answer_ede_msg, t->answer_ede_msg); + if (r < 0) + goto fail; q->answer_dnssec_result = t->answer_dnssec_result; q->answer_query_flags = t->answer_query_flags | dns_transaction_source_to_query_flags(t->answer_source); q->answer_errno = t->answer_errno; diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h index 2723299..29d7288 100644 --- a/src/resolve/resolved-dns-query.h +++ b/src/resolve/resolved-dns-query.h @@ -73,6 +73,8 @@ struct DnsQuery { /* Discovered data */ DnsAnswer *answer; int answer_rcode; + int answer_ede_rcode; + char *answer_ede_msg; DnssecResult answer_dnssec_result; uint64_t answer_query_flags; DnsProtocol answer_protocol; diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c index b280a5a..204d4a6 100644 --- a/src/resolve/resolved-dns-rr.c +++ b/src/resolve/resolved-dns-rr.c @@ -15,6 +15,7 @@ #include "string-util.h" #include "strv.h" #include "terminal-util.h" +#include "unaligned.h" DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { DnsResourceKey *k; @@ -195,7 +196,7 @@ bool dns_resource_key_is_dnssd_two_label_ptr(const DnsResourceKey *key) { if (dns_name_parent(&name) <= 0) return false; - return dns_name_equal(name, "_tcp.local") || dns_name_equal(name, "_udp.local"); + return dns_name_equal(name, "_tcp.local") > 0 || dns_name_equal(name, "_udp.local") > 0; } int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { @@ -310,8 +311,8 @@ static void dns_resource_key_hash_func(const DnsResourceKey *k, struct siphash * assert(k); dns_name_hash_func(dns_resource_key_name(k), state); - siphash24_compress(&k->class, sizeof(k->class), state); - siphash24_compress(&k->type, sizeof(k->type), state); + siphash24_compress_typesafe(k->class, state); + siphash24_compress_typesafe(k->type, state); } static int dns_resource_key_compare_func(const DnsResourceKey *x, const DnsResourceKey *y) { @@ -486,11 +487,24 @@ static DnsResourceRecord* dns_resource_record_free(DnsResourceRecord *rr) { free(rr->tlsa.data); break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + free(rr->svcb.target_name); + dns_svc_param_free_all(rr->svcb.params); + break; + case DNS_TYPE_CAA: free(rr->caa.tag); free(rr->caa.value); break; + case DNS_TYPE_NAPTR: + free(rr->naptr.flags); + free(rr->naptr.services); + free(rr->naptr.regexp); + free(rr->naptr.replacement); + break; + case DNS_TYPE_OPENPGPKEY: default: if (!rr->unparsable) @@ -665,19 +679,24 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou case DNS_TYPE_RRSIG: /* do the fast comparisons first */ - return a->rrsig.type_covered == b->rrsig.type_covered && - a->rrsig.algorithm == b->rrsig.algorithm && - a->rrsig.labels == b->rrsig.labels && - a->rrsig.original_ttl == b->rrsig.original_ttl && - a->rrsig.expiration == b->rrsig.expiration && - a->rrsig.inception == b->rrsig.inception && - a->rrsig.key_tag == b->rrsig.key_tag && - FIELD_EQUAL(a->rrsig, b->rrsig, signature) && - dns_name_equal(a->rrsig.signer, b->rrsig.signer); + if (!(a->rrsig.type_covered == b->rrsig.type_covered && + a->rrsig.algorithm == b->rrsig.algorithm && + a->rrsig.labels == b->rrsig.labels && + a->rrsig.original_ttl == b->rrsig.original_ttl && + a->rrsig.expiration == b->rrsig.expiration && + a->rrsig.inception == b->rrsig.inception && + a->rrsig.key_tag == b->rrsig.key_tag && + FIELD_EQUAL(a->rrsig, b->rrsig, signature))) + return false; + + return dns_name_equal(a->rrsig.signer, b->rrsig.signer); case DNS_TYPE_NSEC: - return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && - bitmap_equal(a->nsec.types, b->nsec.types); + r = dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name); + if (r <= 0) + return r; + + return bitmap_equal(a->nsec.types, b->nsec.types); case DNS_TYPE_NSEC3: return a->nsec3.algorithm == b->nsec3.algorithm && @@ -693,11 +712,31 @@ int dns_resource_record_payload_equal(const DnsResourceRecord *a, const DnsResou a->tlsa.matching_type == b->tlsa.matching_type && FIELD_EQUAL(a->tlsa, b->tlsa, data); + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + + if (!(a->svcb.priority == b->svcb.priority && + dns_svc_params_equal(a->svcb.params, b->svcb.params))) + return false; + + return dns_name_equal(a->svcb.target_name, b->svcb.target_name); + case DNS_TYPE_CAA: return a->caa.flags == b->caa.flags && streq(a->caa.tag, b->caa.tag) && FIELD_EQUAL(a->caa, b->caa, value); + case DNS_TYPE_NAPTR: + r = dns_name_equal(a->naptr.replacement, b->naptr.replacement); + if (r <= 0) + return r; + + return a->naptr.order == b->naptr.order && + a->naptr.preference == b->naptr.preference && + streq(a->naptr.flags, b->naptr.flags) && + streq(a->naptr.services, b->naptr.services) && + streq(a->naptr.regexp, b->naptr.regexp); + case DNS_TYPE_OPENPGPKEY: default: return FIELD_EQUAL(a->generic, b->generic, data); @@ -831,6 +870,107 @@ static char *format_txt(DnsTxtItem *first) { return s; } +static char *format_svc_param_value(DnsSvcParam *i) { + _cleanup_free_ char *value = NULL; + + assert(i); + + switch (i->key) { + case DNS_SVC_PARAM_KEY_ALPN: { + size_t offset = 0; + _cleanup_strv_free_ char **values_strv = NULL; + while (offset < i->length) { + size_t sz = (uint8_t) i->value[offset++]; + + char *alpn = cescape_length((char *)&i->value[offset], sz); + if (!alpn) + return NULL; + + if (strv_push(&values_strv, alpn) < 0) + return NULL; + + offset += sz; + } + value = strv_join(values_strv, ","); + if (!value) + return NULL; + break; + + } + case DNS_SVC_PARAM_KEY_PORT: { + uint16_t port = unaligned_read_be16(i->value); + if (asprintf(&value, "%" PRIu16, port) < 0) + return NULL; + return TAKE_PTR(value); + } + case DNS_SVC_PARAM_KEY_IPV4HINT: { + const struct in_addr *addrs = i->value_in_addr; + _cleanup_strv_free_ char **values_strv = NULL; + for (size_t n = 0; n < i->length / sizeof (struct in_addr); n++) { + char *addr; + if (in_addr_to_string(AF_INET, (const union in_addr_union*) &addrs[n], &addr) < 0) + return NULL; + if (strv_push(&values_strv, addr) < 0) + return NULL; + } + return strv_join(values_strv, ","); + } + case DNS_SVC_PARAM_KEY_IPV6HINT: { + const struct in6_addr *addrs = i->value_in6_addr; + _cleanup_strv_free_ char **values_strv = NULL; + for (size_t n = 0; n < i->length / sizeof (struct in6_addr); n++) { + char *addr; + if (in_addr_to_string(AF_INET6, (const union in_addr_union*) &addrs[n], &addr) < 0) + return NULL; + if (strv_push(&values_strv, addr) < 0) + return NULL; + } + return strv_join(values_strv, ","); + } + default: { + value = decescape((char *)&i->value, " ,", i->length); + if (!value) + return NULL; + break; + } + } + + char *qvalue; + if (asprintf(&qvalue, "\"%s\"", value) < 0) + return NULL; + return qvalue; +} + +static char *format_svc_param(DnsSvcParam *i) { + const char *key = FORMAT_DNS_SVC_PARAM_KEY(i->key); + _cleanup_free_ char *value = NULL; + + assert(i); + + if (i->length == 0) + return strdup(key); + + value = format_svc_param_value(i); + if (!value) + return NULL; + + return strjoin(key, "=", value); +} + +static char *format_svc_params(DnsSvcParam *first) { + _cleanup_strv_free_ char **params = NULL; + + LIST_FOREACH(params, i, first) { + char *param = format_svc_param(i); + if (!param) + return NULL; + if (strv_push(¶ms, param) < 0) + return NULL; + } + + return strv_join(params, " "); +} + const char *dns_resource_record_to_string(DnsResourceRecord *rr) { _cleanup_free_ char *s = NULL, *t = NULL; char k[DNS_RESOURCE_KEY_STRING_MAX]; @@ -1141,6 +1281,19 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) { break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + t = format_svc_params(rr->svcb.params); + if (!t) + return NULL; + r = asprintf(&s, "%s %d %s %s", k, rr->svcb.priority, + isempty(rr->svcb.target_name) ? "." : rr->svcb.target_name, + t); + if (r < 0) + return NULL; + + break; + case DNS_TYPE_OPENPGPKEY: r = asprintf(&s, "%s", k); if (r < 0) @@ -1153,6 +1306,31 @@ const char *dns_resource_record_to_string(DnsResourceRecord *rr) { return NULL; break; + case DNS_TYPE_NAPTR: { + _cleanup_free_ char *tt = NULL, *ttt = NULL; + + t = octescape(rr->naptr.flags, SIZE_MAX); + if (!t) + return NULL; + + tt = octescape(rr->naptr.services, SIZE_MAX); + if (!tt) + return NULL; + + ttt = octescape(rr->naptr.regexp, SIZE_MAX); + if (!ttt) + return NULL; + + if (asprintf(&s, "%" PRIu16 " %" PRIu16 " \"%s\" \"%s\" \"%s\" %s.", + rr->naptr.order, + rr->naptr.preference, + t, + tt, + ttt, + rr->naptr.replacement) < 0) + return NULL; + break; + } default: /* Format as documented in RFC 3597, Section 5 */ if (rr->generic.data_size == 0) @@ -1344,9 +1522,9 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash * switch (rr->unparsable ? _DNS_TYPE_INVALID : rr->key->type) { case DNS_TYPE_SRV: - siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); - siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); - siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + siphash24_compress_typesafe(rr->srv.priority, state); + siphash24_compress_typesafe(rr->srv.weight, state); + siphash24_compress_typesafe(rr->srv.port, state); dns_name_hash_func(rr->srv.name, state); break; @@ -1375,59 +1553,59 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash * } case DNS_TYPE_A: - siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + siphash24_compress_typesafe(rr->a.in_addr, state); break; case DNS_TYPE_AAAA: - siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + siphash24_compress_typesafe(rr->aaaa.in6_addr, state); break; case DNS_TYPE_SOA: dns_name_hash_func(rr->soa.mname, state); dns_name_hash_func(rr->soa.rname, state); - siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); - siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); - siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); - siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); - siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + siphash24_compress_typesafe(rr->soa.serial, state); + siphash24_compress_typesafe(rr->soa.refresh, state); + siphash24_compress_typesafe(rr->soa.retry, state); + siphash24_compress_typesafe(rr->soa.expire, state); + siphash24_compress_typesafe(rr->soa.minimum, state); break; case DNS_TYPE_MX: - siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + siphash24_compress_typesafe(rr->mx.priority, state); dns_name_hash_func(rr->mx.exchange, state); break; case DNS_TYPE_LOC: - siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); - siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); - siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); - siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); - siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); - siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); - siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + siphash24_compress_typesafe(rr->loc.version, state); + siphash24_compress_typesafe(rr->loc.size, state); + siphash24_compress_typesafe(rr->loc.horiz_pre, state); + siphash24_compress_typesafe(rr->loc.vert_pre, state); + siphash24_compress_typesafe(rr->loc.latitude, state); + siphash24_compress_typesafe(rr->loc.longitude, state); + siphash24_compress_typesafe(rr->loc.altitude, state); break; case DNS_TYPE_SSHFP: - siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); - siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress_typesafe(rr->sshfp.algorithm, state); + siphash24_compress_typesafe(rr->sshfp.fptype, state); siphash24_compress_safe(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); break; case DNS_TYPE_DNSKEY: - siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); - siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); - siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress_typesafe(rr->dnskey.flags, state); + siphash24_compress_typesafe(rr->dnskey.protocol, state); + siphash24_compress_typesafe(rr->dnskey.algorithm, state); siphash24_compress_safe(rr->dnskey.key, rr->dnskey.key_size, state); break; case DNS_TYPE_RRSIG: - siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); - siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); - siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); - siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); - siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); - siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); - siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + siphash24_compress_typesafe(rr->rrsig.type_covered, state); + siphash24_compress_typesafe(rr->rrsig.algorithm, state); + siphash24_compress_typesafe(rr->rrsig.labels, state); + siphash24_compress_typesafe(rr->rrsig.original_ttl, state); + siphash24_compress_typesafe(rr->rrsig.expiration, state); + siphash24_compress_typesafe(rr->rrsig.inception, state); + siphash24_compress_typesafe(rr->rrsig.key_tag, state); dns_name_hash_func(rr->rrsig.signer, state); siphash24_compress_safe(rr->rrsig.signature, rr->rrsig.signature_size, state); break; @@ -1440,34 +1618,53 @@ void dns_resource_record_hash_func(const DnsResourceRecord *rr, struct siphash * break; case DNS_TYPE_DS: - siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); - siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); - siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress_typesafe(rr->ds.key_tag, state); + siphash24_compress_typesafe(rr->ds.algorithm, state); + siphash24_compress_typesafe(rr->ds.digest_type, state); siphash24_compress_safe(rr->ds.digest, rr->ds.digest_size, state); break; case DNS_TYPE_NSEC3: - siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); - siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); - siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress_typesafe(rr->nsec3.algorithm, state); + siphash24_compress_typesafe(rr->nsec3.flags, state); + siphash24_compress_typesafe(rr->nsec3.iterations, state); siphash24_compress_safe(rr->nsec3.salt, rr->nsec3.salt_size, state); siphash24_compress_safe(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); /* FIXME: We leave the bitmaps out */ break; case DNS_TYPE_TLSA: - siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); - siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); - siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); + siphash24_compress_typesafe(rr->tlsa.cert_usage, state); + siphash24_compress_typesafe(rr->tlsa.selector, state); + siphash24_compress_typesafe(rr->tlsa.matching_type, state); siphash24_compress_safe(rr->tlsa.data, rr->tlsa.data_size, state); break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + dns_name_hash_func(rr->svcb.target_name, state); + siphash24_compress_typesafe(rr->svcb.priority, state); + LIST_FOREACH(params, j, rr->svcb.params) { + siphash24_compress_typesafe(j->key, state); + siphash24_compress_safe(j->value, j->length, state); + } + break; + case DNS_TYPE_CAA: - siphash24_compress(&rr->caa.flags, sizeof(rr->caa.flags), state); + siphash24_compress_typesafe(rr->caa.flags, state); string_hash_func(rr->caa.tag, state); siphash24_compress_safe(rr->caa.value, rr->caa.value_size, state); break; + case DNS_TYPE_NAPTR: + siphash24_compress_typesafe(rr->naptr.order, state); + siphash24_compress_typesafe(rr->naptr.preference, state); + string_hash_func(rr->naptr.flags, state); + string_hash_func(rr->naptr.services, state); + string_hash_func(rr->naptr.regexp, state); + dns_name_hash_func(rr->naptr.replacement, state); + break; + case DNS_TYPE_OPENPGPKEY: default: siphash24_compress_safe(rr->generic.data, rr->generic.data_size, state); @@ -1675,6 +1872,34 @@ DnsResourceRecord *dns_resource_record_copy(DnsResourceRecord *rr) { copy->caa.value_size = rr->caa.value_size; break; + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: + copy->svcb.priority = rr->svcb.priority; + copy->svcb.target_name = strdup(rr->svcb.target_name); + if (!copy->svcb.target_name) + return NULL; + copy->svcb.params = dns_svc_params_copy(rr->svcb.params); + if (rr->svcb.params && !copy->svcb.params) + return NULL; + break; + + case DNS_TYPE_NAPTR: + copy->naptr.order = rr->naptr.order; + copy->naptr.preference = rr->naptr.preference; + copy->naptr.flags = strdup(rr->naptr.flags); + if (!copy->naptr.flags) + return NULL; + copy->naptr.services = strdup(rr->naptr.services); + if (!copy->naptr.services) + return NULL; + copy->naptr.regexp = strdup(rr->naptr.regexp); + if (!copy->naptr.regexp) + return NULL; + copy->naptr.replacement = strdup(rr->naptr.replacement); + if (!copy->naptr.replacement) + return NULL; + break; + case DNS_TYPE_OPT: default: copy->generic.data = memdup(rr->generic.data, rr->generic.data_size); @@ -1789,6 +2014,13 @@ DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *first) { return NULL; } +DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *first) { + LIST_FOREACH(params, i, first) + free(i); + + return NULL; +} + bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { DnsTxtItem *bb = b; @@ -1825,6 +2057,45 @@ DnsTxtItem *dns_txt_item_copy(DnsTxtItem *first) { return copy; } +bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b) { + DnsSvcParam *bb = b; + + if (a == b) + return true; + + LIST_FOREACH(params, aa, a) { + if (!bb) + return false; + + if (aa->key != bb->key) + return false; + + if (memcmp_nn(aa->value, aa->length, bb->value, bb->length) != 0) + return false; + + bb = bb->params_next; + } + + return !bb; +} + +DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first) { + DnsSvcParam *copy = NULL, *end = NULL; + + LIST_FOREACH(params, i, first) { + DnsSvcParam *j; + + j = memdup(i, offsetof(DnsSvcParam, value) + i->length); + if (!j) + return dns_svc_param_free_all(copy); + + LIST_INSERT_AFTER(params, copy, end, j); + end = j; + } + + return copy; +} + int dns_txt_item_new_empty(DnsTxtItem **ret) { DnsTxtItem *i; @@ -1947,10 +2218,33 @@ static int txt_to_json(DnsTxtItem *items, JsonVariant **ret) { r = json_variant_new_array(ret, elements, n); finalize: - for (size_t i = 0; i < n; i++) - json_variant_unref(elements[i]); + json_variant_unref_many(elements, n); + return r; +} + +static int svc_params_to_json(DnsSvcParam *params, JsonVariant **ret) { + JsonVariant **elements = NULL; + size_t n = 0; + int r; + + assert(ret); + + LIST_FOREACH(params, i, params) { + if (!GREEDY_REALLOC(elements, n + 1)) { + r = -ENOMEM; + goto finalize; + } - free(elements); + r = json_variant_new_base64(elements + n, i->value, i->length); + if (r < 0) + goto finalize; + + n++; + } + + r = json_variant_new_array(ret, elements, n); +finalize: + json_variant_unref_many(elements, n); return r; } @@ -2129,6 +2423,21 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) { JSON_BUILD_PAIR("matchingType", JSON_BUILD_UNSIGNED(rr->tlsa.matching_type)), JSON_BUILD_PAIR("data", JSON_BUILD_HEX(rr->tlsa.data, rr->tlsa.data_size)))); + case DNS_TYPE_SVCB: + case DNS_TYPE_HTTPS: { + _cleanup_(json_variant_unrefp) JsonVariant *p = NULL; + r = svc_params_to_json(rr->svcb.params, &p); + if (r < 0) + return r; + + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->svcb.priority)), + JSON_BUILD_PAIR("target", JSON_BUILD_STRING(rr->svcb.target_name)), + JSON_BUILD_PAIR("params", JSON_BUILD_VARIANT(p)))); + } + case DNS_TYPE_CAA: return json_build(ret, JSON_BUILD_OBJECT( @@ -2137,6 +2446,18 @@ int dns_resource_record_to_json(DnsResourceRecord *rr, JsonVariant **ret) { JSON_BUILD_PAIR("tag", JSON_BUILD_STRING(rr->caa.tag)), JSON_BUILD_PAIR("value", JSON_BUILD_OCTESCAPE(rr->caa.value, rr->caa.value_size)))); + case DNS_TYPE_NAPTR: + return json_build(ret, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("key", JSON_BUILD_VARIANT(k)), + JSON_BUILD_PAIR("order", JSON_BUILD_UNSIGNED(rr->naptr.order)), + JSON_BUILD_PAIR("preference", JSON_BUILD_UNSIGNED(rr->naptr.preference)), + /* NB: we name this flags field here naptrFlags, because there's already another "flags" field (for example in CAA) which has a different type */ + JSON_BUILD_PAIR("naptrFlags", JSON_BUILD_STRING(rr->naptr.flags)), + JSON_BUILD_PAIR("services", JSON_BUILD_STRING(rr->naptr.services)), + JSON_BUILD_PAIR("regexp", JSON_BUILD_STRING(rr->naptr.regexp)), + JSON_BUILD_PAIR("replacement", JSON_BUILD_STRING(rr->naptr.replacement)))); + default: /* Can't provide broken-down format */ *ret = NULL; diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h index 1a12933..156fa01 100644 --- a/src/resolve/resolved-dns-rr.h +++ b/src/resolve/resolved-dns-rr.h @@ -16,6 +16,7 @@ typedef struct DnsResourceKey DnsResourceKey; typedef struct DnsResourceRecord DnsResourceRecord; typedef struct DnsTxtItem DnsTxtItem; +typedef struct DnsSvcParam DnsSvcParam; /* DNSKEY RR flags */ #define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) @@ -90,6 +91,17 @@ struct DnsTxtItem { uint8_t data[]; }; +struct DnsSvcParam { + uint16_t key; + size_t length; + LIST_FIELDS(DnsSvcParam, params); + union { + DECLARE_FLEX_ARRAY(uint8_t, value); + DECLARE_FLEX_ARRAY(struct in_addr, value_in_addr); + DECLARE_FLEX_ARRAY(struct in6_addr, value_in6_addr); + }; +}; + struct DnsResourceRecord { unsigned n_ref; uint32_t ttl; @@ -243,6 +255,13 @@ struct DnsResourceRecord { uint8_t matching_type; } tlsa; + /* https://tools.ietf.org/html/rfc9460 */ + struct { + uint16_t priority; + char *target_name; + DnsSvcParam *params; + } svcb, https; + /* https://tools.ietf.org/html/rfc6844 */ struct { char *tag; @@ -251,6 +270,16 @@ struct DnsResourceRecord { uint8_t flags; } caa; + + /* https://datatracker.ietf.org/doc/html/rfc2915 */ + struct { + uint16_t order; + uint16_t preference; + char *flags; + char *services; + char *regexp; + char *replacement; + } naptr; }; /* Note: fields should be ordered to minimize alignment gaps. Use pahole! */ @@ -369,6 +398,10 @@ bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); DnsTxtItem *dns_txt_item_copy(DnsTxtItem *i); int dns_txt_item_new_empty(DnsTxtItem **ret); +DnsSvcParam *dns_svc_param_free_all(DnsSvcParam *i); +bool dns_svc_params_equal(DnsSvcParam *a, DnsSvcParam *b); +DnsSvcParam *dns_svc_params_copy(DnsSvcParam *first); + int dns_resource_record_new_from_raw(DnsResourceRecord **ret, const void *data, size_t size); int dns_resource_key_to_json(DnsResourceKey *key, JsonVariant **ret); diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c index af8e9cd..17bc823 100644 --- a/src/resolve/resolved-dns-scope.c +++ b/src/resolve/resolved-dns-scope.c @@ -12,6 +12,7 @@ #include "random-util.h" #include "resolved-dnssd.h" #include "resolved-dns-scope.h" +#include "resolved-dns-synthesize.h" #include "resolved-dns-zone.h" #include "resolved-llmnr.h" #include "resolved-mdns.h" @@ -41,6 +42,7 @@ int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int .protocol = protocol, .family = family, .resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC, + .mdns_goodbye_event_source = NULL, }; if (protocol == DNS_PROTOCOL_DNS) { @@ -115,6 +117,8 @@ DnsScope* dns_scope_free(DnsScope *s) { sd_event_source_disable_unref(s->announce_event_source); + sd_event_source_disable_unref(s->mdns_goodbye_event_source); + dns_cache_flush(&s->cache); dns_zone_flush(&s->zone); @@ -616,12 +620,13 @@ static bool dns_refuse_special_use_domain(const char *domain, DnsQuestion *quest DnsScopeMatch dns_scope_good_domain( DnsScope *s, - DnsQuery *q) { + DnsQuery *q, + uint64_t query_flags) { DnsQuestion *question; const char *domain; uint64_t flags; - int ifindex; + int ifindex, r; /* This returns the following return values: * @@ -680,6 +685,15 @@ DnsScopeMatch dns_scope_good_domain( is_dns_proxy_stub_hostname(domain)) return DNS_SCOPE_NO; + /* Don't look up the local host name via the network, unless user turned of local synthesis of it */ + if (manager_is_own_hostname(s->manager, domain) && shall_synthesize_own_hostname_rrs()) + return DNS_SCOPE_NO; + + /* Never send SOA or NS or DNSSEC request to LLMNR, where they make little sense. */ + r = dns_question_types_suitable_for_protocol(question, s->protocol); + if (r <= 0) + return DNS_SCOPE_NO; + switch (s->protocol) { case DNS_PROTOCOL_DNS: { @@ -735,7 +749,8 @@ DnsScopeMatch dns_scope_good_domain( /* If ResolveUnicastSingleLabel=yes and the query is single-label, then bump match result to prevent LLMNR monopoly among candidates. */ - if (s->manager->resolve_unicast_single_label && dns_name_is_single_label(domain)) + if ((s->manager->resolve_unicast_single_label || (query_flags & SD_RESOLVED_RELAX_SINGLE_LABEL)) && + dns_name_is_single_label(domain)) return DNS_SCOPE_YES_BASE + 1; /* Let's return the number of labels in the best matching result */ @@ -1588,7 +1603,7 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { assert(scope); - if (hashmap_size(scope->manager->dnssd_services) == 0) + if (hashmap_isempty(scope->manager->dnssd_services)) return 0; scope->announced = false; @@ -1600,6 +1615,12 @@ int dns_scope_add_dnssd_services(DnsScope *scope) { if (r < 0) log_warning_errno(r, "Failed to add PTR record to MDNS zone: %m"); + if (service->sub_ptr_rr) { + r = dns_zone_put(&scope->zone, scope, service->sub_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add selective PTR record to MDNS zone: %m"); + } + r = dns_zone_put(&scope->zone, scope, service->srv_rr, true); if (r < 0) log_warning_errno(r, "Failed to add SRV record to MDNS zone: %m"); @@ -1632,6 +1653,7 @@ int dns_scope_remove_dnssd_services(DnsScope *scope) { HASHMAP_FOREACH(service, scope->manager->dnssd_services) { dns_zone_remove_rr(&scope->zone, service->ptr_rr); + dns_zone_remove_rr(&scope->zone, service->sub_ptr_rr); dns_zone_remove_rr(&scope->zone, service->srv_rr); LIST_FOREACH(items, txt_data, service->txt_data_items) dns_zone_remove_rr(&scope->zone, txt_data->rr); @@ -1712,3 +1734,65 @@ int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret) { JSON_BUILD_PAIR_CONDITION(scope->link, "ifname", JSON_BUILD_STRING(scope->link ? scope->link->ifname : NULL)), JSON_BUILD_PAIR_VARIANT("cache", cache))); } + +int dns_type_suitable_for_protocol(uint16_t type, DnsProtocol protocol) { + + /* Tests whether it makes sense to route queries for the specified DNS RR types to the specified + * protocol. For classic DNS pretty much all RR types are suitable, but for LLMNR/mDNS let's + * allowlist only a few that make sense. We use this when routing queries so that we can more quickly + * return errors for queries that will almost certainly fail/time-out otherwise. For example, this + * ensures that SOA, NS, or DS/DNSKEY queries are never routed to mDNS/LLMNR where they simply make + * no sense. */ + + if (dns_type_is_obsolete(type)) + return false; + + if (!dns_type_is_valid_query(type)) + return false; + + switch (protocol) { + + case DNS_PROTOCOL_DNS: + return true; + + case DNS_PROTOCOL_LLMNR: + return IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_A, + DNS_TYPE_AAAA, + DNS_TYPE_CNAME, + DNS_TYPE_PTR, + DNS_TYPE_TXT); + + case DNS_PROTOCOL_MDNS: + return IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_A, + DNS_TYPE_AAAA, + DNS_TYPE_CNAME, + DNS_TYPE_PTR, + DNS_TYPE_TXT, + DNS_TYPE_SRV, + DNS_TYPE_NSEC, + DNS_TYPE_HINFO); + + default: + return -EPROTONOSUPPORT; + } +} + +int dns_question_types_suitable_for_protocol(DnsQuestion *q, DnsProtocol protocol) { + DnsResourceKey *key; + int r; + + /* Tests whether the types in the specified question make any sense to be routed to the specified + * protocol, i.e. if dns_type_suitable_for_protocol() is true for any of the contained RR types */ + + DNS_QUESTION_FOREACH(key, q) { + r = dns_type_suitable_for_protocol(key->type, protocol); + if (r != 0) + return r; + } + + return false; +} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h index b1d1206..23f147b 100644 --- a/src/resolve/resolved-dns-scope.h +++ b/src/resolve/resolved-dns-scope.h @@ -46,6 +46,8 @@ struct DnsScope { sd_event_source *announce_event_source; + sd_event_source *mdns_goodbye_event_source; + RateLimit ratelimit; usec_t resend_timeout; @@ -77,7 +79,7 @@ int dns_scope_emit_udp(DnsScope *s, int fd, int af, DnsPacket *p); int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port, union sockaddr_union *ret_socket_address); int dns_scope_socket_udp(DnsScope *s, DnsServer *server); -DnsScopeMatch dns_scope_good_domain(DnsScope *s, DnsQuery *q); +DnsScopeMatch dns_scope_good_domain(DnsScope *s, DnsQuery *q, uint64_t query_flags); bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); DnsServer *dns_scope_get_dns_server(DnsScope *s); @@ -113,3 +115,6 @@ int dns_scope_remove_dnssd_services(DnsScope *scope); bool dns_scope_is_default_route(DnsScope *scope); int dns_scope_dump_cache_to_json(DnsScope *scope, JsonVariant **ret); + +int dns_type_suitable_for_protocol(uint16_t type, DnsProtocol protocol); +int dns_question_types_suitable_for_protocol(DnsQuestion *q, DnsProtocol protocol); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index b7db839..340f11f 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -28,7 +28,8 @@ int dns_server_new( const union in_addr_union *in_addr, uint16_t port, int ifindex, - const char *server_name) { + const char *server_name, + ResolveConfigSource config_source) { _cleanup_free_ char *name = NULL; DnsServer *s; @@ -67,6 +68,7 @@ int dns_server_new( .port = port, .ifindex = ifindex, .server_name = TAKE_PTR(name), + .config_source = config_source, }; dns_server_reset_features(s); @@ -751,10 +753,10 @@ size_t dns_server_get_mtu(DnsServer *s) { static void dns_server_hash_func(const DnsServer *s, struct siphash *state) { assert(s); - siphash24_compress(&s->family, sizeof(s->family), state); - siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); - siphash24_compress(&s->port, sizeof(s->port), state); - siphash24_compress(&s->ifindex, sizeof(s->ifindex), state); + siphash24_compress_typesafe(s->family, state); + in_addr_hash_func(&s->address, s->family, state); + siphash24_compress_typesafe(s->port, state); + siphash24_compress_typesafe(s->ifindex, state); siphash24_compress_string(s->server_name, state); } @@ -794,6 +796,17 @@ void dns_server_unlink_all(DnsServer *first) { dns_server_unlink_all(next); } +void dns_server_unlink_on_reload(DnsServer *server) { + while (server) { + DnsServer *next = server->servers_next; + + if (server->config_source == RESOLVE_CONFIG_SOURCE_FILE) + dns_server_unlink(server); + + server = next; + } +} + bool dns_server_unlink_marked(DnsServer *server) { bool changed = false; diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index ed6560f..ef76bbc 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -24,6 +24,8 @@ typedef enum DnsServerType { _DNS_SERVER_TYPE_INVALID = -EINVAL, } DnsServerType; +#include "resolved-conf.h" + const char* dns_server_type_to_string(DnsServerType i) _const_; DnsServerType dns_server_type_from_string(const char *s) _pure_; @@ -100,6 +102,9 @@ struct DnsServer { /* If linked is set, then this server appears in the servers linked list */ bool linked:1; LIST_FIELDS(DnsServer, servers); + + /* Servers registered via D-Bus are not removed on reload */ + ResolveConfigSource config_source; }; int dns_server_new( @@ -111,7 +116,8 @@ int dns_server_new( const union in_addr_union *address, uint16_t port, int ifindex, - const char *server_string); + const char *server_string, + ResolveConfigSource config_source); DnsServer* dns_server_ref(DnsServer *s); DnsServer* dns_server_unref(DnsServer *s); @@ -145,6 +151,7 @@ void dns_server_warn_downgrade(DnsServer *server); DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr, uint16_t port, int ifindex, const char *name); void dns_server_unlink_all(DnsServer *first); +void dns_server_unlink_on_reload(DnsServer *server); bool dns_server_unlink_marked(DnsServer *first); void dns_server_mark_all(DnsServer *first); diff --git a/src/resolve/resolved-dns-stream.c b/src/resolve/resolved-dns-stream.c index 056ba77..1a43d0b 100644 --- a/src/resolve/resolved-dns-stream.c +++ b/src/resolve/resolved-dns-stream.c @@ -195,7 +195,7 @@ static int dns_stream_identify(DnsStream *s) { /* Make sure all packets for this connection are sent on the same interface */ r = socket_set_unicast_if(s->fd, s->local.sa.sa_family, s->ifindex); if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m"); + log_debug_errno(r, "Failed to invoke IP_UNICAST_IF/IPV6_UNICAST_IF: %m"); } s->identified = true; @@ -454,7 +454,7 @@ static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *use if (progressed && s->timeout_event_source) { r = sd_event_source_set_time_relative(s->timeout_event_source, DNS_STREAM_ESTABLISHED_TIMEOUT_USEC); if (r < 0) - log_warning_errno(errno, "Couldn't restart TCP connection timeout, ignoring: %m"); + log_warning_errno(r, "Couldn't restart TCP connection timeout, ignoring: %m"); } return 0; diff --git a/src/resolve/resolved-dns-stub.c b/src/resolve/resolved-dns-stub.c index 10b35da..23d4db9 100644 --- a/src/resolve/resolved-dns-stub.c +++ b/src/resolve/resolved-dns-stub.c @@ -27,10 +27,10 @@ static int manager_dns_stub_fd(Manager *m, int family, const union in_addr_union static void dns_stub_listener_extra_hash_func(const DnsStubListenerExtra *a, struct siphash *state) { assert(a); - siphash24_compress(&a->mode, sizeof(a->mode), state); - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); - siphash24_compress(&a->port, sizeof(a->port), state); + siphash24_compress_typesafe(a->mode, state); + siphash24_compress_typesafe(a->family, state); + in_addr_hash_func(&a->address, a->family, state); + siphash24_compress_typesafe(a->port, state); } static int dns_stub_listener_extra_compare_func(const DnsStubListenerExtra *a, const DnsStubListenerExtra *b) { @@ -94,11 +94,11 @@ DnsStubListenerExtra *dns_stub_listener_extra_free(DnsStubListenerExtra *p) { static void stub_packet_hash_func(const DnsPacket *p, struct siphash *state) { assert(p); - siphash24_compress(&p->protocol, sizeof(p->protocol), state); - siphash24_compress(&p->family, sizeof(p->family), state); - siphash24_compress(&p->sender, sizeof(p->sender), state); - siphash24_compress(&p->ipproto, sizeof(p->ipproto), state); - siphash24_compress(&p->sender_port, sizeof(p->sender_port), state); + siphash24_compress_typesafe(p->protocol, state); + siphash24_compress_typesafe(p->family, state); + siphash24_compress_typesafe(p->sender, state); + siphash24_compress_typesafe(p->ipproto, state); + siphash24_compress_typesafe(p->sender_port, state); siphash24_compress(DNS_PACKET_HEADER(p), sizeof(DnsPacketHeader), state); /* We don't bother hashing the full packet here, just the header */ @@ -937,7 +937,7 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea return; } - if (dns_type_is_zone_transer(dns_question_first_key(p->question)->type)) { + if (dns_type_is_zone_transfer(dns_question_first_key(p->question)->type)) { log_debug("Got request for zone transfer, refusing."); dns_stub_send_failure(m, l, s, p, DNS_RCODE_REFUSED, false); return; @@ -966,8 +966,8 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea log_debug("Got request to DNS proxy address 127.0.0.54, enabling bypass logic."); bypass = true; protocol_flags = SD_RESOLVED_DNS|SD_RESOLVED_NO_ZONE; /* Turn off mDNS/LLMNR for proxy stub. */ - } else if ((DNS_PACKET_DO(p) && DNS_PACKET_CD(p))) { - log_debug("Got request with DNSSEC checking disabled, enabling bypass logic."); + } else if (DNS_PACKET_DO(p)) { + log_debug("Got request with DNSSEC enabled, enabling bypass logic."); bypass = true; } @@ -978,7 +978,8 @@ static void dns_stub_process_query(Manager *m, DnsStubListenerExtra *l, DnsStrea SD_RESOLVED_NO_SEARCH| SD_RESOLVED_NO_VALIDATE| SD_RESOLVED_REQUIRE_PRIMARY| - SD_RESOLVED_CLAMP_TTL); + SD_RESOLVED_CLAMP_TTL| + SD_RESOLVED_RELAX_SINGLE_LABEL); else r = dns_query_new(m, &q, p->question, p->question, NULL, 0, protocol_flags| diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c index 6144dc0..cccea54 100644 --- a/src/resolve/resolved-dns-synthesize.c +++ b/src/resolve/resolved-dns-synthesize.c @@ -439,6 +439,20 @@ static int synthesize_gateway_ptr( return answer_add_addresses_ptr(answer, "_gateway", addresses, n, af, address); } +bool shall_synthesize_own_hostname_rrs(void) { + static int cached = -1; + int r; + + if (cached >= 0) + return cached; + + r = secure_getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME: %m"); + + return (cached = (r != 0)); +} + int dns_synthesize_answer( Manager *m, DnsQuestion *q, @@ -479,8 +493,9 @@ int dns_synthesize_answer( } else if (manager_is_own_hostname(m, name)) { - if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0) + if (!shall_synthesize_own_hostname_rrs()) continue; + r = synthesize_system_hostname_rr(m, key, ifindex, &answer); if (r < 0) return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); @@ -530,7 +545,7 @@ int dns_synthesize_answer( } else if (dns_name_address(name, &af, &address) > 0) { int v, w, u; - if (getenv_bool("SYSTEMD_RESOLVED_SYNTHESIZE_HOSTNAME") == 0) + if (!shall_synthesize_own_hostname_rrs()) continue; v = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h index bf271e8..ca39e68 100644 --- a/src/resolve/resolved-dns-synthesize.h +++ b/src/resolve/resolved-dns-synthesize.h @@ -9,3 +9,5 @@ int dns_synthesize_family(uint64_t flags); DnsProtocol dns_synthesize_protocol(uint64_t flags); int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); + +bool shall_synthesize_own_hostname_rrs(void); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c index ad8b88e..92ac075 100644 --- a/src/resolve/resolved-dns-transaction.c +++ b/src/resolve/resolved-dns-transaction.c @@ -28,6 +28,8 @@ static void dns_transaction_reset_answer(DnsTransaction *t) { t->received = dns_packet_unref(t->received); t->answer = dns_answer_unref(t->answer); t->answer_rcode = 0; + t->answer_ede_rcode = _DNS_EDE_RCODE_INVALID; + t->answer_ede_msg = mfree(t->answer_ede_msg); t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; t->answer_query_flags = 0; @@ -74,8 +76,12 @@ static void dns_transaction_close_connection( * and the reply we might still get from the server will be eaten up instead of resulting in an ICMP * port unreachable error message. */ - /* Skip the graveyard stuff when we're shutting down, since that requires running event loop */ - if (!t->scope->manager->event || sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED) + /* Skip the graveyard stuff when we're shutting down, since that requires running event loop. + * Note that this is also called from dns_transaction_free(). In that case, scope may be NULL. */ + if (!t->scope || + !t->scope->manager || + !t->scope->manager->event || + sd_event_get_state(t->scope->manager->event) == SD_EVENT_FINISHED) use_graveyard = false; if (use_graveyard && t->dns_udp_fd >= 0 && t->sent && !t->received) { @@ -283,6 +289,7 @@ int dns_transaction_new( .dns_udp_fd = -EBADF, .answer_source = _DNS_TRANSACTION_SOURCE_INVALID, .answer_dnssec_result = _DNSSEC_RESULT_INVALID, + .answer_ede_rcode = _DNS_EDE_RCODE_INVALID, .answer_nsec_ttl = UINT32_MAX, .key = dns_resource_key_ref(key), .query_flags = query_flags, @@ -496,7 +503,7 @@ static int dns_transaction_pick_server(DnsTransaction *t) { dns_server_unref(t->server); t->server = dns_server_ref(server); - t->n_picked_servers ++; + t->n_picked_servers++; log_debug("Using DNS server %s for transaction %u.", strna(dns_server_string_full(t->server)), t->id); @@ -895,8 +902,25 @@ static int dns_transaction_dnssec_ready(DnsTransaction *t) { /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC * validation result */ - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); - t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ + log_debug("Auxiliary DNSSEC RR query failed validation: %s%s%s%s%s%s", + dnssec_result_to_string(dt->answer_dnssec_result), + dt->answer_ede_rcode >= 0 ? " (" : "", + dt->answer_ede_rcode >= 0 ? FORMAT_DNS_EDE_RCODE(dt->answer_ede_rcode) : "", + (dt->answer_ede_rcode >= 0 && !isempty(dt->answer_ede_msg)) ? ": " : "", + dt->answer_ede_rcode >= 0 ? strempty(dt->answer_ede_msg) : "", + dt->answer_ede_rcode >= 0 ? ")" : ""); + + /* Copy error code over */ + t->answer_dnssec_result = dt->answer_dnssec_result; + t->answer_ede_rcode = dt->answer_ede_rcode; + r = free_and_strdup(&t->answer_ede_msg, dt->answer_ede_msg); + if (r < 0) + log_oom_debug(); + + /* The answer would normally be replaced by the validated subset, but at this point + * we aren't going to bother validating the rest, so just drop it. */ + t->answer = dns_answer_unref(t->answer); + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); return 0; @@ -1135,13 +1159,130 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt } } + if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Response was truncated, let's try again with good old TCP */ + log_debug("Reply truncated, retrying via TCP."); + retry_with_tcp = true; + + } else if (t->scope->protocol == DNS_PROTOCOL_DNS && + DNS_PACKET_IS_FRAGMENTED(p)) { + + /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */ + if (t->server) + dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p)); + + if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { + /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack + * issues. (We don't do that on the lowest feature level however, since crappy DNS + * servers often do not implement TCP, hence falling back to TCP on fragmentation is + * counter-productive there.) */ + + log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)", + p->fragsize, p->size); + retry_with_tcp = true; + } + } + + if (retry_with_tcp) { + r = dns_transaction_emit_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } + if (r < 0) { + /* On LLMNR, if we cannot connect to the host, + * we immediately give up */ + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; + + /* On DNS, couldn't send? Try immediately again, with a new server */ + if (dns_transaction_limited_retry(t)) + return; + + /* No new server to try, give up */ + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + } + + return; + } + + /* After the superficial checks, actually parse the message. */ + r = dns_packet_extract(p); + if (r < 0) { + if (t->server) { + dns_server_packet_invalid(t->server, t->current_feature_level); + + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + } + + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + switch (t->scope->protocol) { - case DNS_PROTOCOL_DNS: + case DNS_PROTOCOL_DNS: { assert(t->server); + (void) dns_packet_ede_rcode(p, &t->answer_ede_rcode, &t->answer_ede_msg); + if (!t->bypass && IN_SET(DNS_PACKET_RCODE(p), DNS_RCODE_FORMERR, DNS_RCODE_SERVFAIL, DNS_RCODE_NOTIMP)) { + /* If the server has replied with detailed error data, using a degraded feature set + * will likely not help anyone. Examine the detailed error to determine the best + * course of action. */ + if (t->answer_ede_rcode >= 0 && DNS_PACKET_RCODE(p) == DNS_RCODE_SERVFAIL) { + /* These codes are related to DNSSEC configuration errors. If accurate, + * this is the domain operator's problem, and retrying won't help. */ + if (dns_ede_rcode_is_dnssec(t->answer_ede_rcode)) { + log_debug("Server returned error: %s (%s%s%s). Lookup failed.", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_ede_msg)); + + t->answer_dnssec_result = DNSSEC_UPSTREAM_FAILURE; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + /* These codes probably indicate a transient error. Let's try again. */ + if (IN_SET(t->answer_ede_rcode, DNS_EDE_RCODE_NOT_READY, DNS_EDE_RCODE_NET_ERROR)) { + log_debug("Server returned error: %s (%s%s%s), retrying transaction.", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_ede_msg)); + dns_transaction_retry(t, false); + return; + } + + /* OK, the query failed, but we still shouldn't degrade the feature set for + * this server. */ + log_debug("Server returned error: %s (%s%s%s)", + FORMAT_DNS_RCODE(DNS_PACKET_RCODE(p)), + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode), + isempty(t->answer_ede_msg) ? "" : ": ", + strempty(t->answer_ede_msg)); + break; + } /* Request failed, immediately try again with reduced features */ @@ -1198,7 +1339,11 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt if (DNS_PACKET_RCODE(p) == DNS_RCODE_REFUSED) { /* This server refused our request? If so, try again, use a different server */ - log_debug("Server returned REFUSED, switching servers, and retrying."); + if (t->answer_ede_rcode >= 0) + log_debug("Server returned REFUSED (%s), switching servers, and retrying.", + FORMAT_DNS_EDE_RCODE(t->answer_ede_rcode)); + else + log_debug("Server returned REFUSED, switching servers, and retrying."); if (dns_transaction_limited_retry(t)) return; @@ -1210,6 +1355,7 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt dns_server_packet_truncated(t->server, t->current_feature_level); break; + } case DNS_PROTOCOL_LLMNR: case DNS_PROTOCOL_MDNS: @@ -1220,83 +1366,6 @@ void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p, bool encrypt assert_not_reached(); } - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Response was truncated, let's try again with good old TCP */ - log_debug("Reply truncated, retrying via TCP."); - retry_with_tcp = true; - - } else if (t->scope->protocol == DNS_PROTOCOL_DNS && - DNS_PACKET_IS_FRAGMENTED(p)) { - - /* Report the fragment size, so that we downgrade from LARGE to regular EDNS0 if needed */ - if (t->server) - dns_server_packet_udp_fragmented(t->server, dns_packet_size_unfragmented(p)); - - if (t->current_feature_level > DNS_SERVER_FEATURE_LEVEL_UDP) { - /* Packet was fragmented. Let's retry with TCP to avoid fragmentation attack - * issues. (We don't do that on the lowest feature level however, since crappy DNS - * servers often do not implement TCP, hence falling back to TCP on fragmentation is - * counter-productive there.) */ - - log_debug("Reply fragmented, retrying via TCP. (Largest fragment size: %zu; Datagram size: %zu)", - p->fragsize, p->size); - retry_with_tcp = true; - } - } - - if (retry_with_tcp) { - r = dns_transaction_emit_tcp(t); - if (r == -ESRCH) { - /* No servers found? Damn! */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return; - } - if (r < 0) { - /* On LLMNR, if we cannot connect to the host, - * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) - goto fail; - - /* On DNS, couldn't send? Try immediately again, with a new server */ - if (dns_transaction_limited_retry(t)) - return; - - /* No new server to try, give up */ - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - } - - return; - } - - /* After the superficial checks, actually parse the message. */ - r = dns_packet_extract(p); - if (r < 0) { - if (t->server) { - dns_server_packet_invalid(t->server, t->current_feature_level); - - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - } - - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - if (t->server) { /* Report that we successfully received a valid packet with a good rcode after we initially got a bad * rcode and subsequently downgraded the protocol */ @@ -1770,8 +1839,12 @@ static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { t->answer_source = DNS_TRANSACTION_CACHE; if (t->answer_rcode == DNS_RCODE_SUCCESS) dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else + else { + if (t->received) + (void) dns_packet_ede_rcode(t->received, &t->answer_ede_rcode, &t->answer_ede_msg); + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + } return 0; } } @@ -2268,9 +2341,9 @@ static int dns_transaction_request_dnssec_rr_full(DnsTransaction *t, DnsResource r = dns_transaction_go(aux); if (r < 0) return r; - if (ret) - *ret = aux; } + if (ret) + *ret = aux; return 1; } @@ -2467,7 +2540,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; - /* If we are requesting a DNSKEY, we can anticiapte that we will want the matching DS + /* If we are requesting a DNSKEY, we can anticipate that we will want the matching DS * in the near future. Let's request it in advance so we don't have to wait in the * common case. */ if (aux) { @@ -2545,6 +2618,10 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { return r; if (r == 0) continue; + + /* If we were looking for the DS RR, don't request it again. */ + if (dns_transaction_key(t)->type == DNS_TYPE_DS) + continue; } r = dnssec_has_rrsig(t->answer, rr->key); @@ -2618,6 +2695,21 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { if (r < 0) return r; + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && dns_name_is_root(name)) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + /* We made it all the way to the root zone. If we are in allow-downgrade + * mode, we need to make at least one request that we can be certain should + * have been signed, to test for servers that are not dnssec aware. */ + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting root zone SOA to probe dnssec support."); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + break; } @@ -2676,7 +2768,7 @@ int dns_transaction_request_dnssec_keys(DnsTransaction *t) { * zones return NXDOMAIN even though they have further children. */ if (chased_insecure || was_signed) - /* In this case we already reqeusted what we need above. */ + /* In this case we already requested what we need above. */ name = NULL; else if (dns_transaction_key(t)->type == DNS_TYPE_DS) /* If the DS response is empty, we'll walk up the dns labels requesting DS until we @@ -2844,7 +2936,12 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * if (r == 0) continue; - return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + if (!FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED)) + return false; + + /* We expect this to be signed when the DS record exists, and don't expect it to be + * signed when the DS record is proven not to exist. */ + return dns_answer_match_key(dt->answer, dns_transaction_key(dt), NULL); } return true; @@ -3523,14 +3620,17 @@ int dns_transaction_validate_dnssec(DnsTransaction *t) { bool have_nsec = false; r = dnssec_validate_records(t, phase, &have_nsec, &nvalidations, &validated); - if (r <= 0) + if (r <= 0) { + DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated)); return r; + } if (nvalidations > DNSSEC_VALIDATION_MAX) { /* This reply requires an onerous number of signature validations to verify. Let's * not waste our time trying, as this shouldn't happen for well-behaved domains * anyway. */ t->answer_dnssec_result = DNSSEC_TOO_MANY_VALIDATIONS; + DNS_ANSWER_REPLACE(t->answer, TAKE_PTR(validated)); return 0; } diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h index 6be7c5f..30d2167 100644 --- a/src/resolve/resolved-dns-transaction.h +++ b/src/resolve/resolved-dns-transaction.h @@ -61,6 +61,8 @@ struct DnsTransaction { DnsAnswer *answer; int answer_rcode; + int answer_ede_rcode; + char *answer_ede_msg; DnssecResult answer_dnssec_result; DnsTransactionSource answer_source; uint32_t answer_nsec_ttl; diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c index 8aea5e1..9df93f1 100644 --- a/src/resolve/resolved-dns-trust-anchor.c +++ b/src/resolve/resolved-dns-trust-anchor.c @@ -186,7 +186,7 @@ static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { * trust anchor defined at all. This enables easy overriding * of negative trust anchors. */ - if (set_size(d->negative_by_name) > 0) + if (!set_isempty(d->negative_by_name)) return 0; r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); @@ -233,7 +233,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u return -EINVAL; } - r = extract_many_words(&p, NULL, 0, &class, &type, NULL); + r = extract_many_words(&p, NULL, 0, &class, &type); if (r < 0) return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); if (r != 2) { @@ -253,7 +253,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u int a, dt; size_t l; - r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, NULL); + r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type); if (r < 0) { log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); return -EINVAL; @@ -284,7 +284,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u return -EINVAL; } - r = unhexmem(p, strlen(p), &dd, &l); + r = unhexmem(p, &dd, &l); if (r < 0) { log_warning("Failed to parse DS digest %s on line %s:%u", p, path, line); return -EINVAL; @@ -307,7 +307,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u size_t l; int a; - r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, NULL); + r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm); if (r < 0) return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); if (r != 3) { @@ -343,7 +343,7 @@ static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, u return -EINVAL; } - r = unbase64mem(p, strlen(p), &k, &l); + r = unbase64mem(p, &k, &l); if (r < 0) return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", p, path, line); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c index f533f97..d4ede46 100644 --- a/src/resolve/resolved-dns-zone.c +++ b/src/resolve/resolved-dns-zone.c @@ -70,8 +70,8 @@ void dns_zone_flush(DnsZone *z) { while ((i = hashmap_first(z->by_key))) dns_zone_item_remove_and_free(z, i); - assert(hashmap_size(z->by_key) == 0); - assert(hashmap_size(z->by_name) == 0); + assert(hashmap_isempty(z->by_key)); + assert(hashmap_isempty(z->by_name)); z->by_key = hashmap_free(z->by_key); z->by_name = hashmap_free(z->by_name); diff --git a/src/resolve/resolved-dnssd-bus.c b/src/resolve/resolved-dnssd-bus.c index 0f0d478..4857a92 100644 --- a/src/resolve/resolved-dnssd-bus.c +++ b/src/resolve/resolved-dnssd-bus.c @@ -20,10 +20,14 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ m = s->manager; - r = bus_verify_polkit_async(message, CAP_SYS_ADMIN, - "org.freedesktop.resolve1.unregister-service", - NULL, false, s->originator, - &m->polkit_registry, error); + r = bus_verify_polkit_async_full( + message, + "org.freedesktop.resolve1.unregister-service", + /* details= */ NULL, + /* good_user= */ s->originator, + /* flags= */ 0, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -36,6 +40,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ log_warning_errno(r, "Failed to send goodbye messages in IPv4 scope: %m"); dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->ptr_rr); + dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->sub_ptr_rr); dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, s->srv_rr); LIST_FOREACH(items, txt_data, s->txt_data_items) dns_zone_remove_rr(&l->mdns_ipv4_scope->zone, txt_data->rr); @@ -47,6 +52,7 @@ int bus_dnssd_method_unregister(sd_bus_message *message, void *userdata, sd_bus_ log_warning_errno(r, "Failed to send goodbye messages in IPv6 scope: %m"); dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->ptr_rr); + dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->sub_ptr_rr); dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, s->srv_rr); LIST_FOREACH(items, txt_data, s->txt_data_items) dns_zone_remove_rr(&l->mdns_ipv6_scope->zone, txt_data->rr); @@ -101,7 +107,7 @@ static int dnssd_node_enumerator(sd_bus *bus, const char *path, void *userdata, HASHMAP_FOREACH(service, m->dnssd_services) { char *p; - r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->name, &p); + r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", service->id, &p); if (r < 0) return r; diff --git a/src/resolve/resolved-dnssd-gperf.gperf b/src/resolve/resolved-dnssd-gperf.gperf index f10eae3..e78573b 100644 --- a/src/resolve/resolved-dnssd-gperf.gperf +++ b/src/resolve/resolved-dnssd-gperf.gperf @@ -16,10 +16,11 @@ struct ConfigPerfItem; %struct-type %includes %% -Service.Name, config_parse_dnssd_service_name, 0, 0 -Service.Type, config_parse_dnssd_service_type, 0, 0 -Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port) -Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority) -Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight) -Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0 -Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0 +Service.Name, config_parse_dnssd_service_name, 0, 0 +Service.Type, config_parse_dnssd_service_type, 0, 0 +Service.SubType, config_parse_dnssd_service_subtype, 0, 0 +Service.Port, config_parse_ip_port, 0, offsetof(DnssdService, port) +Service.Priority, config_parse_uint16, 0, offsetof(DnssdService, priority) +Service.Weight, config_parse_uint16, 0, offsetof(DnssdService, weight) +Service.TxtText, config_parse_dnssd_txt, DNS_TXT_ITEM_TEXT, 0 +Service.TxtData, config_parse_dnssd_txt, DNS_TXT_ITEM_DATA, 0 diff --git a/src/resolve/resolved-dnssd.c b/src/resolve/resolved-dnssd.c index 994771e..5f66e3c 100644 --- a/src/resolve/resolved-dnssd.c +++ b/src/resolve/resolved-dnssd.c @@ -3,10 +3,11 @@ #include "conf-files.h" #include "conf-parser.h" #include "constants.h" -#include "resolved-dnssd.h" +#include "path-util.h" +#include "resolved-conf.h" #include "resolved-dns-rr.h" +#include "resolved-dnssd.h" #include "resolved-manager.h" -#include "resolved-conf.h" #include "specifier.h" #include "strv.h" @@ -40,55 +41,81 @@ DnssdService *dnssd_service_free(DnssdService *service) { return NULL; if (service->manager) - hashmap_remove(service->manager->dnssd_services, service->name); + hashmap_remove(service->manager->dnssd_services, service->id); dns_resource_record_unref(service->ptr_rr); + dns_resource_record_unref(service->sub_ptr_rr); dns_resource_record_unref(service->srv_rr); dnssd_txtdata_free_all(service->txt_data_items); - free(service->filename); - free(service->name); + free(service->path); + free(service->id); free(service->type); + free(service->subtype); free(service->name_template); return mfree(service); } -static int dnssd_service_load(Manager *manager, const char *filename) { +void dnssd_service_clear_on_reload(Hashmap *services) { + DnssdService *service; + + HASHMAP_FOREACH(service, services) + if (service->config_source == RESOLVE_CONFIG_SOURCE_FILE) { + hashmap_remove(services, service->id); + dnssd_service_free(service); + } +} + +static int dnssd_id_from_path(const char *path, char **ret_id) { + int r; + + assert(path); + assert(ret_id); + + _cleanup_free_ char *fn = NULL; + r = path_extract_filename(path, &fn); + if (r < 0) + return r; + + char *d = endswith(fn, ".dnssd"); + if (!d) + return -EINVAL; + + *d = '\0'; + + *ret_id = TAKE_PTR(fn); + return 0; +} + +static int dnssd_service_load(Manager *manager, const char *path) { _cleanup_(dnssd_service_freep) DnssdService *service = NULL; _cleanup_(dnssd_txtdata_freep) DnssdTxtData *txt_data = NULL; - char *d; - const char *dropin_dirname; + _cleanup_free_ char *dropin_dirname = NULL; int r; assert(manager); - assert(filename); + assert(path); service = new0(DnssdService, 1); if (!service) return log_oom(); - service->filename = strdup(filename); - if (!service->filename) + service->path = strdup(path); + if (!service->path) return log_oom(); - service->name = strdup(basename(filename)); - if (!service->name) - return log_oom(); - - d = endswith(service->name, ".dnssd"); - if (!d) - return -EINVAL; - - assert(streq(d, ".dnssd")); - - *d = '\0'; + r = dnssd_id_from_path(path, &service->id); + if (r < 0) + return log_error_errno(r, "Failed to extract DNS-SD service id from filename: %m"); - dropin_dirname = strjoina(service->name, ".dnssd.d"); + dropin_dirname = strjoin(service->id, ".dnssd.d"); + if (!dropin_dirname) + return log_oom(); r = config_parse_many( - STRV_MAKE_CONST(filename), DNSSD_SERVICE_DIRS, dropin_dirname, /* root = */ NULL, + STRV_MAKE_CONST(path), DNSSD_SERVICE_DIRS, dropin_dirname, /* root = */ NULL, "Service\0", config_item_perf_lookup, resolved_dnssd_gperf_lookup, CONFIG_PARSE_WARN, @@ -101,12 +128,12 @@ static int dnssd_service_load(Manager *manager, const char *filename) { if (!service->name_template) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s doesn't define service instance name", - service->name); + service->id); if (!service->type) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "%s doesn't define service type", - service->name); + service->id); if (!service->txt_data_items) { txt_data = new0(DnssdTxtData, 1); @@ -121,7 +148,7 @@ static int dnssd_service_load(Manager *manager, const char *filename) { TAKE_PTR(txt_data); } - r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->name, service); + r = hashmap_ensure_put(&manager->dnssd_services, &string_hash_ops, service->id, service); if (r < 0) return r; @@ -138,16 +165,10 @@ static int dnssd_service_load(Manager *manager, const char *filename) { static int specifier_dnssd_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const Manager *m = ASSERT_PTR(userdata); - char *n; assert(m->llmnr_hostname); - n = strdup(m->llmnr_hostname); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return strdup_to(ret, m->llmnr_hostname); } int dnssd_render_instance_name(Manager *m, DnssdService *s, char **ret) { @@ -208,7 +229,7 @@ int dnssd_load(Manager *manager) { } int dnssd_update_rrs(DnssdService *s) { - _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL; + _cleanup_free_ char *n = NULL, *service_name = NULL, *full_name = NULL, *sub_name = NULL, *selective_name = NULL; int r; assert(s); @@ -216,6 +237,7 @@ int dnssd_update_rrs(DnssdService *s) { assert(s->manager); s->ptr_rr = dns_resource_record_unref(s->ptr_rr); + s->sub_ptr_rr = dns_resource_record_unref(s->sub_ptr_rr); s->srv_rr = dns_resource_record_unref(s->srv_rr); LIST_FOREACH(items, txt_data, s->txt_data_items) txt_data->rr = dns_resource_record_unref(txt_data->rr); @@ -230,6 +252,14 @@ int dnssd_update_rrs(DnssdService *s) { r = dns_name_concat(n, service_name, 0, &full_name); if (r < 0) return r; + if (s->subtype) { + r = dns_name_concat("_sub", service_name, 0, &sub_name); + if (r < 0) + return r; + r = dns_name_concat(s->subtype, sub_name, 0, &selective_name); + if (r < 0) + return r; + } LIST_FOREACH(items, txt_data, s->txt_data_items) { txt_data->rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_TXT, @@ -253,6 +283,17 @@ int dnssd_update_rrs(DnssdService *s) { if (!s->ptr_rr->ptr.name) goto oom; + if (selective_name) { + s->sub_ptr_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, selective_name); + if (!s->sub_ptr_rr) + goto oom; + + s->sub_ptr_rr->ttl = MDNS_DEFAULT_TTL; + s->sub_ptr_rr->ptr.name = strdup(full_name); + if (!s->sub_ptr_rr->ptr.name) + goto oom; + } + s->srv_rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SRV, full_name); if (!s->srv_rr) @@ -272,6 +313,7 @@ oom: LIST_FOREACH(items, txt_data, s->txt_data_items) txt_data->rr = dns_resource_record_unref(txt_data->rr); s->ptr_rr = dns_resource_record_unref(s->ptr_rr); + s->sub_ptr_rr = dns_resource_record_unref(s->sub_ptr_rr); s->srv_rr = dns_resource_record_unref(s->srv_rr); return -ENOMEM; } @@ -337,12 +379,12 @@ int dnssd_signal_conflict(Manager *manager, const char *name) { if (s->withdrawn) continue; - if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name)) { + if (dns_name_equal(dns_resource_key_name(s->srv_rr->key), name) > 0) { _cleanup_free_ char *path = NULL; s->withdrawn = true; - r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->name, &path); + r = sd_bus_path_encode("/org/freedesktop/resolve1/dnssd", s->id, &path); if (r < 0) return log_error_errno(r, "Can't get D-BUS object path: %m"); diff --git a/src/resolve/resolved-dnssd.h b/src/resolve/resolved-dnssd.h index e978a0d..84f7853 100644 --- a/src/resolve/resolved-dnssd.h +++ b/src/resolve/resolved-dnssd.h @@ -3,6 +3,7 @@ #pragma once #include "list.h" +#include "resolved-conf.h" typedef struct DnssdService DnssdService; typedef struct DnssdTxtData DnssdTxtData; @@ -25,15 +26,17 @@ struct DnssdTxtData { }; struct DnssdService { - char *filename; - char *name; + char *path; + char *id; char *name_template; char *type; + char *subtype; uint16_t port; uint16_t priority; uint16_t weight; DnsResourceRecord *ptr_rr; + DnsResourceRecord *sub_ptr_rr; DnsResourceRecord *srv_rr; /* Section 6.8 of RFC 6763 allows having service @@ -42,6 +45,9 @@ struct DnssdService { Manager *manager; + /* Services registered via D-Bus are not removed on reload */ + ResolveConfigSource config_source; + bool withdrawn:1; uid_t originator; }; @@ -49,6 +55,7 @@ struct DnssdService { DnssdService *dnssd_service_free(DnssdService *service); DnssdTxtData *dnssd_txtdata_free(DnssdTxtData *txt_data); DnssdTxtData *dnssd_txtdata_free_all(DnssdTxtData *txt_data); +void dnssd_service_clear_on_reload(Hashmap *services); DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdService*, dnssd_service_free); DEFINE_TRIVIAL_CLEANUP_FUNC(DnssdTxtData*, dnssd_txtdata_free); diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c index fbcee7f..3112ccb 100644 --- a/src/resolve/resolved-dnstls-openssl.c +++ b/src/resolve/resolved-dnstls-openssl.c @@ -392,9 +392,6 @@ int dnstls_manager_init(Manager *manager) { assert(manager); - ERR_load_crypto_strings(); - SSL_load_error_strings(); - manager->dnstls_data.ctx = SSL_CTX_new(TLS_client_method()); if (!manager->dnstls_data.ctx) return -ENOMEM; diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c index 6af160a..7b05386 100644 --- a/src/resolve/resolved-etc-hosts.c +++ b/src/resolve/resolved-etc-hosts.c @@ -342,7 +342,7 @@ static int manager_etc_hosts_read(Manager *m) { m->etc_hosts_last = ts; - if (m->etc_hosts_stat.st_mode != 0) { + if (stat_is_set(&m->etc_hosts_stat)) { if (stat("/etc/hosts", &st) < 0) { if (errno != ENOENT) return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); @@ -491,7 +491,7 @@ static int etc_hosts_lookup_by_name( const char *name, DnsAnswer **answer) { - bool found_a = false, found_aaaa = false; + bool question_for_a = false, question_for_aaaa = false; const struct in_addr_data *a; EtcHostsItemByName *item; DnsResourceKey *t; @@ -513,6 +513,7 @@ static int etc_hosts_lookup_by_name( return 0; } + /* Determine whether we are looking for A and/or AAAA RRs */ DNS_QUESTION_FOREACH(t, q) { if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) continue; @@ -526,20 +527,20 @@ static int etc_hosts_lookup_by_name( continue; if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) - found_a = true; + question_for_a = true; if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) - found_aaaa = true; + question_for_aaaa = true; - if (found_a && found_aaaa) - break; + if (question_for_a && question_for_aaaa) + break; /* We are looking for both, no need to continue loop */ } SET_FOREACH(a, item ? item->addresses : NULL) { EtcHostsItemByAddress *item_by_addr; const char *canonical_name; - if ((!found_a && a->family == AF_INET) || - (!found_aaaa && a->family == AF_INET6)) + if ((!question_for_a && a->family == AF_INET) || + (!question_for_aaaa && a->family == AF_INET6)) continue; item_by_addr = hashmap_get(hosts->by_address, a); @@ -559,7 +560,7 @@ static int etc_hosts_lookup_by_name( return r; } - return found_a || found_aaaa; + return true; /* We consider ourselves authoritative for the whole name, all RR types, not just A/AAAA */ } int manager_etc_hosts_lookup(Manager *m, DnsQuestion *q, DnsAnswer **answer) { diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 4f8f591..656bdd9 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -236,10 +236,11 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-dns-servers", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-dns-servers", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, error); if (r < 0) goto finalize; if (r == 0) { @@ -273,7 +274,7 @@ static int bus_link_method_set_dns_servers_internal(sd_bus_message *message, voi if (s) dns_server_move_back_and_unmark(s); else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name); + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i]->family, &dns[i]->address, dns[i]->port, 0, dns[i]->server_name, RESOLVE_CONFIG_SOURCE_DBUS); if (r < 0) { dns_server_unlink_all(l->dns_servers); goto finalize; @@ -368,10 +369,12 @@ int bus_link_method_set_domains(sd_bus_message *message, void *userdata, sd_bus_ if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-domains", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-domains", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -446,10 +449,12 @@ int bus_link_method_set_default_route(sd_bus_message *message, void *userdata, s if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-default-route", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-default-route", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -493,10 +498,12 @@ int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-llmnr", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-llmnr", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -541,10 +548,12 @@ int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_err return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-mdns", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-mdns", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -589,10 +598,12 @@ int bus_link_method_set_dns_over_tls(sd_bus_message *message, void *userdata, sd return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSOverTLS setting: %s", dns_over_tls); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-dns-over-tls", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-dns-over-tls", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -637,10 +648,12 @@ int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_e return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-dnssec", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-dnssec", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -698,10 +711,12 @@ int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, v return -ENOMEM; } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.set-dnssec-negative-trust-anchors", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.set-dnssec-negative-trust-anchors", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) @@ -734,10 +749,12 @@ int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error if (r < 0) return r; - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.resolve1.revert", - NULL, true, UID_INVALID, - &l->manager->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.resolve1.revert", + (const char**) STRV_MAKE("interface", l->ifname), + &l->manager->polkit_registry, + error); if (r < 0) return r; if (r == 0) diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index dd5dadd..bb43a73 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -273,7 +273,7 @@ static int link_update_dns_server_one(Link *l, const char *str) { return 0; } - return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name); + return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, port, 0, name, RESOLVE_CONFIG_SOURCE_NETWORKD); } static int link_update_dns_servers(Link *l) { diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c index b52619e..99787f7 100644 --- a/src/resolve/resolved-manager.c +++ b/src/resolve/resolved-manager.c @@ -11,6 +11,7 @@ #include "af-list.h" #include "alloc-util.h" #include "bus-polkit.h" +#include "daemon-util.h" #include "dirent-util.h" #include "dns-domain.h" #include "event-util.h" @@ -388,7 +389,7 @@ static char* fallback_hostname(void) { static int make_fallback_hostnames(char **full_hostname, char **llmnr_hostname, char **mdns_hostname) { _cleanup_free_ char *h = NULL, *n = NULL, *m = NULL; - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; const char *p; int r; @@ -565,6 +566,73 @@ static int manager_memory_pressure_listen(Manager *m) { return 0; } +static void manager_set_defaults(Manager *m) { + assert(m); + + m->llmnr_support = DEFAULT_LLMNR_MODE; + m->mdns_support = DEFAULT_MDNS_MODE; + m->dnssec_mode = DEFAULT_DNSSEC_MODE; + m->dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE; + m->enable_cache = DNS_CACHE_MODE_YES; + m->dns_stub_listener_mode = DNS_STUB_LISTENER_YES; + m->read_etc_hosts = true; + m->resolve_unicast_single_label = false; + m->cache_from_localhost = false; + m->stale_retention_usec = 0; +} + +static int manager_dispatch_reload_signal(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + Manager *m = ASSERT_PTR(userdata); + int r; + + (void) notify_reloading(); + + manager_set_defaults(m); + + dns_server_unlink_on_reload(m->dns_servers); + dns_server_unlink_on_reload(m->fallback_dns_servers); + m->dns_extra_stub_listeners = ordered_set_free(m->dns_extra_stub_listeners); + dnssd_service_clear_on_reload(m->dnssd_services); + m->unicast_scope = dns_scope_free(m->unicast_scope); + + dns_trust_anchor_flush(&m->trust_anchor); + + r = dns_trust_anchor_load(&m->trust_anchor); + if (r < 0) + return r; + + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse config file on reload: %m"); + else + log_info("Config file reloaded."); + + r = dnssd_load(m); + if (r < 0) + log_warning_errno(r, "Failed to load DNS-SD configuration files: %m"); + + /* The default scope configuration is influenced by the manager's configuration (modes, etc.), so + * recreate it on reload. */ + r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + return r; + + /* The configuration has changed, so reload the per-interface configuration too in order to take + * into account any changes (e.g.: enable/disable DNSSEC). */ + r = on_network_event(/* sd_event_source= */ NULL, -EBADF, /* revents= */ 0, m); + if (r < 0) + log_warning_errno(r, "Failed to update network information: %m"); + + /* We have new configuration, which means potentially new servers, so close all connections and drop + * all caches, so that we can start fresh. */ + (void) dns_stream_disconnect_all(m); + manager_flush_caches(m, LOG_INFO); + manager_verify_all(m); + + (void) sd_notify(/* unset= */ false, NOTIFY_READY); + return 0; +} + int manager_new(Manager **ret) { _cleanup_(manager_freep) Manager *m = NULL; int r; @@ -584,21 +652,16 @@ int manager_new(Manager **ret) { .mdns_ipv6_fd = -EBADF, .hostname_fd = -EBADF, - .llmnr_support = DEFAULT_LLMNR_MODE, - .mdns_support = DEFAULT_MDNS_MODE, - .dnssec_mode = DEFAULT_DNSSEC_MODE, - .dns_over_tls_mode = DEFAULT_DNS_OVER_TLS_MODE, - .enable_cache = DNS_CACHE_MODE_YES, - .dns_stub_listener_mode = DNS_STUB_LISTENER_YES, .read_resolv_conf = true, .need_builtin_fallbacks = true, .etc_hosts_last = USEC_INFINITY, - .read_etc_hosts = true, .sigrtmin18_info.memory_pressure_handler = manager_memory_pressure, .sigrtmin18_info.memory_pressure_userdata = m, }; + manager_set_defaults(m); + r = dns_trust_anchor_load(&m->trust_anchor); if (r < 0) return r; @@ -619,6 +682,7 @@ int manager_new(Manager **ret) { (void) sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); (void) sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(m->event, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, manager_dispatch_reload_signal, m); (void) sd_event_set_watchdog(m->event, true); @@ -731,7 +795,7 @@ Manager *manager_free(Manager *m) { ordered_set_free(m->dns_extra_stub_listeners); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); sd_bus_flush_close_unref(m->bus); @@ -894,7 +958,7 @@ int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { return 1; } -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { +int sendmsg_loop(int fd, struct msghdr *mh, int flags) { usec_t end; int r; @@ -1098,17 +1162,7 @@ static int dns_question_to_json(DnsQuestion *q, JsonVariant **ret) { return 0; } -int manager_monitor_send( - Manager *m, - int state, - int rcode, - int error, - DnsQuestion *question_idna, - DnsQuestion *question_utf8, - DnsPacket *question_bypass, - DnsQuestion *collected_questions, - DnsAnswer *answer) { - +int manager_monitor_send(Manager *m, DnsQuery *q) { _cleanup_(json_variant_unrefp) JsonVariant *jquestion = NULL, *jcollected_questions = NULL, *janswer = NULL; _cleanup_(dns_question_unrefp) DnsQuestion *merged = NULL; Varlink *connection; @@ -1121,14 +1175,14 @@ int manager_monitor_send( return 0; /* Merge all questions into one */ - r = dns_question_merge(question_idna, question_utf8, &merged); + r = dns_question_merge(q->question_idna, q->question_utf8, &merged); if (r < 0) return log_error_errno(r, "Failed to merge UTF8/IDNA questions: %m"); - if (question_bypass) { + if (q->question_bypass) { _cleanup_(dns_question_unrefp) DnsQuestion *merged2 = NULL; - r = dns_question_merge(merged, question_bypass->question, &merged2); + r = dns_question_merge(merged, q->question_bypass->question, &merged2); if (r < 0) return log_error_errno(r, "Failed to merge UTF8/IDNA questions and DNS packet question: %m"); @@ -1142,11 +1196,11 @@ int manager_monitor_send( return log_error_errno(r, "Failed to convert question to JSON: %m"); /* Generate a JSON array of the questions preceding the current one in the CNAME chain */ - r = dns_question_to_json(collected_questions, &jcollected_questions); + r = dns_question_to_json(q->collected_questions, &jcollected_questions); if (r < 0) return log_error_errno(r, "Failed to convert question to JSON: %m"); - DNS_ANSWER_FOREACH_ITEM(rri, answer) { + DNS_ANSWER_FOREACH_ITEM(rri, q->answer) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; r = dns_resource_record_to_json(rri->rr, &v); @@ -1169,12 +1223,28 @@ int manager_monitor_send( SET_FOREACH(connection, m->varlink_subscription) { r = varlink_notifyb(connection, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(state))), - JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_RCODE_FAILURE, "rcode", JSON_BUILD_INTEGER(rcode)), - JSON_BUILD_PAIR_CONDITION(state == DNS_TRANSACTION_ERRNO, "errno", JSON_BUILD_INTEGER(error)), + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("state", JSON_BUILD_STRING(dns_transaction_state_to_string(q->state))), + JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_DNSSEC_FAILED, + "result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))), + JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_RCODE_FAILURE, + "rcode", JSON_BUILD_INTEGER(q->answer_rcode)), + JSON_BUILD_PAIR_CONDITION(q->state == DNS_TRANSACTION_ERRNO, + "errno", JSON_BUILD_INTEGER(q->answer_errno)), + JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_RCODE_FAILURE) && + q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)), + JSON_BUILD_PAIR_CONDITION(IN_SET(q->state, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_RCODE_FAILURE) && + q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)), JSON_BUILD_PAIR("question", JSON_BUILD_VARIANT(jquestion)), - JSON_BUILD_PAIR_CONDITION(jcollected_questions, "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)), - JSON_BUILD_PAIR_CONDITION(janswer, "answer", JSON_BUILD_VARIANT(janswer)))); + JSON_BUILD_PAIR_CONDITION(jcollected_questions, + "collectedQuestions", JSON_BUILD_VARIANT(jcollected_questions)), + JSON_BUILD_PAIR_CONDITION(janswer, + "answer", JSON_BUILD_VARIANT(janswer)))); if (r < 0) log_debug_errno(r, "Failed to send monitor event, ignoring: %m"); } @@ -1279,7 +1349,7 @@ void manager_refresh_rrs(Manager *m) { if (m->mdns_support == RESOLVE_SUPPORT_YES) HASHMAP_FOREACH(s, m->dnssd_services) if (dnssd_update_rrs(s) < 0) - log_warning("Failed to refresh DNS-SD service '%s'", s->name); + log_warning("Failed to refresh DNS-SD service '%s'", s->id); HASHMAP_FOREACH(l, m->links) link_add_rrs(l, false); @@ -1708,7 +1778,7 @@ bool manager_next_dnssd_names(Manager *m) { r = manager_next_random_name(s->name_template, &new_name); if (r < 0) { - log_warning_errno(r, "Failed to get new name for service '%s': %m", s->name); + log_warning_errno(r, "Failed to get new name for service '%s': %m", s->id); continue; } diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h index 5cd5e83..bd0e053 100644 --- a/src/resolve/resolved-manager.h +++ b/src/resolve/resolved-manager.h @@ -176,8 +176,9 @@ int manager_start(Manager *m); uint32_t manager_find_mtu(Manager *m); -int manager_monitor_send(Manager *m, int state, int rcode, int error, DnsQuestion *question_idna, DnsQuestion *question_utf8, DnsPacket *question_bypass, DnsQuestion *collected_questions, DnsAnswer *answer); +int manager_monitor_send(Manager *m, DnsQuery *q); +int sendmsg_loop(int fd, struct msghdr *mh, int flags); int manager_write(Manager *m, int fd, DnsPacket *p); int manager_send(Manager *m, int fd, int ifindex, int family, const union in_addr_union *destination, uint16_t port, const union in_addr_union *source, DnsPacket *p); int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c index 3e6e83f..8f93af3 100644 --- a/src/resolve/resolved-mdns.c +++ b/src/resolve/resolved-mdns.c @@ -349,6 +349,33 @@ static int mdns_scope_process_query(DnsScope *s, DnsPacket *p) { return 0; } +static int mdns_goodbye_callback(sd_event_source *s, uint64_t usec, void *userdata) { + DnsScope *scope = userdata; + int r; + + assert(s); + assert(scope); + + scope->mdns_goodbye_event_source = sd_event_source_disable_unref(scope->mdns_goodbye_event_source); + + dns_cache_prune(&scope->cache); + + if (dns_cache_expiry_in_one_second(&scope->cache, usec)) { + r = sd_event_add_time_relative( + scope->manager->event, + &scope->mdns_goodbye_event_source, + CLOCK_BOOTTIME, + USEC_PER_SEC, + 0, + mdns_goodbye_callback, + scope); + if (r < 0) + return log_error_errno(r, "mDNS: Failed to re-schedule goodbye callback: %m"); + } + + return 0; +} + static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; Manager *m = userdata; @@ -407,6 +434,22 @@ static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *us log_debug("Got a goodbye packet"); /* See the section 10.1 of RFC6762 */ rr->ttl = 1; + + /* Look at the cache 1 second later and remove stale entries. + * This is particularly useful to keep service browsers updated on service removal, + * as there are no other reliable triggers to propagate that info. */ + if (!scope->mdns_goodbye_event_source) { + r = sd_event_add_time_relative( + scope->manager->event, + &scope->mdns_goodbye_event_source, + CLOCK_BOOTTIME, + USEC_PER_SEC, + 0, + mdns_goodbye_callback, + scope); + if (r < 0) + return r; + } } } diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c index 00abada..adcd35d 100644 --- a/src/resolve/resolved-util.c +++ b/src/resolve/resolved-util.c @@ -14,7 +14,7 @@ int resolve_system_hostname(char **full_hostname, char **first_label) { #elif HAVE_LIBIDN int k; #endif - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; const char *p, *decoded; int r; diff --git a/src/resolve/resolved-varlink.c b/src/resolve/resolved-varlink.c index 3e178a6..25f85d8 100644 --- a/src/resolve/resolved-varlink.c +++ b/src/resolve/resolved-varlink.c @@ -15,8 +15,19 @@ typedef struct LookupParameters { union in_addr_union address; size_t address_size; char *name; + uint16_t class; + uint16_t type; } LookupParameters; +typedef struct LookupParametersResolveService { + const char *name; + const char *type; + const char *domain; + int family; + int ifindex; + uint64_t flags; +} LookupParametersResolveService; + static void lookup_parameters_destroy(LookupParameters *p) { assert(p); free(p->name); @@ -49,7 +60,11 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_DNSSEC_FAILED: return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSSECValidationFailed", - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("result", JSON_BUILD_STRING(dnssec_result_to_string(q->answer_dnssec_result))), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)))); case DNS_TRANSACTION_NO_TRUST_ANCHOR: return varlink_error(q->varlink_request, "io.systemd.Resolve.NoTrustAnchor", NULL); @@ -74,7 +89,11 @@ static int reply_query_state(DnsQuery *q) { case DNS_TRANSACTION_RCODE_FAILURE: return varlink_errorb(q->varlink_request, "io.systemd.Resolve.DNSError", - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)))); + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("rcode", JSON_BUILD_INTEGER(q->answer_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0, + "extendedDNSErrorCode", JSON_BUILD_INTEGER(q->answer_ede_rcode)), + JSON_BUILD_PAIR_CONDITION(q->answer_ede_rcode >= 0 && !isempty(q->answer_ede_msg), + "extendedDNSErrorMessage", JSON_BUILD_STRING(q->answer_ede_msg)))); case DNS_TRANSACTION_NULL: case DNS_TRANSACTION_PENDING: @@ -145,6 +164,7 @@ static bool validate_and_mangle_flags( SD_RESOLVED_NO_TRUST_ANCHOR| SD_RESOLVED_NO_NETWORK| SD_RESOLVED_NO_STALE| + SD_RESOLVED_RELAX_SINGLE_LABEL| ok)) return false; @@ -160,45 +180,23 @@ static bool validate_and_mangle_flags( return true; } -static void vl_method_resolve_hostname_complete(DnsQuery *query) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; - _cleanup_(dns_query_freep) DnsQuery *q = query; - _cleanup_free_ char *normalized = NULL; +static int find_addr_records( + JsonVariant **array, + DnsQuestion *question, + DnsQuery *q, + DnsResourceRecord **canonical, + const char *search_domain) { DnsResourceRecord *rr; - DnsQuestion *question; int ifindex, r; - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname_many(q); - if (r == -ELOOP) { - r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_CNAME) { - /* This was a cname, and the query was restarted. */ - TAKE_PTR(q); - return; - } - - question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; int family; const void *p; - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + r = dns_question_matches_rr(question, rr, search_domain); if (r < 0) - goto finish; + return r; if (r == 0) continue; @@ -209,8 +207,7 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { family = AF_INET6; p = &rr->aaaa.in6_addr; } else { - r = -EAFNOSUPPORT; - goto finish; + return -EAFNOSUPPORT; } r = json_build(&entry, @@ -219,16 +216,53 @@ static void vl_method_resolve_hostname_complete(DnsQuery *query) { JSON_BUILD_PAIR("family", JSON_BUILD_INTEGER(family)), JSON_BUILD_PAIR("address", JSON_BUILD_BYTE_ARRAY(p, FAMILY_ADDRESS_SIZE(family))))); if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(rr); + return r; - r = json_variant_append_array(&array, entry); + r = json_variant_append_array(array, entry); if (r < 0) - goto finish; + return r; + + if (canonical && !*canonical) + *canonical = dns_resource_record_ref(rr); } + return 0; +} + +static void vl_method_resolve_hostname_complete(DnsQuery *query) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = query; + _cleanup_free_ char *normalized = NULL; + DnsQuestion *question; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = find_addr_records(&array, question, q, &canonical, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (json_variant_is_blank_object(array)) { r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); goto finish; @@ -530,6 +564,670 @@ static int vl_method_resolve_address(Varlink *link, JsonVariant *parameters, Var return 1; } +static int append_txt(JsonVariant **txt, DnsResourceRecord *rr) { + int r; + + assert(txt); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + _cleanup_(json_variant_unrefp) JsonVariant *entry = NULL; + + if (i->length <= 0) + continue; + + r = json_variant_new_octescape(&entry, i->data, i->length); + if (r < 0) + return r; + + r = json_variant_append_array(txt, entry); + if (r < 0) + return r; + } + + return 1; +} + +static int append_srv( + DnsQuery *q, + DnsResourceRecord *rr, + JsonVariant **array) { + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *normalized = NULL; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(array); + + if (rr->key->type != DNS_TYPE_SRV) + return 0; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + /* First, let's see if we could find an appropriate A or AAAA + * record for the SRV record */ + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + canonical = dns_resource_record_ref(zz); + break; + } + + if (canonical) + break; + } + + /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ + if (!canonical) + return 0; + } + + r = dns_name_normalize(rr->srv.name, 0, &normalized); + if (r < 0) + return r; + + r = json_build(&v, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("priority", JSON_BUILD_UNSIGNED(rr->srv.priority)), + JSON_BUILD_PAIR("weight", JSON_BUILD_UNSIGNED(rr->srv.weight)), + JSON_BUILD_PAIR("port", JSON_BUILD_UNSIGNED(rr->srv.port)), + JSON_BUILD_PAIR("hostname", JSON_BUILD_STRING(normalized)))); + if (r < 0) + return r; + + if (canonical) { + normalized = mfree(normalized); + + r = dns_name_normalize(dns_resource_key_name(canonical->key), 0, &normalized); + if (r < 0) + return r; + + r = json_variant_set_field_string(&v, "canonicalName", normalized); + if (r < 0) + return r; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + _cleanup_(json_variant_unrefp) JsonVariant *addresses = NULL; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + r = find_addr_records(&addresses, question, aux, NULL, NULL); + if (r < 0) + return r; + } + + r = json_variant_set_field(&v, "addresses", addresses); + if (r < 0) + return r; + } + + r = json_variant_append_array(array, v); + if (r < 0) + return r; + + return 1; /* added */ +} + +static Varlink *get_vl_link_aux_query(DnsQuery *aux) { + assert(aux); + + /* Find the main query */ + while (aux->auxiliary_for) + aux = aux->auxiliary_for; + + return aux->varlink_request; +} + +static void resolve_service_all_complete(DnsQuery *query) { + _cleanup_(dns_query_freep) DnsQuery *q = query; + _cleanup_(json_variant_unrefp) JsonVariant *srv = NULL, *txt = NULL; + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + int r; + + assert(q); + + if (q->block_all_complete > 0) { + TAKE_PTR(q); + return; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + DnsQuery *bad = NULL; + bool have_success = false; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + switch (aux->state) { + + case DNS_TRANSACTION_PENDING: + /* If an auxiliary query is still pending, let's wait */ + TAKE_PTR(q); + return; + + case DNS_TRANSACTION_SUCCESS: + if (aux->auxiliary_result == 0) + have_success = true; + else + bad = aux; + break; + + default: + bad = aux; + break; + } + } + if (!have_success) { + /* We can only return one error, hence pick the last error we encountered */ + + assert(bad); + if (bad->state == DNS_TRANSACTION_SUCCESS) { + assert(bad->auxiliary_result != 0); + + if (bad->auxiliary_result == -ELOOP) { + r = varlink_error(query->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + + assert(bad->auxiliary_result < 0); + r = bad->auxiliary_result; + goto finish; + } + + bad->varlink_request = get_vl_link_aux_query(bad); + r = reply_query_state(bad); + bad->varlink_request = NULL; + goto finish; + } + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_srv(q, rr, &srv); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + } + + if (json_variant_is_blank_object(srv)) { + r = varlink_error(query->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_TXT) + continue; + + r = append_txt(&txt, rr); + if (r < 0) + goto finish; + } + + assert(canonical); + r = dns_service_split(dns_resource_key_name(canonical->key), &name, &type, &domain); + if (r < 0) + goto finish; + + r = varlink_replyb(query->varlink_request, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("services", JSON_BUILD_VARIANT(srv)), + JSON_BUILD_PAIR_CONDITION(!json_variant_is_blank_object(txt), "txt", JSON_BUILD_VARIANT(txt)), + JSON_BUILD_PAIR("canonical", JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("type", JSON_BUILD_STRING(type)), + JSON_BUILD_PAIR("domain", JSON_BUILD_STRING(domain)))), + JSON_BUILD_PAIR("flags", JSON_BUILD_UNSIGNED(dns_query_reply_flags_make(query))))); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to resolve service: %m"); + r = varlink_error_errno(q->varlink_request, r); + } +} + +static void resolve_service_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->auxiliary_for); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + resolve_service_all_complete(q->auxiliary_for); + return; + } + + r = dns_query_process_cname_many(q); + if (r == DNS_QUERY_CNAME) /* This was a cname, and the query was restarted. */ + return; + + /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ + q->auxiliary_result = r < 0 ? r : 0; + resolve_service_all_complete(q->auxiliary_for); +} + +static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_query_freep) DnsQuery *aux = NULL; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(rr->key->type == DNS_TYPE_SRV); + + /* OK, we found an SRV record for the service. Let's resolve + * the hostname included in it */ + + r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, question, NULL, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + aux->request_family = q->request_family; + aux->complete = resolve_service_hostname_complete; + + r = dns_query_make_auxiliary(aux, q); + if (r == -EAGAIN) + /* Too many auxiliary lookups? If so, don't complain, + * let's just not add this one, we already have more + * than enough */ + return 0; + if (r < 0) + return r; + + /* Note that auxiliary queries do not track the original + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + return r; + + TAKE_PTR(aux); + return 1; +} + +static void vl_method_resolve_service_complete(DnsQuery *query) { + _cleanup_(dns_query_freep) DnsQuery *q = query; + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned found = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_SRV) + continue; + + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete--; + + if (r < 0) + goto finish; + } + + found++; + } + + if (has_root_domain && found <= 0) { + /* If there's exactly one SRV RR and it uses the root domain as hostname, then the service is + * explicitly not offered on the domain. Report this as a recognizable error. See RFC 2782, + * Section "Usage Rules". */ + r = varlink_error(q->varlink_request, "io.systemd.Resolve.ServiceNotProvided", NULL); + goto finish; + } + + if (found <= 0) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(TAKE_PTR(q)); + return; + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send address reply: %m"); + r = varlink_error_errno(q->varlink_request, r); + } +} + +static int vl_method_resolve_service(Varlink* link, JsonVariant* parameters, VarlinkMethodFlags flags, void* userdata) { + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, name), 0 }, + { "type", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, type), 0 }, + { "domain", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParametersResolveService, domain), JSON_MANDATORY }, + { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, ifindex), 0 }, + { "family", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParametersResolveService, family), 0 }, + { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParametersResolveService, flags), 0 }, + {} + }; + + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + LookupParametersResolveService p = { + .family = AF_UNSPEC, + }; + + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + Manager *m; + int r; + + assert(link); + + m = varlink_server_get_userdata(varlink_get_server(link)); + assert(m); + + if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) + return -EINVAL; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.ifindex < 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); + + if (!IN_SET(p.family, AF_INET, AF_INET6, AF_UNSPEC)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("family")); + + if (isempty(p.name)) + p.name = NULL; + else if (!dns_service_name_is_valid(p.name)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); + + if (isempty(p.type)) + p.type = NULL; + else if (!dns_srv_type_is_valid(p.type)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type")); + + r = dns_name_is_valid(p.domain); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("domain")); + + if (p.name && !p.type) /* Service name cannot be specified without service type. */ + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("type")); + + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + + r = dns_question_new_service(&question_utf8, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), false); + if (r < 0) + return r; + + r = dns_question_new_service(&question_idna, p.name, p.type, p.domain, !(p.flags & SD_RESOLVED_NO_TXT), true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->varlink_request = varlink_ref(link); + q->request_family = p.family; + q->complete = vl_method_resolve_service_complete; + + varlink_set_userdata(link, q); + + r = dns_query_go(q); + if (r < 0) + return r; + + TAKE_PTR(q); + return 1; +} + +static void vl_method_resolve_record_complete(DnsQuery *query) { + _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; + _cleanup_(dns_query_freep) DnsQuery *q = query; + DnsQuestion *question; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname_many(q); + if (r == -ELOOP) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.CNAMELoop", NULL); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_CNAME) { + /* This was a cname, and the query was restarted. */ + TAKE_PTR(q); + return; + } + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + unsigned added = 0; + int ifindex; + DnsResourceRecord *rr; + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = dns_resource_record_to_json(rr, &v); + if (r < 0) + goto finish; + + r = dns_resource_record_to_wire_format(rr, /* canonical= */ false); /* don't use DNSSEC canonical format, since it removes casing, but we want that for DNS_SD compat */ + if (r < 0) + goto finish; + + r = json_variant_append_arrayb( + &array, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_CONDITION(ifindex > 0, "ifindex", JSON_BUILD_INTEGER(ifindex)), + JSON_BUILD_PAIR_CONDITION(v, "rr", JSON_BUILD_VARIANT(v)), + JSON_BUILD_PAIR("raw", JSON_BUILD_BASE64(rr->wire_format, rr->wire_format_size)))); + if (r < 0) + goto finish; + + added++; + } + + if (added <= 0) { + r = varlink_error(q->varlink_request, "io.systemd.Resolve.NoSuchResourceRecord", NULL); + goto finish; + } + + r = varlink_replyb(q->varlink_request, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("rrs", JSON_BUILD_VARIANT(array)), + JSON_BUILD_PAIR("flags", JSON_BUILD_INTEGER(dns_query_reply_flags_make(q))))); +finish: + if (r < 0) { + log_full_errno(ERRNO_IS_DISCONNECT(r) ? LOG_DEBUG : LOG_ERR, r, "Failed to send record reply: %m"); + varlink_error_errno(q->varlink_request, r); + } +} + +static int vl_method_resolve_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + static const JsonDispatch dispatch_table[] = { + { "ifindex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_int, offsetof(LookupParameters, ifindex), 0 }, + { "name", JSON_VARIANT_STRING, json_dispatch_string, offsetof(LookupParameters, name), JSON_MANDATORY }, + { "class", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, class), 0 }, + { "type", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint16, offsetof(LookupParameters, type), JSON_MANDATORY }, + { "flags", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(LookupParameters, flags), 0 }, + {} + }; + + _cleanup_(lookup_parameters_destroy) LookupParameters p = { + .class = DNS_CLASS_IN, + .type = _DNS_TYPE_INVALID, + }; + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + Manager *m; + int r; + + assert(link); + + m = ASSERT_PTR(varlink_server_get_userdata(varlink_get_server(link))); + + if (FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) + return -EINVAL; + + r = varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.ifindex < 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("ifindex")); + + r = dns_name_is_valid(p.name); + if (r < 0) + return r; + if (r == 0) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("name")); + + if (!dns_type_is_valid_query(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeInvalidForQuery", NULL); + if (dns_type_is_zone_transfer(p.type)) + return varlink_error(link, "io.systemd.Resolve.ZoneTransfersNotPermitted", NULL); + if (dns_type_is_obsolete(p.type)) + return varlink_error(link, "io.systemd.Resolve.ResourceRecordTypeObsolete", NULL); + + if (!validate_and_mangle_flags(p.name, &p.flags, SD_RESOLVED_NO_SEARCH)) + return varlink_error_invalid_parameter(link, JSON_VARIANT_STRING_CONST("flags")); + + _cleanup_(dns_question_unrefp) DnsQuestion *question = dns_question_new(1); + if (!question) + return -ENOMEM; + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + key = dns_resource_key_new(p.class, p.type, p.name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key, /* flags= */ 0); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, NULL, p.ifindex, p.flags|SD_RESOLVED_NO_SEARCH|SD_RESOLVED_CLAMP_TTL); + if (r < 0) + return r; + + q->varlink_request = varlink_ref(link); + varlink_set_userdata(link, q); + q->complete = vl_method_resolve_record_complete; + + r = dns_query_go(q); + if (r < 0) + return r; + + TAKE_PTR(q); + return 1; +} + static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { Manager *m; int r; @@ -540,7 +1238,7 @@ static int vl_method_subscribe_query_results(Varlink *link, JsonVariant *paramet /* if the client didn't set the more flag, it is using us incorrectly */ if (!FLAGS_SET(flags, VARLINK_METHOD_MORE)) - return varlink_error_invalid_parameter(link, NULL); + return varlink_error(link, VARLINK_ERROR_EXPECTED_MORE, NULL); if (json_variant_elements(parameters) > 0) return varlink_error_invalid_parameter(link, parameters); @@ -753,8 +1451,10 @@ static int varlink_main_server_init(Manager *m) { r = varlink_server_bind_method_many( s, - "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, - "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address); + "io.systemd.Resolve.ResolveHostname", vl_method_resolve_hostname, + "io.systemd.Resolve.ResolveAddress", vl_method_resolve_address, + "io.systemd.Resolve.ResolveService", vl_method_resolve_service, + "io.systemd.Resolve.ResolveRecord", vl_method_resolve_record); if (r < 0) return log_error_errno(r, "Failed to register varlink methods: %m"); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c index 1625c51..664e7dd 100644 --- a/src/resolve/resolved.c +++ b/src/resolve/resolved.c @@ -67,7 +67,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to drop privileges: %m"); } - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, SIGUSR2, SIGRTMIN+1, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) diff --git a/src/resolve/test-resolved-dummy-server.c b/src/resolve/test-resolved-dummy-server.c new file mode 100644 index 0000000..58257d7 --- /dev/null +++ b/src/resolve/test-resolved-dummy-server.c @@ -0,0 +1,450 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-daemon.h" + +#include "fd-util.h" +#include "iovec-util.h" +#include "log.h" +#include "main-func.h" +#include "resolved-dns-packet.h" +#include "resolved-manager.h" +#include "socket-netlink.h" +#include "socket-util.h" + +/* Taken from resolved-dns-stub.c */ +#define ADVERTISE_DATAGRAM_SIZE_MAX (65536U-14U-20U-8U) + +/* This is more or less verbatim manager_recv() from resolved-manager.c, sans the manager stuff */ +static int server_recv(int fd, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + CMSG_BUFFER_TYPE(CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + CMSG_SPACE(int) /* ttl/hoplimit */ + + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */) control; + union sockaddr_union sa; + struct iovec iov; + struct msghdr mh = { + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + ssize_t ms, l; + int r; + + assert(fd >= 0); + assert(ret); + + ms = next_datagram_size_fd(fd); + if (ms < 0) + return ms; + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, ms, DNS_PACKET_SIZE_MAX); + if (r < 0) + return r; + + iov = IOVEC_MAKE(DNS_PACKET_DATA(p), p->allocated); + + l = recvmsg_safe(fd, &mh, 0); + if (ERRNO_IS_NEG_TRANSIENT(l)) + return 0; + if (l <= 0) + return l; + + assert(!(mh.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + p->family = sa.sa.sa_family; + p->ipproto = IPPROTO_UDP; + if (p->family == AF_INET) { + p->sender.in = sa.in.sin_addr; + p->sender_port = be16toh(sa.in.sin_port); + } else if (p->family == AF_INET6) { + p->sender.in6 = sa.in6.sin6_addr; + p->sender_port = be16toh(sa.in6.sin6_port); + p->ifindex = sa.in6.sin6_scope_id; + } else + return -EAFNOSUPPORT; + + p->timestamp = now(CLOCK_BOOTTIME); + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(p->family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in6_pktinfo); + + if (p->ifindex <= 0) + p->ifindex = i->ipi6_ifindex; + + p->destination.in6 = i->ipi6_addr; + break; + } + + case IPV6_HOPLIMIT: + p->ttl = *CMSG_TYPED_DATA(cmsg, int); + break; + + case IPV6_RECVFRAGSIZE: + p->fragsize = *CMSG_TYPED_DATA(cmsg, int); + break; + } + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(p->family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = CMSG_TYPED_DATA(cmsg, struct in_pktinfo); + + if (p->ifindex <= 0) + p->ifindex = i->ipi_ifindex; + + p->destination.in = i->ipi_addr; + break; + } + + case IP_TTL: + p->ttl = *CMSG_TYPED_DATA(cmsg, int); + break; + + case IP_RECVFRAGSIZE: + p->fragsize = *CMSG_TYPED_DATA(cmsg, int); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the packet came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (p->ifindex == LOOPBACK_IFINDEX) + p->ifindex = 0; + + log_debug("Received DNS UDP packet of size %zu, ifindex=%i, ttl=%u, fragsize=%zu, sender=%s, destination=%s", + p->size, p->ifindex, p->ttl, p->fragsize, + IN_ADDR_TO_STRING(p->family, &p->sender), + IN_ADDR_TO_STRING(p->family, &p->destination)); + + *ret = TAKE_PTR(p); + return 1; +} + +/* Same as above, see manager_ipv4_send() in resolved-manager.c */ +static int server_ipv4_send( + int fd, + const struct in_addr *destination, + uint16_t port, + const struct in_addr *source, + DnsPacket *packet) { + + union sockaddr_union sa; + struct iovec iov; + struct msghdr mh = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa.in), + }; + + assert(fd >= 0); + assert(destination); + assert(port > 0); + assert(packet); + + iov = IOVEC_MAKE(DNS_PACKET_DATA(packet), packet->size); + + sa = (union sockaddr_union) { + .in.sin_family = AF_INET, + .in.sin_addr = *destination, + .in.sin_port = htobe16(port), + }; + + return sendmsg_loop(fd, &mh, 0); +} + +static int make_reply_packet(DnsPacket *packet, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(packet); + assert(ret); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0, DNS_PACKET_PAYLOAD_SIZE_MAX(packet)); + if (r < 0) + return r; + + r = dns_packet_append_question(p, packet->question); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->id = DNS_PACKET_ID(packet); + DNS_PACKET_HEADER(p)->qdcount = htobe16(dns_question_size(packet->question)); + + *ret = TAKE_PTR(p); + return 0; +} + +static int reply_append_edns(DnsPacket *packet, DnsPacket *reply, const char *extra_text, size_t rcode, uint16_t ede_code) { + size_t saved_size; + int r; + + assert(packet); + assert(reply); + + /* Append EDNS0 stuff (inspired by dns_packet_append_opt() from resolved-dns-packet.c). + * + * Relevant headers from RFC 6891: + * + * +------------+--------------+------------------------------+ + * | Field Name | Field Type | Description | + * +------------+--------------+------------------------------+ + * | NAME | domain name | MUST be 0 (root domain) | + * | TYPE | u_int16_t | OPT (41) | + * | CLASS | u_int16_t | requestor's UDP payload size | + * | TTL | u_int32_t | extended RCODE and flags | + * | RDLEN | u_int16_t | length of all RDATA | + * | RDATA | octet stream | {attribute,value} pairs | + * +------------+--------------+------------------------------+ + * + * +0 (MSB) +1 (LSB) + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0: | OPTION-CODE | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2: | OPTION-LENGTH | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 4: | | + * / OPTION-DATA / + * / / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * + * And from RFC 8914: + * + * 1 1 1 1 1 1 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 0: | OPTION-CODE | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 2: | OPTION-LENGTH | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 4: | INFO-CODE | + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + * 6: / EXTRA-TEXT ... / + * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + */ + + saved_size = reply->size; + + /* empty name */ + r = dns_packet_append_uint8(reply, 0, NULL); + if (r < 0) + return r; + + /* type */ + r = dns_packet_append_uint16(reply, DNS_TYPE_OPT, NULL); + if (r < 0) + return r; + + /* class: maximum udp packet that can be received */ + r = dns_packet_append_uint16(reply, ADVERTISE_DATAGRAM_SIZE_MAX, NULL); + if (r < 0) + return r; + + /* extended RCODE and VERSION */ + r = dns_packet_append_uint16(reply, ((uint16_t) rcode & 0x0FF0) << 4, NULL); + if (r < 0) + return r; + + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(reply, 0, NULL); + if (r < 0) + return r; + + /* RDATA */ + + size_t extra_text_len = isempty(extra_text) ? 0 : strlen(extra_text); + /* RDLENGTH (OPTION CODE + OPTION LENGTH + INFO-CODE + EXTRA-TEXT) */ + r = dns_packet_append_uint16(reply, 2 + 2 + 2 + extra_text_len, NULL); + if (r < 0) + return 0; + + /* OPTION-CODE: 15 for EDE */ + r = dns_packet_append_uint16(reply, 15, NULL); + if (r < 0) + return r; + + /* OPTION-LENGTH: INFO-CODE + EXTRA-TEXT */ + r = dns_packet_append_uint16(reply, 2 + extra_text_len, NULL); + if (r < 0) + return r; + + /* INFO-CODE: EDE code */ + r = dns_packet_append_uint16(reply, ede_code, NULL); + if (r < 0) + return r; + + /* EXTRA-TEXT */ + if (extra_text_len > 0) { + /* From RFC 8914: + * EDE text may be null terminated but MUST NOT be assumed to be; the length MUST be derived + * from the OPTION-LENGTH field + * + * Let's exercise our code on the receiving side and not NUL-terminate the EXTRA-TEXT field + */ + r = dns_packet_append_blob(reply, extra_text, extra_text_len, NULL); + if (r < 0) + return r; + } + + DNS_PACKET_HEADER(reply)->arcount = htobe16(DNS_PACKET_ARCOUNT(reply) + 1); + reply->opt_start = saved_size; + reply->opt_size = reply->size - saved_size; + + /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */ + DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode)); + return 0; +} + +static void server_fail(DnsPacket *packet, DnsPacket *reply, int rcode) { + assert(reply); + + /* Order: qr, opcode, aa, tc, rd, ra, ad, cd, rcode */ + DNS_PACKET_HEADER(reply)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1, 0, 0, 0, DNS_PACKET_RD(packet), 1, 0, 1, rcode)); +} + +static int server_handle_edns_bogus_dnssec(DnsPacket *packet, DnsPacket *reply) { + assert(packet); + assert(reply); + + return reply_append_edns(packet, reply, NULL, DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_DNSSEC_BOGUS); +} + +static int server_handle_edns_extra_text(DnsPacket *packet, DnsPacket *reply) { + assert(packet); + assert(reply); + + return reply_append_edns(packet, reply, "Nothing to see here!", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_CENSORED); +} + +static int server_handle_edns_invalid_code(DnsPacket *packet, DnsPacket *reply, const char *extra_text) { + assert(packet); + assert(reply); + assert_cc(_DNS_EDE_RCODE_MAX_DEFINED < UINT16_MAX); + + return reply_append_edns(packet, reply, extra_text, DNS_RCODE_SERVFAIL, _DNS_EDE_RCODE_MAX_DEFINED + 1); +} + +static int server_handle_edns_code_zero(DnsPacket *packet, DnsPacket *reply) { + assert(packet); + assert(reply); + assert_cc(DNS_EDE_RCODE_OTHER == 0); + + return reply_append_edns(packet, reply, "\xF0\x9F\x90\xB1", DNS_RCODE_SERVFAIL, DNS_EDE_RCODE_OTHER); +} + +static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *packet = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *reply = NULL; + const char *name; + int r; + + assert(fd >= 0); + + r = server_recv(fd, &packet); + if (r < 0) { + log_debug_errno(r, "Failed to receive packet, ignoring: %m"); + return 0; + } + + r = dns_packet_validate_query(packet); + if (r < 0) { + log_debug_errno(r, "Invalid DNS UDP packet, ignoring."); + return 0; + } + + r = dns_packet_extract(packet); + if (r < 0) { + log_debug_errno(r, "Failed to extract DNS packet, ignoring: %m"); + return 0; + } + + name = dns_question_first_name(packet->question); + log_info("Processing question for name '%s'", name); + + (void) dns_question_dump(packet->question, stdout); + + r = make_reply_packet(packet, &reply); + if (r < 0) { + log_debug_errno(r, "Failed to make reply packet, ignoring: %m"); + return 0; + } + + if (streq_ptr(name, "edns-bogus-dnssec.forwarded.test")) + r = server_handle_edns_bogus_dnssec(packet, reply); + else if (streq_ptr(name, "edns-extra-text.forwarded.test")) + r = server_handle_edns_extra_text(packet, reply); + else if (streq_ptr(name, "edns-invalid-code.forwarded.test")) + r = server_handle_edns_invalid_code(packet, reply, NULL); + else if (streq_ptr(name, "edns-invalid-code-with-extra-text.forwarded.test")) + r = server_handle_edns_invalid_code(packet, reply, "Hello [#]$%~ World"); + else if (streq_ptr(name, "edns-code-zero.forwarded.test")) + r = server_handle_edns_code_zero(packet, reply); + else + r = log_debug_errno(SYNTHETIC_ERRNO(EFAULT), "Unhandled name '%s', ignoring.", name); + if (r < 0) + server_fail(packet, reply, DNS_RCODE_NXDOMAIN); + + r = server_ipv4_send(fd, &packet->sender.in, packet->sender_port, &packet->destination.in, reply); + if (r < 0) + log_debug_errno(r, "Failed to send reply, ignoring: %m"); + + return 0; +} + +static int run(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_close_ int fd = -EBADF; + int r; + + log_setup(); + + if (argc != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "This program takes one argument in format ip_address:port"); + + fd = make_socket_fd(LOG_DEBUG, argv[1], SOCK_DGRAM, SOCK_CLOEXEC); + if (fd < 0) + return log_error_errno(fd, "Failed to listen on address '%s': %m", argv[1]); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to allocate event: %m"); + + r = sd_event_add_io(event, NULL, fd, EPOLLIN, on_dns_packet, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add IO event source: %m"); + + r = sd_event_set_signal_exit(event, true); + if (r < 0) + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); + + (void) sd_notify(/* unset_environment=false */ false, "READY=1"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/resolve/test-resolved-packet.c b/src/resolve/test-resolved-packet.c index dd8c969..8a65ea0 100644 --- a/src/resolve/test-resolved-packet.c +++ b/src/resolve/test-resolved-packet.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "hexdecoct.h" #include "log.h" #include "resolved-dns-packet.h" #include "tests.h" @@ -23,4 +24,190 @@ TEST(dns_packet_new) { assert_se(dns_packet_new(&p2, DNS_PROTOCOL_DNS, DNS_PACKET_SIZE_MAX + 1, DNS_PACKET_SIZE_MAX) == -EFBIG); } +TEST(naptr) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + static const char twilio_reply[] = + "Sq+BgAABAAkAAAABBnR3aWxpbwNjb20AACMAAcAMACMAAQAABwgAMgAUAAoBUwdTSVArRDJUAARf" + "c2lwBF90Y3AEcHN0bgdpZTEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtE" + "MlUABF9zaXAEX3VkcARwc3RuB3VzMi10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADQAFAAKAVMI" + "U0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgd1czEtdG54BnR3aWxpbwNjb20AwAwAIwABAAAHCAAy" + "AAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB2llMS10bngGdHdpbGlvA2NvbQDADAAjAAEA" + "AAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBzdG4HdXMyLXRueAZ0d2lsaW8DY29tAMAM" + "ACMAAQAABwgANAAUAAoBUwhTSVBTK0QyVAAFX3NpcHMEX3RjcARwc3RuB3VzMi10bngGdHdpbGlv" + "A2NvbQDADAAjAAEAAAcIADQAFAAKAVMIU0lQUytEMlQABV9zaXBzBF90Y3AEcHN0bgdpZTEtdG54" + "BnR3aWxpbwNjb20AwAwAIwABAAAHCAAyAAoACgFTB1NJUCtEMlUABF9zaXAEX3VkcARwc3RuB3Vz" + "MS10bngGdHdpbGlvA2NvbQDADAAjAAEAAAcIADIAFAAKAVMHU0lQK0QyVAAEX3NpcARfdGNwBHBz" + "dG4HdXMxLXRueAZ0d2lsaW8DY29tAAAAKQIAAAAAAAAA"; + + static const char twilio_reply_string[] = + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.ie1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.ie1-tnx.twilio.com.\n" + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.us2-tnx.twilio.com.\n" + "20 10 \"S\" \"SIPS+D2T\" \"\" _sips._tcp.pstn.ie1-tnx.twilio.com.\n" + "10 10 \"S\" \"SIP+D2U\" \"\" _sip._udp.pstn.us1-tnx.twilio.com.\n" + "20 10 \"S\" \"SIP+D2T\" \"\" _sip._tcp.pstn.us1-tnx.twilio.com.\n"; + + static const char twilio_reply_json[] = + "[\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.us1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.us2-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIPS+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sips._tcp.pstn.ie1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 10,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2U\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._udp.pstn.us1-tnx.twilio.com\"\n" + " },\n" + " {\n" + " \"key\" : {\n" + " \"class\" : 1,\n" + " \"type\" : 35,\n" + " \"name\" : \"twilio.com\"\n" + " },\n" + " \"order\" : 20,\n" + " \"preference\" : 10,\n" + " \"naptrFlags\" : \"S\",\n" + " \"services\" : \"SIP+D2T\",\n" + " \"regexp\" : \"\",\n" + " \"replacement\" : \"_sip._tcp.pstn.us1-tnx.twilio.com\"\n" + " }\n" + "]\n"; + + _cleanup_free_ void *buf = NULL; + size_t sz = 0; + + assert_se(unbase64mem(twilio_reply, &buf, &sz) >= 0); + + assert_se(dns_packet_new(&p, DNS_PROTOCOL_DNS, sz, DNS_PACKET_SIZE_MAX) == 0); + assert_se(p->allocated >= sz); + + memcpy(DNS_PACKET_DATA(p), buf, sz); + p->size = sz; + + assert_se(dns_packet_extract(p) >= 0); + + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL; + _cleanup_free_ char *joined = NULL; + DnsResourceRecord *rr; + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *s; + + s = ASSERT_PTR(dns_resource_record_to_string(rr)); + printf("%s\n", s); + + assert_se(strextend(&joined, s, "\n")); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + assert_se(dns_resource_record_to_json(rr, &v) >= 0); + + assert_se(json_variant_append_array(&a, v) >= 0); + } + + assert(streq(joined, twilio_reply_string)); + + _cleanup_(json_variant_unrefp) JsonVariant *parsed = NULL; + assert_se(json_parse(twilio_reply_json, /* flags= */ 0, &parsed, /* ret_line= */ NULL, /* ret_column= */ NULL) >= 0); + + assert_se(json_variant_equal(parsed, a)); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/resolve/test-resolved-stream.c b/src/resolve/test-resolved-stream.c index 847de04..4f27eac 100644 --- a/src/resolve/test-resolved-stream.c +++ b/src/resolve/test-resolved-stream.c @@ -329,7 +329,7 @@ static void test_dns_stream(bool tls) { log_info("test-resolved-stream: Finished %s test", tls ? "TLS" : "TCP"); } -static void try_isolate_network(void) { +static int try_isolate_network(void) { _cleanup_close_ int socket_fd = -EBADF; int r; @@ -356,20 +356,25 @@ static void try_isolate_network(void) { _exit(EXIT_SUCCESS); } if (r == -EPROTO) /* EPROTO means nonzero exit code of child, i.e. the tests in the child failed */ - return; + return 0; assert_se(r > 0); /* Now that we know that the unshare() is safe, let's actually do it */ assert_se(unshare(CLONE_NEWUSER | CLONE_NEWNET) >= 0); - /* Bring up the loopback interfaceon the newly created network namespace */ + /* Bring up the loopback interface on the newly created network namespace */ struct ifreq req = { .ifr_ifindex = 1 }; assert_se((socket_fd = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, 0)) >= 0); assert_se(ioctl(socket_fd, SIOCGIFNAME, &req) >= 0); assert_se(ioctl(socket_fd, SIOCGIFFLAGS, &req) >= 0); assert_se(FLAGS_SET(req.ifr_flags, IFF_LOOPBACK)); req.ifr_flags |= IFF_UP; - assert_se(ioctl(socket_fd, SIOCSIFFLAGS, &req) >= 0); + /* Do not assert on this, fails in the Ubuntu 24.04 CI environment */ + r = RET_NERRNO(ioctl(socket_fd, SIOCSIFFLAGS, &req)); + if (r < 0) + return r; + + return 0; } int main(int argc, char **argv) { @@ -378,10 +383,14 @@ int main(int argc, char **argv) { .in.sin_port = htobe16(random_u64_range(UINT16_MAX - 1024) + 1024), .in.sin_addr.s_addr = htobe32(INADDR_LOOPBACK) }; + int r; test_setup_logging(LOG_DEBUG); - try_isolate_network(); + r = try_isolate_network(); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return log_tests_skipped("lacking privileges"); + assert_se(r >= 0); test_dns_stream(false); #if ENABLE_DNS_OVER_TLS diff --git a/src/rpm/macros.systemd.in b/src/rpm/macros.systemd.in index 317e13d..ce65ec6 100644 --- a/src/rpm/macros.systemd.in +++ b/src/rpm/macros.systemd.in @@ -27,10 +27,6 @@ %_systemd_system_env_generator_dir {{SYSTEM_ENV_GENERATOR_DIR}} %_systemd_user_env_generator_dir {{USER_ENV_GENERATOR_DIR}} -# Because we had one release with a typo... -# This is temporary (Remove after systemd 240 is released) -%_environmnentdir %{warn:Use %%_environmentdir instead}%_environmentdir - %systemd_requires \ Requires(post): systemd \ Requires(preun): systemd \ diff --git a/src/run/meson.build b/src/run/meson.build index 597a25a..221b441 100644 --- a/src/run/meson.build +++ b/src/run/meson.build @@ -7,3 +7,17 @@ executables += [ 'sources' : files('run.c'), }, ] + +install_emptydir(bindir) + +meson.add_install_script(sh, '-c', + ln_s.format(bindir / 'systemd-run', + bindir / 'run0')) + +custom_target( + 'systemd-run0', + input : 'systemd-run0.in', + output : 'systemd-run0', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : pamconfdir != 'no', + install_dir : pamconfdir) diff --git a/src/run/run.c b/src/run/run.c index 88eca0f..5779403 100644 --- a/src/run/run.c +++ b/src/run/run.c @@ -17,11 +17,15 @@ #include "bus-unit-util.h" #include "bus-wait-for-jobs.h" #include "calendarspec.h" +#include "capsule-util.h" +#include "chase.h" #include "env-util.h" #include "escape.h" #include "exit-status.h" #include "fd-util.h" #include "format-util.h" +#include "fs-util.h" +#include "hostname-util.h" #include "main-func.h" #include "parse-argument.h" #include "parse-util.h" @@ -31,8 +35,10 @@ #include "ptyfwd.h" #include "signal-util.h" #include "spawn-polkit-agent.h" +#include "special.h" #include "strv.h" #include "terminal-util.h" +#include "uid-classification.h" #include "unit-def.h" #include "unit-name.h" #include "user-util.h" @@ -73,6 +79,9 @@ static bool arg_aggressive_gc = false; static char *arg_working_directory = NULL; static bool arg_shell = false; static char **arg_cmdline = NULL; +static char *arg_exec_path = NULL; +static bool arg_ignore_failure = false; +static char *arg_background = NULL; STATIC_DESTRUCTOR_REGISTER(arg_description, freep); STATIC_DESTRUCTOR_REGISTER(arg_environment, strv_freep); @@ -82,6 +91,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_socket_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_timer_property, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_working_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_cmdline, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_exec_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_background, freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -91,8 +102,8 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" - "\n%sRun the specified command in a transient scope or service.%s\n\n" + printf("%1$s [OPTIONS...] COMMAND [ARGUMENTS...]\n" + "\n%5$sRun the specified command in a transient scope or service.%6$s\n\n" " -h --help Show this help\n" " --version Show package version\n" " --no-ask-password Do not prompt for password\n" @@ -104,7 +115,7 @@ static int help(void) { " -p --property=NAME=VALUE Set service or scope unit property\n" " --description=TEXT Description for unit\n" " --slice=SLICE Run in the specified slice\n" - " --slice-inherit Inherit the slice\n" + " --slice-inherit Inherit the slice from the caller\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" @@ -122,12 +133,14 @@ static int help(void) { " -P --pipe Pass STDIN/STDOUT/STDERR directly to service\n" " -q --quiet Suppress information messages during runtime\n" " -G --collect Unload unit after it ran, even when failed\n" - " -S --shell Invoke a $SHELL interactively\n\n" - "Path options:\n" - " --path-property=NAME=VALUE Set path unit property\n\n" - "Socket options:\n" - " --socket-property=NAME=VALUE Set socket unit property\n\n" - "Timer options:\n" + " -S --shell Invoke a $SHELL interactively\n" + " --ignore-failure Ignore the exit status of the invoked process\n" + " --background=COLOR Set ANSI color for background\n" + "\n%3$sPath options:%4$s\n" + " --path-property=NAME=VALUE Set path unit property\n" + "\n%3$sSocket options:%4$s\n" + " --socket-property=NAME=VALUE Set socket unit property\n" + "\n%3$sTimer options:%4$s\n" " --on-active=SECONDS Run after SECONDS delay\n" " --on-boot=SECONDS Run SECONDS after machine was booted up\n" " --on-startup=SECONDS Run SECONDS after systemd activation\n" @@ -137,6 +150,40 @@ static int help(void) { " --on-timezone-change Run when the timezone changes\n" " --on-clock-change Run when the realtime clock jumps\n" " --timer-property=NAME=VALUE Set timer unit property\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), ansi_normal(), + ansi_highlight(), ansi_normal()); + + return 0; +} + +static int help_sudo_mode(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("run0", "1", &link); + if (r < 0) + return log_oom(); + + printf("%s [OPTIONS...] COMMAND [ARGUMENTS...]\n" + "\n%sElevate privileges interactively.%s\n\n" + " -h --help Show this help\n" + " -V --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " --machine=CONTAINER Operate on local container\n" + " --unit=UNIT Run under the specified unit name\n" + " --property=NAME=VALUE Set service or scope unit property\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " --slice-inherit Inherit the slice\n" + " -u --user=USER Run as system user\n" + " -g --group=GROUP Run as system group\n" + " --nice=NICE Nice level\n" + " -D --chdir=PATH Set working directory\n" + " --setenv=NAME[=VALUE] Set environment variable\n" + " --background=COLOR Set ANSI color for background\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -146,6 +193,13 @@ static int help(void) { return 0; } +static bool privileged_execution(void) { + if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) + return false; + + return !arg_exec_user || STR_IN_SET(arg_exec_user, "root", "0"); +} + static int add_timer_property(const char *name, const char *val) { char *p; @@ -162,6 +216,18 @@ static int add_timer_property(const char *name, const char *val) { return 0; } +static char **make_login_shell_cmdline(const char *shell) { + _cleanup_free_ char *argv0 = NULL; + + assert(shell); + + argv0 = strjoin("-", shell); /* The - is how shells determine if they shall be consider login shells */ + if (!argv0) + return NULL; + + return strv_new(argv0); +} + static int parse_argv(int argc, char *argv[]) { enum { @@ -194,6 +260,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_WAIT, ARG_WORKING_DIRECTORY, ARG_SHELL, + ARG_IGNORE_FAILURE, + ARG_BACKGROUND, }; static const struct option options[] = { @@ -201,6 +269,7 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, + { "capsule", required_argument, NULL, 'C' }, { "scope", no_argument, NULL, ARG_SCOPE }, { "unit", required_argument, NULL, 'u' }, { "description", required_argument, NULL, ARG_DESCRIPTION }, @@ -239,6 +308,8 @@ static int parse_argv(int argc, char *argv[]) { { "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY }, { "same-dir", no_argument, NULL, 'd' }, { "shell", no_argument, NULL, 'S' }, + { "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE }, + { "background", required_argument, NULL, ARG_BACKGROUND }, {}, }; @@ -251,7 +322,7 @@ static int parse_argv(int argc, char *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) + while ((c = getopt_long(argc, argv, "+hrC:H:M:E:p:tPqGdSu:", options, NULL)) >= 0) switch (c) { @@ -273,6 +344,18 @@ static int parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; break; + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case ARG_SCOPE: arg_scope = true; break; @@ -282,7 +365,7 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_DESCRIPTION: - r = free_and_strdup(&arg_description, optarg); + r = free_and_strdup_warn(&arg_description, optarg); if (r < 0) return r; break; @@ -524,6 +607,16 @@ static int parse_argv(int argc, char *argv[]) { arg_shell = true; break; + case ARG_IGNORE_FAILURE: + arg_ignore_failure = true; + break; + + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -556,11 +649,8 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to get current working directory: %m"); } - if (!arg_service_type) { - arg_service_type = strdup("exec"); - if (!arg_service_type) - return log_oom(); - } + if (!arg_service_type) + arg_service_type = "exec"; arg_wait = true; } @@ -654,6 +744,241 @@ static int parse_argv(int argc, char *argv[]) { return 1; } +static int parse_argv_sudo_mode(int argc, char *argv[]) { + + enum { + ARG_NO_ASK_PASSWORD = 0x100, + ARG_HOST, + ARG_MACHINE, + ARG_UNIT, + ARG_PROPERTY, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SLICE_INHERIT, + ARG_NICE, + ARG_SETENV, + ARG_BACKGROUND, + }; + + /* If invoked as "run0" binary, let's expose a more sudo-like interface. We add various extensions + * though (but limit the extension to long options). */ + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "machine", required_argument, NULL, ARG_MACHINE }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "slice", required_argument, NULL, ARG_SLICE }, + { "slice-inherit", no_argument, NULL, ARG_SLICE_INHERIT }, + { "user", required_argument, NULL, 'u' }, + { "group", required_argument, NULL, 'g' }, + { "nice", required_argument, NULL, ARG_NICE }, + { "chdir", required_argument, NULL, 'D' }, + { "setenv", required_argument, NULL, ARG_SETENV }, + { "background", required_argument, NULL, ARG_BACKGROUND }, + {}, + }; + + int r, c; + + 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, "+hVu:g:D:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help_sudo_mode(); + + case 'V': + return version(); + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_MACHINE: + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_UNIT: + arg_unit = optarg; + break; + + case ARG_PROPERTY: + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_DESCRIPTION: + r = free_and_strdup_warn(&arg_description, optarg); + if (r < 0) + return r; + break; + + case ARG_SLICE: + arg_slice = optarg; + break; + + case ARG_SLICE_INHERIT: + arg_slice_inherit = true; + break; + + case 'u': + arg_exec_user = optarg; + break; + + case 'g': + arg_exec_group = optarg; + break; + + case ARG_NICE: + r = parse_nice(optarg, &arg_nice); + if (r < 0) + return log_error_errno(r, "Failed to parse nice value: %s", optarg); + + arg_nice_set = true; + break; + + case 'D': + r = parse_path_argument(optarg, true, &arg_working_directory); + if (r < 0) + return r; + + break; + + case ARG_SETENV: + r = strv_env_replace_strdup_passthrough(&arg_environment, optarg); + if (r < 0) + return log_error_errno(r, "Cannot assign environment variable %s: %m", optarg); + + break; + + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + + if (!arg_working_directory) { + if (arg_exec_user) { + /* When switching to a specific user, also switch to its home directory. */ + arg_working_directory = strdup("~"); + if (!arg_working_directory) + return log_oom(); + } else { + /* When switching to root without this being specified, then stay in the current directory */ + r = safe_getcwd(&arg_working_directory); + if (r < 0) + return log_error_errno(r, "Failed to get current working directory: %m"); + } + } + + arg_service_type = "exec"; + arg_quiet = true; + arg_wait = true; + arg_aggressive_gc = true; + + arg_stdio = isatty(STDIN_FILENO) && isatty(STDOUT_FILENO) && isatty(STDERR_FILENO) ? ARG_STDIO_PTY : ARG_STDIO_DIRECT; + arg_expand_environment = false; + arg_send_sighup = true; + + _cleanup_strv_free_ char **l = NULL; + if (argc > optind) + l = strv_copy(argv + optind); + else { + const char *e; + + e = strv_env_get(arg_environment, "SHELL"); + if (e) + arg_exec_path = strdup(e); + else { + if (arg_transport == BUS_TRANSPORT_LOCAL) { + r = get_shell(&arg_exec_path); + if (r < 0) + return log_error_errno(r, "Failed to determine shell: %m"); + } else + arg_exec_path = strdup("/bin/sh"); + } + if (!arg_exec_path) + return log_oom(); + + l = make_login_shell_cmdline(arg_exec_path); + } + if (!l) + return log_oom(); + + strv_free_and_replace(arg_cmdline, l); + + if (!arg_slice) { + arg_slice = strdup(SPECIAL_USER_SLICE); + if (!arg_slice) + return log_oom(); + } + + _cleanup_free_ char *un = NULL; + un = getusername_malloc(); + if (!un) + return log_oom(); + + /* Set a bunch of environment variables in a roughly sudo-compatible way */ + r = strv_env_assign(&arg_environment, "SUDO_USER", un); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_USER environment variable: %m"); + + r = strv_env_assignf(&arg_environment, "SUDO_UID", UID_FMT, getuid()); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_UID environment variable: %m"); + + r = strv_env_assignf(&arg_environment, "SUDO_GID", GID_FMT, getgid()); + if (r < 0) + return log_error_errno(r, "Failed to set $SUDO_GID environment variable: %m"); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_UID=" UID_FMT, getuid()) < 0) + return log_oom(); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_GID=" GID_FMT, getgid()) < 0) + return log_oom(); + + if (strv_extendf(&arg_property, "LogExtraFields=ELEVATED_USER=%s", un) < 0) + return log_oom(); + + if (strv_extend(&arg_property, "PAMName=systemd-run0") < 0) + return log_oom(); + + if (!arg_background && arg_stdio == ARG_STDIO_PTY && shall_tint_background()) { + double hue; + + if (privileged_execution()) + hue = 0; /* red */ + else + hue = 60 /* yellow */; + + r = terminal_tint_color(hue, &arg_background); + if (r < 0) + log_debug_errno(r, "Unable to get terminal background color, not tinting background: %m"); + } + + return 1; +} + static int transient_unit_set_properties(sd_bus_message *m, UnitType t, char **properties) { int r; @@ -748,7 +1073,7 @@ static int transient_kill_set_properties(sd_bus_message *m) { return 0; } -static int transient_service_set_properties(sd_bus_message *m, const char *pty_path) { +static int transient_service_set_properties(sd_bus_message *m, const char *pty_path, int pty_fd) { bool send_term = false; int r; @@ -758,6 +1083,7 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p bool use_ex_prop = arg_expand_environment == 0; assert(m); + assert(pty_path || pty_fd < 0); r = transient_unit_set_properties(m, UNIT_SERVICE, arg_property); if (r < 0) @@ -808,12 +1134,22 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p } if (pty_path) { - r = sd_bus_message_append(m, - "(sv)(sv)(sv)(sv)", - "StandardInput", "s", "tty", - "StandardOutput", "s", "tty", - "StandardError", "s", "tty", - "TTYPath", "s", pty_path); + r = sd_bus_message_append(m, "(sv)", "TTYPath", "s", pty_path); + if (r < 0) + return bus_log_create_error(r); + + if (pty_fd >= 0) + r = sd_bus_message_append(m, + "(sv)(sv)(sv)", + "StandardInputFileDescriptor", "h", pty_fd, + "StandardOutputFileDescriptor", "h", pty_fd, + "StandardErrorFileDescriptor", "h", pty_fd); + else + r = sd_bus_message_append(m, + "(sv)(sv)(sv)", + "StandardInput", "s", "tty", + "StandardOutput", "s", "tty", + "StandardError", "s", "tty"); if (r < 0) return bus_log_create_error(r); @@ -902,7 +1238,7 @@ 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", arg_cmdline[0]); + r = sd_bus_message_append(m, "s", arg_exec_path ?: arg_cmdline[0]); if (r < 0) return bus_log_create_error(r); @@ -913,9 +1249,10 @@ static int transient_service_set_properties(sd_bus_message *m, const char *pty_p if (use_ex_prop) r = sd_bus_message_append_strv( m, - STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand")); + STRV_MAKE(arg_expand_environment > 0 ? NULL : "no-env-expand", + arg_ignore_failure ? "ignore-failure" : NULL)); else - r = sd_bus_message_append(m, "b", false); + r = sd_bus_message_append(m, "b", arg_ignore_failure); if (r < 0) return bus_log_create_error(r); @@ -956,18 +1293,13 @@ static int transient_scope_set_properties(sd_bus_message *m, bool allow_pidfd) { if (r < 0) return r; - if (allow_pidfd) { - _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; - r = pidref_set_self(&pidref); - if (r < 0) - return r; + r = pidref_set_self(&pidref); + if (r < 0) + return r; - r = bus_append_scope_pidref(m, &pidref); - } else - r = sd_bus_message_append( - m, "(sv)", - "PIDs", "au", 1, getpid_cached()); + r = bus_append_scope_pidref(m, &pidref, allow_pidfd); if (r < 0) return bus_log_create_error(r); @@ -992,6 +1324,7 @@ static int transient_timer_set_properties(sd_bus_message *m) { } static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { + unsigned soft_reboots_count = 0; const char *unique, *id; char *p; int r; @@ -999,6 +1332,7 @@ static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { assert(bus); assert(t >= 0); assert(t < _UNIT_TYPE_MAX); + assert(ret); r = sd_bus_get_unique_name(bus, &unique); if (r < 0) { @@ -1030,9 +1364,27 @@ static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { "Unique name %s has unexpected format.", unique); - p = strjoin("run-u", id, ".", unit_type_to_string(t)); - if (!p) - return log_oom(); + /* The unique D-Bus names are actually unique per D-Bus instance, so on soft-reboot they will wrap + * and start over since the D-Bus broker is restarted. If there's a failed unit left behind that + * hasn't been garbage collected, we'll conflict. Append the soft-reboot counter to avoid clashing. */ + if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = bus_get_property_trivial( + bus, bus_systemd_mgr, "SoftRebootsCount", &error, 'u', &soft_reboots_count); + if (r < 0) + log_debug_errno(r, + "Failed to get SoftRebootsCount property, ignoring: %s", + bus_error_message(&error, r)); + } + + if (soft_reboots_count > 0) { + if (asprintf(&p, "run-u%s-s%u.%s", id, soft_reboots_count, unit_type_to_string(t)) < 0) + return log_oom(); + } else { + p = strjoin("run-u", id, ".", unit_type_to_string(t)); + if (!p) + return log_oom(); + } *ret = p; return 0; @@ -1085,7 +1437,7 @@ static void run_context_check_done(RunContext *c) { else done = true; - if (c->forward && done) /* If the service is gone, it's time to drain the output */ + if (c->forward && !pty_forward_is_done(c->forward) && done) /* If the service is gone, it's time to drain the output */ done = pty_forward_drain(c->forward); if (done) @@ -1155,11 +1507,18 @@ static int on_properties_changed(sd_bus_message *m, void *userdata, sd_bus_error } static int pty_forward_handler(PTYForward *f, int rcode, void *userdata) { - RunContext *c = userdata; + RunContext *c = ASSERT_PTR(userdata); assert(f); - if (rcode < 0) { + if (rcode == -ECANCELED) { + log_debug_errno(rcode, "PTY forwarder disconnected."); + if (!arg_wait) + return sd_event_exit(c->event, EXIT_SUCCESS); + + /* If --wait is specified, we'll only exit the pty forwarding, but will continue to wait + * for the service to end. If the user hits ^C we'll exit too. */ + } else if (rcode < 0) { sd_event_exit(c->event, EXIT_FAILURE); return log_error_errno(rcode, "Error on PTY forwarding logic: %m"); } @@ -1172,7 +1531,8 @@ static int make_transient_service_unit( sd_bus *bus, sd_bus_message **message, const char *service, - const char *pty_path) { + const char *pty_path, + int pty_fd) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; int r; @@ -1199,7 +1559,7 @@ static int make_transient_service_unit( if (r < 0) return bus_log_create_error(r); - r = transient_service_set_properties(m, pty_path); + r = transient_service_set_properties(m, pty_path, pty_fd); if (r < 0) return r; @@ -1243,8 +1603,6 @@ static int acquire_invocation_id(sd_bus *bus, const char *unit, sd_id128_t *ret) _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ char *object = NULL; - const void *p; - size_t l; int r; assert(bus); @@ -1267,20 +1625,55 @@ static int acquire_invocation_id(sd_bus *bus, const char *unit, sd_id128_t *ret) if (r < 0) return log_error_errno(r, "Failed to request invocation ID for unit: %s", bus_error_message(&error, r)); - r = sd_bus_message_read_array(reply, 'y', &p, &l); + r = bus_message_read_id128(reply, ret); if (r < 0) return bus_log_parse_error(r); - if (l == 0) { - *ret = SD_ID128_NULL; - return 0; /* no uuid set */ - } + return r; /* Return true when we get a non-null invocation ID. */ +} + +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *cl = NULL, *dot = NULL; + assert(f); + + if (!arg_host) + (void) gethostname_strict(&hn); + + cl = strv_join(arg_cmdline, " "); + if (!cl) + return (void) log_oom(); + + if (emoji_enabled()) + dot = strjoin(special_glyph(privileged_execution() ? SPECIAL_GLYPH_RED_CIRCLE : SPECIAL_GLYPH_YELLOW_CIRCLE), " "); + + if (arg_host || hn) + (void) pty_forward_set_titlef(f, "%s%s on %s", strempty(dot), cl, arg_host ?: hn); + else + (void) pty_forward_set_titlef(f, "%s%s", strempty(dot), cl); + + (void) pty_forward_set_title_prefix(f, dot); +} + +static int chown_to_capsule(const char *path, const char *capsule) { + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + assert(capsule); + + p = path_join("/run/capsules/", capsule); + if (!p) + return -ENOMEM; + + struct stat st; + r = chase_and_stat(p, /* root= */ NULL, CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS, /* ret_path= */ NULL, &st); + if (r < 0) + return r; - if (l != sizeof(sd_id128_t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid UUID size, %zu != %zu.", l, sizeof(sd_id128_t)); + if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid)) /* paranoid safety check */ + return -EPERM; - memcpy(ret, p, l); - return !sd_id128_is_null(*ret); + return chmod_and_chown(path, 0600, st.st_uid, st.st_gid); } static int start_transient_service(sd_bus *bus) { @@ -1288,14 +1681,14 @@ static int start_transient_service(sd_bus *bus) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_free_ char *service = NULL, *pty_path = NULL; - _cleanup_close_ int master = -EBADF; + _cleanup_close_ int master = -EBADF, slave = -EBADF; int r; assert(bus); if (arg_stdio == ARG_STDIO_PTY) { - if (arg_transport == BUS_TRANSPORT_LOCAL) { + if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)) { master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); if (master < 0) return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); @@ -1304,9 +1697,21 @@ static int start_transient_service(sd_bus *bus) { if (r < 0) return log_error_errno(r, "Failed to determine tty name: %m"); + if (arg_transport == BUS_TRANSPORT_CAPSULE) { + /* If we are in capsule mode, we must give the capsule UID/GID access to the PTY we just allocated first. */ + + r = chown_to_capsule(pty_path, arg_host); + if (r < 0) + return log_error_errno(r, "Failed to chown tty to capsule UID/GID: %m"); + } + if (unlockpt(master) < 0) return log_error_errno(errno, "Failed to unlock tty: %m"); + slave = open_terminal(pty_path, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (slave < 0) + return log_error_errno(slave, "Failed to open pty slave: %m"); + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; _cleanup_(sd_bus_message_unrefp) sd_bus_message *pty_reply = NULL; @@ -1336,6 +1741,9 @@ static int start_transient_service(sd_bus *bus) { pty_path = strdup(s); if (!pty_path) return log_oom(); + + // FIXME: Introduce OpenMachinePTYEx() that accepts ownership/permission as param + // and additionally returns the pty fd, for #33216 and #32999 } else assert_not_reached(); } @@ -1362,9 +1770,10 @@ static int start_transient_service(sd_bus *bus) { return r; } - r = make_transient_service_unit(bus, &m, service, pty_path); + r = make_transient_service_unit(bus, &m, service, pty_path, slave); if (r < 0) return r; + slave = safe_close(slave); polkit_agent_open_if_enabled(arg_transport, arg_ask_password); @@ -1381,7 +1790,7 @@ static int start_transient_service(sd_bus *bus) { r = bus_wait_for_jobs_one(w, object, - arg_quiet, + arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1420,9 +1829,9 @@ static int start_transient_service(sd_bus *bus) { return log_error_errno(r, "Failed to get event loop: %m"); if (master >= 0) { - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - (void) sd_event_add_signal(c.event, NULL, SIGINT, NULL, NULL); - (void) sd_event_add_signal(c.event, NULL, SIGTERM, NULL, NULL); + assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGWINCH) >= 0); + + (void) sd_event_set_signal_exit(c.event, true); if (!arg_quiet) log_info("Press ^] three times within 1s to disconnect TTY."); @@ -1435,6 +1844,11 @@ static int start_transient_service(sd_bus *bus) { /* Make sure to process any TTY events before we process bus events */ (void) pty_forward_set_priority(c.forward, SD_EVENT_PRIORITY_IMPORTANT); + + if (!isempty(arg_background)) + (void) pty_forward_set_background_color(c.forward, arg_background); + + set_window_title(c.forward); } path = unit_dbus_path_from_name(service); @@ -1482,12 +1896,13 @@ static int start_transient_service(sd_bus *bus) { if (!isempty(c.result)) log_info("Finished with result: %s", strna(c.result)); - if (c.exit_code == CLD_EXITED) - log_info("Main processes terminated with: code=%s/status=%u", - sigchld_code_to_string(c.exit_code), c.exit_status); - else if (c.exit_code > 0) - log_info("Main processes terminated with: code=%s/status=%s", - sigchld_code_to_string(c.exit_code), signal_to_string(c.exit_status)); + if (c.exit_code > 0) + log_info("Main processes terminated with: code=%s, status=%u/%s", + sigchld_code_to_string(c.exit_code), + c.exit_status, + strna(c.exit_code == CLD_EXITED ? + exit_status_to_string(c.exit_status, EXIT_STATUS_FULL) : + signal_to_string(c.exit_status))); if (timestamp_is_set(c.inactive_enter_usec) && timestamp_is_set(c.inactive_exit_usec) && @@ -1499,23 +1914,36 @@ static int start_transient_service(sd_bus *bus) { log_info("CPU time consumed: %s", FORMAT_TIMESPAN(DIV_ROUND_UP(c.cpu_usage_nsec, NSEC_PER_USEC), USEC_PER_MSEC)); - if (c.memory_peak != UINT64_MAX) - log_info("Memory peak: %s", FORMAT_BYTES(c.memory_peak)); + if (c.memory_peak != UINT64_MAX) { + const char *swap; + + if (c.memory_swap_peak != UINT64_MAX) + swap = strjoina(" (swap: ", FORMAT_BYTES(c.memory_swap_peak), ")"); + else + swap = ""; + + log_info("Memory peak: %s%s", FORMAT_BYTES(c.memory_peak), swap); + } - if (c.memory_swap_peak != UINT64_MAX) - log_info("Memory swap peak: %s", FORMAT_BYTES(c.memory_swap_peak)); + const char *ip_ingress = NULL, *ip_egress = NULL; - if (c.ip_ingress_bytes != UINT64_MAX) - log_info("IP traffic received: %s", FORMAT_BYTES(c.ip_ingress_bytes)); + if (!IN_SET(c.ip_ingress_bytes, 0, UINT64_MAX)) + ip_ingress = strjoina(" received: ", FORMAT_BYTES(c.ip_ingress_bytes)); + if (!IN_SET(c.ip_egress_bytes, 0, UINT64_MAX)) + ip_egress = strjoina(" sent: ", FORMAT_BYTES(c.ip_egress_bytes)); - if (c.ip_egress_bytes != UINT64_MAX) - log_info("IP traffic sent: %s", FORMAT_BYTES(c.ip_egress_bytes)); + if (ip_ingress || ip_egress) + log_info("IP traffic%s%s", strempty(ip_ingress), strempty(ip_egress)); - if (c.io_read_bytes != UINT64_MAX) - log_info("IO bytes read: %s", FORMAT_BYTES(c.io_read_bytes)); + const char *io_read = NULL, *io_write = NULL; - if (c.io_write_bytes != UINT64_MAX) - log_info("IO bytes written: %s", FORMAT_BYTES(c.io_write_bytes)); + if (!IN_SET(c.io_read_bytes, 0, UINT64_MAX)) + io_read = strjoina(" read: ", FORMAT_BYTES(c.io_read_bytes)); + if (!IN_SET(c.io_write_bytes, 0, UINT64_MAX)) + io_write = strjoina(" written: ", FORMAT_BYTES(c.io_write_bytes)); + + if (io_read || io_write) + log_info("IO bytes%s%s", strempty(io_read), strempty(io_write)); } /* Try to propagate the service's return value. But if the service defines @@ -1616,7 +2044,8 @@ static int start_transient_scope(sd_bus *bus) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, + arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1789,7 +2218,7 @@ static int make_transient_trigger_unit( if (r < 0) return bus_log_create_error(r); - r = transient_service_set_properties(m, NULL); + r = transient_service_set_properties(m, /* pty_path = */ NULL, /* pty_fd = */ -EBADF); if (r < 0) return r; @@ -1886,7 +2315,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, arg_quiet, arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); + r = bus_wait_for_jobs_one(w, object, arg_quiet ? 0 : BUS_WAIT_JOBS_LOG_ERROR, + arg_runtime_scope == RUNTIME_SCOPE_USER ? STRV_MAKE_CONST("--user") : NULL); if (r < 0) return r; @@ -1900,6 +2330,8 @@ static int start_transient_trigger(sd_bus *bus, const char *suffix) { } static bool shall_make_executable_absolute(void) { + if (arg_exec_path) + return false; if (strv_isempty(arg_cmdline)) return false; if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -1916,11 +2348,12 @@ static int run(int argc, char* argv[]) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); - r = parse_argv(argc, argv); + if (invoked_as(argv, "run0")) + r = parse_argv_sudo_mode(argc, argv); + else + r = parse_argv(argc, argv); if (r <= 0) return r; @@ -1966,7 +2399,7 @@ static int run(int argc, char* argv[]) { * limited direct connection */ if (arg_wait || arg_stdio != ARG_STDIO_NONE || - (arg_runtime_scope == RUNTIME_SCOPE_USER && arg_transport != BUS_TRANSPORT_LOCAL)) + (arg_runtime_scope == RUNTIME_SCOPE_USER && !IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE))) r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus); else r = bus_connect_transport_systemd(arg_transport, arg_host, arg_runtime_scope, &bus); diff --git a/src/run/systemd-run0.in b/src/run/systemd-run0.in new file mode 100644 index 0000000..11f830b --- /dev/null +++ b/src/run/systemd-run0.in @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# This file is part of systemd. +# +# Used by run0 sessions + +{% if ENABLE_HOMED %} +-account sufficient pam_systemd_home.so +{% endif %} +account required pam_unix.so + +{% if HAVE_SELINUX %} +session required pam_selinux.so close +session required pam_selinux.so open +{% endif %} +session required pam_loginuid.so +session optional pam_keyinit.so force revoke +session required pam_namespace.so +{% if ENABLE_HOMED %} +-session optional pam_systemd_home.so +{% endif %} +session optional pam_umask.so silent +session optional pam_systemd.so +session required pam_unix.so diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 0e323f4..bf79dc2 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -147,18 +147,17 @@ static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, c return 0; } -static int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { - +static int ask_password_keyring(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) { key_serial_t serial; int r; - assert(keyname); + assert(req); assert(ret); if (!FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED)) return -EUNATCH; - r = lookup_key(keyname, &serial); + r = lookup_key(req->keyring, &serial); if (ERRNO_IS_NEG_NOT_SUPPORTED(r) || r == -EPERM) /* When retrieving, the distinction between "kernel or container manager don't support or * allow this" and "no matching key known" doesn't matter. Note that we propagate EACCESS @@ -205,7 +204,7 @@ static int backspace_string(int ttyfd, const char *str) { } int ask_password_plymouth( - const char *message, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, @@ -225,8 +224,10 @@ int ask_password_plymouth( assert(ret); - if (!message) - message = "Password:"; + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + + const char *message = req && req->message ? req->message : "Password:"; if (flag_file) { notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); @@ -357,8 +358,7 @@ int ask_password_plymouth( int ask_password_tty( int ttyfd, - const char *message, - const char *keyname, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, @@ -381,16 +381,19 @@ int ask_password_tty( assert(ret); + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + if (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY)) return -EUNATCH; - if (!message) - message = "Password:"; + const char *message = req && req->message ? req->message : "Password:"; + const char *keyring = req ? req->keyring : NULL; if (!FLAGS_SET(flags, ASK_PASSWORD_HIDE_EMOJI) && emoji_enabled()) message = strjoina(special_glyph(SPECIAL_GLYPH_LOCK_AND_KEY), " ", message); - if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname)) { + if (flag_file || (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyring)) { notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); if (notify < 0) return -errno; @@ -399,8 +402,8 @@ int ask_password_tty( if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) return -errno; } - if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); + if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && keyring) { + r = ask_password_keyring(req, flags, ret); if (r >= 0) return 0; else if (r != -ENOKEY) @@ -443,9 +446,7 @@ int ask_password_tty( (void) loop_write(ttyfd, ANSI_NORMAL, SIZE_MAX); new_termios = old_termios; - new_termios.c_lflag &= ~(ICANON|ECHO); - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; + termios_disable_echo(&new_termios); r = RET_NERRNO(tcsetattr(ttyfd, TCSADRAIN, &new_termios)); if (r < 0) @@ -489,10 +490,10 @@ int ask_password_tty( goto finish; } - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyname) { + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0 && keyring) { (void) flush_fd(notify); - r = ask_password_keyring(keyname, flags, ret); + r = ask_password_keyring(req, flags, ret); if (r >= 0) { r = 0; goto finish; @@ -631,8 +632,8 @@ skipped: if (strv_isempty(l)) r = log_debug_errno(SYNTHETIC_ERRNO(ECANCELED), "Password query was cancelled."); else { - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); + if (keyring) + (void) add_to_keyring_and_log(keyring, flags, l); *ret = TAKE_PTR(l); r = 0; @@ -681,10 +682,7 @@ static int create_socket(char **ret) { } int ask_password_agent( - const char *message, - const char *icon, - const char *id, - const char *keyname, + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, char ***ret) { @@ -708,17 +706,20 @@ int ask_password_agent( assert(ret); + if (FLAGS_SET(flags, ASK_PASSWORD_HEADLESS)) + return -ENOEXEC; + if (FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) return -EUNATCH; assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM) >= 0); assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); (void) mkdir_p_label("/run/systemd/ask-password", 0755); - if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); + if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && req && req->keyring) { + r = ask_password_keyring(req, flags, ret); if (r >= 0) { r = 0; goto finish; @@ -777,14 +778,16 @@ int ask_password_agent( until, FLAGS_SET(flags, ASK_PASSWORD_SILENT)); - if (message) - fprintf(f, "Message=%s\n", message); + if (req) { + if (req->message) + fprintf(f, "Message=%s\n", req->message); - if (icon) - fprintf(f, "Icon=%s\n", icon); + if (req->icon) + fprintf(f, "Icon=%s\n", req->icon); - if (id) - fprintf(f, "Id=%s\n", id); + if (req->id) + fprintf(f, "Id=%s\n", req->id); + } r = fflush_and_check(f); if (r < 0) @@ -839,12 +842,14 @@ int ask_password_agent( if (notify >= 0 && pollfd[FD_INOTIFY].revents != 0) { (void) flush_fd(notify); - r = ask_password_keyring(keyname, flags, ret); - if (r >= 0) { - r = 0; - goto finish; - } else if (r != -ENOKEY) - goto finish; + if (req && req->keyring) { + r = ask_password_keyring(req, flags, ret); + if (r >= 0) { + r = 0; + goto finish; + } else if (r != -ENOKEY) + goto finish; + } } if (pollfd[FD_SOCKET].revents == 0) @@ -923,8 +928,8 @@ int ask_password_agent( log_debug("Invalid packet"); } - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); + if (req && req->keyring) + (void) add_to_keyring_and_log(req->keyring, flags, l); *ret = TAKE_PTR(l); r = 0; @@ -942,16 +947,17 @@ finish: return r; } -static int ask_password_credential(const char *credential_name, AskPasswordFlags flags, char ***ret) { +static int ask_password_credential(const AskPasswordRequest *req, AskPasswordFlags flags, char ***ret) { _cleanup_(erase_and_freep) char *buffer = NULL; size_t size; char **l; int r; - assert(credential_name); + assert(req); + assert(req->credential); assert(ret); - r = read_credential(credential_name, (void**) &buffer, &size); + r = read_credential(req->credential, (void**) &buffer, &size); if (IN_SET(r, -ENXIO, -ENOENT)) /* No credentials passed or this credential not defined? */ return -ENOKEY; @@ -964,11 +970,7 @@ static int ask_password_credential(const char *credential_name, AskPasswordFlags } int ask_password_auto( - const char *message, - const char *icon, - const char *id, /* id in "ask-password" protocol */ - const char *key_name, /* name in kernel keyring */ - const char *credential_name, /* name in $CREDENTIALS_DIRECTORY directory */ + const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, char ***ret) { @@ -977,26 +979,26 @@ int ask_password_auto( assert(ret); - if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && credential_name) { - r = ask_password_credential(credential_name, flags, ret); + if (!FLAGS_SET(flags, ASK_PASSWORD_NO_CREDENTIAL) && req && req->credential) { + r = ask_password_credential(req, flags, ret); if (r != -ENOKEY) return r; } if (FLAGS_SET(flags, ASK_PASSWORD_ACCEPT_CACHED) && - key_name && + req && req->keyring && (FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) || !isatty(STDIN_FILENO)) && FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) { - r = ask_password_keyring(key_name, flags, ret); + r = ask_password_keyring(req, flags, ret); if (r != -ENOKEY) return r; } if (!FLAGS_SET(flags, ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) - return ask_password_tty(-1, message, key_name, until, flags, NULL, ret); + return ask_password_tty(-EBADF, req, until, flags, NULL, ret); if (!FLAGS_SET(flags, ASK_PASSWORD_NO_AGENT)) - return ask_password_agent(message, icon, id, key_name, until, flags, ret); + return ask_password_agent(req, until, flags, ret); return -EUNATCH; } diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h index 7464e7f..e851d6d 100644 --- a/src/shared/ask-password-api.h +++ b/src/shared/ask-password-api.h @@ -15,9 +15,19 @@ typedef enum AskPasswordFlags { ASK_PASSWORD_CONSOLE_COLOR = 1 << 6, /* Use color if /dev/console points to a console that supports color */ ASK_PASSWORD_NO_CREDENTIAL = 1 << 7, /* never use $CREDENTIALS_DIRECTORY data */ ASK_PASSWORD_HIDE_EMOJI = 1 << 8, /* hide the lock and key emoji */ + ASK_PASSWORD_HEADLESS = 1 << 9, /* headless mode: never query interactively */ } AskPasswordFlags; -int ask_password_tty(int tty_fd, const char *message, const char *key_name, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_plymouth(const char *message, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); -int ask_password_agent(const char *message, const char *icon, const char *id, const char *key_name, usec_t until, AskPasswordFlags flag, char ***ret); -int ask_password_auto(const char *message, const char *icon, const char *id, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags flag, char ***ret); +/* Encapsulates the mostly static fields of a password query */ +typedef struct AskPasswordRequest { + const char *message; /* The human readable password prompt when asking interactively */ + const char *keyring; /* kernel keyring key name (key of "user" type) */ + const char *icon; /* freedesktop icon spec name */ + const char *id; /* some identifier used for this prompt for the "ask-password" protocol */ + const char *credential; /* $CREDENTIALS_DIRECTORY credential name */ +} AskPasswordRequest; + +int ask_password_tty(int tty_fd, const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); +int ask_password_plymouth(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret); +int ask_password_agent(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); +int ask_password_auto(const AskPasswordRequest *req, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/shared/async.c b/src/shared/async.c index 41f6b97..bbb8b81 100644 --- a/src/shared/async.c +++ b/src/shared/async.c @@ -94,7 +94,7 @@ int asynchronous_close(int fd) { pid = clone_with_nested_stack(close_func, CLONE_FILES | ((v & NEED_DOUBLE_FORK) ? 0 : SIGCHLD), UINT_TO_PTR(v)); if (pid < 0) - assert_se(close_nointr(fd) != -EBADF); /* local fallback */ + safe_close(fd); /* local fallback */ else if (v & NEED_DOUBLE_FORK) { /* Reap the intermediate child. Key here is that we specify __WCLONE, since we didn't ask for diff --git a/src/shared/bitmap.c b/src/shared/bitmap.c index 6cf08b8..9aa7661 100644 --- a/src/shared/bitmap.c +++ b/src/shared/bitmap.c @@ -163,9 +163,9 @@ bool bitmap_iterate(const Bitmap *b, Iterator *i, unsigned *n) { rem = BITMAP_NUM_TO_REM(i->idx); bitmask = UINT64_C(1) << rem; - for (; offset < b->n_bitmaps; offset ++) { + for (; offset < b->n_bitmaps; offset++) { if (b->bitmaps[offset]) { - for (; bitmask; bitmask <<= 1, rem ++) { + for (; bitmask; bitmask <<= 1, rem++) { if (b->bitmaps[offset] & bitmask) { *n = BITMAP_OFFSET_TO_NUM(offset, rem); i->idx = *n + 1; diff --git a/src/shared/blockdev-util.c b/src/shared/blockdev-util.c index 7a2dd1c..2055550 100644 --- a/src/shared/blockdev-util.c +++ b/src/shared/blockdev-util.c @@ -57,23 +57,12 @@ static int fd_get_devnum(int fd, BlockDeviceLookupFlag flags, dev_t *ret) { } int block_device_is_whole_disk(sd_device *dev) { - const char *s; - int r; - assert(dev); - r = sd_device_get_subsystem(dev, &s); - if (r < 0) - return r; - - if (!streq(s, "block")) + if (!device_in_subsystem(dev, "block")) return -ENOTBLK; - r = sd_device_get_devtype(dev, &s); - if (r < 0) - return r; - - return streq(s, "disk"); + return device_is_devtype(dev, "disk"); } int block_device_get_whole_disk(sd_device *dev, sd_device **ret) { @@ -380,15 +369,44 @@ int blockdev_partscan_enabled(int fd) { * is 1, which can be check with 'ext_range' sysfs attribute. Explicit flag ('GENHD_FL_NO_PART_SCAN') * can be obtained from 'capability' sysattr. * - * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), we - * can check the flag from 'ext_range' sysfs attribute directly. + * With https://github.com/torvalds/linux/commit/46e7eac647b34ed4106a8262f8bedbb90801fadd (v5.17), + * the flag is renamed to GENHD_FL_NO_PART. + * + * With https://github.com/torvalds/linux/commit/1ebe2e5f9d68e94c524aba876f27b945669a7879 (v5.17), + * we can check the flag from 'ext_range' sysfs attribute directly. + * + * With https://github.com/torvalds/linux/commit/430cc5d3ab4d0ba0bd011cfbb0035e46ba92920c (v5.17), + * the value of GENHD_FL_NO_PART is changed from 0x0200 to 0x0004. 💣💣💣 + * Note, the new value was used by the GENHD_FL_MEDIA_CHANGE_NOTIFY flag, which was introduced by + * 86ce18d7b7925bfd6b64c061828ca2a857ee83b8 (v2.6.22), and removed by + * 9243c6f3e012a92dd900d97ef45efaf8a8edc448 (v5.7). If we believe the commit message of + * e81cd5a983bb35dabd38ee472cf3fea1c63e0f23, the flag was never used. So, fortunately, we can use + * both the new and old values safely. + * + * With https://github.com/torvalds/linux/commit/b9684a71fca793213378dd410cd11675d973eaa1 (v5.19), + * another flag GD_SUPPRESS_PART_SCAN is introduced for loopback block device, and partition scanning + * is done only when both GENHD_FL_NO_PART and GD_SUPPRESS_PART_SCAN are not set. Before the commit, + * LO_FLAGS_PARTSCAN flag was directly tied with GENHD_FL_NO_PART. But with this change now it is + * tied with GD_SUPPRESS_PART_SCAN. So, LO_FLAGS_PARTSCAN cannot be obtained from 'ext_range' + * sysattr, which corresponds to GENHD_FL_NO_PART, and we need to read 'loop/partscan'. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/73a166d9749230d598320fdae3b687cdc0e2e205 (v6.3), + * the GD_SUPPRESS_PART_SCAN flag is also introduced for userspace block device (ublk). Though, not + * sure if we should support the device... * * With https://github.com/torvalds/linux/commit/e81cd5a983bb35dabd38ee472cf3fea1c63e0f23 (v6.3), - * the 'capability' sysfs attribute is deprecated, hence we cannot check the flag from it. + * the 'capability' sysfs attribute is deprecated, hence we cannot check flags from it. 💣💣💣 + * + * With https://github.com/torvalds/linux/commit/a4217c6740dc64a3eb6815868a9260825e8c68c6 (v6.10, + * backported to v6.6+), the partscan status is directly exposed as 'partscan' sysattr. * - * To support both old and new kernels, we need to do the following: first check 'ext_range' sysfs - * attribute, and if '1' we can conclude partition scanning is disabled, otherwise check 'capability' - * sysattr for older version. */ + * To support both old and new kernels, we need to do the following: + * 1) check 'partscan' sysfs attribute where the information is made directly available, + * 2) check if the blockdev refers to a partition, where partscan is not supported, + * 3) check 'loop/partscan' sysfs attribute for loopback block devices, and if '0' we can conclude + * partition scanning is disabled, + * 4) check 'ext_range' sysfs attribute, and if '1' we can conclude partition scanning is disabled, + * 5) otherwise check 'capability' sysfs attribute for ancient version. */ assert(fd >= 0); @@ -396,6 +414,21 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; + /* For v6.10 or newer. */ + r = device_get_sysattr_bool(dev, "partscan"); + if (r != -ENOENT) + return r; + + /* Partition block devices never have partition scanning on, there's no concept of sub-partitions for + * partitions. */ + if (device_is_devtype(dev, "partition")) + return false; + + /* For loopback block device, especially for v5.19 or newer. Even if this is enabled, we also need to + * check GENHD_FL_NO_PART flag through 'ext_range' and 'capability' sysfs attributes below. */ + if (device_get_sysattr_bool(dev, "loop/partscan") == 0) + return false; + r = device_get_sysattr_int(dev, "ext_range", &ext_range); if (r == -ENOENT) /* If the ext_range file doesn't exist then we are most likely looking at a * partition block device, not the whole block device. And that means we have no @@ -405,7 +438,7 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; - if (ext_range <= 1) /* The valus should be always positive, but the kernel uses '%d' for the + if (ext_range <= 1) /* The value should be always positive, but the kernel uses '%d' for the * attribute. Let's gracefully handle zero or negative. */ return false; @@ -415,12 +448,10 @@ int blockdev_partscan_enabled(int fd) { if (r < 0) return r; -#ifndef GENHD_FL_NO_PART_SCAN -#define GENHD_FL_NO_PART_SCAN (0x0200) -#endif - - /* If 0x200 is set, part scanning is definitely off. */ - if (FLAGS_SET(capability, GENHD_FL_NO_PART_SCAN)) +#define GENHD_FL_NO_PART_OLD 0x0200 +#define GENHD_FL_NO_PART_NEW 0x0004 + /* If one of the NO_PART flags is set, part scanning is definitely off. */ + if ((capability & (GENHD_FL_NO_PART_OLD | GENHD_FL_NO_PART_NEW)) != 0) return false; /* Otherwise, assume part scanning is on, we have no further checks available. Assume the best. */ @@ -801,6 +832,21 @@ int blockdev_get_sector_size(int fd, uint32_t *ret) { return 0; } +int blockdev_get_device_size(int fd, uint64_t *ret) { + uint64_t sz = 0; + + assert(fd >= 0); + assert(ret); + + /* This is just a type-safe wrapper around BLKGETSIZE64 that gets us around having to include messy linux/fs.h in various clients */ + + if (ioctl(fd, BLKGETSIZE64, &sz) < 0) + return -errno; + + *ret = sz; + return 0; +} + int blockdev_get_root(int level, dev_t *ret) { _cleanup_free_ char *p = NULL; dev_t devno; diff --git a/src/shared/blockdev-util.h b/src/shared/blockdev-util.h index 954a23d..28aede2 100644 --- a/src/shared/blockdev-util.h +++ b/src/shared/blockdev-util.h @@ -57,5 +57,6 @@ int block_device_has_partitions(sd_device *dev); int blockdev_reread_partition_table(sd_device *dev); int blockdev_get_sector_size(int fd, uint32_t *ret); +int blockdev_get_device_size(int fd, uint64_t *ret); int blockdev_get_root(int level, dev_t *ret); diff --git a/src/shared/boot-entry.c b/src/shared/boot-entry.c index e726073..72d3cbe 100644 --- a/src/shared/boot-entry.c +++ b/src/shared/boot-entry.c @@ -15,20 +15,18 @@ 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) { +static int entry_token_load_one(int rfd, const char *dir, 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(dir); assert(type); assert(*type == BOOT_ENTRY_TOKEN_AUTO); assert(token); - if (!etc_kernel) - return 0; - - p = path_join(etc_kernel, "entry-token"); + p = path_join(dir, "entry-token"); if (!p) return log_oom(); @@ -55,6 +53,26 @@ static int entry_token_load(int rfd, const char *etc_kernel, BootEntryTokenType return 1; } +static int entry_token_load(int rfd, const char *conf_root, BootEntryTokenType *type, char **token) { + int r; + + assert(rfd >= 0 || rfd == AT_FDCWD); + assert(type); + assert(*type == BOOT_ENTRY_TOKEN_AUTO); + assert(token); + + if (conf_root) + return entry_token_load_one(rfd, conf_root, type, token); + + FOREACH_STRING(path, "/etc/kernel", "/usr/lib/kernel") { + r = entry_token_load_one(rfd, path, type, token); + if (r != 0) + return r; + } + + return 0; +} + static int entry_token_from_machine_id(sd_id128_t machine_id, BootEntryTokenType *type, char **token) { char *p; @@ -123,7 +141,7 @@ static int entry_token_from_os_release(int rfd, BootEntryTokenType *type, char * int boot_entry_token_ensure_at( int rfd, - const char *etc_kernel, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, @@ -141,7 +159,7 @@ int boot_entry_token_ensure_at( switch (*type) { case BOOT_ENTRY_TOKEN_AUTO: - r = entry_token_load(rfd, etc_kernel, type, token); + r = entry_token_load(rfd, conf_root, type, token); if (r != 0) return r; @@ -198,7 +216,7 @@ int boot_entry_token_ensure_at( int boot_entry_token_ensure( const char *root, - const char *etc_kernel, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, @@ -215,7 +233,7 @@ int boot_entry_token_ensure( if (rfd < 0) return -errno; - return boot_entry_token_ensure_at(rfd, etc_kernel, machine_id, machine_id_is_random, type, token); + return boot_entry_token_ensure_at(rfd, conf_root, machine_id, machine_id_is_random, type, token); } int parse_boot_entry_token_type(const char *s, BootEntryTokenType *type, char **token) { diff --git a/src/shared/boot-entry.h b/src/shared/boot-entry.h index f3a6f28..836b637 100644 --- a/src/shared/boot-entry.h +++ b/src/shared/boot-entry.h @@ -17,14 +17,14 @@ 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. */ + const char *conf_root, /* 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, + const char *conf_root, sd_id128_t machine_id, bool machine_id_is_random, BootEntryTokenType *type, diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index f4b2fdc..4bc3ae7 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -57,6 +57,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->machine_id); free(entry->architecture); strv_free(entry->options); + free(entry->local_addons.items); free(entry->kernel); free(entry->efi); strv_free(entry->initrd); @@ -78,10 +79,7 @@ static int mangle_path( assert(ret); /* Spec leaves open if prefixed with "/" or not, let's normalize that */ - if (path_is_absolute(p)) - c = strdup(p); - else - c = strjoin("/", p); + c = path_make_absolute(p, "/"); if (!c) return -ENOMEM; @@ -288,7 +286,6 @@ static int boot_entry_load_type1( BootEntry *entry) { _cleanup_(boot_entry_free) BootEntry tmp = BOOT_ENTRY_INIT(BOOT_ENTRY_CONF); - unsigned line = 1; char *c; int r; @@ -323,18 +320,16 @@ static int boot_entry_load_type1( if (!tmp.root) return log_oom(); - for (;;) { + for (unsigned line = 1;; line++) { _cleanup_free_ char *buf = NULL, *field = NULL; r = read_stripped_line(f, LONG_LINE_MAX, &buf); - if (r == 0) - break; if (r == -ENOBUFS) return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Line too long."); if (r < 0) return log_syntax(NULL, LOG_ERR, tmp.path, line, r, "Error while reading: %m"); - - line++; + if (r == 0) + break; if (IN_SET(buf[0], '#', '\0')) continue; @@ -421,44 +416,36 @@ void boot_config_free(BootConfig *config) { assert(config); free(config->default_pattern); - free(config->timeout); - free(config->editor); - free(config->auto_entries); - free(config->auto_firmware); - free(config->console_mode); - free(config->beep); free(config->entry_oneshot); free(config->entry_default); free(config->entry_selected); - for (size_t i = 0; i < config->n_entries; i++) - boot_entry_free(config->entries + i); + FOREACH_ARRAY(i, config->entries, config->n_entries) + boot_entry_free(i); free(config->entries); + free(config->global_addons.items); set_free(config->inodes_seen); } int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) { - unsigned line = 1; int r; assert(config); assert(file); assert(path); - for (;;) { + for (unsigned line = 1;; line++) { _cleanup_free_ char *buf = NULL, *field = NULL; r = read_stripped_line(file, LONG_LINE_MAX, &buf); - if (r == 0) - break; if (r == -ENOBUFS) return log_syntax(NULL, LOG_ERR, path, line, r, "Line too long."); if (r < 0) return log_syntax(NULL, LOG_ERR, path, line, r, "Error while reading: %m"); - - line++; + if (r == 0) + break; if (IN_SET(buf[0], '#', '\0')) continue; @@ -480,20 +467,10 @@ int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) { if (streq(field, "default")) r = free_and_strdup(&config->default_pattern, p); - else if (streq(field, "timeout")) - r = free_and_strdup(&config->timeout, p); - else if (streq(field, "editor")) - r = free_and_strdup(&config->editor, p); - else if (streq(field, "auto-entries")) - r = free_and_strdup(&config->auto_entries, p); - else if (streq(field, "auto-firmware")) - r = free_and_strdup(&config->auto_firmware, p); - else if (streq(field, "console-mode")) - r = free_and_strdup(&config->console_mode, p); - else if (streq(field, "random-seed-mode")) - log_syntax(NULL, LOG_WARNING, path, line, 0, "'random-seed-mode' has been deprecated, ignoring."); - else if (streq(field, "beep")) - r = free_and_strdup(&config->beep, p); + else if (STR_IN_SET(field, "timeout", "editor", "auto-entries", "auto-firmware", + "auto-poweroff", "auto-reboot", "beep", "reboot-for-bitlocker", + "secure-boot-enroll", "console-mode")) + r = 0; /* we don't parse these in userspace, but they are OK */ else { log_syntax(NULL, LOG_WARNING, path, line, 0, "Unknown line '%s', ignoring.", field); continue; @@ -609,8 +586,8 @@ static int boot_entries_find_type1( if (r < 0) return log_error_errno(r, "Failed to read directory '%s': %m", full); - for (size_t i = 0; i < dentries->n_entries; i++) { - const struct dirent *de = dentries->entries[i]; + FOREACH_ARRAY(i, dentries->entries, dentries->n_entries) { + const struct dirent *de = *i; _cleanup_fclose_ FILE *f = NULL; if (!dirent_is_file(de)) @@ -633,7 +610,7 @@ static int boot_entries_find_type1( r = boot_config_load_type1(config, f, root, full, de->d_name); if (r == -ENOMEM) /* ignore all other errors */ - return r; + return log_oom(); } return 0; @@ -753,13 +730,12 @@ static int boot_entry_load_unified( static int find_sections( int fd, const char *path, - char **ret_osrelease, - char **ret_cmdline) { + IMAGE_SECTION_HEADER **ret_sections, + PeHeader **ret_pe_header) { - _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; _cleanup_free_ IMAGE_DOS_HEADER *dos_header = NULL; - _cleanup_free_ char *osrel = NULL, *cmdline = NULL; - _cleanup_free_ PeHeader *pe_header = NULL; + IMAGE_SECTION_HEADER *sections; + PeHeader *pe_header; int r; assert(fd >= 0); @@ -773,25 +749,253 @@ static int find_sections( if (r < 0) return log_warning_errno(r, "Failed to parse PE sections of '%s': %m", path); - if (!pe_is_uki(pe_header, sections)) - return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); + if (ret_pe_header) + *ret_pe_header = TAKE_PTR(pe_header); + if (ret_sections) + *ret_sections = TAKE_PTR(sections); - r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) &osrel, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path); + return 0; +} + +static int find_cmdline_section( + int fd, + const char *path, + IMAGE_SECTION_HEADER *sections, + PeHeader *pe_header, + char **ret_cmdline) { + + int r; + char *cmdline = NULL, *t = NULL; + _cleanup_free_ char *word = NULL; + + assert(path); + + if (!ret_cmdline) + return 0; r = pe_read_section_data(fd, pe_header, sections, ".cmdline", PE_SECTION_SIZE_MAX, (void**) &cmdline, NULL); - if (r < 0 && r != -ENXIO) /* cmdline is optional */ + if (r == -ENXIO) { /* cmdline is optional */ + *ret_cmdline = NULL; + return 0; + } + if (r < 0) return log_warning_errno(r, "Failed to read .cmdline section of '%s': %m", path); - if (ret_osrelease) - *ret_osrelease = TAKE_PTR(osrel); - if (ret_cmdline) + word = strdup(cmdline); + if (!word) + return log_oom(); + + /* Quick test to check if there is actual content in the addon cmdline */ + t = delete_chars(word, NULL); + if (isempty(t)) + *ret_cmdline = NULL; + else *ret_cmdline = TAKE_PTR(cmdline); return 0; } +static int find_osrel_section( + int fd, + const char *path, + IMAGE_SECTION_HEADER *sections, + PeHeader *pe_header, + char **ret_osrelease) { + + int r; + + if (!ret_osrelease) + return 0; + + r = pe_read_section_data(fd, pe_header, sections, ".osrel", PE_SECTION_SIZE_MAX, (void**) ret_osrelease, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to read .osrel section of '%s': %m", path); + + return 0; +} + +static int find_uki_sections( + int fd, + const char *path, + char **ret_osrelease, + char **ret_cmdline) { + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + int r; + + r = find_sections(fd, path, §ions, &pe_header); + if (r < 0) + return r; + + if (!pe_is_uki(pe_header, sections)) + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Parsed PE file '%s' is not a UKI.", path); + + r = find_osrel_section(fd, path, sections, pe_header, ret_osrelease); + if (r < 0) + return r; + + r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); + if (r < 0) + return r; + + return 0; +} + +static int find_addon_sections( + int fd, + const char *path, + char **ret_cmdline) { + + _cleanup_free_ IMAGE_SECTION_HEADER *sections = NULL; + _cleanup_free_ PeHeader *pe_header = NULL; + int r; + + r = find_sections(fd, path, §ions, &pe_header); + if (r < 0) + return r; + + r = find_cmdline_section(fd, path, sections, pe_header, ret_cmdline); + /* If addon cmdline is empty or contains just separators, + * don't bother tracking it. + * Don't check r because it cannot return <0 if cmdline is empty, + * as cmdline is always optional. */ + if (!ret_cmdline) + return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Addon %s contains empty cmdline and will be therefore ignored.", path); + + return r; +} + +static int insert_boot_entry_addon( + BootEntryAddons *addons, + char *location, + char *cmdline) { + + assert(addons); + + if (!GREEDY_REALLOC(addons->items, addons->n_items + 1)) + return log_oom(); + + addons->items[addons->n_items++] = (BootEntryAddon) { + .location = location, + .cmdline = cmdline, + }; + + return 0; +} + +static void boot_entry_addons_done(BootEntryAddons *addons) { + assert(addons); + + FOREACH_ARRAY(addon, addons->items, addons->n_items) { + free(addon->cmdline); + free(addon->location); + } + addons->items = mfree(addons->items); + addons->n_items = 0; +} + +static int boot_entries_find_unified_addons( + BootConfig *config, + int d_fd, + const char *addon_dir, + const char *root, + BootEntryAddons *ret_addons) { + + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *full = NULL; + _cleanup_(boot_entry_addons_done) BootEntryAddons addons = {}; + int r; + + assert(ret_addons); + assert(config); + + r = chase_and_opendirat(d_fd, addon_dir, CHASE_AT_RESOLVE_IN_ROOT, &full, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open '%s/%s': %m", root, addon_dir); + + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", full)) { + _cleanup_free_ char *j = NULL, *cmdline = NULL, *location = NULL; + _cleanup_close_ int fd = -EBADF; + + if (!dirent_is_file(de)) + continue; + + if (!endswith_no_case(de->d_name, ".addon.efi")) + continue; + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOFOLLOW|O_NOCTTY); + if (fd < 0) { + log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", full, de->d_name); + continue; + } + + r = config_check_inode_relevant_and_unseen(config, fd, de->d_name); + if (r < 0) + return r; + if (r == 0) /* inode already seen or otherwise not relevant */ + continue; + + j = path_join(full, de->d_name); + if (!j) + return log_oom(); + + if (find_addon_sections(fd, j, &cmdline) < 0) + continue; + + location = strdup(j); + if (!location) + return log_oom(); + + r = insert_boot_entry_addon(&addons, location, cmdline); + if (r < 0) + return r; + + TAKE_PTR(location); + TAKE_PTR(cmdline); + } + + *ret_addons = TAKE_STRUCT(addons); + return 0; +} + +static int boot_entries_find_unified_global_addons( + BootConfig *config, + const char *root, + const char *d_name) { + + int r; + _cleanup_closedir_ DIR *d = NULL; + + r = chase_and_opendir(root, NULL, CHASE_PROHIBIT_SYMLINKS, NULL, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to open '%s/%s': %m", root, d_name); + + return boot_entries_find_unified_addons(config, dirfd(d), d_name, root, &config->global_addons); +} + +static int boot_entries_find_unified_local_addons( + BootConfig *config, + int d_fd, + const char *d_name, + const char *root, + BootEntry *ret) { + + _cleanup_free_ char *addon_dir = NULL; + + assert(ret); + + addon_dir = strjoin(d_name, ".extra.d"); + if (!addon_dir) + return log_oom(); + + return boot_entries_find_unified_addons(config, d_fd, addon_dir, root, &ret->local_addons); +} + static int boot_entries_find_unified( BootConfig *config, const char *root, @@ -839,13 +1043,18 @@ static int boot_entries_find_unified( if (!j) return log_oom(); - if (find_sections(fd, j, &osrelease, &cmdline) < 0) + if (find_uki_sections(fd, j, &osrelease, &cmdline) < 0) continue; r = boot_entry_load_unified(root, j, osrelease, cmdline, config->entries + config->n_entries); if (r < 0) continue; + /* look for .efi.extra.d */ + r = boot_entries_find_unified_local_addons(config, dirfd(d), de->d_name, full, config->entries + config->n_entries); + if (r < 0) + continue; + config->n_entries++; } @@ -1062,6 +1271,7 @@ int boot_config_load( int r; assert(config); + config->global_addons = (BootEntryAddons) {}; if (esp_path) { r = boot_loader_read_conf_path(config, esp_path, "/loader/loader.conf"); @@ -1075,6 +1285,10 @@ int boot_config_load( r = boot_entries_find_unified(config, esp_path, "/EFI/Linux/"); if (r < 0) return r; + + r = boot_entries_find_unified_global_addons(config, esp_path, "/loader/addons/"); + if (r < 0) + return r; } if (xbootldr_path) { @@ -1238,13 +1452,155 @@ static void boot_entry_file_list( *ret_status = status; } +static void print_addon( + BootEntryAddon *addon, + const char *addon_str) { + + printf(" %s: %s\n", addon_str, addon->location); + printf(" options: %s%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), addon->cmdline); +} + +static int indent_embedded_newlines(char *cmdline, char **ret_cmdline) { + _cleanup_free_ char *t = NULL; + _cleanup_strv_free_ char **ts = NULL; + + assert(ret_cmdline); + + ts = strv_split_newlines(cmdline); + if (!ts) + return -ENOMEM; + + t = strv_join(ts, "\n "); + if (!t) + return -ENOMEM; + + *ret_cmdline = TAKE_PTR(t); + + return 0; +} + +static int print_cmdline( + const BootEntry *e, + const BootEntryAddons *global_arr) { + + _cleanup_free_ char *options = NULL, *combined_cmdline = NULL, *t2 = NULL; + + assert(e); + + if (!strv_isempty(e->options)) { + _cleanup_free_ char *t = NULL; + + options = strv_join(e->options, " "); + if (!options) + return log_oom(); + + if (indent_embedded_newlines(options, &t) < 0) + return log_oom(); + + printf(" options: %s\n", t); + t2 = strdup(options); + if (!t2) + return log_oom(); + } + + FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) { + print_addon(addon, "global-addon"); + if (!strextend(&t2, " ", addon->cmdline)) + return log_oom(); + } + + FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { + /* Add space at the beginning of addon_str to align it correctly */ + print_addon(addon, " local-addon"); + if (!strextend(&t2, " ", addon->cmdline)) + return log_oom(); + } + + /* Don't print the combined cmdline if it's same as options. */ + if (streq_ptr(t2, options)) + return 0; + + if (indent_embedded_newlines(t2, &combined_cmdline) < 0) + return log_oom(); + + if (combined_cmdline) + printf(" cmdline: %s\n", combined_cmdline); + + return 0; +} + +static int json_addon( + BootEntryAddon *addon, + const char *addon_str, + JsonVariant **array) { + + int r; + + assert(addon); + assert(addon_str); + + r = json_variant_append_arrayb( + array, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR(addon_str, JSON_BUILD_STRING(addon->location)), + JSON_BUILD_PAIR("options", JSON_BUILD_STRING(addon->cmdline)))); + if (r < 0) + return log_oom(); + + return 0; +} + +static int json_cmdline( + const BootEntry *e, + const BootEntryAddons *global_arr, + const char *def_cmdline, + JsonVariant **v) { + + _cleanup_free_ char *combined_cmdline = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *addons_array = NULL; + int r; + + assert(e); + + if (def_cmdline) { + combined_cmdline = strdup(def_cmdline); + if (!combined_cmdline) + return log_oom(); + } + + FOREACH_ARRAY(addon, global_arr->items, global_arr->n_items) { + r = json_addon(addon, "globalAddon", &addons_array); + if (r < 0) + return r; + if (!strextend(&combined_cmdline, " ", addon->cmdline)) + return log_oom(); + } + + FOREACH_ARRAY(addon, e->local_addons.items, e->local_addons.n_items) { + r = json_addon(addon, "localAddon", &addons_array); + if (r < 0) + return r; + if (!strextend(&combined_cmdline, " ", addon->cmdline)) + return log_oom(); + } + + r = json_variant_merge_objectb( + v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("addons", JSON_BUILD_VARIANT(addons_array)), + JSON_BUILD_PAIR_CONDITION(combined_cmdline, "cmdline", JSON_BUILD_STRING(combined_cmdline)))); + if (r < 0) + return log_oom(); + return 0; +} + int show_boot_entry( const BootEntry *e, + const BootEntryAddons *global_addons, bool show_as_default, bool show_as_selected, bool show_reported) { - int status = 0; + int status = 0, r = 0; /* Returns 0 on success, negative on processing error, and positive if something is wrong with the boot entry itself. */ @@ -1323,24 +1679,9 @@ int show_boot_entry( *s, &status); - if (!strv_isempty(e->options)) { - _cleanup_free_ char *t = NULL, *t2 = NULL; - _cleanup_strv_free_ char **ts = NULL; - - t = strv_join(e->options, " "); - if (!t) - return log_oom(); - - ts = strv_split_newlines(t); - if (!ts) - return log_oom(); - - t2 = strv_join(ts, "\n "); - if (!t2) - return log_oom(); - - printf(" options: %s\n", t2); - } + r = print_cmdline(e, global_addons); + if (r < 0) + return r; if (e->device_tree) boot_entry_file_list("devicetree", e->root, e->device_tree, &status); @@ -1354,6 +1695,71 @@ int show_boot_entry( return -status; } +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_free_ char *opts = NULL; + const BootEntry *e; + int r; + + assert(c); + assert(ret); + + if (i >= c->n_entries) { + *ret = NULL; + return 0; + } + + e = c->entries + i; + + if (!strv_isempty(e->options)) { + opts = strv_join(e->options, " "); + if (!opts) + return log_oom(); + } + + r = json_variant_merge_objectb( + &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)), + JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), + JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), + JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), + JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), + JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), + JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), + JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), + JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), + JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), + JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); + if (r < 0) + return log_oom(); + + /* Sanitizers (only memory sanitizer?) do not like function call with too many + * arguments and trigger false positive warnings. Let's not add too many json objects + * at once. */ + r = json_variant_merge_objectb( + &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(c->default_entry >= 0, "isDefault", JSON_BUILD_BOOLEAN(i == (size_t) c->default_entry)), + JSON_BUILD_PAIR_CONDITION(c->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) c->selected_entry)))); + + if (r < 0) + return log_oom(); + + r = json_cmdline(e, &c->global_addons, opts, &v); + if (r < 0) + return log_oom(); + + *ret = TAKE_PTR(v); + return 1; +} + int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { int r; @@ -1363,48 +1769,9 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; for (size_t i = 0; i < config->n_entries; i++) { - _cleanup_free_ char *opts = NULL; - const BootEntry *e = config->entries + i; _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - if (!strv_isempty(e->options)) { - opts = strv_join(e->options, " "); - if (!opts) - return log_oom(); - } - - r = json_variant_merge_objectb( - &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)), - JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), - JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), - JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), - JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), - JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), - JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), - JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), - JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), - JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), - JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); - if (r < 0) - return log_oom(); - - /* Sanitizers (only memory sanitizer?) do not like function call with too many - * arguments and trigger false positive warnings. Let's not add too many json objects - * at once. */ - r = json_variant_merge_objectb( - &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)), - JSON_BUILD_PAIR_CONDITION(config->selected_entry >= 0, "isSelected", JSON_BUILD_BOOLEAN(i == (size_t) config->selected_entry)))); - + r = boot_entry_to_json(config, i, &v); if (r < 0) return log_oom(); @@ -1413,12 +1780,12 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { return log_oom(); } - json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); - - } else { + return json_variant_dump(array, json_format | JSON_FORMAT_EMPTY_ARRAY, NULL, NULL); + } else for (size_t n = 0; n < config->n_entries; n++) { r = show_boot_entry( config->entries + n, + &config->global_addons, /* show_as_default= */ n == (size_t) config->default_entry, /* show_as_selected= */ n == (size_t) config->selected_entry, /* show_discovered= */ true); @@ -1428,7 +1795,6 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { if (n+1 < config->n_entries) putchar('\n'); } - } return 0; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ddd149e..1885a88 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -20,6 +20,18 @@ typedef enum BootEntryType { _BOOT_ENTRY_TYPE_INVALID = -EINVAL, } BootEntryType; +typedef struct BootEntryAddon { + char *location; + char *cmdline; +} BootEntryAddon; + +typedef struct BootEntryAddons { + BootEntryAddon *items; + size_t n_items; +} BootEntryAddons; + +BootEntryAddon* boot_entry_addon_free(BootEntryAddon *t); + typedef struct BootEntry { BootEntryType type; bool reported_by_loader; @@ -34,6 +46,7 @@ typedef struct BootEntry { char *machine_id; char *architecture; char **options; + BootEntryAddons local_addons; char *kernel; /* linux is #defined to 1, yikes! */ char *efi; char **initrd; @@ -52,12 +65,6 @@ typedef struct BootEntry { typedef struct BootConfig { char *default_pattern; - char *timeout; - char *editor; - char *auto_entries; - char *auto_firmware; - char *console_mode; - char *beep; char *entry_oneshot; char *entry_default; @@ -66,6 +73,8 @@ typedef struct BootConfig { BootEntry *entries; size_t n_entries; + BootEntryAddons global_addons; + ssize_t default_entry; ssize_t selected_entry; @@ -119,6 +128,7 @@ static inline const char* boot_entry_title(const BootEntry *entry) { int show_boot_entry( const BootEntry *e, + const BootEntryAddons *global_addons, bool show_as_default, bool show_as_selected, bool show_reported); @@ -127,3 +137,5 @@ int show_boot_entries( JsonFormatFlags json_format); int boot_filename_extract_tries(const char *fname, char **ret_stripped, unsigned *ret_tries_left, unsigned *ret_tries_done); + +int boot_entry_to_json(const BootConfig *c, size_t i, JsonVariant **ret); diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h index 9ccb7d8..5a7945c 100644 --- a/src/shared/bpf-compat.h +++ b/src/shared/bpf-compat.h @@ -26,6 +26,7 @@ struct bpf_map_create_opts; * When removing this file move these back to bpf-dlopen.h */ extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); extern int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); +extern struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); /* compat symbols removed in libbpf 1.0 */ extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c index f00dbea..50491fc 100644 --- a/src/shared/bpf-dlopen.c +++ b/src/shared/bpf-dlopen.c @@ -18,26 +18,41 @@ #define MODERN_LIBBPF 0 #endif -struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); -struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); -int (*sym_bpf_link__fd)(const struct bpf_link *); -int (*sym_bpf_link__destroy)(struct bpf_link *); -int (*sym_bpf_map__fd)(const struct bpf_map *); -const char* (*sym_bpf_map__name)(const struct bpf_map *); +DLSYM_FUNCTION(bpf_link__destroy); +DLSYM_FUNCTION(bpf_link__fd); +DLSYM_FUNCTION(bpf_link__open); +DLSYM_FUNCTION(bpf_link__pin); +DLSYM_FUNCTION(bpf_map__fd); +DLSYM_FUNCTION(bpf_map__name); +DLSYM_FUNCTION(bpf_map__set_inner_map_fd); +DLSYM_FUNCTION(bpf_map__set_max_entries); +DLSYM_FUNCTION(bpf_map__set_pin_path); +DLSYM_FUNCTION(bpf_map_delete_elem); +DLSYM_FUNCTION(bpf_map_get_fd_by_id); +DLSYM_FUNCTION(bpf_map_lookup_elem); +DLSYM_FUNCTION(bpf_map_update_elem); +DLSYM_FUNCTION(bpf_object__attach_skeleton); +DLSYM_FUNCTION(bpf_object__destroy_skeleton); +DLSYM_FUNCTION(bpf_object__detach_skeleton); +DLSYM_FUNCTION(bpf_object__load_skeleton); +DLSYM_FUNCTION(bpf_object__name); +DLSYM_FUNCTION(bpf_object__open_skeleton); +DLSYM_FUNCTION(bpf_object__pin_maps); +DLSYM_FUNCTION(bpf_program__attach); +DLSYM_FUNCTION(bpf_program__attach_cgroup); +DLSYM_FUNCTION(bpf_program__attach_lsm); +DLSYM_FUNCTION(bpf_program__name); +DLSYM_FUNCTION(libbpf_get_error); +DLSYM_FUNCTION(libbpf_set_print); +DLSYM_FUNCTION(ring_buffer__epoll_fd); +DLSYM_FUNCTION(ring_buffer__free); +DLSYM_FUNCTION(ring_buffer__new); +DLSYM_FUNCTION(ring_buffer__poll); + +/* new symbols available from libbpf 0.7.0 */ int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); -int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32); -int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64); -int (*sym_bpf_map_delete_elem)(int, const void *); -int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int); -int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *); -int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *); -int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *); -void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *); -void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *); int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); -const char* (*sym_bpf_program__name)(const struct bpf_program *); -libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t); -long (*sym_libbpf_get_error)(const void *); +struct bpf_map* (*sym_bpf_object__next_map)(const struct bpf_object *obj, const struct bpf_map *map); /* compat symbols removed in libbpf 1.0 */ int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); @@ -61,6 +76,11 @@ int dlopen_bpf(void) { void *dl; int r; + ELF_NOTE_DLOPEN("bpf", + "Support firewalling and sandboxing with BPF", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libbpf.so.1", "libbpf.so.0"); + DISABLE_WARNING_DEPRECATED_DECLARATIONS; dl = dlopen("libbpf.so.1", RTLD_LAZY); @@ -88,6 +108,8 @@ int dlopen_bpf(void) { DLSYM_ARG(bpf_probe_prog_type) #endif ); + + /* NB: we don't try to load bpf_object__next_map() on old versions */ } else { log_debug("Loaded 'libbpf.so.1' via dlopen()"); @@ -96,11 +118,13 @@ int dlopen_bpf(void) { dl, LOG_DEBUG, #if MODERN_LIBBPF DLSYM_ARG(bpf_map_create), - DLSYM_ARG(libbpf_probe_bpf_prog_type) + DLSYM_ARG(libbpf_probe_bpf_prog_type), + DLSYM_ARG(bpf_object__next_map) #else /* These symbols did not exist in old libbpf, hence we cannot type check them */ DLSYM_ARG_FORCE(bpf_map_create), - DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type) + DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type), + DLSYM_ARG_FORCE(bpf_object__next_map) #endif ); } @@ -111,28 +135,41 @@ int dlopen_bpf(void) { dl, LOG_DEBUG, DLSYM_ARG(bpf_link__destroy), DLSYM_ARG(bpf_link__fd), + DLSYM_ARG(bpf_link__open), + DLSYM_ARG(bpf_link__pin), DLSYM_ARG(bpf_map__fd), DLSYM_ARG(bpf_map__name), + DLSYM_ARG(bpf_map__set_inner_map_fd), DLSYM_ARG(bpf_map__set_max_entries), - DLSYM_ARG(bpf_map_update_elem), + DLSYM_ARG(bpf_map__set_pin_path), DLSYM_ARG(bpf_map_delete_elem), - DLSYM_ARG(bpf_map__set_inner_map_fd), - DLSYM_ARG(bpf_object__open_skeleton), - DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_map_get_fd_by_id), + DLSYM_ARG(bpf_map_lookup_elem), + DLSYM_ARG(bpf_map_update_elem), DLSYM_ARG(bpf_object__attach_skeleton), - DLSYM_ARG(bpf_object__detach_skeleton), DLSYM_ARG(bpf_object__destroy_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__load_skeleton), + DLSYM_ARG(bpf_object__name), + DLSYM_ARG(bpf_object__open_skeleton), + DLSYM_ARG(bpf_object__pin_maps), #if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach), DLSYM_ARG(bpf_program__attach_cgroup), DLSYM_ARG(bpf_program__attach_lsm), #else /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ + DLSYM_ARG_FORCE(bpf_program__attach), DLSYM_ARG_FORCE(bpf_program__attach_cgroup), DLSYM_ARG_FORCE(bpf_program__attach_lsm), #endif DLSYM_ARG(bpf_program__name), + DLSYM_ARG(libbpf_get_error), DLSYM_ARG(libbpf_set_print), - DLSYM_ARG(libbpf_get_error)); + DLSYM_ARG(ring_buffer__epoll_fd), + DLSYM_ARG(ring_buffer__free), + DLSYM_ARG(ring_buffer__new), + DLSYM_ARG(ring_buffer__poll)); if (r < 0) return r; @@ -144,6 +181,23 @@ int dlopen_bpf(void) { return r; } +int bpf_get_error_translated(const void *ptr) { + int r; + + r = sym_libbpf_get_error(ptr); + + switch (r) { + case -524: + /* Workaround for kernel bug, BPF returns an internal error instead of translating it, until + * it is fixed: + * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/errno.h?h=v6.9&id=a38297e3fb012ddfa7ce0321a7e5a8daeb1872b6#n27 + */ + return -EOPNOTSUPP; + default: + return r; + } +} + #else int dlopen_bpf(void) { diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h index 0750abc..df12d08 100644 --- a/src/shared/bpf-dlopen.h +++ b/src/shared/bpf-dlopen.h @@ -7,27 +7,44 @@ #include #include "bpf-compat.h" +#include "dlfcn-util.h" -extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); -extern struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); -extern int (*sym_bpf_link__fd)(const struct bpf_link *); -extern int (*sym_bpf_link__destroy)(struct bpf_link *); -extern int (*sym_bpf_map__fd)(const struct bpf_map *); -extern const char* (*sym_bpf_map__name)(const struct bpf_map *); -extern int (*sym_bpf_map__set_max_entries)(struct bpf_map *, __u32); -extern int (*sym_bpf_map_update_elem)(int, const void *, const void *, __u64); -extern int (*sym_bpf_map_delete_elem)(int, const void *); -extern int (*sym_bpf_map__set_inner_map_fd)(struct bpf_map *, int); +DLSYM_PROTOTYPE(bpf_link__destroy); +DLSYM_PROTOTYPE(bpf_link__fd); +DLSYM_PROTOTYPE(bpf_link__open); +DLSYM_PROTOTYPE(bpf_link__pin); +DLSYM_PROTOTYPE(bpf_map__fd); +DLSYM_PROTOTYPE(bpf_map__name); +DLSYM_PROTOTYPE(bpf_map__set_inner_map_fd); +DLSYM_PROTOTYPE(bpf_map__set_max_entries); +DLSYM_PROTOTYPE(bpf_map__set_pin_path); +DLSYM_PROTOTYPE(bpf_map_delete_elem); +DLSYM_PROTOTYPE(bpf_map_get_fd_by_id); +DLSYM_PROTOTYPE(bpf_map_lookup_elem); +DLSYM_PROTOTYPE(bpf_map_update_elem); /* The *_skeleton APIs are autogenerated by bpftool, the targets can be found * in ./build/src/core/bpf/socket_bind/socket-bind.skel.h */ -extern int (*sym_bpf_object__open_skeleton)(struct bpf_object_skeleton *, const struct bpf_object_open_opts *); -extern int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *); -extern int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *); -extern void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *); -extern void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *); -extern const char* (*sym_bpf_program__name)(const struct bpf_program *); -extern libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t); -extern long (*sym_libbpf_get_error)(const void *); +DLSYM_PROTOTYPE(bpf_object__attach_skeleton); +DLSYM_PROTOTYPE(bpf_object__destroy_skeleton); +DLSYM_PROTOTYPE(bpf_object__detach_skeleton); +DLSYM_PROTOTYPE(bpf_object__load_skeleton); +DLSYM_PROTOTYPE(bpf_object__name); +DLSYM_PROTOTYPE(bpf_object__open_skeleton); +DLSYM_PROTOTYPE(bpf_object__pin_maps); +DLSYM_PROTOTYPE(bpf_program__attach); +DLSYM_PROTOTYPE(bpf_program__attach_cgroup); +DLSYM_PROTOTYPE(bpf_program__attach_lsm); +DLSYM_PROTOTYPE(bpf_program__name); +DLSYM_PROTOTYPE(libbpf_set_print); +DLSYM_PROTOTYPE(ring_buffer__epoll_fd); +DLSYM_PROTOTYPE(ring_buffer__free); +DLSYM_PROTOTYPE(ring_buffer__new); +DLSYM_PROTOTYPE(ring_buffer__poll); + +/* libbpf sometimes returns error codes that make sense only in the kernel, like 524 for EOPNOTSUPP. Use + * this helper instead of libbpf_get_error() to ensure some of the known ones are translated into errnos + * we understand. */ +int bpf_get_error_translated(const void *ptr); #endif diff --git a/src/shared/bpf-link.c b/src/shared/bpf-link.c index fea49b2..77f6a4e 100644 --- a/src/shared/bpf-link.c +++ b/src/shared/bpf-link.c @@ -16,7 +16,7 @@ bool bpf_can_link_program(struct bpf_program *prog) { link = sym_bpf_program__attach_cgroup(prog, /*cgroup_fd=*/-1); /* EBADF indicates that bpf_link is supported by kernel. */ - return sym_libbpf_get_error(link) == -EBADF; + return bpf_get_error_translated(link) == -EBADF; } int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *link) { @@ -25,7 +25,7 @@ int bpf_serialize_link(FILE *f, FDSet *fds, const char *key, struct bpf_link *li if (!link) return -ENOENT; - if (sym_libbpf_get_error(link) != 0) + if (bpf_get_error_translated(link) != 0) return -EINVAL; return serialize_fd(f, fds, key, sym_bpf_link__fd(link)); diff --git a/src/shared/bpf-program.c b/src/shared/bpf-program.c index bbdd4f6..ac92ec8 100644 --- a/src/shared/bpf-program.c +++ b/src/shared/bpf-program.c @@ -321,7 +321,7 @@ int bpf_map_new( /* 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) */ + * https://docs.kernel.org/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 : '_'; diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index 2ed6bf2..f6055a8 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -262,24 +262,49 @@ static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args } typedef struct BtrfsForeachIterator { - const void *p; - size_t i; + const struct btrfs_ioctl_search_args *args; + size_t offset; + unsigned index; + struct btrfs_ioctl_search_header *header; + const void **body; } BtrfsForeachIterator; +static int btrfs_iterate(BtrfsForeachIterator *i) { + assert(i); + assert(i->args); + assert(i->header); + assert(i->body); + + if (i->index >= i->args->key.nr_items) + return 0; /* end */ + + assert_cc(BTRFS_SEARCH_ARGS_BUFSIZE >= sizeof(struct btrfs_ioctl_search_header)); + if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header)) + return -EBADMSG; + + struct btrfs_ioctl_search_header h; + memcpy(&h, (const uint8_t*) i->args->buf + i->offset, sizeof(struct btrfs_ioctl_search_header)); + + if (i->offset > BTRFS_SEARCH_ARGS_BUFSIZE - sizeof(struct btrfs_ioctl_search_header) - h.len) + return -EBADMSG; + + *i->body = (const uint8_t*) i->args->buf + i->offset + sizeof(struct btrfs_ioctl_search_header); + *i->header = h; + i->offset += sizeof(struct btrfs_ioctl_search_header) + h.len; + i->index++; + + return 1; +} + /* Iterates through a series of struct btrfs_file_extent_item elements. They are unfortunately not aligned, * hence we copy out the header from them */ -#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(sh, body, args) \ +#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(_sh, _body, _args) \ for (BtrfsForeachIterator iterator = { \ - .p = ({ \ - memcpy(&(sh), (args).buf, sizeof(struct btrfs_ioctl_search_header)); \ - (body) = (const void*) ((const uint8_t*) (args).buf + sizeof(struct btrfs_ioctl_search_header)); \ - (args).buf; \ - }), \ + .args = &(_args), \ + .header = &(_sh), \ + .body = &(_body), \ }; \ - iterator.i < (args).key.nr_items; \ - iterator.i++, \ - memcpy(&(sh), iterator.p = (const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header) + (sh).len, sizeof(struct btrfs_ioctl_search_header)), \ - (body) = (const void*) ((const uint8_t*) iterator.p + sizeof(struct btrfs_ioctl_search_header))) + btrfs_iterate(&iterator) > 0; ) int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { struct btrfs_ioctl_search_args args = { @@ -1162,6 +1187,8 @@ static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_sub if (n_old_qgroups <= 0) /* Nothing to copy */ return n_old_qgroups; + assert(old_qgroups); /* Coverity gets confused by the macro iterator allocating this, add a hint */ + r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); if (r == -ENXIO) /* We have no parent, hence nothing to copy. */ @@ -1785,6 +1812,24 @@ int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_i return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); } +int btrfs_subvol_make_default(const char *path) { + _cleanup_close_ int fd = -EBADF; + uint64_t id; + int r; + + assert(path); + + fd = open(path, O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + r = btrfs_subvol_get_id_fd(fd, &id); + if (r < 0) + return r; + + return RET_NERRNO(ioctl(fd, BTRFS_IOC_DEFAULT_SUBVOL, &id)); +} + int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { struct btrfs_ioctl_search_args args = { diff --git a/src/shared/btrfs-util.h b/src/shared/btrfs-util.h index cd80903..6108a26 100644 --- a/src/shared/btrfs-util.h +++ b/src/shared/btrfs-util.h @@ -108,6 +108,8 @@ int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); +int btrfs_subvol_make_default(const char *path); + int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); diff --git a/src/shared/bus-map-properties.c b/src/shared/bus-map-properties.c index 809759d..a4833a5 100644 --- a/src/shared/bus-map-properties.c +++ b/src/shared/bus-map-properties.c @@ -8,21 +8,12 @@ int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { sd_id128_t *p = userdata; - const void *v; - size_t n; int r; - r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); + r = bus_message_read_id128(m, p); if (r < 0) return bus_log_parse_error_debug(r); - if (n == 0) - *p = SD_ID128_NULL; - else if (n == 16) - memcpy((*p).bytes, v, n); - else - return -EINVAL; - return 0; } diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c index 904b897..0382d0b 100644 --- a/src/shared/bus-polkit.c +++ b/src/shared/bus-polkit.c @@ -4,10 +4,11 @@ #include "bus-message.h" #include "bus-polkit.h" #include "bus-util.h" +#include "process-util.h" #include "strv.h" #include "user-util.h" -static int check_good_user(sd_bus_message *m, uid_t good_user) { +static int bus_message_check_good_user(sd_bus_message *m, uid_t good_user) { _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; uid_t sender_uid; int r; @@ -15,7 +16,7 @@ static int check_good_user(sd_bus_message *m, uid_t good_user) { assert(m); if (good_user == UID_INVALID) - return 0; + return false; r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); if (r < 0) @@ -47,14 +48,10 @@ static int bus_message_append_strv_key_value(sd_bus_message *m, const char **l) return r; } - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - return r; + return sd_bus_message_close_container(m); } -static int bus_message_new_polkit_auth_call( +static int bus_message_new_polkit_auth_call_for_bus( sd_bus_message *m, const char *action, const char **details, @@ -102,7 +99,6 @@ static int bus_message_new_polkit_auth_call( int bus_test_polkit( sd_bus_message *call, - int capability, const char *action, const char **details, uid_t good_user, @@ -116,11 +112,11 @@ int bus_test_polkit( /* Tests non-interactively! */ - r = check_good_user(call, good_user); + r = bus_message_check_good_user(call, good_user); if (r != 0) return r; - r = sd_bus_query_sender_privilege(call, capability); + r = sd_bus_query_sender_privilege(call, -1); if (r < 0) return r; if (r > 0) @@ -130,7 +126,7 @@ int bus_test_polkit( _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL, *reply = NULL; int authorized = false, challenge = false; - r = bus_message_new_polkit_auth_call(call, action, details, /* interactive = */ false, &request); + r = bus_message_new_polkit_auth_call_for_bus(call, action, details, /* interactive = */ false, &request); if (r < 0) return r; @@ -189,18 +185,21 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQueryAction*, async_polkit_query_action_f typedef struct AsyncPolkitQuery { unsigned n_ref; - AsyncPolkitQueryAction *action; + AsyncPolkitQueryAction *action; /* action currently being processed */ - sd_bus_message *request; + sd_bus *bus; + sd_bus_message *request; /* the original bus method call that triggered the polkit auth, NULL in case of varlink */ sd_bus_slot *slot; + Varlink *link; /* the original varlink method call that triggered the polkit auth, NULL in case of bus */ Hashmap *registry; sd_event_source *defer_event_source; - LIST_HEAD(AsyncPolkitQueryAction, authorized_actions); - AsyncPolkitQueryAction *denied_action; - AsyncPolkitQueryAction *error_action; - sd_bus_error error; + LIST_HEAD(AsyncPolkitQueryAction, authorized_actions); /* actions we successfully were authorized for */ + AsyncPolkitQueryAction *denied_action; /* if we received denial for an action, it's this one */ + AsyncPolkitQueryAction *absent_action; /* If polkit was absent for some action, it's this one */ + AsyncPolkitQueryAction *error_action; /* if we encountered any other error, it's this one */ + sd_bus_error error; /* the precise error, in case error_action is set */ } AsyncPolkitQuery; static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { @@ -209,11 +208,18 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { sd_bus_slot_unref(q->slot); - if (q->registry && q->request) - hashmap_remove(q->registry, q->request); + if (q->registry) { + if (q->request) + hashmap_remove(q->registry, q->request); + if (q->link) + hashmap_remove(q->registry, q->link); + } sd_bus_message_unref(q->request); + sd_bus_unref(q->bus); + varlink_unref(q->link); + async_polkit_query_action_free(q->action); sd_event_source_disable_unref(q->defer_event_source); @@ -221,6 +227,7 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { LIST_CLEAR(authorized, q->authorized_actions, async_polkit_query_action_free); async_polkit_query_action_free(q->denied_action); + async_polkit_query_action_free(q->absent_action); async_polkit_query_action_free(q->error_action); sd_bus_error_free(&q->error); @@ -231,6 +238,14 @@ static AsyncPolkitQuery *async_polkit_query_free(AsyncPolkitQuery *q) { DEFINE_PRIVATE_TRIVIAL_REF_UNREF_FUNC(AsyncPolkitQuery, async_polkit_query, async_polkit_query_free); DEFINE_TRIVIAL_CLEANUP_FUNC(AsyncPolkitQuery*, async_polkit_query_unref); +DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR( + async_polkit_query_hash_ops, + void, + trivial_hash_func, + trivial_compare_func, + AsyncPolkitQuery, + async_polkit_query_unref); + static int async_polkit_defer(sd_event_source *s, void *userdata) { AsyncPolkitQuery *q = ASSERT_PTR(userdata); @@ -252,23 +267,34 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) { /* Processing of a PolicyKit checks is canceled on the first auth. error. */ assert(!q->denied_action); + assert(!q->absent_action); assert(!q->error_action); assert(!sd_bus_error_is_set(&q->error)); - assert(q->action); - a = TAKE_PTR(q->action); + a = ASSERT_PTR(TAKE_PTR(q->action)); if (sd_bus_message_is_method_error(reply, NULL)) { const sd_bus_error *e; e = sd_bus_message_get_error(reply); - if (bus_error_is_unknown_service(e)) - /* Treat no PK available as access denied */ + if (bus_error_is_unknown_service(e)) { + /* If PK is absent, then store this away, as it depends on the callers flags whether + * this means deny or allow */ + log_debug("Polkit found to be unavailable while trying to authorize action '%s'.", a->action); + q->absent_action = TAKE_PTR(a); + } else if (sd_bus_error_has_names( + e, + "org.freedesktop.PolicyKit1.Error.Failed", + "org.freedesktop.PolicyKit1.Error.Cancelled", + "org.freedesktop.PolicyKit1.Error.NotAuthorized")) { + /* Treat some of the well-known PK errors as denial. */ + log_debug("Polkit authorization for action '%s' failed with an polkit error: %s", a->action, e->name); q->denied_action = TAKE_PTR(a); - else { + } else { /* Save error from polkit reply, so it can be returned when the same authorization * is attempted for second time */ + log_debug("Polkit authorization for action '%s' failed with an unexpected error: %s", a->action, e->name); q->error_action = TAKE_PTR(a); r = sd_bus_error_copy(&q->error, e); if (r == -ENOMEM) @@ -284,13 +310,17 @@ static int async_polkit_read_reply(sd_bus_message *reply, AsyncPolkitQuery *q) { if (r < 0) return r; - if (authorized) + if (authorized) { + log_debug("Polkit authorization for action '%s' succeeded.", a->action); LIST_PREPEND(authorized, q->authorized_actions, TAKE_PTR(a)); - else if (challenge) { + } else if (challenge) { + log_debug("Polkit authorization for action requires '%s' interactive authentication, which we didn't allow.", a->action); q->error_action = TAKE_PTR(a); sd_bus_error_set_const(&q->error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); - } else + } else { + log_debug("Polkit authorization for action '%s' denied.", a->action); q->denied_action = TAKE_PTR(a); + } return 0; } @@ -317,7 +347,7 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (!q->defer_event_source) { r = sd_event_add_defer( - sd_bus_get_event(sd_bus_message_get_bus(reply)), + sd_bus_get_event(q->bus), &q->defer_event_source, async_polkit_defer, q); @@ -333,13 +363,21 @@ static int async_polkit_process_reply(sd_bus_message *reply, AsyncPolkitQuery *q if (r < 0) return r; - r = sd_bus_message_rewind(q->request, true); - if (r < 0) - return r; + if (q->request) { + r = sd_bus_message_rewind(q->request, true); + if (r < 0) + return r; - r = sd_bus_enqueue_for_read(sd_bus_message_get_bus(q->request), q->request); - if (r < 0) - return r; + r = sd_bus_enqueue_for_read(q->bus, q->request); + if (r < 0) + return r; + } + + if (q->link) { + r = varlink_dispatch_again(q->link); + if (r < 0) + return r; + } return 1; } @@ -353,35 +391,54 @@ static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_e r = async_polkit_process_reply(reply, q); if (r < 0) { log_debug_errno(r, "Processing asynchronous PolicyKit reply failed, ignoring: %m"); - (void) sd_bus_reply_method_errno(q->request, r, NULL); + if (q->request) + (void) sd_bus_reply_method_errno(q->request, r, NULL); + if (q->link) + (void) varlink_error_errno(q->link, r); async_polkit_query_unref(q); } return r; } +static bool async_polkit_query_have_action( + AsyncPolkitQuery *q, + const char *action, + const char **details) { + + assert(q); + assert(action); + + LIST_FOREACH(authorized, a, q->authorized_actions) + if (streq(a->action, action) && strv_equal(a->details, (char**) details)) + return true; + + return false; +} + static int async_polkit_query_check_action( AsyncPolkitQuery *q, const char *action, const char **details, + PolkitFlags flags, sd_bus_error *ret_error) { assert(q); assert(action); - assert(ret_error); - LIST_FOREACH(authorized, a, q->authorized_actions) - if (streq(a->action, action) && strv_equal(a->details, (char**) details)) - return 1; + if (async_polkit_query_have_action(q, action, details)) + return 1; /* Allow! */ if (q->error_action && streq(q->error_action->action, action)) return sd_bus_error_copy(ret_error, &q->error); if (q->denied_action && streq(q->denied_action->action, action)) - return -EACCES; + return -EACCES; /* Deny! */ - return 0; -} + if (q->absent_action) + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 /* Allow! */ : -EACCES /* Deny! */; + return 0; /* no reply yet */ +} #endif /* bus_verify_polkit_async() handles verification of D-Bus calls with polkit. Because the polkit API @@ -465,24 +522,24 @@ static int async_polkit_query_check_action( * <- async_polkit_defer(q) */ -int bus_verify_polkit_async( +int bus_verify_polkit_async_full( sd_bus_message *call, - int capability, const char *action, const char **details, - bool interactive, uid_t good_user, + PolkitFlags flags, Hashmap **registry, - sd_bus_error *ret_error) { + sd_bus_error *error) { int r; assert(call); assert(action); assert(registry); - assert(ret_error); - r = check_good_user(call, good_user); + log_debug("Trying to acquire polkit authentication for '%s'.", action); + + r = bus_message_check_good_user(call, good_user); if (r != 0) return r; @@ -493,20 +550,25 @@ int bus_verify_polkit_async( /* This is a repeated invocation of this function, hence let's check if we've already got * a response from polkit for this action */ if (q) { - r = async_polkit_query_check_action(q, action, details, ret_error); - if (r != 0) + r = async_polkit_query_check_action(q, action, details, flags, error); + if (r != 0) { + log_debug("Found matching previous polkit authentication for '%s'.", action); return r; + } } #endif - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - if (r > 0) - return 1; + if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) { + /* Don't query PK if client is privileged */ + r = sd_bus_query_sender_privilege(call, /* capability= */ -1); + if (r < 0) + return r; + if (r > 0) + return 1; + } #if ENABLE_POLKIT - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + bool interactive = FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE); int c = sd_bus_message_get_allow_interactive_authorization(call); if (c < 0) @@ -514,11 +576,8 @@ int bus_verify_polkit_async( if (c > 0) interactive = true; - r = hashmap_ensure_allocated(registry, NULL); - if (r < 0) - return r; - - r = bus_message_new_polkit_auth_call(call, action, details, interactive, &pk); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + r = bus_message_new_polkit_auth_call_for_bus(call, action, details, interactive, &pk); if (r < 0) return r; @@ -530,6 +589,7 @@ int bus_verify_polkit_async( *q = (AsyncPolkitQuery) { .n_ref = 1, .request = sd_bus_message_ref(call), + .bus = sd_bus_ref(sd_bus_message_get_bus(call)), }; } @@ -546,7 +606,7 @@ int bus_verify_polkit_async( return -ENOMEM; if (!q->registry) { - r = hashmap_put(*registry, call, q); + r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, call, q); if (r < 0) return r; @@ -560,16 +620,264 @@ int bus_verify_polkit_async( TAKE_PTR(q); return 0; +#else + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; #endif +} - return -EACCES; +static int varlink_check_good_user(Varlink *link, uid_t good_user) { + int r; + + assert(link); + + if (good_user == UID_INVALID) + return false; + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + return good_user == peer_uid; +} + +static int varlink_check_peer_privilege(Varlink *link) { + int r; + + assert(link); + + uid_t peer_uid; + r = varlink_get_peer_uid(link, &peer_uid); + if (r < 0) + return r; + + uid_t our_uid = getuid(); + return peer_uid == our_uid || + (our_uid != 0 && peer_uid == 0); +} + +#if ENABLE_POLKIT +static int bus_message_new_polkit_auth_call_for_varlink( + sd_bus *bus, + Varlink *link, + const char *action, + const char **details, + bool interactive, + sd_bus_message **ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert(bus); + assert(link); + assert(action); + assert(ret); + + r = varlink_get_peer_pidref(link, &pidref); + if (r < 0) + return r; + if (r == 0) /* if we couldn't get a pidfd this returns == 0 */ + return log_debug_errno(SYNTHETIC_ERRNO(EPERM), "Failed to get peer pidfd, cannot securely authenticate."); + + uid_t uid; + r = varlink_get_peer_uid(link, &uid); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &c, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + c, + "(sa{sv})s", + "unix-process", 2, + "pidfd", "h", (uint32_t) pidref.fd, + "uid", "i", (int32_t) uid, + action); + if (r < 0) + return r; + + r = bus_message_append_strv_key_value(c, details); + if (r < 0) + return r; + + r = sd_bus_message_append(c, "us", interactive, NULL); + if (r < 0) + return r; + + *ret = TAKE_PTR(c); + return 0; +} + +static bool varlink_allow_interactive_authentication(Varlink *link) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + int r; + + assert(link); + + /* We look for the allowInteractiveAuthentication field in the message currently being dispatched, + * always under the same name. */ + + r = varlink_get_current_parameters(link, &v); + if (r < 0) { + log_debug_errno(r, "Unable to query current parameters: %m"); + return false; + } + + JsonVariant *b; + b = json_variant_by_key(v, "allowInteractiveAuthentication"); + if (b) { + if (json_variant_is_boolean(b)) + return json_variant_boolean(b); + + log_debug("Incoming 'allowInteractiveAuthentication' field is not a boolean, ignoring."); + } + + return false; +} +#endif + +int varlink_verify_polkit_async_full( + Varlink *link, + sd_bus *bus, + const char *action, + const char **details, + uid_t good_user, + PolkitFlags flags, + Hashmap **registry) { + + int r; + + assert(link); + assert(registry); + + log_debug("Trying to acquire polkit authentication for '%s'.", action); + + /* This is the same as bus_verify_polkit_async_full(), but authenticates the peer of a varlink + * connection rather than the sender of a bus message. */ + + r = varlink_check_good_user(link, good_user); + if (r != 0) + return r; + + if (!FLAGS_SET(flags, POLKIT_ALWAYS_QUERY)) { + r = varlink_check_peer_privilege(link); + if (r != 0) + return r; + } + +#if ENABLE_POLKIT + _cleanup_(async_polkit_query_unrefp) AsyncPolkitQuery *q = NULL; + + q = async_polkit_query_ref(hashmap_get(*registry, link)); + /* This is a repeated invocation of this function, hence let's check if we've already got + * a response from polkit for this action */ + if (q) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + r = async_polkit_query_check_action(q, action, details, flags, &error); + if (r != 0) + log_debug("Found matching previous polkit authentication for '%s'.", action); + if (r < 0) { + /* Reply with a nice error */ + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED)) + (void) varlink_error(link, VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED, NULL); + else if (ERRNO_IS_NEG_PRIVILEGE(r)) + (void) varlink_error(link, VARLINK_ERROR_PERMISSION_DENIED, NULL); + + return r; + } + if (r > 0) + return r; + } + + _cleanup_(sd_bus_unrefp) sd_bus *mybus = NULL; + if (!bus) { + r = sd_bus_open_system(&mybus); + if (r < 0) + return r; + + r = sd_bus_attach_event(mybus, varlink_get_event(link), 0); + if (r < 0) + return r; + + bus = mybus; + } + + bool interactive = + FLAGS_SET(flags, POLKIT_ALLOW_INTERACTIVE) || + varlink_allow_interactive_authentication(link); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + r = bus_message_new_polkit_auth_call_for_varlink(bus, link, action, details, interactive, &pk); + if (r < 0) + return r; + + if (!q) { + q = new(AsyncPolkitQuery, 1); + if (!q) + return -ENOMEM; + + *q = (AsyncPolkitQuery) { + .n_ref = 1, + .link = varlink_ref(link), + .bus = sd_bus_ref(bus), + }; + } + + assert(!q->action); + q->action = new(AsyncPolkitQueryAction, 1); + if (!q->action) + return -ENOMEM; + + *q->action = (AsyncPolkitQueryAction) { + .action = strdup(action), + .details = strv_copy((char**) details), + }; + if (!q->action->action || !q->action->details) + return -ENOMEM; + + if (!q->registry) { + r = hashmap_ensure_put(registry, &async_polkit_query_hash_ops, link, q); + if (r < 0) + return r; + + q->registry = *registry; + } + + r = sd_bus_call_async(bus, &q->slot, pk, async_polkit_callback, q, 0); + if (r < 0) + return r; + + TAKE_PTR(q); + + return 0; +#else + return FLAGS_SET(flags, POLKIT_DEFAULT_ALLOW) ? 1 : -EACCES; +#endif } -Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry) { +bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry) { + assert(link); + assert(action); + assert(registry); + + /* Checks if we already have acquired some action previously */ + #if ENABLE_POLKIT - return hashmap_free_with_destructor(registry, async_polkit_query_unref); + AsyncPolkitQuery *q = hashmap_get(*registry, link); + if (!q) + return false; + + return async_polkit_query_have_action(q, action, details); #else - assert(hashmap_isempty(registry)); - return hashmap_free(registry); + return false; #endif } diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h index e2a3b7e..f3741b2 100644 --- a/src/shared/bus-polkit.h +++ b/src/shared/bus-polkit.h @@ -4,8 +4,33 @@ #include "sd-bus.h" #include "hashmap.h" +#include "user-util.h" +#include "varlink.h" -int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); +typedef enum PolkitFLags { + POLKIT_ALLOW_INTERACTIVE = 1 << 0, /* Allow interactive auth (typically not required, because can be derived from bus message/link automatically) */ + POLKIT_ALWAYS_QUERY = 1 << 1, /* Query polkit even if client is privileged */ + POLKIT_DEFAULT_ALLOW = 1 << 2, /* If polkit is not around, assume "allow" rather than the usual "deny" */ +} PolkitFlags; -int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); -Hashmap *bus_verify_polkit_async_registry_free(Hashmap *registry); +int bus_test_polkit(sd_bus_message *call, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); + +int bus_verify_polkit_async_full(sd_bus_message *call, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry, sd_bus_error *error); +static inline int bus_verify_polkit_async(sd_bus_message *call, const char *action, const char **details, Hashmap **registry, sd_bus_error *error) { + return bus_verify_polkit_async_full(call, action, details, UID_INVALID, 0, registry, error); +} + +int varlink_verify_polkit_async_full(Varlink *link, sd_bus *bus, const char *action, const char **details, uid_t good_user, PolkitFlags flags, Hashmap **registry); +static inline int varlink_verify_polkit_async(Varlink *link, sd_bus *bus, const char *action, const char **details, Hashmap **registry) { + return varlink_verify_polkit_async_full(link, bus, action, details, UID_INVALID, 0, registry); +} + +/* A JsonDispatch initializer that makes sure the allowInteractiveAuthentication boolean field we want for + * polkit support in Varlink calls is ignored while regular dispatching (and does not result in errors + * regarding unexpected fields) */ +#define VARLINK_DISPATCH_POLKIT_FIELD { \ + .name = "allowInteractiveAuthentication", \ + .type = JSON_VARIANT_BOOLEAN, \ + } + +bool varlink_has_polkit_action(Varlink *link, const char *action, const char **details, Hashmap **registry); diff --git a/src/shared/bus-print-properties.c b/src/shared/bus-print-properties.c index 6704e1e..99b1cc7 100644 --- a/src/shared/bus-print-properties.c +++ b/src/shared/bus-print-properties.c @@ -164,9 +164,11 @@ static int bus_print_property(const char *name, const char *expected_value, sd_b bus_print_property_value(name, expected_value, flags, "[not set]"); - else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", "MemoryHigh", "MemoryMax", "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") && + else if ((ENDSWITH_SET(name, "MemoryLow", "MemoryMin", + "MemoryHigh", "MemoryMax", + "MemorySwapMax", "MemoryZSwapMax", "MemoryLimit") && u == CGROUP_LIMIT_MAX) || - (STR_IN_SET(name, "TasksMax", "DefaultTasksMax") && u == UINT64_MAX) || + (endswith(name, "TasksMax") && u == UINT64_MAX) || (startswith(name, "Limit") && u == UINT64_MAX) || (startswith(name, "DefaultLimit") && u == UINT64_MAX)) diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 50de989..da83422 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -2,6 +2,7 @@ #include "af-list.h" #include "alloc-util.h" +#include "bus-common-errors.h" #include "bus-error.h" #include "bus-locator.h" #include "bus-unit-util.h" @@ -494,14 +495,14 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char * if (r == 0) break; if (isempty(tuple)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &nfproto_str, &table, &set); if (r == -ENOMEM) return log_oom(); if (r != 4 || !isempty(q)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); assert(source_str); assert(nfproto_str); @@ -510,11 +511,11 @@ static int bus_append_nft_set(sd_bus_message *m, const char *field, const char * source = nft_set_source_from_string(source_str); if (!IN_SET(source, NFT_SET_SOURCE_CGROUP, NFT_SET_SOURCE_USER, NFT_SET_SOURCE_GROUP)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); nfproto = nfproto_from_string(nfproto_str); if (nfproto < 0 || !nft_identifier_valid(table) || !nft_identifier_valid(set)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s", field); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse %s.", field); r = sd_bus_message_append(m, "(iiss)", source, nfproto, table, set); if (r < 0) @@ -562,6 +563,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons if (STR_IN_SET(field, "CPUAccounting", "MemoryAccounting", + "MemoryZSwapWriteback", "IOAccounting", "BlockIOAccounting", "TasksAccounting", @@ -678,8 +680,7 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons else { r = parse_permyriad_unbounded(eq); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ERANGE), - "CPU quota too small."); + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "CPU quota too small."); if (r < 0) return log_error_errno(r, "CPU quota '%s' invalid.", eq); @@ -1213,7 +1214,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con _cleanup_free_ void *decoded = NULL; size_t decoded_size; - r = unbase64mem(p, SIZE_MAX, &decoded, &decoded_size); + r = unbase64mem(p, &decoded, &decoded_size); if (r < 0) return log_error_errno(r, "Failed to base64 decode encrypted credential: %m"); @@ -1400,7 +1401,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con _cleanup_free_ void *decoded = NULL; size_t sz; - r = unbase64mem(eq, SIZE_MAX, &decoded, &sz); + r = unbase64mem(eq, &decoded, &sz); if (r < 0) return log_error_errno(r, "Failed to decode base64 data '%s': %m", eq); @@ -1787,11 +1788,11 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_string(m, "RootHashPath", eq); /* We have a roothash to decode, eg: RootHash=012345789abcdef */ - r = unhexmem(eq, strlen(eq), &roothash_decoded, &roothash_decoded_size); + r = unhexmem(eq, &roothash_decoded, &roothash_decoded_size); if (r < 0) return log_error_errno(r, "Failed to decode RootHash= '%s': %m", eq); if (roothash_decoded_size < sizeof(sd_id128_t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short: %m", eq); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RootHash= '%s' is too short.", eq); return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size); } @@ -1806,10 +1807,10 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_string(m, "RootHashSignaturePath", eq); if (!(value = startswith(eq, "base64:"))) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:'.", eq); /* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */ - r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size); + r = unbase64mem(value, &roothash_sig_decoded, &roothash_sig_decoded_size); if (r < 0) return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq); @@ -1894,7 +1895,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &first, &second); if (r < 0) return log_error_errno(r, "Failed to parse MountImages= property: %s", eq); if (r == 0) @@ -1926,7 +1927,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con for (;;) { _cleanup_free_ char *partition = NULL, *mount_options = NULL; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return log_error_errno(r, "Failed to parse MountImages= property: %s", eq); if (r == 0) @@ -2027,7 +2028,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con for (;;) { _cleanup_free_ char *partition = NULL, *mount_options = NULL; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return log_error_errno(r, "Failed to parse ExtensionImages= property: %s", eq); if (r == 0) @@ -2088,7 +2089,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con break; const char *t = tuple; - r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + r = extract_many_words(&t, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination); if (r <= 0) return log_error_errno(r ?: SYNTHETIC_ERRNO(EINVAL), "Failed to parse argument: %m"); @@ -2449,6 +2450,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons "Transparent", "Broadcast", "PassCredentials", + "PassFileDescriptorsToExec", "PassSecurity", "PassPacketInfo", "ReusePort", @@ -2643,6 +2645,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const if (unit_dependency_from_string(field) >= 0 || STR_IN_SET(field, "Documentation", "RequiresMountsFor", + "WantsMountsFor", "Markers")) return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE); @@ -2819,13 +2822,13 @@ int bus_append_unit_property_assignment_many(sd_bus_message *m, UnitType t, char return 0; } -int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref) { +int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd) { assert(m); if (!pidref_is_set(pidref)) return -ESRCH; - if (pidref->fd >= 0) + if (pidref->fd >= 0 && allow_pidfd) return sd_bus_message_append( m, "(sv)", "PIDFDs", "ah", 1, pidref->fd); @@ -2936,3 +2939,107 @@ int bus_service_manager_reload(sd_bus *bus) { return 0; } + +typedef struct UnitFreezer { + char *name; + sd_bus *bus; +} UnitFreezer; + +/* Wait for 60 seconds at maximum for freezer operation */ +#define FREEZE_BUS_CALL_TIMEOUT (60 * USEC_PER_SEC) + +UnitFreezer* unit_freezer_free(UnitFreezer *f) { + if (!f) + return NULL; + + free(f->name); + sd_bus_flush_close_unref(f->bus); + + return mfree(f); +} + +int unit_freezer_new(const char *name, UnitFreezer **ret) { + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + int r; + + assert(name); + assert(ret); + + f = new(UnitFreezer, 1); + if (!f) + return log_oom(); + + *f = (UnitFreezer) { + .name = strdup(name), + }; + if (!f->name) + return log_oom(); + + r = bus_connect_system_systemd(&f->bus); + if (r < 0) + return log_error_errno(r, "Failed to open connection to systemd: %m"); + + (void) sd_bus_set_method_call_timeout(f->bus, FREEZE_BUS_CALL_TIMEOUT); + + *ret = TAKE_PTR(f); + return 0; +} + +static int unit_freezer_action(UnitFreezer *f, bool freeze) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(f); + assert(f->name); + assert(f->bus); + + r = bus_call_method(f->bus, bus_systemd_mgr, + freeze ? "FreezeUnit" : "ThawUnit", + &error, + /* reply = */ NULL, + "s", + f->name); + if (r < 0) { + if (sd_bus_error_has_names(&error, + BUS_ERROR_NO_SUCH_UNIT, + BUS_ERROR_UNIT_INACTIVE, + SD_BUS_ERROR_NOT_SUPPORTED)) { + + log_debug_errno(r, "Skipping freezer for '%s': %s", f->name, bus_error_message(&error, r)); + return 0; + } + + return log_error_errno(r, "Failed to %s unit '%s': %s", + freeze ? "freeze" : "thaw", f->name, bus_error_message(&error, r)); + } + + log_info("Successfully %s unit '%s'.", freeze ? "froze" : "thawed", f->name); + return 1; +} + +int unit_freezer_freeze(UnitFreezer *f) { + return unit_freezer_action(f, true); +} + +int unit_freezer_thaw(UnitFreezer *f) { + return unit_freezer_action(f, false); +} + +int unit_freezer_new_freeze(const char *name, UnitFreezer **ret) { + _cleanup_(unit_freezer_freep) UnitFreezer *f = NULL; + int r; + + assert(name); + assert(ret); + + r = unit_freezer_new(name, &f); + if (r < 0) + return r; + + r = unit_freezer_freeze(f); + if (r < 0) + return r; + + *ret = TAKE_PTR(f); + return 0; +} diff --git a/src/shared/bus-unit-util.h b/src/shared/bus-unit-util.h index d52c847..ea4056c 100644 --- a/src/shared/bus-unit-util.h +++ b/src/shared/bus-unit-util.h @@ -26,7 +26,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_append_scope_pidref(sd_bus_message *m, const PidRef *pidref); +int bus_append_scope_pidref(sd_bus_message *m, const PidRef *pidref, bool allow_pidfd); int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet); @@ -35,3 +35,15 @@ int unit_load_state(sd_bus *bus, const char *name, char **load_state); int unit_info_compare(const UnitInfo *a, const UnitInfo *b); int bus_service_manager_reload(sd_bus *bus); + +typedef struct UnitFreezer UnitFreezer; + +UnitFreezer* unit_freezer_free(UnitFreezer *f); +DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFreezer*, unit_freezer_free); + +int unit_freezer_new(const char *name, UnitFreezer **ret); + +int unit_freezer_freeze(UnitFreezer *f); +int unit_freezer_thaw(UnitFreezer *f); + +int unit_freezer_new_freeze(const char *name, UnitFreezer **ret); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c index 4123152..30f9602 100644 --- a/src/shared/bus-util.c +++ b/src/shared/bus-util.c @@ -18,12 +18,17 @@ #include "bus-internal.h" #include "bus-label.h" #include "bus-util.h" +#include "capsule-util.h" +#include "chase.h" +#include "daemon-util.h" #include "data-fd-util.h" #include "fd-util.h" +#include "format-util.h" #include "memstream-util.h" #include "path-util.h" #include "socket-util.h" #include "stdio-util.h" +#include "uid-classification.h" static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { sd_event *e = ASSERT_PTR(userdata); @@ -128,8 +133,8 @@ int bus_event_loop_with_idle( if (r == 0 && !exiting && idle) { /* Inform the service manager that we are going down, so that it will queue all - * further start requests, instead of assuming we are already running. */ - sd_notify(false, "STOPPING=1"); + * further start requests, instead of assuming we are still running. */ + (void) sd_notify(false, NOTIFY_STOPPING); r = bus_async_unregister_and_exit(e, bus, name); if (r < 0) @@ -267,6 +272,131 @@ int bus_connect_user_systemd(sd_bus **ret_bus) { return 0; } +static int pin_capsule_socket(const char *capsule, const char *suffix, uid_t *ret_uid, gid_t *ret_gid) { + _cleanup_close_ int inode_fd = -EBADF; + _cleanup_free_ char *p = NULL; + struct stat st; + int r; + + assert(capsule); + assert(suffix); + assert(ret_uid); + assert(ret_gid); + + p = path_join("/run/capsules", capsule, suffix); + if (!p) + return -ENOMEM; + + /* We enter territory owned by the user, hence let's be paranoid about symlinks and ownership */ + r = chase(p, /* root= */ NULL, CHASE_SAFE|CHASE_PROHIBIT_SYMLINKS, /* ret_path= */ NULL, &inode_fd); + if (r < 0) + return r; + + if (fstat(inode_fd, &st) < 0) + return negative_errno(); + + /* Paranoid safety check */ + if (uid_is_system(st.st_uid) || gid_is_system(st.st_gid)) + return -EPERM; + + *ret_uid = st.st_uid; + *ret_gid = st.st_gid; + + return TAKE_FD(inode_fd); +} + +static int bus_set_address_capsule(sd_bus *bus, const char *capsule, const char *suffix, int *ret_pin_fd) { + _cleanup_close_ int inode_fd = -EBADF; + _cleanup_free_ char *pp = NULL; + uid_t uid; + gid_t gid; + int r; + + assert(bus); + assert(capsule); + assert(suffix); + assert(ret_pin_fd); + + /* Connects to a capsule's user bus. We need to do so under the capsule's UID/GID, otherwise + * the service manager might refuse our connection. Hence fake it. */ + + r = capsule_name_is_valid(capsule); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + inode_fd = pin_capsule_socket(capsule, suffix, &uid, &gid); + if (inode_fd < 0) + return inode_fd; + + pp = bus_address_escape(FORMAT_PROC_FD_PATH(inode_fd)); + if (!pp) + return -ENOMEM; + + if (asprintf(&bus->address, "unix:path=%s,uid=" UID_FMT ",gid=" GID_FMT, pp, uid, gid) < 0) + return -ENOMEM; + + *ret_pin_fd = TAKE_FD(inode_fd); /* This fd must be kept pinned until the connection has been established */ + return 0; +} + +int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd) { + return bus_set_address_capsule(bus, capsule, "bus", ret_pin_fd); +} + +int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus) { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int inode_fd = -EBADF; + int r; + + assert(capsule); + assert(ret_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_capsule(bus, capsule, "systemd/private", &inode_fd); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return r; + + *ret_bus = TAKE_PTR(bus); + return 0; +} + +int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus) { + _cleanup_(sd_bus_close_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int inode_fd = -EBADF; + int r; + + assert(capsule); + assert(ret_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_capsule_bus(bus, capsule, &inode_fd); + if (r < 0) + return r; + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return r; + + *ret_bus = TAKE_PTR(bus); + return 0; +} + int bus_connect_transport( BusTransport transport, const char *host, @@ -280,12 +410,10 @@ int bus_connect_transport( assert(transport < _BUS_TRANSPORT_MAX); assert(ret); - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport != BUS_TRANSPORT_REMOTE || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); - switch (transport) { case BUS_TRANSPORT_LOCAL: + assert_return(!host, -EINVAL); switch (runtime_scope) { @@ -307,11 +435,12 @@ int bus_connect_transport( break; case BUS_TRANSPORT_REMOTE: + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + r = sd_bus_open_system_remote(&bus, host); break; case BUS_TRANSPORT_MACHINE: - switch (runtime_scope) { case RUNTIME_SCOPE_USER: @@ -328,6 +457,12 @@ int bus_connect_transport( break; + case BUS_TRANSPORT_CAPSULE: + assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL); + + r = bus_connect_capsule_bus(host, &bus); + break; + default: assert_not_reached(); } @@ -342,28 +477,32 @@ int bus_connect_transport( return 0; } -int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus) { +int bus_connect_transport_systemd( + BusTransport transport, + const char *host, + RuntimeScope runtime_scope, + sd_bus **ret_bus) { + assert(transport >= 0); assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + assert(ret_bus); switch (transport) { case BUS_TRANSPORT_LOCAL: + assert_return(!host, -EINVAL); + switch (runtime_scope) { case RUNTIME_SCOPE_USER: - return bus_connect_user_systemd(bus); + return bus_connect_user_systemd(ret_bus); case RUNTIME_SCOPE_SYSTEM: if (sd_booted() <= 0) /* Print a friendly message when the local system is actually not running systemd as PID 1. */ return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "System has not been booted with systemd as init system (PID 1). Can't operate."); - return bus_connect_system_systemd(bus); + return bus_connect_system_systemd(ret_bus); default: assert_not_reached(); @@ -372,10 +511,16 @@ int bus_connect_transport_systemd(BusTransport transport, const char *host, Runt break; case BUS_TRANSPORT_REMOTE: - return sd_bus_open_system_remote(bus, host); + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + return sd_bus_open_system_remote(ret_bus, host); case BUS_TRANSPORT_MACHINE: - return sd_bus_open_system_machine(bus, host); + assert_return(runtime_scope == RUNTIME_SCOPE_SYSTEM, -EOPNOTSUPP); + return sd_bus_open_system_machine(ret_bus, host); + + case BUS_TRANSPORT_CAPSULE: + assert_return(runtime_scope == RUNTIME_SCOPE_USER, -EINVAL); + return bus_connect_capsule_systemd(host, ret_bus); default: assert_not_reached(); @@ -626,7 +771,7 @@ static int method_dump_memory_state_by_fd(sd_bus_message *message, void *userdat if (r < 0) return r; - fd = acquire_data_fd(dump, dump_size, 0); + fd = acquire_data_fd_full(dump, dump_size, /* flags = */ 0); if (fd < 0) return fd; @@ -709,3 +854,75 @@ int bus_property_get_string_set( return bus_message_append_string_set(reply, *s); } + +int bus_creds_get_pidref( + sd_bus_creds *c, + PidRef *ret) { + + int pidfd = -EBADF; + pid_t pid; + int r; + + assert(c); + assert(ret); + + r = sd_bus_creds_get_pid(c, &pid); + if (r < 0) + return r; + + r = sd_bus_creds_get_pidfd_dup(c, &pidfd); + if (r < 0 && r != -ENODATA) + return r; + + *ret = (PidRef) { + .pid = pid, + .fd = pidfd, + }; + + return 0; +} + +int bus_query_sender_pidref( + sd_bus_message *m, + PidRef *ret) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + int r; + + assert(m); + assert(ret); + + r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_PID|SD_BUS_CREDS_PIDFD, &creds); + if (r < 0) + return r; + + return bus_creds_get_pidref(creds, ret); +} + +int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret) { + const void *a; + size_t sz; + int r; + + assert(m); + + r = sd_bus_message_read_array(m, 'y', &a, &sz); + if (r < 0) + return r; + + switch (sz) { + case 0: + if (ret) + *ret = SD_ID128_NULL; + return 0; + + case sizeof(sd_id128_t): + if (ret) + memcpy(ret, a, sz); + return !memeqzero(a, sz); /* This mimics sd_id128_is_null(), but ret may be NULL, + * and a may be misaligned, so use memeqzero() here. */ + + default: + return -EINVAL; + } +} diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h index 869c639..9c6f01d 100644 --- a/src/shared/bus-util.h +++ b/src/shared/bus-util.h @@ -11,6 +11,7 @@ #include "errno-util.h" #include "macro.h" +#include "pidref.h" #include "runtime-scope.h" #include "set.h" #include "string-util.h" @@ -20,6 +21,7 @@ typedef enum BusTransport { BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_REMOTE, BUS_TRANSPORT_MACHINE, + BUS_TRANSPORT_CAPSULE, _BUS_TRANSPORT_MAX, _BUS_TRANSPORT_INVALID = -EINVAL, } BusTransport; @@ -35,8 +37,12 @@ bool bus_error_is_unknown_service(const sd_bus_error *error); int bus_check_peercred(sd_bus *c); +int bus_set_address_capsule_bus(sd_bus *bus, const char *capsule, int *ret_pin_fd); + int bus_connect_system_systemd(sd_bus **ret_bus); int bus_connect_user_systemd(sd_bus **ret_bus); +int bus_connect_capsule_systemd(const char *capsule, sd_bus **ret_bus); +int bus_connect_capsule_bus(const char *capsule, sd_bus **ret_bus); int bus_connect_transport(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus); int bus_connect_transport_systemd(BusTransport transport, const char *host, RuntimeScope runtime_scope, sd_bus **bus); @@ -73,3 +79,8 @@ extern const struct hash_ops bus_message_hash_ops; int bus_message_append_string_set(sd_bus_message *m, Set *s); int bus_property_get_string_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); + +int bus_creds_get_pidref(sd_bus_creds *c, PidRef *ret); +int bus_query_sender_pidref(sd_bus_message *m, PidRef *ret); + +int bus_message_read_id128(sd_bus_message *m, sd_id128_t *ret); diff --git a/src/shared/bus-wait-for-jobs.c b/src/shared/bus-wait-for-jobs.c index 969c629..e12189f 100644 --- a/src/shared/bus-wait-for-jobs.c +++ b/src/shared/bus-wait-for-jobs.c @@ -1,13 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" -#include "bus-wait-for-jobs.h" -#include "set.h" -#include "bus-util.h" #include "bus-internal.h" -#include "unit-def.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" #include "escape.h" +#include "set.h" #include "strv.h" +#include "unit-def.h" typedef struct BusWaitForJobs { sd_bus *bus; @@ -23,60 +23,56 @@ typedef struct BusWaitForJobs { sd_bus_slot *slot_disconnected; } BusWaitForJobs; +BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) { + if (!d) + return NULL; + + set_free(d->jobs); + + sd_bus_slot_unref(d->slot_disconnected); + sd_bus_slot_unref(d->slot_job_removed); + + sd_bus_unref(d->bus); + + free(d->name); + free(d->result); + + return mfree(d); +} + static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { assert(m); - log_error("Warning! D-Bus connection terminated."); + log_warning("D-Bus connection terminated while waiting for jobs."); sd_bus_close(sd_bus_message_get_bus(m)); return 0; } static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char *path, *unit, *result; BusWaitForJobs *d = ASSERT_PTR(userdata); - uint32_t id; - char *found; + _cleanup_free_ char *job_found = NULL; + const char *path, *unit, *result; int r; assert(m); - r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); + r = sd_bus_message_read(m, "uoss", /* id = */ NULL, &path, &unit, &result); if (r < 0) { bus_log_parse_error(r); return 0; } - found = set_remove(d->jobs, (char*) path); - if (!found) + job_found = set_remove(d->jobs, (char*) path); + if (!job_found) return 0; - free(found); - - (void) free_and_strdup(&d->result, empty_to_null(result)); - (void) free_and_strdup(&d->name, empty_to_null(unit)); + (void) free_and_strdup(&d->result, empty_to_null(result)); return 0; } -BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d) { - if (!d) - return NULL; - - set_free(d->jobs); - - sd_bus_slot_unref(d->slot_disconnected); - sd_bus_slot_unref(d->slot_job_removed); - - sd_bus_unref(d->bus); - - free(d->name); - free(d->result); - - return mfree(d); -} - int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; int r; @@ -92,9 +88,8 @@ int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { .bus = sd_bus_ref(bus), }; - /* When we are a bus client we match by sender. Direct - * connections OTOH have no initialized sender field, and - * hence we ignore the sender then */ + /* When we are a bus client we match by sender. Direct connections OTOH have no initialized sender + * field, and hence we ignore the sender then */ r = sd_bus_match_signal_async( bus, &d->slot_job_removed, @@ -138,12 +133,12 @@ static int bus_process_wait(sd_bus *bus) { } } -static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { +static int bus_job_get_service_result(BusWaitForJobs *d, char **ret) { _cleanup_free_ char *dbus_path = NULL; assert(d); assert(d->name); - assert(result); + assert(ret); if (!endswith(d->name, ".service")) return -EINVAL; @@ -158,67 +153,57 @@ static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { "org.freedesktop.systemd1.Service", "Result", NULL, - result); + ret); } static void log_job_error_with_service_result(const char* service, const char *result, const char* const* extra_args) { - _cleanup_free_ char *service_shell_quoted = NULL; - const char *systemctl = "systemctl", *journalctl = "journalctl"; static const struct { const char *result, *explanation; } explanations[] = { - { "resources", "of unavailable resources or another system error" }, + { "resources", "of unavailable resources or another system error" }, { "protocol", "the service did not take the steps required by its unit configuration" }, - { "timeout", "a timeout was exceeded" }, - { "exit-code", "the control process exited with error code" }, - { "signal", "a fatal signal was delivered to the control process" }, + { "timeout", "a timeout was exceeded" }, + { "exit-code", "the control process exited with error code" }, + { "signal", "a fatal signal was delivered to the control process" }, { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, - { "watchdog", "the service failed to send watchdog ping" }, - { "start-limit", "start of the service was attempted too often" } + { "watchdog", "the service failed to send watchdog ping" }, + { "start-limit", "start of the service was attempted too often" }, }; + _cleanup_free_ char *service_shell_quoted = NULL; + const char *systemctl = "systemctl", *journalctl = "journalctl"; + assert(service); service_shell_quoted = shell_maybe_quote(service, 0); - if (!strv_isempty((char**) extra_args)) { + if (!strv_isempty((char* const*) extra_args)) { _cleanup_free_ char *t = NULL; - t = strv_join((char**) extra_args, " "); + t = strv_join((char* const*) extra_args, " "); systemctl = strjoina("systemctl ", t ?: ""); journalctl = strjoina("journalctl ", t ?: ""); } - if (!isempty(result)) { - size_t i; - - for (i = 0; i < ELEMENTSOF(explanations); ++i) - if (streq(result, explanations[i].result)) - break; - - if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s.\n" - "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", - service, - explanations[i].explanation, - systemctl, - service_shell_quoted ?: "", - journalctl, - service_shell_quoted ?: ""); - goto finish; - } - } + if (!isempty(result)) + FOREACH_ELEMENT(i, explanations) + if (streq(result, i->result)) { + log_error("Job for %s failed because %s.\n" + "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", + service, i->explanation, + systemctl, service_shell_quoted ?: "", + journalctl, service_shell_quoted ?: ""); + goto extra; + } log_error("Job for %s failed.\n" "See \"%s status %s\" and \"%s -xeu %s\" for details.\n", service, - systemctl, - service_shell_quoted ?: "", - journalctl, - service_shell_quoted ?: ""); + systemctl, service_shell_quoted ?: "", + journalctl, service_shell_quoted ?: ""); -finish: +extra: /* For some results maybe additional explanation is required */ if (streq_ptr(result, "start-limit")) log_info("To force a start use \"%1$s reset-failed %2$s\"\n" @@ -227,42 +212,56 @@ finish: service_shell_quoted ?: ""); } -static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { +static int check_wait_response(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) { + int r; + assert(d); assert(d->name); assert(d->result); - if (!quiet) { + if (streq(d->result, "done")) { + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS)) + log_info("Job for %s finished.", d->name); + + return 0; + } else if (streq(d->result, "skipped")) { + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_SUCCESS)) + log_info("Job for %s was skipped.", d->name); + + return 0; + } + + if (FLAGS_SET(flags, BUS_WAIT_JOBS_LOG_ERROR)) { if (streq(d->result, "canceled")) - log_error("Job for %s canceled.", strna(d->name)); + log_error("Job for %s canceled.", d->name); else if (streq(d->result, "timeout")) - log_error("Job for %s timed out.", strna(d->name)); + log_error("Job for %s timed out.", d->name); else if (streq(d->result, "dependency")) - log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); + log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", d->name); else if (streq(d->result, "invalid")) - log_error("%s is not active, cannot reload.", strna(d->name)); + log_error("%s is not active, cannot reload.", d->name); else if (streq(d->result, "assert")) - log_error("Assertion failed on job for %s.", strna(d->name)); + log_error("Assertion failed on job for %s.", d->name); else if (streq(d->result, "unsupported")) - log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); + log_error("Operation on or unit type of %s not supported on this system.", d->name); else if (streq(d->result, "collected")) - log_error("Queued job for %s was garbage collected.", strna(d->name)); + log_error("Queued job for %s was garbage collected.", d->name); else if (streq(d->result, "once")) - log_error("Unit %s was started already once and can't be started again.", strna(d->name)); - else if (!STR_IN_SET(d->result, "done", "skipped")) { - - if (d->name && endswith(d->name, ".service")) { - _cleanup_free_ char *result = NULL; - int q; - - q = bus_job_get_service_result(d, &result); - if (q < 0) - log_debug_errno(q, "Failed to get Result property of unit %s: %m", d->name); - - log_job_error_with_service_result(d->name, result, extra_args); - } else - log_error("Job failed. See \"journalctl -xe\" for details."); - } + log_error("Unit %s was started already once and can't be started again.", d->name); + else if (streq(d->result, "frozen")) + log_error("Cannot perform operation on frozen unit %s.", d->name); + else if (endswith(d->name, ".service")) { + /* Job result is unknown. For services, let's also try Result property. */ + _cleanup_free_ char *result = NULL; + + r = bus_job_get_service_result(d, &result); + if (r < 0) + log_debug_errno(r, "Failed to get Result property of unit %s, ignoring: %m", + d->name); + + log_job_error_with_service_result(d->name, result, extra_args); + } else /* Otherwise we just show a generic message. */ + log_error("Job failed. See \"journalctl -xe\" for details."); } if (STR_IN_SET(d->result, "canceled", "collected")) @@ -279,14 +278,15 @@ static int check_wait_response(BusWaitForJobs *d, bool quiet, const char* const* return -EOPNOTSUPP; else if (streq(d->result, "once")) return -ESTALE; - else if (STR_IN_SET(d->result, "done", "skipped")) - return 0; + else if (streq(d->result, "frozen")) + return -EDEADLK; - return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Unexpected job result, assuming server side newer than us: %s", d->result); + return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM), + "Unexpected job result '%s' for unit '%s', assuming server side newer than us.", + d->result, d->name); } -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args) { +int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args) { int r = 0; assert(d); @@ -299,14 +299,12 @@ int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_ar return log_error_errno(q, "Failed to wait for response: %m"); if (d->name && d->result) { - q = check_wait_response(d, quiet, extra_args); - /* Return the first error as it is most likely to be - * meaningful. */ - if (q < 0 && r == 0) - r = q; + q = check_wait_response(d, flags, extra_args); + /* Return the first error as it is most likely to be meaningful. */ + RET_GATHER(r, q); log_full_errno_zerook(LOG_DEBUG, q, - "Got result %s/%m for job %s", d->result, d->name); + "Got result %s/%m for job %s.", d->result, d->name); } d->name = mfree(d->name); @@ -322,12 +320,12 @@ int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { return set_put_strdup(&d->jobs, path); } -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args) { +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args) { int r; r = bus_wait_for_jobs_add(d, path); if (r < 0) return log_oom(); - return bus_wait_for_jobs(d, quiet, extra_args); + return bus_wait_for_jobs(d, flags, extra_args); } diff --git a/src/shared/bus-wait-for-jobs.h b/src/shared/bus-wait-for-jobs.h index 5acf8b9..2336b13 100644 --- a/src/shared/bus-wait-for-jobs.h +++ b/src/shared/bus-wait-for-jobs.h @@ -7,10 +7,15 @@ typedef struct BusWaitForJobs BusWaitForJobs; -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); -BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d); -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char* const* extra_args); -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet, const char* const* extra_args); +typedef enum WaitJobsFlags { + BUS_WAIT_JOBS_LOG_ERROR = 1 << 0, + BUS_WAIT_JOBS_LOG_SUCCESS = 1 << 1, +} WaitJobsFlags; +BusWaitForJobs* bus_wait_for_jobs_free(BusWaitForJobs *d); DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); +int bus_wait_for_jobs(BusWaitForJobs *d, WaitJobsFlags flags, const char* const* extra_args); +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, WaitJobsFlags flags, const char* const* extra_args); diff --git a/src/shared/bus-wait-for-units.c b/src/shared/bus-wait-for-units.c index 0dd2a29..6ccf822 100644 --- a/src/shared/bus-wait-for-units.c +++ b/src/shared/bus-wait-for-units.c @@ -18,7 +18,7 @@ typedef struct WaitForItem { sd_bus_slot *slot_get_all; sd_bus_slot *slot_properties_changed; - bus_wait_for_units_unit_callback unit_callback; + bus_wait_for_units_unit_callback_t unit_callback; void *userdata; char *active_state; @@ -32,11 +32,6 @@ typedef struct BusWaitForUnits { Hashmap *items; - bus_wait_for_units_ready_callback ready_callback; - void *userdata; - - WaitForItem *current; - BusWaitForUnitsState state; bool has_failed:1; } BusWaitForUnits; @@ -63,10 +58,7 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) { log_debug_errno(r, "Failed to drop reference to unit %s, ignoring: %m", item->bus_path); } - assert_se(hashmap_remove(item->parent->items, item->bus_path) == item); - - if (item->parent->current == item) - item->parent->current = NULL; + assert_se(hashmap_remove_value(item->parent->items, item->bus_path, item)); } sd_bus_slot_unref(item->slot_properties_changed); @@ -82,8 +74,6 @@ static WaitForItem *wait_for_item_free(WaitForItem *item) { DEFINE_TRIVIAL_CLEANUP_FUNC(WaitForItem*, wait_for_item_free); static void call_unit_callback_and_wait(BusWaitForUnits *d, WaitForItem *item, bool good) { - d->current = item; - if (item->unit_callback) item->unit_callback(d, item->bus_path, good, item->userdata); @@ -109,14 +99,10 @@ static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *e assert(m); - log_error("Warning! D-Bus connection terminated."); + log_warning("D-Bus connection terminated while waiting for unit."); bus_wait_for_units_clear(d); - - if (d->ready_callback) - d->ready_callback(d, false, d->userdata); - else /* If no ready callback is specified close the connection so that the event loop exits */ - sd_bus_close(sd_bus_message_get_bus(m)); + sd_bus_close(sd_bus_message_get_bus(m)); return 0; } @@ -172,13 +158,6 @@ static bool bus_wait_for_units_is_ready(BusWaitForUnits *d) { return hashmap_isempty(d->items); } -void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata) { - assert(d); - - d->ready_callback = callback; - d->userdata = userdata; -} - static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { assert(d); @@ -186,9 +165,6 @@ static void bus_wait_for_units_check_ready(BusWaitForUnits *d) { return; d->state = d->has_failed ? BUS_WAIT_FAILURE : BUS_WAIT_SUCCESS; - - if (d->ready_callback) - d->ready_callback(d, d->state, d->userdata); } static void wait_for_item_check_ready(WaitForItem *item) { @@ -221,32 +197,26 @@ static void wait_for_item_check_ready(WaitForItem *item) { bus_wait_for_units_check_ready(d); } -static int property_map_job( +static int property_map_job_id( sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - WaitForItem *item = ASSERT_PTR(userdata); - const char *path; - uint32_t id; - int r; + uint32_t *job_id = ASSERT_PTR(userdata); - r = sd_bus_message_read(m, "(uo)", &id, &path); - if (r < 0) - return r; + assert(m); - item->job_id = id; - return 0; + return sd_bus_message_read(m, "(uo)", job_id, /* path = */ NULL); } static int wait_for_item_parse_properties(WaitForItem *item, sd_bus_message *m) { static const struct bus_properties_map map[] = { - { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, - { "Job", "(uo)", property_map_job, 0 }, - { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, + { "ActiveState", "s", NULL, offsetof(WaitForItem, active_state) }, + { "Job", "(uo)", property_map_job_id, offsetof(WaitForItem, job_id) }, + { "CleanResult", "s", NULL, offsetof(WaitForItem, clean_result) }, {} }; @@ -315,20 +285,23 @@ int bus_wait_for_units_add_unit( BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, - bus_wait_for_units_unit_callback callback, + bus_wait_for_units_unit_callback_t callback, void *userdata) { _cleanup_(wait_for_item_freep) WaitForItem *item = NULL; + _cleanup_free_ char *bus_path = NULL; int r; assert(d); assert(unit); + assert((flags & _BUS_WAIT_FOR_TARGET) != 0); - assert(flags != 0); + bus_path = unit_dbus_path_from_name(unit); + if (!bus_path) + return -ENOMEM; - r = hashmap_ensure_allocated(&d->items, &string_hash_ops); - if (r < 0) - return r; + if (hashmap_contains(d->items, bus_path)) + return 0; item = new(WaitForItem, 1); if (!item) @@ -336,15 +309,12 @@ int bus_wait_for_units_add_unit( *item = (WaitForItem) { .flags = flags, - .bus_path = unit_dbus_path_from_name(unit), + .bus_path = TAKE_PTR(bus_path), .unit_callback = callback, .userdata = userdata, .job_id = UINT32_MAX, }; - if (!item->bus_path) - return -ENOMEM; - if (!FLAGS_SET(item->flags, BUS_WAIT_REFFED)) { r = sd_bus_call_method_async( d->bus, @@ -388,14 +358,16 @@ int bus_wait_for_units_add_unit( if (r < 0) return log_debug_errno(r, "Failed to request properties of unit %s: %m", unit); - r = hashmap_put(d->items, item->bus_path, item); + r = hashmap_ensure_put(&d->items, &string_hash_ops, item->bus_path, item); if (r < 0) return r; + assert(r > 0); d->state = BUS_WAIT_RUNNING; item->parent = d; TAKE_PTR(item); - return 0; + + return 1; } int bus_wait_for_units_run(BusWaitForUnits *d) { diff --git a/src/shared/bus-wait-for-units.h b/src/shared/bus-wait-for-units.h index 2623e72..a4a4dc4 100644 --- a/src/shared/bus-wait-for-units.h +++ b/src/shared/bus-wait-for-units.h @@ -8,7 +8,7 @@ typedef struct BusWaitForUnits BusWaitForUnits; typedef enum BusWaitForUnitsState { BUS_WAIT_SUCCESS, /* Nothing to wait for anymore and nothing failed */ - BUS_WAIT_FAILURE, /* dito, but something failed */ + BUS_WAIT_FAILURE, /* ditto, but something failed */ BUS_WAIT_RUNNING, /* Still something to wait for */ _BUS_WAIT_FOR_UNITS_STATE_MAX, _BUS_WAIT_FOR_UNITS_STATE_INVALID = -EINVAL, @@ -19,17 +19,22 @@ typedef enum BusWaitForUnitsFlags { BUS_WAIT_FOR_INACTIVE = 1 << 1, /* Wait until the unit is back in inactive or dead state */ BUS_WAIT_NO_JOB = 1 << 2, /* Wait until there's no more job pending */ BUS_WAIT_REFFED = 1 << 3, /* The unit is already reffed with RefUnit() */ + _BUS_WAIT_FOR_TARGET = BUS_WAIT_FOR_MAINTENANCE_END|BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, } BusWaitForUnitsFlags; -typedef void (*bus_wait_for_units_ready_callback)(BusWaitForUnits *d, BusWaitForUnitsState state, void *userdata); -typedef void (*bus_wait_for_units_unit_callback)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); +typedef void (*bus_wait_for_units_unit_callback_t)(BusWaitForUnits *d, const char *unit_path, bool good, void *userdata); int bus_wait_for_units_new(sd_bus *bus, BusWaitForUnits **ret); + BusWaitForUnits* bus_wait_for_units_free(BusWaitForUnits *d); +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); -BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); -void bus_wait_for_units_set_ready_callback(BusWaitForUnits *d, bus_wait_for_units_ready_callback callback, void *userdata); -int bus_wait_for_units_add_unit(BusWaitForUnits *d, const char *unit, BusWaitForUnitsFlags flags, bus_wait_for_units_unit_callback callback, void *userdata); -int bus_wait_for_units_run(BusWaitForUnits *d); +int bus_wait_for_units_add_unit( + BusWaitForUnits *d, + const char *unit, + BusWaitForUnitsFlags flags, + bus_wait_for_units_unit_callback_t callback, + void *userdata); -DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForUnits*, bus_wait_for_units_free); +int bus_wait_for_units_run(BusWaitForUnits *d); +BusWaitForUnitsState bus_wait_for_units_state(BusWaitForUnits *d); diff --git a/src/shared/capsule-util.c b/src/shared/capsule-util.c new file mode 100644 index 0000000..3689a78 --- /dev/null +++ b/src/shared/capsule-util.c @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "capsule-util.h" +#include "path-util.h" +#include "user-util.h" + +int capsule_name_is_valid(const char *name) { + + if (!filename_is_valid(name)) + return false; + + _cleanup_free_ char *prefixed = strjoin("c-", name); + if (!prefixed) + return -ENOMEM; + + return valid_user_group_name(prefixed, /* flags= */ 0); +} diff --git a/src/shared/capsule-util.h b/src/shared/capsule-util.h new file mode 100644 index 0000000..437153b --- /dev/null +++ b/src/shared/capsule-util.h @@ -0,0 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +int capsule_name_is_valid(const char *name); diff --git a/src/shared/cgroup-setup.c b/src/shared/cgroup-setup.c index 934a16e..093b6d0 100644 --- a/src/shared/cgroup-setup.c +++ b/src/shared/cgroup-setup.c @@ -51,7 +51,7 @@ static int cg_any_controller_used_for_v1(void) { continue; const char *p = *line; - r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled, NULL); + r = extract_many_words(&p, NULL, 0, &name, &hierarchy_id, &num, &enabled); if (r < 0) return log_debug_errno(r, "Error parsing /proc/cgroups line, ignoring: %m"); else if (r < 4) { @@ -81,9 +81,6 @@ static int cg_any_controller_used_for_v1(void) { bool cg_is_unified_wanted(void) { static thread_local int wanted = -1; - bool b; - const bool is_default = DEFAULT_HIERARCHY == CGROUP_UNIFIED_ALL; - _cleanup_free_ char *c = NULL; int r; /* If we have a cached value, return that. */ @@ -96,21 +93,20 @@ bool cg_is_unified_wanted(void) { return (wanted = r >= CGROUP_UNIFIED_ALL); /* If we were explicitly passed systemd.unified_cgroup_hierarchy, respect that. */ + bool b; r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", /* flags = */ 0, &b); if (r > 0) return (wanted = b); /* If we passed cgroup_no_v1=all with no other instructions, it seems highly unlikely that we want to * use hybrid or legacy hierarchy. */ + _cleanup_free_ char *c = NULL; r = proc_cmdline_get_key("cgroup_no_v1", 0, &c); if (r > 0 && streq_ptr(c, "all")) return (wanted = true); /* If any controller is in use as v1, don't use unified. */ - if (cg_any_controller_used_for_v1() > 0) - return (wanted = false); - - return (wanted = is_default); + return (wanted = (cg_any_controller_used_for_v1() <= 0)); } bool cg_is_legacy_wanted(void) { @@ -132,10 +128,6 @@ bool cg_is_legacy_wanted(void) { bool cg_is_hybrid_wanted(void) { static thread_local int wanted = -1; int r; - bool b; - const bool is_default = DEFAULT_HIERARCHY >= CGROUP_UNIFIED_SYSTEMD; - /* We default to true if the default is "hybrid", obviously, but also when the default is "unified", - * because if we get called, it means that unified hierarchy was not mounted. */ /* If we have a cached value, return that. */ if (wanted >= 0) @@ -146,12 +138,33 @@ bool cg_is_hybrid_wanted(void) { return (wanted = false); /* Otherwise, let's see what the kernel command line has to say. Since checking is expensive, cache - * a non-error result. */ + * a non-error result. + * The meaning of the kernel option is reversed wrt. to the return value of this function, hence the + * negation. */ + bool b; r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", /* flags = */ 0, &b); + if (r > 0) + return (wanted = !b); - /* The meaning of the kernel option is reversed wrt. to the return value of this function, hence the - * negation. */ - return (wanted = r > 0 ? !b : is_default); + /* The default hierarchy is "unified". But if this is reached, it means that unified hierarchy was + * not mounted, so return true too. */ + return (wanted = true); +} + +bool cg_is_legacy_force_enabled(void) { + bool force; + + if (!cg_is_legacy_wanted()) + return false; + + /* If in container, we have to follow host's cgroup hierarchy. */ + if (detect_container() > 0) + return true; + + if (proc_cmdline_get_bool("SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE", /* flags = */ 0, &force) < 0) + return false; + + return force; } int cg_weight_parse(const char *s, uint64_t *ret) { @@ -371,6 +384,20 @@ int cg_attach(const char *controller, const char *path, pid_t pid) { return 0; } +int cg_fd_attach(int fd, pid_t pid) { + char c[DECIMAL_STR_MAX(pid_t) + 2]; + + assert(fd >= 0); + assert(pid >= 0); + + if (pid == 0) + pid = getpid_cached(); + + xsprintf(c, PID_FMT "\n", pid); + + return write_string_file_at(fd, "cgroup.procs", c, WRITE_STRING_FILE_DISABLE_BUFFER); +} + int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { int r; @@ -571,75 +598,56 @@ int cg_migrate( bool done = false; _cleanup_set_free_ Set *s = NULL; int r, ret = 0; - pid_t my_pid; assert(cfrom); assert(pfrom); assert(cto); assert(pto); - s = set_new(NULL); - if (!s) - return -ENOMEM; - - my_pid = getpid_cached(); - do { _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; + pid_t pid; + done = true; r = cg_enumerate_processes(cfrom, pfrom, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } + if (r < 0) + return RET_GATHER(ret, r); - while ((r = cg_read_pid(f, &pid)) > 0) { + while ((r = cg_read_pid(f, &pid, flags)) > 0) { + /* Throw an error if unmappable PIDs are in output, we can't migrate those. */ + if (pid == 0) + return -EREMOTE; - /* This might do weird stuff if we aren't a - * single-threaded program. However, we - * luckily know we are not */ - if ((flags & CGROUP_IGNORE_SELF) && pid == my_pid) + /* This might do weird stuff if we aren't a single-threaded program. However, we + * luckily know we are. */ + if (FLAGS_SET(flags, CGROUP_IGNORE_SELF) && pid == getpid_cached()) continue; - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + if (set_contains(s, PID_TO_PTR(pid))) continue; - /* Ignore kernel threads. Since they can only - * exist in the root cgroup, we only check for - * them there. */ - if (cfrom && - empty_or_root(pfrom) && + /* Ignore kernel threads. Since they can only exist in the root cgroup, we only + * check for them there. */ + if (cfrom && empty_or_root(pfrom) && pid_is_kernel_thread(pid) > 0) continue; r = cg_attach(cto, pto, pid); if (r < 0) { - if (ret >= 0 && r != -ESRCH) - ret = r; + if (r != -ESRCH) + RET_GATHER(ret, r); } else if (ret == 0) ret = 1; done = false; - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; + r = set_ensure_put(&s, /* hash_ops = */ NULL, PID_TO_PTR(pid)); + if (r < 0) + return RET_GATHER(ret, r); } + if (r < 0) + return RET_GATHER(ret, r); } while (!done); return ret; diff --git a/src/shared/cgroup-setup.h b/src/shared/cgroup-setup.h index 1b6f071..283ab67 100644 --- a/src/shared/cgroup-setup.h +++ b/src/shared/cgroup-setup.h @@ -10,6 +10,7 @@ bool cg_is_unified_wanted(void); bool cg_is_legacy_wanted(void); bool cg_is_hybrid_wanted(void); +bool cg_is_legacy_force_enabled(void); int cg_weight_parse(const char *s, uint64_t *ret); int cg_cpu_weight_parse(const char *s, uint64_t *ret); @@ -20,6 +21,7 @@ int cg_trim(const char *controller, const char *path, bool delete_root); int cg_create(const char *controller, const char *path); int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_fd_attach(int fd, pid_t pid); int cg_attach_fallback(const char *controller, const char *path, pid_t pid); int cg_create_and_attach(const char *controller, const char *path, pid_t pid); diff --git a/src/shared/cgroup-show.c b/src/shared/cgroup-show.c index c2ee1c5..8717731 100644 --- a/src/shared/cgroup-show.c +++ b/src/shared/cgroup-show.c @@ -108,7 +108,7 @@ static int show_cgroup_one_by_path( * From https://docs.kernel.org/admin-guide/cgroup-v2.html#threads, * “cgroup.procs” in a threaded domain cgroup contains the PIDs of all processes in * the subtree and is not readable in the subtree proper. */ - r = cg_read_pid(f, &pid); + r = cg_read_pid(f, &pid, /* flags = */ 0); if (IN_SET(r, 0, -EOPNOTSUPP)) break; if (r < 0) @@ -150,18 +150,9 @@ static int show_cgroup_name( delegate = r > 0; if (FLAGS_SET(flags, OUTPUT_CGROUP_ID)) { - cg_file_handle fh = CG_FILE_HANDLE_INIT; - int mnt_id = -1; - - if (name_to_handle_at( - fd, - "", - &fh.file_handle, - &mnt_id, - AT_EMPTY_PATH) < 0) - log_debug_errno(errno, "Failed to determine cgroup ID of %s, ignoring: %m", path); - else - cgroupid = CG_FILE_HANDLE_CGROUPID(fh); + r = cg_fd_get_cgroupid(fd, &cgroupid); + if (r < 0) + log_debug_errno(r, "Failed to determine cgroup ID of %s, ignoring: %m", path); } r = path_extract_filename(path, &b); diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c index bbb343f..1e90cc2 100644 --- a/src/shared/clean-ipc.c +++ b/src/shared/clean-ipc.c @@ -58,7 +58,7 @@ static int clean_sysvipc_shm(uid_t delete_uid, gid_t delete_gid, bool rm) { r = read_line(f, LONG_LINE_MAX, &line); if (r < 0) - return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); + return log_warning_errno(r, "Failed to read /proc/sysvipc/shm: %m"); if (r == 0) break; diff --git a/src/shared/clock-util.c b/src/shared/clock-util.c index b0cbe30..37d0232 100644 --- a/src/shared/clock-util.c +++ b/src/shared/clock-util.c @@ -27,10 +27,11 @@ int clock_get_hwclock(struct tm *tm) { if (fd < 0) return -errno; - /* This leaves the timezone fields of struct tm - * uninitialized! */ + /* This leaves the timezone fields of struct tm uninitialized! */ if (ioctl(fd, RTC_RD_TIME, tm) < 0) - return -errno; + /* Some drivers return -EINVAL in case the time could not be kept, i.e. power loss + * happened. Let's turn that into a clearly recognizable error */ + return errno == EINVAL ? -ENODATA : -errno; /* We don't know daylight saving, so we reset this in order not * to confuse mktime(). */ diff --git a/src/shared/color-util.c b/src/shared/color-util.c new file mode 100644 index 0000000..9d714c0 --- /dev/null +++ b/src/shared/color-util.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "color-util.h" +#include "macro.h" + +void rgb_to_hsv(double r, double g, double b, + double *ret_h, double *ret_s, double *ret_v) { + + assert(r >= 0 && r <= 1); + assert(g >= 0 && g <= 1); + assert(b >= 0 && b <= 1); + + double max_color = fmax(r, fmax(g, b)); + double min_color = fmin(r, fmin(g, b)); + double delta = max_color - min_color; + + if (ret_v) + *ret_v = max_color * 100.0; + + if (max_color <= 0) { + if (ret_s) + *ret_s = 0; + if (ret_h) + *ret_h = NAN; + return; + } + + if (ret_s) + *ret_s = delta / max_color * 100.0; + + if (ret_h) { + if (delta > 0) { + if (r >= max_color) + *ret_h = 60 * fmod((g - b) / delta, 6); + else if (g >= max_color) + *ret_h = 60 * (((b - r) / delta) + 2); + else if (b >= max_color) + *ret_h = 60 * (((r - g) / delta) + 4); + + *ret_h = fmod(*ret_h, 360); + } else + *ret_h = NAN; + } +} + +void hsv_to_rgb(double h, double s, double v, + uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b) { + + double c, x, m, r, g, b; + + assert(s >= 0 && s <= 100); + assert(v >= 0 && v <= 100); + assert(ret_r); + assert(ret_g); + assert(ret_b); + + h = fmod(h, 360); + c = (s / 100.0) * (v / 100.0); + x = c * (1 - fabs(fmod(h / 60.0, 2) - 1)); + m = (v / 100) - c; + + if (h >= 0 && h < 60) + r = c, g = x, b = 0.0; + else if (h >= 60 && h < 120) + r = x, g = c, b = 0.0; + else if (h >= 120 && h < 180) + r = 0.0, g = c, b = x; + else if (h >= 180 && h < 240) + r = 0.0, g = x, b = c; + else if (h >= 240 && h < 300) + r = x, g = 0.0, b = c; + else + r = c, g = 0.0, b = x; + + *ret_r = (uint8_t) ((r + m) * 255); + *ret_g = (uint8_t) ((g + m) * 255); + *ret_b = (uint8_t) ((b + m) * 255); +} diff --git a/src/shared/color-util.h b/src/shared/color-util.h new file mode 100644 index 0000000..a4ae9eb --- /dev/null +++ b/src/shared/color-util.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +void rgb_to_hsv(double r, double g, double b, + double *ret_h, double *ret_s, double *ret_v); + +void hsv_to_rgb( + double h, double s, double v, + uint8_t* ret_r, uint8_t *ret_g, uint8_t *ret_b); diff --git a/src/shared/compare-operator.c b/src/shared/compare-operator.c index 0da28fc..a13db8e 100644 --- a/src/shared/compare-operator.c +++ b/src/shared/compare-operator.c @@ -6,6 +6,7 @@ #include "string-util.h" CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags flags) { + static const struct { CompareOperator op; const char *str; @@ -40,19 +41,19 @@ CompareOperator parse_compare_operator(const char **s, CompareOperatorParseFlags * parse_compare_operator() are use on the same string? */ return _COMPARE_OPERATOR_INVALID; - for (size_t i = 0; i < ELEMENTSOF(table); i ++) { + FOREACH_ELEMENT(i, table) { const char *e; - if (table[i].need_mask != 0 && !FLAGS_SET(flags, table[i].need_mask)) + if (i->need_mask != 0 && !FLAGS_SET(flags, i->need_mask)) continue; - e = startswith(*s, table[i].str); + e = startswith(*s, i->str); if (e) { - if (table[i].valid_mask != 0 && !FLAGS_SET(flags, table[i].valid_mask)) + if (i->valid_mask != 0 && !FLAGS_SET(flags, i->valid_mask)) return _COMPARE_OPERATOR_INVALID; *s = e; - return table[i].op; + return i->op; } } diff --git a/src/shared/condition.c b/src/shared/condition.c index d3446e8..b53b2ef 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -59,7 +59,7 @@ #include "string-util.h" #include "tomoyo-util.h" #include "tpm2-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" #include "virt.h" @@ -141,7 +141,6 @@ static int condition_test_kernel_command_line(Condition *c, char **env) { } static int condition_test_credential(Condition *c, char **env) { - int (*gd)(const char **ret); int r; assert(c); @@ -155,7 +154,8 @@ static int condition_test_credential(Condition *c, char **env) { if (!credential_name_valid(c->parameter)) /* credentials with invalid names do not exist */ return false; - FOREACH_POINTER(gd, get_credentials_dir, get_encrypted_credentials_dir) { + int (*gd)(const char **ret); + FOREACH_ARGUMENT(gd, get_credentials_dir, get_encrypted_credentials_dir) { _cleanup_free_ char *j = NULL; const char *cd; @@ -260,13 +260,13 @@ static int condition_test_osrelease(Condition *c, char **env) { /* The os-release spec mandates env-var-like key names */ if (r == 0 || isempty(word) || !env_name_is_valid(key)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse parameter, key/value format expected: %m"); + "Failed to parse parameter, key/value format expected."); /* Do not allow whitespace after the separator, as that's not a valid os-release format */ operator = parse_compare_operator(&word, COMPARE_ALLOW_FNMATCH|COMPARE_EQUAL_BY_STRING); if (operator < 0 || isempty(word) || strchr(WHITESPACE, *word) != NULL) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to parse parameter, key/value format expected: %m"); + "Failed to parse parameter, key/value format expected."); r = parse_os_release(NULL, key, &actual_value); if (r < 0) @@ -543,7 +543,7 @@ static int condition_test_firmware_smbios_field(const char *expression) { /* Read actual value from sysfs */ if (!filename_is_valid(field)) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name"); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid SMBIOS field name."); const char *p = strjoina("/sys/class/dmi/id/", field); r = read_virtual_file(p, SIZE_MAX, &actual_value, NULL); @@ -599,7 +599,7 @@ static int condition_test_firmware(Condition *c, char **env) { end = strrchr(arg, ')'); if (!end || *(end + 1) != '\0') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed ConditionFirmware=%s.", c->parameter); smbios_arg = strndup(arg, end - arg); if (!smbios_arg) @@ -751,9 +751,9 @@ static int condition_test_needs_update(Condition *c, char **env) { assert(c->parameter); assert(c->type == CONDITION_NEEDS_UPDATE); - r = proc_cmdline_get_bool("systemd.condition-needs-update", /* flags = */ 0, &b); + r = proc_cmdline_get_bool("systemd.condition_needs_update", /* flags = */ 0, &b); if (r < 0) - log_debug_errno(r, "Failed to parse systemd.condition-needs-update= kernel command line argument, ignoring: %m"); + log_debug_errno(r, "Failed to parse systemd.condition_needs_update= kernel command line argument, ignoring: %m"); if (r > 0) return b; @@ -931,7 +931,7 @@ static int condition_test_path_is_mount_point(Condition *c, char **env) { assert(c->parameter); assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); - return path_is_mount_point(c->parameter, NULL, AT_SYMLINK_FOLLOW) > 0; + return path_is_mount_point_full(c->parameter, /* root = */ NULL, AT_SYMLINK_FOLLOW) > 0; } static int condition_test_path_is_read_write(Condition *c, char **env) { @@ -1024,7 +1024,7 @@ static int condition_test_psi(Condition *c, char **env) { "io"; p = c->parameter; - r = extract_many_words(&p, ":", 0, &first, &second, NULL); + r = extract_many_words(&p, ":", 0, &first, &second); if (r <= 0) return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); /* If only one parameter is passed, then we look at the global system pressure rather than a specific cgroup. */ @@ -1046,7 +1046,7 @@ static int condition_test_psi(Condition *c, char **env) { slice = strstrip(first); if (!slice) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); r = cg_all_unified(); if (r < 0) @@ -1099,7 +1099,7 @@ static int condition_test_psi(Condition *c, char **env) { /* If a value including a specific timespan (in the intervals allowed by the kernel), * parse it, otherwise we assume just a plain percentage that will be checked if it is * smaller or equal to the current pressure average over 5 minutes. */ - r = extract_many_words(&value, "/", 0, &third, &fourth, NULL); + r = extract_many_words(&value, "/", 0, &third, &fourth); if (r <= 0) return log_debug_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); if (r == 1) @@ -1109,7 +1109,7 @@ static int condition_test_psi(Condition *c, char **env) { timespan = skip_leading_chars(fourth, NULL); if (!timespan) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); if (startswith(timespan, "10sec")) current = &pressure.avg10; @@ -1118,12 +1118,12 @@ static int condition_test_psi(Condition *c, char **env) { else if (startswith(timespan, "5min")) current = &pressure.avg300; else - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); } value = strstrip(third); if (!value) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s: %m", c->parameter); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse condition parameter %s.", c->parameter); r = parse_permyriad(value); if (r < 0) diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c index e8ecd9b..fcc45c6 100644 --- a/src/shared/conf-parser.c +++ b/src/shared/conf-parser.c @@ -8,6 +8,7 @@ #include #include "alloc-util.h" +#include "chase.h" #include "conf-files.h" #include "conf-parser.h" #include "constants.h" @@ -159,7 +160,11 @@ static int next_assignment( /* Warn about unknown non-extension fields. */ if (!(flags & CONFIG_PARSE_RELAXED) && !startswith(lvalue, "X-")) log_syntax(unit, LOG_WARNING, filename, line, 0, - "Unknown key name '%s' in section '%s', ignoring.", lvalue, section); + "Unknown key '%s'%s%s%s, ignoring.", + lvalue, + section ? " in section [" : "", + strempty(section), + section ? "]" : ""); return 0; } @@ -480,6 +485,7 @@ int hashmap_put_stats_by_path(Hashmap **stats_by_path, const char *path, const s } static int config_parse_many_files( + const char *root, const char* const* conf_files, char **files, const char *sections, @@ -502,19 +508,16 @@ static int config_parse_many_files( } STRV_FOREACH(fn, files) { - _cleanup_free_ struct stat *st_dropin = NULL; _cleanup_fclose_ FILE *f = NULL; - int fd; + _cleanup_free_ char *fname = NULL; - f = fopen(*fn, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } + r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", &fname, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; - fd = fileno(f); + int fd = fileno(f); r = ordered_hashmap_ensure_put(&dropins, &config_file_hash_ops_fclose, *fn, f); if (r < 0) { @@ -527,7 +530,7 @@ static int config_parse_many_files( /* Get inodes for all drop-ins. Later we'll verify if main config is a symlink to or is * symlinked as one of them. If so, we skip reading main config file directly. */ - st_dropin = new(struct stat, 1); + _cleanup_free_ struct stat *st_dropin = new(struct stat, 1); if (!st_dropin) return -ENOMEM; @@ -543,13 +546,11 @@ static int config_parse_many_files( STRV_FOREACH(fn, conf_files) { _cleanup_fclose_ FILE *f = NULL; - f = fopen(*fn, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } + r = chase_and_fopen_unlocked(*fn, root, CHASE_AT_RESOLVE_IN_ROOT, "re", NULL, &f); + if (r == -ENOENT) + continue; + if (r < 0) + return r; if (inodes) { if (fstat(fileno(f), &st) < 0) @@ -561,7 +562,7 @@ static int config_parse_many_files( } } - r = config_parse(NULL, *fn, f, sections, lookup, table, flags, userdata, &st); + r = config_parse(/* unit= */ NULL, *fn, f, sections, lookup, table, flags, userdata, &st); if (r < 0) return r; assert(r > 0); @@ -580,7 +581,7 @@ static int config_parse_many_files( const char *path_dropin; FILE *f_dropin; ORDERED_HASHMAP_FOREACH_KEY(f_dropin, path_dropin, dropins) { - r = config_parse(NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st); + r = config_parse(/* unit= */ NULL, path_dropin, f_dropin, sections, lookup, table, flags, userdata, &st); if (r < 0) return r; assert(r > 0); @@ -598,54 +599,6 @@ static int config_parse_many_files( return 0; } -/* Parse one main config file located in /etc/systemd and its drop-ins, which is what all systemd daemons - * do. */ -int config_parse_config_file( - const char *conf_file, - const char *sections, - ConfigItemLookup lookup, - const void *table, - ConfigParseFlags flags, - void *userdata) { - - _cleanup_strv_free_ char **dropins = NULL, **dropin_dirs = NULL; - char **conf_paths = CONF_PATHS_STRV(""); - int r; - - assert(conf_file); - - /* build the dropin dir list */ - dropin_dirs = new0(char*, strv_length(conf_paths) + 1); - if (!dropin_dirs) { - if (flags & CONFIG_PARSE_WARN) - return log_oom(); - return -ENOMEM; - } - - size_t i = 0; - STRV_FOREACH(p, conf_paths) { - char *d; - - d = strjoin(*p, "systemd/", conf_file, ".d"); - if (!d) { - if (flags & CONFIG_PARSE_WARN) - return log_oom(); - return -ENOMEM; - } - - dropin_dirs[i++] = d; - } - - r = conf_files_list_strv(&dropins, ".conf", NULL, 0, (const char**) dropin_dirs); - if (r < 0) - return r; - - const char *sysconf_file = strjoina(PKGSYSCONFDIR, "/", conf_file); - - return config_parse_many_files(STRV_MAKE_CONST(sysconf_file), dropins, - sections, lookup, table, flags, userdata, NULL); -} - /* Parse each config file in the directories specified as strv. */ int config_parse_many( const char* const* conf_files, @@ -665,14 +618,13 @@ int config_parse_many( assert(conf_file_dirs); assert(dropin_dirname); - assert(sections); assert(table); r = conf_files_list_dropins(&files, dropin_dirname, root, conf_file_dirs); if (r < 0) return r; - r = config_parse_many_files(conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path); + r = config_parse_many_files(root, conf_files, files, sections, lookup, table, flags, userdata, ret_stats_by_path); if (r < 0) return r; @@ -682,6 +634,50 @@ int config_parse_many( return 0; } +int config_parse_standard_file_with_dropins_full( + const char *root, + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata, + Hashmap **ret_stats_by_path, + char ***ret_dropin_files) { + + const char* const *conf_paths = (const char* const*) CONF_PATHS_STRV(""); + _cleanup_strv_free_ char **configs = NULL; + int r; + + /* Build the list of main config files */ + r = strv_extend_strv_biconcat(&configs, root, conf_paths, main_file); + if (r < 0) { + if (flags & CONFIG_PARSE_WARN) + log_oom(); + return r; + } + + _cleanup_free_ char *dropin_dirname = strjoin(main_file, ".d"); + if (!dropin_dirname) { + if (flags & CONFIG_PARSE_WARN) + log_oom(); + return -ENOMEM; + } + + return config_parse_many( + (const char* const*) configs, + conf_paths, + dropin_dirname, + root, + sections, + lookup, + table, + flags, + userdata, + ret_stats_by_path, + ret_dropin_files); +} + static int dropins_get_stats_by_path( const char* conf_file, const char* const* conf_file_dirs, @@ -795,12 +791,12 @@ bool stats_by_path_equal(Hashmap *a, Hashmap *b) { return true; } -static void config_section_hash_func(const ConfigSection *c, struct siphash *state) { +void config_section_hash_func(const ConfigSection *c, struct siphash *state) { siphash24_compress_string(c->filename, state); - siphash24_compress(&c->line, sizeof(c->line), state); + siphash24_compress_typesafe(c->line, state); } -static int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) { +int config_section_compare_func(const ConfigSection *x, const ConfigSection *y) { int r; r = strcmp(x->filename, y->filename); @@ -1062,7 +1058,7 @@ int config_parse_tristate( if (isempty(rvalue)) { *t = -1; - return 0; + return 1; } r = parse_tristate(rvalue, t); @@ -1072,7 +1068,7 @@ int config_parse_tristate( return 0; } - return 0; + return 1; } int config_parse_string( @@ -1088,6 +1084,7 @@ int config_parse_string( void *userdata) { char **s = ASSERT_PTR(data); + int r; assert(filename); assert(lvalue); @@ -1095,7 +1092,7 @@ int config_parse_string( if (isempty(rvalue)) { *s = mfree(*s); - return 0; + return 1; } if (FLAGS_SET(ltype, CONFIG_PARSE_STRING_SAFE) && !string_is_safe(rvalue)) { @@ -1116,7 +1113,11 @@ int config_parse_string( return 0; } - return free_and_strdup_warn(s, empty_to_null(rvalue)); + r = free_and_strdup_warn(s, empty_to_null(rvalue)); + if (r < 0) + return r; + + return 1; } int config_parse_dns_name( @@ -1592,7 +1593,7 @@ int config_parse_mtu( return 0; } - return 0; + return 1; } int config_parse_rlimit( @@ -1982,3 +1983,37 @@ int config_parse_unsigned_bounded( DEFINE_CONFIG_PARSE(config_parse_percent, parse_percent, "Failed to parse percent value"); DEFINE_CONFIG_PARSE(config_parse_permyriad, parse_permyriad, "Failed to parse permyriad value"); DEFINE_CONFIG_PARSE_PTR(config_parse_sec_fix_0, parse_sec_fix_0, usec_t, "Failed to parse time value"); + +int config_parse_timezone( + 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 **tz = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *tz = mfree(*tz); + return 0; + } + + r = verify_timezone(rvalue, LOG_WARNING); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Timezone is not valid, ignoring assignment: %s", rvalue); + return 0; + } + + return free_and_strdup_warn(tz, rvalue); +} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h index a1768cd..35e203c 100644 --- a/src/shared/conf-parser.h +++ b/src/shared/conf-parser.h @@ -93,20 +93,12 @@ int config_parse( void *userdata, struct stat *ret_stat); /* possibly NULL */ -int config_parse_config_file( - const char *conf_file, - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - ConfigParseFlags flags, - void *userdata); - int config_parse_many( const char* const* conf_files, /* possibly empty */ const char* const* conf_file_dirs, const char *dropin_dirname, const char *root, - const char *sections, /* nulstr */ + const char *sections, /* nulstr */ ConfigItemLookup lookup, const void *table, ConfigParseFlags flags, @@ -114,6 +106,36 @@ int config_parse_many( Hashmap **ret_stats_by_path, /* possibly NULL */ char ***ret_drop_in_files); /* possibly NULL */ +int config_parse_standard_file_with_dropins_full( + const char *root, + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata, + Hashmap **ret_stats_by_path, /* possibly NULL */ + char ***ret_dropin_files); /* possibly NULL */ + +static inline int config_parse_standard_file_with_dropins( + const char *main_file, /* A path like "systemd/frobnicator.conf" */ + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + ConfigParseFlags flags, + void *userdata) { + return config_parse_standard_file_with_dropins_full( + /* root= */ NULL, + main_file, + sections, + lookup, + table, + flags, + userdata, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); +} + int config_get_stats_by_path( const char *suffix, const char *root, @@ -137,7 +159,11 @@ static inline ConfigSection* config_section_free(ConfigSection *cs) { DEFINE_TRIVIAL_CLEANUP_FUNC(ConfigSection*, config_section_free); int config_section_new(const char *filename, unsigned line, ConfigSection **ret); + +void config_section_hash_func(const ConfigSection *c, struct siphash *state); +int config_section_compare_func(const ConfigSection *x, const ConfigSection *y); extern const struct hash_ops config_section_hash_ops; + int _hashmap_by_section_find_unused_line( HashmapBase *entries_by_section, const char *filename, @@ -224,6 +250,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_percent); CONFIG_PARSER_PROTOTYPE(config_parse_permyriad); CONFIG_PARSER_PROTOTYPE(config_parse_pid); CONFIG_PARSER_PROTOTYPE(config_parse_sec_fix_0); +CONFIG_PARSER_PROTOTYPE(config_parse_timezone); typedef enum Disabled { DISABLED_CONFIGURATION, diff --git a/src/shared/copy.c b/src/shared/copy.c index 2b87cba..8389774 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -170,13 +170,14 @@ int copy_bytes_full( assert(fdt >= 0); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); - /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a maximum - * of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. Returns negative on - * error, zero if EOF is hit before the bytes limit is hit and positive otherwise. If the copy fails for some - * reason but we read but didn't yet write some data an ret_remains/ret_remains_size is not NULL, then it will - * be initialized with an allocated buffer containing this "remaining" data. Note that these two parameters are - * initialized with a valid buffer only on failure and only if there's actually data already read. Otherwise - * these parameters if non-NULL are set to NULL. */ + /* Tries to copy bytes from the file descriptor 'fdf' to 'fdt' in the smartest possible way. Copies a + * maximum of 'max_bytes', which may be specified as UINT64_MAX, in which no maximum is applied. + * Returns negative on error, zero if EOF is hit before the bytes limit is hit and positive + * otherwise. If the copy fails for some reason but we read but didn't yet write some data and + * ret_remains/ret_remains_size is not NULL, then it will be initialized with an allocated buffer + * containing this "remaining" data. Note that these two parameters are initialized with a valid + * buffer only on failure and only if there's actually data already read. Otherwise these parameters + * if non-NULL are set to NULL. */ if (ret_remains) *ret_remains = NULL; diff --git a/src/shared/cpu-set-util.c b/src/shared/cpu-set-util.c index d096576..1112de1 100644 --- a/src/shared/cpu-set-util.c +++ b/src/shared/cpu-set-util.c @@ -11,6 +11,7 @@ #include "errno-util.h" #include "extract-word.h" #include "fd-util.h" +#include "hexdecoct.h" #include "log.h" #include "macro.h" #include "memory-util.h" @@ -82,6 +83,63 @@ char *cpu_set_to_range_string(const CPUSet *set) { return TAKE_PTR(str) ?: strdup(""); } +char* cpu_set_to_mask_string(const CPUSet *a) { + _cleanup_free_ char *str = NULL; + size_t len = 0; + bool found_nonzero = false; + + assert(a); + + /* Return CPU set in hexadecimal bitmap mask, e.g. + * CPU 0 -> "1" + * CPU 1 -> "2" + * CPU 0,1 -> "3" + * CPU 0-3 -> "f" + * CPU 0-7 -> "ff" + * CPU 4-7 -> "f0" + * CPU 7 -> "80" + * None -> "0" + * + * When there are more than 32 CPUs, separate every 32 CPUs by comma, e.g. + * CPU 0-47 -> "ffff,ffffffff" + * CPU 0-63 -> "ffffffff,ffffffff" + * CPU 0-71 -> "ff,ffffffff,ffffffff" */ + + for (ssize_t i = a->allocated * 8; i >= 0; i -= 4) { + uint8_t m = 0; + + for (size_t j = 0; j < 4; j++) + if (CPU_ISSET_S(i + j, a->allocated, a->set)) + m |= 1U << j; + + if (!found_nonzero) + found_nonzero = m > 0; + + if (!found_nonzero && m == 0) + /* Skip leading zeros */ + continue; + + if (!GREEDY_REALLOC(str, len + 3)) + return NULL; + + str[len++] = hexchar(m); + if (i >= 4 && i % 32 == 0) + /* Separate by comma for each 32 CPUs. */ + str[len++] = ','; + str[len] = 0; + } + + return TAKE_PTR(str) ?: strdup("0"); +} + +CPUSet* cpu_set_free(CPUSet *c) { + if (!c) + return c; + + cpu_set_reset(c); + return mfree(c); +} + int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus) { size_t need; @@ -290,3 +348,22 @@ int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set) { *set = TAKE_STRUCT(s); return 0; } + +int cpu_mask_add_all(CPUSet *mask) { + long m; + int r; + + assert(mask); + + m = sysconf(_SC_NPROCESSORS_ONLN); + if (m < 0) + return -errno; + + for (unsigned i = 0; i < (unsigned) m; i++) { + r = cpu_set_add(mask, i); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/shared/cpu-set-util.h b/src/shared/cpu-set-util.h index 3c63a58..618fe1b 100644 --- a/src/shared/cpu-set-util.h +++ b/src/shared/cpu-set-util.h @@ -19,11 +19,15 @@ static inline void cpu_set_reset(CPUSet *a) { *a = (CPUSet) {}; } +CPUSet* cpu_set_free(CPUSet *c); +DEFINE_TRIVIAL_CLEANUP_FUNC(CPUSet*, cpu_set_free); + int cpu_set_add_all(CPUSet *a, const CPUSet *b); int cpu_set_add(CPUSet *a, unsigned cpu); char* cpu_set_to_string(const CPUSet *a); char *cpu_set_to_range_string(const CPUSet *a); +char* cpu_set_to_mask_string(const CPUSet *a); int cpu_set_realloc(CPUSet *cpu_set, unsigned ncpus); int parse_cpu_set_full( @@ -50,3 +54,4 @@ int cpu_set_to_dbus(const CPUSet *set, uint8_t **ret, size_t *allocated); int cpu_set_from_dbus(const uint8_t *bits, size_t size, CPUSet *set); int cpus_in_affinity_mask(void); +int cpu_mask_add_all(CPUSet *mask); diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c index fa8ebe0..1d8bd91 100644 --- a/src/shared/creds-util.c +++ b/src/shared/creds-util.c @@ -12,23 +12,28 @@ #include "capability-util.h" #include "chattr-util.h" #include "constants.h" +#include "copy.h" #include "creds-util.h" #include "efi-api.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" +#include "format-util.h" #include "fs-util.h" #include "io-util.h" #include "memory-util.h" -#include "mkdir.h" +#include "mkdir-label.h" #include "openssl-util.h" #include "parse-util.h" #include "path-util.h" #include "random-util.h" +#include "recurse-dir.h" #include "sparse-endian.h" #include "stat-util.h" +#include "tmpfile-util.h" #include "tpm2-util.h" -#include "virt.h" +#include "user-util.h" +#include "varlink.h" #define PUBLIC_KEY_MAX (UINT32_C(1024) * UINT32_C(1024)) @@ -100,6 +105,17 @@ int get_encrypted_credentials_dir(const char **ret) { return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret); } +int open_credentials_dir(void) { + const char *d; + int r; + + r = get_credentials_dir(&d); + if (r < 0) + return r; + + return RET_NERRNO(open(d, O_CLOEXEC|O_DIRECTORY)); +} + int read_credential(const char *name, void **ret, size_t *ret_size) { _cleanup_free_ char *fn = NULL; const char *d; @@ -127,14 +143,13 @@ int read_credential(const char *name, void **ret, size_t *ret_size) { } int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) { + _cleanup_(iovec_done_erase) struct iovec ret_iovec = {}; _cleanup_(erase_and_freep) void *data = NULL; _cleanup_free_ char *fn = NULL; size_t sz = 0; const char *d; int r; - assert(ret); - /* Just like read_credential() but will also look for encrypted credentials. Note that services only * receive decrypted credentials, hence use read_credential() for those. This helper here is for * generators, i.e. code that runs outside of service context, and thus has no decrypted credentials @@ -177,23 +192,37 @@ int read_credential_with_decryption(const char *name, void **ret, size_t *ret_si if (r < 0) return log_error_errno(r, "Failed to read encrypted credential data: %m"); - r = decrypt_credential_and_warn( - name, - now(CLOCK_REALTIME), - /* tpm2_device = */ NULL, - /* tpm2_signature_path = */ NULL, - data, - sz, - ret, - ret_size); + if (geteuid() != 0) + r = ipc_decrypt_credential( + name, + now(CLOCK_REALTIME), + getuid(), + &IOVEC_MAKE(data, sz), + CREDENTIAL_ANY_SCOPE, + &ret_iovec); + else + r = decrypt_credential_and_warn( + name, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + getuid(), + &IOVEC_MAKE(data, sz), + CREDENTIAL_ANY_SCOPE, + &ret_iovec); if (r < 0) return r; + if (ret) + *ret = TAKE_PTR(ret_iovec.iov_base); + if (ret_size) + *ret_size = ret_iovec.iov_len; + return 1; /* found */ not_found: - *ret = NULL; - + if (ret) + *ret = NULL; if (ret_size) *ret_size = 0; @@ -205,6 +234,7 @@ int read_credential_strings_many_internal( ...) { _cleanup_free_ void *b = NULL; + bool all = true; int r, ret = 0; /* Reads a bunch of credentials into the specified buffers. If the specified buffers are already @@ -220,10 +250,11 @@ int read_credential_strings_many_internal( r = read_credential(first_name, &b, NULL); if (r == -ENXIO) /* No creds passed at all? Bail immediately. */ return 0; - if (r < 0) { - if (r != -ENOENT) - ret = r; - } else + if (r == -ENOENT) + all = false; + else if (r < 0) + RET_GATHER(ret, r); + else free_and_replace(*first_value, b); va_list ap; @@ -238,20 +269,19 @@ int read_credential_strings_many_internal( if (!name) break; - value = va_arg(ap, char **); - if (*value) - continue; + value = ASSERT_PTR(va_arg(ap, char **)); r = read_credential(name, &bb, NULL); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - ret = r; - } else + if (r == -ENOENT) + all = false; + else if (r < 0) + RET_GATHER(ret, r); + else free_and_replace(*value, bb); } va_end(ap); - return ret; + return ret < 0 ? ret : all; } int read_credential_bool(const char *name) { @@ -341,8 +371,7 @@ static int make_credential_host_secret( CredentialSecretFlags flags, const char *dirname, const char *fn, - void **ret_data, - size_t *ret_size) { + struct iovec *ret) { _cleanup_free_ char *t = NULL; _cleanup_close_ int fd = -EBADF; @@ -351,22 +380,9 @@ static int make_credential_host_secret( assert(dfd >= 0); assert(fn); - /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the - * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH - * capability when it uses the AT_EMPTY_PATH flag. */ - if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) { - fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400); - if (fd < 0) - log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m"); - } - if (fd < 0) { - if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0) - return -ENOMEM; - - fd = openat(dfd, t, O_CLOEXEC|O_WRONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0400); - if (fd < 0) - return -errno; - } + fd = open_tmpfile_linkable_at(dfd, fn, O_CLOEXEC|O_WRONLY, &t); + if (fd < 0) + return log_debug_errno(fd, "Failed to create temporary file for credential host secret: %m"); r = chattr_secret(fd, 0); if (r < 0) @@ -386,44 +402,34 @@ static int make_credential_host_secret( if (r < 0) goto fail; - if (fsync(fd) < 0) { + if (fchmod(fd, 0400) < 0) { r = -errno; goto fail; } - warn_not_encrypted(fd, flags, dirname, fn); - - if (t) { - r = rename_noreplace(dfd, t, dfd, fn); - if (r < 0) - goto fail; - - t = mfree(t); - } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) { + if (fsync(fd) < 0) { r = -errno; goto fail; } - if (fsync(dfd) < 0) { - r = -errno; + warn_not_encrypted(fd, flags, dirname, fn); + + r = link_tmpfile_at(fd, dfd, t, fn, LINK_TMPFILE_SYNC); + if (r < 0) { + log_debug_errno(r, "Failed to link host key into place: %m"); goto fail; } - if (ret_data) { + if (ret) { void *copy; copy = memdup(buf.data, sizeof(buf.data)); - if (!copy) { - r = -ENOMEM; - goto fail; - } + if (!copy) + return -ENOMEM; - *ret_data = copy; + *ret = IOVEC_MAKE(copy, sizeof(buf.data)); } - if (ret_size) - *ret_size = sizeof(buf.data); - return 0; fail: @@ -433,7 +439,7 @@ fail: return r; } -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) { +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { _cleanup_free_ char *_dirname = NULL, *_filename = NULL; _cleanup_close_ int dfd = -EBADF; sd_id128_t machine_id; @@ -501,7 +507,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * "Failed to open %s/%s: %m", dirname, filename); - r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret, ret_size); + r = make_credential_host_secret(dfd, machine_id, flags, dirname, filename, ret); if (r == -EEXIST) { log_debug_errno(r, "Credential secret %s/%s appeared while we were creating it, rereading.", dirname, filename); @@ -549,7 +555,7 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * "Failed to read %s/%s: %m", dirname, filename); if ((size_t) n != l) /* What? The size changed? */ return log_debug_errno(SYNTHETIC_ERRNO(EIO), - "Failed to read %s/%s: %m", dirname, filename); + "Failed to read %s/%s.", dirname, filename); if (sd_id128_equal(machine_id, f->machine_id)) { size_t sz; @@ -568,12 +574,9 @@ int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t * if (!copy) return log_oom_debug(); - *ret = copy; + *ret = IOVEC_MAKE(copy, sz); } - if (ret_size) - *ret_size = sz; - return 0; } @@ -658,6 +661,11 @@ struct _packed_ tpm2_public_key_credential_header { /* Followed by NUL bytes until next 8 byte boundary */ }; +struct _packed_ scoped_credential_header { + le64_t flags; /* SCOPE_HASH_DATA_BASE_FLAGS for now */ +}; + +/* This header is encrypted */ struct _packed_ metadata_credential_header { le64_t timestamp; le64_t not_after; @@ -666,23 +674,38 @@ struct _packed_ metadata_credential_header { /* Followed by NUL bytes until next 8 byte boundary */ }; +struct _packed_ scoped_hash_data { + le64_t flags; /* copy of the scoped_credential_header.flags */ + le32_t uid; + sd_id128_t machine_id; + char username[]; /* followed by the username */ + /* Later on we might want to extend this: with a cgroup path to allow per-app secrets, and with the user's $HOME encryption key */ +}; + +enum { + /* Flags for scoped_hash_data.flags and scoped_credential_header.flags */ + SCOPE_HASH_DATA_HAS_UID = 1 << 0, + SCOPE_HASH_DATA_HAS_MACHINE = 1 << 1, + SCOPE_HASH_DATA_HAS_USERNAME = 1 << 2, + + SCOPE_HASH_DATA_BASE_FLAGS = SCOPE_HASH_DATA_HAS_UID | SCOPE_HASH_DATA_HAS_USERNAME | SCOPE_HASH_DATA_HAS_MACHINE, +}; + /* Some generic limit for parts of the encrypted credential for which we don't know the right size ahead of * time, but where we are really sure it won't be larger than this. Should be larger than any possible IV, * padding, tag size and so on. This is purely used for early filtering out of invalid sizes. */ #define CREDENTIAL_FIELD_SIZE_MAX (16U*1024U) static int sha256_hash_host_and_tpm2_key( - const void *host_key, - size_t host_key_size, - const void *tpm2_key, - size_t tpm2_key_size, + const struct iovec *host_key, + const struct iovec *tpm2_key, uint8_t ret[static SHA256_DIGEST_LENGTH]) { _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md = NULL; unsigned l; - assert(host_key_size == 0 || host_key); - assert(tpm2_key_size == 0 || tpm2_key); + assert(iovec_is_valid(host_key)); + assert(iovec_is_valid(tpm2_key)); assert(ret); /* Combines the host key and the TPM2 HMAC hash into a SHA256 hash value we'll use as symmetric encryption key. */ @@ -694,10 +717,10 @@ static int sha256_hash_host_and_tpm2_key( if (EVP_DigestInit_ex(md, EVP_sha256(), NULL) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initial SHA256 context."); - if (host_key && EVP_DigestUpdate(md, host_key, host_key_size) != 1) + if (iovec_is_set(host_key) && EVP_DigestUpdate(md, host_key->iov_base, host_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash host key."); - if (tpm2_key && EVP_DigestUpdate(md, tpm2_key, tpm2_key_size) != 1) + if (iovec_is_set(tpm2_key) && EVP_DigestUpdate(md, tpm2_key->iov_base, tpm2_key->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to hash TPM2 key."); assert(EVP_MD_CTX_size(md) == SHA256_DIGEST_LENGTH); @@ -709,6 +732,58 @@ static int sha256_hash_host_and_tpm2_key( return 0; } +static int mangle_uid_into_key( + uid_t uid, + uint8_t md[static SHA256_DIGEST_LENGTH]) { + + sd_id128_t mid; + int r; + + assert(uid_is_valid(uid)); + assert(md); + + /* If we shall encrypt for a specific user, we HMAC() a structure with the user's credentials + * (specifically, UID, user name, machine ID) with the key we'd otherwise use for system credentials, + * and use the resulting hash as actual encryption key. */ + + errno = 0; + struct passwd *pw = getpwuid(uid); + if (!pw) + return log_error_errno( + IN_SET(errno, 0, ENOENT) ? SYNTHETIC_ERRNO(ESRCH) : errno, + "Failed to resolve UID " UID_FMT ": %m", uid); + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to read machine ID: %m"); + + size_t sz = offsetof(struct scoped_hash_data, username) + strlen(pw->pw_name) + 1; + _cleanup_free_ struct scoped_hash_data *d = malloc0(sz); + if (!d) + return log_oom(); + + d->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS); + d->uid = htole32(uid); + d->machine_id = mid; + + strcpy(d->username, pw->pw_name); + + _cleanup_(erase_and_freep) void *buf = NULL; + size_t buf_size = 0; + r = openssl_hmac_many( + "sha256", + md, SHA256_DIGEST_LENGTH, + &IOVEC_MAKE(d, sz), 1, + &buf, &buf_size); + if (r < 0) + return r; + + assert(buf_size == SHA256_DIGEST_LENGTH); + memcpy(md, buf, buf_size); + + return 0; +} + int encrypt_credential_and_warn( sd_id128_t with_key, const char *name, @@ -718,38 +793,39 @@ int encrypt_credential_and_warn( uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, - const void *input, - size_t input_size, - void **ret, - size_t *ret_size) { + uid_t uid, + const struct iovec *input, + CredentialFlags flags, + struct iovec *ret) { + _cleanup_(iovec_done) struct iovec tpm2_blob = {}, tpm2_policy_hash = {}, iv = {}, pubkey = {}; + _cleanup_(iovec_done_erase) struct iovec tpm2_key = {}, output = {}, host_key = {}; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL; - size_t host_key_size = 0, tpm2_key_size = 0, tpm2_blob_size = 0, tpm2_policy_hash_size = 0, output_size, p, ml; - _cleanup_free_ void *tpm2_blob = NULL, *tpm2_policy_hash = NULL, *iv = NULL, *output = NULL; _cleanup_free_ struct metadata_credential_header *m = NULL; uint16_t tpm2_pcr_bank = 0, tpm2_primary_alg = 0; struct encrypted_credential_header *h; int ksz, bsz, ivsz, tsz, added, r; - _cleanup_free_ void *pubkey = NULL; - size_t pubkey_size = 0; uint8_t md[SHA256_DIGEST_LENGTH]; const EVP_CIPHER *cc; sd_id128_t id; + size_t p, ml; - assert(input || input_size == 0); + assert(iovec_is_valid(input)); assert(ret); - assert(ret_size); if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_HOST, + CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, - CRED_AES256_GCM_BY_TPM2_ABSENT)) + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED, + CRED_AES256_GCM_BY_NULL)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid key type: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(with_key)); if (name && !credential_name_valid(name)) @@ -769,19 +845,32 @@ int encrypt_credential_and_warn( log_debug("Including not-after timestamp '%s' in encrypted credential.", format_timestamp(buf, sizeof(buf), not_after)); } + if (sd_id128_in_set(with_key, + _CRED_AUTO_SCOPED, + CRED_AES256_GCM_BY_HOST_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { + if (!uid_is_valid(uid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Scoped credential selected, but no UID specified."); + } else + uid = UID_INVALID; + if (sd_id128_in_set(with_key, _CRED_AUTO, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_HOST, + CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) { + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { r = get_credential_host_secret( CREDENTIAL_SECRET_GENERATE| CREDENTIAL_SECRET_WARN_NOT_ENCRYPTED| - (sd_id128_equal(with_key, _CRED_AUTO) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0), - &host_key, - &host_key_size); - if (r == -ENOMEDIUM && sd_id128_equal(with_key, _CRED_AUTO)) + (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED) ? CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS : 0), + &host_key); + if (r == -ENOMEDIUM && sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED)) log_debug_errno(r, "Credential host secret location on temporary file system, not using."); else if (r < 0) return log_error_errno(r, "Failed to determine local credential host secret: %m"); @@ -789,7 +878,7 @@ int encrypt_credential_and_warn( #if HAVE_TPM2 bool try_tpm2; - if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) { + if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) { /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a * container tpm2_support will detect this, and will return a different flag combination of * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ @@ -802,27 +891,31 @@ int encrypt_credential_and_warn( CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); if (try_tpm2) { if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, + _CRED_AUTO_SCOPED, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, - CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK)) { + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED)) { /* Load public key for PCR policies, if one is specified, or explicitly requested */ - r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey, &pubkey_size); + r = tpm2_load_pcr_public_key(tpm2_pubkey_path, &pubkey.iov_base, &pubkey.iov_len); if (r < 0) { - if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) + if (tpm2_pubkey_path || r != -ENOENT || !sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) return log_error_errno(r, "Failed read TPM PCR public key: %m"); log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); } } - if (!pubkey) + if (!iovec_is_set(&pubkey)) tpm2_pubkey_pcr_mask = 0; _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; @@ -844,8 +937,8 @@ int encrypt_credential_and_warn( return log_error_errno(r, "Could not read PCR values: %m"); TPM2B_PUBLIC public; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); + if (iovec_is_set(&pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey.iov_base, pubkey.iov_len, &public); if (r < 0) return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); } @@ -854,7 +947,7 @@ int encrypt_credential_and_warn( r = tpm2_calculate_sealing_policy( tpm2_hash_pcr_values, tpm2_n_hash_pcr_values, - pubkey ? &public : NULL, + iovec_is_set(&pubkey) ? &public : NULL, /* use_pin= */ false, /* pcrlock_policy= */ NULL, &tpm2_policy); @@ -865,56 +958,61 @@ int encrypt_credential_and_warn( /* seal_key_handle= */ 0, &tpm2_policy, /* pin= */ NULL, - &tpm2_key, &tpm2_key_size, - &tpm2_blob, &tpm2_blob_size, + &tpm2_key, + &tpm2_blob, &tpm2_primary_alg, - /* ret_srk_buf= */ NULL, - /* ret_srk_buf_size= */ NULL); + /* ret_srk= */ NULL); 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."); - else if (!sd_id128_equal(with_key, _CRED_AUTO)) + else if (!sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_SCOPED)) return log_error_errno(r, "Failed to seal to TPM2: %m"); log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); } - tpm2_policy_hash_size = tpm2_policy.size; - tpm2_policy_hash = malloc(tpm2_policy_hash_size); - if (!tpm2_policy_hash) + if (!iovec_memdup(&IOVEC_MAKE(tpm2_policy.buffer, tpm2_policy.size), &tpm2_policy_hash)) return log_oom(); - memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size); - assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX); - assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_blob.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_policy_hash.iov_len <= CREDENTIAL_FIELD_SIZE_MAX); } #endif - if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) { + if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD, _CRED_AUTO_SCOPED)) { /* Let's settle the key type in auto mode now. */ - if (host_key && tpm2_key) - id = pubkey ? CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC; - else if (tpm2_key) - id = pubkey ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC; - else if (host_key) - id = CRED_AES256_GCM_BY_HOST; + if (iovec_is_set(&host_key) && iovec_is_set(&tpm2_key)) + id = iovec_is_set(&pubkey) ? (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK) + : (sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED : CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC); + else if (iovec_is_set(&tpm2_key) && !sd_id128_equal(with_key, _CRED_AUTO_SCOPED)) + id = iovec_is_set(&pubkey) ? CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK : CRED_AES256_GCM_BY_TPM2_HMAC; + else if (iovec_is_set(&host_key)) + id = sd_id128_equal(with_key, _CRED_AUTO_SCOPED) ? CRED_AES256_GCM_BY_HOST_SCOPED : CRED_AES256_GCM_BY_HOST; else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) - id = CRED_AES256_GCM_BY_TPM2_ABSENT; + id = CRED_AES256_GCM_BY_NULL; else return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 not available and host key located on temporary file system, no encryption key available."); } else id = with_key; - if (sd_id128_equal(id, CRED_AES256_GCM_BY_TPM2_ABSENT)) + if (sd_id128_equal(id, CRED_AES256_GCM_BY_NULL) && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) log_warning("Using a null key for encryption and signing. Confidentiality or authenticity will not be provided."); /* Let's now take the host key and the TPM2 key and hash it together, to use as encryption key for the data */ - r = sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md); + r = sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md); if (r < 0) return r; + if (uid_is_valid(uid)) { + r = mangle_uid_into_key(uid, md); + if (r < 0) + return r; + } + assert_se(cc = EVP_aes_256_gcm()); ksz = EVP_CIPHER_key_length(cc); @@ -928,11 +1026,13 @@ int encrypt_credential_and_warn( if (ivsz > 0) { assert((size_t) ivsz <= CREDENTIAL_FIELD_SIZE_MAX); - iv = malloc(ivsz); - if (!iv) + iv.iov_base = malloc(ivsz); + if (!iv.iov_base) return log_oom(); - r = crypto_random_bytes(iv, ivsz); + iv.iov_len = ivsz; + + r = crypto_random_bytes(iv.iov_base, iv.iov_len); if (r < 0) return log_error_errno(r, "Failed to acquired randomized IV: %m"); } @@ -944,61 +1044,71 @@ int encrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(ENOMEM), "Failed to allocate encryption object: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_EncryptInit_ex(context, cc, NULL, md, iv) != 1) + if (EVP_EncryptInit_ex(context, cc, NULL, md, iv.iov_base) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context: %s", ERR_error_string(ERR_get_error(), NULL)); /* Just an upper estimate */ - output_size = + output.iov_len = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz) + - ALIGN8(tpm2_key ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size : 0) + - ALIGN8(pubkey ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size : 0) + + ALIGN8(iovec_is_set(&tpm2_key) ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len : 0) + + ALIGN8(iovec_is_set(&pubkey) ? offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len : 0) + + ALIGN8(uid_is_valid(uid) ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name) + strlen_ptr(name)) + - input_size + 2U * (size_t) bsz + + input->iov_len + 2U * (size_t) bsz + tsz; - output = malloc0(output_size); - if (!output) + output.iov_base = malloc0(output.iov_len); + if (!output.iov_base) return log_oom(); - h = (struct encrypted_credential_header*) output; + h = (struct encrypted_credential_header*) output.iov_base; h->id = id; h->block_size = htole32(bsz); h->key_size = htole32(ksz); h->tag_size = htole32(tsz); h->iv_size = htole32(ivsz); - memcpy(h->iv, iv, ivsz); + memcpy(h->iv, iv.iov_base, ivsz); p = ALIGN8(offsetof(struct encrypted_credential_header, iv) + ivsz); - if (tpm2_key) { + if (iovec_is_set(&tpm2_key)) { struct tpm2_credential_header *t; - t = (struct tpm2_credential_header*) ((uint8_t*) output + p); + t = (struct tpm2_credential_header*) ((uint8_t*) output.iov_base + p); t->pcr_mask = htole64(tpm2_hash_pcr_mask); t->pcr_bank = htole16(tpm2_pcr_bank); t->primary_alg = htole16(tpm2_primary_alg); - t->blob_size = htole32(tpm2_blob_size); - t->policy_hash_size = htole32(tpm2_policy_hash_size); - memcpy(t->policy_hash_and_blob, tpm2_blob, tpm2_blob_size); - memcpy(t->policy_hash_and_blob + tpm2_blob_size, tpm2_policy_hash, tpm2_policy_hash_size); + t->blob_size = htole32(tpm2_blob.iov_len); + t->policy_hash_size = htole32(tpm2_policy_hash.iov_len); + memcpy(t->policy_hash_and_blob, tpm2_blob.iov_base, tpm2_blob.iov_len); + memcpy(t->policy_hash_and_blob + tpm2_blob.iov_len, tpm2_policy_hash.iov_base, tpm2_policy_hash.iov_len); - p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob_size + tpm2_policy_hash_size); + p += ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + tpm2_blob.iov_len + tpm2_policy_hash.iov_len); } - if (pubkey) { + if (iovec_is_set(&pubkey)) { struct tpm2_public_key_credential_header *z; - z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output + p); + z = (struct tpm2_public_key_credential_header*) ((uint8_t*) output.iov_base + p); z->pcr_mask = htole64(tpm2_pubkey_pcr_mask); - z->size = htole32(pubkey_size); - memcpy(z->data, pubkey, pubkey_size); + z->size = htole32(pubkey.iov_len); + memcpy(z->data, pubkey.iov_base, pubkey.iov_len); + + p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey.iov_len); + } + + if (uid_is_valid(uid)) { + struct scoped_credential_header *w; + + w = (struct scoped_credential_header*) ((uint8_t*) output.iov_base + p); + w->flags = htole64(SCOPE_HASH_DATA_BASE_FLAGS); - p += ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + pubkey_size); + p += ALIGN8(sizeof(struct scoped_credential_header)); } - /* Pass the encrypted + TPM2 header as AAD */ - if (EVP_EncryptUpdate(context, NULL, &added, output, p) != 1) + /* Pass the encrypted + TPM2 header + scoped header as AAD */ + if (EVP_EncryptUpdate(context, NULL, &added, output.iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", ERR_error_string(ERR_get_error(), NULL)); @@ -1014,53 +1124,52 @@ int encrypt_credential_and_warn( memcpy_safe(m->name, name, ml); /* And encrypt the metadata header */ - if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) + if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, (const unsigned char*) m, ALIGN8(offsetof(struct metadata_credential_header, name) + ml)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt metadata header: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; /* Then encrypt the plaintext */ - if (EVP_EncryptUpdate(context, (uint8_t*) output + p, &added, input, input_size) != 1) + if (EVP_EncryptUpdate(context, (uint8_t*) output.iov_base + p, &added, input->iov_base, input->iov_len) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt data: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; /* Finalize */ - if (EVP_EncryptFinal_ex(context, (uint8_t*) output + p, &added) != 1) + if (EVP_EncryptFinal_ex(context, (uint8_t*) output.iov_base + p, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize data encryption: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= output_size - p); + assert((size_t) added <= output.iov_len - p); p += added; - assert(p <= output_size - tsz); + assert(p <= output.iov_len - tsz); /* Append tag */ - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output + p) != 1) + if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_GET_TAG, tsz, (uint8_t*) output.iov_base + p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get tag: %s", ERR_error_string(ERR_get_error(), NULL)); p += tsz; - assert(p <= output_size); + assert(p <= output.iov_len); + output.iov_len = p; - if (DEBUG_LOGGING && input_size > 0) { + if (DEBUG_LOGGING && input->iov_len > 0) { size_t base64_size; - base64_size = DIV_ROUND_UP(p * 4, 3); /* Include base64 size increase in debug output */ - assert(base64_size >= input_size); - log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input_size, base64_size, base64_size * 100 / input_size - 100); + base64_size = DIV_ROUND_UP(output.iov_len * 4, 3); /* Include base64 size increase in debug output */ + assert(base64_size >= input->iov_len); + log_debug("Input of %zu bytes grew to output of %zu bytes (+%2zu%%).", input->iov_len, base64_size, base64_size * 100 / input->iov_len - 100); } - *ret = TAKE_PTR(output); - *ret_size = p; - + *ret = TAKE_STRUCT(output); return 0; } @@ -1069,39 +1178,39 @@ int decrypt_credential_and_warn( usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, - const void *input, - size_t input_size, - void **ret, - size_t *ret_size) { + uid_t uid, + const struct iovec *input, + CredentialFlags flags, + struct iovec *ret) { - _cleanup_(erase_and_freep) void *host_key = NULL, *tpm2_key = NULL, *plaintext = NULL; + _cleanup_(iovec_done_erase) struct iovec host_key = {}, plaintext = {}, tpm2_key = {}; _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL; - size_t host_key_size = 0, tpm2_key_size = 0, plaintext_size, p, hs; struct encrypted_credential_header *h; struct metadata_credential_header *m; uint8_t md[SHA256_DIGEST_LENGTH]; - bool with_tpm2, with_host_key, is_tpm2_absent, with_tpm2_pk; + bool with_tpm2, with_tpm2_pk, with_host_key, with_null, with_scope; const EVP_CIPHER *cc; + size_t p, hs; int r, added; - assert(input || input_size == 0); + assert(iovec_is_valid(input)); assert(ret); - assert(ret_size); - h = (struct encrypted_credential_header*) input; + h = (struct encrypted_credential_header*) input->iov_base; /* The ID must fit in, for the current and all future formats */ - if (input_size < sizeof(h->id)) + if (input->iov_len < sizeof(h->id)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); - with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); - with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK); - with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC) || with_tpm2_pk; - is_tpm2_absent = sd_id128_equal(h->id, CRED_AES256_GCM_BY_TPM2_ABSENT); + with_host_key = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); + with_tpm2_pk = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); + with_tpm2 = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED) || with_tpm2_pk; + with_null = sd_id128_equal(h->id, CRED_AES256_GCM_BY_NULL); + with_scope = sd_id128_in_set(h->id, CRED_AES256_GCM_BY_HOST_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED); - if (!with_host_key && !with_tpm2 && !is_tpm2_absent) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data: %m"); + if (!with_host_key && !with_tpm2 && !with_null) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unknown encryption format, or corrupted data."); if (with_tpm2_pk) { r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json); @@ -1109,7 +1218,7 @@ int decrypt_credential_and_warn( return log_error_errno(r, "Failed to load pcr signature: %m"); } - if (is_tpm2_absent) { + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) { /* So this is a credential encrypted with a zero length key. We support this to cover for the * case where neither a host key not a TPM2 are available (specifically: initrd environments * where the host key is not yet accessible and no TPM2 chip exists at all), to minimize @@ -1129,8 +1238,19 @@ int decrypt_credential_and_warn( log_debug("Credential uses fixed key for use when TPM2 is absent, and TPM2 indeed is absent. Accepting."); } + if (with_scope) { + if (!uid_is_valid(uid)) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to a user, but no user selected."); + } else { + /* Refuse to unlock system credentials if user scope is requested. */ + if (uid_is_valid(uid) && !FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE)) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Encrypted file is scoped to the system, but user scope selected."); + + uid = UID_INVALID; + } + /* Now we know the minimum header size */ - if (input_size < offsetof(struct encrypted_credential_header, iv)) + if (input->iov_len < offsetof(struct encrypted_credential_header, iv)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); /* Verify some basic header values */ @@ -1145,10 +1265,11 @@ int decrypt_credential_and_warn( /* Ensure we have space for the full header now (we don't know the size of the name hence this is a * lower limit only) */ - if (input_size < + if (input->iov_len < ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + ALIGN8(with_tpm2 ? offsetof(struct tpm2_credential_header, policy_hash_and_blob) : 0) + ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1157,7 +1278,7 @@ int decrypt_credential_and_warn( if (with_tpm2) { #if HAVE_TPM2 - struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input + p); + struct tpm2_credential_header* t = (struct tpm2_credential_header*) ((uint8_t*) input->iov_base + p); struct tpm2_public_key_credential_header *z = NULL; if (!TPM2_PCR_MASK_VALID(t->pcr_mask)) @@ -1173,10 +1294,11 @@ int decrypt_credential_and_warn( /* Ensure we have space for the full TPM2 header now (still don't know the name, and its size * though, hence still just a lower limit test only) */ - if (input_size < - ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + + if (input->iov_len < + p + ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) + ALIGN8(with_tpm2_pk ? offsetof(struct tpm2_public_key_credential_header, data) : 0) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1186,17 +1308,17 @@ int decrypt_credential_and_warn( le32toh(t->policy_hash_size)); if (with_tpm2_pk) { - z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input + p); + z = (struct tpm2_public_key_credential_header*) ((uint8_t*) input->iov_base + p); if (!TPM2_PCR_MASK_VALID(le64toh(z->pcr_mask)) || le64toh(z->pcr_mask) == 0) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range."); if (le32toh(z->size) > PUBLIC_KEY_MAX) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected public key size."); - if (input_size < - ALIGN8(offsetof(struct encrypted_credential_header, iv) + le32toh(h->iv_size)) + - ALIGN8(offsetof(struct tpm2_credential_header, policy_hash_and_blob) + le32toh(t->blob_size) + le32toh(t->policy_hash_size)) + + if (input->iov_len < + p + ALIGN8(offsetof(struct tpm2_public_key_credential_header, data) + le32toh(z->size)) + + ALIGN8(with_scope ? sizeof(struct scoped_credential_header) : 0) + ALIGN8(offsetof(struct metadata_credential_header, name)) + le32toh(h->tag_size)) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); @@ -1215,21 +1337,16 @@ int decrypt_credential_and_warn( r = tpm2_unseal(tpm2_context, le64toh(t->pcr_mask), le16toh(t->pcr_bank), - z ? z->data : NULL, - z ? le32toh(z->size) : 0, + z ? &IOVEC_MAKE(z->data, le32toh(z->size)) : NULL, z ? le64toh(z->pcr_mask) : 0, signature_json, /* pin= */ NULL, /* pcrlock_policy= */ NULL, le16toh(t->primary_alg), - t->policy_hash_and_blob, - 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); + &IOVEC_MAKE(t->policy_hash_and_blob, le32toh(t->blob_size)), + &IOVEC_MAKE(t->policy_hash_and_blob + le32toh(t->blob_size), le32toh(t->policy_hash_size)), + /* srk= */ NULL, + &tpm2_key); if (r < 0) return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); #else @@ -1237,19 +1354,38 @@ int decrypt_credential_and_warn( #endif } + if (with_scope) { + struct scoped_credential_header* sh = (struct scoped_credential_header*) ((uint8_t*) input->iov_base + p); + + if (le64toh(sh->flags) != SCOPE_HASH_DATA_BASE_FLAGS) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Scoped credential with unsupported flags."); + + if (input->iov_len < + p + + sizeof(struct scoped_credential_header) + + ALIGN8(offsetof(struct metadata_credential_header, name)) + + le32toh(h->tag_size)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Encrypted file too short."); + + p += sizeof(struct scoped_credential_header); + } + if (with_host_key) { - r = get_credential_host_secret( - 0, - &host_key, - &host_key_size); + r = get_credential_host_secret(/* flags= */ 0, &host_key); if (r < 0) return log_error_errno(r, "Failed to determine local credential key: %m"); } - if (is_tpm2_absent) + if (with_null && !FLAGS_SET(flags, CREDENTIAL_ALLOW_NULL)) log_warning("Warning: using a null key for decryption and authentication. Confidentiality or authenticity are not provided."); - sha256_hash_host_and_tpm2_key(host_key, host_key_size, tpm2_key, tpm2_key_size, md); + sha256_hash_host_and_tpm2_key(&host_key, &tpm2_key, md); + + if (with_scope) { + r = mangle_uid_into_key(uid, md); + if (r < 0) + return r; + } assert_se(cc = EVP_aes_256_gcm()); @@ -1276,41 +1412,41 @@ int decrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set IV and key: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_DecryptUpdate(context, NULL, &added, input, p) != 1) + if (EVP_DecryptUpdate(context, NULL, &added, input->iov_base, p) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to write AAD data: %s", ERR_error_string(ERR_get_error(), NULL)); - plaintext = malloc(input_size - p - le32toh(h->tag_size)); - if (!plaintext) + plaintext.iov_base = malloc(input->iov_len - p - le32toh(h->tag_size)); + if (!plaintext.iov_base) return -ENOMEM; if (EVP_DecryptUpdate( context, - plaintext, + plaintext.iov_base, &added, - (uint8_t*) input + p, - input_size - p - le32toh(h->tag_size)) != 1) + (uint8_t*) input->iov_base + p, + input->iov_len - p - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt data: %s", ERR_error_string(ERR_get_error(), NULL)); assert(added >= 0); - assert((size_t) added <= input_size - p - le32toh(h->tag_size)); - plaintext_size = added; + assert((size_t) added <= input->iov_len - p - le32toh(h->tag_size)); + plaintext.iov_len = added; - if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input + input_size - le32toh(h->tag_size)) != 1) + if (EVP_CIPHER_CTX_ctrl(context, EVP_CTRL_GCM_SET_TAG, le32toh(h->tag_size), (uint8_t*) input->iov_base + input->iov_len - le32toh(h->tag_size)) != 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set tag: %s", ERR_error_string(ERR_get_error(), NULL)); - if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext + plaintext_size, &added) != 1) + if (EVP_DecryptFinal_ex(context, (uint8_t*) plaintext.iov_base + plaintext.iov_len, &added) != 1) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Decryption failed (incorrect key?): %s", ERR_error_string(ERR_get_error(), NULL)); - plaintext_size += added; + plaintext.iov_len += added; - if (plaintext_size < ALIGN8(offsetof(struct metadata_credential_header, name))) + if (plaintext.iov_len < ALIGN8(offsetof(struct metadata_credential_header, name))) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete."); - m = plaintext; + m = plaintext.iov_base; if (le64toh(m->timestamp) != USEC_INFINITY && le64toh(m->not_after) != USEC_INFINITY && @@ -1321,7 +1457,7 @@ int decrypt_credential_and_warn( return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Embedded credential name too long, refusing."); hs = ALIGN8(offsetof(struct metadata_credential_header, name) + le32toh(m->name_size)); - if (plaintext_size < hs) + if (plaintext.iov_len < hs) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Metadata header incomplete."); if (le32toh(m->name_size) > 0) { @@ -1336,7 +1472,7 @@ int decrypt_credential_and_warn( if (validate_name && !streq(embedded_name, validate_name)) { - r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NAME"); + r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NAME"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NAME: %m"); if (r != 0) @@ -1352,7 +1488,7 @@ int decrypt_credential_and_warn( if (le64toh(m->not_after) != USEC_INFINITY && le64toh(m->not_after) < validate_timestamp) { - r = getenv_bool_secure("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER"); + r = secure_getenv_bool("SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_CREDENTIAL_VALIDATE_NOT_AFTER: %m"); if (r != 0) @@ -1363,33 +1499,245 @@ int decrypt_credential_and_warn( } if (ret) { - char *without_metadata; + _cleanup_(iovec_done_erase) struct iovec without_metadata = {}; - without_metadata = memdup((uint8_t*) plaintext + hs, plaintext_size - hs); - if (!without_metadata) + without_metadata.iov_len = plaintext.iov_len - hs; + without_metadata.iov_base = memdup_suffix0((uint8_t*) plaintext.iov_base + hs, without_metadata.iov_len); + if (!without_metadata.iov_base) return log_oom(); - *ret = without_metadata; + *ret = TAKE_STRUCT(without_metadata); } - if (ret_size) - *ret_size = plaintext_size - hs; - return 0; } #else -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size) { +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size) { +int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size) { +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Support for encrypted credentials not available."); } #endif + +int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(input && iovec_is_valid(input)); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials"); + if (r < 0) + return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m"); + + /* Mark anything we get from the service as sensitive, given that it might use a NULL cypher, at least in theory */ + r = varlink_set_input_sensitive(vl); + if (r < 0) + return log_error_errno(r, "Failed to enable sensitive Varlink input: %m"); + + /* Create the input data blob object separately, so that we can mark it as sensitive */ + _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL; + r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input)); + if (r < 0) + return log_error_errno(r, "Failed to create input object: %m"); + + json_variant_sensitive(jinput); + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + const char *error_id = NULL; + r = varlink_callb(vl, + "io.systemd.Credentials.Encrypt", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(name, "name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("data", JSON_BUILD_VARIANT(jinput)), + JSON_BUILD_PAIR_CONDITION(timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(timestamp)), + JSON_BUILD_PAIR_CONDITION(not_after != USEC_INFINITY, "notAfter", JSON_BUILD_UNSIGNED(not_after)), + JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)))); + if (r < 0) + return log_error_errno(r, "Failed to call Encrypt() varlink call."); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Credentials.NoSuchUser")) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user."); + + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to encrypt: %s", error_id); + } + + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "blob", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY }, + {}, + }, + JSON_LOG|JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + if (r < 0) + return r; + + return 0; +} + +int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(input && iovec_is_valid(input)); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.Credentials"); + if (r < 0) + return log_error_errno(r, "Failed to connect to io.systemd.Credentials: %m"); + + r = varlink_set_input_sensitive(vl); + if (r < 0) + return log_error_errno(r, "Failed to enable sensitive Varlink input: %m"); + + /* Create the input data blob object separately, so that we can mark it as sensitive (it's supposed + * to be encrypted, but who knows maybe it uses the NULL cypher). */ + _cleanup_(json_variant_unrefp) JsonVariant *jinput = NULL; + r = json_build(&jinput, JSON_BUILD_IOVEC_BASE64(input)); + if (r < 0) + return log_error_errno(r, "Failed to create input object: %m"); + + json_variant_sensitive(jinput); + + _cleanup_(json_variant_unrefp) JsonVariant *reply = NULL; + const char *error_id = NULL; + r = varlink_callb(vl, + "io.systemd.Credentials.Decrypt", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(validate_name, "name", JSON_BUILD_STRING(validate_name)), + JSON_BUILD_PAIR("blob", JSON_BUILD_VARIANT(jinput)), + JSON_BUILD_PAIR_CONDITION(validate_timestamp != USEC_INFINITY, "timestamp", JSON_BUILD_UNSIGNED(validate_timestamp)), + JSON_BUILD_PAIR_CONDITION(!FLAGS_SET(flags, CREDENTIAL_ANY_SCOPE), "scope", JSON_BUILD_STRING(uid_is_valid(uid) ? "user" : "system")), + JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)))); + if (r < 0) + return log_error_errno(r, "Failed to call Decrypt() varlink call."); + if (!isempty(error_id)) { + if (streq(error_id, "io.systemd.Credentials.BadFormat")) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Bad credential format."); + if (streq(error_id, "io.systemd.Credentials.NameMismatch")) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Name in credential doesn't match expectations."); + if (streq(error_id, "io.systemd.Credentials.TimeMismatch")) + return log_error_errno(SYNTHETIC_ERRNO(ESTALE), "Outside of credential validity time window."); + if (streq(error_id, "io.systemd.Credentials.NoSuchUser")) + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "No such user."); + if (streq(error_id, "io.systemd.Credentials.BadScope")) + return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Scope mismtach."); + + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to decrypt: %s", error_id); + } + + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "data", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, PTR_TO_SIZE(ret), JSON_MANDATORY }, + {}, + }, + JSON_LOG|JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + if (r < 0) + return r; + + return 0; +} + +static int pick_up_credential_one( + int credential_dir_fd, + const char *credential_name, + const PickUpCredential *table_entry) { + + _cleanup_free_ char *fn = NULL, *target_path = NULL; + const char *e; + int r; + + assert(credential_dir_fd >= 0); + assert(credential_name); + assert(table_entry); + + e = startswith(credential_name, table_entry->credential_prefix); + if (!e) + return 0; /* unmatched */ + + fn = strjoin(e, table_entry->filename_suffix); + if (!fn) + return log_oom(); + + if (!filename_is_valid(fn)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), + "Passed credential '%s' would result in invalid filename '%s'.", + credential_name, fn); + + r = mkdir_p_label(table_entry->target_dir, 0755); + if (r < 0) + return log_warning_errno(r, "Failed to create '%s': %m", table_entry->target_dir); + + target_path = path_join(table_entry->target_dir, fn); + if (!target_path) + return log_oom(); + + r = copy_file_at( + credential_dir_fd, credential_name, + AT_FDCWD, target_path, + /* open_flags= */ 0, + 0644, + /* flags= */ 0); + if (r < 0) + return log_warning_errno(r, "Failed to copy credential %s → file %s: %m", + credential_name, target_path); + + log_info("Installed %s from credential.", target_path); + return 1; /* done */ +} + +int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry) { + _cleanup_close_ int credential_dir_fd = -EBADF; + int r, ret = 0; + + assert(table); + assert(n_table_entry > 0); + + credential_dir_fd = open_credentials_dir(); + if (IN_SET(credential_dir_fd, -ENXIO, -ENOENT)) { + /* Credential env var not set, or dir doesn't exist. */ + log_debug("No credentials found."); + return 0; + } + if (credential_dir_fd < 0) + return log_error_errno(credential_dir_fd, "Failed to open credentials directory: %m"); + + _cleanup_free_ DirectoryEntries *des = NULL; + r = readdir_all(credential_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, &des); + if (r < 0) + return log_error_errno(r, "Failed to enumerate credentials: %m"); + + FOREACH_ARRAY(i, des->entries, des->n_entries) { + struct dirent *de = *i; + + if (de->d_type != DT_REG) + continue; + + FOREACH_ARRAY(t, table, n_table_entry) { + r = pick_up_credential_one(credential_dir_fd, de->d_name, t); + if (r != 0) { + RET_GATHER(ret, r); + break; /* Done, or failed. Let's move to the next credential. */ + } + } + } + + return ret; +} diff --git a/src/shared/creds-util.h b/src/shared/creds-util.h index 5e39a6a..b80755b 100644 --- a/src/shared/creds-util.h +++ b/src/shared/creds-util.h @@ -31,6 +31,8 @@ bool credential_glob_valid(const char *s); int get_credentials_dir(const char **ret); int get_encrypted_credentials_dir(const char **ret); +int open_credentials_dir(void); + /* Where creds have been passed to the system */ #define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system" #define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted" @@ -51,21 +53,31 @@ typedef enum CredentialSecretFlags { CREDENTIAL_SECRET_FAIL_ON_TEMPORARY_FS = 1 << 2, } CredentialSecretFlags; -int get_credential_host_secret(CredentialSecretFlags flags, void **ret, size_t *ret_size); +int get_credential_host_secret(CredentialSecretFlags flags, struct iovec *ret); int get_credential_user_password(const char *username, char **ret_password, bool *ret_is_hashed); +typedef enum CredentialFlags { + CREDENTIAL_ALLOW_NULL = 1 << 0, /* allow decryption of NULL key, even if TPM is around */ + CREDENTIAL_ANY_SCOPE = 1 << 1, /* allow decryption of both system and user credentials */ +} CredentialFlags; + /* The four modes we support: keyed only by on-disk key, only by TPM2 HMAC key, and by the combination of * both, as well as one with a fixed zero length key if TPM2 is missing (the latter of course provides no * authenticity or confidentiality, but is still useful for integrity protection, and makes things simpler * for us to handle). */ #define CRED_AES256_GCM_BY_HOST SD_ID128_MAKE(5a,1c,6a,86,df,9d,40,96,b1,d5,a6,5e,08,62,f1,9a) +#define CRED_AES256_GCM_BY_HOST_SCOPED SD_ID128_MAKE(55,b9,ed,1d,38,59,4d,43,a8,31,9d,2e,bb,33,2a,c6) #define CRED_AES256_GCM_BY_TPM2_HMAC SD_ID128_MAKE(0c,7c,c0,7b,11,76,45,91,9c,4b,0b,ea,08,bc,20,fe) #define CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK SD_ID128_MAKE(fa,f7,eb,93,41,e3,41,2c,a1,a4,36,f9,5a,29,36,2f) #define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC SD_ID128_MAKE(93,a8,94,09,48,74,44,90,90,ca,f2,fc,93,ca,b5,53) +#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED \ + SD_ID128_MAKE(ef,4a,c1,36,79,a9,48,0e,a7,db,68,89,7f,9f,16,5d) #define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK \ SD_ID128_MAKE(af,49,50,a8,49,13,4e,b1,a7,38,46,30,4f,f3,0c,05) -#define CRED_AES256_GCM_BY_TPM2_ABSENT SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb) +#define CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED \ + SD_ID128_MAKE(ad,bc,4c,a3,ef,b6,42,01,ba,88,1b,6f,2e,40,95,ea) +#define CRED_AES256_GCM_BY_NULL SD_ID128_MAKE(05,84,69,da,f6,f5,43,24,80,05,49,da,0f,8e,a2,fb) /* Two special IDs to pick a general automatic mode (i.e. tpm2+host if TPM2 exists, only host otherwise) or * an initrd-specific automatic mode (i.e. tpm2 if firmware can do it, otherwise fixed zero-length key, and @@ -74,6 +86,18 @@ int get_credential_user_password(const char *username, char **ret_password, bool * with an underscore. */ #define _CRED_AUTO SD_ID128_MAKE(a2,19,cb,07,85,b2,4c,04,b1,6d,18,ca,b9,d2,ee,01) #define _CRED_AUTO_INITRD SD_ID128_MAKE(02,dc,8e,de,3a,02,43,ab,a9,ec,54,9c,05,e6,a0,71) +#define _CRED_AUTO_SCOPED SD_ID128_MAKE(23,88,96,85,6f,74,48,8a,9c,78,6f,6a,b0,e7,3b,6a) + +int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); +int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); + +int ipc_encrypt_credential(const char *name, usec_t timestamp, usec_t not_after, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); +int ipc_decrypt_credential(const char *validate_name, usec_t validate_timestamp, uid_t uid, const struct iovec *input, CredentialFlags flags, struct iovec *ret); + +typedef struct PickUpCredential { + const char *credential_prefix; + const char *target_dir; + const char *filename_suffix; +} PickUpCredential; -int encrypt_credential_and_warn(sd_id128_t with_key, const char *name, usec_t timestamp, usec_t not_after, const char *tpm2_device, uint32_t tpm2_hash_pcr_mask, const char *tpm2_pubkey_path, uint32_t tpm2_pubkey_pcr_mask, const void *input, size_t input_size, void **ret, size_t *ret_size); -int decrypt_credential_and_warn(const char *validate_name, usec_t validate_timestamp, const char *tpm2_device, const char *tpm2_signature_path, const void *input, size_t input_size, void **ret, size_t *ret_size); +int pick_up_credentials(const PickUpCredential *table, size_t n_table_entry); diff --git a/src/shared/cryptsetup-fido2.c b/src/shared/cryptsetup-fido2.c index 285b82a..5ab5cef 100644 --- a/src/shared/cryptsetup-fido2.c +++ b/src/shared/cryptsetup-fido2.c @@ -24,11 +24,11 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { _cleanup_(erase_and_freep) char *envpw = NULL; _cleanup_strv_free_erase_ char **pins = NULL; @@ -38,11 +38,11 @@ int acquire_fido2_key( size_t salt_size; int r; - if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && headless) + if ((required & (FIDO2ENROLL_PIN | FIDO2ENROLL_UP | FIDO2ENROLL_UV)) && FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Local verification is required to unlock this volume, but the 'headless' parameter was set."); - ask_password_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; + askpw_flags |= ASK_PASSWORD_PUSH_CACHE | ASK_PASSWORD_ACCEPT_CACHED; assert(cid); assert(key_file || key_data); @@ -115,15 +115,22 @@ int acquire_fido2_key( device_exists = true; /* that a PIN is needed/wasn't correct means that we managed to * talk to a device */ - if (headless) + if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the '$PIN' environment variable."); + static const AskPasswordRequest req = { + .message = "Please enter security token PIN:", + .icon = "drive-harddisk", + .keyring = "fido2-pin", + .credential = "cryptsetup.fido2-pin", + }; + pins = strv_free_erase(pins); - r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, ask_password_flags, &pins); + r = ask_password_auto(&req, until, askpw_flags, &pins); if (r < 0) return log_error_errno(r, "Failed to ask for user password: %m"); - ask_password_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + askpw_flags &= ~ASK_PASSWORD_ACCEPT_CACHED; } } @@ -133,10 +140,10 @@ int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { _cleanup_free_ void *cid = NULL; size_t cid_size = 0; @@ -150,7 +157,7 @@ int acquire_fido2_key_auto( /* Loads FIDO2 metadata from LUKS2 JSON token headers. */ - for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token ++) { + for (int token = 0; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; JsonVariant *w; _cleanup_free_ void *salt = NULL; @@ -177,7 +184,7 @@ int acquire_fido2_key_auto( return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "FIDO2 token data lacks 'fido2-credential' field."); - r = unbase64mem(json_variant_string(w), SIZE_MAX, &cid, &cid_size); + r = unbase64mem(json_variant_string(w), &cid, &cid_size); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid base64 data in 'fido2-credential' field."); @@ -189,7 +196,7 @@ int acquire_fido2_key_auto( assert(!salt); assert(salt_size == 0); - r = unbase64mem(json_variant_string(w), SIZE_MAX, &salt, &salt_size); + r = unbase64mem(json_variant_string(w), &salt, &salt_size); if (r < 0) return log_error_errno(r, "Failed to decode base64 encoded salt."); @@ -254,10 +261,11 @@ int acquire_fido2_key_auto( /* key_file_offset= */ 0, salt, salt_size, until, - headless, required, - ret_decrypted_key, ret_decrypted_key_size, - ask_password_flags); + "cryptsetup.fido2-pin", + askpw_flags, + ret_decrypted_key, + ret_decrypted_key_size); if (ret == 0) break; } diff --git a/src/shared/cryptsetup-fido2.h b/src/shared/cryptsetup-fido2.h index d96bb40..bd25566 100644 --- a/src/shared/cryptsetup-fido2.h +++ b/src/shared/cryptsetup-fido2.h @@ -23,11 +23,11 @@ int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags); + size_t *ret_decrypted_key_size); int acquire_fido2_key_auto( struct crypt_device *cd, @@ -35,10 +35,10 @@ int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags); + size_t *ret_decrypted_key_size); #else @@ -55,11 +55,11 @@ static inline int acquire_fido2_key( const void *key_data, size_t key_data_size, usec_t until, - bool headless, Fido2EnrollFlags required, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); @@ -71,10 +71,10 @@ static inline int acquire_fido2_key_auto( const char *friendly_name, const char *fido2_device, usec_t until, - bool headless, + const char *askpw_credential, + AskPasswordFlags askpw_flags, void **ret_decrypted_key, - size_t *ret_decrypted_key_size, - AskPasswordFlags ask_password_flags) { + size_t *ret_decrypted_key_size) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "FIDO2 token support not available."); diff --git a/src/shared/cryptsetup-tpm2.c b/src/shared/cryptsetup-tpm2.c new file mode 100644 index 0000000..bfd7d3a --- /dev/null +++ b/src/shared/cryptsetup-tpm2.c @@ -0,0 +1,302 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "cryptsetup-tpm2.h" +#include "env-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "json.h" +#include "parse-util.h" +#include "random-util.h" +#include "sha256.h" +#include "tpm2-util.h" + +static int get_pin( + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + char **ret_pin_str) { + _cleanup_(erase_and_freep) char *pin_str = NULL; + _cleanup_strv_free_erase_ char **pin = NULL; + int r; + + assert(ret_pin_str); + + r = getenv_steal_erase("PIN", &pin_str); + if (r < 0) + return log_error_errno(r, "Failed to acquire PIN from environment: %m"); + if (!r) { + if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) + return log_error_errno( + SYNTHETIC_ERRNO(ENOPKG), + "PIN querying disabled via 'headless' option. " + "Use the '$PIN' environment variable."); + + AskPasswordRequest req = { + .message = "Please enter TPM2 PIN:", + .icon = "drive-harddisk", + .keyring = "tpm2-pin", + .credential = askpw_credential, + }; + + pin = strv_free_erase(pin); + r = ask_password_auto(&req, until, askpw_flags, &pin); + if (r < 0) + return log_error_errno(r, "Failed to ask for user pin: %m"); + assert(strv_length(pin) == 1); + + pin_str = strdup(pin[0]); + if (!pin_str) + return log_oom(); + } + + *ret_pin_str = TAKE_PTR(pin_str); + + return r; +} + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key) { + + _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; + _cleanup_free_ void *loaded_blob = NULL; + _cleanup_free_ char *auto_device = NULL; + struct iovec blob; + int r; + + assert(iovec_is_valid(salt)); + + if (!device) { + r = tpm2_find_device_auto(&auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) + return log_error_errno(r, "Could not find TPM2 device: %m"); + + device = auto_device; + } + + if (iovec_is_set(key_data)) + blob = *key_data; + else { + _cleanup_free_ char *bindname = NULL; + + /* If we read the salt via AF_UNIX, make this client recognizable */ + if (asprintf(&bindname, "@%" PRIx64"/cryptsetup-tpm2/%s", random_u64(), volume_name) < 0) + return log_oom(); + + r = read_full_file_full( + AT_FDCWD, key_file, + key_file_offset == 0 ? UINT64_MAX : key_file_offset, + key_file_size == 0 ? SIZE_MAX : key_file_size, + READ_FULL_FILE_CONNECT_SOCKET, + bindname, + (char**) &loaded_blob, &blob.iov_len); + if (r < 0) + return r; + + blob.iov_base = loaded_blob; + } + + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) + return log_error_errno(r, "Failed to load pcr signature: %m"); + } + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy pcrlock_policy = {}; + + if (FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK)) { + r = tpm2_pcrlock_policy_load(pcrlock_path, &pcrlock_policy); + if (r < 0) + return r; + if (r == 0) { + /* Not found? Then search among passed credentials */ + r = tpm2_pcrlock_policy_from_credentials(srk, pcrlock_nv, &pcrlock_policy); + if (r < 0) + return r; + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Couldn't find pcrlock policy for volume."); + } + } + + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new_or_warn(device, &tpm2_context); + if (r < 0) + return r; + + if (!(flags & TPM2_FLAGS_USE_PIN)) { + r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, + pubkey_pcr_mask, + signature_json, + /* pin= */ NULL, + FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, + primary_alg, + &blob, + policy_hash, + srk, + ret_decrypted_key); + if (r < 0) + return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + return r; + } + + for (int i = 5;; i--) { + _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; + + if (i <= 0) + return -EACCES; + + r = get_pin(until, askpw_credential, askpw_flags, &pin_str); + if (r < 0) + return r; + + if (iovec_is_set(salt)) { + uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; + CLEANUP_ERASE(salted_pin); + + r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt->iov_base, salt->iov_len, salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to perform PBKDF2: %m"); + + r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); + if (r < 0) + return log_error_errno(r, "Failed to base64 encode salted pin: %m"); + } else + /* no salting needed, backwards compat with non-salted pins */ + b64_salted_pin = TAKE_PTR(pin_str); + + r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, + pubkey_pcr_mask, + signature_json, + b64_salted_pin, + FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK) ? &pcrlock_policy : NULL, + primary_alg, + &blob, + policy_hash, + srk, + ret_decrypted_key); + if (r < 0) { + log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + /* We get this error in case there is an authentication policy mismatch. This should + * not happen, but this avoids confusing behavior, just in case. */ + if (!IN_SET(r, -EPERM, -ENOLCK)) + continue; + } + + return r; + } +} + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { + + int r, token; + + assert(cd); + + for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags; + int keyslot; + + r = cryptsetup_get_token_as_json(cd, token, "systemd-tpm2", &v); + if (IN_SET(r, -ENOENT, -EINVAL, -EMEDIUMTYPE)) + continue; + if (r < 0) + return log_error_errno(r, "Failed to read JSON token data off disk: %m"); + + r = tpm2_parse_luks2_json( + v, + &keyslot, + &hash_pcr_mask, + &pcr_bank, + &pubkey, + &pubkey_pcr_mask, + &primary_alg, + &blob, + &policy_hash, + &salt, + &srk, + &pcrlock_nv, + &flags); + if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ + continue; + if (r < 0) + return log_error_errno(r, "Failed to parse TPM2 JSON data: %m"); + + if (search_pcr_mask == UINT32_MAX || + search_pcr_mask == hash_pcr_mask) { + + if (start_token <= 0) + log_info("Automatically discovered security TPM2 token unlocks volume."); + + *ret_hash_pcr_mask = hash_pcr_mask; + *ret_pcr_bank = pcr_bank; + *ret_pubkey = TAKE_STRUCT(pubkey); + *ret_pubkey_pcr_mask = pubkey_pcr_mask; + *ret_primary_alg = primary_alg; + *ret_blob = TAKE_STRUCT(blob); + *ret_policy_hash = TAKE_STRUCT(policy_hash); + *ret_salt = TAKE_STRUCT(salt); + *ret_keyslot = keyslot; + *ret_token = token; + *ret_srk = TAKE_STRUCT(srk); + *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); + *ret_flags = flags; + return 0; + } + + /* PCR mask doesn't match what is configured, ignore this entry, let's see next */ + } + + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No valid TPM2 token data found."); +} diff --git a/src/shared/cryptsetup-tpm2.h b/src/shared/cryptsetup-tpm2.h new file mode 100644 index 0000000..b9905f4 --- /dev/null +++ b/src/shared/cryptsetup-tpm2.h @@ -0,0 +1,108 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "ask-password-api.h" +#include "cryptsetup-util.h" +#include "log.h" +#include "time-util.h" +#include "tpm2-util.h" + +#if HAVE_TPM2 + +int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key); + +int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token); + +#else + +static inline int acquire_tpm2_key( + const char *volume_name, + const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const struct iovec *pubkey, + uint32_t pubkey_pcr_mask, + const char *signature_path, + const char *pcrlock_path, + uint16_t primary_alg, + const char *key_file, + size_t key_file_size, + uint64_t key_file_offset, + const struct iovec *key_data, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, + TPM2Flags flags, + usec_t until, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + struct iovec *ret_decrypted_key) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +static inline int find_tpm2_auto_data( + struct crypt_device *cd, + uint32_t search_pcr_mask, + int start_token, + uint32_t *ret_hash_pcr_mask, + uint16_t *ret_pcr_bank, + struct iovec *ret_pubkey, + uint32_t *ret_pubkey_pcr_mask, + uint16_t *ret_primary_alg, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { + + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 support not available."); +} + +#endif diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index ab5764d..288e6e8 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -9,58 +9,62 @@ #if HAVE_LIBCRYPTSETUP static void *cryptsetup_dl = NULL; -int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags); +DLSYM_FUNCTION(crypt_activate_by_passphrase); #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY -int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags); +DLSYM_FUNCTION(crypt_activate_by_signed_key); #endif -int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags); -int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags); -int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params); -void (*sym_crypt_free)(struct crypt_device *cd); -const char *(*sym_crypt_get_cipher)(struct crypt_device *cd); -const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd); -uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd); -const char *(*sym_crypt_get_device_name)(struct crypt_device *cd); -const char *(*sym_crypt_get_dir)(void); -const char *(*sym_crypt_get_type)(struct crypt_device *cd); -const char *(*sym_crypt_get_uuid)(struct crypt_device *cd); -int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp); -int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd); -int (*sym_crypt_init)(struct crypt_device **cd, const char *device); -int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name); -int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size); -int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot); -int (*sym_crypt_keyslot_max)(const char *type); -int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params); -int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size); -int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size); -int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device); -void (*sym_crypt_set_debug_level)(int level); -void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr); +DLSYM_FUNCTION(crypt_activate_by_volume_key); +DLSYM_FUNCTION(crypt_deactivate_by_name); +DLSYM_FUNCTION(crypt_format); +DLSYM_FUNCTION(crypt_free); +DLSYM_FUNCTION(crypt_get_cipher); +DLSYM_FUNCTION(crypt_get_cipher_mode); +DLSYM_FUNCTION(crypt_get_data_offset); +DLSYM_FUNCTION(crypt_get_device_name); +DLSYM_FUNCTION(crypt_get_dir); +DLSYM_FUNCTION(crypt_get_type); +DLSYM_FUNCTION(crypt_get_uuid); +DLSYM_FUNCTION(crypt_get_verity_info); +DLSYM_FUNCTION(crypt_get_volume_key_size); +DLSYM_FUNCTION(crypt_init); +DLSYM_FUNCTION(crypt_init_by_name); +DLSYM_FUNCTION(crypt_keyslot_add_by_volume_key); +DLSYM_FUNCTION(crypt_keyslot_destroy); +DLSYM_FUNCTION(crypt_keyslot_max); +DLSYM_FUNCTION(crypt_load); +DLSYM_FUNCTION(crypt_resize); +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY +DLSYM_FUNCTION(crypt_resume_by_volume_key); +#endif +DLSYM_FUNCTION(crypt_set_data_device); +DLSYM_FUNCTION(crypt_set_debug_level); +DLSYM_FUNCTION(crypt_set_log_callback); #if HAVE_CRYPT_SET_METADATA_SIZE -int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size); +DLSYM_FUNCTION(crypt_set_metadata_size); #endif -int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf); -int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name); -int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json); -int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json); +DLSYM_FUNCTION(crypt_set_pbkdf_type); +DLSYM_FUNCTION(crypt_suspend); +DLSYM_FUNCTION(crypt_token_json_get); +DLSYM_FUNCTION(crypt_token_json_set); #if HAVE_CRYPT_TOKEN_MAX -int (*sym_crypt_token_max)(const char *type); +DLSYM_FUNCTION(crypt_token_max); #endif -crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); -int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +DLSYM_FUNCTION(crypt_token_status); +DLSYM_FUNCTION(crypt_volume_key_get); #if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE -int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +DLSYM_FUNCTION(crypt_reencrypt_init_by_passphrase); #endif #if HAVE_CRYPT_REENCRYPT -int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_FUNCTION(crypt_reencrypt); +REENABLE_WARNING; #endif -int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +DLSYM_FUNCTION(crypt_metadata_locking); #if HAVE_CRYPT_SET_DATA_OFFSET -int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +DLSYM_FUNCTION(crypt_set_data_offset); #endif -int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); -int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); +DLSYM_FUNCTION(crypt_header_restore); +DLSYM_FUNCTION(crypt_volume_key_keyring); /* Unfortunately libcryptsetup provides neither an environment variable to redirect where to look for token * modules, nor does it have an API to change the token lookup path at runtime. The maintainers suggest using @@ -248,6 +252,11 @@ int dlopen_cryptsetup(void) { DISABLE_WARNING_DEPRECATED_DECLARATIONS; + ELF_NOTE_DLOPEN("cryptsetup", + "Support for disk encryption, integrity, and authentication", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libcryptsetup.so.12"); + r = dlopen_many_sym_or_warn( &cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG, DLSYM_ARG(crypt_activate_by_passphrase), @@ -274,7 +283,9 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_keyslot_max), DLSYM_ARG(crypt_load), DLSYM_ARG(crypt_resize), - DLSYM_ARG(crypt_resume_by_passphrase), +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY + DLSYM_ARG(crypt_resume_by_volume_key), +#endif DLSYM_ARG(crypt_set_data_device), DLSYM_ARG(crypt_set_debug_level), DLSYM_ARG(crypt_set_log_callback), diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 5ff439d..f00ac36 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -2,6 +2,7 @@ #pragma once #include "alloc-util.h" +#include "dlfcn-util.h" #include "json.h" #include "macro.h" @@ -16,43 +17,45 @@ #define CRYPT_ACTIVATE_NO_WRITE_WORKQUEUE (1 << 25) #endif -extern int (*sym_crypt_activate_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size, uint32_t flags); +DLSYM_PROTOTYPE(crypt_activate_by_passphrase); #if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY -extern int (*sym_crypt_activate_by_signed_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, const char *signature, size_t signature_size, uint32_t flags); +DLSYM_PROTOTYPE(crypt_activate_by_signed_key); #endif -extern int (*sym_crypt_activate_by_volume_key)(struct crypt_device *cd, const char *name, const char *volume_key, size_t volume_key_size, uint32_t flags); -extern int (*sym_crypt_deactivate_by_name)(struct crypt_device *cd, const char *name, uint32_t flags); -extern int (*sym_crypt_format)(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, void *params); -extern void (*sym_crypt_free)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_cipher)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_cipher_mode)(struct crypt_device *cd); -extern uint64_t (*sym_crypt_get_data_offset)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_device_name)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_dir)(void); -extern const char *(*sym_crypt_get_type)(struct crypt_device *cd); -extern const char *(*sym_crypt_get_uuid)(struct crypt_device *cd); -extern int (*sym_crypt_get_verity_info)(struct crypt_device *cd, struct crypt_params_verity *vp); -extern int (*sym_crypt_get_volume_key_size)(struct crypt_device *cd); -extern int (*sym_crypt_init)(struct crypt_device **cd, const char *device); -extern int (*sym_crypt_init_by_name)(struct crypt_device **cd, const char *name); -extern int (*sym_crypt_keyslot_add_by_volume_key)(struct crypt_device *cd, int keyslot, const char *volume_key, size_t volume_key_size, const char *passphrase, size_t passphrase_size); -extern int (*sym_crypt_keyslot_destroy)(struct crypt_device *cd, int keyslot); -extern int (*sym_crypt_keyslot_max)(const char *type); -extern int (*sym_crypt_load)(struct crypt_device *cd, const char *requested_type, void *params); -extern int (*sym_crypt_resize)(struct crypt_device *cd, const char *name, uint64_t new_size); -extern int (*sym_crypt_resume_by_passphrase)(struct crypt_device *cd, const char *name, int keyslot, const char *passphrase, size_t passphrase_size); -extern int (*sym_crypt_set_data_device)(struct crypt_device *cd, const char *device); -extern void (*sym_crypt_set_debug_level)(int level); -extern void (*sym_crypt_set_log_callback)(struct crypt_device *cd, void (*log)(int level, const char *msg, void *usrptr), void *usrptr); +DLSYM_PROTOTYPE(crypt_activate_by_volume_key); +DLSYM_PROTOTYPE(crypt_deactivate_by_name); +DLSYM_PROTOTYPE(crypt_format); +DLSYM_PROTOTYPE(crypt_free); +DLSYM_PROTOTYPE(crypt_get_cipher); +DLSYM_PROTOTYPE(crypt_get_cipher_mode); +DLSYM_PROTOTYPE(crypt_get_data_offset); +DLSYM_PROTOTYPE(crypt_get_device_name); +DLSYM_PROTOTYPE(crypt_get_dir); +DLSYM_PROTOTYPE(crypt_get_type); +DLSYM_PROTOTYPE(crypt_get_uuid); +DLSYM_PROTOTYPE(crypt_get_verity_info); +DLSYM_PROTOTYPE(crypt_get_volume_key_size); +DLSYM_PROTOTYPE(crypt_init); +DLSYM_PROTOTYPE(crypt_init_by_name); +DLSYM_PROTOTYPE(crypt_keyslot_add_by_volume_key); +DLSYM_PROTOTYPE(crypt_keyslot_destroy); +DLSYM_PROTOTYPE(crypt_keyslot_max); +DLSYM_PROTOTYPE(crypt_load); +DLSYM_PROTOTYPE(crypt_resize); +#if HAVE_CRYPT_RESUME_BY_VOLUME_KEY +DLSYM_PROTOTYPE(crypt_resume_by_volume_key); +#endif +DLSYM_PROTOTYPE(crypt_set_data_device); +DLSYM_PROTOTYPE(crypt_set_debug_level); +DLSYM_PROTOTYPE(crypt_set_log_callback); #if HAVE_CRYPT_SET_METADATA_SIZE -extern int (*sym_crypt_set_metadata_size)(struct crypt_device *cd, uint64_t metadata_size, uint64_t keyslots_size); +DLSYM_PROTOTYPE(crypt_set_metadata_size); #endif -extern int (*sym_crypt_set_pbkdf_type)(struct crypt_device *cd, const struct crypt_pbkdf_type *pbkdf); -extern int (*sym_crypt_suspend)(struct crypt_device *cd, const char *name); -extern int (*sym_crypt_token_json_get)(struct crypt_device *cd, int token, const char **json); -extern int (*sym_crypt_token_json_set)(struct crypt_device *cd, int token, const char *json); +DLSYM_PROTOTYPE(crypt_set_pbkdf_type); +DLSYM_PROTOTYPE(crypt_suspend); +DLSYM_PROTOTYPE(crypt_token_json_get); +DLSYM_PROTOTYPE(crypt_token_json_set); #if HAVE_CRYPT_TOKEN_MAX -extern int (*sym_crypt_token_max)(const char *type); +DLSYM_PROTOTYPE(crypt_token_max); #else /* As a fallback, use the same hard-coded value libcryptsetup uses internally. */ static inline int crypt_token_max(_unused_ const char *type) { @@ -62,20 +65,22 @@ static inline int crypt_token_max(_unused_ const char *type) { } #define sym_crypt_token_max(type) crypt_token_max(type) #endif -extern crypt_token_info (*sym_crypt_token_status)(struct crypt_device *cd, int token, const char **type); -extern int (*sym_crypt_volume_key_get)(struct crypt_device *cd, int keyslot, char *volume_key, size_t *volume_key_size, const char *passphrase, size_t passphrase_size); +DLSYM_PROTOTYPE(crypt_token_status); +DLSYM_PROTOTYPE(crypt_volume_key_get); #if HAVE_CRYPT_REENCRYPT_INIT_BY_PASSPHRASE -extern int (*sym_crypt_reencrypt_init_by_passphrase)(struct crypt_device *cd, const char *name, const char *passphrase, size_t passphrase_size, int keyslot_old, int keyslot_new, const char *cipher, const char *cipher_mode, const struct crypt_params_reencrypt *params); +DLSYM_PROTOTYPE(crypt_reencrypt_init_by_passphrase); #endif #if HAVE_CRYPT_REENCRYPT -extern int (*sym_crypt_reencrypt)(struct crypt_device *cd, int (*progress)(uint64_t size, uint64_t offset, void *usrptr)); +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DLSYM_PROTOTYPE(crypt_reencrypt); +REENABLE_WARNING; #endif -extern int (*sym_crypt_metadata_locking)(struct crypt_device *cd, int enable); +DLSYM_PROTOTYPE(crypt_metadata_locking); #if HAVE_CRYPT_SET_DATA_OFFSET -extern int (*sym_crypt_set_data_offset)(struct crypt_device *cd, uint64_t data_offset); +DLSYM_PROTOTYPE(crypt_set_data_offset); #endif -extern int (*sym_crypt_header_restore)(struct crypt_device *cd, const char *requested_type, const char *backup_file); -extern int (*sym_crypt_volume_key_keyring)(struct crypt_device *cd, int enable); +DLSYM_PROTOTYPE(crypt_header_restore); +DLSYM_PROTOTYPE(crypt_volume_key_keyring); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); diff --git a/src/shared/daemon-util.c b/src/shared/daemon-util.c index 32180a1..aa386b4 100644 --- a/src/shared/daemon-util.c +++ b/src/shared/daemon-util.c @@ -4,6 +4,7 @@ #include "fd-util.h" #include "log.h" #include "string-util.h" +#include "time-util.h" static int notify_remove_fd_warn(const char *name) { int r; @@ -74,3 +75,18 @@ int notify_push_fdf(int fd, const char *format, ...) { return notify_push_fd(fd, name); } + +int notify_reloading_full(const char *status) { + int r; + + r = sd_notifyf(/* unset_environment = */ false, + "RELOADING=1\n" + "MONOTONIC_USEC=" USEC_FMT + "%s%s", + now(CLOCK_MONOTONIC), + status ? "\nSTATUS=" : "", strempty(status)); + if (r < 0) + return log_debug_errno(r, "Failed to notify service manager for reloading status: %m"); + + return 0; +} diff --git a/src/shared/daemon-util.h b/src/shared/daemon-util.h index 711885b..cbefa8d 100644 --- a/src/shared/daemon-util.h +++ b/src/shared/daemon-util.h @@ -10,7 +10,7 @@ #define NOTIFY_READY "READY=1\n" "STATUS=Processing requests..." #define NOTIFY_STOPPING "STOPPING=1\n" "STATUS=Shutting down..." -static inline const char *notify_start(const char *start, const char *stop) { +static inline const char* notify_start(const char *start, const char *stop) { if (start) (void) sd_notify(false, start); @@ -26,3 +26,8 @@ static inline void notify_on_cleanup(const char **p) { int notify_remove_fd_warnf(const char *format, ...) _printf_(1, 2); int close_and_notify_warn(int fd, const char *name); int notify_push_fdf(int fd, const char *format, ...) _printf_(2, 3); + +int notify_reloading_full(const char *status); +static inline int notify_reloading(void) { + return notify_reloading_full("Reloading configuration..."); +} diff --git a/src/shared/data-fd-util.c b/src/shared/data-fd-util.c index b939206..adc7d74 100644 --- a/src/shared/data-fd-util.c +++ b/src/shared/data-fd-util.c @@ -20,16 +20,15 @@ #include "tmpfile-util.h" /* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */ -#define DATA_FD_MEMORY_LIMIT (64U*1024U) +#define DATA_FD_MEMORY_LIMIT (64U * U64_KB) /* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */ -#define DATA_FD_TMP_LIMIT (1024U*1024U) +#define DATA_FD_TMP_LIMIT (1U * U64_MB) -int acquire_data_fd(const void *data, size_t size, unsigned flags) { - _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR; +int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags) { _cleanup_close_ int fd = -EBADF; - int isz = 0, r; ssize_t n; + int r; assert(data || size == 0); @@ -52,24 +51,25 @@ int acquire_data_fd(const void *data, size_t size, unsigned flags) { * It sucks a bit that depending on the situation we return very different objects here, but that's Linux I * figure. */ - if (size == 0 && ((flags & ACQUIRE_NO_DEV_NULL) == 0)) + if (size == SIZE_MAX) + size = strlen(data); + + if (size == 0 && !FLAGS_SET(flags, ACQUIRE_NO_DEV_NULL)) /* As a special case, return /dev/null if we have been called for an empty data block */ return RET_NERRNO(open("/dev/null", O_RDONLY|O_CLOEXEC|O_NOCTTY)); - if ((flags & ACQUIRE_NO_MEMFD) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_MEMFD)) { fd = memfd_new_and_seal("data-fd", data, size); - if (fd < 0) { - if (ERRNO_IS_NOT_SUPPORTED(fd)) - goto try_pipe; - + if (fd < 0 && !ERRNO_IS_NOT_SUPPORTED(fd)) return fd; - } - - return TAKE_FD(fd); + if (fd >= 0) + return TAKE_FD(fd); } -try_pipe: - if ((flags & ACQUIRE_NO_PIPE) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_PIPE)) { + _cleanup_close_pair_ int pipefds[2] = EBADF_PAIR; + int isz; + if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0) return -errno; @@ -106,7 +106,7 @@ try_pipe: } try_dev_shm: - if ((flags & ACQUIRE_NO_TMPFILE) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_TMPFILE)) { fd = open("/dev/shm", O_RDWR|O_TMPFILE|O_CLOEXEC, 0500); if (fd < 0) goto try_dev_shm_without_o_tmpfile; @@ -122,7 +122,7 @@ try_dev_shm: } try_dev_shm_without_o_tmpfile: - if ((flags & ACQUIRE_NO_REGULAR) == 0) { + if (!FLAGS_SET(flags, ACQUIRE_NO_REGULAR)) { char pattern[] = "/dev/shm/data-fd-XXXXXX"; fd = mkostemp_safe(pattern); @@ -179,7 +179,7 @@ int copy_data_fd(int fd) { * that we use the reported regular file size only as a hint, given that there are plenty special files in * /proc and /sys which report a zero file size but can be read from. */ - if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) { + if (!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_MEMORY_LIMIT) { /* Try a memfd first */ copy_fd = memfd_new("data-fd"); @@ -252,7 +252,7 @@ int copy_data_fd(int fd) { } /* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */ - if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) && + if ((!S_ISREG(st.st_mode) || (uint64_t) st.st_size < DATA_FD_TMP_LIMIT) && (DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) { off_t f; diff --git a/src/shared/data-fd-util.h b/src/shared/data-fd-util.h index 6d99209..d77e09f 100644 --- a/src/shared/data-fd-util.h +++ b/src/shared/data-fd-util.h @@ -2,15 +2,20 @@ #pragma once #include +#include -enum { +typedef enum DataFDFlags { ACQUIRE_NO_DEV_NULL = 1 << 0, ACQUIRE_NO_MEMFD = 1 << 1, ACQUIRE_NO_PIPE = 1 << 2, ACQUIRE_NO_TMPFILE = 1 << 3, ACQUIRE_NO_REGULAR = 1 << 4, -}; +} DataFDFlags; + +int acquire_data_fd_full(const void *data, size_t size, DataFDFlags flags); +static inline int acquire_data_fd(const void *data) { + return acquire_data_fd_full(data, SIZE_MAX, 0); +} -int acquire_data_fd(const void *data, size_t size, unsigned flags); int copy_data_fd(int fd); int memfd_clone_fd(int fd, const char *name, int mode); diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c index f7ed161..4b4b625 100644 --- a/src/shared/dev-setup.c +++ b/src/shared/dev-setup.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "dev-setup.h" #include "fd-util.h" +#include "fs-util.h" #include "label-util.h" #include "lock-util.h" #include "log.h" @@ -21,13 +22,15 @@ int lock_dev_console(void) { _cleanup_close_ int fd = -EBADF; int r; - fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + /* NB: We do not use O_NOFOLLOW here, because some container managers might place a symlink to some + * pty in /dev/console, in which case it should be fine to lock the target TTY. */ + fd = open_terminal("/dev/console", O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return fd; r = lock_generic(fd, LOCK_BSD, LOCK_EX); if (r < 0) - return log_error_errno(r, "Failed to lock /dev/console: %m"); + return r; return TAKE_FD(fd); } @@ -79,15 +82,11 @@ int make_inaccessible_nodes( uid_t uid, gid_t gid) { - static const struct { - const char *name; - mode_t mode; - } table[] = { - { "inaccessible", S_IFDIR | 0755 }, - { "inaccessible/reg", S_IFREG | 0000 }, - { "inaccessible/dir", S_IFDIR | 0000 }, - { "inaccessible/fifo", S_IFIFO | 0000 }, - { "inaccessible/sock", S_IFSOCK | 0000 }, + static const mode_t table[] = { + S_IFREG, + S_IFDIR, + S_IFIFO, + S_IFSOCK, /* The following two are likely to fail if we lack the privs for it (for example in an userns * environment, if CAP_SYS_MKNOD is missing, or if a device node policy prohibits creation of @@ -95,10 +94,13 @@ int make_inaccessible_nodes( * should implement falling back to use a different node then, for example * /inaccessible/sock, which is close enough in behaviour and semantics for most uses. */ - { "inaccessible/chr", S_IFCHR | 0000 }, - { "inaccessible/blk", S_IFBLK | 0000 }, + S_IFCHR, + S_IFBLK, + + /* NB: S_IFLNK is not listed here, as there is no such thing as an inaccessible symlink */ }; + _cleanup_close_ int parent_fd = -EBADF, inaccessible_fd = -EBADF; int r; if (!parent_dir) @@ -106,32 +108,48 @@ int make_inaccessible_nodes( BLOCK_WITH_UMASK(0000); + parent_fd = open(parent_dir, O_DIRECTORY|O_CLOEXEC|O_PATH, 0); + if (parent_fd < 0) + return -errno; + + inaccessible_fd = open_mkdir_at_full(parent_fd, "inaccessible", O_CLOEXEC, XO_LABEL, 0755); + if (inaccessible_fd < 0) + return inaccessible_fd; + /* Set up inaccessible (and empty) file nodes of all types. This are used to as mount sources for over-mounting * ("masking") file nodes that shall become inaccessible and empty for specific containers or services. We try * to lock down these nodes as much as we can, but otherwise try to match them as closely as possible with the * underlying file, i.e. in the best case we offer the same node type as the underlying node. */ - for (size_t i = 0; i < ELEMENTSOF(table); i++) { + FOREACH_ELEMENT(m, table) { _cleanup_free_ char *path = NULL; + mode_t inode_type = *m; + const char *fn; - path = path_join(parent_dir, table[i].name); + fn = inode_type_to_string(inode_type); + path = path_join(parent_dir, fn); if (!path) return log_oom(); - if (S_ISDIR(table[i].mode)) - r = mkdir_label(path, table[i].mode & 07777); + if (S_ISDIR(inode_type)) + r = mkdirat_label(inaccessible_fd, fn, 0000); else - r = mknod_label(path, table[i].mode, makedev(0, 0)); - if (r < 0) { + r = mknodat_label(inaccessible_fd, fn, inode_type | 0000, makedev(0, 0)); + if (r == -EEXIST) { + if (fchmodat(inaccessible_fd, fn, 0000, AT_SYMLINK_NOFOLLOW) < 0) + log_debug_errno(errno, "Failed to adjust access mode of existing inode '%s', ignoring: %m", path); + } else if (r < 0) { log_debug_errno(r, "Failed to create '%s', ignoring: %m", path); continue; } - if (uid != UID_INVALID || gid != GID_INVALID) { - if (lchown(path, uid, gid) < 0) - log_debug_errno(errno, "Failed to chown '%s': %m", path); - } + if (uid_is_valid(uid) || gid_is_valid(gid)) + if (fchownat(inaccessible_fd, fn, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + log_debug_errno(errno, "Failed to chown '%s', ignoring: %m", path); } + if (fchmod(inaccessible_fd, 0555) < 0) + log_debug_errno(errno, "Failed to mark inaccessible directory read-only, ignoring: %m"); + return 0; } diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index e8f4dfb..1079d28 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -13,6 +13,7 @@ #include #include "alloc-util.h" +#include "blockdev-util.h" #include "btrfs-util.h" #include "chase.h" #include "chattr-util.h" @@ -44,9 +45,10 @@ #include "strv.h" #include "time-util.h" #include "utf8.h" +#include "vpick.h" #include "xattr-util.h" -static const char* const image_search_path[_IMAGE_CLASS_MAX] = { +const char* const image_search_path[_IMAGE_CLASS_MAX] = { [IMAGE_MACHINE] = "/etc/machines\0" /* only place symlinks here */ "/run/machines\0" /* and here too */ "/var/lib/machines\0" /* the main place for images */ @@ -74,15 +76,20 @@ static const char* const image_search_path[_IMAGE_CLASS_MAX] = { "/usr/lib/confexts\0", }; -/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext in extension - * search dir) */ +/* Inside the initrd, use a slightly different set of search path (i.e. include .extra/sysext/ and + * .extra/confext/ 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 */ + "/.extra/sysext\0", /* put sysext picked up by systemd-stub last, since not trusted */ + + [IMAGE_CONFEXT] = "/run/confexts\0" /* only place symlinks here */ + "/var/lib/confexts\0" /* the main place for images */ + "/usr/local/lib/confexts\0" + "/.extra/confext\0", /* put confext picked up by systemd-stub last, since not trusted */ }; static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = { @@ -92,6 +99,15 @@ static const char* image_class_suffix_table[_IMAGE_CLASS_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_class_suffix, ImageClass); +static const char *const image_root_table[_IMAGE_CLASS_MAX] = { + [IMAGE_MACHINE] = "/var/lib/machines", + [IMAGE_PORTABLE] = "/var/lib/portables", + [IMAGE_SYSEXT] = "/var/lib/extensions", + [IMAGE_CONFEXT] = "/var/lib/confexts", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass); + static Image *image_free(Image *i) { assert(i); @@ -209,43 +225,101 @@ static int image_new( return 0; } -static int extract_pretty( +static int extract_image_basename( const char *path, - const char *class_suffix, - const char *format_suffix, - char **ret) { + const char *class_suffix, /* e.g. ".sysext" (this is an optional suffix) */ + char **format_suffixes, /* e.g. ".raw" (one of these will be required) */ + char **ret_basename, + char **ret_suffix) { - _cleanup_free_ char *name = NULL; + _cleanup_free_ char *name = NULL, *suffix = NULL; int r; assert(path); - assert(ret); r = path_extract_filename(path, &name); if (r < 0) return r; - if (format_suffix) { - char *e = endswith(name, format_suffix); + if (format_suffixes) { + char *e = endswith_strv(name, format_suffixes); if (!e) /* Format suffix is required */ return -EINVAL; + if (ret_suffix) { + suffix = strdup(e); + if (!suffix) + return -ENOMEM; + } + *e = 0; } if (class_suffix) { char *e = endswith(name, class_suffix); - if (e) /* Class suffix is optional */ + if (e) { /* Class suffix is optional */ + if (ret_suffix) { + _cleanup_free_ char *j = strjoin(e, suffix); + if (!j) + return -ENOMEM; + + free_and_replace(suffix, j); + } + *e = 0; + } } if (!image_name_is_valid(name)) return -EINVAL; - *ret = TAKE_PTR(name); + if (ret_suffix) + *ret_suffix = TAKE_PTR(suffix); + + if (ret_basename) + *ret_basename = TAKE_PTR(name); + return 0; } +static int image_update_quota(Image *i, int fd) { + _cleanup_close_ int fd_close = -EBADF; + int r; + + assert(i); + + if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) + return -EROFS; + + if (i->type != IMAGE_SUBVOLUME) + return -EOPNOTSUPP; + + if (fd < 0) { + fd_close = open(i->path, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (fd_close < 0) + return -errno; + fd = fd_close; + } + + r = btrfs_quota_scan_ongoing(fd); + if (r < 0) + return r; + if (r > 0) + return 0; + + BtrfsQuotaInfo quota; + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r < 0) + return r; + + i->usage = quota.referenced; + i->usage_exclusive = quota.exclusive; + i->limit = quota.referenced_max; + i->limit_exclusive = quota.exclusive_max; + + return 1; +} + static int image_make( ImageClass c, const char *pretty, @@ -297,7 +371,12 @@ static int image_make( return 0; if (!pretty) { - r = extract_pretty(filename, image_class_suffix_to_string(c), NULL, &pretty_buffer); + r = extract_image_basename( + filename, + image_class_suffix_to_string(c), + /* format_suffix= */ NULL, + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -334,19 +413,7 @@ static int image_make( if (r < 0) return r; - if (btrfs_quota_scan_ongoing(fd) == 0) { - BtrfsQuotaInfo quota; - - r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; - - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; - } - } - + (void) image_update_quota(*ret, fd); return 0; } } @@ -384,7 +451,12 @@ static int image_make( (void) fd_getcrtime_at(dfd, filename, AT_SYMLINK_FOLLOW, &crtime); if (!pretty) { - r = extract_pretty(filename, image_class_suffix_to_string(c), ".raw", &pretty_buffer); + r = extract_image_basename( + filename, + image_class_suffix_to_string(c), + STRV_MAKE(".raw"), + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -418,7 +490,12 @@ static int image_make( return 0; if (!pretty) { - r = extract_pretty(filename, NULL, NULL, &pretty_buffer); + r = extract_image_basename( + filename, + /* class_suffix= */ NULL, + /* format_suffix= */ NULL, + &pretty_buffer, + /* ret_suffix= */ NULL); if (r < 0) return r; @@ -446,8 +523,9 @@ static int image_make( read_only = true; } - if (ioctl(block_fd, BLKGETSIZE64, &size) < 0) - log_debug_errno(errno, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename); + r = blockdev_get_device_size(block_fd, &size); + if (r < 0) + log_debug_errno(r, "Failed to issue BLKGETSIZE64 on device %s/%s, ignoring: %m", path ?: strnull(parent), filename); block_fd = safe_close(block_fd); } @@ -481,6 +559,37 @@ static const char *pick_image_search_path(ImageClass class) { return in_initrd() && image_search_path_initrd[class] ? image_search_path_initrd[class] : image_search_path[class]; } +static char **make_possible_filenames(ImageClass class, const char *image_name) { + _cleanup_strv_free_ char **l = NULL; + + assert(image_name); + + FOREACH_STRING(v_suffix, "", ".v") + FOREACH_STRING(format_suffix, "", ".raw") { + _cleanup_free_ char *j = NULL; + const char *class_suffix; + + class_suffix = image_class_suffix_to_string(class); + if (class_suffix) { + j = strjoin(image_name, class_suffix, format_suffix, v_suffix); + if (!j) + return NULL; + + if (strv_consume(&l, TAKE_PTR(j)) < 0) + return NULL; + } + + j = strjoin(image_name, format_suffix, v_suffix); + if (!j) + return NULL; + + if (strv_consume(&l, TAKE_PTR(j)) < 0) + return NULL; + } + + return TAKE_PTR(l); +} + int image_find(ImageClass class, const char *name, const char *root, @@ -496,6 +605,10 @@ int image_find(ImageClass class, if (!image_name_is_valid(name)) return -ENOENT; + _cleanup_strv_free_ char **names = make_possible_filenames(class, name); + if (!names) + return -ENOMEM; + NULSTR_FOREACH(path, pick_image_search_path(class)) { _cleanup_free_ char *resolved = NULL; _cleanup_closedir_ DIR *d = NULL; @@ -512,43 +625,97 @@ int image_find(ImageClass class, * to symlink block devices into the search path. (For now, we disable that when operating * relative to some root directory.) */ flags = root ? AT_SYMLINK_NOFOLLOW : 0; - if (fstatat(dirfd(d), name, &st, flags) < 0) { - _cleanup_free_ char *raw = NULL; - if (errno != ENOENT) - return -errno; + STRV_FOREACH(n, names) { + _cleanup_free_ char *fname_buf = NULL; + const char *fname = *n; - raw = strjoin(name, ".raw"); - if (!raw) - return -ENOMEM; + if (fstatat(dirfd(d), fname, &st, flags) < 0) { + if (errno != ENOENT) + return -errno; - if (fstatat(dirfd(d), raw, &st, flags) < 0) { - if (errno == ENOENT) + continue; /* Vanished while we were looking at it */ + } + + if (endswith(fname, ".raw")) { + if (!S_ISREG(st.st_mode)) { + log_debug("Ignoring non-regular file '%s' with .raw suffix.", fname); continue; + } - return -errno; - } + } else if (endswith(fname, ".v")) { - if (!S_ISREG(st.st_mode)) - continue; + if (!S_ISDIR(st.st_mode)) { + log_debug("Ignoring non-directory file '%s' with .v suffix.", fname); + continue; + } - r = image_make(class, name, dirfd(d), resolved, raw, &st, ret); + _cleanup_free_ char *suffix = NULL; + suffix = strdup(ASSERT_PTR(startswith(fname, name))); + if (!suffix) + return -ENOMEM; + + *ASSERT_PTR(endswith(suffix, ".v")) = 0; + + _cleanup_free_ char *vp = path_join(resolved, fname); + if (!vp) + return -ENOMEM; + + PickFilter filter = { + .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR), + .basename = name, + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(suffix), + }; + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(root, + /* toplevel_fd= */ AT_FDCWD, + vp, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) { + log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp); + continue; + } + if (!result.path) { + log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp); + continue; + } + + /* Refresh the stat data for the discovered target */ + st = result.st; + + _cleanup_free_ char *bn = NULL; + r = path_extract_filename(result.path, &bn); + if (r < 0) { + log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path); + continue; + } + + fname_buf = path_join(fname, bn); + if (!fname_buf) + return log_oom(); - } else { - if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) + fname = fname_buf; + + } else if (!S_ISDIR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_debug("Ignoring non-directory and non-block device file '%s' without suffix.", fname); continue; + } - r = image_make(class, name, dirfd(d), resolved, name, &st, ret); - } - if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) - continue; - if (r < 0) - return r; + r = image_make(class, name, dirfd(d), resolved, fname, &st, ret); + if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) + continue; + if (r < 0) + return r; - if (ret) - (*ret)->discoverable = true; + if (ret) + (*ret)->discoverable = true; - return 1; + return 1; + } } if (class == IMAGE_MACHINE && streq(name, ".host")) { @@ -559,7 +726,7 @@ int image_find(ImageClass class, if (ret) (*ret)->discoverable = true; - return r; + return 1; } return -ENOENT; @@ -606,43 +773,133 @@ int image_discover( return r; FOREACH_DIRENT_ALL(de, d, return -errno) { + _cleanup_free_ char *pretty = NULL, *fname_buf = NULL; _cleanup_(image_unrefp) Image *image = NULL; - _cleanup_free_ char *pretty = NULL; + const char *fname = de->d_name; struct stat st; int flags; - if (dot_or_dot_dot(de->d_name)) + if (dot_or_dot_dot(fname)) continue; /* As mentioned above, we follow symlinks on this fstatat(), because we want to * permit people to symlink block devices into the search path. */ flags = root ? AT_SYMLINK_NOFOLLOW : 0; - if (fstatat(dirfd(d), de->d_name, &st, flags) < 0) { + if (fstatat(dirfd(d), fname, &st, flags) < 0) { if (errno == ENOENT) continue; return -errno; } - if (S_ISREG(st.st_mode)) - r = extract_pretty(de->d_name, image_class_suffix_to_string(class), ".raw", &pretty); - else if (S_ISDIR(st.st_mode)) - r = extract_pretty(de->d_name, image_class_suffix_to_string(class), NULL, &pretty); - else if (S_ISBLK(st.st_mode)) - r = extract_pretty(de->d_name, NULL, NULL, &pretty); - else { - log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", de->d_name); - continue; - } - if (r < 0) { - log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", de->d_name); + if (S_ISREG(st.st_mode)) { + r = extract_image_basename( + fname, + image_class_suffix_to_string(class), + STRV_MAKE(".raw"), + &pretty, + /* suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } else if (S_ISDIR(st.st_mode)) { + const char *v; + + v = endswith(fname, ".v"); + if (v) { + _cleanup_free_ char *suffix = NULL, *nov = NULL; + + nov = strndup(fname, v - fname); /* Chop off the .v */ + if (!nov) + return -ENOMEM; + + r = extract_image_basename( + nov, + image_class_suffix_to_string(class), + STRV_MAKE(".raw", ""), + &pretty, + &suffix); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like a versioned image.", fname); + continue; + } + + _cleanup_free_ char *vp = path_join(resolved, fname); + if (!vp) + return -ENOMEM; + + PickFilter filter = { + .type_mask = endswith(suffix, ".raw") ? (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) : (UINT32_C(1) << DT_DIR), + .basename = pretty, + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(suffix), + }; + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(root, + /* toplevel_fd= */ AT_FDCWD, + vp, + &filter, + PICK_ARCHITECTURE|PICK_TRIES, + &result); + if (r < 0) { + log_debug_errno(r, "Failed to pick versioned image on '%s', skipping: %m", vp); + continue; + } + if (!result.path) { + log_debug("Found versioned directory '%s', without matching entry, skipping: %m", vp); + continue; + } + + /* Refresh the stat data for the discovered target */ + st = result.st; + + _cleanup_free_ char *bn = NULL; + r = path_extract_filename(result.path, &bn); + if (r < 0) { + log_debug_errno(r, "Failed to extract basename of image path '%s', skipping: %m", result.path); + continue; + } + + fname_buf = path_join(fname, bn); + if (!fname_buf) + return log_oom(); + + fname = fname_buf; + } else { + r = extract_image_basename( + fname, + image_class_suffix_to_string(class), + /* format_suffix= */ NULL, + &pretty, + /* ret_suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } + + } else if (S_ISBLK(st.st_mode)) { + r = extract_image_basename( + fname, + /* class_suffix= */ NULL, + /* format_suffix= */ NULL, + &pretty, + /* ret_v_suffix= */ NULL); + if (r < 0) { + log_debug_errno(r, "Skipping directory entry '%s', which doesn't look like an image.", fname); + continue; + } + } else { + log_debug("Skipping directory entry '%s', which is neither regular file, directory nor block device.", fname); continue; } if (hashmap_contains(h, pretty)) continue; - r = image_make(class, pretty, dirfd(d), resolved, de->d_name, &st, &image); + r = image_make(class, pretty, dirfd(d), resolved, fname, &st, &image); if (IN_SET(r, -ENOENT, -EMEDIUMTYPE)) continue; if (r < 0) @@ -1056,6 +1313,7 @@ int image_read_only(Image *i, bool b) { return -EOPNOTSUPP; } + i->read_only = b; return 0; } @@ -1064,7 +1322,12 @@ static void make_lock_dir(void) { (void) mkdir("/run/systemd/nspawn/locks", 0700); } -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { +int image_path_lock( + const char *path, + int operation, + LockFile *ret_global, + LockFile *ret_local) { + _cleanup_free_ char *p = NULL; LockFile t = LOCK_FILE_INIT; struct stat st; @@ -1072,8 +1335,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile int r; assert(path); - assert(global); - assert(local); + assert(ret_local); /* Locks an image path. This actually creates two locks: one "local" one, next to the image path * itself, which might be shared via NFS. And another "global" one, in /run, that uses the @@ -1095,7 +1357,9 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile } if (getenv_bool("SYSTEMD_NSPAWN_LOCK") == 0) { - *local = *global = (LockFile) LOCK_FILE_INIT; + *ret_local = LOCK_FILE_INIT; + if (ret_global) + *ret_global = LOCK_FILE_INIT; return 0; } @@ -1111,19 +1375,23 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile if (exclusive) return -EBUSY; - *local = *global = (LockFile) LOCK_FILE_INIT; + *ret_local = LOCK_FILE_INIT; + if (ret_global) + *ret_global = LOCK_FILE_INIT; return 0; } - if (stat(path, &st) >= 0) { - if (S_ISBLK(st.st_mode)) - r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev)); - else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) - r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino); - else - return -ENOTTY; - if (r < 0) - return -ENOMEM; + if (ret_global) { + if (stat(path, &st) >= 0) { + if (S_ISBLK(st.st_mode)) + r = asprintf(&p, "/run/systemd/nspawn/locks/block-%u:%u", major(st.st_rdev), minor(st.st_rdev)); + else if (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode)) + r = asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino); + else + return -ENOTTY; + if (r < 0) + return -ENOMEM; + } } /* For block devices we don't need the "local" lock, as the major/minor lock above should be @@ -1141,19 +1409,21 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile if (p) { make_lock_dir(); - r = make_lock_file(p, operation, global); + r = make_lock_file(p, operation, ret_global); if (r < 0) { release_lock_file(&t); return r; } - } else - *global = (LockFile) LOCK_FILE_INIT; + } else if (ret_global) + *ret_global = LOCK_FILE_INIT; - *local = t; + *ret_local = t; return 0; } int image_set_limit(Image *i, uint64_t referenced_max) { + int r; + assert(i); if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i)) @@ -1169,7 +1439,12 @@ int image_set_limit(Image *i, uint64_t referenced_max) { (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + r = btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); + if (r < 0) + return r; + + (void) image_update_quota(i, -EBADF); + return 0; } int image_read_metadata(Image *i, const ImagePolicy *image_policy) { @@ -1206,7 +1481,7 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { else if (r >= 0) { r = read_etc_hostname(path, &hostname); if (r < 0) - log_debug_errno(errno, "Failed to read /etc/hostname of image %s: %m", i->name); + log_debug_errno(r, "Failed to read /etc/hostname of image %s: %m", i->name); } path = mfree(path); @@ -1249,8 +1524,25 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { case IMAGE_BLOCK: { _cleanup_(loop_device_unrefp) LoopDevice *d = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; - - r = loop_device_make_by_path(i->path, O_RDONLY, /* sector_size= */ UINT32_MAX, LO_FLAGS_PARTSCAN, LOCK_SH, &d); + DissectImageFlags flags = + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_READ_ONLY | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_ADD_PARTITION_DEVICES | + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_VALIDATE_OS_EXT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; + + r = loop_device_make_by_path( + i->path, + O_RDONLY, + /* sector_size= */ UINT32_MAX, + LO_FLAGS_PARTSCAN, + LOCK_SH, + &d); if (r < 0) return r; @@ -1259,20 +1551,15 @@ int image_read_metadata(Image *i, const ImagePolicy *image_policy) { /* verity= */ NULL, /* mount_options= */ NULL, image_policy, - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_READ_ONLY | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES, + flags, &m); if (r < 0) return r; - r = dissected_image_acquire_metadata(m, - DISSECT_IMAGE_VALIDATE_OS | - DISSECT_IMAGE_VALIDATE_OS_EXT); + r = dissected_image_acquire_metadata( + m, + /* userns_fd= */ -EBADF, + flags); if (r < 0) return r; diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index a30a3d9..6491cec 100644 --- a/src/shared/discover-image.h +++ b/src/shared/discover-image.h @@ -119,4 +119,8 @@ static inline bool IMAGE_IS_HOST(const struct Image *i) { int image_to_json(const struct Image *i, JsonVariant **ret); +const char *image_root_to_string(ImageClass c) _const_; + extern const struct hash_ops image_hash_ops; + +extern const char* const image_search_path[_IMAGE_CLASS_MAX]; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 84cfbcd..a9e211f 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -32,6 +32,7 @@ #include "copy.h" #include "cryptsetup-util.h" #include "device-nodes.h" +#include "device-private.h" #include "device-util.h" #include "devnum-util.h" #include "discover-image.h" @@ -60,6 +61,7 @@ #include "openssl-util.h" #include "os-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "process-util.h" #include "raw-clone.h" #include "resize-fs.h" @@ -73,6 +75,7 @@ #include "tmpfile-util.h" #include "udev-util.h" #include "user-util.h" +#include "varlink.h" #include "xattr-util.h" /* how many times to wait for the device nodes to appear */ @@ -267,16 +270,8 @@ int probe_filesystem_full( (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); if (fstype) { - char *t; - log_debug("Probed fstype '%s' on partition %s.", fstype, path); - - t = strdup(fstype); - if (!t) - return -ENOMEM; - - *ret_fstype = t; - return 1; + return strdup_to_full(ret_fstype, fstype); } not_found: @@ -522,6 +517,38 @@ static void dissected_partition_done(DissectedPartition *p) { } #if HAVE_BLKID +static int diskseq_should_be_used( + const char *whole_devname, + uint64_t diskseq, + DissectImageFlags flags) { + + int r; + + assert(whole_devname); + + /* No diskseq. We cannot use by-diskseq symlink. */ + if (diskseq == 0) + return false; + + /* Do not use by-diskseq link unless DISSECT_IMAGE_DISKSEQ_DEVNODE flag is explicitly set. */ + if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE)) + return false; + + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + r = sd_device_new_from_devname(&dev, whole_devname); + if (r < 0) + return r; + + /* When ID_IGNORE_DISKSEQ udev property is set, the by-diskseq symlink will not be created. */ + r = device_get_property_bool(dev, "ID_IGNORE_DISKSEQ"); + if (r >= 0) + return !r; /* If explicitly specified, use it. */ + if (r != -ENOENT) + return r; + + return true; +} + static int make_partition_devname( const char *whole_devname, uint64_t diskseq, @@ -536,8 +563,10 @@ static int make_partition_devname( assert(nr != 0); /* zero is not a valid partition nr */ assert(ret); - if (!FLAGS_SET(flags, DISSECT_IMAGE_DISKSEQ_DEVNODE) || diskseq == 0) { - + r = diskseq_should_be_used(whole_devname, diskseq, flags); + if (r < 0) + log_debug_errno(r, "Failed to determine if diskseq should be used for %s, assuming no, ignoring: %m", whole_devname); + if (r <= 0) { /* Given a whole block device node name (e.g. /dev/sda or /dev/loop7) generate a partition * device name (e.g. /dev/sda7 or /dev/loop7p5). The rule the kernel uses is simple: if whole * block device node name ends in a digit, then suffix a 'p', followed by the partition @@ -799,7 +828,7 @@ static int dissect_image( if (suuid) { /* blkid will return FAT's serial number as UUID, hence it is quite possible * that parsing this will fail. We'll ignore the ID, since it's just too - * short to be useful as tru identifier. */ + * short to be useful as true identifier. */ r = sd_id128_from_string(suuid, &uuid); if (r < 0) log_debug_errno(r, "Failed to parse file system UUID '%s', ignoring: %m", suuid); @@ -1547,6 +1576,7 @@ int dissect_image_file( #if HAVE_BLKID _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; _cleanup_close_ int fd = -EBADF; + struct stat st; int r; assert(path); @@ -1555,7 +1585,10 @@ int dissect_image_file( if (fd < 0) return -errno; - r = fd_verify_regular(fd); + if (fstat(fd, &st) < 0) + return -errno; + + r = stat_verify_regular(&st); if (r < 0) return r; @@ -1563,6 +1596,8 @@ int dissect_image_file( if (r < 0) return r; + m->image_size = st.st_size; + r = probe_sector_size(fd, &m->sector_size); if (r < 0) return r; @@ -1644,6 +1679,20 @@ int dissect_image_file_and_warn( verity); } +void dissected_image_close(DissectedImage *m) { + if (!m) + return; + + /* Closes all fds we keep open associated with this, but nothing else */ + + FOREACH_ARRAY(p, m->partitions, _PARTITION_DESIGNATOR_MAX) { + p->mount_node_fd = safe_close(p->mount_node_fd); + p->fsmount_fd = safe_close(p->fsmount_fd); + } + + m->loop = loop_device_unref(m->loop); +} + DissectedImage* dissected_image_unref(DissectedImage *m) { if (!m) return NULL; @@ -1759,8 +1808,9 @@ static int fs_grow(const char *node_path, int mount_fd, const char *mount_path) if (node_fd < 0) return log_debug_errno(errno, "Failed to open node device %s: %m", node_path); - if (ioctl(node_fd, BLKGETSIZE64, &size) != 0) - return log_debug_errno(errno, "Failed to get block device size of %s: %m", node_path); + r = blockdev_get_device_size(node_fd, &size); + if (r < 0) + return log_debug_errno(r, "Failed to get block device size of %s: %m", node_path); if (mount_fd < 0) { assert(mount_path); @@ -1861,9 +1911,12 @@ int partition_pick_mount_options( * access that actually modifies stuff work on such image files. Or to say this differently: if * people want their file systems to be fixed up they should just open them in writable mode, where * all these problems don't exist. */ - if (!rw && fstype && fstype_can_norecovery(fstype)) - if (!strextend_with_separator(&options, ",", "norecovery")) + if (!rw && fstype) { + const char *option = fstype_norecovery_option(fstype); + + if (option && !strextend_with_separator(&options, ",", option)) return -ENOMEM; + } if (discard && fstype && fstype_can_discard(fstype)) if (!strextend_with_separator(&options, ",", "discard")) @@ -1958,7 +2011,7 @@ static int mount_partition( if (where) { if (directory) { /* Automatically create missing mount points inside the image, if necessary. */ - r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755, NULL); + r = mkdir_p_root(where, directory, uid_shift, (gid_t) uid_shift, 0755); if (r < 0 && r != -EROFS) return r; @@ -2113,7 +2166,7 @@ int dissected_image_mount( * If 'where' is not NULL then we'll either mount the partitions to the right places ourselves, * or use DissectedPartition.fsmount_fd and bind it to the right places. * - * This allows splitting the setting up up the superblocks and the binding to file systems paths into + * This allows splitting the setting up the superblocks and the binding to file systems paths into * two distinct and differently privileged components: one that gets the fsmount fds, and the other * that then applies them. * @@ -2137,7 +2190,7 @@ int dissected_image_mount( if (userns_fd < 0 && need_user_mapping(uid_shift, uid_range) && FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_IDMAPPED)) { - my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); + my_userns_fd = make_userns(uid_shift, uid_range, UID_INVALID, UID_INVALID, REMOUNT_IDMAPPING_HOST_ROOT); if (my_userns_fd < 0) return my_userns_fd; @@ -2278,19 +2331,19 @@ int dissected_image_mount_and_warn( r = dissected_image_mount(m, where, uid_shift, uid_range, userns_fd, flags); if (r == -ENXIO) - return log_error_errno(r, "Not root file system found in image."); + return log_error_errno(r, "Failed to mount image: No root file system found in image."); if (r == -EMEDIUMTYPE) - return log_error_errno(r, "No suitable os-release/extension-release file in image found."); + return log_error_errno(r, "Failed to mount image: No suitable os-release/extension-release file in image found."); if (r == -EUNATCH) - return log_error_errno(r, "Encrypted file system discovered, but decryption not requested."); + return log_error_errno(r, "Failed to mount image: Encrypted file system discovered, but decryption not requested."); if (r == -EUCLEAN) - return log_error_errno(r, "File system check on image failed."); + return log_error_errno(r, "Failed to mount image: File system check on image failed."); if (r == -EBUSY) - return log_error_errno(r, "File system already mounted elsewhere."); + return log_error_errno(r, "Failed to mount image: File system already mounted elsewhere."); if (r == -EAFNOSUPPORT) - return log_error_errno(r, "File system type not supported or not known."); + return log_error_errno(r, "Failed to mount image: File system type not supported or not known."); if (r == -EIDRM) - return log_error_errno(r, "File system is too uncommon, refused."); + return log_error_errno(r, "Failed to mount image: File system is too uncommon, refused."); if (r < 0) return log_error_errno(r, "Failed to mount image: %m"); @@ -2530,7 +2583,35 @@ static char* dm_deferred_remove_clean(char *name) { } DEFINE_TRIVIAL_CLEANUP_FUNC(char *, dm_deferred_remove_clean); -static int validate_signature_userspace(const VeritySettings *verity) { +static int validate_signature_userspace(const VeritySettings *verity, DissectImageFlags flags) { + int r; + + if (!FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_USERSPACE_VERITY)) { + log_debug("Userspace dm-verity signature authentication disabled via flag."); + return 0; + } + + r = secure_getenv_bool("SYSTEMD_ALLOW_USERSPACE_VERITY"); + if (r < 0 && r != -ENXIO) { + log_debug_errno(r, "Failed to parse $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable, refusing userspace dm-verity signature authentication."); + return 0; + } + if (!r) { + log_debug("Userspace dm-verity signature authentication disabled via $SYSTEMD_ALLOW_USERSPACE_VERITY environment variable."); + return 0; + } + + bool b; + r = proc_cmdline_get_bool("systemd.allow_userspace_verity", PROC_CMDLINE_TRUE_WHEN_MISSING, &b); + if (r < 0) { + log_debug_errno(r, "Failed to parse systemd.allow_userspace_verity= kernel command line option, refusing userspace dm-verity signature authentication."); + return 0; + } + if (!b) { + log_debug("Userspace dm-verity signature authentication disabled via systemd.allow_userspace_verity= kernel command line variable."); + return 0; + } + #if HAVE_OPENSSL _cleanup_(sk_X509_free_allp) STACK_OF(X509) *sk = NULL; _cleanup_strv_free_ char **certs = NULL; @@ -2539,7 +2620,6 @@ static int validate_signature_userspace(const VeritySettings *verity) { _cleanup_(BIO_freep) BIO *bio = NULL; /* 'bio' must be freed first, 's' second, hence keep this order * of declaration in place, please */ const unsigned char *d; - int r; assert(verity); assert(verity->root_hash); @@ -2611,7 +2691,8 @@ static int validate_signature_userspace(const VeritySettings *verity) { static int do_crypt_activate_verity( struct crypt_device *cd, const char *name, - const VeritySettings *verity) { + const VeritySettings *verity, + DissectImageFlags flags) { bool check_signature; int r, k; @@ -2621,7 +2702,7 @@ static int do_crypt_activate_verity( assert(verity); if (verity->root_hash_sig) { - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIGNATURE"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIGNATURE"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIGNATURE"); @@ -2656,7 +2737,7 @@ static int do_crypt_activate_verity( /* Preferably propagate the original kernel error, so that the fallback logic can work, * as the device-mapper is finicky around concurrent activations of the same volume */ - k = validate_signature_userspace(verity); + k = validate_signature_userspace(verity, flags); if (k < 0) return r < 0 ? r : k; if (k == 0) @@ -2777,7 +2858,7 @@ static int verity_partition( goto check; /* The device already exists. Let's check it. */ /* The symlink to the device node does not exist yet. Assume not activated, and let's activate it. */ - r = do_crypt_activate_verity(cd, name, verity); + r = do_crypt_activate_verity(cd, name, verity, flags); if (r >= 0) goto try_open; /* The device is activated. Let's open it. */ /* libdevmapper can return EINVAL when the device is already in the activation stage. @@ -2787,7 +2868,9 @@ static int verity_partition( * https://gitlab.com/cryptsetup/cryptsetup/-/merge_requests/96 */ if (r == -EINVAL && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) break; - if (r == -ENODEV) /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again */ + /* Volume is being opened but not ready, crypt_init_by_name would fail, try to open again if + * sharing is enabled. */ + if (r == -ENODEV && FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)) goto try_again; if (!IN_SET(r, -EEXIST, /* Volume has already been opened and ready to be used. */ @@ -2907,6 +2990,7 @@ int dissected_image_decrypt( * > 0 → Decrypted successfully * -ENOKEY → There's something to decrypt but no key was supplied * -EKEYREJECTED → Passed key was not correct + * -EBUSY → Generic Verity error (kernel is not very explanatory) */ if (verity && verity->root_hash && verity->root_hash_size < sizeof(sd_id128_t)) @@ -2933,7 +3017,9 @@ int dissected_image_decrypt( k = partition_verity_of(i); if (k >= 0) { - r = verity_partition(i, p, m->partitions + k, verity, flags | DISSECT_IMAGE_VERITY_SHARE, d); + flags |= getenv_bool("SYSTEMD_VERITY_SHARING") != 0 ? DISSECT_IMAGE_VERITY_SHARE : 0; + + r = verity_partition(i, p, m->partitions + k, verity, flags, d); if (r < 0) return r; } @@ -2978,9 +3064,16 @@ int dissected_image_decrypt_interactively( return log_error_errno(SYNTHETIC_ERRNO(EKEYREJECTED), "Too many retries."); - z = strv_free(z); + z = strv_free_erase(z); + + static const AskPasswordRequest req = { + .message = "Please enter image passphrase:", + .id = "dissect", + .keyring = "dissect", + .credential = "dissect.passphrase", + }; - r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", "dissect.passphrase", USEC_INFINITY, 0, &z); + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &z); if (r < 0) return log_error_errno(r, "Failed to query for passphrase: %m"); @@ -3082,7 +3175,7 @@ int verity_settings_load( if (is_device_path(image)) return 0; - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_SIDECAR"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_SIDECAR"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_SIDECAR, ignoring: %m"); if (r == 0) @@ -3159,7 +3252,7 @@ int verity_settings_load( } if (text) { - r = unhexmem(text, strlen(text), &root_hash, &root_hash_size); + r = unhexmem(text, &root_hash, &root_hash_size); if (r < 0) return r; if (root_hash_size < sizeof(sd_id128_t)) @@ -3267,7 +3360,7 @@ int dissected_image_load_verity_sig_partition( if (verity->root_hash && verity->root_hash_sig) /* Already loaded? */ return 0; - r = getenv_bool_secure("SYSTEMD_DISSECT_VERITY_EMBEDDED"); + r = secure_getenv_bool("SYSTEMD_DISSECT_VERITY_EMBEDDED"); if (r < 0 && r != -ENXIO) log_debug_errno(r, "Failed to parse $SYSTEMD_DISSECT_VERITY_EMBEDDED, ignoring: %m"); if (r == 0) @@ -3313,7 +3406,7 @@ int dissected_image_load_verity_sig_partition( if (!json_variant_is_string(rh)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'rootHash' field of signature JSON object is not a string."); - r = unhexmem(json_variant_string(rh), SIZE_MAX, &root_hash, &root_hash_size); + r = unhexmem(json_variant_string(rh), &root_hash, &root_hash_size); if (r < 0) return log_debug_errno(r, "Failed to parse root hash field: %m"); @@ -3334,7 +3427,7 @@ int dissected_image_load_verity_sig_partition( if (!json_variant_is_string(sig)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "'signature' field of signature JSON object is not a string."); - r = unbase64mem(json_variant_string(sig), SIZE_MAX, &root_hash_sig, &root_hash_sig_size); + r = unbase64mem(json_variant_string(sig), &root_hash_sig, &root_hash_sig_size); if (r < 0) return log_debug_errno(r, "Failed to parse signature field: %m"); @@ -3347,7 +3440,10 @@ int dissected_image_load_verity_sig_partition( return 1; } -int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags) { +int dissected_image_acquire_metadata( + DissectedImage *m, + int userns_fd, + DissectImageFlags extra_flags) { enum { META_HOSTNAME, @@ -3375,11 +3471,10 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ }; _cleanup_strv_free_ char **machine_info = NULL, **os_release = NULL, **initrd_release = NULL, **sysext_release = NULL, **confext_release = NULL; + _cleanup_free_ char *hostname = NULL, *t = NULL; _cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR; - _cleanup_(rmdir_and_freep) char *t = NULL; _cleanup_(sigkill_waitp) pid_t child = 0; sd_id128_t machine_id = SD_ID128_NULL; - _cleanup_free_ char *hostname = NULL; unsigned n_meta_initialized = 0; int fds[2 * _META_MAX], r, v; int has_init_system = -1; @@ -3389,11 +3484,8 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ assert(m); - for (; n_meta_initialized < _META_MAX; n_meta_initialized ++) { - if (!paths[n_meta_initialized]) { - fds[2*n_meta_initialized] = fds[2*n_meta_initialized+1] = -EBADF; - continue; - } + for (; n_meta_initialized < _META_MAX; n_meta_initialized++) { + assert(paths[n_meta_initialized]); if (pipe2(fds + 2*n_meta_initialized, O_CLOEXEC) < 0) { r = -errno; @@ -3401,7 +3493,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ } } - r = mkdtemp_malloc("/tmp/dissect-XXXXXX", &t); + r = get_common_dissect_directory(&t); if (r < 0) goto finish; @@ -3410,13 +3502,22 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ goto finish; } - r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE, &child); + r = safe_fork("(sd-dissect)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM, &child); if (r < 0) goto finish; if (r == 0) { - /* Child in a new mount namespace */ + /* Child */ error_pipe[0] = safe_close(error_pipe[0]); + if (userns_fd < 0) + r = detach_mount_namespace_harder(0, 0); + else + r = detach_mount_namespace_userns(userns_fd); + if (r < 0) { + log_debug_errno(r, "Failed to detach mount namespace: %m"); + goto inner_fail; + } + r = dissected_image_mount( m, t, @@ -3435,8 +3536,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ for (unsigned k = 0; k < _META_MAX; k++) { _cleanup_close_ int fd = -ENOENT; - if (!paths[k]) - continue; + assert(paths[k]); fds[2*k] = safe_close(fds[2*k]); @@ -3541,8 +3641,7 @@ int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_ for (unsigned k = 0; k < _META_MAX; k++) { _cleanup_fclose_ FILE *f = NULL; - if (!paths[k]) - continue; + assert(paths[k]); fds[2*k+1] = safe_close(fds[2*k+1]); @@ -3703,6 +3802,7 @@ int dissect_loop_device( return r; m->loop = loop_device_ref(loop); + m->image_size = m->loop->device_size; m->sector_size = m->loop->sector_size; r = dissect_image(m, loop->fd, loop->node, verity, mount_options, image_policy, flags); @@ -3953,10 +4053,12 @@ int verity_dissect_and_mount( if (r < 0) return log_debug_errno(r, "Failed to load root hash: %m"); - dissect_image_flags = (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) | + dissect_image_flags = + (verity.data_path ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0) | (relax_extension_release_check ? DISSECT_IMAGE_RELAX_EXTENSION_CHECK : 0) | DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES; + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; /* Note that we don't use loop_device_make here, as the FD is most likely O_PATH which would not be * accepted by LOOP_CONFIGURE, so just let loop_device_make_by_path reopen it as a regular FD. */ @@ -4067,3 +4169,236 @@ int verity_dissect_and_mount( return 0; } + +int get_common_dissect_directory(char **ret) { + _cleanup_free_ char *t = NULL; + int r; + + /* A common location we mount dissected images to. The assumption is that everyone who uses this + * function runs in their own private mount namespace (with mount propagation off on /run/systemd/, + * and thus can mount something here without affecting anyone else). */ + + t = strdup("/run/systemd/dissect-root"); + if (!t) + return log_oom_debug(); + + r = mkdir_parents(t, 0755); + if (r < 0) + return log_debug_errno(r, "Failed to create parent dirs of mount point '%s': %m", t); + + r = RET_NERRNO(mkdir(t, 0000)); /* It's supposed to be overmounted, hence let's make this inaccessible */ + if (r < 0 && r != -EEXIST) + return log_debug_errno(r, "Failed to create mount point '%s': %m", t); + + if (ret) + *ret = TAKE_PTR(t); + + return 0; +} + +#if HAVE_BLKID + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_architecture, Architecture, architecture_from_string); +static JSON_DISPATCH_ENUM_DEFINE(dispatch_partition_designator, PartitionDesignator, partition_designator_from_string); + +typedef struct PartitionFields { + PartitionDesignator designator; + bool rw; + bool growfs; + unsigned partno; + Architecture architecture; + sd_id128_t uuid; + char *fstype; + char *label; + uint64_t size; + uint64_t offset; + unsigned fsmount_fd_idx; +} PartitionFields; + +static void partition_fields_done(PartitionFields *f) { + assert(f); + + f->fstype = mfree(f->fstype); + f->label = mfree(f->label); +} + +typedef struct ReplyParameters { + JsonVariant *partitions; + char *image_policy; + uint64_t image_size; + uint32_t sector_size; + sd_id128_t image_uuid; +} ReplyParameters; + +static void reply_parameters_done(ReplyParameters *p) { + assert(p); + + p->image_policy = mfree(p->image_policy); + p->partitions = json_variant_unref(p->partitions); +} + +#endif + +int mountfsd_mount_image( + const char *path, + int userns_fd, + const ImagePolicy *image_policy, + DissectImageFlags flags, + DissectedImage **ret) { + +#if HAVE_BLKID + _cleanup_(reply_parameters_done) ReplyParameters p = {}; + + static const JsonDispatch dispatch_table[] = { + { "partitions", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(struct ReplyParameters, partitions), JSON_MANDATORY }, + { "imagePolicy", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct ReplyParameters, image_policy), 0 }, + { "imageSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct ReplyParameters, image_size), JSON_MANDATORY }, + { "sectorSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(struct ReplyParameters, sector_size), JSON_MANDATORY }, + { "imageUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct ReplyParameters, image_uuid), 0 }, + {} + }; + + _cleanup_(dissected_image_unrefp) DissectedImage *di = NULL; + _cleanup_close_ int image_fd = -EBADF; + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_free_ char *ps = NULL; + unsigned max_fd = UINT_MAX; + const char *error_id; + int r; + + assert(path); + assert(ret); + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem"); + if (r < 0) + return log_error_errno(r, "Failed to connect to mountfsd: %m"); + + r = varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for read: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + image_fd = open(path, O_RDONLY|O_CLOEXEC); + if (image_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + r = varlink_push_dup_fd(vl, image_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + + if (userns_fd >= 0) { + r = varlink_push_dup_fd(vl, userns_fd); + if (r < 0) + return log_error_errno(r, "Failed to push image fd into varlink connection: %m"); + } + + if (image_policy) { + r = image_policy_to_string(image_policy, /* simplify= */ false, &ps); + if (r < 0) + return log_error_errno(r, "Failed format image policy to string: %m"); + } + + JsonVariant *reply = NULL; + r = varlink_callb( + vl, + "io.systemd.MountFileSystem.MountImage", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("imageFileDescriptor", JSON_BUILD_UNSIGNED(0)), + JSON_BUILD_PAIR_CONDITION(userns_fd >= 0, "userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(1)), + JSON_BUILD_PAIR("readOnly", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY))), + JSON_BUILD_PAIR("growFileSystems", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_GROWFS))), + JSON_BUILD_PAIR_CONDITION(ps, "imagePolicy", JSON_BUILD_STRING(ps)), + JSON_BUILD_PAIR("allowInteractiveAuthentication", JSON_BUILD_BOOLEAN(FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))))); + if (r < 0) + return log_error_errno(r, "Failed to call MountImage() varlink call: %m"); + if (!isempty(error_id)) + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to call MountImage() varlink call: %s", error_id); + + r = json_dispatch(reply, dispatch_table, JSON_ALLOW_EXTENSIONS, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse MountImage() reply: %m"); + + log_debug("Effective image policy: %s", p.image_policy); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, p.partitions) { + _cleanup_close_ int fsmount_fd = -EBADF; + + _cleanup_(partition_fields_done) PartitionFields pp = { + .designator = _PARTITION_DESIGNATOR_INVALID, + .architecture = _ARCHITECTURE_INVALID, + .size = UINT64_MAX, + .offset = UINT64_MAX, + .fsmount_fd_idx = UINT_MAX, + }; + + static const JsonDispatch partition_dispatch_table[] = { + { "designator", JSON_VARIANT_STRING, dispatch_partition_designator, offsetof(struct PartitionFields, designator), JSON_MANDATORY }, + { "writable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, rw), JSON_MANDATORY }, + { "growFileSystem", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(struct PartitionFields, growfs), JSON_MANDATORY }, + { "partitionNumber", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, partno), 0 }, + { "architecture", JSON_VARIANT_STRING, dispatch_architecture, offsetof(struct PartitionFields, architecture), 0 }, + { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(struct PartitionFields, uuid), 0 }, + { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, fstype), JSON_MANDATORY }, + { "partitionLabel", JSON_VARIANT_STRING, json_dispatch_string, offsetof(struct PartitionFields, label), 0 }, + { "size", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, size), JSON_MANDATORY }, + { "offset", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(struct PartitionFields, offset), JSON_MANDATORY }, + { "mountFileDescriptor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint, offsetof(struct PartitionFields, fsmount_fd_idx), JSON_MANDATORY }, + {} + }; + + r = json_dispatch(i, partition_dispatch_table, JSON_ALLOW_EXTENSIONS, &pp); + if (r < 0) + return log_error_errno(r, "Failed to parse partition data: %m"); + + if (pp.fsmount_fd_idx != UINT_MAX) { + if (max_fd == UINT_MAX || pp.fsmount_fd_idx > max_fd) + max_fd = pp.fsmount_fd_idx; + + fsmount_fd = varlink_take_fd(vl, pp.fsmount_fd_idx); + if (fsmount_fd < 0) + return fsmount_fd; + } + + assert(pp.designator >= 0); + + if (!di) { + r = dissected_image_new(path, &di); + if (r < 0) + return log_error_errno(r, "Failed to allocated new dissected image structure: %m"); + } + + if (di->partitions[pp.designator].found) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate partition data for '%s'.", partition_designator_to_string(pp.designator)); + + di->partitions[pp.designator] = (DissectedPartition) { + .found = true, + .rw = pp.rw, + .growfs = pp.growfs, + .partno = pp.partno, + .architecture = pp.architecture, + .uuid = pp.uuid, + .fstype = TAKE_PTR(pp.fstype), + .label = TAKE_PTR(pp.label), + .mount_node_fd = -EBADF, + .size = pp.size, + .offset = pp.offset, + .fsmount_fd = TAKE_FD(fsmount_fd), + }; + } + + di->image_size = p.image_size; + di->sector_size = p.sector_size; + di->image_uuid = p.image_uuid; + + *ret = TAKE_PTR(di); + return 0; +#else + return -EOPNOTSUPP; +#endif +} diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 15c0bf7..e31fd54 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -87,6 +87,8 @@ typedef enum DissectImageFlags { 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 */ DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE = 1 << 25, /* Try to mount the image beneath the specified mountpoint, rather than on top of it, and then umount the top */ + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY = 1 << 26, /* Allow userspace verity keyring in /etc/verity.d/ and related dirs */ + DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH = 1 << 27, /* Allow interactive authorization when going through mountfsd */ } DissectImageFlags; struct DissectedImage { @@ -102,6 +104,7 @@ struct DissectedImage { DecryptedImage *decrypted_image; uint32_t sector_size; + uint64_t image_size; char *image_name; sd_id128_t image_uuid; @@ -161,6 +164,7 @@ int dissect_image_file_and_warn(const char *path, const VeritySettings *verity, 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); +void dissected_image_close(DissectedImage *m); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); @@ -169,7 +173,7 @@ int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphr int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); int dissected_image_mount_and_warn(DissectedImage *m, const char *where, uid_t uid_shift, uid_t uid_range, int userns_fd, DissectImageFlags flags); -int dissected_image_acquire_metadata(DissectedImage *m, DissectImageFlags extra_flags); +int dissected_image_acquire_metadata(DissectedImage *m, int userns_fd, DissectImageFlags extra_flags); Architecture dissected_image_architecture(DissectedImage *m); @@ -196,6 +200,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(DecryptedImage*, decrypted_image_unref); int dissected_image_relinquish(DissectedImage *m); int verity_settings_load(VeritySettings *verity, const char *image, const char *root_hash_path, const char *root_hash_sig_path); + +static inline bool verity_settings_set(const VeritySettings *settings) { + return settings && + (settings->root_hash_size > 0 || + (settings->root_hash_sig_size > 0 || + settings->data_path)); +} + void verity_settings_done(VeritySettings *verity); static inline bool verity_settings_data_covers(const VeritySettings *verity, PartitionDesignator d) { @@ -228,3 +240,7 @@ static inline const char *dissected_partition_fstype(const DissectedPartition *m return m->decrypted_node ? m->decrypted_fstype : m->fstype; } + +int get_common_dissect_directory(char **ret); + +int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, DissectImageFlags flags, DissectedImage **ret); diff --git a/src/shared/dlfcn-util.c b/src/shared/dlfcn-util.c deleted file mode 100644 index 8022f55..0000000 --- a/src/shared/dlfcn-util.c +++ /dev/null @@ -1,66 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "dlfcn-util.h" - -static int dlsym_many_or_warnv(void *dl, int log_level, va_list ap) { - void (**fn)(void); - - /* Tries to resolve a bunch of function symbols, and logs an error about if it cannot resolve one of - * them. Note that this function possibly modifies the supplied function pointers if the whole - * operation fails. */ - - while ((fn = va_arg(ap, typeof(fn)))) { - void (*tfn)(void); - const char *symbol; - - symbol = va_arg(ap, typeof(symbol)); - - tfn = (typeof(tfn)) dlsym(dl, symbol); - if (!tfn) - return log_full_errno(log_level, - SYNTHETIC_ERRNO(ELIBBAD), - "Can't find symbol %s: %s", symbol, dlerror()); - *fn = tfn; - } - - return 0; -} - -int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) { - va_list ap; - int r; - - va_start(ap, log_level); - r = dlsym_many_or_warnv(dl, log_level, ap); - va_end(ap); - - return r; -} - -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) { - _cleanup_(dlclosep) void *dl = NULL; - int r; - - if (*dlp) - return 0; /* Already loaded */ - - dl = dlopen(filename, RTLD_LAZY); - if (!dl) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "%s is not installed: %s", filename, dlerror()); - - log_debug("Loaded '%s' via dlopen()", filename); - - va_list ap; - va_start(ap, log_level); - r = dlsym_many_or_warnv(dl, log_level, ap); - va_end(ap); - - if (r < 0) - return r; - - /* Note that we never release the reference here, because there's no real reason to. After all this - * was traditionally a regular shared library dependency which lives forever too. */ - *dlp = TAKE_PTR(dl); - return 1; -} diff --git a/src/shared/dlfcn-util.h b/src/shared/dlfcn-util.h deleted file mode 100644 index 7d8cb4c..0000000 --- a/src/shared/dlfcn-util.h +++ /dev/null @@ -1,39 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#include "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(void*, dlclose, NULL); - -int dlsym_many_or_warn_sentinel(void *dl, int log_level, ...) _sentinel_; -int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_level, ...) _sentinel_; - -#define dlsym_many_or_warn(dl, log_level, ...) \ - dlsym_many_or_warn_sentinel(dl, log_level, __VA_ARGS__, NULL) -#define dlopen_many_sym_or_warn(dlp, filename, log_level, ...) \ - dlopen_many_sym_or_warn_sentinel(dlp, filename, log_level, __VA_ARGS__, NULL) - -#define DLSYM_PROTOTYPE(symbol) \ - extern typeof(symbol)* sym_##symbol -#define DLSYM_FUNCTION(symbol) \ - typeof(symbol)* sym_##symbol = NULL - -/* Macro useful for putting together variable/symbol name pairs when calling dlsym_many_or_warn(). Assumes - * that each library symbol to resolve will be placed in a variable with the "sym_" prefix, i.e. a symbol - * "foobar" is loaded into a variable "sym_foobar". */ -#define DLSYM_ARG(arg) \ - ({ assert_cc(__builtin_types_compatible_p(typeof(sym_##arg), typeof(&arg))); &sym_##arg; }), STRINGIFY(arg) - -/* libbpf is a bit confused about type-safety and API compatibility. Provide a macro that can tape over that mess. Sad. */ -#define DLSYM_ARG_FORCE(arg) \ - &sym_##arg, STRINGIFY(arg) - -static inline void *safe_dlclose(void *p) { - if (!p) - return NULL; - - assert_se(dlclose(p) == 0); - return NULL; -} diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c index b41c9b0..ba24a77 100644 --- a/src/shared/dns-domain.c +++ b/src/shared/dns-domain.c @@ -410,7 +410,7 @@ int dns_name_concat(const char *a, const char *b, DNSLabelFlags flags, char **_r goto finish; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, label, sizeof label, flags); if (r < 0) @@ -507,7 +507,7 @@ int dns_name_compare_func(const char *a, const char *b) { y = b + strlen(b); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; if (x == NULL && y == NULL) return 0; @@ -543,7 +543,7 @@ int dns_name_equal(const char *x, const char *y) { assert(y); for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; r = dns_label_unescape(&x, la, sizeof la, 0); if (r < 0) @@ -574,7 +574,7 @@ int dns_name_endswith(const char *name, const char *suffix) { s = suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; r = dns_label_unescape(&n, ln, sizeof ln, 0); if (r < 0) @@ -612,7 +612,7 @@ int dns_name_startswith(const char *name, const char *prefix) { p = prefix; for (;;) { - char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], lp[DNS_LABEL_MAX+1]; r = dns_label_unescape(&p, lp, sizeof lp, 0); if (r < 0) @@ -644,7 +644,7 @@ int dns_name_change_suffix(const char *name, const char *old_suffix, const char s = old_suffix; for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + char ln[DNS_LABEL_MAX+1], ls[DNS_LABEL_MAX+1]; if (!saved_before) saved_before = n; @@ -929,7 +929,7 @@ bool dns_srv_type_is_valid(const char *name) { return false; for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; /* This more or less implements RFC 6335, Section 5.1 */ @@ -980,6 +980,29 @@ bool dns_service_name_is_valid(const char *name) { return true; } +bool dns_subtype_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 7.2 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > DNS_LABEL_MAX) + return false; + + return true; +} + int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { char escaped[DNS_LABEL_ESCAPED_MAX]; _cleanup_free_ char *n = NULL; @@ -1227,7 +1250,7 @@ int dns_name_common_suffix(const char *a, const char *b, const char **ret) { return m; for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + char la[DNS_LABEL_MAX+1], lb[DNS_LABEL_MAX+1]; const char *x, *y; if (k >= n || k >= m) { @@ -1328,7 +1351,7 @@ int dns_name_apply_idna(const char *name, char **ret) { assert(ret); for (;;) { - char label[DNS_LABEL_MAX]; + char label[DNS_LABEL_MAX+1]; r = dns_label_unescape(&name, label, sizeof label, 0); if (r < 0) diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h index 331fb89..8ad00d6 100644 --- a/src/shared/dns-domain.h +++ b/src/shared/dns-domain.h @@ -83,6 +83,7 @@ int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, boo bool dns_srv_type_is_valid(const char *name); bool dnssd_srv_type_is_valid(const char *name); bool dns_service_name_is_valid(const char *name); +bool dns_subtype_name_is_valid(const char *name); int dns_service_join(const char *name, const char *type, const char *domain, char **ret); int dns_service_split(const char *joined, char **ret_name, char **ret_type, char **ret_domain); diff --git a/src/shared/dropin.c b/src/shared/dropin.c index d46e838..9a786d0 100644 --- a/src/shared/dropin.c +++ b/src/shared/dropin.c @@ -26,7 +26,7 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, const char *name, char **ret_p, char **ret_q) { - char prefix[DECIMAL_STR_MAX(unsigned)]; + char prefix[DECIMAL_STR_MAX(unsigned) + 1] = {}; _cleanup_free_ char *b = NULL, *p = NULL, *q = NULL; assert(unit); @@ -34,7 +34,8 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, assert(ret_p); assert(ret_q); - sprintf(prefix, "%u", level); + if (level != UINT_MAX) + xsprintf(prefix, "%u-", level); b = xescape(name, "/."); if (!b) @@ -44,7 +45,7 @@ int drop_in_file(const char *dir, const char *unit, unsigned level, return -EINVAL; p = strjoin(dir, "/", unit, ".d"); - q = strjoin(p, "/", prefix, "-", b, ".conf"); + q = strjoin(p, "/", prefix, b, ".conf"); if (!p || !q) return -ENOMEM; diff --git a/src/shared/edit-util.c b/src/shared/edit-util.c index 045839b..cfb2828 100644 --- a/src/shared/edit-util.c +++ b/src/shared/edit-util.c @@ -16,6 +16,15 @@ #include "strv.h" #include "tmpfile-util-label.h" +typedef struct EditFile { + EditFileContext *context; + char *path; + char *original_path; + char **comment_paths; + char *temp; + unsigned line; +} EditFile; + void edit_file_context_done(EditFileContext *context) { int r; @@ -93,41 +102,22 @@ int edit_files_add( .path = TAKE_PTR(new_path), .original_path = TAKE_PTR(new_original_path), .comment_paths = TAKE_PTR(new_comment_paths), + .line = 1, }; context->n_files++; return 1; } -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; - +static int populate_edit_temp_file(EditFile *e, FILE *f, const char *filename) { assert(e); - assert(e->context); - assert(e->path); - assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end)); + assert(f); + assert(filename); - if (e->temp) - return 0; - - r = mkdir_parents_label(e->path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path); - - r = fopen_temporary_label(e->path, e->path, &f, &temp); - if (r < 0) - return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path); - - if (fchmod(fileno(f), 0644) < 0) - return log_error_errno(errno, "Failed to change mode of 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; + bool has_original = e->original_path && access(e->original_path, F_OK) >= 0; + bool has_target = access(e->path, F_OK) >= 0; + const char *source; + int r; if (has_original && (!has_target || e->context->overwrite_with_origin)) /* We are asked to overwrite target with original_path or target doesn't exist. */ @@ -160,7 +150,7 @@ static int create_edit_temp_file(EditFile *e) { source_contents && endswith(source_contents, "\n") ? "" : "\n", e->context->marker_end); - line = 4; /* Start editing at the contents area */ + e->line = 4; /* Start editing at the contents area */ STRV_FOREACH(path, e->comment_paths) { _cleanup_free_ char *comment = NULL; @@ -189,16 +179,54 @@ static int create_edit_temp_file(EditFile *e) { 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); + return log_error_errno(r, "Failed to copy file '%s' to temporary file '%s': %m", + source, filename); } } + return 0; +} + +static int create_edit_temp_file(EditFile *e, const char *contents, size_t contents_size) { + _cleanup_(unlink_and_freep) char *temp = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(e); + assert(e->context); + assert(e->path); + assert(!e->comment_paths || (e->context->marker_start && e->context->marker_end)); + assert(contents || contents_size == 0); + + if (e->temp) + return 0; + + r = mkdir_parents_label(e->path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create parent directories for '%s': %m", e->path); + + r = fopen_temporary_label(e->path, e->path, &f, &temp); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file for '%s': %m", e->path); + + if (fchmod(fileno(f), 0644) < 0) + return log_error_errno(errno, "Failed to change mode of temporary file '%s': %m", temp); + + if (e->context->stdin) { + if (fwrite(contents, 1, contents_size, f) != contents_size) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to copy input to temporary file '%s'.", temp); + } else { + r = populate_edit_temp_file(e, f, temp); + if (r < 0) + return r; + } + 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; return 0; } @@ -282,7 +310,7 @@ static int run_editor(const EditFileContext *context) { } static int strip_edit_temp_file(EditFile *e) { - _cleanup_free_ char *old_contents = NULL, *new_contents = NULL; + _cleanup_free_ char *old_contents = NULL, *tmp = NULL, *new_contents = NULL; const char *stripped; int r; @@ -294,15 +322,17 @@ static int strip_edit_temp_file(EditFile *e) { if (r < 0) return log_error_errno(r, "Failed to read temporary file '%s': %m", e->temp); - if (e->context->marker_start) { + tmp = strdup(old_contents); + if (!tmp) + return log_oom(); + + if (e->context->marker_start && !e->context->stdin) { /* Trim out the lines between the two markers */ char *contents_start, *contents_end; assert(e->context->marker_end); - contents_start = strstrafter(old_contents, e->context->marker_start); - if (!contents_start) - contents_start = old_contents; + contents_start = strstrafter(tmp, e->context->marker_start) ?: tmp; contents_end = strstr(contents_start, e->context->marker_end); if (contents_end) @@ -310,9 +340,13 @@ static int strip_edit_temp_file(EditFile *e) { stripped = strstrip(contents_start); } else - stripped = strstrip(old_contents); - if (isempty(stripped)) - return 0; /* File is empty (has no real changes) */ + stripped = strstrip(tmp); + + if (isempty(stripped)) { + /* File is empty (has no real changes) */ + log_notice("%s: after editing, new contents are empty, not writing file.", e->path); + return 0; + } /* Trim prefix and suffix, but ensure suffixed by single newline */ new_contents = strjoin(stripped, "\n"); @@ -320,16 +354,19 @@ static int strip_edit_temp_file(EditFile *e) { return log_oom(); if (streq(old_contents, new_contents)) /* Don't touch the file if the above didn't change a thing */ - return 1; /* Contents unchanged after stripping but has changes */ + return 1; /* Contents have real changes */ - r = write_string_file(e->temp, new_contents, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_TRUNCATE | WRITE_STRING_FILE_AVOID_NEWLINE); + 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 strip temporary file '%s': %m", e->temp); - return 1; /* Contents have real changes and are changed after stripping */ + return 1; /* Contents have real changes */ } int do_edit_files_and_install(EditFileContext *context) { + _cleanup_free_ char *data = NULL; + size_t data_size = 0; int r; assert(context); @@ -337,33 +374,41 @@ int do_edit_files_and_install(EditFileContext *context) { if (context->n_files == 0) return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Got no files to edit."); - FOREACH_ARRAY(i, context->files, context->n_files) { - r = create_edit_temp_file(i); + if (context->stdin) { + r = read_full_stream(stdin, &data, &data_size); + if (r < 0) + return log_error_errno(r, "Failed to read stdin: %m"); + } + + FOREACH_ARRAY(editfile, context->files, context->n_files) { + r = create_edit_temp_file(editfile, data, data_size); if (r < 0) return r; } - r = run_editor(context); - if (r < 0) - return r; + if (!context->stdin) { + r = run_editor(context); + if (r < 0) + return r; + } - FOREACH_ARRAY(i, context->files, context->n_files) { + FOREACH_ARRAY(editfile, context->files, context->n_files) { /* Always call strip_edit_temp_file which will tell if the temp file has actual changes */ - r = strip_edit_temp_file(i); + r = strip_edit_temp_file(editfile); if (r < 0) return r; if (r == 0) /* temp file doesn't carry actual changes, ignoring */ continue; - r = RET_NERRNO(rename(i->temp, i->path)); + r = RET_NERRNO(rename(editfile->temp, editfile->path)); if (r < 0) 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); + editfile->temp, + editfile->path); + editfile->temp = mfree(editfile->temp); - log_info("Successfully installed edited file '%s'.", i->path); + log_info("Successfully installed edited file '%s'.", editfile->path); } return 0; diff --git a/src/shared/edit-util.h b/src/shared/edit-util.h index 83b3df8..9d9c890 100644 --- a/src/shared/edit-util.h +++ b/src/shared/edit-util.h @@ -7,25 +7,16 @@ #define DROPIN_MARKER_END "### Edits below this comment will be discarded" typedef struct EditFile EditFile; -typedef struct EditFileContext EditFileContext; - -struct EditFile { - EditFileContext *context; - char *path; - char *original_path; - char **comment_paths; - char *temp; - unsigned line; -}; - -struct EditFileContext { + +typedef struct EditFileContext { EditFile *files; size_t n_files; const char *marker_start; const char *marker_end; bool remove_parent; - bool overwrite_with_origin; /* whether to always overwrite target with original file */ -}; + bool overwrite_with_origin; /* Always overwrite target with original file. */ + bool stdin; /* Read contents from stdin instead of launching an editor. */ +} EditFileContext; void edit_file_context_done(EditFileContext *context); diff --git a/src/shared/efi-api.c b/src/shared/efi-api.c index 4cd1091..3ca33ef 100644 --- a/src/shared/efi-api.c +++ b/src/shared/efi-api.c @@ -7,6 +7,7 @@ #include "efi-api.h" #include "efivars.h" #include "fd-util.h" +#include "fileio.h" #include "sort-util.h" #include "stat-util.h" #include "stdio-util.h" @@ -453,13 +454,13 @@ int efi_get_boot_options(uint16_t **ret_options) { FOREACH_DIRENT(de, dir, return -errno) { int id; - if (strncmp(de->d_name, "Boot", 4) != 0) + if (!startswith(de->d_name, "Boot")) continue; if (strlen(de->d_name) != 45) continue; - if (strcmp(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR("")) != 0) /* generate variable suffix using macro */ + if (!streq(de->d_name + 8, EFI_GLOBAL_VARIABLE_STR(""))) /* generate variable suffix using macro */ continue; id = boot_id_hex(de->d_name + 4); @@ -481,6 +482,7 @@ int efi_get_boot_options(uint16_t **ret_options) { bool efi_has_tpm2(void) { static int cache = -1; + int r; /* Returns whether the system has a TPM2 chip which is known to the EFI firmware. */ @@ -488,30 +490,35 @@ bool efi_has_tpm2(void) { return cache; /* First, check if we are on an EFI boot at all. */ - if (!is_efi_boot()) { - cache = 0; - return cache; - } + if (!is_efi_boot()) + return (cache = false); /* Then, check if the ACPI table "TPM2" exists, which is the TPM2 event log table, see: * https://trustedcomputinggroup.org/wp-content/uploads/TCG_ACPIGeneralSpecification_v1.20_r8.pdf - * This table exists whenever the firmware is hooked up to TPM2. */ - cache = access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0; - if (cache) - return cache; - + * This table exists whenever the firmware knows ACPI and is hooked up to TPM2. */ + if (access("/sys/firmware/acpi/tables/TPM2", F_OK) >= 0) + return (cache = true); if (errno != ENOENT) log_debug_errno(errno, "Unable to test whether /sys/firmware/acpi/tables/TPM2 exists, assuming it doesn't: %m"); /* As the last try, check if the EFI firmware provides the EFI_TCG2_FINAL_EVENTS_TABLE * stored in EFI configuration table, see: - * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf - */ - cache = access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0; - if (!cache && errno != ENOENT) - log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m"); + * + * https://trustedcomputinggroup.org/wp-content/uploads/EFI-Protocol-Specification-rev13-160330final.pdf */ + if (access("/sys/kernel/security/tpm0/binary_bios_measurements", F_OK) >= 0) { + _cleanup_free_ char *major = NULL; + + /* The EFI table might exist for TPM 1.2 as well, hence let's check explicitly which TPM version we are looking at here. */ + r = read_virtual_file("/sys/class/tpm/tpm0/tpm_version_major", SIZE_MAX, &major, /* ret_size= */ NULL); + if (r >= 0) + return (cache = streq(strstrip(major), "2")); + + log_debug_errno(r, "Unable to read /sys/class/tpm/tpm0/tpm_version_major, assuming TPM does not qualify as TPM2: %m"); + + } else if (errno != ENOENT) + log_debug_errno(errno, "Unable to test whether /sys/kernel/security/tpm0/binary_bios_measurements exists, assuming it doesn't: %m"); - return cache; + return (cache = false); } #endif diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c index 7d6bda9..ab377aa 100644 --- a/src/shared/efi-loader.c +++ b/src/shared/efi-loader.c @@ -262,7 +262,7 @@ int efi_measured_uki(int log_level) { * being used, but it measured things into a different PCR than we are configured for in * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */ - r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, + r = secure_getenv_bool("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, * for debugging purposes */ if (r >= 0) return (cached = r); diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index 24ed16e..9d1f494 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -38,55 +38,60 @@ static void *dw_dl = NULL; static void *elf_dl = NULL; /* libdw symbols */ -Dwarf_Attribute *(*sym_dwarf_attr_integrate)(Dwarf_Die *, unsigned int, Dwarf_Attribute *); -const char *(*sym_dwarf_diename)(Dwarf_Die *); -const char *(*sym_dwarf_formstring)(Dwarf_Attribute *); -int (*sym_dwarf_getscopes)(Dwarf_Die *, Dwarf_Addr, Dwarf_Die **); -int (*sym_dwarf_getscopes_die)(Dwarf_Die *, Dwarf_Die **); -Elf *(*sym_dwelf_elf_begin)(int); +static DLSYM_FUNCTION(dwarf_attr_integrate); +static DLSYM_FUNCTION(dwarf_diename); +static DLSYM_FUNCTION(dwarf_formstring); +static DLSYM_FUNCTION(dwarf_getscopes); +static DLSYM_FUNCTION(dwarf_getscopes_die); +static DLSYM_FUNCTION(dwelf_elf_begin); #if HAVE_DWELF_ELF_E_MACHINE_STRING -const char *(*sym_dwelf_elf_e_machine_string)(int); +static DLSYM_FUNCTION(dwelf_elf_e_machine_string); #endif -ssize_t (*sym_dwelf_elf_gnu_build_id)(Elf *, const void **); -int (*sym_dwarf_tag)(Dwarf_Die *); -Dwfl_Module *(*sym_dwfl_addrmodule)(Dwfl *, Dwarf_Addr); -Dwfl *(*sym_dwfl_begin)(const Dwfl_Callbacks *); -int (*sym_dwfl_build_id_find_elf)(Dwfl_Module *, void **, const char *, Dwarf_Addr, char **, Elf **); -int (*sym_dwfl_core_file_attach)(Dwfl *, Elf *); -int (*sym_dwfl_core_file_report)(Dwfl *, Elf *, const char *); -void (*sym_dwfl_end)(Dwfl *); -const char *(*sym_dwfl_errmsg)(int); -int (*sym_dwfl_errno)(void); -bool (*sym_dwfl_frame_pc)(Dwfl_Frame *, Dwarf_Addr *, bool *); -ptrdiff_t (*sym_dwfl_getmodules)(Dwfl *, int (*)(Dwfl_Module *, void **, const char *, Dwarf_Addr, void *), void *, ptrdiff_t); -int (*sym_dwfl_getthreads)(Dwfl *, int (*)(Dwfl_Thread *, void *), void *); -Dwarf_Die *(*sym_dwfl_module_addrdie)(Dwfl_Module *, Dwarf_Addr, Dwarf_Addr *); -const char *(*sym_dwfl_module_addrname)(Dwfl_Module *, GElf_Addr); -int (*sym_dwfl_module_build_id)(Dwfl_Module *, const unsigned char **, GElf_Addr *); -Elf *(*sym_dwfl_module_getelf)(Dwfl_Module *, GElf_Addr *); -const char *(*sym_dwfl_module_info)(Dwfl_Module *, void ***, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, Dwarf_Addr *, const char **, const char **); -int (*sym_dwfl_offline_section_address)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, GElf_Word, const GElf_Shdr *, Dwarf_Addr *); -int (*sym_dwfl_report_end)(Dwfl *, int (*)(Dwfl_Module *, void *, const char *, Dwarf_Addr, void *), void *); -int (*sym_dwfl_standard_find_debuginfo)(Dwfl_Module *, void **, const char *, Dwarf_Addr, const char *, const char *, GElf_Word, char **); -int (*sym_dwfl_thread_getframes)(Dwfl_Thread *, int (*)(Dwfl_Frame *, void *), void *); -pid_t (*sym_dwfl_thread_tid)(Dwfl_Thread *); +static DLSYM_FUNCTION(dwelf_elf_gnu_build_id); +static DLSYM_FUNCTION(dwarf_tag); +static DLSYM_FUNCTION(dwfl_addrmodule); +static DLSYM_FUNCTION(dwfl_begin); +static DLSYM_FUNCTION(dwfl_build_id_find_elf); +static DLSYM_FUNCTION(dwfl_core_file_attach); +static DLSYM_FUNCTION(dwfl_core_file_report); +static DLSYM_FUNCTION(dwfl_end); +static DLSYM_FUNCTION(dwfl_errmsg); +static DLSYM_FUNCTION(dwfl_errno); +static DLSYM_FUNCTION(dwfl_frame_pc); +static DLSYM_FUNCTION(dwfl_getmodules); +static DLSYM_FUNCTION(dwfl_getthreads); +static DLSYM_FUNCTION(dwfl_module_addrdie); +static DLSYM_FUNCTION(dwfl_module_addrname); +static DLSYM_FUNCTION(dwfl_module_build_id); +static DLSYM_FUNCTION(dwfl_module_getelf); +static DLSYM_FUNCTION(dwfl_module_info); +static DLSYM_FUNCTION(dwfl_offline_section_address); +static DLSYM_FUNCTION(dwfl_report_end); +static DLSYM_FUNCTION(dwfl_standard_find_debuginfo); +static DLSYM_FUNCTION(dwfl_thread_getframes); +static DLSYM_FUNCTION(dwfl_thread_tid); /* libelf symbols */ -Elf *(*sym_elf_begin)(int, Elf_Cmd, Elf *); -int (*sym_elf_end)(Elf *); -Elf_Data *(*sym_elf_getdata_rawchunk)(Elf *, int64_t, size_t, Elf_Type); -GElf_Ehdr *(*sym_gelf_getehdr)(Elf *, GElf_Ehdr *); -int (*sym_elf_getphdrnum)(Elf *, size_t *); -const char *(*sym_elf_errmsg)(int); -int (*sym_elf_errno)(void); -Elf *(*sym_elf_memory)(char *, size_t); -unsigned int (*sym_elf_version)(unsigned int); -GElf_Phdr *(*sym_gelf_getphdr)(Elf *, int, GElf_Phdr *); -size_t (*sym_gelf_getnote)(Elf_Data *, size_t, GElf_Nhdr *, size_t *, size_t *); +static DLSYM_FUNCTION(elf_begin); +static DLSYM_FUNCTION(elf_end); +static DLSYM_FUNCTION(elf_getdata_rawchunk); +static DLSYM_FUNCTION(gelf_getehdr); +static DLSYM_FUNCTION(elf_getphdrnum); +static DLSYM_FUNCTION(elf_errmsg); +static DLSYM_FUNCTION(elf_errno); +static DLSYM_FUNCTION(elf_memory); +static DLSYM_FUNCTION(elf_version); +static DLSYM_FUNCTION(gelf_getphdr); +static DLSYM_FUNCTION(gelf_getnote); int dlopen_dw(void) { int r; + ELF_NOTE_DLOPEN("dw", + "Support for backtrace and ELF package metadata decoding from core files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libdw.so.1"); + r = dlopen_many_sym_or_warn( &dw_dl, "libdw.so.1", LOG_DEBUG, DLSYM_ARG(dwarf_getscopes), @@ -130,6 +135,11 @@ int dlopen_dw(void) { int dlopen_elf(void) { int r; + ELF_NOTE_DLOPEN("elf", + "Support for backtraces and reading ELF package metadata from core files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libelf.so.1"); + r = dlopen_many_sym_or_warn( &elf_dl, "libelf.so.1", LOG_DEBUG, DLSYM_ARG(elf_begin), @@ -348,7 +358,7 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e /* Package metadata is in PT_NOTE headers. */ program_header = sym_gelf_getphdr(elf, i, &mem); - if (!program_header || (program_header->p_type != PT_NOTE && program_header->p_type != PT_INTERP)) + if (!program_header || !IN_SET(program_header->p_type, PT_NOTE, PT_INTERP)) continue; if (program_header->p_type == PT_INTERP) { @@ -540,7 +550,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, continue; /* Check that the end of segment is a valid address. */ - if (__builtin_add_overflow(program_header->p_vaddr, program_header->p_memsz, &end_of_segment)) { + if (!ADD_SAFE(&end_of_segment, program_header->p_vaddr, program_header->p_memsz)) { log_error("Abort due to corrupted core dump, end of segment address %#zx + %#zx overflows", (size_t)program_header->p_vaddr, (size_t)program_header->p_memsz); return DWARF_CB_ABORT; } diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c index dce9e00..1e100c3 100644 --- a/src/shared/ethtool-util.c +++ b/src/shared/ethtool-util.c @@ -182,7 +182,6 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) { struct ifreq ifr = { .ifr_data = (void*) &ecmd, }; - char *d; int r; assert(ethtool_fd); @@ -201,12 +200,7 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) { if (isempty(ecmd.driver)) return -ENODATA; - d = strdup(ecmd.driver); - if (!d) - return -ENOMEM; - - *ret = d; - return 0; + return strdup_to(ret, ecmd.driver); } int ethtool_get_link_info( diff --git a/src/shared/exec-util.c b/src/shared/exec-util.c index c27f3a5..996edbf 100644 --- a/src/shared/exec-util.c +++ b/src/shared/exec-util.c @@ -36,27 +36,35 @@ /* Put this test here for a lack of better place */ assert_cc(EAGAIN == EWOULDBLOCK); -static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, bool set_systemd_exec_pid) { - pid_t _pid; +static int do_spawn( + const char *path, + char *argv[], + int stdout_fd, + bool set_systemd_exec_pid, + pid_t *ret_pid) { + int r; + assert(path); + assert(ret_pid); + if (null_or_empty_path(path) > 0) { log_debug("%s is empty (a mask).", path); return 0; } - r = safe_fork("(direxec)", FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE, &_pid); + pid_t pid; + r = safe_fork_full( + "(direxec)", + (const int[]) { STDIN_FILENO, stdout_fd < 0 ? STDOUT_FILENO : stdout_fd, STDERR_FILENO }, + /* except_fds= */ NULL, /* n_except_fds= */ 0, + FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO|FORK_CLOSE_ALL_FDS, + &pid); if (r < 0) return r; if (r == 0) { char *_argv[2]; - if (stdout_fd >= 0) { - r = rearrange_stdio(STDIN_FILENO, TAKE_FD(stdout_fd), STDERR_FILENO); - if (r < 0) - _exit(EXIT_FAILURE); - } - if (set_systemd_exec_pid) { r = setenv_systemd_exec_pid(false); if (r < 0) @@ -75,7 +83,7 @@ static int do_spawn(const char *path, char *argv[], int stdout_fd, pid_t *pid, b _exit(EXIT_FAILURE); } - *pid = _pid; + *ret_pid = pid; return 1; } @@ -147,7 +155,7 @@ static int do_execute( log_debug("About to execute %s%s%s", t, argv ? " " : "", argv ? strnull(args) : ""); } - r = do_spawn(t, argv, fd, &pid, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID)); + r = do_spawn(t, argv, fd, FLAGS_SET(flags, EXEC_DIR_SET_SYSTEMD_EXEC_PID), &pid); if (r <= 0) continue; @@ -539,7 +547,7 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *ret r = safe_fork_full(name, NULL, - except, + (int*) except, /* safe_fork_full only changes except if you pass in FORK_PACK_FDS, which we don't */ n_except, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE, ret_pid); diff --git a/src/shared/fdset.c b/src/shared/fdset.c index e5b8e92..cb5a69e 100644 --- a/src/shared/fdset.c +++ b/src/shared/fdset.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "sd-daemon.h" @@ -40,8 +41,8 @@ int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) { if (!s) return -ENOMEM; - for (size_t i = 0; i < n_fds; i++) { - r = fdset_put(s, fds[i]); + FOREACH_ARRAY(fd, fds, n_fds) { + r = fdset_put(s, *fd); if (r < 0) return r; } @@ -71,7 +72,7 @@ void fdset_close(FDSet *s) { log_debug("Closing set fd %i (%s)", fd, strna(path)); } - (void) close_nointr(fd); + (void) close(fd); } } @@ -246,7 +247,7 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) { return -ENOMEM; n = sd_listen_fds(unset); - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { r = fdset_put(s, fd); if (r < 0) return r; diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index db87084..f830d6d 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -556,13 +556,16 @@ int find_esp_and_warn( 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); + 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; @@ -871,12 +874,12 @@ int find_xbootldr_and_warn_at( } int find_xbootldr_and_warn( - const char *root, - const char *path, - int unprivileged_mode, - char **ret_path, - sd_id128_t *ret_uuid, - dev_t *ret_devid) { + const char *root, + const char *path, + int unprivileged_mode, + char **ret_path, + sd_id128_t *ret_uuid, + dev_t *ret_devid) { _cleanup_close_ int rfd = -EBADF; _cleanup_free_ char *p = NULL; @@ -888,10 +891,13 @@ int find_xbootldr_and_warn( 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); + 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; diff --git a/src/shared/firewall-util-iptables.c b/src/shared/firewall-util-iptables.c index b70b740..e2e5bb3 100644 --- a/src/shared/firewall-util-iptables.c +++ b/src/shared/firewall-util-iptables.c @@ -1,19 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -/* Temporary work-around for broken glibc vs. linux kernel header definitions - * This is already fixed upstream, remove this when distributions have updated. - */ -#define _NET_IF_H 1 - +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include #include #include -#include -#ifndef IFNAMSIZ -#define IFNAMSIZ 16 -#endif #include #include #include @@ -361,6 +354,11 @@ int fw_iptables_add_local_dnat( } static int dlopen_iptc(void) { + ELF_NOTE_DLOPEN("ip4tc", + "Support for firewall rules with iptables backend", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libip4tc.so.2"); + return dlopen_many_sym_or_warn( &iptc_dl, "libip4tc.so.2", LOG_DEBUG, diff --git a/src/shared/firewall-util-nft.c b/src/shared/firewall-util-nft.c index fe986ed..e9bd286 100644 --- a/src/shared/firewall-util-nft.c +++ b/src/shared/firewall-util-nft.c @@ -1316,7 +1316,7 @@ int config_parse_nft_set( return 0; q = tuple; - r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set, NULL); + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE, &source_str, &family_str, &table, &set); if (r == -ENOMEM) return log_oom(); if (r != 4 || !isempty(q)) { diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h index 14e35be..25ba0d4 100644 --- a/src/shared/firewall-util.h +++ b/src/shared/firewall-util.h @@ -43,7 +43,7 @@ typedef enum NFTSetSource { NFT_SET_SOURCE_GROUP, _NFT_SET_SOURCE_MAX, _NFT_SET_SOURCE_INVALID = -EINVAL, -} NFTSetSource; +} NFTSetSource; typedef struct NFTSet { NFTSetSource source; diff --git a/src/shared/format-table.c b/src/shared/format-table.c index 9a19177..9146444 100644 --- a/src/shared/format-table.c +++ b/src/shared/format-table.c @@ -77,7 +77,9 @@ typedef struct TableData { unsigned ellipsize_percent; /* 0 … 100, where to place the ellipsis when compression is needed */ unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */ - bool uppercase; /* Uppercase string on display */ + bool uppercase:1; /* Uppercase string on display */ + bool underline:1; + bool rgap_underline:1; const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */ const char *rgap_color; /* The ANSI color to use for the gap right of this cell. Usually used to underline entire rows in a gapless fashion */ @@ -401,7 +403,7 @@ static bool table_data_matches( return false; /* If a color/url is set, refuse to merge */ - if (d->color || d->rgap_color) + if (d->color || d->rgap_color || d->underline || d->rgap_underline) return false; if (d->url) return false; @@ -617,6 +619,8 @@ static int table_dedup_cell(Table *t, TableCell *cell) { nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -759,6 +763,46 @@ int table_set_rgap_color(Table *t, TableCell *cell, const char *color) { return 0; } +int table_set_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->underline == b) + return 0; + + d->underline = b; + return 1; +} + +int table_set_rgap_underline(Table *t, TableCell *cell, bool b) { + TableData *d; + int r; + + assert(t); + assert(cell); + + r = table_dedup_cell(t, cell); + if (r < 0) + return r; + + assert_se(d = table_get_data(t, cell)); + + if (d->rgap_underline == b) + return 0; + + d->rgap_underline = b; + return 1; +} + int table_set_url(Table *t, TableCell *cell, const char *url) { _cleanup_free_ char *copy = NULL; int r; @@ -834,6 +878,8 @@ int table_update(Table *t, TableCell *cell, TableDataType type, const void *data nd->color = od->color; nd->rgap_color = od->rgap_color; + nd->underline = od->underline; + nd->rgap_underline = od->rgap_underline; nd->url = TAKE_PTR(curl); table_data_unref(od); @@ -1101,6 +1147,31 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) { goto check; } + case TABLE_SET_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_RGAP_UNDERLINE: { + int u = va_arg(ap, int); + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + + case TABLE_SET_BOTH_UNDERLINES: { + int u = va_arg(ap, int); + + r = table_set_underline(t, last_cell, u); + if (r < 0) { + va_end(ap); + return r; + } + + r = table_set_rgap_underline(t, last_cell, u); + goto check; + } + case TABLE_SET_URL: { const char *u = va_arg(ap, const char*); r = table_set_url(t, last_cell, u); @@ -1641,7 +1712,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas if (!p) return NULL; - if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, 0)) + if (!format_bytes_full(p, FORMAT_BYTES_MAX, d->size, FORMAT_BYTES_BELOW_POINT)) return table_ersatz_string(t); n = strlen(p); @@ -1932,7 +2003,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas if (d->mode == MODE_INVALID) return table_ersatz_string(t); - return inode_type_to_string(d->mode); + return inode_type_to_string(d->mode) ?: table_ersatz_string(t); case TABLE_DEVNUM: if (devnum_is_zero(d->devnum)) @@ -2130,8 +2201,6 @@ static const char* table_data_color(TableData *d) { if (d->type == TABLE_FIELD) return ansi_bright_blue(); - if (d->type == TABLE_HEADER) - return ansi_underline(); return NULL; } @@ -2139,11 +2208,29 @@ static const char* table_data_color(TableData *d) { static const char* table_data_rgap_color(TableData *d) { assert(d); - if (d->rgap_color) - return d->rgap_color; + return d->rgap_color ?: d->rgap_color; +} + +static const char* table_data_underline(TableData *d) { + assert(d); + + if (d->underline) + return /* cescape( */ansi_add_underline_grey()/* ) */; if (d->type == TABLE_HEADER) - return ansi_underline(); + return ansi_add_underline(); + + return NULL; +} + +static const char* table_data_rgap_underline(TableData *d) { + assert(d); + + if (d->rgap_underline) + return ansi_add_underline_grey(); + + if (d->type == TABLE_HEADER) + return ansi_add_underline(); return NULL; } @@ -2418,13 +2505,13 @@ int table_print(Table *t, FILE *f) { row = t->data + i * t->n_columns; do { - const char *gap_color = NULL; + const char *gap_color = NULL, *gap_underline = NULL; more_sublines = false; for (size_t j = 0; j < display_columns; j++) { _cleanup_free_ char *buffer = NULL, *extracted = NULL; bool lines_truncated = false; - const char *field, *color = NULL; + const char *field, *color = NULL, *underline = NULL; TableData *d; size_t l; @@ -2490,6 +2577,7 @@ int table_print(Table *t, FILE *f) { /* Drop trailing white spaces of last column when no cosmetics is set. */ if (j == display_columns - 1 && (!colors_enabled() || !table_data_color(d)) && + (!underline_enabled() || !table_data_underline(d)) && (!urlify_enabled() || !d->url)) delete_trailing_chars(aligned, NULL); @@ -2511,31 +2599,40 @@ int table_print(Table *t, FILE *f) { if (colors_enabled() && gap_color) fputs(gap_color, f); + if (underline_enabled() && gap_underline) + fputs(gap_underline, f); if (j > 0) fputc(' ', f); /* column separator left of cell */ + /* Undo gap color/underline */ + if ((colors_enabled() && gap_color) || + (underline_enabled() && gap_underline)) + fputs(ANSI_NORMAL, f); + if (colors_enabled()) { color = table_data_color(d); - - /* Undo gap color */ - if (gap_color) - fputs(ANSI_NORMAL, f); - if (color) fputs(color, f); } + if (underline_enabled()) { + underline = table_data_underline(d); + if (underline) + fputs(underline, f); + } + fputs(field, f); - if (colors_enabled() && color) + if (color || underline) fputs(ANSI_NORMAL, f); gap_color = table_data_rgap_color(d); + gap_underline = table_data_rgap_underline(d); } fputc('\n', f); - n_subline ++; + n_subline++; } while (more_sublines); } diff --git a/src/shared/format-table.h b/src/shared/format-table.h index 37bfbca..b169eb0 100644 --- a/src/shared/format-table.h +++ b/src/shared/format-table.h @@ -68,6 +68,9 @@ typedef enum TableDataType { TABLE_SET_COLOR, TABLE_SET_RGAP_COLOR, TABLE_SET_BOTH_COLORS, + TABLE_SET_UNDERLINE, + TABLE_SET_RGAP_UNDERLINE, + TABLE_SET_BOTH_UNDERLINES, TABLE_SET_URL, TABLE_SET_UPPERCASE, @@ -111,6 +114,8 @@ int table_set_align_percent(Table *t, TableCell *cell, unsigned percent); int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent); int table_set_color(Table *t, TableCell *cell, const char *color); int table_set_rgap_color(Table *t, TableCell *cell, const char *color); +int table_set_underline(Table *t, TableCell *cell, bool b); +int table_set_rgap_underline(Table *t, TableCell *cell, bool b); int table_set_url(Table *t, TableCell *cell, const char *url); int table_set_uppercase(Table *t, TableCell *cell, bool b); @@ -129,7 +134,7 @@ int table_set_sort_internal(Table *t, size_t first_column, ...); #define table_set_sort(...) table_set_sort_internal(__VA_ARGS__, SIZE_MAX) int table_set_reverse(Table *t, size_t column, bool b); int table_hide_column_from_display_internal(Table *t, ...); -#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, (size_t) -1) +#define table_hide_column_from_display(t, ...) table_hide_column_from_display_internal(t, __VA_ARGS__, SIZE_MAX) int table_print(Table *t, FILE *f); int table_format(Table *t, char **ret); @@ -139,6 +144,12 @@ static inline TableCell* TABLE_HEADER_CELL(size_t i) { } size_t table_get_rows(Table *t); +static inline bool table_isempty(Table *t) { + if (!t) + return true; + + return table_get_rows(t) <= 1; +} size_t table_get_columns(Table *t); size_t table_get_current_column(Table *t); diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c index 55e76b6..60ceff5 100644 --- a/src/shared/fstab-util.c +++ b/src/shared/fstab-util.c @@ -74,8 +74,8 @@ bool fstab_is_extrinsic(const char *mount, const char *opts) { "/run/initramfs", /* This should stay around from before we boot until after we shutdown */ "/run/nextroot", /* Similar (though might be updated from the host) */ "/proc", /* All of this is API VFS */ - "/sys", /* … dito … */ - "/dev")) /* … dito … */ + "/sys", /* … ditto … */ + "/dev")) /* … ditto … */ return true; /* If this is an initrd mount, and we are not in the initrd, then leave @@ -105,6 +105,34 @@ static int fstab_is_same_node(const char *what_fstab, const char *path) { return false; } +int fstab_has_mount_point_prefix_strv(char **prefixes) { + _cleanup_endmntent_ FILE *f = NULL; + + assert(prefixes); + + /* This function returns true if at least one entry in fstab has a mount point that starts with one + * of the passed prefixes. */ + + if (!fstab_enabled()) + return false; + + f = setmntent(fstab_path(), "re"); + if (!f) + return errno == ENOENT ? false : -errno; + + for (;;) { + struct mntent *me; + + errno = 0; + me = getmntent(f); + if (!me) + return errno != 0 ? -errno : false; + + if (path_startswith_strv(me->mnt_dir, prefixes)) + return true; + } +} + int fstab_is_mount_point_full(const char *where, const char *path) { _cleanup_endmntent_ FILE *f = NULL; int r; @@ -136,8 +164,6 @@ int fstab_is_mount_point_full(const char *where, const char *path) { if (r > 0 || (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r))) return r; } - - return false; } int fstab_filter_options( @@ -148,16 +174,16 @@ int fstab_filter_options( char ***ret_values, char **ret_filtered) { - const char *namefound = NULL, *x; - _cleanup_strv_free_ char **stor = NULL, **values = NULL; - _cleanup_free_ char *value = NULL, **filtered = NULL; + _cleanup_strv_free_ char **values = NULL; + _cleanup_free_ char *value = NULL, *filtered = NULL; + const char *namefound = NULL; int r; - assert(names && *names); + assert(!isempty(names)); assert(!(ret_value && ret_values)); if (!opts) - goto answer; + goto finish; /* Finds any options matching 'names', and returns: * - the last matching option name in ret_namefound, @@ -169,50 +195,49 @@ int fstab_filter_options( * * Returns negative on error, true if any matching options were found, false otherwise. */ - if (ret_filtered || ret_value || ret_values) { + if (ret_value || ret_values || ret_filtered) { + _cleanup_strv_free_ char **opts_split = NULL; + _cleanup_free_ char **filtered_strv = NULL; /* strings are owned by 'opts_split' */ + /* For backwards compatibility, we need to pass-through escape characters. * The only ones we "consume" are the ones used as "\," or "\\". */ - r = strv_split_full(&stor, opts, ",", EXTRACT_UNESCAPE_SEPARATORS | EXTRACT_UNESCAPE_RELAX); + r = strv_split_full(&opts_split, opts, ",", EXTRACT_UNESCAPE_SEPARATORS|EXTRACT_UNESCAPE_RELAX); if (r < 0) return r; - filtered = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); - if (!filtered) - return -ENOMEM; + STRV_FOREACH(opt, opts_split) { + bool found = false; + const char *x; - char **t = filtered; - for (char **s = t; *s; s++) { NULSTR_FOREACH(name, names) { - x = startswith(*s, name); + x = startswith(*opt, name); if (!x) continue; - /* Match name, but when ret_values, only when followed by assignment. */ + + /* If ret_values, only accept settings followed by assignment. */ if (*x == '=' || (!ret_values && *x == '\0')) { - /* Keep the last occurrence found */ namefound = name; - goto found; + found = true; + break; } } - *t = *s; - t++; - continue; - found: - if (ret_value || ret_values) { - assert(IN_SET(*x, '=', '\0')); - - if (ret_value) { + if (found) { + if (ret_value) r = free_and_strdup(&value, *x == '=' ? x + 1 : NULL); - if (r < 0) - return r; - } else if (*x) { + else if (ret_values) r = strv_extend(&values, x + 1); - if (r < 0) - return r; - } - } + else + r = 0; + } else + r = strv_push(&filtered_strv, *opt); + if (r < 0) + return r; } - *t = NULL; + + filtered = strv_join_full(filtered_strv, ",", NULL, /* escape_separator = */ true); + if (!filtered) + return -ENOMEM; } else for (const char *word = opts;;) { const char *end = word; @@ -226,64 +251,55 @@ int fstab_filter_options( if (IN_SET(*end, ',', '\0')) break; assert(*end == '\\'); - end ++; /* Skip the backslash */ + end++; /* Skip the backslash */ if (*end != '\0') - end ++; /* Skip the escaped char, but watch out for a trailing comma */ + end++; /* Skip the escaped char, but watch out for a trailing comma */ } NULSTR_FOREACH(name, names) { - if (end < word + strlen(name)) - continue; - if (!strneq(word, name, strlen(name))) + const char *x = startswith(word, name); + if (!x || x > end) continue; /* We know that the string is NUL terminated, so *x is valid */ - x = word + strlen(name); if (IN_SET(*x, '\0', '=', ',')) { namefound = name; break; } } - if (*end) - word = end + 1; - else + if (*end == '\0') break; + + word = end + 1; } -answer: +finish: if (ret_namefound) - *ret_namefound = namefound; - if (ret_filtered) { - char *f; - - f = strv_join_full(filtered, ",", NULL, true); - if (!f) - return -ENOMEM; - - *ret_filtered = f; - } + *ret_namefound = namefound; /* owned by 'names' (passed-in) */ if (ret_value) *ret_value = TAKE_PTR(value); if (ret_values) *ret_values = TAKE_PTR(values); + if (ret_filtered) + *ret_filtered = TAKE_PTR(filtered); return !!namefound; } -int fstab_find_pri(const char *options, int *ret) { - _cleanup_free_ char *opt = NULL; +int fstab_find_pri(const char *opts, int *ret) { + _cleanup_free_ char *v = NULL; int r, pri; assert(ret); - r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL, NULL); + r = fstab_filter_options(opts, "pri\0", NULL, &v, NULL, NULL); if (r < 0) return r; - if (r == 0 || !opt) + if (r == 0 || !v) return 0; - r = safe_atoi(opt, &pri); + r = safe_atoi(v, &pri); if (r < 0) return r; diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h index 9cf34f0..040f076 100644 --- a/src/shared/fstab-util.h +++ b/src/shared/fstab-util.h @@ -25,6 +25,8 @@ static inline int fstab_has_node(const char *path) { return fstab_is_mount_point_full(NULL, path); } +int fstab_has_mount_point_prefix_strv(char **prefixes); + int fstab_filter_options( const char *opts, const char *names, @@ -32,23 +34,20 @@ int fstab_filter_options( char **ret_value, char ***ret_values, char **ret_filtered); - static inline bool fstab_test_option(const char *opts, const char *names) { - return !!fstab_filter_options(opts, names, NULL, NULL, NULL, NULL); + return fstab_filter_options(opts, names, NULL, NULL, NULL, NULL); } - -int fstab_find_pri(const char *options, int *ret); - static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { - const char *opt; + const char *opt_found; /* If first name given is last, return 1. * If second name given is last or neither is found, return 0. */ - assert_se(fstab_filter_options(opts, yes_no, &opt, NULL, NULL, NULL) >= 0); + assert_se(fstab_filter_options(opts, yes_no, &opt_found, NULL, NULL, NULL) >= 0); - return opt == yes_no; + return opt_found == yes_no; } +int fstab_find_pri(const char *opts, int *ret); char *fstab_node_to_udev_node(const char *p); diff --git a/src/shared/generator.c b/src/shared/generator.c index 5626587..1b3304a 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -15,6 +15,7 @@ #include "log.h" #include "macro.h" #include "mkdir-label.h" +#include "mountpoint-util.h" #include "path-util.h" #include "process-util.h" #include "special.h" @@ -29,6 +30,7 @@ int generator_open_unit_file_full( const char *source, const char *fn, FILE **ret_file, + char **ret_final_path, char **ret_temp_path) { _cleanup_free_ char *p = NULL; @@ -72,10 +74,13 @@ int generator_open_unit_file_full( program_invocation_short_name); *ret_file = f; + + if (ret_final_path) + *ret_final_path = TAKE_PTR(p); + return 0; } - int generator_add_symlink_full( const char *dir, const char *dst, @@ -88,11 +93,13 @@ int generator_add_symlink_full( assert(dir); assert(dst); - assert(dep_type); assert(src); - /* Adds a symlink from ./ to (if src is absolute) or ../ (otherwise). If - * is specified, then must be a template unit name, and we'll instantiate it. */ + /* If 'dep_type' is specified adds a symlink from ./ to (if src is absolute) or ../ (otherwise). + * + * If 'dep_type' is NULL, it will create a symlink to (i.e. create an alias. + * + * If is specified, then must be a template unit name, and we'll instantiate it. */ r = path_extract_directory(src, &dn); if (r < 0 && r != -EDESTADDRREQ) /* EDESTADDRREQ → just a file name was passed */ @@ -110,11 +117,19 @@ int generator_add_symlink_full( return log_error_errno(r, "Failed to instantiate '%s' for '%s': %m", fn, instance); } - from = path_join(dn ?: "..", fn); - if (!from) - return log_oom(); + if (dep_type) { /* Create a .wants/ style dep */ + from = path_join(dn ?: "..", fn); + if (!from) + return log_oom(); + + to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn); + } else { /* or create an alias */ + from = dn ? path_join(dn, fn) : strdup(fn); + if (!from) + return log_oom(); - to = strjoin(dir, "/", dst, ".", dep_type, "/", instantiated ?: fn); + to = strjoin(dir, "/", dst); + } if (!to) return log_oom(); @@ -694,6 +709,77 @@ int generator_hook_up_pcrfs( return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance); } +int generator_hook_up_quotacheck( + const char *dir, + const char *what, + const char *where, + const char *target, + const char *fstype) { + + _cleanup_free_ char *where_unit = NULL, *instance = NULL; + int r; + + assert(dir); + assert(where); + + if (isempty(fstype) || streq(fstype, "auto")) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Couldn't determine filesystem type for %s, quota cannot be activated", what); + if (!fstype_needs_quota(fstype)) + return log_warning_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Quota was requested for %s, but not supported, ignoring: %s", what, fstype); + + /* quotacheck unit for system root */ + if (path_equal(where, "/")) + return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_ROOT_SERVICE); + + r = unit_name_path_escape(where, &instance); + if (r < 0) + return log_error_errno(r, "Failed to escape path '%s': %m", where); + + if (target) { + r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTACHECK_SERVICE, instance); + if (r < 0) + return r; + } + + r = unit_name_from_path(where, ".mount", &where_unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); + + return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTACHECK_SERVICE, instance); +} + +int generator_hook_up_quotaon( + const char *dir, + const char *where, + const char *target) { + + _cleanup_free_ char *where_unit = NULL, *instance = NULL; + int r; + + assert(dir); + assert(where); + + /* quotaon unit for system root is not instantiated */ + if (path_equal(where, "/")) + return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_ROOT_SERVICE); + + r = unit_name_path_escape(where, &instance); + if (r < 0) + return log_error_errno(r, "Failed to escape path '%s': %m", where); + + if (target) { + r = generator_add_ordering(dir, target, "After", SPECIAL_QUOTAON_SERVICE, instance); + if (r < 0) + return r; + } + + r = unit_name_from_path(where, ".mount", &where_unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); + + return generator_add_symlink_full(dir, where_unit, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_QUOTAON_SERVICE, instance); +} + int generator_enable_remount_fs_service(const char *dir) { /* Pull in systemd-remount-fs.service */ return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", @@ -790,6 +876,7 @@ int generator_write_cryptsetup_service_section( "TimeoutSec=infinity\n" /* The binary handles timeouts on its own */ "KeyringMode=shared\n" /* Make sure we can share cached keys among instances */ "OOMScoreAdjust=500\n" /* Unlocking can allocate a lot of memory if Argon2 is used */ + "ImportCredential=cryptsetup.*\n" "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", name_escaped, what_escaped, strempty(key_file_escaped), strempty(options_escaped), @@ -883,6 +970,5 @@ void log_setup_generator(void) { log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); } - log_parse_environment(); - (void) log_open(); + log_setup(); } diff --git a/src/shared/generator.h b/src/shared/generator.h index d97d6ed..baf1daf 100644 --- a/src/shared/generator.h +++ b/src/shared/generator.h @@ -6,10 +6,10 @@ #include "macro.h" #include "main-func.h" -int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_temp_path); +int generator_open_unit_file_full(const char *dest, const char *source, const char *name, FILE **ret_file, char **ret_final_path, char **ret_temp_path); static inline int generator_open_unit_file(const char *dest, const char *source, const char *name, FILE **ret_file) { - return generator_open_unit_file_full(dest, source, name, ret_file, NULL); + return generator_open_unit_file_full(dest, source, name, ret_file, NULL, NULL); } int generator_add_symlink_full(const char *dir, const char *dst, const char *dep_type, const char *src, const char *instance); @@ -85,6 +85,16 @@ int generator_hook_up_pcrfs( const char *dir, const char *where, const char *target); +int generator_hook_up_quotacheck( + const char *dir, + const char *what, + const char *where, + const char *target, + const char *fstype); +int generator_hook_up_quotaon( + const char *dir, + const char *where, + const char *target); int generator_enable_remount_fs_service(const char *dir); @@ -102,4 +112,5 @@ void log_setup_generator(void); impl(argv[1], \ argv[argc == 4 ? 2 : 1], \ argv[argc == 4 ? 3 : 1]), \ - r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) + exit_failure_if_negative, \ + exit_failure_if_negative) diff --git a/src/shared/group-record.c b/src/shared/group-record.c index 1e33bdf..6c1e41a 100644 --- a/src/shared/group-record.c +++ b/src/shared/group-record.c @@ -2,7 +2,7 @@ #include "group-record.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" GroupRecord* group_record_new(void) { @@ -102,33 +102,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); JSON_VARIANT_ARRAY_FOREACH(e, variant) { - bool matching = false; - JsonVariant *m; - if (!json_variant_is_object(e)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name)); - m = json_variant_by_key(e, "matchMachineId"); - if (m) { - r = per_machine_id_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - - if (!matching) { - m = json_variant_by_key(e, "matchHostname"); - if (m) { - r = per_machine_hostname_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - } - - if (!matching) + r = per_machine_match(e, flags); + if (r < 0) + return r; + if (r == 0) continue; r = json_dispatch(e, per_machine_dispatch_table, flags, userdata); @@ -230,7 +210,7 @@ int group_record_load( if (r < 0) return r; - r = json_dispatch(h->json, group_dispatch_table, json_flags, h); + r = json_dispatch(h->json, group_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h); if (r < 0) return r; diff --git a/src/shared/hibernate-util.c b/src/shared/hibernate-util.c index c3991cf..7c21157 100644 --- a/src/shared/hibernate-util.c +++ b/src/shared/hibernate-util.c @@ -159,8 +159,9 @@ static int read_resume_config(dev_t *ret_devno, uint64_t *ret_offset) { } if (devno == 0 && offset > 0 && offset != UINT64_MAX) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), - "Found resume_offset=%" PRIu64 " but resume= is unset, refusing.", offset); + return log_debug_errno(SYNTHETIC_ERRNO(ENOMEDIUM), + "Found populated /sys/power/resume_offset (%" PRIu64 ") but /sys/power/resume is not set, refusing.", + offset); *ret_devno = devno; *ret_offset = offset; @@ -393,7 +394,7 @@ int find_suitable_hibernation_device_full(HibernationDevice *ret_device, uint64_ if (!entry) { /* No need to check n_swaps == 0, since it's rejected early */ assert(resume_config_devno > 0); - return log_debug_errno(SYNTHETIC_ERRNO(ENOSPC), "Cannot find swap entry corresponding to /sys/power/resume."); + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Cannot find swap entry corresponding to /sys/power/resume."); } if (ret_device) { @@ -451,11 +452,11 @@ int hibernation_is_safe(void) { bypass_space_check = getenv_bool("SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK") > 0; r = find_suitable_hibernation_device_full(NULL, &size, &used); - if (r == -ENOSPC && bypass_space_check) - /* If we don't have any available swap space at all, and SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK - * is set, skip all remaining checks since we can't do that properly anyway. It is quite - * possible that the user is using a setup similar to #30083. When we actually perform - * hibernation in sleep.c we'll check everything again. */ + if (IN_SET(r, -ENOSPC, -ESTALE) && bypass_space_check) + /* If we don't have any available swap space at all, or the specified resume device is missing, + * and $SYSTEMD_BYPASS_HIBERNATION_MEMORY_CHECK is set, skip all remaining checks since + * we can't do that properly anyway. It is quite possible that the user is using a setup + * similar to #30083. When we actually perform hibernation in sleep.c we'll check everything again. */ return 0; if (r < 0) return r; @@ -466,7 +467,7 @@ int hibernation_is_safe(void) { "Not running on EFI and resume= is not set. Hibernation is not safe."); if (bypass_space_check) - return true; + return 0; r = get_proc_meminfo_active(&active); if (r < 0) @@ -483,30 +484,23 @@ int hibernation_is_safe(void) { int write_resume_config(dev_t devno, uint64_t offset, const char *device) { char offset_str[DECIMAL_STR_MAX(uint64_t)]; - _cleanup_free_ char *path = NULL; const char *devno_str; int r; + assert(devno > 0); + assert(device); + devno_str = FORMAT_DEVNUM(devno); xsprintf(offset_str, "%" PRIu64, offset); - if (!device) { - r = device_path_make_canonical(S_IFBLK, devno, &path); - if (r < 0) - return log_error_errno(r, - "Failed to format canonical device path for devno '" DEVNUM_FORMAT_STR "': %m", - DEVNUM_FORMAT_VAL(devno)); - device = path; - } - /* We write the offset first since it's safer. Note that this file is only available in 4.17+, so * fail gracefully if it doesn't exist and we're only overwriting it with 0. */ r = write_string_file("/sys/power/resume_offset", offset_str, WRITE_STRING_FILE_DISABLE_BUFFER); if (r == -ENOENT) { if (offset != 0) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), - "Can't configure hibernation offset %" PRIu64 ", kernel does not support /sys/power/resume_offset. Refusing.", - offset); + "Can't configure swap file offset %s, kernel does not support /sys/power/resume_offset. Refusing.", + offset_str); log_warning_errno(r, "/sys/power/resume_offset is unavailable, skipping writing swap file offset."); } else if (r < 0) @@ -526,3 +520,18 @@ int write_resume_config(dev_t devno, uint64_t offset, const char *device) { return 0; } + +int clear_efi_hibernate_location_and_warn(void) { + int r; + + if (!is_efi_boot()) + return 0; + + r = efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_warning_errno(r, "Failed to clear HibernateLocation EFI variable: %m"); + + return 1; +} diff --git a/src/shared/hibernate-util.h b/src/shared/hibernate-util.h index 2ae10fb..394d0b4 100644 --- a/src/shared/hibernate-util.h +++ b/src/shared/hibernate-util.h @@ -22,5 +22,7 @@ int hibernation_is_safe(void); int write_resume_config(dev_t devno, uint64_t offset, const char *device); +int clear_efi_hibernate_location_and_warn(void); + /* Only for test-fiemap */ int read_fiemap(int fd, struct fiemap **ret); diff --git a/src/shared/hostname-setup.c b/src/shared/hostname-setup.c index 137c29a..6cfd4b5 100644 --- a/src/shared/hostname-setup.c +++ b/src/shared/hostname-setup.c @@ -6,12 +6,16 @@ #include #include +#include "sd-daemon.h" + #include "alloc-util.h" +#include "creds-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" #include "hostname-setup.h" #include "hostname-util.h" +#include "initrd-util.h" #include "log.h" #include "macro.h" #include "proc-cmdline.h" @@ -23,7 +27,8 @@ static int sethostname_idempotent_full(const char *s, bool really) { assert(s); - assert_se(uname(&u) >= 0); + if (uname(&u) < 0) + return -errno; if (streq_ptr(s, u.nodename)) return 0; @@ -40,37 +45,56 @@ int sethostname_idempotent(const char *s) { } int shorten_overlong(const char *s, char **ret) { - char *h, *p; + _cleanup_free_ char *h = NULL; /* Shorten an overlong name to HOST_NAME_MAX or to the first dot, * whatever comes earlier. */ assert(s); + assert(ret); h = strdup(s); if (!h) return -ENOMEM; if (hostname_is_valid(h, 0)) { - *ret = h; + *ret = TAKE_PTR(h); return 0; } - p = strchr(h, '.'); + char *p = strchr(h, '.'); if (p) *p = 0; strshorten(h, HOST_NAME_MAX); - if (!hostname_is_valid(h, 0)) { - free(h); + if (!hostname_is_valid(h, /* flags= */ 0)) return -EDOM; - } - *ret = h; + *ret = TAKE_PTR(h); return 1; } +static int acquire_hostname_from_credential(char **ret) { + _cleanup_free_ char *cred = NULL; + int r; + + assert(ret); + + r = read_credential_with_decryption("system.hostname", (void **) &cred, /* ret_size= */ NULL); + if (r < 0) + return log_warning_errno(r, "Failed to read system.hostname credential, ignoring: %m"); + if (r == 0) /* not found */ + return -ENXIO; + + if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */ + return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred); + + log_info("Initializing hostname from credential."); + *ret = TAKE_PTR(cred); + return 0; +} + int read_etc_hostname_stream(FILE *f, char **ret) { int r; @@ -126,66 +150,64 @@ void hostname_update_source_hint(const char *hostname, HostnameSource source) { r = write_string_file("/run/systemd/default-hostname", hostname, WRITE_STRING_FILE_CREATE | WRITE_STRING_FILE_ATOMIC); if (r < 0) - log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\": %m"); + log_warning_errno(r, "Failed to create \"/run/systemd/default-hostname\", ignoring: %m"); } else unlink_or_warn("/run/systemd/default-hostname"); } int hostname_setup(bool really) { - _cleanup_free_ char *b = NULL; - const char *hn = NULL; + _cleanup_free_ char *hn = NULL; HostnameSource source; bool enoent = false; int r; - r = proc_cmdline_get_key("systemd.hostname", 0, &b); + r = proc_cmdline_get_key("systemd.hostname", 0, &hn); if (r < 0) log_warning_errno(r, "Failed to retrieve system hostname from kernel command line, ignoring: %m"); else if (r > 0) { - if (hostname_is_valid(b, VALID_HOSTNAME_TRAILING_DOT)) { - hn = b; + if (hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT)) source = HOSTNAME_TRANSIENT; - } else { - log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", b); - b = mfree(b); + else { + log_warning("Hostname specified on kernel command line is invalid, ignoring: %s", hn); + hn = mfree(hn); } } if (!hn) { - r = read_etc_hostname(NULL, &b); - if (r < 0) { - if (r == -ENOENT) - enoent = true; - else - log_warning_errno(r, "Failed to read configured hostname: %m"); - } else { - hn = b; + r = read_etc_hostname(NULL, &hn); + if (r == -ENOENT) + enoent = true; + else if (r < 0) + log_warning_errno(r, "Failed to read configured hostname, ignoring: %m"); + else source = HOSTNAME_STATIC; - } } if (!hn) { - _cleanup_free_ char *buf = NULL; + r = acquire_hostname_from_credential(&hn); + if (r >= 0) + source = HOSTNAME_TRANSIENT; + } + if (!hn) { /* Don't override the hostname if it is already set and not explicitly configured */ - r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &buf); + r = gethostname_full(GET_HOSTNAME_ALLOW_LOCALHOST, &hn); if (r == -ENOMEM) return log_oom(); if (r >= 0) { - log_debug("No hostname configured, leaving existing hostname <%s> in place.", buf); - return 0; + log_debug("No hostname configured, leaving existing hostname <%s> in place.", hn); + goto finish; } if (enoent) log_info("No hostname configured, using default hostname."); - hn = b = get_default_hostname(); + hn = get_default_hostname(); if (!hn) return log_oom(); source = HOSTNAME_DEFAULT; - } r = sethostname_idempotent_full(hn, really); @@ -201,7 +223,11 @@ int hostname_setup(bool really) { if (really) hostname_update_source_hint(hn, source); - return r; +finish: + if (!in_initrd()) + (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_HOSTNAME=%s", hn); + + return 0; } static const char* const hostname_source_table[] = { diff --git a/src/shared/hwdb-util.c b/src/shared/hwdb-util.c index f67e917..d96902c 100644 --- a/src/shared/hwdb-util.c +++ b/src/shared/hwdb-util.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "conf-files.h" +#include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -483,7 +484,7 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr if (r == 0) break; - line_number ++; + line_number++; /* comment line */ if (line[0] == '#') @@ -710,3 +711,16 @@ bool hwdb_should_reload(sd_hwdb *hwdb) { return true; return false; } + +int hwdb_bypass(void) { + int r; + + r = getenv_bool("SYSTEMD_HWDB_UPDATE_BYPASS"); + if (r < 0 && r != -ENXIO) + log_debug_errno(r, "Failed to parse $SYSTEMD_HWDB_UPDATE_BYPASS, assuming no."); + if (r <= 0) + return false; + + log_debug("$SYSTEMD_HWDB_UPDATE_BYPASS is enabled, skipping execution."); + return true; +} diff --git a/src/shared/hwdb-util.h b/src/shared/hwdb-util.h index cb93690..00610b1 100644 --- a/src/shared/hwdb-util.h +++ b/src/shared/hwdb-util.h @@ -8,3 +8,4 @@ bool hwdb_should_reload(sd_hwdb *hwdb); int hwdb_update(const char *root, const char *hwdb_bin_dir, bool strict, bool compat); int hwdb_query(const char *modalias, const char *root); +int hwdb_bypass(void); diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c index 26a9d60..aa88e11 100644 --- a/src/shared/idn-util.c +++ b/src/shared/idn-util.c @@ -16,11 +16,16 @@ static void* idn_dl = NULL; #endif #if HAVE_LIBIDN2 -int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags) = NULL; +DLSYM_FUNCTION(idn2_lookup_u8); const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; -int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags) = NULL; +DLSYM_FUNCTION(idn2_to_unicode_8z8z); int dlopen_idn(void) { + ELF_NOTE_DLOPEN("idn", + "Support for internationalized domain names", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libidn2.so.0"); + return dlopen_many_sym_or_warn( &idn_dl, "libidn2.so.0", LOG_DEBUG, DLSYM_ARG(idn2_lookup_u8), @@ -30,15 +35,20 @@ int dlopen_idn(void) { #endif #if HAVE_LIBIDN -int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); -int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen, uint32_t * out, size_t * outlen, int flags); -char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); -uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); +DLSYM_FUNCTION(idna_to_ascii_4i); +DLSYM_FUNCTION(idna_to_unicode_44i); +DLSYM_FUNCTION(stringprep_ucs4_to_utf8); +DLSYM_FUNCTION(stringprep_utf8_to_ucs4); int dlopen_idn(void) { _cleanup_(dlclosep) void *dl = NULL; int r; + ELF_NOTE_DLOPEN("idn", + "Support for internationalized domain names", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libidn.so.12", "libidn.so.11"); + if (idn_dl) return 0; /* Already loaded */ diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h index e64bd99..3ae2f13 100644 --- a/src/shared/idn-util.h +++ b/src/shared/idn-util.h @@ -11,6 +11,8 @@ #include #if HAVE_LIBIDN2 || HAVE_LIBIDN +#include "dlfcn-util.h" + int dlopen_idn(void); #else static inline int dlopen_idn(void) { @@ -19,14 +21,14 @@ static inline int dlopen_idn(void) { #endif #if HAVE_LIBIDN2 -extern int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags); +DLSYM_PROTOTYPE(idn2_lookup_u8); extern const char *(*sym_idn2_strerror)(int rc) _const_; -extern int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags); +DLSYM_PROTOTYPE(idn2_to_unicode_8z8z); #endif #if HAVE_LIBIDN -extern int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); -extern int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags); -extern char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); -extern uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); +DLSYM_PROTOTYPE(idna_to_ascii_4i); +DLSYM_PROTOTYPE(idna_to_unicode_44i); +DLSYM_PROTOTYPE(stringprep_ucs4_to_utf8); +DLSYM_PROTOTYPE(stringprep_utf8_to_ucs4); #endif diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 3c3de50..e7bd84e 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -50,6 +50,20 @@ PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags) { return flags; } +PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags) { + /* The reverse of partition_policy_flags_extend(): if some parts of the flags field allow all + * possible options, let's remove it from the flags to make them shorter */ + + if (FLAGS_SET(flags, _PARTITION_POLICY_USE_MASK)) + flags &= ~_PARTITION_POLICY_USE_MASK; + if (FLAGS_SET(flags, _PARTITION_POLICY_READ_ONLY_MASK)) + flags &= ~_PARTITION_POLICY_READ_ONLY_MASK; + if (FLAGS_SET(flags, _PARTITION_POLICY_GROWFS_MASK)) + flags &= ~_PARTITION_POLICY_GROWFS_MASK; + + return flags; +} + static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPolicy *policy) { PartitionPolicyFlags flags = ASSERT_PTR(policy)->flags; @@ -676,6 +690,90 @@ int parse_image_policy_argument(const char *s, ImagePolicy **policy) { return free_and_replace_full(*policy, np, image_policy_free); } +static bool partition_policy_flags_has_unspecified(PartitionPolicyFlags flags) { + + if ((flags & _PARTITION_POLICY_USE_MASK) == 0) + return true; + if ((flags & _PARTITION_POLICY_READ_ONLY_MASK) == 0) + return true; + if ((flags & _PARTITION_POLICY_GROWFS_MASK) == 0) + return true; + + return false; +} + +int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret) { + _cleanup_(image_policy_freep) ImagePolicy *p = NULL; + + /* Calculates the intersection of the specified policies, i.e. only what is permitted in both. This + * might fail with -ENAVAIL if the intersection is an "impossible policy". For example, if a root + * partition my neither be used, nor be absent, nor be unused then this is considered + * "impossible". */ + + p = image_policy_new(_PARTITION_DESIGNATOR_MAX); + if (!p) + return -ENOMEM; + + p->default_flags = + partition_policy_flags_extend(image_policy_default(a)) & + partition_policy_flags_extend(image_policy_default(b)); + + if (partition_policy_flags_has_unspecified(p->default_flags)) /* Intersection empty? */ + return -ENAVAIL; + + p->default_flags = partition_policy_flags_reduce(p->default_flags); + + for (PartitionDesignator d = 0; d < _PARTITION_DESIGNATOR_MAX; d++) { + PartitionPolicyFlags x, y, z, df; + + /* If this designator has no entry in either policy we don't need to include it in the intersection either. */ + if (!image_policy_bsearch(a, d) && !image_policy_bsearch(b, d)) + continue; + + /* Expand this policy flags field to the "long" form, i.e. for each part of the flags that + * are left unspcified add in all possible options */ + x = image_policy_get_exhaustively(a, d); + if (x < 0) + return x; + + y = image_policy_get_exhaustively(b, d); + if (y < 0) + return y; + + /* Mask it */ + z = x & y; + + /* Check if the intersection is empty for this partition. If so, generate a clear error */ + if (partition_policy_flags_has_unspecified(z)) + return -ENAVAIL; + + df = partition_policy_normalized_flags( + &(const PartitionPolicy) { + .flags = image_policy_default(p), + .designator = d, + }); + if (df < 0) + return df; + if (df == z) /* Same as default? then let's skip this */ + continue; + + /* image_policy_get_exhaustively() may have extended the flags mask to include all + * read-only/growfs flags if not set. Let's remove them again, if they are both set to + * minimize the policy again. */ + z = partition_policy_flags_reduce(z); + + p->policies[p->n_policies++] = (struct PartitionPolicy) { + .designator = d, + .flags = z, + }; + } + + if (ret) + *ret = TAKE_PTR(p); + + return 0; +} + const ImagePolicy image_policy_allow = { /* Allow policy */ .n_policies = 0, @@ -726,6 +824,14 @@ const ImagePolicy image_policy_confext = { .default_flags = PARTITION_POLICY_IGNORE, }; +const ImagePolicy image_policy_confext_strict = { + .n_policies = 1, + .policies = { + { PARTITION_ROOT, PARTITION_POLICY_SIGNED|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, diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index f59c16e..a1a6afa 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -58,9 +58,10 @@ struct ImagePolicy { 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_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_confext_strict; /* Signed verity required */ extern const ImagePolicy image_policy_container; extern const ImagePolicy image_policy_service; extern const ImagePolicy image_policy_host; @@ -79,6 +80,7 @@ static inline size_t image_policy_n_entries(const ImagePolicy *policy) { } PartitionPolicyFlags partition_policy_flags_extend(PartitionPolicyFlags flags); +PartitionPolicyFlags partition_policy_flags_reduce(PartitionPolicyFlags flags); PartitionPolicyFlags partition_policy_flags_from_string(const char *s); int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, char **ret); @@ -94,6 +96,8 @@ 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. */ +int image_policy_intersect(const ImagePolicy *a, const ImagePolicy *b, ImagePolicy **ret); + static inline ImagePolicy* image_policy_free(ImagePolicy *p) { return mfree(p); } diff --git a/src/shared/in-addr-prefix-util.c b/src/shared/in-addr-prefix-util.c index 7c0033d..edccca5 100644 --- a/src/shared/in-addr-prefix-util.c +++ b/src/shared/in-addr-prefix-util.c @@ -59,9 +59,9 @@ static void in_addr_prefix_hash_func(const struct in_addr_prefix *a, struct siph assert(a); assert(state); - siphash24_compress(&a->family, sizeof(a->family), state); - siphash24_compress(&a->prefixlen, sizeof(a->prefixlen), state); - siphash24_compress(&a->address, FAMILY_ADDRESS_SIZE(a->family), state); + siphash24_compress_typesafe(a->family, state); + siphash24_compress_typesafe(a->prefixlen, state); + in_addr_hash_func(&a->address, a->family, state); } static int in_addr_prefix_compare_func(const struct in_addr_prefix *x, const struct in_addr_prefix *y) { diff --git a/src/shared/initreq.h b/src/shared/initreq.h index da9783c..c6a1050 100644 --- a/src/shared/initreq.h +++ b/src/shared/initreq.h @@ -1,14 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.0-or-later */ /* - * initreq.h Interface to talk to init through /dev/initctl. - * - * Copyright (C) 1995-2004 Miquel van Smoorenburg - * - * This library 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 of the License, or (at your option) any later version. - * + * Copyright (C) 1995-2004 Miquel van Smoorenburg * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS */ diff --git a/src/shared/install-file.c b/src/shared/install-file.c index 3b4d651..ea2189f 100644 --- a/src/shared/install-file.c +++ b/src/shared/install-file.c @@ -12,7 +12,7 @@ #include "rm-rf.h" #include "sync-util.h" -int fs_make_very_read_only(int fd) { +static int fs_make_very_read_only(int fd) { struct stat st; int r; diff --git a/src/shared/install-file.h b/src/shared/install-file.h index c37254f..af07ab2 100644 --- a/src/shared/install-file.h +++ b/src/shared/install-file.h @@ -1,8 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once -int fs_make_very_read_only(int fd); - typedef enum InstallFileFlags { INSTALL_REPLACE = 1 << 0, /* Replace an existing inode */ INSTALL_READ_ONLY = 1 << 1, /* Call fs_make_very_read_only() to make the inode comprehensively read-only */ diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c index 3cc7093..bdb44b3 100644 --- a/src/shared/install-printf.c +++ b/src/shared/install-printf.c @@ -37,16 +37,11 @@ static int specifier_prefix_and_instance(char specifier, const void *data, const static int specifier_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const InstallInfo *i = ASSERT_PTR(userdata); - char *ans; if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE) && i->default_instance) return unit_name_replace_instance(i->name, i->default_instance, ret); - ans = strdup(i->name); - if (!ans) - return -ENOMEM; - *ret = ans; - return 0; + return strdup_to(ret, i->name); } static int specifier_prefix(char specifier, const void *data, const char *root, const void *userdata, char **ret) { @@ -86,14 +81,10 @@ static int specifier_last_component(char specifier, const void *data, const char return r; dash = strrchr(prefix, '-'); - if (dash) { - dash = strdup(dash + 1); - if (!dash) - return -ENOMEM; - *ret = dash; - } else - *ret = TAKE_PTR(prefix); + if (dash) + return strdup_to(ret, dash + 1); + *ret = TAKE_PTR(prefix); return 0; } diff --git a/src/shared/install.c b/src/shared/install.c index 27a421e..dd2bd5c 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -10,6 +10,7 @@ #include #include "alloc-util.h" +#include "bus-common-errors.h" #include "chase.h" #include "conf-files.h" #include "conf-parser.h" @@ -161,9 +162,10 @@ static int path_is_generator(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->generator) || - path_equal_ptr(parent, lp->generator_early) || - path_equal_ptr(parent, lp->generator_late); + return PATH_IN_SET(parent, + lp->generator, + lp->generator_early, + lp->generator_late); } static int path_is_transient(const LookupPaths *lp, const char *path) { @@ -177,7 +179,7 @@ static int path_is_transient(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->transient); + return path_equal(parent, lp->transient); } static int path_is_control(const LookupPaths *lp, const char *path) { @@ -191,8 +193,9 @@ static int path_is_control(const LookupPaths *lp, const char *path) { if (r < 0) return r; - return path_equal_ptr(parent, lp->persistent_control) || - path_equal_ptr(parent, lp->runtime_control); + return PATH_IN_SET(parent, + lp->persistent_control, + lp->runtime_control); } static int path_is_config(const LookupPaths *lp, const char *path, bool check_parent) { @@ -213,8 +216,9 @@ static int path_is_config(const LookupPaths *lp, const char *path, bool check_pa path = parent; } - return path_equal_ptr(path, lp->persistent_config) || - path_equal_ptr(path, lp->runtime_config); + return PATH_IN_SET(path, + lp->persistent_config, + lp->runtime_config); } static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_parent) { @@ -240,12 +244,13 @@ static int path_is_runtime(const LookupPaths *lp, const char *path, bool check_p path = parent; } - return path_equal_ptr(path, lp->runtime_config) || - path_equal_ptr(path, lp->generator) || - path_equal_ptr(path, lp->generator_early) || - path_equal_ptr(path, lp->generator_late) || - path_equal_ptr(path, lp->transient) || - path_equal_ptr(path, lp->runtime_control); + return PATH_IN_SET(path, + lp->runtime_config, + lp->generator, + lp->generator_early, + lp->generator_late, + lp->transient, + lp->runtime_control); } static int path_is_vendor_or_generator(const LookupPaths *lp, const char *path) { @@ -324,130 +329,176 @@ InstallChangeType install_changes_add( void install_changes_free(InstallChange *changes, size_t n_changes) { assert(changes || n_changes == 0); - for (size_t i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); + FOREACH_ARRAY(i, changes, n_changes) { + free(i->path); + free(i->source); } free(changes); } -void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet) { - int err = 0; +static void install_change_dump_success(const InstallChange *change) { + assert(change); + assert(change->path); - assert(changes || n_changes == 0); - /* If verb is not specified, errors are not allowed! */ - assert(verb || r >= 0); + switch (change->type) { - for (size_t i = 0; i < n_changes; i++) { - assert(changes[i].path); - /* This tries to tell the compiler that it's safe to use 'verb' in a string format if there - * was an error, but the compiler doesn't care and fails anyway, so strna(verb) is used - * too. */ - assert(verb || changes[i].type >= 0); - verb = strna(verb); + case INSTALL_CHANGE_SYMLINK: + return log_info("Created symlink '%s' %s '%s'.", + change->path, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), change->source); - /* When making changes here, make sure to also change install_error() in dbus-manager.c. */ + case INSTALL_CHANGE_UNLINK: + return log_info("Removed '%s'.", change->path); - switch (changes[i].type) { - case INSTALL_CHANGE_SYMLINK: - if (!quiet) - log_info("Created symlink %s %s %s.", - changes[i].path, - special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), - changes[i].source); - break; - case INSTALL_CHANGE_UNLINK: - if (!quiet) - log_info("Removed \"%s\".", changes[i].path); - break; - case INSTALL_CHANGE_IS_MASKED: - if (!quiet) - log_info("Unit %s is masked, ignoring.", changes[i].path); - break; - case INSTALL_CHANGE_IS_MASKED_GENERATOR: - if (!quiet) - log_info("Unit %s is masked via a generator and cannot be unmasked.", - changes[i].path); - break; - case INSTALL_CHANGE_IS_DANGLING: - if (!quiet) - log_info("Unit %s is an alias to a unit that is not present, ignoring.", - changes[i].path); - break; - case INSTALL_CHANGE_DESTINATION_NOT_PRESENT: - if (!quiet) - log_warning("Unit %s is added as a dependency to a non-existent unit %s.", - changes[i].source, changes[i].path); - break; - case INSTALL_CHANGE_AUXILIARY_FAILED: + case INSTALL_CHANGE_IS_MASKED: + return log_info("Unit %s is masked, ignoring.", change->path); + + case INSTALL_CHANGE_IS_MASKED_GENERATOR: + return log_info("Unit %s is masked via a generator and cannot be unmasked, skipping.", change->path); + + case INSTALL_CHANGE_IS_DANGLING: + return log_info("Unit %s is an alias to a non-existent unit, ignoring.", change->path); + + case INSTALL_CHANGE_DESTINATION_NOT_PRESENT: + return log_warning("Unit %s is added as a dependency to a non-existent unit %s.", + change->source, change->path); + + case INSTALL_CHANGE_AUXILIARY_FAILED: + return log_warning("Failed to enable auxiliary unit %s, ignoring.", change->path); + + default: + assert_not_reached(); + } +} + +int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error) { + char *m; + const char *bus_error; + + /* Returns 0: known error and ret_errmsg formatted + * < 0: non-recognizable error */ + + assert(change); + assert(change->path); + assert(change->type < 0); + assert(ret_errmsg); + + switch (change->type) { + + case -EEXIST: + m = strjoin("File '", change->path, "' already exists", + change->source ? " and is a symlink to " : NULL, change->source); + bus_error = BUS_ERROR_UNIT_EXISTS; + break; + + case -ERFKILL: + m = strjoin("Unit ", change->path, " is masked"); + bus_error = BUS_ERROR_UNIT_MASKED; + break; + + case -EADDRNOTAVAIL: + m = strjoin("Unit ", change->path, " is transient or generated"); + bus_error = BUS_ERROR_UNIT_GENERATED; + break; + + case -ETXTBSY: + m = strjoin("File '", change->path, "' is under the systemd unit hierarchy already"); + bus_error = BUS_ERROR_UNIT_BAD_PATH; + break; + + case -EBADSLT: + m = strjoin("Invalid specifier in unit ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -EIDRM: + m = strjoin("Refusing to operate on template unit ", change->source, + " when destination unit ", change->path, " is a non-template unit"); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -EUCLEAN: + m = strjoin("Invalid unit name ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -ELOOP: + m = strjoin("Refusing to operate on linked unit file ", change->path); + bus_error = BUS_ERROR_UNIT_LINKED; + break; + + case -EXDEV: + if (change->source) + m = strjoin("Cannot alias ", change->source, " as ", change->path); + else + m = strjoin("Invalid unit reference ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + case -ENOENT: + m = strjoin("Unit ", change->path, " does not exist"); + bus_error = BUS_ERROR_NO_SUCH_UNIT; + break; + + case -ENOLINK: + m = strjoin("Unit ", change->path, " is an unresolvable alias"); + bus_error = BUS_ERROR_NO_SUCH_UNIT; + break; + + case -EUNATCH: + m = strjoin("Cannot resolve specifiers in unit ", change->path); + bus_error = BUS_ERROR_BAD_UNIT_SETTING; + break; + + default: + return change->type; + } + if (!m) + return -ENOMEM; + + *ret_errmsg = m; + if (ret_bus_error) + *ret_bus_error = bus_error; + + return 0; +} + +void install_changes_dump( + int error, + const char *verb, + const InstallChange *changes, + size_t n_changes, + bool quiet) { + + bool err_logged = false; + int r; + + /* If verb is not specified, errors are not allowed! */ + assert(verb || error >= 0); + assert(changes || n_changes == 0); + + FOREACH_ARRAY(i, changes, n_changes) + if (i->type >= 0) { if (!quiet) - log_warning("Failed to enable auxiliary unit %s, ignoring.", changes[i].path); - break; - case -EEXIST: - if (changes[i].source) - err = log_error_errno(changes[i].type, - "Failed to %s unit, file \"%s\" already exists and is a symlink to \"%s\".", - verb, changes[i].path, changes[i].source); - else - err = log_error_errno(changes[i].type, - "Failed to %s unit, file \"%s\" already exists.", - verb, changes[i].path); - break; - case -ERFKILL: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is masked.", - verb, changes[i].path); - break; - case -EADDRNOTAVAIL: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s is transient or generated.", - verb, changes[i].path); - break; - case -ETXTBSY: - err = log_error_errno(changes[i].type, "Failed to %s unit, file %s is under the systemd unit hierarchy already.", - verb, changes[i].path); - break; - case -EBADSLT: - err = log_error_errno(changes[i].type, "Failed to %s unit, invalid specifier in \"%s\".", - verb, changes[i].path); - break; - case -EIDRM: - err = log_error_errno(changes[i].type, "Failed to %s %s, destination unit %s is a non-template unit.", - verb, changes[i].source, changes[i].path); - break; - case -EUCLEAN: - err = log_error_errno(changes[i].type, - "Failed to %s unit, \"%s\" is not a valid unit name.", - verb, changes[i].path); - break; - case -ELOOP: - err = log_error_errno(changes[i].type, "Failed to %s unit, refusing to operate on linked unit file %s.", - verb, changes[i].path); - break; - case -EXDEV: - if (changes[i].source) - err = log_error_errno(changes[i].type, "Failed to %s unit, cannot alias %s as %s.", - verb, changes[i].source, changes[i].path); + install_change_dump_success(i); + } else { + _cleanup_free_ char *err_message = NULL; + + assert(verb); + + r = install_change_dump_error(i, &err_message, /* ret_bus_error = */ NULL); + if (r == -ENOMEM) + return (void) log_oom(); + if (r < 0) + log_error_errno(r, "Failed to %s unit %s: %m", verb, i->path); else - err = log_error_errno(changes[i].type, "Failed to %s unit, invalid unit reference \"%s\".", - verb, changes[i].path); - break; - case -ENOENT: - err = log_error_errno(changes[i].type, "Failed to %s unit, unit %s does not exist.", - verb, changes[i].path); - break; - case -EUNATCH: - err = log_error_errno(changes[i].type, "Failed to %s unit, cannot resolve specifiers in \"%s\".", - verb, changes[i].path); - break; - default: - assert(changes[i].type < 0); - err = log_error_errno(changes[i].type, "Failed to %s unit, file \"%s\": %m", - verb, changes[i].path); + log_error_errno(i->type, "Failed to %s unit: %s", verb, err_message); + + err_logged = true; } - } - if (r < 0 && err >= 0) - log_error_errno(r, "Failed to %s: %m.", verb); + if (error < 0 && !err_logged) + log_error_errno(error, "Failed to %s unit: %m.", verb); } /** @@ -750,7 +801,7 @@ static int remove_marked_symlinks( assert(config_path); assert(lp); - if (set_size(remove_symlinks_to) <= 0) + if (set_isempty(remove_symlinks_to)) return 0; fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC); @@ -996,7 +1047,7 @@ static int find_symlinks_in_scope( if (r > 0) { /* We found symlinks in this dir? Yay! Let's see where precisely it is enabled. */ - if (path_equal_ptr(*p, lp->persistent_config)) { + if (path_equal(*p, lp->persistent_config)) { /* This is the best outcome, let's return it immediately. */ *state = UNIT_FILE_ENABLED; return 1; @@ -1017,7 +1068,7 @@ static int find_symlinks_in_scope( enabled_at_all = true; } else if (same_name_link) { - if (path_equal_ptr(*p, lp->persistent_config)) + if (path_equal(*p, lp->persistent_config)) same_name_link_config = true; else { r = path_is_runtime(lp, *p, false); @@ -1813,7 +1864,7 @@ int unit_file_verify_alias( _cleanup_free_ char *dir = NULL; char *p; - path_alias ++; /* skip over slash */ + path_alias++; /* skip over slash */ r = path_extract_directory(dst, &dir); if (r < 0) @@ -1892,28 +1943,25 @@ static int install_info_symlink_alias( InstallChange **changes, size_t *n_changes) { - int r = 0, q; + int r, ret = 0; assert(info); assert(lp); assert(config_path); STRV_FOREACH(s, info->aliases) { - _cleanup_free_ char *alias_path = NULL, *dst = NULL, *dst_updated = NULL; - bool broken; + _cleanup_free_ char *alias_path = NULL, *alias_target = NULL, *dst = NULL, *dst_updated = NULL; - q = install_name_printf(scope, info, *s, &dst); - if (q < 0) { - install_changes_add(changes, n_changes, q, *s, NULL); - r = r < 0 ? r : q; + r = install_name_printf(scope, info, *s, &dst); + if (r < 0) { + RET_GATHER(ret, install_changes_add(changes, n_changes, r, *s, NULL)); continue; } - q = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes); - if (q == -ELOOP) - continue; - if (q < 0) { - r = r < 0 ? r : q; + r = unit_file_verify_alias(info, dst, &dst_updated, changes, n_changes); + if (r < 0) { + if (r != -ELOOP) + RET_GATHER(ret, r); continue; } @@ -1921,18 +1969,30 @@ static int install_info_symlink_alias( if (!alias_path) return -ENOMEM; - q = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, NULL, NULL); - if (q < 0 && q != -ENOENT) { - r = r < 0 ? r : q; + r = in_search_path(lp, info->path); + if (r < 0) + return r; + if (r == 0) { + /* The unit path itself is outside of the search path. To + * correctly apply the alias, we need the alias symlink to + * point to the symlink that was created in the search path. */ + alias_target = path_join(config_path, info->name); + if (!alias_target) + return -ENOMEM; + } + + bool broken; + r = chase(alias_path, lp->root_dir, CHASE_NONEXISTENT, /* ret_path = */ NULL, /* ret_fd = */ NULL); + if (r < 0 && r != -ENOENT) { + RET_GATHER(ret, r); continue; } - broken = q == 0; /* symlink target does not exist? */ + broken = r == 0; /* symlink target does not exist? */ - q = create_symlink(lp, info->path, alias_path, force || broken, changes, n_changes); - r = r < 0 ? r : q; + RET_GATHER(ret, create_symlink(lp, alias_target ?: info->path, alias_path, force || broken, changes, n_changes)); } - return r; + return ret; } static int install_info_symlink_wants( @@ -1995,10 +2055,7 @@ static int install_info_symlink_wants( q = install_name_printf(scope, info, *s, &dst); if (q < 0) { - install_changes_add(changes, n_changes, q, *s, NULL); - if (r >= 0) - r = q; - + RET_GATHER(r, install_changes_add(changes, n_changes, q, *s, NULL)); continue; } @@ -2010,15 +2067,13 @@ static int install_info_symlink_wants( * 'systemctl enable serial-getty@.service' should fail, the user should specify an * instance like in 'systemctl enable serial-getty@ttyS0.service'. */ - if (file_flags & UNIT_FILE_IGNORE_AUXILIARY_FAILURE) + if (FLAGS_SET(file_flags, UNIT_FILE_IGNORE_AUXILIARY_FAILURE)) continue; if (unit_name_is_valid(dst, UNIT_NAME_ANY)) - q = install_changes_add(changes, n_changes, -EIDRM, dst, n); + RET_GATHER(r, install_changes_add(changes, n_changes, -EIDRM, dst, n)); else - q = install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL); - if (r >= 0) - r = q; + RET_GATHER(r, install_changes_add(changes, n_changes, -EUCLEAN, dst, NULL)); continue; } @@ -2027,7 +2082,7 @@ static int install_info_symlink_wants( if (!path) return -ENOMEM; - q = create_symlink(lp, info->path, path, true, changes, n_changes); + q = create_symlink(lp, info->path, path, /* force = */ true, changes, n_changes); if ((q < 0 && r >= 0) || r == 0) r = q; @@ -2259,7 +2314,7 @@ int unit_file_mask( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; const char *config_path; int r; @@ -2274,13 +2329,13 @@ int unit_file_mask( if (!config_path) return -ENXIO; + r = 0; + STRV_FOREACH(name, names) { _cleanup_free_ char *path = NULL; - int q; if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) { - if (r == 0) - r = -EINVAL; + RET_GATHER(r, -EINVAL); continue; } @@ -2288,9 +2343,7 @@ int unit_file_mask( if (!path) return -ENOMEM; - q = create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, create_symlink(&lp, "/dev/null", path, flags & UNIT_FILE_FORCE, changes, n_changes)); } return r; @@ -2304,7 +2357,7 @@ int unit_file_unmask( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; _cleanup_strv_free_ char **todo = NULL; const char *config_path; @@ -2386,8 +2439,7 @@ int unit_file_unmask( if (!dry_run && unlink(path) < 0) { if (errno != ENOENT) { - if (r >= 0) - r = -errno; + RET_GATHER(r, -errno); install_changes_add(changes, n_changes, -errno, path, NULL); } @@ -2404,9 +2456,7 @@ int unit_file_unmask( return q; } - q = remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes); - if (r >= 0) - r = q; + RET_GATHER(r, remove_marked_symlinks(remove_symlinks_to, config_path, &lp, dry_run, changes, n_changes)); return r; } @@ -2419,11 +2469,11 @@ int unit_file_link( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **todo = NULL; const char *config_path; size_t n_todo = 0; - int r, q; + int r; assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); @@ -2492,9 +2542,7 @@ int unit_file_link( if (!new_path) return -ENOMEM; - q = create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes); - if (q < 0 && r >= 0) - r = q; + RET_GATHER(r, create_symlink(&lp, *i, new_path, flags & UNIT_FILE_FORCE, changes, n_changes)); } return r; @@ -2527,7 +2575,7 @@ int unit_file_revert( size_t *n_changes) { _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **todo = NULL; size_t n_todo = 0; int r, q; @@ -2685,7 +2733,7 @@ int unit_file_add_dependency( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info, *target_info; const char *config_path; @@ -2786,7 +2834,7 @@ int unit_file_enable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -2814,11 +2862,12 @@ static int do_unit_file_disable( _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - InstallInfo *info; bool has_install_info = false; int r; STRV_FOREACH(name, names) { + InstallInfo *info; + if (!unit_name_is_valid(*name, UNIT_NAME_ANY)) return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL); @@ -2838,7 +2887,6 @@ static int do_unit_file_disable( r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes); if (r >= 0) r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes); - if (r < 0) return r; @@ -2854,7 +2902,7 @@ int unit_file_disable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -2897,7 +2945,7 @@ static int normalize_linked_files( return log_debug_errno(SYNTHETIC_ERRNO(EISDIR), "Unexpected path to a directory \"%s\", refusing.", *a); - if (!is_path(*a)) { + if (!is_path(*a) && !unit_name_is_valid(*a, UNIT_NAME_INSTANCE)) { r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL); if (r < 0) log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n); @@ -2937,7 +2985,7 @@ int unit_file_reenable( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL, **files = NULL; int r; @@ -2973,7 +3021,7 @@ int unit_file_set_default( InstallChange **changes, size_t *n_changes) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info; const char *new_path; @@ -3003,17 +3051,16 @@ int unit_file_set_default( int unit_file_get_default( RuntimeScope scope, const char *root_dir, - char **name) { + char **ret) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; InstallInfo *info; - char *n; int r; assert(scope >= 0); assert(scope < _RUNTIME_SCOPE_MAX); - assert(name); + assert(ret); r = lookup_paths_init(&lp, scope, 0, root_dir); if (r < 0) @@ -3024,12 +3071,7 @@ int unit_file_get_default( if (r < 0) return r; - n = strdup(info->name); - if (!n) - return -ENOMEM; - - *name = n; - return 0; + return strdup_to(ret, info->name); } int unit_file_lookup_state( @@ -3136,7 +3178,7 @@ int unit_file_get_state( const char *name, UnitFileState *ret) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); @@ -3150,8 +3192,10 @@ int unit_file_get_state( return unit_file_lookup_state(scope, &lp, name, ret); } -int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name) { - _cleanup_(install_context_done) InstallContext c = { .scope = scope }; +int unit_file_exists_full(RuntimeScope scope, const LookupPaths *lp, const char *name, char **ret_path) { + _cleanup_(install_context_done) InstallContext c = { + .scope = scope, + }; int r; assert(lp); @@ -3160,12 +3204,31 @@ int unit_file_exists(RuntimeScope scope, const LookupPaths *lp, const char *name if (!unit_name_is_valid(name, UNIT_NAME_ANY)) return -EINVAL; - r = install_info_discover(&c, lp, name, 0, NULL, NULL, NULL); - if (r == -ENOENT) + InstallInfo *info = NULL; + r = install_info_discover( + &c, + lp, + name, + /* flags= */ 0, + ret_path ? &info : NULL, + /* changes= */ NULL, + /* n_changes= */ NULL); + if (r == -ENOENT) { + if (ret_path) + *ret_path = NULL; return 0; + } if (r < 0) return r; + if (ret_path) { + assert(info); + + r = strdup_to(ret_path, info->path); + if (r < 0) + return r; + } + return 1; } @@ -3203,8 +3266,8 @@ static int split_pattern_into_name_and_instances(const char *pattern, char **out } static int presets_find_config(RuntimeScope scope, const char *root_dir, char ***files) { - static const char* const system_dirs[] = {CONF_PATHS("systemd/system-preset"), NULL}; - static const char* const user_dirs[] = {CONF_PATHS_USR("systemd/user-preset"), NULL}; + static const char* const system_dirs[] = { CONF_PATHS("systemd/system-preset"), NULL }; + static const char* const user_dirs[] = { CONF_PATHS("systemd/user-preset"), NULL }; const char* const* dirs; assert(scope >= 0); @@ -3540,7 +3603,7 @@ int unit_file_preset( size_t *n_changes) { _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(unit_file_presets_done) UnitFilePresets presets = {}; const char *config_path; int r; @@ -3579,7 +3642,7 @@ int unit_file_preset_all( size_t *n_changes) { _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_(unit_file_presets_done) UnitFilePresets presets = {}; const char *config_path = NULL; int r; @@ -3600,18 +3663,19 @@ int unit_file_preset_all( if (r < 0) return r; + r = 0; STRV_FOREACH(i, lp.search_path) { _cleanup_closedir_ DIR *d = NULL; d = opendir(*i); if (!d) { - if (errno == ENOENT) - continue; - - return -errno; + if (errno != ENOENT) + RET_GATHER(r, -errno); + continue; } - FOREACH_DIRENT(de, d, return -errno) { + FOREACH_DIRENT(de, d, RET_GATHER(r, -errno)) { + int k; if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) continue; @@ -3619,12 +3683,23 @@ int unit_file_preset_all( if (!IN_SET(de->d_type, DT_LNK, DT_REG)) continue; - r = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes); - if (r < 0 && - !IN_SET(r, -EEXIST, -ERFKILL, -EADDRNOTAVAIL, -EBADSLT, -EIDRM, -EUCLEAN, -ELOOP, -ENOENT, -EUNATCH, -EXDEV)) + k = preset_prepare_one(scope, &plus, &minus, &lp, de->d_name, &presets, changes, n_changes); + if (k < 0 && + !IN_SET(k, -EEXIST, + -ERFKILL, + -EADDRNOTAVAIL, + -ETXTBSY, + -EBADSLT, + -EIDRM, + -EUCLEAN, + -ELOOP, + -EXDEV, + -ENOENT, + -ENOLINK, + -EUNATCH)) /* Ignore generated/transient/missing/invalid units when applying preset, propagate other errors. - * Coordinate with install_changes_dump() above. */ - return r; + * Coordinate with install_change_dump_error() above. */ + RET_GATHER(r, k); } } @@ -3656,7 +3731,7 @@ int unit_file_get_list( char **states, char **patterns) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; int r; assert(scope >= 0); diff --git a/src/shared/install.h b/src/shared/install.h index bc0c6db..13d1630 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -63,9 +63,9 @@ struct InstallChange { char *source; }; -static inline bool install_changes_have_modification(const InstallChange* changes, size_t n_changes) { - for (size_t i = 0; i < n_changes; i++) - if (IN_SET(changes[i].type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK)) +static inline bool install_changes_have_modification(const InstallChange *changes, size_t n_changes) { + FOREACH_ARRAY(i, changes, n_changes) + if (IN_SET(i->type, INSTALL_CHANGE_SYMLINK, INSTALL_CHANGE_UNLINK)) return true; return false; } @@ -175,7 +175,7 @@ int unit_file_set_default( int unit_file_get_default( RuntimeScope scope, const char *root_dir, - char **name); + char **ret); int unit_file_add_dependency( RuntimeScope scope, UnitFileFlags flags, @@ -193,7 +193,11 @@ int unit_file_lookup_state( UnitFileState *ret); int unit_file_get_state(RuntimeScope scope, const char *root_dir, const char *filename, UnitFileState *ret); -int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name); + +int unit_file_exists_full(RuntimeScope scope, const LookupPaths *paths, const char *name, char **ret_path); +static inline int unit_file_exists(RuntimeScope scope, const LookupPaths *paths, const char *name) { + return unit_file_exists_full(scope, paths, name, NULL); +} int unit_file_get_list(RuntimeScope scope, const char *root_dir, Hashmap *h, char **states, char **patterns); @@ -201,7 +205,14 @@ 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); -void install_changes_dump(int r, const char *verb, const InstallChange *changes, size_t n_changes, bool quiet); + +int install_change_dump_error(const InstallChange *change, char **ret_errmsg, const char **ret_bus_error); +void install_changes_dump( + int error, + const char *verb, + const InstallChange *changes, + size_t n_changes, + bool quiet); int unit_file_verify_alias( const InstallInfo *info, diff --git a/src/shared/journal-file-util.c b/src/shared/journal-file-util.c index bdceac4..8df165d 100644 --- a/src/shared/journal-file-util.c +++ b/src/shared/journal-file-util.c @@ -404,7 +404,7 @@ JournalFile* journal_file_offline_close(JournalFile *f) { if (sd_event_source_get_enabled(f->post_change_timer, NULL) > 0) journal_file_post_change(f); - sd_event_source_disable_unref(f->post_change_timer); + f->post_change_timer = sd_event_source_disable_unref(f->post_change_timer); journal_file_set_offline(f, true); @@ -451,7 +451,7 @@ int journal_file_rotate( set_clear_with_destructor(deferred_closes, journal_file_offline_close); r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, path, (*f)->open_flags, file_flags, @@ -476,14 +476,13 @@ int journal_file_open_reliably( uint64_t compress_threshold_bytes, JournalMetrics *metrics, MMapCache *mmap_cache, - JournalFile *template, JournalFile **ret) { _cleanup_(journal_file_offline_closep) JournalFile *old_file = NULL; int r; r = journal_file_open( - /* fd= */ -1, + /* fd= */ -EBADF, fname, open_flags, file_flags, @@ -491,7 +490,7 @@ int journal_file_open_reliably( compress_threshold_bytes, metrics, mmap_cache, - template, + /* template = */ NULL, ret); if (!IN_SET(r, -EBADMSG, /* Corrupted */ @@ -517,23 +516,19 @@ int journal_file_open_reliably( /* The file is corrupted. Rotate it away and try it again (but only once) */ log_warning_errno(r, "File %s corrupted or uncleanly shut down, renaming and replacing.", fname); - if (!template) { - /* The file is corrupted and no template is specified. Try opening it read-only as the - * template before rotating to inherit its sequence number and ID. */ - r = journal_file_open(-1, fname, - (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY, - file_flags, 0, compress_threshold_bytes, NULL, - mmap_cache, NULL, &old_file); - if (r < 0) - log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname); - else - template = old_file; - } + /* The file is corrupted. Try opening it read-only as the template before rotating to inherit its + * sequence number and ID. */ + r = journal_file_open(-EBADF, fname, + (open_flags & ~(O_ACCMODE|O_CREAT|O_EXCL)) | O_RDONLY, + file_flags, 0, compress_threshold_bytes, NULL, + mmap_cache, /* template = */ NULL, &old_file); + if (r < 0) + log_debug_errno(r, "Failed to continue sequence from file %s, ignoring: %m", fname); r = journal_file_dispose(AT_FDCWD, fname); if (r < 0) return r; - return journal_file_open(-1, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics, - mmap_cache, template, ret); + return journal_file_open(-EBADF, fname, open_flags, file_flags, mode, compress_threshold_bytes, metrics, + mmap_cache, /* template = */ old_file, ret); } diff --git a/src/shared/journal-file-util.h b/src/shared/journal-file-util.h index f9426c4..8df10a7 100644 --- a/src/shared/journal-file-util.h +++ b/src/shared/journal-file-util.h @@ -17,7 +17,6 @@ int journal_file_open_reliably( uint64_t compress_threshold_bytes, JournalMetrics *metrics, MMapCache *mmap_cache, - JournalFile *template, JournalFile **ret); JournalFile* journal_file_initiate_close(JournalFile *f, Set *deferred_closes); diff --git a/src/shared/journal-importer.c b/src/shared/journal-importer.c index 83e9834..bb0536e 100644 --- a/src/shared/journal-importer.c +++ b/src/shared/journal-importer.c @@ -93,7 +93,12 @@ static int get_line(JournalImporter *imp, char **line, size_t *size) { imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); @@ -134,7 +139,12 @@ static int fill_fixed_size(JournalImporter *imp, void **data, size_t size) { n = read(imp->fd, imp->buf + imp->filled, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); if (n < 0) { - if (errno != EAGAIN) + if (ERRNO_IS_DISCONNECT(errno)) { + log_debug_errno(errno, "Got disconnect for importer %s.", strna(imp->name)); + return 0; + } + + if (!ERRNO_IS_TRANSIENT(errno)) log_error_errno(errno, "read(%d, ..., %zu): %m", imp->fd, MALLOC_SIZEOF_SAFE(imp->buf) - imp->filled); return -errno; diff --git a/src/shared/journal-util.c b/src/shared/journal-util.c index d73d7c4..ab70f4d 100644 --- a/src/shared/journal-util.c +++ b/src/shared/journal-util.c @@ -145,7 +145,7 @@ int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_use return r; } -int journal_open_machine(sd_journal **ret, const char *machine) { +int journal_open_machine(sd_journal **ret, const char *machine, int flags) { _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_flush_close_unrefp) sd_bus *bus = NULL; @@ -178,7 +178,7 @@ int journal_open_machine(sd_journal **ret, const char *machine) { if (machine_fd < 0) return log_error_errno(errno, "Failed to duplicate file descriptor: %m"); - r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD); + r = sd_journal_open_directory_fd(&j, machine_fd, SD_JOURNAL_OS_ROOT | SD_JOURNAL_TAKE_DIRECTORY_FD | flags); if (r < 0) return log_error_errno(r, "Failed to open journal in machine '%s': %m", machine); diff --git a/src/shared/journal-util.h b/src/shared/journal-util.h index afad249..5bd8e34 100644 --- a/src/shared/journal-util.h +++ b/src/shared/journal-util.h @@ -8,4 +8,4 @@ int journal_access_blocked(sd_journal *j); int journal_access_check_and_warn(sd_journal *j, bool quiet, bool want_other_users); -int journal_open_machine(sd_journal **ret, const char *machine); +int journal_open_machine(sd_journal **ret, const char *machine, int flags); diff --git a/src/shared/json.c b/src/shared/json.c index 06c9e85..4af34e5 100644 --- a/src/shared/json.c +++ b/src/shared/json.c @@ -14,13 +14,16 @@ #include "fd-util.h" #include "fileio.h" #include "float.h" +#include "glyph-util.h" #include "hexdecoct.h" +#include "iovec-util.h" #include "json-internal.h" #include "json.h" #include "macro.h" #include "math-util.h" #include "memory-util.h" #include "memstream-util.h" +#include "path-util.h" #include "set.h" #include "string-table.h" #include "string-util.h" @@ -87,6 +90,9 @@ struct JsonVariant { /* Erase from memory when freeing */ bool sensitive:1; + /* True if we know that any referenced json object is marked sensitive */ + bool recursive_sensitive:1; + /* If this is an object the fields are strictly ordered by name */ bool sorted:1; @@ -559,7 +565,7 @@ static int _json_variant_array_put_element(JsonVariant *array, JsonVariant *elem return -ELNRNG; if (d >= array->depth) array->depth = d + 1; - array->n_elements ++; + array->n_elements++; *w = (JsonVariant) { .is_embedded = true, @@ -1451,6 +1457,33 @@ bool json_variant_is_sensitive(JsonVariant *v) { return v->sensitive; } +bool json_variant_is_sensitive_recursive(JsonVariant *v) { + if (!v) + return false; + if (json_variant_is_sensitive(v)) + return true; + if (!json_variant_is_regular(v)) + return false; + if (v->recursive_sensitive) /* Already checked this before */ + return true; + if (!IN_SET(v->type, JSON_VARIANT_ARRAY, JSON_VARIANT_OBJECT)) + return false; + if (v->is_reference) { + if (!json_variant_is_sensitive_recursive(v->reference)) + return false; + + return (v->recursive_sensitive = true); + } + + for (size_t i = 0; i < json_variant_elements(v); i++) + if (json_variant_is_sensitive_recursive(json_variant_by_index(v, i))) + return (v->recursive_sensitive = true); + + /* Note: we only cache the result here in case true, since we allow all elements down the tree to + * have their sensitive flag toggled later on (but never off) */ + return false; +} + static void json_variant_propagate_sensitive(JsonVariant *from, JsonVariant *to) { if (json_variant_is_sensitive(from)) json_variant_sensitive(to); @@ -1580,6 +1613,15 @@ static int json_format(FILE *f, JsonVariant *v, JsonFormatFlags flags, const cha assert(f); assert(v); + if (FLAGS_SET(flags, JSON_FORMAT_CENSOR_SENSITIVE) && json_variant_is_sensitive(v)) { + if (flags & JSON_FORMAT_COLOR) + fputs(ansi_red(), f); + fputs("\"\"", f); + if (flags & JSON_FORMAT_COLOR) + fputs(ANSI_NORMAL, f); + return 0; + } + switch (json_variant_type(v)) { case JSON_VARIANT_REAL: { @@ -2310,7 +2352,7 @@ static int json_variant_copy(JsonVariant **nv, JsonVariant *v) { source = json_variant_string(v); k = strnlen(source, INLINE_STRING_MAX + 1); if (k <= INLINE_STRING_MAX) { - k ++; + k++; break; } @@ -2594,7 +2636,7 @@ static int json_parse_string(const char **p, char **ret) { return -ENOMEM; s[n++] = ch; - c ++; + c++; continue; } @@ -3790,7 +3832,8 @@ int json_buildv(JsonVariant **ret, va_list ap) { break; } - case _JSON_BUILD_IOVEC_BASE64: { + case _JSON_BUILD_IOVEC_BASE64: + case _JSON_BUILD_IOVEC_HEX: { const struct iovec *iov; if (!IN_SET(current->expect, EXPECT_TOPLEVEL, EXPECT_OBJECT_VALUE, EXPECT_ARRAY_ELEMENT)) { @@ -3798,10 +3841,14 @@ int json_buildv(JsonVariant **ret, va_list ap) { goto finish; } - iov = ASSERT_PTR(va_arg(ap, const struct iovec*)); + iov = va_arg(ap, const struct iovec*); if (current->n_suppress == 0) { - r = json_variant_new_base64(&add, iov->iov_base, iov->iov_len); + if (iov) + r = command == _JSON_BUILD_IOVEC_BASE64 ? json_variant_new_base64(&add, iov->iov_base, iov->iov_len) : + json_variant_new_hex(&add, iov->iov_base, iov->iov_len); + else + r = json_variant_new_string(&add, ""); if (r < 0) goto finish; } @@ -4574,7 +4621,7 @@ int json_dispatch_full( } } - done ++; + done++; } else { /* Didn't find a matching entry! ☹️ */ @@ -4589,11 +4636,15 @@ int json_dispatch_full( return r; } else - done ++; + done++; } else { - json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); + if (flags & JSON_ALLOW_EXTENSIONS) { + json_log(value, flags|JSON_DEBUG, 0, "Unrecognized object field '%s', assuming extension.", json_variant_string(key)); + continue; + } + json_log(value, flags, 0, "Unexpected object field '%s'.", json_variant_string(key)); if (flags & JSON_PERMISSIVE) continue; @@ -4762,6 +4813,42 @@ int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFla return 0; } +int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + int8_t *i = ASSERT_PTR(userdata); + int64_t i64; + int r; + + assert(variant); + + r = json_dispatch_int64(name, variant, flags, &i64); + if (r < 0) + return r; + + if (i64 < INT8_MIN || i64 > INT8_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *i = (int8_t) i64; + return 0; +} + +int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + uint8_t *u = ASSERT_PTR(userdata); + uint64_t u64; + int r; + + assert(variant); + + r = json_dispatch_uint64(name, variant, flags, &u64); + if (r < 0) + return r; + + if (u64 > UINT8_MAX) + return json_log(variant, flags, SYNTHETIC_ERRNO(ERANGE), "JSON field '%s' out of bounds.", strna(name)); + + *u = (uint8_t) u64; + return 0; +} + int json_dispatch_string(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { char **s = ASSERT_PTR(userdata); int r; @@ -4920,6 +5007,27 @@ int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDi return 0; } +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + const char *path; + char **p = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + path = json_variant_string(variant); + if (!path_is_valid(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid path.", strna(name)); + if (!path_is_absolute(path)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' must be an absolute path.", strna(name)); + + if (free_and_strdup(p, path) < 0) + return json_log_oom(variant, flags); + + return 0; +} + int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { sd_id128_t *uuid = userdata; int r; @@ -4961,6 +5069,63 @@ int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDis return 0; } +int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_free_ uint8_t *buffer = NULL; + struct iovec *iov = ASSERT_PTR(userdata); + size_t sz, k = 0; + + assert(variant); + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + sz = json_variant_elements(variant); + + buffer = new(uint8_t, sz + 1); + if (!buffer) + return json_log(variant, flags, SYNTHETIC_ERRNO(ENOMEM), "Out of memory."); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + uint64_t b; + + if (!json_variant_is_unsigned(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name)); + + b = json_variant_unsigned(i); + if (b > 0xff) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), + "Element %zu of JSON field '%s' is out of range 0%s255.", + k, strna(name), special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + + buffer[k++] = (uint8_t) b; + } + assert(k == sz); + + /* Append a NUL byte for safety, like we do in memdup_suffix0() and others. */ + buffer[sz] = 0; + + free_and_replace(iov->iov_base, buffer); + iov->iov_len = sz; + return 0; +} + +int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + struct in_addr *address = ASSERT_PTR(userdata); + _cleanup_(iovec_done) struct iovec iov = {}; + int r; + + r = json_dispatch_byte_array_iovec(name, variant, flags, &iov); + if (r < 0) + return r; + + if (iov.iov_len != sizeof(struct in_addr)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is array of unexpected size.", strna(name)); + + memcpy(address, iov.iov_base, iov.iov_len); + return 0; +} + static int json_cmp_strings(const void *x, const void *y) { JsonVariant *const *a = x, *const *b = y; @@ -5107,14 +5272,14 @@ int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size) { if (!json_variant_is_string(v)) return -EINVAL; - return unbase64mem(json_variant_string(v), SIZE_MAX, ret, ret_size); + return unbase64mem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size); } int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size) { if (!json_variant_is_string(v)) return -EINVAL; - return unhexmem(json_variant_string(v), SIZE_MAX, ret, ret_size); + return unhexmem_full(json_variant_string(v), SIZE_MAX, /* secure= */ json_variant_is_sensitive(v), ret, ret_size); } static const char* const json_variant_type_table[_JSON_VARIANT_TYPE_MAX] = { diff --git a/src/shared/json.h b/src/shared/json.h index c40c234..6a93530 100644 --- a/src/shared/json.h +++ b/src/shared/json.h @@ -155,6 +155,7 @@ bool json_variant_equal(JsonVariant *a, JsonVariant *b); void json_variant_sensitive(JsonVariant *v); bool json_variant_is_sensitive(JsonVariant *v); +bool json_variant_is_sensitive_recursive(JsonVariant *v); struct json_variant_foreach_state { JsonVariant *variant; @@ -185,17 +186,18 @@ struct json_variant_foreach_state { int json_variant_get_source(JsonVariant *v, const char **ret_source, unsigned *ret_line, unsigned *ret_column); typedef enum JsonFormatFlags { - JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ - JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ - JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */ - JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */ - JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */ - JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */ - JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ - JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ - JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ - JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */ - JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */ + JSON_FORMAT_NEWLINE = 1 << 0, /* suffix with newline */ + JSON_FORMAT_PRETTY = 1 << 1, /* add internal whitespace to appeal to human readers */ + JSON_FORMAT_PRETTY_AUTO = 1 << 2, /* same, but only if connected to a tty (and JSON_FORMAT_NEWLINE otherwise) */ + JSON_FORMAT_COLOR = 1 << 3, /* insert ANSI color sequences */ + JSON_FORMAT_COLOR_AUTO = 1 << 4, /* insert ANSI color sequences if colors_enabled() says so */ + JSON_FORMAT_SOURCE = 1 << 5, /* prefix with source filename/line/column */ + JSON_FORMAT_SSE = 1 << 6, /* prefix/suffix with W3C server-sent events */ + JSON_FORMAT_SEQ = 1 << 7, /* prefix/suffix with RFC 7464 application/json-seq */ + JSON_FORMAT_FLUSH = 1 << 8, /* call fflush() after dumping JSON */ + JSON_FORMAT_EMPTY_ARRAY = 1 << 9, /* output "[]" for empty input */ + JSON_FORMAT_OFF = 1 << 10, /* make json_variant_format() fail with -ENOEXEC */ + JSON_FORMAT_CENSOR_SENSITIVE = 1 << 11, /* Replace all sensitive elements with the string "" */ } JsonFormatFlags; int json_variant_format(JsonVariant *v, JsonFormatFlags flags, char **ret); @@ -271,6 +273,7 @@ enum { _JSON_BUILD_IOVEC_BASE64, _JSON_BUILD_BASE32HEX, _JSON_BUILD_HEX, + _JSON_BUILD_IOVEC_HEX, _JSON_BUILD_OCTESCAPE, _JSON_BUILD_ID128, _JSON_BUILD_UUID, @@ -315,6 +318,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user #define JSON_BUILD_IOVEC_BASE64(iov) _JSON_BUILD_IOVEC_BASE64, (const struct iovec*) { iov } #define JSON_BUILD_BASE32HEX(p, n) _JSON_BUILD_BASE32HEX, (const void*) { p }, (size_t) { n } #define JSON_BUILD_HEX(p, n) _JSON_BUILD_HEX, (const void*) { p }, (size_t) { n } +#define JSON_BUILD_IOVEC_HEX(iov) _JSON_BUILD_IOVEC_HEX, (const struct iovec*) { iov } #define JSON_BUILD_OCTESCAPE(p, n) _JSON_BUILD_OCTESCAPE, (const void*) { p }, (size_t) { n } #define JSON_BUILD_ID128(id) _JSON_BUILD_ID128, (const sd_id128_t*) { &(id) } #define JSON_BUILD_UUID(id) _JSON_BUILD_UUID, (const sd_id128_t*) { &(id) } @@ -345,6 +349,7 @@ typedef int (*JsonBuildCallback)(JsonVariant **ret, const char *name, void *user #define JSON_BUILD_PAIR_BASE64(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_BASE64(p, n)) #define JSON_BUILD_PAIR_IOVEC_BASE64(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_BASE64(iov)) #define JSON_BUILD_PAIR_HEX(name, p, n) JSON_BUILD_PAIR(name, JSON_BUILD_HEX(p, n)) +#define JSON_BUILD_PAIR_IOVEC_HEX(name, iov) JSON_BUILD_PAIR(name, JSON_BUILD_IOVEC_HEX(iov)) #define JSON_BUILD_PAIR_ID128(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_ID128(id)) #define JSON_BUILD_PAIR_UUID(name, id) JSON_BUILD_PAIR(name, JSON_BUILD_UUID(id)) #define JSON_BUILD_PAIR_BYTE_ARRAY(name, v, n) JSON_BUILD_PAIR(name, JSON_BUILD_BYTE_ARRAY(v, n)) @@ -375,15 +380,16 @@ int json_buildv(JsonVariant **ret, va_list ap); * entry, as well the bitmask specified for json_log() calls */ typedef enum JsonDispatchFlags { /* The following three may be set in JsonDispatch's .flags field or the json_dispatch() flags parameter */ - JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this property? */ - JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */ - JSON_LOG = 1 << 2, /* Should the parser log about errors? */ - JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */ - JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */ + JSON_PERMISSIVE = 1 << 0, /* Shall parsing errors be considered fatal for this field or object? */ + JSON_MANDATORY = 1 << 1, /* Should existence of this property be mandatory? */ + JSON_LOG = 1 << 2, /* Should the parser log about errors? */ + JSON_SAFE = 1 << 3, /* Don't accept "unsafe" strings in json_dispatch_string() + json_dispatch_string() */ + JSON_RELAX = 1 << 4, /* Use relaxed user name checking in json_dispatch_user_group_name */ + JSON_ALLOW_EXTENSIONS = 1 << 5, /* Subset of JSON_PERMISSIVE: allow additional fields, but no other permissive handling */ /* The following two may be passed into log_json() in addition to those above */ - JSON_DEBUG = 1 << 5, /* Indicates that this log message is a debug message */ - JSON_WARNING = 1 << 6, /* Indicates that this log message is a warning message */ + JSON_DEBUG = 1 << 6, /* Indicates that this log message is a debug message */ + JSON_WARNING = 1 << 7, /* Indicates that this log message is a warning message */ } JsonDispatchFlags; typedef int (*JsonDispatchCallback)(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); @@ -415,11 +421,16 @@ int json_dispatch_uint32(const char *name, JsonVariant *variant, JsonDispatchFla int json_dispatch_int32(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uint16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_int16(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_int8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_uint8(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_uid_gid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_user_group_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_absolute_path(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_id128(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unsupported(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); int json_dispatch_unbase64_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_byte_array_iovec(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); +int json_dispatch_in_addr(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata); assert_cc(sizeof(uint32_t) == sizeof(unsigned)); #define json_dispatch_uint json_dispatch_uint32 @@ -427,6 +438,28 @@ assert_cc(sizeof(uint32_t) == sizeof(unsigned)); assert_cc(sizeof(int32_t) == sizeof(int)); #define json_dispatch_int json_dispatch_int32 +#define JSON_DISPATCH_ENUM_DEFINE(name, type, func) \ + int name(const char *n, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { \ + type *c = ASSERT_PTR(userdata); \ + \ + assert(variant); \ + \ + if (json_variant_is_null(variant)) { \ + *c = (type) -EINVAL; \ + return 0; \ + } \ + \ + if (!json_variant_is_string(variant)) \ + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(n)); \ + \ + type cc = func(json_variant_string(variant)); \ + if (cc < 0) \ + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Value of JSON field '%s' not recognized.", strna(n)); \ + \ + *c = cc; \ + return 0; \ + } + static inline int json_dispatch_level(JsonDispatchFlags flags) { /* Did the user request no logging? If so, then never log higher than LOG_DEBUG. Also, if this is marked as @@ -470,5 +503,13 @@ int json_log_internal(JsonVariant *variant, int level, int error, const char *fi int json_variant_unbase64(JsonVariant *v, void **ret, size_t *ret_size); int json_variant_unhex(JsonVariant *v, void **ret, size_t *ret_size); +static inline int json_variant_unbase64_iovec(JsonVariant *v, struct iovec *ret) { + return json_variant_unbase64(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); +} + +static inline int json_variant_unhex_iovec(JsonVariant *v, struct iovec *ret) { + return json_variant_unhex(v, ret ? &ret->iov_base : NULL, ret ? &ret->iov_len : NULL); +} + const char *json_variant_type_to_string(JsonVariantType t); JsonVariantType json_variant_type_from_string(const char *s); diff --git a/src/shared/kbd-util.c b/src/shared/kbd-util.c index 2f2d161..60e0429 100644 --- a/src/shared/kbd-util.c +++ b/src/shared/kbd-util.c @@ -1,9 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "env-util.h" #include "errno-util.h" #include "kbd-util.h" #include "log.h" -#include "nulstr-util.h" #include "path-util.h" #include "recurse-dir.h" #include "set.h" @@ -11,6 +11,25 @@ #include "strv.h" #include "utf8.h" +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/", \ + "/usr/share/kbd/keymaps/", \ + "/usr/lib/kbd/keymaps/" + +int keymap_directories(char ***ret) { + assert(ret); + + if (getenv_path_list("SYSTEMD_KEYMAP_DIRECTORIES", ret) >= 0) + return 0; + + char **paths = strv_new(KBD_KEYMAP_DIRS); + if (!paths) + return log_oom_debug(); + + *ret = TAKE_PTR(paths); + return 0; +} + struct recurse_dir_userdata { const char *keymap_name; Set *keymaps; @@ -65,16 +84,21 @@ static int keymap_recurse_dir_callback( int get_keymaps(char ***ret) { _cleanup_set_free_free_ Set *keymaps = NULL; + _cleanup_strv_free_ char **keymap_dirs = NULL; int r; + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + keymaps = set_new(&string_hash_ops); if (!keymaps) return -ENOMEM; - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + STRV_FOREACH(dir, keymap_dirs) { r = recurse_dir_at( AT_FDCWD, - dir, + *dir, /* statx_mask= */ 0, /* n_depth_max= */ UINT_MAX, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, @@ -85,9 +109,9 @@ int get_keymaps(char ***ret) { if (r == -ENOENT) continue; if (ERRNO_IS_NEG_RESOURCE(r)) - return log_warning_errno(r, "Failed to read keymap list from %s: %m", dir); + return log_warning_errno(r, "Failed to read keymap list from %s: %m", *dir); if (r < 0) - log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir); + log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir); } _cleanup_strv_free_ char **l = set_get_strv(keymaps); @@ -127,15 +151,20 @@ bool keymap_is_valid(const char *name) { } int keymap_exists(const char *name) { + _cleanup_strv_free_ char **keymap_dirs = NULL; int r; if (!keymap_is_valid(name)) return -EINVAL; - NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) { + r = keymap_directories(&keymap_dirs); + if (r < 0) + return r; + + STRV_FOREACH(dir, keymap_dirs) { r = recurse_dir_at( AT_FDCWD, - dir, + *dir, /* statx_mask= */ 0, /* n_depth_max= */ UINT_MAX, RECURSE_DIR_IGNORE_DOT|RECURSE_DIR_ENSURE_TYPE, @@ -148,7 +177,7 @@ int keymap_exists(const char *name) { if (ERRNO_IS_NEG_RESOURCE(r)) return r; if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", dir); + log_debug_errno(r, "Failed to read keymap list from %s, ignoring: %m", *dir); } return false; diff --git a/src/shared/kbd-util.h b/src/shared/kbd-util.h index aca0dee..a8e365e 100644 --- a/src/shared/kbd-util.h +++ b/src/shared/kbd-util.h @@ -3,11 +3,7 @@ #include -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" - -int get_keymaps(char ***l); +int keymap_directories(char ***ret); +int get_keymaps(char ***ret); bool keymap_is_valid(const char *name); int keymap_exists(const char *name); diff --git a/src/shared/kernel-config.c b/src/shared/kernel-config.c new file mode 100644 index 0000000..483ca28 --- /dev/null +++ b/src/shared/kernel-config.c @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "conf-parser.h" +#include "kernel-config.h" +#include "macro.h" +#include "path-util.h" +#include "strv.h" + +int load_kernel_install_conf( + const char *root, + const char *conf_root, + char **ret_machine_id, + char **ret_boot_root, + char **ret_layout, + char **ret_initrd_generator, + char **ret_uki_generator) { + + _cleanup_free_ char *machine_id = NULL, *boot_root = NULL, *layout = NULL, + *initrd_generator = NULL, *uki_generator = NULL; + const ConfigTableItem items[] = { + { NULL, "MACHINE_ID", config_parse_string, 0, &machine_id }, + { NULL, "BOOT_ROOT", config_parse_string, 0, &boot_root }, + { NULL, "layout", config_parse_string, 0, &layout }, + { NULL, "initrd_generator", config_parse_string, 0, &initrd_generator }, + { NULL, "uki_generator", config_parse_string, 0, &uki_generator }, + {} + }; + int r; + + if (conf_root) { + _cleanup_free_ char *conf = path_join(conf_root, "install.conf"); + if (!conf) + return log_oom(); + + r = config_parse_many( + STRV_MAKE_CONST(conf), + STRV_MAKE_CONST(conf_root), + "install.conf.d", + /* root= */ NULL, /* $KERNEL_INSTALL_CONF_ROOT and --root are independent */ + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); + } else + r = config_parse_standard_file_with_dropins_full( + root, + "kernel/install.conf", + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); + if (r < 0 && r != -ENOENT) + return r; + + if (ret_machine_id) + *ret_machine_id = TAKE_PTR(machine_id); + if (ret_boot_root) + *ret_boot_root = TAKE_PTR(boot_root); + if (ret_layout) + *ret_layout = TAKE_PTR(layout); + if (ret_initrd_generator) + *ret_initrd_generator = TAKE_PTR(initrd_generator); + if (ret_uki_generator) + *ret_uki_generator = TAKE_PTR(uki_generator); + return r >= 0; /* Return 0 if we got -ENOENT above, 1 otherwise. */ +} diff --git a/src/shared/kernel-config.h b/src/shared/kernel-config.h new file mode 100644 index 0000000..5681870 --- /dev/null +++ b/src/shared/kernel-config.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +int load_kernel_install_conf( + const char *root, + const char *conf_root, + char **ret_machine_id, + char **ret_boot_root, + char **ret_layout, + char **ret_initrd_generator, + char **ret_uki_generator); diff --git a/src/shared/keyring-util.c b/src/shared/keyring-util.c deleted file mode 100644 index fadd90e..0000000 --- a/src/shared/keyring-util.c +++ /dev/null @@ -1,35 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "keyring-util.h" -#include "memory-util.h" -#include "missing_syscall.h" - -int keyring_read(key_serial_t serial, void **ret, size_t *ret_size) { - size_t bufsize = 100; - - for (;;) { - _cleanup_(erase_and_freep) uint8_t *buf = NULL; - long n; - - buf = new(uint8_t, bufsize + 1); - if (!buf) - return -ENOMEM; - - n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) buf, (unsigned long) bufsize, 0); - if (n < 0) - return -errno; - - if ((size_t) n <= bufsize) { - buf[n] = 0; /* NUL terminate, just in case */ - - if (ret) - *ret = TAKE_PTR(buf); - if (ret_size) - *ret_size = n; - - return 0; - } - - bufsize = (size_t) n; - } -} diff --git a/src/shared/keyring-util.h b/src/shared/keyring-util.h deleted file mode 100644 index c8c53f1..0000000 --- a/src/shared/keyring-util.h +++ /dev/null @@ -1,11 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ -#pragma once - -#include - -#include "missing_keyctl.h" - -/* Like TAKE_PTR() but for key_serial_t, resetting them to -1 */ -#define TAKE_KEY_SERIAL(key_serial) TAKE_GENERIC(key_serial, key_serial_t, -1) - -int keyring_read(key_serial_t serial, void **ret, size_t *ret_size); diff --git a/src/shared/killall.c b/src/shared/killall.c index 917b773..a087364 100644 --- a/src/shared/killall.c +++ b/src/shared/killall.c @@ -116,18 +116,18 @@ static bool ignore_proc(const PidRef *pid, bool warn_rootfs) { return true; } -static void log_children_no_yet_killed(Set *pids) { +static void log_children_not_yet_killed(Set *pids) { _cleanup_free_ char *lst_child = NULL; - void *p; int r; + void *p; SET_FOREACH(p, pids) { _cleanup_free_ char *s = NULL; if (pid_get_comm(PTR_TO_PID(p), &s) >= 0) - r = strextendf(&lst_child, ", " PID_FMT " (%s)", PTR_TO_PID(p), s); + r = strextendf_with_separator(&lst_child, ", ", PID_FMT " (%s)", PTR_TO_PID(p), s); else - r = strextendf(&lst_child, ", " PID_FMT, PTR_TO_PID(p)); + r = strextendf_with_separator(&lst_child, ", ", PID_FMT, PTR_TO_PID(p)); if (r < 0) return (void) log_oom_warning(); } @@ -135,7 +135,7 @@ static void log_children_no_yet_killed(Set *pids) { if (isempty(lst_child)) return; - log_warning("Waiting for process: %s", lst_child + 2); + log_warning("Waiting for process: %s", lst_child); } static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) { @@ -200,7 +200,7 @@ static int wait_for_children(Set *pids, sigset_t *mask, usec_t timeout) { n = now(CLOCK_MONOTONIC); if (date_log_child > 0 && n >= date_log_child) { - log_children_no_yet_killed(pids); + log_children_not_yet_killed(pids); /* Log the children not yet killed only once */ date_log_child = 0; } @@ -234,14 +234,14 @@ static int killall(int sig, Set *pids, bool send_sighup) { r = proc_dir_open(&dir); if (r < 0) - return log_warning_errno(r, "opendir(/proc) failed: %m"); + return log_warning_errno(r, "Failed to open /proc/: %m"); for (;;) { _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; r = proc_dir_read_pidref(dir, &pidref); if (r < 0) - return log_warning_errno(r, "Failed to enumerate /proc: %m"); + return log_warning_errno(r, "Failed to enumerate /proc/: %m"); if (r == 0) break; diff --git a/src/shared/label-util.c b/src/shared/label-util.c index 308fbff..2c482da 100644 --- a/src/shared/label-util.c +++ b/src/shared/label-util.c @@ -81,22 +81,23 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati return mac_smack_fix(to, 0); } -int mknod_label(const char *pathname, mode_t mode, dev_t dev) { +int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev) { int r; + assert(dirfd >= 0 || dirfd == AT_FDCWD); assert(pathname); - r = mac_selinux_create_file_prepare(pathname, mode); + r = mac_selinux_create_file_prepare_at(dirfd, pathname, mode); if (r < 0) return r; - r = RET_NERRNO(mknod(pathname, mode, dev)); + r = RET_NERRNO(mknodat(dirfd, pathname, mode, dev)); mac_selinux_create_file_clear(); if (r < 0) return r; - return mac_smack_fix(pathname, 0); + return mac_smack_fix_full(dirfd, pathname, NULL, 0); } int btrfs_subvol_make_label(const char *path) { diff --git a/src/shared/label-util.h b/src/shared/label-util.h index 7fb98c7..5a19a4c 100644 --- a/src/shared/label-util.h +++ b/src/shared/label-util.h @@ -21,7 +21,11 @@ int symlink_atomic_full_label(const char *from, const char *to, bool make_relati static inline int symlink_atomic_label(const char *from, const char *to) { return symlink_atomic_full_label(from, to, false); } -int mknod_label(const char *pathname, mode_t mode, dev_t dev); + +int mknodat_label(int dirfd, const char *pathname, mode_t mode, dev_t dev); +static inline int mknod_label(const char *pathname, mode_t mode, dev_t dev) { + return mknodat_label(AT_FDCWD, pathname, mode, dev); +} int btrfs_subvol_make_label(const char *path); diff --git a/src/shared/libarchive-util.c b/src/shared/libarchive-util.c new file mode 100644 index 0000000..58f6554 --- /dev/null +++ b/src/shared/libarchive-util.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "libarchive-util.h" + +#if HAVE_LIBARCHIVE +static void *libarchive_dl = NULL; + +DLSYM_FUNCTION(archive_entry_free); +DLSYM_FUNCTION(archive_entry_new); +DLSYM_FUNCTION(archive_entry_set_ctime); +DLSYM_FUNCTION(archive_entry_set_filetype); +DLSYM_FUNCTION(archive_entry_set_gid); +DLSYM_FUNCTION(archive_entry_set_mtime); +DLSYM_FUNCTION(archive_entry_set_pathname); +DLSYM_FUNCTION(archive_entry_set_perm); +DLSYM_FUNCTION(archive_entry_set_rdevmajor); +DLSYM_FUNCTION(archive_entry_set_rdevminor); +DLSYM_FUNCTION(archive_entry_set_symlink); +DLSYM_FUNCTION(archive_entry_set_size); +DLSYM_FUNCTION(archive_entry_set_uid); +DLSYM_FUNCTION(archive_error_string); +DLSYM_FUNCTION(archive_write_close); +DLSYM_FUNCTION(archive_write_data); +DLSYM_FUNCTION(archive_write_free); +DLSYM_FUNCTION(archive_write_header); +DLSYM_FUNCTION(archive_write_new); +DLSYM_FUNCTION(archive_write_open_FILE); +DLSYM_FUNCTION(archive_write_open_fd); +DLSYM_FUNCTION(archive_write_set_format_filter_by_ext); +DLSYM_FUNCTION(archive_write_set_format_gnutar); + +int dlopen_libarchive(void) { + ELF_NOTE_DLOPEN("archive", + "Support for decompressing archive files", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libarchive.so.13"); + + return dlopen_many_sym_or_warn( + &libarchive_dl, + "libarchive.so.13", + LOG_DEBUG, + DLSYM_ARG(archive_entry_free), + DLSYM_ARG(archive_entry_new), + DLSYM_ARG(archive_entry_set_ctime), + DLSYM_ARG(archive_entry_set_filetype), + DLSYM_ARG(archive_entry_set_gid), + DLSYM_ARG(archive_entry_set_mtime), + DLSYM_ARG(archive_entry_set_pathname), + DLSYM_ARG(archive_entry_set_perm), + DLSYM_ARG(archive_entry_set_rdevmajor), + DLSYM_ARG(archive_entry_set_rdevminor), + DLSYM_ARG(archive_entry_set_size), + DLSYM_ARG(archive_entry_set_symlink), + DLSYM_ARG(archive_entry_set_uid), + DLSYM_ARG(archive_error_string), + DLSYM_ARG(archive_write_close), + DLSYM_ARG(archive_write_data), + DLSYM_ARG(archive_write_free), + DLSYM_ARG(archive_write_header), + DLSYM_ARG(archive_write_new), + DLSYM_ARG(archive_write_open_FILE), + DLSYM_ARG(archive_write_open_fd), + DLSYM_ARG(archive_write_set_format_filter_by_ext), + DLSYM_ARG(archive_write_set_format_gnutar)); +} + +#endif diff --git a/src/shared/libarchive-util.h b/src/shared/libarchive-util.h new file mode 100644 index 0000000..8003da9 --- /dev/null +++ b/src/shared/libarchive-util.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "dlfcn-util.h" + +#if HAVE_LIBARCHIVE +#include +#include + +DLSYM_PROTOTYPE(archive_entry_free); +DLSYM_PROTOTYPE(archive_entry_new); +DLSYM_PROTOTYPE(archive_entry_set_ctime); +DLSYM_PROTOTYPE(archive_entry_set_filetype); +DLSYM_PROTOTYPE(archive_entry_set_gid); +DLSYM_PROTOTYPE(archive_entry_set_mtime); +DLSYM_PROTOTYPE(archive_entry_set_pathname); +DLSYM_PROTOTYPE(archive_entry_set_perm); +DLSYM_PROTOTYPE(archive_entry_set_rdevmajor); +DLSYM_PROTOTYPE(archive_entry_set_rdevminor); +DLSYM_PROTOTYPE(archive_entry_set_symlink); +DLSYM_PROTOTYPE(archive_entry_set_size); +DLSYM_PROTOTYPE(archive_entry_set_uid); +DLSYM_PROTOTYPE(archive_error_string); +DLSYM_PROTOTYPE(archive_write_close); +DLSYM_PROTOTYPE(archive_write_data); +DLSYM_PROTOTYPE(archive_write_free); +DLSYM_PROTOTYPE(archive_write_header); +DLSYM_PROTOTYPE(archive_write_new); +DLSYM_PROTOTYPE(archive_write_open_FILE); +DLSYM_PROTOTYPE(archive_write_open_fd); +DLSYM_PROTOTYPE(archive_write_set_format_filter_by_ext); +DLSYM_PROTOTYPE(archive_write_set_format_gnutar); + +int dlopen_libarchive(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive_entry*, sym_archive_entry_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct archive*, sym_archive_write_free, NULL); + +#else + +static inline int dlopen_libarchive(void) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/libcrypt-util.c b/src/shared/libcrypt-util.c index 81e6f17..50b2502 100644 --- a/src/shared/libcrypt-util.c +++ b/src/shared/libcrypt-util.c @@ -114,7 +114,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da if (!*data) { *data = new0(struct crypt_data, 1); if (!*data) { - errno = -ENOMEM; + errno = ENOMEM; return NULL; } @@ -140,7 +140,7 @@ static char* systemd_crypt_ra(const char *phrase, const char *setting, void **da int hash_password_full(const char *password, void **cd_data, int *cd_size, char **ret) { _cleanup_free_ char *salt = NULL; _cleanup_(erase_and_freep) void *_cd_data = NULL; - char *p; + const char *p; int r, _cd_size = 0; assert(!!cd_data == !!cd_size); @@ -155,12 +155,7 @@ int hash_password_full(const char *password, void **cd_data, int *cd_size, char return log_debug_errno(errno_or_else(SYNTHETIC_ERRNO(EINVAL)), CRYPT_RA_NAME "() failed: %m"); - p = strdup(p); - if (!p) - return -ENOMEM; - - *ret = p; - return 0; + return strdup_to(ret, p); } bool looks_like_hashed_password(const char *s) { diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 1cc3afe..37f6898 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -16,53 +16,54 @@ static void *libfido2_dl = NULL; -int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t) = NULL; -void (*sym_fido_assert_free)(fido_assert_t **) = NULL; -size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t) = NULL; -const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t) = NULL; -fido_assert_t* (*sym_fido_assert_new)(void) = NULL; -int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_assert_set_extensions)(fido_assert_t *, int) = NULL; -int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *) = NULL; -int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t) = NULL; -int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t) = NULL; -size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *) = NULL; -char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *) = NULL; -void (*sym_fido_cbor_info_free)(fido_cbor_info_t **) = NULL; -fido_cbor_info_t* (*sym_fido_cbor_info_new)(void) = NULL; -size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *) = NULL; -char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *) = NULL; -const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *) = NULL; -void (*sym_fido_cred_free)(fido_cred_t **) = NULL; -size_t (*sym_fido_cred_id_len)(const fido_cred_t *) = NULL; -const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *) = NULL; -fido_cred_t* (*sym_fido_cred_new)(void) = NULL; -int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t) = NULL; -int (*sym_fido_cred_set_extensions)(fido_cred_t *, int) = NULL; -int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t) = NULL; -int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *) = NULL; -int (*sym_fido_cred_set_type)(fido_cred_t *, int) = NULL; -int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *) = NULL; -int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t) = NULL; -void (*sym_fido_dev_free)(fido_dev_t **) = NULL; -int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *) = NULL; -int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *) = NULL; -void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t) = NULL; -int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *) = NULL; -const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *) = NULL; -const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *) = NULL; -fido_dev_info_t* (*sym_fido_dev_info_new)(size_t) = NULL; -const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *) = NULL; -const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t) = NULL; -bool (*sym_fido_dev_is_fido2)(const fido_dev_t *) = NULL; -int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *) = NULL; -fido_dev_t* (*sym_fido_dev_new)(void) = NULL; -int (*sym_fido_dev_open)(fido_dev_t *, const char *) = NULL; -int (*sym_fido_dev_close)(fido_dev_t *) = NULL; -void (*sym_fido_init)(int) = NULL; -void (*sym_fido_set_log_handler)(fido_log_handler_t *) = NULL; -const char* (*sym_fido_strerr)(int) = NULL; +DLSYM_FUNCTION(fido_assert_allow_cred); +DLSYM_FUNCTION(fido_assert_free); +DLSYM_FUNCTION(fido_assert_hmac_secret_len); +DLSYM_FUNCTION(fido_assert_hmac_secret_ptr); +DLSYM_FUNCTION(fido_assert_new); +DLSYM_FUNCTION(fido_assert_set_clientdata_hash); +DLSYM_FUNCTION(fido_assert_set_extensions); +DLSYM_FUNCTION(fido_assert_set_hmac_salt); +DLSYM_FUNCTION(fido_assert_set_rp); +DLSYM_FUNCTION(fido_assert_set_up); +DLSYM_FUNCTION(fido_assert_set_uv); +DLSYM_FUNCTION(fido_cbor_info_extensions_len); +DLSYM_FUNCTION(fido_cbor_info_extensions_ptr); +DLSYM_FUNCTION(fido_cbor_info_free); +DLSYM_FUNCTION(fido_cbor_info_new); +DLSYM_FUNCTION(fido_cbor_info_options_len); +DLSYM_FUNCTION(fido_cbor_info_options_name_ptr); +DLSYM_FUNCTION(fido_cbor_info_options_value_ptr); +DLSYM_FUNCTION(fido_cred_free); +DLSYM_FUNCTION(fido_cred_id_len); +DLSYM_FUNCTION(fido_cred_id_ptr); +DLSYM_FUNCTION(fido_cred_new); +DLSYM_FUNCTION(fido_cred_set_clientdata_hash); +DLSYM_FUNCTION(fido_cred_set_extensions); +DLSYM_FUNCTION(fido_cred_set_prot); +DLSYM_FUNCTION(fido_cred_set_rk); +DLSYM_FUNCTION(fido_cred_set_rp); +DLSYM_FUNCTION(fido_cred_set_type); +DLSYM_FUNCTION(fido_cred_set_user); +DLSYM_FUNCTION(fido_cred_set_uv); +DLSYM_FUNCTION(fido_dev_free); +DLSYM_FUNCTION(fido_dev_get_assert); +DLSYM_FUNCTION(fido_dev_get_cbor_info); +DLSYM_FUNCTION(fido_dev_info_free); +DLSYM_FUNCTION(fido_dev_info_manifest); +DLSYM_FUNCTION(fido_dev_info_manufacturer_string); +DLSYM_FUNCTION(fido_dev_info_product_string); +DLSYM_FUNCTION(fido_dev_info_new); +DLSYM_FUNCTION(fido_dev_info_path); +DLSYM_FUNCTION(fido_dev_info_ptr); +DLSYM_FUNCTION(fido_dev_is_fido2); +DLSYM_FUNCTION(fido_dev_make_cred); +DLSYM_FUNCTION(fido_dev_new); +DLSYM_FUNCTION(fido_dev_open); +DLSYM_FUNCTION(fido_dev_close); +DLSYM_FUNCTION(fido_init); +DLSYM_FUNCTION(fido_set_log_handler); +DLSYM_FUNCTION(fido_strerr); static void fido_log_propagate_handler(const char *s) { log_debug("libfido2: %s", strempty(s)); @@ -71,6 +72,11 @@ static void fido_log_propagate_handler(const char *s) { int dlopen_libfido2(void) { int r; + ELF_NOTE_DLOPEN("fido2", + "Support fido2 for encryption and authentication", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libfido2.so.1"); + r = dlopen_many_sym_or_warn( &libfido2_dl, "libfido2.so.1", LOG_DEBUG, DLSYM_ARG(fido_assert_allow_cred), @@ -97,6 +103,7 @@ int dlopen_libfido2(void) { DLSYM_ARG(fido_cred_new), DLSYM_ARG(fido_cred_set_clientdata_hash), DLSYM_ARG(fido_cred_set_extensions), + DLSYM_ARG(fido_cred_set_prot), DLSYM_ARG(fido_cred_set_rk), DLSYM_ARG(fido_cred_set_rp), DLSYM_ARG(fido_cred_set_type), @@ -574,7 +581,7 @@ static int fido2_use_hmac_hash_specific_token( /* COSE_ECDH_ES256 is not usable with fido_cred_set_type() thus it's not listed here. */ static const char *fido2_algorithm_to_string(int alg) { - switch(alg) { + switch (alg) { case COSE_ES256: return "es256"; case COSE_RS256: @@ -686,7 +693,8 @@ int fido2_generate_hmac_hash( const char *user_name, const char *user_display_name, const char *user_icon, - const char *askpw_icon_name, + const char *askpw_icon, + const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, void **ret_cid, size_t *ret_cid_size, @@ -775,10 +783,21 @@ int fido2_generate_hmac_hash( if (!c) return log_oom(); - r = sym_fido_cred_set_extensions(c, FIDO_EXT_HMAC_SECRET); + int extensions = FIDO_EXT_HMAC_SECRET; + if (FLAGS_SET(lock_with, FIDO2ENROLL_UV)) { + /* Attempt to use the "cred protect" extension, requiring user verification (UV) for this + * credential. If the authenticator doesn't support the extension, it will be ignored. */ + extensions |= FIDO_EXT_CRED_PROTECT; + + r = sym_fido_cred_set_prot(c, FIDO_CRED_PROT_UV_REQUIRED); + if (r != FIDO_OK) + log_warning("Failed to set protection level on FIDO2 credential, ignoring: %s", sym_fido_strerr(r)); + } + + r = sym_fido_cred_set_extensions(c, extensions); if (r != FIDO_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to enable HMAC-SECRET extension on FIDO2 credential: %s", sym_fido_strerr(r)); + "Failed to enable extensions on FIDO2 credential: %s", sym_fido_strerr(r)); r = sym_fido_cred_set_rp(c, rp_id, rp_name); if (r != FIDO_OK) @@ -829,7 +848,17 @@ int fido2_generate_hmac_hash( emoji_enabled() ? special_glyph(SPECIAL_GLYPH_TOUCH) : "", emoji_enabled() ? " " : ""); - r = sym_fido_dev_make_cred(d, c, NULL); + /* If we are using the user PIN, then we must pass that PIN to the get_assertion call below, or + * the authenticator will use the non-user-verification HMAC secret (which differs from the one when + * the PIN is passed). + * + * Rather than potentially trying and failing to create the credential, just collect the PIN first + * and then pass it to both the make_credential and the get_assertion operations. */ + if (FLAGS_SET(lock_with, FIDO2ENROLL_PIN)) + r = FIDO_ERR_PIN_REQUIRED; + else + r = sym_fido_dev_make_cred(d, c, NULL); + if (r == FIDO_ERR_PIN_REQUIRED) { if (!has_client_pin) @@ -838,8 +867,14 @@ int fido2_generate_hmac_hash( for (;;) { _cleanup_strv_free_erase_ char **pin = NULL; - - r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin); + AskPasswordRequest req = { + .message = "Please enter security token PIN:", + .icon = askpw_icon, + .keyring = "fido2-pin", + .credential = askpw_credential, + }; + + r = ask_password_auto(&req, USEC_INFINITY, /* flags= */ 0, &pin); if (r < 0) return log_error_errno(r, "Failed to acquire user PIN: %m"); diff --git a/src/shared/libfido2-util.h b/src/shared/libfido2-util.h index 4cfc95f..5e40b2a 100644 --- a/src/shared/libfido2-util.h +++ b/src/shared/libfido2-util.h @@ -17,53 +17,56 @@ typedef enum Fido2EnrollFlags { #if HAVE_LIBFIDO2 #include -extern int (*sym_fido_assert_allow_cred)(fido_assert_t *, const unsigned char *, size_t); -extern void (*sym_fido_assert_free)(fido_assert_t **); -extern size_t (*sym_fido_assert_hmac_secret_len)(const fido_assert_t *, size_t); -extern const unsigned char* (*sym_fido_assert_hmac_secret_ptr)(const fido_assert_t *, size_t); -extern fido_assert_t* (*sym_fido_assert_new)(void); -extern int (*sym_fido_assert_set_clientdata_hash)(fido_assert_t *, const unsigned char *, size_t); -extern int (*sym_fido_assert_set_extensions)(fido_assert_t *, int); -extern int (*sym_fido_assert_set_hmac_salt)(fido_assert_t *, const unsigned char *, size_t); -extern int (*sym_fido_assert_set_rp)(fido_assert_t *, const char *); -extern int (*sym_fido_assert_set_up)(fido_assert_t *, fido_opt_t); -extern int (*sym_fido_assert_set_uv)(fido_assert_t *, fido_opt_t); -extern size_t (*sym_fido_cbor_info_extensions_len)(const fido_cbor_info_t *); -extern char **(*sym_fido_cbor_info_extensions_ptr)(const fido_cbor_info_t *); -extern void (*sym_fido_cbor_info_free)(fido_cbor_info_t **); -extern fido_cbor_info_t* (*sym_fido_cbor_info_new)(void); -extern size_t (*sym_fido_cbor_info_options_len)(const fido_cbor_info_t *); -extern char** (*sym_fido_cbor_info_options_name_ptr)(const fido_cbor_info_t *); -extern const bool* (*sym_fido_cbor_info_options_value_ptr)(const fido_cbor_info_t *); -extern void (*sym_fido_cred_free)(fido_cred_t **); -extern size_t (*sym_fido_cred_id_len)(const fido_cred_t *); -extern const unsigned char* (*sym_fido_cred_id_ptr)(const fido_cred_t *); -extern fido_cred_t* (*sym_fido_cred_new)(void); -extern int (*sym_fido_cred_set_clientdata_hash)(fido_cred_t *, const unsigned char *, size_t); -extern int (*sym_fido_cred_set_extensions)(fido_cred_t *, int); -extern int (*sym_fido_cred_set_rk)(fido_cred_t *, fido_opt_t); -extern int (*sym_fido_cred_set_rp)(fido_cred_t *, const char *, const char *); -extern int (*sym_fido_cred_set_type)(fido_cred_t *, int); -extern int (*sym_fido_cred_set_user)(fido_cred_t *, const unsigned char *, size_t, const char *, const char *, const char *); -extern int (*sym_fido_cred_set_uv)(fido_cred_t *, fido_opt_t); -extern void (*sym_fido_dev_free)(fido_dev_t **); -extern int (*sym_fido_dev_get_assert)(fido_dev_t *, fido_assert_t *, const char *); -extern int (*sym_fido_dev_get_cbor_info)(fido_dev_t *, fido_cbor_info_t *); -extern void (*sym_fido_dev_info_free)(fido_dev_info_t **, size_t); -extern int (*sym_fido_dev_info_manifest)(fido_dev_info_t *, size_t, size_t *); -extern const char* (*sym_fido_dev_info_manufacturer_string)(const fido_dev_info_t *); -extern const char* (*sym_fido_dev_info_product_string)(const fido_dev_info_t *); -extern fido_dev_info_t* (*sym_fido_dev_info_new)(size_t); -extern const char* (*sym_fido_dev_info_path)(const fido_dev_info_t *); -extern const fido_dev_info_t* (*sym_fido_dev_info_ptr)(const fido_dev_info_t *, size_t); -extern bool (*sym_fido_dev_is_fido2)(const fido_dev_t *); -extern int (*sym_fido_dev_make_cred)(fido_dev_t *, fido_cred_t *, const char *); -extern fido_dev_t* (*sym_fido_dev_new)(void); -extern int (*sym_fido_dev_open)(fido_dev_t *, const char *); -extern int (*sym_fido_dev_close)(fido_dev_t *); -extern void (*sym_fido_init)(int); -extern void (*sym_fido_set_log_handler)(fido_log_handler_t *); -extern const char* (*sym_fido_strerr)(int); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(fido_assert_allow_cred); +DLSYM_PROTOTYPE(fido_assert_free); +DLSYM_PROTOTYPE(fido_assert_hmac_secret_len); +DLSYM_PROTOTYPE(fido_assert_hmac_secret_ptr); +DLSYM_PROTOTYPE(fido_assert_new); +DLSYM_PROTOTYPE(fido_assert_set_clientdata_hash); +DLSYM_PROTOTYPE(fido_assert_set_extensions); +DLSYM_PROTOTYPE(fido_assert_set_hmac_salt); +DLSYM_PROTOTYPE(fido_assert_set_rp); +DLSYM_PROTOTYPE(fido_assert_set_up); +DLSYM_PROTOTYPE(fido_assert_set_uv); +DLSYM_PROTOTYPE(fido_cbor_info_extensions_len); +DLSYM_PROTOTYPE(fido_cbor_info_extensions_ptr); +DLSYM_PROTOTYPE(fido_cbor_info_free); +DLSYM_PROTOTYPE(fido_cbor_info_new); +DLSYM_PROTOTYPE(fido_cbor_info_options_len); +DLSYM_PROTOTYPE(fido_cbor_info_options_name_ptr); +DLSYM_PROTOTYPE(fido_cbor_info_options_value_ptr); +DLSYM_PROTOTYPE(fido_cred_free); +DLSYM_PROTOTYPE(fido_cred_id_len); +DLSYM_PROTOTYPE(fido_cred_id_ptr); +DLSYM_PROTOTYPE(fido_cred_new); +DLSYM_PROTOTYPE(fido_cred_set_clientdata_hash); +DLSYM_PROTOTYPE(fido_cred_set_extensions); +DLSYM_PROTOTYPE(fido_cred_set_prot); +DLSYM_PROTOTYPE(fido_cred_set_rk); +DLSYM_PROTOTYPE(fido_cred_set_rp); +DLSYM_PROTOTYPE(fido_cred_set_type); +DLSYM_PROTOTYPE(fido_cred_set_user); +DLSYM_PROTOTYPE(fido_cred_set_uv); +DLSYM_PROTOTYPE(fido_dev_free); +DLSYM_PROTOTYPE(fido_dev_get_assert); +DLSYM_PROTOTYPE(fido_dev_get_cbor_info); +DLSYM_PROTOTYPE(fido_dev_info_free); +DLSYM_PROTOTYPE(fido_dev_info_manifest); +DLSYM_PROTOTYPE(fido_dev_info_manufacturer_string); +DLSYM_PROTOTYPE(fido_dev_info_product_string); +DLSYM_PROTOTYPE(fido_dev_info_new); +DLSYM_PROTOTYPE(fido_dev_info_path); +DLSYM_PROTOTYPE(fido_dev_info_ptr); +DLSYM_PROTOTYPE(fido_dev_is_fido2); +DLSYM_PROTOTYPE(fido_dev_make_cred); +DLSYM_PROTOTYPE(fido_dev_new); +DLSYM_PROTOTYPE(fido_dev_open); +DLSYM_PROTOTYPE(fido_dev_close); +DLSYM_PROTOTYPE(fido_init); +DLSYM_PROTOTYPE(fido_set_log_handler); +DLSYM_PROTOTYPE(fido_strerr); int dlopen_libfido2(void); @@ -109,7 +112,8 @@ int fido2_generate_hmac_hash( const char *user_name, const char *user_display_name, const char *user_icon, - const char *askpw_icon_name, + const char *askpw_icon, + const char *askpw_credential, Fido2EnrollFlags lock_with, int cred_alg, void **ret_cid, size_t *ret_cid_size, diff --git a/src/shared/local-addresses.c b/src/shared/local-addresses.c index a1577de..5d5435f 100644 --- a/src/shared/local-addresses.c +++ b/src/shared/local-addresses.c @@ -25,7 +25,11 @@ static int address_compare(const struct local_address *a, const struct local_add if (r != 0) return r; - r = CMP(a->metric, b->metric); + r = CMP(a->priority, b->priority); + if (r != 0) + return r; + + r = CMP(a->weight, b->weight); if (r != 0) return r; @@ -36,6 +40,17 @@ static int address_compare(const struct local_address *a, const struct local_add return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); } +bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle) { + assert(addresses || n_addresses == 0); + assert(needle); + + for (size_t i = 0; i < n_addresses; i++) + if (address_compare(addresses + i, needle) == 0) + return true; + + return false; +} + static void suppress_duplicates(struct local_address *list, size_t *n_list) { size_t old_size, new_size; @@ -58,6 +73,48 @@ static void suppress_duplicates(struct local_address *list, size_t *n_list) { *n_list = new_size; } +static int add_local_address_full( + struct local_address **list, + size_t *n_list, + int ifindex, + unsigned char scope, + uint32_t priority, + uint32_t weight, + int family, + const union in_addr_union *address) { + + assert(list); + assert(n_list); + assert(ifindex > 0); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (!GREEDY_REALLOC(*list, *n_list + 1)) + return -ENOMEM; + + (*list)[(*n_list)++] = (struct local_address) { + .ifindex = ifindex, + .scope = scope, + .priority = priority, + .weight = weight, + .family = family, + .address = *address, + }; + + return 1; +} + +static int add_local_address( + struct local_address **list, + size_t *n_list, + int ifindex, + unsigned char scope, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, scope, 0, 0, family, address); +} + int local_addresses( sd_netlink *context, int ifindex, @@ -91,8 +148,8 @@ int local_addresses( return r; for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - unsigned char flags; + union in_addr_union a; + unsigned char flags, scope; uint16_t type; int ifi, family; @@ -115,62 +172,58 @@ int local_addresses( r = sd_rtnl_message_addr_get_family(m, &family); if (r < 0) return r; + if (!IN_SET(family, AF_INET, AF_INET6)) + continue; if (af != AF_UNSPEC && af != family) continue; r = sd_rtnl_message_addr_get_flags(m, &flags); if (r < 0) return r; - if (flags & IFA_F_DEPRECATED) + if ((flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) != 0) continue; - if (!GREEDY_REALLOC0(list, n_list+1)) - return -ENOMEM; - - a = list + n_list; - - r = sd_rtnl_message_addr_get_scope(m, &a->scope); + r = sd_rtnl_message_addr_get_scope(m, &scope); if (r < 0) return r; - if (ifindex == 0 && IN_SET(a->scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) + if (ifindex == 0 && IN_SET(scope, RT_SCOPE_HOST, RT_SCOPE_NOWHERE)) continue; switch (family) { case AF_INET: - r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); + r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a.in); if (r < 0) { - r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); + r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a.in); if (r < 0) continue; } break; case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); + r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a.in6); if (r < 0) { - r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); + r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a.in6); if (r < 0) continue; } break; default: - continue; + assert_not_reached(); } - a->ifindex = ifi; - a->family = family; - - n_list++; + r = add_local_address(&list, &n_list, ifi, scope, family, &a); + if (r < 0) + return r; }; - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } @@ -178,27 +231,117 @@ int local_addresses( static int add_local_gateway( struct local_address **list, size_t *n_list, - int af, int ifindex, - uint32_t metric, - const RouteVia *via) { + uint32_t priority, + uint32_t weight, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, 0, priority, weight, family, address); +} + +static int parse_nexthop_one( + struct local_address **list, + size_t *n_list, + bool allow_via, + int family, + uint32_t priority, + const struct rtnexthop *rtnh) { + + bool has_gw = false; + int r; + + assert(rtnh); + + size_t len = rtnh->rtnh_len - sizeof(struct rtnexthop); + for (struct rtattr *attr = RTNH_DATA(rtnh); RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) + + switch (attr->rta_type) { + case RTA_GATEWAY: + if (has_gw) + return -EBADMSG; + + has_gw = true; + + if (attr->rta_len != RTA_LENGTH(FAMILY_ADDRESS_SIZE(family))) + return -EBADMSG; + + union in_addr_union a; + memcpy(&a, RTA_DATA(attr), FAMILY_ADDRESS_SIZE(family)); + r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, family, &a); + if (r < 0) + return r; + + break; + + case RTA_VIA: + if (has_gw) + return -EBADMSG; + + has_gw = true; + + if (!allow_via) + continue; + + if (family != AF_INET) + return -EBADMSG; /* RTA_VIA is only supported for IPv4 routes. */ + + if (attr->rta_len != RTA_LENGTH(sizeof(RouteVia))) + return -EBADMSG; + + RouteVia *via = RTA_DATA(attr); + if (via->family != AF_INET6) + return -EBADMSG; /* gateway address should be always IPv6. */ + + r = add_local_gateway(list, n_list, rtnh->rtnh_ifindex, priority, rtnh->rtnh_hops, via->family, + &(union in_addr_union) { .in6 = via->address.in6 }); + if (r < 0) + return r; + + break; + } + + return 0; +} + +static int parse_nexthops( + struct local_address **list, + size_t *n_list, + int ifindex, + bool allow_via, + int family, + uint32_t priority, + const struct rtnexthop *rtnh, + size_t size) { + + int r; assert(list); assert(n_list); - assert(via); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(rtnh || size == 0); - if (af != AF_UNSPEC && af != via->family) - return 0; + if (size < sizeof(struct rtnexthop)) + return -EBADMSG; - if (!GREEDY_REALLOC(*list, *n_list + 1)) - return -ENOMEM; + for (; size >= sizeof(struct rtnexthop); ) { + if (NLMSG_ALIGN(rtnh->rtnh_len) > size) + return -EBADMSG; - (*list)[(*n_list)++] = (struct local_address) { - .ifindex = ifindex, - .metric = metric, - .family = via->family, - .address = via->address, - }; + if (rtnh->rtnh_len < sizeof(struct rtnexthop)) + return -EBADMSG; + + if (ifindex > 0 && rtnh->rtnh_ifindex != ifindex) + goto next_nexthop; + + r = parse_nexthop_one(list, n_list, allow_via, family, priority, rtnh); + if (r < 0) + return r; + + next_nexthop: + size -= NLMSG_ALIGN(rtnh->rtnh_len); + rtnh = RTNH_NEXT(rtnh); + } return 0; } @@ -215,6 +358,12 @@ int local_gateways( size_t n_list = 0; int r; + /* The RTA_VIA attribute is used only for IPv4 routes with an IPv6 gateway. If IPv4 gateways are + * requested (af == AF_INET), then we do not return IPv6 gateway addresses. Similarly, if IPv6 + * gateways are requested (af == AF_INET6), then we do not return gateway addresses for IPv4 routes. + * So, the RTA_VIA attribute is only parsed when af == AF_UNSPEC. */ + bool allow_via = af == AF_UNSPEC; + if (context) rtnl = sd_netlink_ref(context); else { @@ -244,15 +393,10 @@ int local_gateways( return r; for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { - _cleanup_ordered_set_free_free_ OrderedSet *multipath_routes = NULL; - _cleanup_free_ void *rta_multipath = NULL; - union in_addr_union gateway; uint16_t type; unsigned char dst_len, src_len, table; - uint32_t ifi = 0, metric = 0; - size_t rta_len; + uint32_t ifi = 0, priority = 0; int family; - RouteVia via; r = sd_netlink_message_get_errno(m); if (r < 0) @@ -283,7 +427,7 @@ int local_gateways( if (table != RT_TABLE_MAIN) continue; - r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &metric); + r = sd_netlink_message_read_u32(m, RTA_PRIORITY, &priority); if (r < 0 && r != -ENODATA) return r; @@ -292,6 +436,8 @@ int local_gateways( return r; if (!IN_SET(family, AF_INET, AF_INET6)) continue; + if (af != AF_UNSPEC && af != family) + continue; r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); if (r < 0 && r != -ENODATA) @@ -302,64 +448,73 @@ int local_gateways( if (ifindex > 0 && (int) ifi != ifindex) continue; + union in_addr_union gateway; r = netlink_message_read_in_addr_union(m, RTA_GATEWAY, family, &gateway); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - via.family = family; - via.address = gateway; - r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); + r = add_local_gateway(&list, &n_list, ifi, priority, 0, family, &gateway); if (r < 0) return r; continue; } + if (!allow_via) + continue; + if (family != AF_INET) continue; + RouteVia via; r = sd_netlink_message_read(m, RTA_VIA, sizeof(via), &via); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - r = add_local_gateway(&list, &n_list, af, ifi, metric, &via); + if (via.family != AF_INET6) + return -EBADMSG; + + r = add_local_gateway(&list, &n_list, ifi, priority, 0, via.family, + &(union in_addr_union) { .in6 = via.address.in6 }); if (r < 0) return r; - - continue; } + + /* If the route has RTA_OIF, it does not have RTA_MULTIPATH. */ + continue; } + size_t rta_len; + _cleanup_free_ void *rta_multipath = NULL; r = sd_netlink_message_read_data(m, RTA_MULTIPATH, &rta_len, &rta_multipath); if (r < 0 && r != -ENODATA) return r; if (r >= 0) { - MultipathRoute *mr; - - r = rtattr_read_nexthop(rta_multipath, rta_len, family, &multipath_routes); + r = parse_nexthops(&list, &n_list, ifindex, allow_via, family, priority, rta_multipath, rta_len); if (r < 0) return r; - - ORDERED_SET_FOREACH(mr, multipath_routes) { - if (ifindex > 0 && mr->ifindex != ifindex) - continue; - - r = add_local_gateway(&list, &n_list, af, ifi, metric, &mr->gateway); - if (r < 0) - return r; - } } } - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } +static int add_local_outbound( + struct local_address **list, + size_t *n_list, + int ifindex, + int family, + const union in_addr_union *address) { + + return add_local_address_full(list, n_list, ifindex, 0, 0, 0, family, address); +} + int local_outbounds( sd_netlink *context, int ifindex, @@ -466,29 +621,20 @@ int local_outbounds( if (in4_addr_is_null(&sa.in.sin_addr)) /* Auto-binding didn't work. :-( */ continue; - if (!GREEDY_REALLOC(list, n_list+1)) - return -ENOMEM; - - list[n_list++] = (struct local_address) { - .family = gateways[i].family, - .ifindex = gateways[i].ifindex, - .address.in = sa.in.sin_addr, - }; - + r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family, + &(union in_addr_union) { .in = sa.in.sin_addr }); + if (r < 0) + return r; break; case AF_INET6: if (in6_addr_is_null(&sa.in6.sin6_addr)) continue; - if (!GREEDY_REALLOC(list, n_list+1)) - return -ENOMEM; - - list[n_list++] = (struct local_address) { - .family = gateways[i].family, - .ifindex = gateways[i].ifindex, - .address.in6 = sa.in6.sin6_addr, - }; + r = add_local_outbound(&list, &n_list, gateways[i].ifindex, gateways[i].family, + &(union in_addr_union) { .in6 = sa.in6.sin6_addr }); + if (r < 0) + return r; break; default: @@ -496,11 +642,11 @@ int local_outbounds( } } - if (ret) { - typesafe_qsort(list, n_list, address_compare); - suppress_duplicates(list, &n_list); + typesafe_qsort(list, n_list, address_compare); + suppress_duplicates(list, &n_list); + + if (ret) *ret = TAKE_PTR(list); - } return (int) n_list; } diff --git a/src/shared/local-addresses.h b/src/shared/local-addresses.h index 38a17d2..399d0c6 100644 --- a/src/shared/local-addresses.h +++ b/src/shared/local-addresses.h @@ -6,12 +6,16 @@ #include "in-addr-util.h" struct local_address { - int family, ifindex; + int ifindex; unsigned char scope; - uint32_t metric; + uint32_t priority; + uint32_t weight; + int family; union in_addr_union address; }; +bool has_local_address(const struct local_address *addresses, size_t n_addresses, const struct local_address *needle); + int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c index 0a31be3..c71c868 100644 --- a/src/shared/logs-show.c +++ b/src/shared/logs-show.c @@ -5,7 +5,6 @@ #include #include #include -#include #include #include @@ -27,11 +26,9 @@ #include "log.h" #include "logs-show.h" #include "macro.h" -#include "namespace-util.h" #include "output-mode.h" #include "parse-util.h" #include "pretty-print.h" -#include "process-util.h" #include "sparse-endian.h" #include "stdio-util.h" #include "string-table.h" @@ -367,39 +364,37 @@ static int output_timestamp_realtime( sd_journal *j, OutputMode mode, OutputFlags flags, - const dual_timestamp *display_ts) { + usec_t usec) { char buf[CONST_MAX(FORMAT_TIMESTAMP_MAX, 64U)]; - int r; assert(f); assert(j); - assert(display_ts); - if (!VALID_REALTIME(display_ts->realtime)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); + if (!VALID_REALTIME(usec)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available."); if (IN_SET(mode, OUTPUT_SHORT_FULL, OUTPUT_WITH_UNIT)) { const char *k; if (flags & OUTPUT_UTC) - k = format_timestamp_style(buf, sizeof(buf), display_ts->realtime, TIMESTAMP_UTC); + k = format_timestamp_style(buf, sizeof(buf), usec, TIMESTAMP_UTC); else - k = format_timestamp(buf, sizeof(buf), display_ts->realtime); + k = format_timestamp(buf, sizeof(buf), usec); if (!k) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format timestamp: %" PRIu64, display_ts->realtime); + "Failed to format timestamp: %" PRIu64, usec); } else { struct tm tm; time_t t; - t = (time_t) (display_ts->realtime / USEC_PER_SEC); + t = (time_t) (usec / USEC_PER_SEC); switch (mode) { case OUTPUT_SHORT_UNIX: - xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, display_ts->realtime % USEC_PER_SEC); + xsprintf(buf, "%10"PRI_TIME".%06"PRIu64, t, usec % USEC_PER_SEC); break; case OUTPUT_SHORT_ISO: @@ -407,13 +402,11 @@ static int output_timestamp_realtime( size_t tail = strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)); if (tail == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format ISO time"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format ISO time."); /* No usec in strftime, need to append */ if (mode == OUTPUT_SHORT_ISO_PRECISE) { - assert(ELEMENTSOF(buf) - tail >= 7); - snprintf(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, display_ts->realtime % USEC_PER_SEC); + assert_se(snprintf_ok(buf + tail, ELEMENTSOF(buf) - tail, ".%06"PRI_USEC, usec % USEC_PER_SEC)); tail += 7; } @@ -428,19 +421,12 @@ static int output_timestamp_realtime( if (strftime(buf, sizeof(buf), "%b %d %H:%M:%S", localtime_or_gmtime_r(&t, &tm, flags & OUTPUT_UTC)) <= 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format syslog time"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format syslog time."); if (mode == OUTPUT_SHORT_PRECISE) { - size_t k; - assert(sizeof(buf) > strlen(buf)); - k = sizeof(buf) - strlen(buf); - - r = snprintf(buf + strlen(buf), k, ".%06"PRIu64, display_ts->realtime % USEC_PER_SEC); - if (r <= 0 || (size_t) r >= k) /* too long? */ - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Failed to format precise time"); + if (!snprintf_ok(buf + strlen(buf), sizeof(buf) - strlen(buf), ".%06"PRIu64, usec % USEC_PER_SEC)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to format precise time."); } break; @@ -453,6 +439,77 @@ static int output_timestamp_realtime( return (int) strlen(buf); } +static void parse_display_realtime( + sd_journal *j, + const char *source_realtime, + const char *source_monotonic, + usec_t *ret) { + + usec_t t, s, u; + + assert(j); + assert(ret); + + /* First, try _SOURCE_REALTIME_TIMESTAMP. */ + if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) { + *ret = t; + return; + } + + /* Read realtime timestamp in the entry header. */ + if (sd_journal_get_realtime_usec(j, &t) < 0) { + *ret = USEC_INFINITY; + return; + } + + /* If _SOURCE_MONOTONIC_TIMESTAMP is provided, adjust the header timestamp. */ + if (source_monotonic && safe_atou64(source_monotonic, &s) >= 0 && VALID_MONOTONIC(s) && + sd_journal_get_monotonic_usec(j, &u, &(sd_id128_t) {}) >= 0) { + *ret = map_clock_usec_raw(t, u, s); + return; + } + + /* Otherwise, use the header timestamp as is. */ + *ret = t; +} + +static void parse_display_timestamp( + sd_journal *j, + const char *source_realtime, + const char *source_monotonic, + dual_timestamp *ret_display_ts, + sd_id128_t *ret_boot_id) { + + dual_timestamp header_ts = DUAL_TIMESTAMP_INFINITY, source_ts = DUAL_TIMESTAMP_INFINITY; + sd_id128_t boot_id = SD_ID128_NULL; + usec_t t; + + assert(j); + assert(ret_display_ts); + assert(ret_boot_id); + + if (source_realtime && safe_atou64(source_realtime, &t) >= 0 && VALID_REALTIME(t)) + source_ts.realtime = t; + + if (source_monotonic && safe_atou64(source_monotonic, &t) >= 0 && VALID_MONOTONIC(t)) + source_ts.monotonic = t; + + (void) sd_journal_get_realtime_usec(j, &header_ts.realtime); + (void) sd_journal_get_monotonic_usec(j, &header_ts.monotonic, &boot_id); + + /* Adjust timestamp if possible. */ + if (header_ts.realtime != USEC_INFINITY && header_ts.monotonic != USEC_INFINITY) { + if (source_ts.realtime == USEC_INFINITY && source_ts.monotonic != USEC_INFINITY) + source_ts.realtime = map_clock_usec_raw(header_ts.realtime, header_ts.monotonic, source_ts.monotonic); + else if (source_ts.realtime != USEC_INFINITY && source_ts.monotonic == USEC_INFINITY) + source_ts.monotonic = map_clock_usec_raw(header_ts.monotonic, header_ts.realtime, source_ts.realtime); + } + + ret_display_ts->realtime = source_ts.realtime != USEC_INFINITY ? source_ts.realtime : header_ts.realtime; + ret_display_ts->monotonic = source_ts.monotonic != USEC_INFINITY ? source_ts.monotonic : header_ts.monotonic; + *ret_boot_id = boot_id; +} + static int output_short( FILE *f, sd_journal *j, @@ -461,42 +518,43 @@ static int output_short( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */ + sd_id128_t *previous_boot_id) { /* in and out, used only when mode is OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA. */ int r; const void *data; size_t length, n = 0; _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *priority = NULL, *transport = NULL, - *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL; + *config_file = NULL, *unit = NULL, *user_unit = NULL, *documentation_url = NULL, + *realtime = NULL, *monotonic = NULL; size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, priority_len = 0, transport_len = 0, config_file_len = 0, unit_len = 0, user_unit_len = 0, documentation_url_len = 0; + dual_timestamp display_ts; + sd_id128_t boot_id; int p = LOG_INFO; bool ellipsized = false, audit; const ParseFieldVec fields[] = { - PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len), - PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len), - PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len), - PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len), - PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len), - PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len), - PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len), - PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len), - PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len), - PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len), - PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len), - PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), + PARSE_FIELD_VEC_ENTRY("_PID=", &pid, &pid_len ), + PARSE_FIELD_VEC_ENTRY("_COMM=", &comm, &comm_len ), + PARSE_FIELD_VEC_ENTRY("MESSAGE=", &message, &message_len ), + PARSE_FIELD_VEC_ENTRY("PRIORITY=", &priority, &priority_len ), + PARSE_FIELD_VEC_ENTRY("_TRANSPORT=", &transport, &transport_len ), + PARSE_FIELD_VEC_ENTRY("_HOSTNAME=", &hostname, &hostname_len ), + PARSE_FIELD_VEC_ENTRY("SYSLOG_PID=", &fake_pid, &fake_pid_len ), + PARSE_FIELD_VEC_ENTRY("SYSLOG_IDENTIFIER=", &identifier, &identifier_len ), + PARSE_FIELD_VEC_ENTRY("CONFIG_FILE=", &config_file, &config_file_len ), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_UNIT=", &unit, &unit_len ), + PARSE_FIELD_VEC_ENTRY("_SYSTEMD_USER_UNIT=", &user_unit, &user_unit_len ), + PARSE_FIELD_VEC_ENTRY("DOCUMENTATION=", &documentation_url, &documentation_url_len), + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL ), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL ), }; size_t highlight_shifted[] = {highlight ? highlight[0] : 0, highlight ? highlight[1] : 0}; assert(f); assert(j); - assert(display_ts); - assert(boot_id); assert(previous_display_ts); assert(previous_boot_id); @@ -523,6 +581,9 @@ static int output_short( return 0; } + if (identifier && set_contains(j->exclude_syslog_identifiers, identifier)) + return 0; + if (!(flags & OUTPUT_SHOW_ALL)) strip_tab_ansi(&message, &message_len, highlight_shifted); @@ -534,10 +595,14 @@ static int output_short( audit = streq_ptr(transport, "audit"); - if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) - r = output_timestamp_monotonic(f, mode, display_ts, boot_id, previous_display_ts, previous_boot_id); - else - r = output_timestamp_realtime(f, j, mode, flags, display_ts); + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) { + parse_display_timestamp(j, realtime, monotonic, &display_ts, &boot_id); + r = output_timestamp_monotonic(f, mode, &display_ts, &boot_id, previous_display_ts, previous_boot_id); + } else { + usec_t usec; + parse_display_realtime(j, realtime, monotonic, &usec); + r = output_timestamp_realtime(f, j, mode, flags, usec); + } if (r < 0) return r; n += r; @@ -658,9 +723,48 @@ static int output_short( if (flags & OUTPUT_CATALOG) (void) print_catalog(f, j); + if (IN_SET(mode, OUTPUT_SHORT_MONOTONIC, OUTPUT_SHORT_DELTA)) { + *previous_display_ts = display_ts; + *previous_boot_id = boot_id; + } + return ellipsized; } +static int get_display_realtime(sd_journal *j, usec_t *ret) { + const void *data; + _cleanup_free_ char *realtime = NULL, *monotonic = NULL; + size_t length; + const ParseFieldVec message_fields[] = { + PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, NULL), + PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, NULL), + }; + int r; + + assert(j); + assert(ret); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); + if (r < 0) + return r; + + if (realtime && monotonic) + break; + } + if (r < 0) + return r; + + (void) parse_display_realtime(j, realtime, monotonic, ret); + + /* Restart all data before */ + sd_journal_restart_data(j); + sd_journal_restart_unique(j); + sd_journal_restart_fields(j); + + return 0; +} + static int output_verbose( FILE *f, sd_journal *j, @@ -669,35 +773,38 @@ static int output_verbose( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ const void *data; size_t length; _cleanup_free_ char *cursor = NULL; char buf[FORMAT_TIMESTAMP_MAX + 7]; const char *timestamp; + usec_t usec; int r; assert(f); assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); - if (!VALID_REALTIME(display_ts->realtime)) + r = get_display_realtime(j, &usec); + if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) { + log_debug_errno(r, "Skipping message we can't read: %m"); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + if (!VALID_REALTIME(usec)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No valid realtime timestamp available"); r = sd_journal_get_cursor(j, &cursor); if (r < 0) return log_error_errno(r, "Failed to get cursor: %m"); - timestamp = format_timestamp_style(buf, sizeof buf, display_ts->realtime, + timestamp = format_timestamp_style(buf, sizeof buf, usec, flags & OUTPUT_UTC ? TIMESTAMP_US_UTC : TIMESTAMP_US); fprintf(f, "%s%s%s %s[%s]%s\n", timestamp && (flags & OUTPUT_COLOR) ? ANSI_UNDERLINE : "", @@ -786,10 +893,8 @@ static int output_export( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ sd_id128_t journal_boot_id, seqnum_id; _cleanup_free_ char *cursor = NULL; @@ -800,10 +905,6 @@ static int output_export( int r; assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); @@ -1055,10 +1156,8 @@ static int output_json( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ char usecbuf[CONST_MAX(DECIMAL_STR_MAX(usec_t), DECIMAL_STR_MAX(uint64_t))]; _cleanup_(json_variant_unrefp) JsonVariant *object = NULL; @@ -1073,10 +1172,6 @@ static int output_json( int r; assert(j); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); @@ -1238,20 +1333,14 @@ static int output_cat( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id) { + dual_timestamp *previous_display_ts, /* unused */ + sd_id128_t *previous_boot_id) { /* unused */ int r, prio = LOG_INFO; const char *field; assert(j); assert(f); - assert(display_ts); - assert(boot_id); - assert(previous_display_ts); - assert(previous_boot_id); (void) sd_journal_set_data_threshold(j, 0); @@ -1291,63 +1380,6 @@ static int output_cat( return 0; } -static int get_display_timestamp( - sd_journal *j, - dual_timestamp *ret_display_ts, - sd_id128_t *ret_boot_id) { - - const void *data; - _cleanup_free_ char *realtime = NULL, *monotonic = NULL; - size_t length = 0, realtime_len = 0, monotonic_len = 0; - const ParseFieldVec message_fields[] = { - PARSE_FIELD_VEC_ENTRY("_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len), - PARSE_FIELD_VEC_ENTRY("_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len), - }; - int r; - bool realtime_good = false, monotonic_good = false, boot_id_good = false; - - assert(j); - assert(ret_display_ts); - assert(ret_boot_id); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - r = parse_fieldv(data, length, message_fields, ELEMENTSOF(message_fields)); - if (r < 0) - return r; - - if (realtime && monotonic) - break; - } - if (r < 0) - return r; - - if (realtime) - realtime_good = safe_atou64(realtime, &ret_display_ts->realtime) >= 0; - if (!realtime_good || !VALID_REALTIME(ret_display_ts->realtime)) - realtime_good = sd_journal_get_realtime_usec(j, &ret_display_ts->realtime) >= 0; - if (!realtime_good) - ret_display_ts->realtime = USEC_INFINITY; - - if (monotonic) - monotonic_good = safe_atou64(monotonic, &ret_display_ts->monotonic) >= 0; - if (!monotonic_good || !VALID_MONOTONIC(ret_display_ts->monotonic)) - monotonic_good = boot_id_good = sd_journal_get_monotonic_usec(j, &ret_display_ts->monotonic, ret_boot_id) >= 0; - if (!monotonic_good) - ret_display_ts->monotonic = USEC_INFINITY; - - if (!boot_id_good) - boot_id_good = sd_journal_get_monotonic_usec(j, NULL, ret_boot_id) >= 0; - if (!boot_id_good) - *ret_boot_id = SD_ID128_NULL; - - /* Restart all data before */ - sd_journal_restart_data(j); - sd_journal_restart_unique(j); - sd_journal_restart_fields(j); - - return 0; -} - typedef int (*output_func_t)( FILE *f, sd_journal *j, @@ -1356,10 +1388,8 @@ typedef int (*output_func_t)( OutputFlags flags, const Set *output_fields, const size_t highlight[2], - const dual_timestamp *display_ts, - const sd_id128_t *boot_id, - const dual_timestamp *previous_display_ts, - const sd_id128_t *previous_boot_id); + dual_timestamp *previous_display_ts, + sd_id128_t *previous_boot_id); static output_func_t output_funcs[_OUTPUT_MODE_MAX] = { @@ -1393,8 +1423,6 @@ int show_journal_entry( dual_timestamp *previous_display_ts, sd_id128_t *previous_boot_id) { - dual_timestamp display_ts = DUAL_TIMESTAMP_NULL; - sd_id128_t boot_id = SD_ID128_NULL; int r; assert(mode >= 0); @@ -1405,14 +1433,6 @@ int show_journal_entry( if (n_columns <= 0) n_columns = columns(); - r = get_display_timestamp(j, &display_ts, &boot_id); - if (IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)) { - log_debug_errno(r, "Skipping message we can't read: %m"); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to get journal fields: %m"); - r = output_funcs[mode]( f, j, @@ -1421,15 +1441,9 @@ int show_journal_entry( flags, output_fields, highlight, - &display_ts, - &boot_id, previous_display_ts, previous_boot_id); - /* Store timestamp and boot ID for next iteration */ - *previous_display_ts = display_ts; - *previous_boot_id = boot_id; - if (ellipsized && r > 0) *ellipsized = true; @@ -1570,202 +1584,115 @@ int show_journal( } int add_matches_for_unit(sd_journal *j, const char *unit) { - const char *m1, *m2, *m3, *m4; int r; assert(j); assert(unit); - m1 = strjoina("_SYSTEMD_UNIT=", unit); - m2 = strjoina("COREDUMP_UNIT=", unit); - m3 = strjoina("UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); - - (void)( + (void) ( /* Look for messages from the service itself */ - (r = sd_journal_add_match(j, m1, 0)) || + (r = journal_add_match_pair(j, "_SYSTEMD_UNIT", unit)) || /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", SIZE_MAX)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "COREDUMP_UNIT", unit)) || /* Look for messages from PID 1 about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_PID=1", 0)) || - (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, "_PID=1", SIZE_MAX)) || + (r = journal_add_match_pair(j, "UNIT", unit)) || /* Look for messages from authorized daemons about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m4, 0)) + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_UNIT", unit)) ); - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_SLICE=", unit); - + if (r == 0 && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ - (void)( + (void) ( (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) - ); - } + (r = journal_add_match_pair(j, "_SYSTEMD_SLICE", unit)) + ); return r; } -int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { +int add_matches_for_user_unit(sd_journal *j, const char *unit) { + uid_t uid = getuid(); int r; - char *m1, *m2, *m3, *m4; - char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; assert(j); assert(unit); - m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); - m2 = strjoina("USER_UNIT=", unit); - m3 = strjoina("COREDUMP_USER_UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); - sprintf(muid, "_UID="UID_FMT, uid); - (void) ( /* Look for messages from the user service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || + (r = journal_add_match_pair(j, "_SYSTEMD_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || /* Look for messages from systemd about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || + (r = journal_add_match_pair(j, "USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || /* Look for coredumps of the service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = journal_add_match_pair(j, "COREDUMP_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) || /* Look for messages from authorized daemons about this service */ (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m4, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) + (r = journal_add_match_pair(j, "OBJECT_SYSTEMD_USER_UNIT", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) || + (r = sd_journal_add_match(j, "_UID=0", SIZE_MAX)) ); - if (r == 0 && endswith(unit, ".slice")) { - const char *m5; - - m5 = strjoina("_SYSTEMD_USER_SLICE=", unit); - + if (r == 0 && endswith(unit, ".slice")) /* Show all messages belonging to a slice */ - (void)( + (void) ( (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) || - (r = sd_journal_add_match(j, muid, 0)) - ); - } + (r = journal_add_match_pair(j, "_SYSTEMD_USER_SLICE", unit)) || + (r = journal_add_matchf(j, "_UID="UID_FMT, uid)) + ); return r; } -static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { - _cleanup_close_pair_ int pair[2] = EBADF_PAIR; - _cleanup_close_ int pidnsfd = -EBADF, mntnsfd = -EBADF, rootfd = -EBADF; - char buf[SD_ID128_UUID_STRING_MAX]; - pid_t pid, child; - ssize_t k; +int add_match_boot_id(sd_journal *j, sd_id128_t id) { int r; - assert(machine); - assert(boot_id); - - r = container_get_leader(machine, &pid); - if (r < 0) - return r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - r = namespace_fork("(sd-bootidns)", "(sd-bootid)", NULL, 0, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL, - pidnsfd, mntnsfd, -1, -1, rootfd, &child); - if (r < 0) - return r; - if (r == 0) { - int fd; - - pair[0] = safe_close(pair[0]); - - fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - _exit(EXIT_FAILURE); + assert(j); - r = loop_read_exact(fd, buf, 36, false); - safe_close(fd); + if (sd_id128_is_null(id)) { + r = sd_id128_get_boot(&id); if (r < 0) - _exit(EXIT_FAILURE); - - k = send(pair[1], buf, 36, MSG_NOSIGNAL); - if (k != 36) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); + return log_error_errno(r, "Failed to get boot ID: %m"); } - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate_and_check("(sd-bootidns)", child, 0); - if (r < 0) - return r; - if (r != EXIT_SUCCESS) - return -EIO; - - k = recv(pair[0], buf, 36, 0); - if (k != 36) - return -EIO; - - buf[36] = 0; - r = sd_id128_from_string(buf, boot_id); + r = journal_add_match_pair(j, "_BOOT_ID", SD_ID128_TO_STRING(id)); if (r < 0) - return r; + return log_error_errno(r, "Failed to add match: %m"); return 0; } -int add_match_boot_id(sd_journal *j, sd_id128_t id) { - char match[STRLEN("_BOOT_ID=") + SD_ID128_STRING_MAX]; - - assert(j); - assert(!sd_id128_is_null(id)); - - sd_id128_to_string(id, stpcpy(match, "_BOOT_ID=")); - return sd_journal_add_match(j, match, strlen(match)); -} - int add_match_this_boot(sd_journal *j, const char *machine) { sd_id128_t boot_id; int r; assert(j); - if (machine) { - r = get_boot_id_for_machine(machine, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); - } else { - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - } + r = id128_get_boot_for_machine(machine, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot ID%s%s: %m", + isempty(machine) ? "" : " of container ", strempty(machine)); r = add_match_boot_id(j, boot_id); if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); + return r; r = sd_journal_add_conjunction(j); if (r < 0) @@ -1782,7 +1709,6 @@ int show_journal_by_unit( unsigned n_columns, usec_t not_before, unsigned how_many, - uid_t uid, OutputFlags flags, int journal_open_flags, bool system_unit, @@ -1798,14 +1724,17 @@ int show_journal_by_unit( if (how_many <= 0) return 0; - r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE); + r = sd_journal_open_namespace(&j, log_namespace, + journal_open_flags | + SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE | + SD_JOURNAL_ASSUME_IMMUTABLE); if (r < 0) return log_error_errno(r, "Failed to open journal: %m"); if (system_unit) r = add_matches_for_unit(j, unit); else - r = add_matches_for_user_unit(j, unit, uid); + r = add_matches_for_user_unit(j, unit); if (r < 0) return log_error_errno(r, "Failed to add unit matches: %m"); @@ -1861,6 +1790,7 @@ static int discover_next_boot( if (r < 0) return r; if (r == 0) { + sd_journal_flush_matches(j); *ret = (BootId) {}; return 0; /* End of journal, yay. */ } @@ -1911,7 +1841,7 @@ static int discover_next_boot( goto try_again; } - r = sd_journal_get_realtime_usec(j, &boot.first_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &boot.last_usec : &boot.first_usec); if (r < 0) return r; @@ -1933,7 +1863,7 @@ static int discover_next_boot( goto try_again; } - r = sd_journal_get_realtime_usec(j, &boot.last_usec); + r = sd_journal_get_realtime_usec(j, advance_older ? &boot.first_usec : &boot.last_usec); if (r < 0) return r; @@ -1979,37 +1909,9 @@ static int discover_next_boot( } } -int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id) { - int r; - - assert(j); - assert(!sd_id128_is_null(boot_id)); - - sd_journal_flush_matches(j); - - r = add_match_boot_id(j, boot_id); - if (r < 0) - return r; - - r = sd_journal_seek_head(j); /* seek to oldest */ - if (r < 0) - return r; - - r = sd_journal_next(j); /* read the oldest entry */ - if (r < 0) - return r; - - /* At this point the read pointer is positioned at the oldest occurrence of the reference boot ID. - * After flushing the matches, one more invocation of _previous() will hence place us at the - * following entry, which must then have an older boot ID */ - - sd_journal_flush_matches(j); - return r > 0; -} - -int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { +int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret) { bool advance_older; - int r; + int r, offset_start; assert(j); assert(ret); @@ -2018,21 +1920,52 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { * (chronological) first boot in the journal. */ advance_older = offset <= 0; - if (advance_older) - r = sd_journal_seek_tail(j); /* seek to newest */ - else - r = sd_journal_seek_head(j); /* seek to oldest */ - if (r < 0) - return r; + sd_journal_flush_matches(j); - /* No sd_journal_next()/_previous() here. - * - * At this point the read pointer is positioned after the newest/before the oldest entry in the whole - * journal. The next invocation of _previous()/_next() will hence position us at the newest/oldest - * entry we have. */ + if (!sd_id128_is_null(boot_id)) { + r = add_match_boot_id(j, boot_id); + if (r < 0) + return r; - sd_id128_t boot_id = SD_ID128_NULL; - for (int off = !advance_older; ; off += advance_older ? -1 : 1) { + if (advance_older) + r = sd_journal_seek_head(j); /* seek to oldest */ + else + r = sd_journal_seek_tail(j); /* seek to newest */ + if (r < 0) + return r; + + r = sd_journal_step_one(j, advance_older); + if (r < 0) + return r; + if (r == 0) { + sd_journal_flush_matches(j); + *ret = SD_ID128_NULL; + return false; + } + if (offset == 0) { + /* If boot ID is specified without an offset, then let's short cut the loop below. */ + sd_journal_flush_matches(j); + *ret = boot_id; + return true; + } + + offset_start = advance_older ? -1 : 1; + } else { + if (advance_older) + r = sd_journal_seek_tail(j); /* seek to newest */ + else + r = sd_journal_seek_head(j); /* seek to oldest */ + if (r < 0) + return r; + + offset_start = advance_older ? 0 : 1; + } + + /* At this point the cursor is positioned at the newest/oldest entry of the reference boot ID if + * specified, or whole journal otherwise. The next invocation of _previous()/_next() will hence + * position us at the newest/oldest entry we have. */ + + for (int off = offset_start; ; off += advance_older ? -1 : 1) { BootId boot; r = discover_next_boot(j, boot_id, advance_older, &boot); @@ -2046,15 +1979,20 @@ int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret) { boot_id = boot.id; log_debug("Found boot ID %s by offset %i", SD_ID128_TO_STRING(boot_id), off); - if (off == offset) - break; + if (off == offset) { + *ret = boot_id; + return true; + } } - - *ret = boot_id; - return true; } -int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { +int journal_get_boots( + sd_journal *j, + bool advance_older, + size_t max_ids, + BootId **ret_boots, + size_t *ret_n_boots) { + _cleanup_free_ BootId *boots = NULL; size_t n_boots = 0; int r; @@ -2063,7 +2001,12 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { assert(ret_boots); assert(ret_n_boots); - r = sd_journal_seek_head(j); /* seek to oldest */ + sd_journal_flush_matches(j); + + if (advance_older) + r = sd_journal_seek_tail(j); /* seek to newest */ + else + r = sd_journal_seek_head(j); /* seek to oldest */ if (r < 0) return r; @@ -2076,7 +2019,10 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { for (;;) { BootId boot; - r = discover_next_boot(j, previous_boot_id, /* advance_older = */ false, &boot); + if (n_boots >= max_ids) + break; + + r = discover_next_boot(j, previous_boot_id, advance_older, &boot); if (r < 0) return r; if (r == 0) @@ -2090,10 +2036,8 @@ int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots) { * Exiting as otherwise this problem would cause an infinite loop. */ goto finish; - if (!GREEDY_REALLOC(boots, n_boots + 1)) + if (!GREEDY_REALLOC_APPEND(boots, n_boots, &boot, 1)) return -ENOMEM; - - boots[n_boots++] = boot; } finish: diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h index 3a8ce8b..7e7b2af 100644 --- a/src/shared/logs-show.h +++ b/src/shared/logs-show.h @@ -49,8 +49,7 @@ int add_matches_for_unit( int add_matches_for_user_unit( sd_journal *j, - const char *unit, - uid_t uid); + const char *unit); int show_journal_by_unit( FILE *f, @@ -60,7 +59,6 @@ int show_journal_by_unit( unsigned n_columns, usec_t not_before, unsigned how_many, - uid_t uid, OutputFlags flags, int journal_open_flags, bool system_unit, @@ -72,6 +70,10 @@ void json_escape( size_t l, OutputFlags flags); -int journal_find_boot_by_id(sd_journal *j, sd_id128_t boot_id); -int journal_find_boot_by_offset(sd_journal *j, int offset, sd_id128_t *ret); -int journal_get_boots(sd_journal *j, BootId **ret_boots, size_t *ret_n_boots); +int journal_find_boot(sd_journal *j, sd_id128_t boot_id, int offset, sd_id128_t *ret); +int journal_get_boots( + sd_journal *j, + bool advance_older, + size_t max_ids, + BootId **ret_boots, + size_t *ret_n_boots); diff --git a/src/shared/loop-util.c b/src/shared/loop-util.c index 6d55df7..e295eb2 100644 --- a/src/shared/loop-util.c +++ b/src/shared/loop-util.c @@ -57,21 +57,6 @@ static int loop_is_bound(int fd) { return true; /* bound! */ } -static int get_current_uevent_seqnum(uint64_t *ret) { - _cleanup_free_ char *p = NULL; - int r; - - r = read_full_virtual_file("/sys/kernel/uevent_seqnum", &p, NULL); - if (r < 0) - return log_debug_errno(r, "Failed to read current uevent sequence number: %m"); - - r = safe_atou64(strstrip(p), ret); - if (r < 0) - return log_debug_errno(r, "Failed to parse current uevent sequence number: %s", p); - - return 0; -} - static int open_lock_fd(int primary_fd, int operation) { _cleanup_close_ int lock_fd = -EBADF; @@ -149,8 +134,9 @@ static int loop_configure_verify(int fd, const struct loop_config *c) { * effect hence. And if not use classic LOOP_SET_STATUS64. */ uint64_t z; - if (ioctl(fd, BLKGETSIZE64, &z) < 0) - return -errno; + r = blockdev_get_device_size(fd, &z); + if (r < 0) + return r; if (z != c->info.lo_sizelimit) { log_debug("LOOP_CONFIGURE is broken, doesn't honour .info.lo_sizelimit. Falling back to LOOP_SET_STATUS64."); @@ -265,8 +251,7 @@ static int loop_configure( _cleanup_(cleanup_clear_loop_close) int loop_with_fd = -EBADF; /* This must be declared before lock_fd. */ _cleanup_close_ int fd = -EBADF, lock_fd = -EBADF; _cleanup_free_ char *node = NULL; - uint64_t diskseq = 0, seqnum = UINT64_MAX; - usec_t timestamp = USEC_INFINITY; + uint64_t diskseq = 0; dev_t devno; int r; @@ -326,18 +311,6 @@ static int loop_configure( "Removed partitions on the loopback block device."); if (!loop_configure_broken) { - /* Acquire uevent seqnum immediately before attaching the loopback device. This allows - * callers to ignore all uevents with a seqnum before this one, if they need to associate - * uevent with this attachment. Doing so isn't race-free though, as uevents that happen in - * the window between this reading of the seqnum, and the LOOP_CONFIGURE call might still be - * mistaken as originating from our attachment, even though might be caused by an earlier - * use. But doing this at least shortens the race window a bit. */ - r = get_current_uevent_seqnum(&seqnum); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m"); - - timestamp = now(CLOCK_MONOTONIC); - if (ioctl(fd, LOOP_CONFIGURE, c) < 0) { /* Do fallback only if LOOP_CONFIGURE is not supported, propagate all other * errors. Note that the kernel is weird: non-existing ioctls currently return EINVAL @@ -369,13 +342,6 @@ static int loop_configure( } if (loop_configure_broken) { - /* Let's read the seqnum again, to shorten the window. */ - r = get_current_uevent_seqnum(&seqnum); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get the current uevent seqnum: %m"); - - timestamp = now(CLOCK_MONOTONIC); - if (ioctl(fd, LOOP_SET_FD, c->fd) < 0) return log_device_debug_errno(dev, errno, "ioctl(LOOP_SET_FD) failed: %m"); @@ -404,6 +370,11 @@ static int loop_configure( assert_not_reached(); } + uint64_t device_size; + r = blockdev_get_device_size(loop_with_fd, &device_size); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to get loopback device size: %m"); + LoopDevice *d = new(LoopDevice, 1); if (!d) return log_oom_debug(); @@ -417,9 +388,9 @@ static int loop_configure( .devno = devno, .dev = TAKE_PTR(dev), .diskseq = diskseq, - .uevent_seqnum_not_before = seqnum, - .timestamp_not_before = timestamp, .sector_size = c->block_size, + .device_size = device_size, + .created = true, }; *ret = TAKE_PTR(d); @@ -944,6 +915,11 @@ int loop_device_open( if (r < 0) return r; + uint64_t device_size; + r = blockdev_get_device_size(fd, &device_size); + if (r < 0) + return r; + r = sd_device_get_devnum(dev, &devnum); if (r < 0) return r; @@ -973,9 +949,9 @@ int loop_device_open( .relinquished = true, /* It's not ours, don't try to destroy it when this object is freed */ .devno = devnum, .diskseq = diskseq, - .uevent_seqnum_not_before = UINT64_MAX, - .timestamp_not_before = USEC_INFINITY, .sector_size = sector_size, + .device_size = device_size, + .created = false, }; *ret = d; @@ -1057,8 +1033,9 @@ static int resize_partition(int partition_fd, uint64_t offset, uint64_t size) { return -EINVAL; current_offset *= 512U; - if (ioctl(partition_fd, BLKGETSIZE64, ¤t_size) < 0) - return -EINVAL; + r = blockdev_get_device_size(partition_fd, ¤t_size); + if (r < 0) + return r; if (size == UINT64_MAX && offset == UINT64_MAX) return 0; diff --git a/src/shared/loop-util.h b/src/shared/loop-util.h index d77c314..4f56317 100644 --- a/src/shared/loop-util.h +++ b/src/shared/loop-util.h @@ -22,12 +22,12 @@ struct LoopDevice { sd_device *dev; char *backing_file; bool relinquished; + bool created; /* If we created the device */ dev_t backing_devno; /* The backing file's dev_t */ ino_t backing_inode; /* The backing file's ino_t */ uint64_t diskseq; /* Block device sequence number, monothonically incremented by the kernel on create/attach, or 0 if we don't know */ - uint64_t uevent_seqnum_not_before; /* uevent sequm right before we attached the loopback device, or UINT64_MAX if we don't know */ - usec_t timestamp_not_before; /* CLOCK_MONOTONIC timestamp taken immediately before attaching the loopback device, or USEC_INFINITY if we don't know */ uint32_t sector_size; + uint64_t device_size; }; /* Returns true if LoopDevice object is not actually a loopback device but some other block device we just wrap */ diff --git a/src/shared/loopback-setup.c b/src/shared/loopback-setup.c index a02baf8..84d7234 100644 --- a/src/shared/loopback-setup.c +++ b/src/shared/loopback-setup.c @@ -60,7 +60,7 @@ static int start_loopback(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } @@ -95,7 +95,7 @@ static int add_ipv4_address(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } @@ -136,7 +136,7 @@ static int add_ipv6_address(sd_netlink *rtnl, struct state *s) { if (r < 0) return r; - s->n_messages ++; + s->n_messages++; return 0; } diff --git a/src/shared/machine-credential.c b/src/shared/machine-credential.c index 17f7afc..c1e76e4 100644 --- a/src/shared/machine-credential.c +++ b/src/shared/machine-credential.c @@ -19,76 +19,82 @@ static void machine_credential_done(MachineCredential *cred) { cred->size = 0; } -void machine_credential_free_all(MachineCredential *creds, size_t n) { - assert(creds || n == 0); +void machine_credential_context_done(MachineCredentialContext *ctx) { + assert(ctx); - FOREACH_ARRAY(cred, creds, n) + FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials) machine_credential_done(cred); - free(creds); + free(ctx->credentials); } -int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string) { - _cleanup_free_ char *word = NULL, *data = NULL; +bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id) { + assert(ctx); + assert(id); + + FOREACH_ARRAY(cred, ctx->credentials, ctx->n_credentials) + if (streq(cred->id, id)) + return true; + + return false; +} + +int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str) { + _cleanup_(machine_credential_done) MachineCredential cred = {}; ssize_t l; int r; - const char *p = ASSERT_PTR(cred_string); - assert(credentials && n_credentials); - assert(*credentials || *n_credentials == 0); + assert(ctx); - r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + const char *p = ASSERT_PTR(cred_str); + + r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return log_error_errno(r, "Failed to parse --set-credential= parameter: %m"); if (r == 0 || !p) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", cred_string); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Missing value for --set-credential=: %s", cred_str); - if (!credential_name_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word); + if (!credential_name_valid(cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id); - FOREACH_ARRAY(cred, *credentials, *n_credentials) - if (streq(cred->id, word)) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word); + if (machine_credentials_contains(ctx, cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id); - l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data); + l = cunescape(p, UNESCAPE_ACCEPT_NUL, &cred.data); if (l < 0) return log_error_errno(l, "Failed to unescape credential data: %s", p); + cred.size = l; - if (!GREEDY_REALLOC(*credentials, *n_credentials + 1)) + if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1)) return log_oom(); - (*credentials)[(*n_credentials)++] = (MachineCredential) { - .id = TAKE_PTR(word), - .data = TAKE_PTR(data), - .size = l, - }; + ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred); return 0; } -int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path) { +int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path) { + _cleanup_(machine_credential_done) MachineCredential cred = {}; + _cleanup_free_ char *path_alloc = NULL; ReadFullFileFlags flags = READ_FULL_FILE_SECURE; - _cleanup_(erase_and_freep) char *data = NULL; - _cleanup_free_ char *word = NULL, *j = NULL; - const char *p = ASSERT_PTR(cred_path); - size_t size; int r; - assert(credentials && n_credentials); - assert(*credentials || *n_credentials == 0); + assert(ctx); + + const char *p = ASSERT_PTR(cred_path); - r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + r = extract_first_word(&p, &cred.id, ":", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) return log_error_errno(r, "Failed to parse --load-credential= parameter: %m"); if (r == 0 || !p) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --load-credential=: %s", cred_path); - if (!credential_name_valid(word)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word); + if (!credential_name_valid(cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", cred.id); - FOREACH_ARRAY(cred, *credentials, *n_credentials) - if (streq(cred->id, word)) - return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word); + if (machine_credentials_contains(ctx, cred.id)) + return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", cred.id); if (is_path(p) && path_is_valid(p)) flags |= READ_FULL_FILE_CONNECT_SOCKET; @@ -97,31 +103,29 @@ int machine_credential_load(MachineCredential **credentials, size_t *n_credentia r = get_credentials_dir(&e); if (r < 0) - return log_error_errno(r, "Credential not available (no credentials passed at all): %s", word); + return log_error_errno(r, + "Credential not available (no credentials passed at all): %s", cred.id); - j = path_join(e, p); - if (!j) + path_alloc = path_join(e, p); + if (!path_alloc) return log_oom(); - p = j; + p = path_alloc; } else - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential source appears to be neither a valid path nor a credential name: %s", p); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Credential source appears to be neither a valid path nor a credential name: %s", p); r = read_full_file_full(AT_FDCWD, p, UINT64_MAX, SIZE_MAX, flags, NULL, - &data, &size); + &cred.data, &cred.size); if (r < 0) return log_error_errno(r, "Failed to read credential '%s': %m", p); - if (!GREEDY_REALLOC(*credentials, *n_credentials + 1)) + if (!GREEDY_REALLOC(ctx->credentials, ctx->n_credentials + 1)) return log_oom(); - (*credentials)[(*n_credentials)++] = (MachineCredential) { - .id = TAKE_PTR(word), - .data = TAKE_PTR(data), - .size = size, - }; + ctx->credentials[ctx->n_credentials++] = TAKE_STRUCT(cred); return 0; } diff --git a/src/shared/machine-credential.h b/src/shared/machine-credential.h index c9044a2..182f077 100644 --- a/src/shared/machine-credential.h +++ b/src/shared/machine-credential.h @@ -5,10 +5,18 @@ typedef struct MachineCredential { char *id; - void *data; + char *data; size_t size; } MachineCredential; -void machine_credential_free_all(MachineCredential *creds, size_t n); -int machine_credential_set(MachineCredential **credentials, size_t *n_credentials, const char *cred_string); -int machine_credential_load(MachineCredential **credentials, size_t *n_credentials, const char *cred_path); +typedef struct MachineCredentialContext { + MachineCredential *credentials; + size_t n_credentials; +} MachineCredentialContext; + +void machine_credential_context_done(MachineCredentialContext *ctx); + +bool machine_credentials_contains(const MachineCredentialContext *ctx, const char *id); + +int machine_credential_set(MachineCredentialContext *ctx, const char *cred_str); +int machine_credential_load(MachineCredentialContext *ctx, const char *cred_path); diff --git a/src/shared/machine-id-setup.c b/src/shared/machine-id-setup.c index 3efba03..1a63794 100644 --- a/src/shared/machine-id-setup.c +++ b/src/shared/machine-id-setup.c @@ -5,6 +5,7 @@ #include #include +#include "sd-daemon.h" #include "sd-id128.h" #include "alloc-util.h" @@ -12,6 +13,7 @@ #include "creds-util.h" #include "fd-util.h" #include "id128-util.h" +#include "initrd-util.h" #include "io-util.h" #include "log.h" #include "machine-id-setup.h" @@ -46,13 +48,22 @@ static int acquire_machine_id_from_credential(sd_id128_t *ret) { return 0; } -static int generate_machine_id(const char *root, sd_id128_t *ret) { +static int acquire_machine_id(const char *root, sd_id128_t *ret) { _cleanup_close_ int fd = -EBADF; int r; assert(ret); - /* First, try reading the D-Bus machine id, unless it is a symlink */ + /* First, try reading the machine ID from /run/machine-id, which may not be mounted on + * /etc/machine-id yet. This is important on switching root especially on soft-reboot, Otherwise, + * machine ID may be changed after the transition. */ + if (isempty(root) && running_in_chroot() <= 0 && + id128_read("/run/machine-id", ID128_FORMAT_PLAIN, ret) >= 0) { + log_info("Reusing machine ID stored in /run/machine-id."); + return 1; /* Indicate that the machine ID is reused. */ + } + + /* Then, try reading the D-Bus machine id, unless it is a symlink */ 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."); @@ -61,9 +72,8 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) { if (isempty(root) && running_in_chroot() <= 0) { /* Let's use a system credential for the machine ID if we can */ - r = acquire_machine_id_from_credential(ret); - if (r >= 0) - return r; + if (acquire_machine_id_from_credential(ret) >= 0) + return 0; /* If that didn't work, see if we are running in a container, * and a machine ID was passed in via $container_uuid the way @@ -103,7 +113,7 @@ static int generate_machine_id(const char *root, sd_id128_t *ret) { int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_id, sd_id128_t *ret) { const char *etc_machine_id, *run_machine_id; _cleanup_close_ int fd = -EBADF; - bool writable; + bool writable, write_run_machine_id = true; int r; etc_machine_id = prefix_roota(root, "/etc/machine-id"); @@ -141,13 +151,14 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ if (sd_id128_is_null(machine_id)) { /* Try to read any existing machine ID */ - if (id128_read_fd(fd, ID128_FORMAT_PLAIN, ret) >= 0) - return 0; + if (id128_read_fd(fd, ID128_FORMAT_PLAIN, &machine_id) >= 0) + goto finish; - /* Hmm, so, the id currently stored is not useful, then let's generate one */ - r = generate_machine_id(root, &machine_id); + /* Hmm, so, the id currently stored is not useful, then let's acquire one. */ + r = acquire_machine_id(root, &machine_id); if (r < 0) return r; + write_run_machine_id = !r; } if (writable) { @@ -185,11 +196,13 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ run_machine_id = prefix_roota(root, "/run/machine-id"); - WITH_UMASK(0022) - r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id); - if (r < 0) { - (void) unlink(run_machine_id); - return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + if (write_run_machine_id) { + WITH_UMASK(0022) + r = id128_write(run_machine_id, ID128_FORMAT_PLAIN, machine_id); + if (r < 0) { + (void) unlink(run_machine_id); + return log_error_errno(r, "Cannot write %s: %m", run_machine_id); + } } /* And now, let's mount it over */ @@ -207,6 +220,9 @@ int machine_id_setup(const char *root, bool force_transient, sd_id128_t machine_ return r; finish: + if (!in_initrd()) + (void) sd_notifyf(/* unset_environment= */ false, "X_SYSTEMD_MACHINE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(machine_id)); + if (ret) *ret = machine_id; @@ -237,7 +253,7 @@ int machine_id_commit(const char *root) { etc_machine_id = prefix_roota(root, "/etc/machine-id"); - r = path_is_mount_point(etc_machine_id, NULL, 0); + r = path_is_mount_point(etc_machine_id); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id); if (r == 0) { @@ -265,7 +281,12 @@ int machine_id_commit(const char *root) { fd = safe_close(fd); /* Store current mount namespace */ - r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); + r = namespace_open(0, + /* ret_pidns_fd = */ NULL, + &initial_mntns_fd, + /* ret_netns_fd = */ NULL, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); if (r < 0) return log_error_errno(r, "Can't fetch current mount namespace: %m"); @@ -284,7 +305,11 @@ int machine_id_commit(const char *root) { return log_error_errno(r, "Cannot write %s. This is mandatory to get a persistent machine ID: %m", etc_machine_id); /* Return to initial namespace and proceed a lazy tmpfs unmount */ - r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); + r = namespace_enter(/* pidns_fd = */ -EBADF, + initial_mntns_fd, + /* netns_fd = */ -EBADF, + /* userns_fd = */ -EBADF, + /* root_fd = */ -EBADF); if (r < 0) return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); diff --git a/src/shared/main-func.h b/src/shared/main-func.h index 3f6b6a8..d0689b4 100644 --- a/src/shared/main-func.h +++ b/src/shared/main-func.h @@ -3,16 +3,22 @@ #include +#if HAVE_VALGRIND_VALGRIND_H +# include +#endif + #include "sd-daemon.h" #include "argv-util.h" +#include "hashmap.h" #include "pager.h" #include "selinux-util.h" +#include "signal-util.h" #include "spawn-ask-password-agent.h" #include "spawn-polkit-agent.h" #include "static-destruct.h" -#define _DEFINE_MAIN_FUNCTION(intro, impl, ret) \ +#define _DEFINE_MAIN_FUNCTION(intro, impl, result_to_exit_status, result_to_return_value) \ int main(int argc, char *argv[]) { \ int r; \ assert_se(argc > 0 && !isempty(argv[0])); \ @@ -21,22 +27,55 @@ r = impl; \ if (r < 0) \ (void) sd_notifyf(0, "ERRNO=%i", -r); \ - (void) sd_notifyf(0, "EXIT_STATUS=%i", ret); \ + (void) sd_notifyf(0, "EXIT_STATUS=%i", \ + result_to_exit_status(r)); \ ask_password_agent_close(); \ polkit_agent_close(); \ pager_close(); \ mac_selinux_finish(); \ static_destruct(); \ - return ret; \ + return result_to_return_value(r); \ } +static inline int exit_failure_if_negative(int result) { + return result < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} + /* Negative return values from impl are mapped to EXIT_FAILURE, and * everything else means success! */ #define DEFINE_MAIN_FUNCTION(impl) \ - _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : EXIT_SUCCESS) + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, exit_failure_if_negative) + +static inline int exit_failure_if_nonzero(int result) { + return result < 0 ? EXIT_FAILURE : result; +} /* Zero is mapped to EXIT_SUCCESS, negative values are mapped to EXIT_FAILURE, * and positive values are propagated. * Note: "true" means failure! */ #define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(impl) \ - _DEFINE_MAIN_FUNCTION(,impl(argc, argv), r < 0 ? EXIT_FAILURE : r) + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_nonzero, exit_failure_if_nonzero) + +static inline int raise_or_exit_status(int ret) { + if (ret < 0) + return EXIT_FAILURE; + if (ret == 0) + return EXIT_SUCCESS; + if (!SIGNAL_VALID(ret)) + return EXIT_FAILURE; + +#if HAVE_VALGRIND_VALGRIND_H + /* If raise() below succeeds, the destructor cleanup_pools() in hashmap.c will never called. */ + if (RUNNING_ON_VALGRIND) + hashmap_trim_pools(); +#endif + + (void) raise(ret); + /* exit with failure if raise() does not immediately abort the program. */ + return EXIT_FAILURE; +} + +/* Negative return values from impl are mapped to EXIT_FAILURE, zero is mapped to EXIT_SUCCESS, + * and raise if a positive signal is returned from impl. */ +#define DEFINE_MAIN_FUNCTION_WITH_POSITIVE_SIGNAL(impl) \ + _DEFINE_MAIN_FUNCTION(,impl(argc, argv), exit_failure_if_negative, raise_or_exit_status) diff --git a/src/shared/meson.build b/src/shared/meson.build index b24a541..c5106d8 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -39,6 +39,7 @@ shared_sources = files( 'chown-recursive.c', 'clean-ipc.c', 'clock-util.c', + 'color-util.c', 'common-signal.c', 'compare-operator.c', 'condition.c', @@ -54,7 +55,6 @@ shared_sources = files( 'device-nodes.c', 'discover-image.c', 'dissect-image.c', - 'dlfcn-util.c', 'dm-util.c', 'dns-domain.c', 'dropin.c', @@ -98,10 +98,11 @@ shared_sources = files( 'journal-util.c', 'json.c', 'kbd-util.c', + 'kernel-config.c', 'kernel-image.c', - 'keyring-util.c', 'killall.c', 'label-util.c', + 'libarchive-util.c', 'libcrypt-util.c', 'libfido2-util.c', 'libmount-util.c', @@ -117,6 +118,7 @@ shared_sources = files( 'macvlan-util.c', 'mkdir-label.c', 'mkfs-util.c', + 'module-util.c', 'mount-setup.c', 'mount-util.c', 'net-condition.c', @@ -124,6 +126,7 @@ shared_sources = files( 'netif-sriov.c', 'netif-util.c', 'nsflags.c', + 'nsresource.c', 'numa-util.c', 'open-file.c', 'openssl-util.c', @@ -139,6 +142,7 @@ shared_sources = files( 'pkcs11-util.c', 'plymouth-util.c', 'pretty-print.c', + 'capsule-util.c', 'ptyfwd.c', 'qrcode-util.c', 'quota-util.c', @@ -172,11 +176,19 @@ shared_sources = files( 'varlink.c', 'varlink-idl.c', 'varlink-io.systemd.c', + 'varlink-io.systemd.BootControl.c', + 'varlink-io.systemd.Credentials.c', + 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.ManagedOOM.c', + 'varlink-io.systemd.MountFileSystem.c', + 'varlink-io.systemd.NamespaceResource.c', + 'varlink-io.systemd.Network.c', 'varlink-io.systemd.PCRExtend.c', - 'varlink-io.systemd.Resolve.Monitor.c', + 'varlink-io.systemd.PCRLock.c', 'varlink-io.systemd.Resolve.c', + 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.UserDatabase.c', 'varlink-io.systemd.oom.c', 'varlink-io.systemd.service.c', @@ -186,6 +198,7 @@ shared_sources = files( 'verbs.c', 'vlan-util.c', 'volatile-util.c', + 'vpick.c', 'wall.c', 'watchdog.c', 'web-util.c', @@ -194,9 +207,7 @@ shared_sources = files( ) if get_option('tests') != 'false' - shared_sources += files( - 'tests.c', - ) + shared_sources += files('tests.c') endif generate_syscall_list = find_program('generate-syscall-list.py') @@ -210,9 +221,7 @@ syscall_list_h = custom_target( capture : true) if conf.get('HAVE_ACL') == 1 - shared_sources += files( - 'devnode-acl.c', - ) + shared_sources += files('devnode-acl.c') endif if conf.get('ENABLE_UTMP') == 1 @@ -229,19 +238,11 @@ if conf.get('HAVE_LIBIPTC') == 1 endif if conf.get('HAVE_LIBBPF') == 1 - shared_sources += files( - 'bpf-link.c', - ) -endif - -if conf.get('HAVE_KMOD') == 1 - shared_sources += files('module-util.c') + shared_sources += files('bpf-link.c') endif if conf.get('HAVE_PAM') == 1 - shared_sources += files( - 'pam-util.c', - ) + shared_sources += files('pam-util.c') endif if conf.get('ENABLE_NSCD') == 1 @@ -252,6 +253,10 @@ if conf.get('HAVE_LIBFIDO2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 shared_sources += files('cryptsetup-fido2.c') endif +if conf.get('HAVE_TPM2') == 1 and conf.get('HAVE_LIBCRYPTSETUP') == 1 + shared_sources += files('cryptsetup-tpm2.c') +endif + generate_ip_protocol_list = find_program('generate-ip-protocol-list.sh') ip_protocol_list_txt = custom_target( 'ip-protocol-list.txt', @@ -320,8 +325,8 @@ libshared_deps = [threads, libdl, libgcrypt, libiptc_cflags, - libkmod, - liblz4, + libkmod_cflags, + liblz4_cflags, libmount, libopenssl, libp11kit_cflags, @@ -330,8 +335,8 @@ libshared_deps = [threads, libseccomp, libselinux, libxenctrl_cflags, - libxz, - libzstd] + libxz_cflags, + libzstd_cflags] libshared_sym_path = meson.current_source_dir() / 'libshared.sym' libshared_build_dir = meson.current_build_dir() @@ -354,16 +359,13 @@ libshared = shared_library( link_depends : libshared_sym_path, link_whole : [libshared_static, libbasic, - libbasic_gcrypt, libsystemd_static], dependencies : [libshared_deps, userspace], install : true, install_dir : pkglibdir) -shared_fdisk_sources = files( - 'fdisk-util.c', -) +shared_fdisk_sources = files('fdisk-util.c') libshared_fdisk = static_library( 'shared-fdisk', diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 4e58b6e..4d44012 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -104,7 +104,6 @@ static int mangle_fat_label(const char *s, char **ret) { static int do_mcopy(const char *node, const char *root) { _cleanup_free_ char *mcopy = NULL; _cleanup_strv_free_ char **argv = NULL; - _cleanup_close_ int rfd = -EBADF; _cleanup_free_ DirectoryEntries *de = NULL; int r; @@ -128,11 +127,7 @@ static int do_mcopy(const char *node, const char *root) { /* mcopy copies the top level directory instead of everything in it so we have to pass all * the subdirectories to mcopy instead to end up with the correct directory structure. */ - rfd = open(root, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (rfd < 0) - return log_error_errno(errno, "Failed to open directory '%s': %m", root); - - r = readdir_all(rfd, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); + r = readdir_all_at(AT_FDCWD, root, RECURSE_DIR_SORT|RECURSE_DIR_ENSURE_TYPE, &de); if (r < 0) return log_error_errno(r, "Failed to read '%s' contents: %m", root); @@ -430,7 +425,7 @@ int make_filesystem( "-T", "default", node); - if (root && strv_extend_strv(&argv, STRV_MAKE("-d", root), false) < 0) + if (root && strv_extend_many(&argv, "-d", root) < 0) return log_oom(); if (quiet && strv_extend(&argv, "-q") < 0) @@ -455,7 +450,7 @@ int make_filesystem( if (!discard && strv_extend(&argv, "--nodiscard") < 0) return log_oom(); - if (root && strv_extend_strv(&argv, STRV_MAKE("-r", root), false) < 0) + if (root && strv_extend_many(&argv, "-r", root) < 0) return log_oom(); if (quiet && strv_extend(&argv, "-q") < 0) @@ -519,7 +514,7 @@ int make_filesystem( if (!protofile_with_opt) return -ENOMEM; - if (strv_extend_strv(&argv, STRV_MAKE("-p", protofile_with_opt), false) < 0) + if (strv_extend_many(&argv, "-p", protofile_with_opt) < 0) return log_oom(); } diff --git a/src/shared/module-util.c b/src/shared/module-util.c index 951701d..fa1e0f8 100644 --- a/src/shared/module-util.c +++ b/src/shared/module-util.c @@ -6,8 +6,52 @@ #include "proc-cmdline.h" #include "strv.h" +#if HAVE_KMOD + +static void *libkmod_dl = NULL; + +DLSYM_FUNCTION(kmod_list_next); +DLSYM_FUNCTION(kmod_load_resources); +DLSYM_FUNCTION(kmod_module_get_initstate); +DLSYM_FUNCTION(kmod_module_get_module); +DLSYM_FUNCTION(kmod_module_get_name); +DLSYM_FUNCTION(kmod_module_new_from_lookup); +DLSYM_FUNCTION(kmod_module_probe_insert_module); +DLSYM_FUNCTION(kmod_module_unref); +DLSYM_FUNCTION(kmod_module_unref_list); +DLSYM_FUNCTION(kmod_new); +DLSYM_FUNCTION(kmod_set_log_fn); +DLSYM_FUNCTION(kmod_unref); +DLSYM_FUNCTION(kmod_validate_resources); + +int dlopen_libkmod(void) { + ELF_NOTE_DLOPEN("kmod", + "Support for loading kernel modules", + ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, + "libkmod.so.2"); + + return dlopen_many_sym_or_warn( + &libkmod_dl, + "libkmod.so.2", + LOG_DEBUG, + DLSYM_ARG(kmod_list_next), + DLSYM_ARG(kmod_load_resources), + DLSYM_ARG(kmod_module_get_initstate), + DLSYM_ARG(kmod_module_get_module), + DLSYM_ARG(kmod_module_get_name), + DLSYM_ARG(kmod_module_new_from_lookup), + DLSYM_ARG(kmod_module_probe_insert_module), + DLSYM_ARG(kmod_module_unref), + DLSYM_ARG(kmod_module_unref_list), + DLSYM_ARG(kmod_new), + DLSYM_ARG(kmod_set_log_fn), + DLSYM_ARG(kmod_unref), + DLSYM_ARG(kmod_validate_resources)); +} + static int denylist_modules(const char *p, char ***denylist) { _cleanup_strv_free_ char **k = NULL; + int r; assert(p); assert(denylist); @@ -16,8 +60,9 @@ static int denylist_modules(const char *p, char ***denylist) { if (!k) return -ENOMEM; - if (strv_extend_strv(denylist, k, true) < 0) - return -ENOMEM; + r = strv_extend_strv(denylist, k, /* filter_duplicates= */ true); + if (r < 0) + return r; return 0; } @@ -39,19 +84,21 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat } int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) { - const int probe_flags = KMOD_PROBE_APPLY_BLACKLIST; - struct kmod_list *itr; - _cleanup_(kmod_module_unref_listp) struct kmod_list *modlist = NULL; + _cleanup_(sym_kmod_module_unref_listp) struct kmod_list *modlist = NULL; _cleanup_strv_free_ char **denylist = NULL; bool denylist_parsed = false; + struct kmod_list *itr; int r; + assert(ctx); + assert(module); + /* verbose==true means we should log at non-debug level if we * fail to find or load the module. */ log_debug("Loading module: %s", module); - r = kmod_module_new_from_lookup(ctx, module, &modlist); + r = sym_kmod_module_new_from_lookup(ctx, module, &modlist); if (r < 0) return log_full_errno(verbose ? LOG_ERR : LOG_DEBUG, r, "Failed to look up module alias '%s': %m", module); @@ -61,32 +108,37 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) SYNTHETIC_ERRNO(ENOENT), "Failed to find module '%s'", module); - kmod_list_foreach(itr, modlist) { - _cleanup_(kmod_module_unrefp) struct kmod_module *mod = NULL; + sym_kmod_list_foreach(itr, modlist) { + _cleanup_(sym_kmod_module_unrefp) struct kmod_module *mod = NULL; int state, err; - mod = kmod_module_get_module(itr); - state = kmod_module_get_initstate(mod); + mod = sym_kmod_module_get_module(itr); + state = sym_kmod_module_get_initstate(mod); switch (state) { case KMOD_MODULE_BUILTIN: log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is built in", kmod_module_get_name(mod)); + "Module '%s' is built in", sym_kmod_module_get_name(mod)); break; case KMOD_MODULE_LIVE: - log_debug("Module '%s' is already loaded", kmod_module_get_name(mod)); + log_debug("Module '%s' is already loaded", sym_kmod_module_get_name(mod)); break; default: - err = kmod_module_probe_insert_module(mod, probe_flags, - NULL, NULL, NULL, NULL); + err = sym_kmod_module_probe_insert_module( + mod, + KMOD_PROBE_APPLY_BLACKLIST, + /* extra_options= */ NULL, + /* run_install= */ NULL, + /* data= */ NULL, + /* print_action= */ NULL); if (err == 0) log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Inserted module '%s'", kmod_module_get_name(mod)); + "Inserted module '%s'", sym_kmod_module_get_name(mod)); else if (err == KMOD_PROBE_APPLY_BLACKLIST) log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is deny-listed (by kmod)", kmod_module_get_name(mod)); + "Module '%s' is deny-listed (by kmod)", sym_kmod_module_get_name(mod)); else { assert(err < 0); @@ -100,9 +152,9 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) denylist_parsed = true; } - if (strv_contains(denylist, kmod_module_get_name(mod))) { + if (strv_contains(denylist, sym_kmod_module_get_name(mod))) { log_full(verbose ? LOG_INFO : LOG_DEBUG, - "Module '%s' is deny-listed (by kernel)", kmod_module_get_name(mod)); + "Module '%s' is deny-listed (by kernel)", sym_kmod_module_get_name(mod)); continue; } } @@ -113,7 +165,7 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) LOG_ERR, err, "Failed to insert module '%s': %m", - kmod_module_get_name(mod)); + sym_kmod_module_get_name(mod)); if (!IN_SET(err, -ENODEV, -ENOENT)) r = err; } @@ -122,3 +174,38 @@ int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) return r; } + +_printf_(6,0) static void systemd_kmod_log( + void *data, + int priority, + const char *file, + int line, + const char *fn, + const char *format, + va_list args) { + + log_internalv(priority, 0, file, line, fn, format, args); +} + +int module_setup_context(struct kmod_ctx **ret) { + _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL; + int r; + + assert(ret); + + r = dlopen_libkmod(); + if (r < 0) + return r; + + ctx = sym_kmod_new(NULL, NULL); + if (!ctx) + return -ENOMEM; + + (void) sym_kmod_load_resources(ctx); + sym_kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + + *ret = TAKE_PTR(ctx); + return 0; +} + +#endif diff --git a/src/shared/module-util.h b/src/shared/module-util.h index 8ca6a06..0819738 100644 --- a/src/shared/module-util.h +++ b/src/shared/module-util.h @@ -1,12 +1,56 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "dlfcn-util.h" + +#if HAVE_KMOD + #include #include "macro.h" -DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, kmod_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, kmod_module_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, kmod_module_unref_list, NULL); +DLSYM_PROTOTYPE(kmod_list_next); +DLSYM_PROTOTYPE(kmod_load_resources); +DLSYM_PROTOTYPE(kmod_module_get_initstate); +DLSYM_PROTOTYPE(kmod_module_get_module); +DLSYM_PROTOTYPE(kmod_module_get_name); +DLSYM_PROTOTYPE(kmod_module_new_from_lookup); +DLSYM_PROTOTYPE(kmod_module_probe_insert_module); +DLSYM_PROTOTYPE(kmod_module_unref); +DLSYM_PROTOTYPE(kmod_module_unref_list); +DLSYM_PROTOTYPE(kmod_new); +DLSYM_PROTOTYPE(kmod_set_log_fn); +DLSYM_PROTOTYPE(kmod_unref); +DLSYM_PROTOTYPE(kmod_validate_resources); + +int dlopen_libkmod(void); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_ctx*, sym_kmod_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct kmod_module*, sym_kmod_module_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct kmod_list*, sym_kmod_module_unref_list, NULL); + +#define sym_kmod_list_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry != NULL; \ + list_entry = sym_kmod_list_next(first_entry, list_entry)) int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose); +int module_setup_context(struct kmod_ctx **ret); + +#else + +struct kmod_ctx; + +static inline int dlopen_libkmod(void) { + return -EOPNOTSUPP; +} + +static inline int module_setup_context(struct kmod_ctx **ret) { + return -EOPNOTSUPP; +} + +static inline int module_load_and_warn(struct kmod_ctx *ctx, const char *module, bool verbose) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/shared/mount-setup.c b/src/shared/mount-setup.c index 1226ca1..ba291bd 100644 --- a/src/shared/mount-setup.c +++ b/src/shared/mount-setup.c @@ -69,7 +69,7 @@ static bool check_recursiveprot_supported(void) { r = mount_option_supported("cgroup2", "memory_recursiveprot", NULL); if (r < 0) - log_debug_errno(r, "Failed to determiner whether the 'memory_recursiveprot' mount option is supported, assuming not: %m"); + log_debug_errno(r, "Failed to determine whether the 'memory_recursiveprot' mount option is supported, assuming not: %m"); else if (r == 0) log_debug("This kernel version does not support 'memory_recursiveprot', not using mount option."); @@ -107,16 +107,6 @@ static const MountPoint mount_table[] = { cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, { "cgroup2", "/sys/fs/cgroup", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, cg_is_unified_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, #if ENABLE_PSTORE { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL, MNT_NONE }, @@ -135,8 +125,8 @@ bool mount_point_is_api(const char *path) { /* Checks if this mount point is considered "API", and hence * should be ignored */ - for (size_t i = 0; i < ELEMENTSOF(mount_table); i ++) - if (path_equal(path, mount_table[i].where)) + FOREACH_ELEMENT(i, mount_table) + if (path_equal(path, i->where)) return true; return path_startswith(path, "/sys/fs/cgroup/"); @@ -167,8 +157,11 @@ static int mount_one(const MountPoint *p, bool relabel) { int r, priority; assert(p); + assert(p->what); + assert(p->where); + assert(p->type); - priority = (p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG; + priority = FLAGS_SET(p->mode, MNT_FATAL) ? LOG_ERR : LOG_DEBUG; if (p->condition_fn && !p->condition_fn()) return 0; @@ -177,16 +170,16 @@ static int mount_one(const MountPoint *p, bool relabel) { if (relabel) (void) label_fix(p->where, LABEL_IGNORE_ENOENT|LABEL_IGNORE_EROFS); - r = path_is_mount_point(p->where, NULL, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(p->where, /* root = */ NULL, AT_SYMLINK_FOLLOW); if (r < 0 && r != -ENOENT) { log_full_errno(priority, r, "Failed to determine whether %s is a mount point: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; } if (r > 0) return 0; /* Skip securityfs in a container */ - if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) + if (!FLAGS_SET(p->mode, MNT_IN_CONTAINER) && detect_container() > 0) return 0; /* The access mode here doesn't really matter too much, since @@ -202,44 +195,37 @@ static int mount_one(const MountPoint *p, bool relabel) { p->type, strna(p->options)); - if (FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)) - r = mount_follow_verbose(priority, p->what, p->where, p->type, p->flags, p->options); - else - r = mount_nofollow_verbose(priority, p->what, p->where, p->type, p->flags, p->options); + r = mount_verbose_full(priority, p->what, p->where, p->type, p->flags, p->options, FLAGS_SET(p->mode, MNT_FOLLOW_SYMLINK)); if (r < 0) - return (p->mode & MNT_FATAL) ? r : 0; + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; /* Relabel again, since we now mounted something fresh here */ if (relabel) (void) label_fix(p->where, 0); - if (p->mode & MNT_CHECK_WRITABLE) { + if (FLAGS_SET(p->mode, MNT_CHECK_WRITABLE)) if (access(p->where, W_OK) < 0) { r = -errno; (void) umount2(p->where, UMOUNT_NOFOLLOW); (void) rmdir(p->where); - log_full_errno(priority, r, "Mount point %s not writable after mounting, undoing: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; + log_full_errno(priority, r, "Mount point '%s' not writable after mounting, undoing: %m", p->where); + return FLAGS_SET(p->mode, MNT_FATAL) ? r : 0; } - } return 1; } static int mount_points_setup(size_t n, bool loaded_policy) { - int ret = 0, r; + int r = 0; assert(n <= ELEMENTSOF(mount_table)); - FOREACH_ARRAY(mp, mount_table, n) { - r = mount_one(mp, loaded_policy); - if (r != 0 && ret >= 0) - ret = r; - } + FOREACH_ARRAY(mp, mount_table, n) + RET_GATHER(r, mount_one(mp, loaded_policy)); - return ret; + return r; } int mount_setup_early(void) { @@ -297,81 +283,6 @@ static int symlink_controller(const char *target, const char *alias) { return 0; } -int mount_cgroup_controllers(void) { - _cleanup_set_free_ Set *controllers = NULL; - int r; - - if (!cg_is_legacy_wanted()) - return 0; - - /* Mount all available cgroup controllers that are built into the kernel. */ - r = cg_kernel_controllers(&controllers); - if (r < 0) - return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; - const char *other_controller; - MountPoint p = { - .what = "cgroup", - .type = "cgroup", - .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, - .mode = MNT_IN_CONTAINER, - }; - - controller = set_steal_first(controllers); - if (!controller) - break; - - /* Check if we shall mount this together with another controller */ - other_controller = join_with(controller); - if (other_controller) { - _cleanup_free_ char *c = NULL; - - /* Check if the other controller is actually available in the kernel too */ - c = set_remove(controllers, other_controller); - if (c) { - - /* Join the two controllers into one string, and maintain a stable ordering */ - if (strcmp(controller, other_controller) < 0) - options = strjoin(controller, ",", other_controller); - else - options = strjoin(other_controller, ",", controller); - if (!options) - return log_oom(); - } - } - - /* The simple case, where there's only one controller to mount together */ - if (!options) - options = TAKE_PTR(controller); - - where = path_join("/sys/fs/cgroup", options); - if (!where) - return log_oom(); - - p.where = where; - p.options = options; - - r = mount_one(&p, true); - if (r < 0) - return r; - - /* Create symlinks from the individual controller names, in case we have a joined mount */ - if (controller) - (void) symlink_controller(options, controller); - if (other_controller) - (void) symlink_controller(options, other_controller); - } - - /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */ - (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs", - MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, - "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP); - - return 0; -} - #if HAVE_SELINUX || ENABLE_SMACK static int relabel_cb( RecurseDirEvent event, @@ -415,34 +326,6 @@ static int relabel_tree(const char *path) { return r; } -static int relabel_cgroup_filesystems(void) { - int r; - struct statfs st; - - r = cg_all_unified(); - if (r == 0) { - /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this - only when the filesystem has been already populated by a previous instance of systemd - running from initrd. Otherwise don't remount anything and leave the filesystem read-write - for the cgroup filesystems to be mounted inside. */ - if (statfs("/sys/fs/cgroup", &st) < 0) - return log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup: %m"); - - if (st.f_flags & ST_RDONLY) - (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL); - - (void) label_fix("/sys/fs/cgroup", 0); - (void) relabel_tree("/sys/fs/cgroup"); - - if (st.f_flags & ST_RDONLY) - (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL); - - } else if (r < 0) - return log_error_errno(r, "Failed to determine whether we are in all unified mode: %m"); - - return 0; -} - static int relabel_extra(void) { _cleanup_strv_free_ char **files = NULL; int r, c = 0; @@ -533,14 +416,12 @@ int mount_setup(bool loaded_policy, bool leave_propagation) { FOREACH_STRING(i, "/dev", "/dev/shm", "/run") (void) relabel_tree(i); - (void) relabel_cgroup_filesystems(); - n_extra = relabel_extra(); after_relabel = now(CLOCK_MONOTONIC); - log_info("Relabeled /dev, /dev/shm, /run, /sys/fs/cgroup%s in %s.", - n_extra > 0 ? ", additional files" : "", + log_info("Relabeled /dev/, /dev/shm/, /run/%s in %s.", + n_extra > 0 ? ", and additional files" : "", FORMAT_TIMESPAN(after_relabel - before_relabel, 0)); } #endif @@ -589,3 +470,127 @@ int mount_setup(bool loaded_policy, bool leave_propagation) { return 0; } + +static const MountPoint cgroupv1_mount_table[] = { + { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP, MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", "nsdelegate", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, + { "cgroup2", "/sys/fs/cgroup/unified", "cgroup2", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_hybrid_wanted, MNT_IN_CONTAINER|MNT_CHECK_WRITABLE }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, +}; + +static void relabel_cgroup_legacy_hierarchy(void) { +#if HAVE_SELINUX || ENABLE_SMACK + struct statfs st; + + assert(cg_is_legacy_wanted()); + + /* Temporarily remount the root cgroup filesystem to give it a proper label. Do this + only when the filesystem has been already populated by a previous instance of systemd + running from initrd. Otherwise don't remount anything and leave the filesystem read-write + for the cgroup filesystems to be mounted inside. */ + if (statfs("/sys/fs/cgroup", &st) < 0) + return (void) log_error_errno(errno, "Failed to determine mount flags for /sys/fs/cgroup/: %m"); + + if (st.f_flags & ST_RDONLY) + (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT, NULL); + + (void) label_fix("/sys/fs/cgroup", 0); + (void) relabel_tree("/sys/fs/cgroup"); + + if (st.f_flags & ST_RDONLY) + (void) mount_nofollow(NULL, "/sys/fs/cgroup", NULL, MS_REMOUNT|MS_RDONLY, NULL); +#endif +} + +int mount_cgroup_legacy_controllers(bool loaded_policy) { + _cleanup_set_free_ Set *controllers = NULL; + int r; + + if (!cg_is_legacy_wanted()) + return 0; + + if (!cg_is_legacy_force_enabled()) + return -ERFKILL; + + FOREACH_ELEMENT(mp, cgroupv1_mount_table) { + r = mount_one(mp, loaded_policy); + if (r < 0) + return r; + } + + if (loaded_policy) + relabel_cgroup_legacy_hierarchy(); + + /* Mount all available cgroup controllers that are built into the kernel. */ + r = cg_kernel_controllers(&controllers); + if (r < 0) + return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; + const char *other_controller; + MountPoint p = { + .what = "cgroup", + .type = "cgroup", + .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, + .mode = MNT_IN_CONTAINER, + }; + + controller = set_steal_first(controllers); + if (!controller) + break; + + /* Check if we shall mount this together with another controller */ + other_controller = join_with(controller); + if (other_controller) { + _cleanup_free_ char *c = NULL; + + /* Check if the other controller is actually available in the kernel too */ + c = set_remove(controllers, other_controller); + if (c) { + + /* Join the two controllers into one string, and maintain a stable ordering */ + if (strcmp(controller, other_controller) < 0) + options = strjoin(controller, ",", other_controller); + else + options = strjoin(other_controller, ",", controller); + if (!options) + return log_oom(); + } + } + + /* The simple case, where there's only one controller to mount together */ + if (!options) + options = TAKE_PTR(controller); + + where = path_join("/sys/fs/cgroup", options); + if (!where) + return log_oom(); + + p.where = where; + p.options = options; + + r = mount_one(&p, true); + if (r < 0) + return r; + + /* Create symlinks from the individual controller names, in case we have a joined mount */ + if (controller) + (void) symlink_controller(options, controller); + if (other_controller) + (void) symlink_controller(options, other_controller); + } + + /* Now that we mounted everything, let's make the tmpfs the cgroup file systems are mounted into read-only. */ + (void) mount_nofollow("tmpfs", "/sys/fs/cgroup", "tmpfs", + MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, + "mode=0755" TMPFS_LIMITS_SYS_FS_CGROUP); + + return 1; +} diff --git a/src/shared/mount-setup.h b/src/shared/mount-setup.h index 29bd62f..9fc4933 100644 --- a/src/shared/mount-setup.h +++ b/src/shared/mount-setup.h @@ -3,10 +3,10 @@ #include +bool mount_point_is_api(const char *path); +bool mount_point_ignore(const char *path); + int mount_setup_early(void); int mount_setup(bool loaded_policy, bool leave_propagation); -int mount_cgroup_controllers(void); - -bool mount_point_is_api(const char *path); -bool mount_point_ignore(const char *path); +int mount_cgroup_legacy_controllers(bool loaded_policy); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 4f2acce..4ddfb9a 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -79,7 +79,9 @@ int umount_recursive_full(const char *prefix, int flags, char **keep) { continue; if (prefix && !path_startswith(path, prefix)) { - log_trace("Not unmounting %s, outside of prefix: %s", path, prefix); + // FIXME: This is extremely noisy, we're probably doing something very wrong + // to trigger this so often, needs more investigation. + // log_trace("Not unmounting %s, outside of prefix: %s", path, prefix); continue; } @@ -347,7 +349,7 @@ int bind_remount_recursive_with_mountinfo( * think autofs, NFS, FUSE, …), but let's generate useful debug messages at * the very least. */ - q = path_is_mount_point(x, NULL, 0); + q = path_is_mount_point(x); if (IN_SET(q, 0, -ENOENT)) { /* Hmm, whaaaa? The mount point is not actually a mount point? Then * it is either obstructed by a later mount or somebody has been @@ -453,14 +455,20 @@ int bind_remount_one_with_mountinfo( return 0; } +int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return log_debug_errno(errno, "Failed to open /proc/self/mountinfo: %m"); + + return bind_remount_one_with_mountinfo(path, new_flags, flags_mask, proc_self_mountinfo); +} + static int mount_switch_root_pivot(int fd_newroot, const char *path) { assert(fd_newroot >= 0); assert(path); - /* Change into the new rootfs. */ - if (fchdir(fd_newroot) < 0) - return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); - /* Let the kernel tuck the new root under the old one. */ if (pivot_root(".", ".") < 0) return log_debug_errno(errno, "Failed to pivot root to new rootfs '%s': %m", path); @@ -477,10 +485,6 @@ static int mount_switch_root_move(int fd_newroot, const char *path) { assert(fd_newroot >= 0); assert(path); - /* Change into the new rootfs. */ - if (fchdir(fd_newroot) < 0) - return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); - /* Move the new root fs */ if (mount(".", "/", NULL, MS_MOVE, NULL) < 0) return log_debug_errno(errno, "Failed to move new rootfs '%s': %m", path); @@ -494,7 +498,7 @@ static int mount_switch_root_move(int fd_newroot, const char *path) { int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move) { _cleanup_close_ int fd_newroot = -EBADF; - int r; + int r, is_current_root; assert(path); assert(mount_propagation_flag_is_valid(mount_propagation_flag)); @@ -503,19 +507,31 @@ int mount_switch_root_full(const char *path, unsigned long mount_propagation_fla if (fd_newroot < 0) return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path); - if (!force_ms_move) { - r = mount_switch_root_pivot(fd_newroot, path); - if (r < 0) { - log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path); - force_ms_move = true; + is_current_root = path_is_root_at(fd_newroot, NULL); + if (is_current_root < 0) + return log_debug_errno(is_current_root, "Failed to determine if target dir is our root already: %m"); + + /* Change into the new rootfs. */ + if (fchdir(fd_newroot) < 0) + return log_debug_errno(errno, "Failed to chdir into new rootfs '%s': %m", path); + + /* Make this a NOP if we are supposed to switch to our current root fs. After all, both pivot_root() + * and MS_MOVE don't like that. */ + if (!is_current_root) { + if (!force_ms_move) { + r = mount_switch_root_pivot(fd_newroot, path); + if (r < 0) { + log_debug_errno(r, "Failed to pivot into new rootfs '%s', will try to use MS_MOVE instead: %m", path); + force_ms_move = true; + } + } + if (force_ms_move) { + /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is + * an initramfs in which case pivot_root() isn't supported. */ + r = mount_switch_root_move(fd_newroot, path); + if (r < 0) + return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path); } - } - if (force_ms_move) { - /* Failed to pivot_root() fallback to MS_MOVE. For example, this may happen if the rootfs is - * an initramfs in which case pivot_root() isn't supported. */ - r = mount_switch_root_move(fd_newroot, path); - if (r < 0) - return log_debug_errno(r, "Failed to switch to new rootfs '%s' with MS_MOVE: %m", path); } /* Finally, let's establish the requested propagation flags. */ @@ -817,8 +833,8 @@ int mount_option_mangle( if (!(ent->mask & MNT_INVERT)) mount_flags |= ent->id; - else if (mount_flags & ent->id) - mount_flags ^= ent->id; + else + mount_flags &= ~ent->id; break; } @@ -1096,7 +1112,7 @@ static int mount_in_namespace( if (!pidref_is_set(target)) return -ESRCH; - r = namespace_open(target->pid, &pidns_fd, &mntns_fd, NULL, NULL, &root_fd); + r = namespace_open(target->pid, &pidns_fd, &mntns_fd, /* ret_netns_fd = */ NULL, /* ret_userns_fd = */ NULL, &root_fd); if (r < 0) return log_debug_errno(r, "Failed to retrieve FDs of the target process' namespace: %m"); @@ -1200,7 +1216,9 @@ static int mount_in_namespace( (void) mkdir_parents(dest, 0755); if (img) { - DissectImageFlags f = DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE; + DissectImageFlags f = + DISSECT_IMAGE_TRY_ATOMIC_MOUNT_EXCHANGE | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; if (make_file_or_directory) f |= DISSECT_IMAGE_MKDIR; @@ -1279,7 +1297,7 @@ int make_mount_point(const char *path) { /* If 'path' is already a mount point, does nothing and returns 0. If it is not it makes it one, and returns 1. */ - r = path_is_mount_point(path, NULL, 0); + r = path_is_mount_point(path); if (r < 0) return log_debug_errno(r, "Failed to determine whether '%s' is a mount point: %m", path); if (r > 0) @@ -1310,7 +1328,7 @@ int fd_make_mount_point(int fd) { return 1; } -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 source_owner, uid_t dest_owner, RemountIdmapping idmapping) { _cleanup_close_ int userns_fd = -EBADF; _cleanup_free_ char *line = NULL; @@ -1345,8 +1363,20 @@ int make_userns(uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER) { /* Remap the owner of the bind mounted directory to the root user within the container. This * way every file written by root within the container to the bind-mounted directory will - * be owned by the original user. All other user will remain unmapped. */ - if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", owner, uid_shift, 1u) < 0) + * be owned by the original user from the host. All other users will remain unmapped. */ + if (asprintf(&line, UID_FMT " " UID_FMT " " UID_FMT "\n", source_owner, uid_shift, 1u) < 0) + return log_oom_debug(); + } + + if (idmapping == REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER) { + /* Remap the owner of the bind mounted directory to the owner of the target directory + * within the container. This way every file written by target directory owner within the + * container to the bind-mounted directory will be owned by the original host user. + * All other users will remain unmapped. */ + if (asprintf( + &line, + UID_FMT " " UID_FMT " " UID_FMT "\n", + source_owner, dest_owner, 1u) < 0) return log_oom_debug(); } @@ -1366,7 +1396,7 @@ int remount_idmap_fd( assert(userns_fd >= 0); - /* This remounts all specified paths with the specified userns as idmap. It will do so in in the + /* This remounts all specified paths with the specified userns as idmap. It will do so in the * order specified in the strv: the expectation is that the top-level directories are at the * beginning, and nested directories in the right, so that the tree can be built correctly from left * to right. */ @@ -1420,10 +1450,10 @@ int remount_idmap_fd( return 0; } -int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping) { +int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t source_owner, uid_t dest_owner,RemountIdmapping idmapping) { _cleanup_close_ int userns_fd = -EBADF; - userns_fd = make_userns(uid_shift, uid_range, owner, idmapping); + userns_fd = make_userns(uid_shift, uid_range, source_owner, dest_owner, idmapping); if (userns_fd < 0) return userns_fd; @@ -1586,7 +1616,7 @@ int bind_mount_submounts( if (!t) return -ENOMEM; - r = path_is_mount_point(t, NULL, 0); + r = path_is_mount_point(t); if (r < 0) { log_debug_errno(r, "Failed to detect if '%s' already is a mount point, ignoring: %m", t); continue; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index ef31104..26d96b2 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -26,6 +26,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f } int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo); +int bind_remount_one(const char *path, unsigned long new_flags, unsigned long flags_mask); int mount_switch_root_full(const char *path, unsigned long mount_propagation_flag, bool force_ms_move); static inline int mount_switch_root(const char *path, unsigned long mount_propagation_flag) { @@ -116,16 +117,19 @@ typedef enum RemountIdmapping { * certain security implications defaults to off, and requires explicit opt-in. */ REMOUNT_IDMAPPING_HOST_ROOT, /* Define a mapping from root user within the container to the owner of the bind mounted directory. - * This ensure no root-owned files will be written in a bind-mounted directory owned by a different + * This ensures no root-owned files will be written in a bind-mounted directory owned by a different * user. No other users are mapped. */ REMOUNT_IDMAPPING_HOST_OWNER, + /* Define a mapping from bind-target owner within the container to the host owner of the bind mounted + * directory. No other users are mapped. */ + REMOUNT_IDMAPPING_HOST_OWNER_TO_TARGET_OWNER, _REMOUNT_IDMAPPING_MAX, _REMOUNT_IDMAPPING_INVALID = -EINVAL, } RemountIdmapping; -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 host_owner, uid_t dest_owner, RemountIdmapping idmapping); int remount_idmap_fd(char **p, int userns_fd); -int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t owner, RemountIdmapping idmapping); +int remount_idmap(char **p, uid_t uid_shift, uid_t uid_range, uid_t host_owner, uid_t dest_owner, RemountIdmapping idmapping); int bind_mount_submounts( const char *source, diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c index fbaf5c5..2955b6e 100644 --- a/src/shared/netif-naming-scheme.c +++ b/src/shared/netif-naming-scheme.c @@ -1,6 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "sd-device.h" + #include "alloc-util.h" +#include "device-private.h" #include "netif-naming-scheme.h" #include "proc-cmdline.h" #include "string-util.h" @@ -50,7 +53,7 @@ const NamingScheme* naming_scheme(void) { return cache; /* Acquire setting from the kernel command line */ - (void) proc_cmdline_get_key("net.naming-scheme", 0, &buffer); + (void) proc_cmdline_get_key("net.naming_scheme", 0, &buffer); /* Also acquire it from an env var */ e = getenv("NET_NAMING_SCHEME"); @@ -101,3 +104,81 @@ static const char* const alternative_names_policy_table[_NAMEPOLICY_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(alternative_names_policy, NamePolicy); + +static int naming_sysattr_allowed_by_default(sd_device *dev) { + int r; + + assert(dev); + + r = device_get_property_bool(dev, "ID_NET_NAME_ALLOW"); + if (r == -ENOENT) + return true; + + return r; +} + +static int naming_sysattr_allowed(sd_device *dev, const char *sysattr) { + char *sysattr_property; + int r; + + assert(dev); + assert(sysattr); + + sysattr_property = strjoina("ID_NET_NAME_ALLOW_", sysattr); + ascii_strupper(sysattr_property); + + r = device_get_property_bool(dev, sysattr_property); + if (r == -ENOENT) + /* If ID_NET_NAME_ALLOW is not set or set to 1 default is to allow */ + return naming_sysattr_allowed_by_default(dev); + + return r; +} + +int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_int(device, sysattr, ret_value); +} + +int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_unsigned(device, sysattr, ret_value); +} + +int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return device_get_sysattr_bool(device, sysattr); +} + +int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value) { + int r; + + r = naming_sysattr_allowed(device, sysattr); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + return sd_device_get_sysattr_value(device, sysattr, ret_value); +} diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h index 3f7be08..62afdc5 100644 --- a/src/shared/netif-naming-scheme.h +++ b/src/shared/netif-naming-scheme.h @@ -3,6 +3,8 @@ #include +#include "sd-device.h" + #include "macro.h" /* So here's the deal: net_id is supposed to be an exercise in providing stable names for network devices. However, we @@ -95,3 +97,8 @@ NamePolicy name_policy_from_string(const char *p) _pure_; const char *alternative_names_policy_to_string(NamePolicy p) _const_; NamePolicy alternative_names_policy_from_string(const char *p) _pure_; + +int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value); +int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value); +int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr); +int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value); diff --git a/src/shared/netif-sriov.c b/src/shared/netif-sriov.c index 7559b0d..20e175e 100644 --- a/src/shared/netif-sriov.c +++ b/src/shared/netif-sriov.c @@ -84,7 +84,7 @@ void sr_iov_hash_func(const SRIOV *sr_iov, struct siphash *state) { assert(sr_iov); assert(state); - siphash24_compress(&sr_iov->vf, sizeof(sr_iov->vf), state); + siphash24_compress_typesafe(sr_iov->vf, state); } int sr_iov_compare_func(const SRIOV *s1, const SRIOV *s2) { diff --git a/src/shared/netif-util.c b/src/shared/netif-util.c index f56c564..8adc2c8 100644 --- a/src/shared/netif-util.c +++ b/src/shared/netif-util.c @@ -5,13 +5,17 @@ #include "arphrd-util.h" #include "device-util.h" +#include "hexdecoct.h" #include "log-link.h" #include "memory-util.h" +#include "netif-naming-scheme.h" #include "netif-util.h" #include "siphash24.h" #include "sparse-endian.h" #include "strv.h" +#define SHORTEN_IFNAME_HASH_KEY SD_ID128_MAKE(e1,90,a4,04,a8,ef,4b,51,8c,cc,c3,3a,9f,11,fc,a2) + bool netif_has_carrier(uint8_t operstate, unsigned flags) { /* see Documentation/networking/operstates.txt in the kernel sources */ @@ -32,14 +36,8 @@ int net_get_type_string(sd_device *device, uint16_t iftype, char **ret) { if (device && sd_device_get_devtype(device, &t) >= 0 && - !isempty(t)) { - p = strdup(t); - if (!p) - return -ENOMEM; - - *ret = p; - return 0; - } + !isempty(t)) + return strdup_to(ret, t); t = arphrd_to_name(iftype); if (!t) @@ -204,3 +202,79 @@ int net_verify_hardware_address( return 0; } + +int net_generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx) { + + uint64_t result; + size_t l, sz; + uint8_t *v, *i; + int r; + + l = strlen(machine_name); + sz = sizeof(sd_id128_t) + l; + if (idx > 0) + sz += sizeof(idx); + + v = newa(uint8_t, sz); + + /* fetch some persistent data unique to the host */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this host) to this + * container instance */ + i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); + if (idx > 0) { + idx = htole64(idx); + memcpy(i, &idx, sizeof(idx)); + } + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + result = htole64(siphash24(v, sz, hash_key.bytes)); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + + ether_addr_mark_random(mac); + + return 0; +} + +int net_shorten_ifname(char *ifname, bool check_naming_scheme) { + char new_ifname[IFNAMSIZ]; + + assert(ifname); + + if (strlen(ifname) < IFNAMSIZ) /* Name is short enough */ + return 0; + + if (!check_naming_scheme || naming_scheme_has(NAMING_NSPAWN_LONG_HASH)) { + uint64_t h; + + /* Calculate 64-bit hash value */ + h = siphash24(ifname, strlen(ifname), SHORTEN_IFNAME_HASH_KEY.bytes); + + /* Set the final four bytes (i.e. 32-bit) to the lower 24bit of the hash, encoded in url-safe base64 */ + memcpy(new_ifname, ifname, IFNAMSIZ - 5); + new_ifname[IFNAMSIZ - 5] = urlsafe_base64char(h >> 18); + new_ifname[IFNAMSIZ - 4] = urlsafe_base64char(h >> 12); + new_ifname[IFNAMSIZ - 3] = urlsafe_base64char(h >> 6); + new_ifname[IFNAMSIZ - 2] = urlsafe_base64char(h); + } else + /* On old nspawn versions we just truncated the name, provide compatibility */ + memcpy(new_ifname, ifname, IFNAMSIZ-1); + + new_ifname[IFNAMSIZ - 1] = 0; + + /* Log the incident to make it more discoverable */ + log_warning("Network interface name '%s' has been changed to '%s' to fit length constraints.", ifname, new_ifname); + + strcpy(ifname, new_ifname); + return 1; +} diff --git a/src/shared/netif-util.h b/src/shared/netif-util.h index fb6a27c..02c531e 100644 --- a/src/shared/netif-util.h +++ b/src/shared/netif-util.h @@ -20,3 +20,9 @@ int net_verify_hardware_address( uint16_t iftype, const struct hw_addr_data *ib_hw_addr, struct hw_addr_data *new_hw_addr); +int net_generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx); +int net_shorten_ifname(char *ifname, bool check_naming_scheme); diff --git a/src/shared/nscd-flush.c b/src/shared/nscd-flush.c index 6df18d7..3d47ae3 100644 --- a/src/shared/nscd-flush.c +++ b/src/shared/nscd-flush.c @@ -93,7 +93,7 @@ static int nscd_flush_cache_one(const char *database, usec_t end) { ssize_t m; if (has_read >= sizeof(resp)) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Response from nscd longer than expected."); m = recv(fd, (uint8_t*) &resp + has_read, sizeof(resp) - has_read, 0); if (m < 0) { diff --git a/src/shared/nsresource.c b/src/shared/nsresource.c new file mode 100644 index 0000000..bc0a352 --- /dev/null +++ b/src/shared/nsresource.c @@ -0,0 +1,330 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "fd-util.h" +#include "format-util.h" +#include "missing_sched.h" +#include "namespace-util.h" +#include "nsresource.h" +#include "process-util.h" +#include "varlink.h" + +static int make_pid_name(char **ret) { + char comm[TASK_COMM_LEN]; + + assert(ret); + + if (prctl(PR_GET_NAME, comm) < 0) + return -errno; + + /* So the namespace name should be 16 chars at max (because we want that it is usable in usernames, + * which have a limit of 31 chars effectively, and the nsresourced service wants to prefix/suffix + * some bits). But it also should be unique if we are called multiple times in a row. Hence we take + * the "comm" name (which is 15 chars), and suffix it with the UID, possibly overriding the end. */ + assert_cc(TASK_COMM_LEN == 15 + 1); + + char spid[DECIMAL_STR_MAX(pid_t)]; + xsprintf(spid, PID_FMT, getpid_cached()); + + assert(strlen(spid) <= 16); + strshorten(comm, 16 - strlen(spid)); + + _cleanup_free_ char *s = strjoin(comm, spid); + if (!s) + return -ENOMEM; + + *ret = TAKE_PTR(s); + return 0; +} + +int nsresource_allocate_userns(const char *name, uint64_t size) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int userns_fd = -EBADF; + _cleanup_free_ char *_name = NULL; + const char *error_id; + int r, userns_fd_idx; + + /* Allocate a new dynamic user namespace via the userdb registry logic */ + + if (!name) { + r = make_pid_name(&_name); + if (r < 0) + return r; + + name = _name; + } + + if (size <= 0 || size > UINT64_C(0x100000000)) /* Note: the server actually only allows allocating 1 or 64K right now */ + return -EINVAL; + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd = userns_acquire_empty(); + if (userns_fd < 0) + return log_debug_errno(userns_fd, "Failed to acquire empty user namespace: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AllocateUserRange", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("size", JSON_BUILD_UNSIGNED(size)), + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AllocateUserRange() varlink call: %m"); + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to allocate user namespace with %" PRIu64 " users: %s", size, error_id); + + return TAKE_FD(userns_fd); +} + +int nsresource_register_userns(const char *name, int userns_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + _cleanup_free_ char *_name = NULL; + const char *error_id; + int r, userns_fd_idx; + + /* Register the specified user namespace with userbd. */ + + if (!name) { + r = make_pid_name(&_name); + if (r < 0) + return r; + + name = _name; + } + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.RegisterUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("name", JSON_BUILD_STRING(name)), + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call RegisterUserNamespace() varlink call: %m"); + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to register user namespace: %s", error_id); + + return 0; +} + +int nsresource_add_mount(int userns_fd, int mount_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + int r, userns_fd_idx, mount_fd_idx; + const char *error_id; + + assert(mount_fd >= 0); + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return _userns_fd; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_error_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_error_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + mount_fd_idx = varlink_push_dup_fd(vl, mount_fd); + if (mount_fd_idx < 0) + return log_error_errno(mount_fd_idx, "Failed to push mount fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddMountToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("mountFileDescriptor", JSON_BUILD_UNSIGNED(mount_fd_idx)))); + if (r < 0) + return log_error_errno(r, "Failed to call AddMountToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding mount to registration."); + return 0; + } + if (error_id) + return log_error_errno(varlink_error_to_errno(error_id, reply), "Failed to mount image: %s", error_id); + + return 1; +} + +int nsresource_add_cgroup(int userns_fd, int cgroup_fd) { + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + _cleanup_close_ int _userns_fd = -EBADF; + int r, userns_fd_idx, cgroup_fd_idx; + const char *error_id; + + assert(cgroup_fd >= 0); + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + cgroup_fd_idx = varlink_push_dup_fd(vl, cgroup_fd); + if (cgroup_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push cgroup fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddControlGroupToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("controlGroupFileDescriptor", JSON_BUILD_UNSIGNED(cgroup_fd_idx)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AddControlGroupToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding cgroup to registration."); + return 0; + } + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add cgroup to user namespace: %s", error_id); + + return 1; +} + +int nsresource_add_netif( + int userns_fd, + int netns_fd, + const char *namespace_ifname, + char **ret_host_ifname, + char **ret_namespace_ifname) { + + _cleanup_close_ int _userns_fd = -EBADF, _netns_fd = -EBADF; + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r, userns_fd_idx, netns_fd_idx; + const char *error_id; + + if (userns_fd < 0) { + _userns_fd = namespace_open_by_type(NAMESPACE_USER); + if (_userns_fd < 0) + return -errno; + + userns_fd = _userns_fd; + } + + if (netns_fd < 0) { + _netns_fd = namespace_open_by_type(NAMESPACE_NET); + if (_netns_fd < 0) + return -errno; + + netns_fd = _netns_fd; + } + + r = varlink_connect_address(&vl, "/run/systemd/io.systemd.NamespaceResource"); + if (r < 0) + return log_debug_errno(r, "Failed to connect to namespace resource manager: %m"); + + r = varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_debug_errno(r, "Failed to enable varlink fd passing for write: %m"); + + userns_fd_idx = varlink_push_dup_fd(vl, userns_fd); + if (userns_fd_idx < 0) + return log_debug_errno(userns_fd_idx, "Failed to push userns fd into varlink connection: %m"); + + netns_fd_idx = varlink_push_dup_fd(vl, netns_fd); + if (netns_fd_idx < 0) + return log_debug_errno(netns_fd_idx, "Failed to push netns fd into varlink connection: %m"); + + JsonVariant *reply = NULL; + r = varlink_callb(vl, + "io.systemd.NamespaceResource.AddNetworkToUserNamespace", + &reply, + &error_id, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("userNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(userns_fd_idx)), + JSON_BUILD_PAIR("networkNamespaceFileDescriptor", JSON_BUILD_UNSIGNED(netns_fd_idx)), + JSON_BUILD_PAIR("mode", JSON_BUILD_CONST_STRING("veth")), + JSON_BUILD_PAIR_CONDITION(namespace_ifname, "namespaceInterfaceName", JSON_BUILD_STRING(namespace_ifname)))); + if (r < 0) + return log_debug_errno(r, "Failed to call AddNetworkToUserNamespace() varlink call: %m"); + if (streq_ptr(error_id, "io.systemd.NamespaceResource.UserNamespaceNotRegistered")) { + log_notice("User namespace has not been allocated via namespace resource registry, not adding network to registration."); + return 0; + } + if (error_id) + return log_debug_errno(varlink_error_to_errno(error_id, reply), "Failed to add network to user namespace: %s", error_id); + + _cleanup_free_ char *host_interface_name = NULL, *namespace_interface_name = NULL; + r = json_dispatch( + reply, + (const JsonDispatch[]) { + { "hostInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&host_interface_name) }, + { "namespaceInterfaceName", JSON_VARIANT_STRING, json_dispatch_string, PTR_TO_SIZE(&namespace_interface_name) }, + }, + JSON_ALLOW_EXTENSIONS, + /* userdata= */ NULL); + + if (ret_host_ifname) + *ret_host_ifname = TAKE_PTR(host_interface_name); + if (ret_namespace_ifname) + *ret_namespace_ifname = TAKE_PTR(namespace_interface_name); + + return 1; +} diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h new file mode 100644 index 0000000..6b807b3 --- /dev/null +++ b/src/shared/nsresource.h @@ -0,0 +1,10 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +int nsresource_allocate_userns(const char *name, uint64_t size); +int nsresource_register_userns(const char *name, int userns_fd); +int nsresource_add_mount(int userns_fd, int mount_fd); +int nsresource_add_cgroup(int userns_fd, int cgroup_fd); +int nsresource_add_netif(int userns_fd, int netns_fd, const char *namespace_ifname, char **ret_host_ifname, char **ret_namespace_ifname); diff --git a/src/shared/open-file.c b/src/shared/open-file.c index 7d7a8a9..a9c2a11 100644 --- a/src/shared/open-file.c +++ b/src/shared/open-file.c @@ -1,4 +1,3 @@ - /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include @@ -23,7 +22,7 @@ int open_file_parse(const char *v, OpenFile **ret) { if (!of) return -ENOMEM; - r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options, NULL); + r = extract_many_words(&v, ":", EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_CUNESCAPE, &of->path, &of->fdname, &options); if (r < 0) return r; if (r == 0) @@ -122,19 +121,14 @@ int open_file_to_string(const OpenFile *of, char **ret) { return 0; } -OpenFile *open_file_free(OpenFile *of) { +OpenFile* open_file_free(OpenFile *of) { if (!of) return NULL; free(of->path); free(of->fdname); - return mfree(of); -} -void open_file_free_many(OpenFile **head) { - assert(head); - - LIST_CLEAR(open_files, *head, open_file_free); + return mfree(of); } static const char * const open_file_flags_table[_OPENFILE_MAX] = { diff --git a/src/shared/open-file.h b/src/shared/open-file.h index bb63ec8..4999c96 100644 --- a/src/shared/open-file.h +++ b/src/shared/open-file.h @@ -1,8 +1,8 @@ - /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once #include "list.h" +#include "macro.h" typedef enum OpenFileFlag { OPENFILE_READ_ONLY = 1 << 0, @@ -27,10 +27,12 @@ int open_file_validate(const OpenFile *of); int open_file_to_string(const OpenFile *of, char **ret); -OpenFile *open_file_free(OpenFile *of); +OpenFile* open_file_free(OpenFile *of); DEFINE_TRIVIAL_CLEANUP_FUNC(OpenFile*, open_file_free); -void open_file_free_many(OpenFile **head); +static inline void open_file_free_many(OpenFile **head) { + LIST_CLEAR(open_files, *ASSERT_PTR(head), open_file_free); +} -const char *open_file_flags_to_string(OpenFileFlag t) _const_; +const char* open_file_flags_to_string(OpenFileFlag t) _const_; OpenFileFlag open_file_flags_from_string(const char *t) _pure_; diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c index b0a5563..2ab89c7 100644 --- a/src/shared/openssl-util.c +++ b/src/shared/openssl-util.c @@ -1,14 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "fd-util.h" #include "hexdecoct.h" +#include "memory-util.h" #include "openssl-util.h" +#include "random-util.h" #include "string-util.h" #if HAVE_OPENSSL -/* For each error in the the OpenSSL thread error queue, log the provided message and the OpenSSL error - * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No openssl +# include +# include + +# if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) +# include +DISABLE_WARNING_DEPRECATED_DECLARATIONS; +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ENGINE*, ENGINE_free, NULL); +REENABLE_WARNING; +# endif + +/* For each error in the OpenSSL thread error queue, log the provided message and the OpenSSL error + * string. If there are no errors in the OpenSSL thread queue, this logs the message with "No OpenSSL * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */ #define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__) #define _log_openssl_errors(u, fmt, ...) \ @@ -523,7 +537,6 @@ int rsa_encrypt_bytes( *ret_encrypt_key = TAKE_PTR(b); *ret_encrypt_key_size = l; - return 0; } @@ -624,48 +637,55 @@ int rsa_pkey_to_suitable_key_size( return 0; } -/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it - * must be provided here big-endian, e.g. wrap it with htobe32(). */ +/* Generate RSA public key from provided "n" and "e" values. Numbers "n" and "e" must be provided here + * in big-endian format, e.g. wrap it with htobe32() for uint32_t. */ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; assert(n); + assert(n_size != 0); assert(e); + assert(e_size != 0); assert(ret); - _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (!ctx) return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); - _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); - if (!bn_n) - return log_openssl_errors("Failed to create BIGNUM for RSA n"); - - _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); - if (!bn_e) - return log_openssl_errors("Failed to create BIGNUM for RSA e"); - -#if OPENSSL_VERSION_MAJOR >= 3 if (EVP_PKEY_fromdata_init(ctx) <= 0) return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); - _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); - if (!bld) - return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); + OSSL_PARAM params[3]; - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) - return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_N"); +#if __BYTE_ORDER == __BIG_ENDIAN + params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, (void*)n, n_size); + params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, (void*)e, e_size); +#else + _cleanup_free_ void *native_n = memdup_reverse(n, n_size); + if (!native_n) + return log_oom_debug(); - if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) - return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_E"); + _cleanup_free_ void *native_e = memdup_reverse(e, e_size); + if (!native_e) + return log_oom_debug(); - _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); - if (!params) - return log_openssl_errors("Failed to build RSA OSSL_PARAM"); + params[0] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_N, native_n, n_size); + params[1] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_RSA_E, native_e, e_size); +#endif + params[2] = OSSL_PARAM_construct_end(); if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) return log_openssl_errors("Failed to create RSA EVP_PKEY"); #else + _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); + if (!bn_n) + return log_openssl_errors("Failed to create BIGNUM for RSA n"); + + _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); + if (!bn_e) + return log_openssl_errors("Failed to create BIGNUM for RSA e"); + _cleanup_(RSA_freep) RSA *rsa_key = RSA_new(); if (!rsa_key) return log_openssl_errors("Failed to create new RSA"); @@ -989,7 +1009,7 @@ int ecc_ecdh(const EVP_PKEY *private_pkey, if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) return log_openssl_errors("Failed to get ECC shared secret size"); - _cleanup_free_ void *shared_secret = malloc(shared_secret_size); + _cleanup_(erase_and_freep) void *shared_secret = malloc(shared_secret_size); if (!shared_secret) return log_oom_debug(); @@ -1128,6 +1148,258 @@ int string_hashsum( return 0; } # endif + +static int ecc_pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_new = NULL; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_free_ unsigned char *saved_key = NULL; + size_t decrypted_key_size, saved_key_size; + int nid = NID_undef; + int r; + +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_free_ char *curve_name = NULL; + size_t len = 0; + + if (EVP_PKEY_get_group_name(pkey, NULL, 0, &len) != 1 || len == 0) + return log_openssl_errors("Failed to determine PKEY group name length"); + + len++; + curve_name = new(char, len); + if (!curve_name) + return log_oom_debug(); + + if (EVP_PKEY_get_group_name(pkey, curve_name, len, &len) != 1) + return log_openssl_errors("Failed to get PKEY group name"); + + nid = OBJ_sn2nid(curve_name); +#else + EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(pkey); + if (!ec_key) + return log_openssl_errors("PKEY doesn't have EC_KEY associated"); + + if (EC_KEY_check_key(ec_key) != 1) + return log_openssl_errors("EC_KEY associated with PKEY is not valid"); + + nid = EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key)); +#endif + + r = ecc_pkey_new(nid, &pkey_new); + if (r < 0) + return log_debug_errno(r, "Failed to generate a new EC keypair: %m"); + + r = ecc_ecdh(pkey_new, pkey, &decrypted_key, &decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to derive shared secret: %m"); + +#if OPENSSL_VERSION_MAJOR >= 3 + /* EVP_PKEY_get1_encoded_public_key() always returns uncompressed format of EC points. + See https://github.com/openssl/openssl/discussions/22835 */ + saved_key_size = EVP_PKEY_get1_encoded_public_key(pkey_new, &saved_key); + if (saved_key_size == 0) + return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); +#else + EC_KEY *ec_key_new = EVP_PKEY_get0_EC_KEY(pkey_new); + if (!ec_key_new) + return log_openssl_errors("The generated key doesn't have associated EC_KEY"); + + if (EC_KEY_check_key(ec_key_new) != 1) + return log_openssl_errors("EC_KEY associated with the generated key is not valid"); + + saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new), + EC_KEY_get0_public_key(ec_key_new), + POINT_CONVERSION_UNCOMPRESSED, + NULL, 0, NULL); + if (saved_key_size == 0) + return log_openssl_errors("Failed to determine size of the generated public key"); + + saved_key = malloc(saved_key_size); + if (!saved_key) + return log_oom_debug(); + + saved_key_size = EC_POINT_point2oct(EC_KEY_get0_group(ec_key_new), + EC_KEY_get0_public_key(ec_key_new), + POINT_CONVERSION_UNCOMPRESSED, + saved_key, saved_key_size, NULL); + if (saved_key_size == 0) + return log_openssl_errors("Failed to convert the generated public key to SEC1 format"); +#endif + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +static int rsa_pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_free_ void *saved_key = NULL; + size_t decrypted_key_size, saved_key_size; + int r; + + r = rsa_pkey_to_suitable_key_size(pkey, &decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to determine RSA public key size."); + + log_debug("Generating %zu bytes random key.", decrypted_key_size); + + decrypted_key = malloc(decrypted_key_size); + if (!decrypted_key) + return log_oom_debug(); + + r = crypto_random_bytes(decrypted_key, decrypted_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to generate random key: %m"); + + r = rsa_encrypt_bytes(pkey, decrypted_key, decrypted_key_size, &saved_key, &saved_key_size); + if (r < 0) + return log_debug_errno(r, "Failed to encrypt random key: %m"); + + *ret_decrypted_key = TAKE_PTR(decrypted_key); + *ret_decrypted_key_size = decrypted_key_size; + *ret_saved_key = TAKE_PTR(saved_key); + *ret_saved_key_size = saved_key_size; + return 0; +} + +int pkey_generate_volume_keys( + EVP_PKEY *pkey, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size, + void **ret_saved_key, + size_t *ret_saved_key_size) { + + assert(pkey); + assert(ret_decrypted_key); + assert(ret_decrypted_key_size); + assert(ret_saved_key); + assert(ret_saved_key_size); + +#if OPENSSL_VERSION_MAJOR >= 3 + int type = EVP_PKEY_get_base_id(pkey); +#else + int type = EVP_PKEY_base_id(pkey); +#endif + switch (type) { + + case EVP_PKEY_RSA: + return rsa_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case EVP_PKEY_EC: + return ecc_pkey_generate_volume_keys(pkey, ret_decrypted_key, ret_decrypted_key_size, ret_saved_key, ret_saved_key_size); + + case NID_undef: + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine a type of public key."); + + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported public key type: %s", OBJ_nid2sn(type)); + } +} + +static int load_key_from_provider(const char *provider, const char *private_key_uri, EVP_PKEY **ret) { + + assert(provider); + assert(private_key_uri); + assert(ret); + +#if OPENSSL_VERSION_MAJOR >= 3 + /* Load the provider so that this can work without any custom written configuration in /etc/. + * Also load the 'default' as that seems to be the recommendation. */ + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, provider, /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider '%s'", provider); + if (!OSSL_PROVIDER_try_load(/* ctx= */ NULL, "default", /* retain_fallbacks= */ true)) + return log_openssl_errors("Failed to load OpenSSL provider 'default'"); + + _cleanup_(OSSL_STORE_closep) OSSL_STORE_CTX *store = OSSL_STORE_open( + private_key_uri, + /* ui_method= */ NULL, + /* ui_data= */ NULL, + /* post_process= */ NULL, + /* post_process_data= */ NULL); + if (!store) + return log_openssl_errors("Failed to open OpenSSL store via '%s'", private_key_uri); + + _cleanup_(OSSL_STORE_INFO_freep) OSSL_STORE_INFO *info = OSSL_STORE_load(store); + if (!info) + return log_openssl_errors("Failed to load OpenSSL store via '%s'", private_key_uri); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = OSSL_STORE_INFO_get1_PKEY(info); + if (!private_key) + return log_openssl_errors("Failed to load private key via '%s'", private_key_uri); + + *ret = TAKE_PTR(private_key); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static int load_key_from_engine(const char *engine, const char *private_key_uri, EVP_PKEY **ret) { + + assert(engine); + assert(private_key_uri); + assert(ret); + +#if !defined(OPENSSL_NO_ENGINE) && !defined(OPENSSL_NO_DEPRECATED_3_0) + DISABLE_WARNING_DEPRECATED_DECLARATIONS; + _cleanup_(ENGINE_freep) ENGINE *e = ENGINE_by_id(engine); + if (!e) + return log_openssl_errors("Failed to load signing engine '%s'", engine); + + if (ENGINE_init(e) == 0) + return log_openssl_errors("Failed to initialize signing engine '%s'", engine); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *private_key = ENGINE_load_private_key( + e, + private_key_uri, + /* ui_method= */ NULL, + /* callback_data= */ NULL); + if (!private_key) + return log_openssl_errors("Failed to load private key from '%s'", private_key_uri); + REENABLE_WARNING; + + *ret = TAKE_PTR(private_key); + + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +int openssl_load_key_from_token( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + EVP_PKEY **ret) { + + assert(IN_SET(private_key_source_type, OPENSSL_KEY_SOURCE_ENGINE, OPENSSL_KEY_SOURCE_PROVIDER)); + assert(private_key_source); + assert(private_key); + + switch (private_key_source_type) { + + case OPENSSL_KEY_SOURCE_ENGINE: + return load_key_from_engine(private_key_source, private_key, ret); + case OPENSSL_KEY_SOURCE_PROVIDER: + return load_key_from_provider(private_key_source, private_key, ret); + default: + assert_not_reached(); + } +} #endif int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { @@ -1144,6 +1416,37 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { sha256_direct(der, dersz, buffer); return 0; #else - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint: %m"); + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL is not supported, cannot calculate X509 fingerprint."); #endif } + +int parse_openssl_key_source_argument( + const char *argument, + char **private_key_source, + KeySourceType *private_key_source_type) { + + KeySourceType type; + const char *e = NULL; + int r; + + assert(argument); + assert(private_key_source); + assert(private_key_source_type); + + if (streq(argument, "file")) + type = OPENSSL_KEY_SOURCE_FILE; + else if ((e = startswith(argument, "engine:"))) + type = OPENSSL_KEY_SOURCE_ENGINE; + else if ((e = startswith(argument, "provider:"))) + type = OPENSSL_KEY_SOURCE_PROVIDER; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid private key source '%s'", argument); + + r = free_and_strdup_warn(private_key_source, e); + if (r < 0) + return r; + + *private_key_source_type = type; + + return 0; +} diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h index e3f34a8..1a89fcc 100644 --- a/src/shared/openssl-util.h +++ b/src/shared/openssl-util.h @@ -5,6 +5,16 @@ #include "macro.h" #include "sha256.h" +typedef enum KeySourceType { + OPENSSL_KEY_SOURCE_FILE, + OPENSSL_KEY_SOURCE_ENGINE, + OPENSSL_KEY_SOURCE_PROVIDER, + _OPENSSL_KEY_SOURCE_MAX, + _OPENSSL_KEY_SOURCE_INVALID = -EINVAL, +} KeySourceType; + +int parse_openssl_key_source_argument(const char *argument, char **private_key_source, KeySourceType *private_key_source_type); + #define X509_FINGERPRINT_SIZE SHA256_DIGEST_SIZE #if HAVE_OPENSSL @@ -25,6 +35,8 @@ # include # include # include +# include +# include # endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); @@ -40,6 +52,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(ASN1_OCTET_STRING*, ASN1_OCTET_STRING_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); @@ -49,6 +63,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_CTX*, OSSL_STORE_close, NULL); +DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_STORE_INFO*, OSSL_STORE_INFO_free, NULL); #else DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(HMAC_CTX*, HMAC_CTX_free, NULL); @@ -108,10 +124,14 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret); int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); +int pkey_generate_volume_keys(EVP_PKEY *pkey, void **ret_decrypted_key, size_t *ret_decrypted_key_size, void **ret_saved_key, size_t *ret_saved_key_size); + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); int digest_and_sign(const EVP_MD *md, EVP_PKEY *privkey, const void *data, size_t size, void **ret, size_t *ret_size); +int openssl_load_key_from_token(KeySourceType private_key_source_type, const char *private_key_source, const char *private_key, EVP_PKEY **ret); + #else typedef struct X509 X509; @@ -127,6 +147,15 @@ static inline void *EVP_PKEY_free(EVP_PKEY *p) { return NULL; } +static inline int openssl_load_key_from_token( + KeySourceType private_key_source_type, + const char *private_key_source, + const char *private_key, + EVP_PKEY **ret) { + + return -EOPNOTSUPP; +} + #endif DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509*, X509_free, NULL); diff --git a/src/shared/pager.c b/src/shared/pager.c index 19deefa..9b8ae76 100644 --- a/src/shared/pager.c +++ b/src/shared/pager.c @@ -175,7 +175,7 @@ void pager_open(PagerFlags flags) { * pager. If they didn't, use secure mode when under euid is changed. If $SYSTEMD_PAGERSECURE * wasn't explicitly set, and we autodetect the need for secure mode, only use the pager we * know to be good. */ - int use_secure_mode = getenv_bool_secure("SYSTEMD_PAGERSECURE"); + int use_secure_mode = secure_getenv_bool("SYSTEMD_PAGERSECURE"); bool trust_pager = use_secure_mode >= 0; if (use_secure_mode == -ENXIO) { uid_t uid; diff --git a/src/shared/pam-util.c b/src/shared/pam-util.c index f5814ef..3cbe431 100644 --- a/src/shared/pam-util.c +++ b/src/shared/pam-util.c @@ -14,6 +14,14 @@ #include "stdio-util.h" #include "string-util.h" +void pam_log_setup(void) { + /* Make sure we don't leak the syslog fd we open by opening/closing the fd each time. */ + log_set_open_when_needed(true); + + /* pam logs to syslog so let's make our generic logging functions do the same thing. */ + log_set_target(LOG_TARGET_SYSLOG); +} + int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) { va_list ap; @@ -96,7 +104,9 @@ static void pam_bus_data_destroy(pam_handle_t *handle, void *data, int error_sta if (FLAGS_SET(error_status, PAM_DATA_SILENT) && d->bus && bus_origin_changed(d->bus)) /* Please adjust test/units/end.sh when updating the log message. */ - pam_syslog(handle, LOG_DEBUG, "Attempted to close sd-bus after fork whose connection is opened before the fork, this should not happen."); + pam_syslog(handle, LOG_DEBUG, + "Warning: cannot close sd-bus connection (%s) after fork when it was opened before the fork.", + strna(d->cache_id)); pam_bus_data_free(data); } @@ -177,6 +187,8 @@ int pam_acquire_bus_connection( if (r != PAM_SUCCESS) return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to set PAM bus data: @PAMERR@"); + pam_syslog(handle, LOG_DEBUG, "New sd-bus connection (%s) opened.", d->cache_id); + success: *ret_bus = sd_bus_ref(d->bus); @@ -205,7 +217,99 @@ int pam_release_bus_connection(pam_handle_t *handle, const char *module_name) { return PAM_SUCCESS; } +int pam_get_bus_data( + pam_handle_t *handle, + const char *module_name, + PamBusData **ret) { + + PamBusData *d = NULL; + _cleanup_free_ char *cache_id = NULL; + int r; + + assert(handle); + assert(module_name); + assert(ret); + + cache_id = pam_make_bus_cache_id(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, cache_id, (const void**) &d); + if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) + return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get bus connection: @PAMERR@"); + + *ret = d; + return PAM_SUCCESS; +} + void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status) { /* A generic destructor for pam_set_data() that just frees the specified data */ free(data); } + +int pam_get_item_many_internal(pam_handle_t *handle, ...) { + va_list ap; + int r; + + va_start(ap, handle); + for (;;) { + int item_type = va_arg(ap, int); + + if (item_type <= 0) { + r = PAM_SUCCESS; + break; + } + + const void **value = ASSERT_PTR(va_arg(ap, const void **)); + + r = pam_get_item(handle, item_type, value); + if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) + break; + } + va_end(ap); + + return r; +} + +int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) { + va_list args; + int r; + + assert(handle); + assert(fmt); + + /* This is just like pam_prompt(), but does not noisily (i.e. beyond LOG_DEBUG) log on its own, but leaves that to the caller */ + + _cleanup_free_ char *msg = NULL; + va_start(args, fmt); + r = vasprintf(&msg, fmt, args); + va_end(args); + if (r < 0) + return PAM_BUF_ERR; + + const struct pam_conv *conv = NULL; + r = pam_get_item(handle, PAM_CONV, (const void**) &conv); + if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM)) + return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Failed to get conversation function structure: @PAMERR@"); + if (!conv || !conv->conv) { + pam_syslog(handle, LOG_DEBUG, "No conversation function."); + return PAM_SYSTEM_ERR; + } + + struct pam_message message = { + .msg_style = style, + .msg = msg, + }; + const struct pam_message *pmessage = &message; + _cleanup_free_ struct pam_response *response = NULL; + r = conv->conv(1, &pmessage, &response, conv->appdata_ptr); + _cleanup_(erase_and_freep) char *rr = response ? response->resp : NULL; /* make sure string is freed + erased */ + if (r != PAM_SUCCESS) + return pam_syslog_pam_error(handle, LOG_DEBUG, r, "Conversation function failed: @PAMERR@"); + + if (ret_response) + *ret_response = TAKE_PTR(rr); + + return PAM_SUCCESS; +} diff --git a/src/shared/pam-util.h b/src/shared/pam-util.h index 5a05fb7..d627eb7 100644 --- a/src/shared/pam-util.h +++ b/src/shared/pam-util.h @@ -5,6 +5,8 @@ #include "sd-bus.h" +void pam_log_setup(void); + int pam_syslog_errno(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5); int pam_syslog_pam_error(pam_handle_t *handle, int level, int error, const char *format, ...) _printf_(4,5); @@ -37,5 +39,12 @@ void pam_bus_data_disconnectp(PamBusData **d); * 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_bus, PamBusData **ret_bus_data); int pam_release_bus_connection(pam_handle_t *handle, const char *module_name); +int pam_get_bus_data(pam_handle_t *handle, const char *module_name, PamBusData **ret); void pam_cleanup_free(pam_handle_t *handle, void *data, int error_status); + +int pam_get_item_many_internal(pam_handle_t *handle, ...); + +#define pam_get_item_many(handle, ...) pam_get_item_many_internal(handle, __VA_ARGS__, -1) + +int pam_prompt_graceful(pam_handle_t *handle, int style, char **ret_response, const char *fmt, ...) _printf_(4,5); diff --git a/src/shared/parse-helpers.c b/src/shared/parse-helpers.c index 9664b9c..ca6842d 100644 --- a/src/shared/parse-helpers.c +++ b/src/shared/parse-helpers.c @@ -4,6 +4,7 @@ #include "extract-word.h" #include "ip-protocol-list.h" #include "log.h" +#include "mountpoint-util.h" #include "parse-helpers.h" #include "parse-util.h" #include "path-util.h" @@ -11,47 +12,56 @@ int path_simplify_and_warn( char *path, - unsigned flag, + PathSimplifyWarnFlags flags, const char *unit, const char *filename, unsigned line, const char *lvalue) { - bool fatal = flag & PATH_CHECK_FATAL; + bool fatal = flags & PATH_CHECK_FATAL; + int level = fatal ? LOG_ERR : LOG_WARNING; - assert(!FLAGS_SET(flag, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); + assert(path); + assert(!FLAGS_SET(flags, PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)); + assert(lvalue); if (!utf8_is_valid(path)) return log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, path); - if (flag & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { + if (flags & (PATH_CHECK_ABSOLUTE | PATH_CHECK_RELATIVE)) { bool absolute; absolute = path_is_absolute(path); - if (!absolute && (flag & PATH_CHECK_ABSOLUTE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + if (!absolute && (flags & PATH_CHECK_ABSOLUTE)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is not absolute%s: %s", lvalue, fatal ? "" : ", ignoring", path); - if (absolute && (flag & PATH_CHECK_RELATIVE)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + if (absolute && (flags & PATH_CHECK_RELATIVE)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is absolute%s: %s", lvalue, fatal ? "" : ", ignoring", path); } - path_simplify_full(path, flag & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0); + path_simplify_full(path, flags & PATH_KEEP_TRAILING_SLASH ? PATH_SIMPLIFY_KEEP_TRAILING_SLASH : 0); if (!path_is_valid(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path has invalid length (%zu bytes)%s.", lvalue, strlen(path), fatal ? "" : ", ignoring"); if (!path_is_normalized(path)) - return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), "%s= path is not normalized%s: %s", lvalue, fatal ? "" : ", ignoring", path); + if (FLAGS_SET(flags, PATH_CHECK_NON_API_VFS) && path_below_api_vfs(path)) + return log_syntax(unit, level, filename, line, SYNTHETIC_ERRNO(EINVAL), + "%s= path is below API VFS%s: %s", + lvalue, fatal ? ", refusing" : ", ignoring", + path); + return 0; } @@ -102,6 +112,8 @@ static int parse_ip_ports_token( uint16_t *nr_ports, uint16_t *port_min) { + int r; + assert(token); assert(nr_ports); assert(port_min); @@ -110,7 +122,7 @@ static int parse_ip_ports_token( *nr_ports = *port_min = 0; else { uint16_t mn = 0, mx = 0; - int r = parse_ip_port_range(token, &mn, &mx); + r = parse_ip_port_range(token, &mn, &mx, /* allow_zero = */ true); if (r < 0) return r; @@ -194,6 +206,7 @@ int parse_socket_bind_item( *ip_protocol = proto; *nr_ports = nr; *port_min = mn; + return 0; } diff --git a/src/shared/parse-helpers.h b/src/shared/parse-helpers.h index 3e4ad3c..6d1034b 100644 --- a/src/shared/parse-helpers.h +++ b/src/shared/parse-helpers.h @@ -3,27 +3,28 @@ #include -enum { - PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ - PATH_CHECK_ABSOLUTE = 1 << 1, - PATH_CHECK_RELATIVE = 1 << 2, +typedef enum PathSimplifyWarnFlags { + PATH_CHECK_FATAL = 1 << 0, /* If not set, then error message is appended with 'ignoring'. */ + PATH_CHECK_ABSOLUTE = 1 << 1, + PATH_CHECK_RELATIVE = 1 << 2, PATH_KEEP_TRAILING_SLASH = 1 << 3, -}; + PATH_CHECK_NON_API_VFS = 1 << 4, +} PathSimplifyWarnFlags; int path_simplify_and_warn( char *path, - unsigned flag, + PathSimplifyWarnFlags flags, const char *unit, const char *filename, unsigned line, const char *lvalue); int parse_socket_bind_item( - const char *str, - int *address_family, - int *ip_protocol, - uint16_t *nr_ports, - uint16_t *port_min); + const char *str, + int *address_family, + int *ip_protocol, + uint16_t *nr_ports, + uint16_t *port_min); int config_parse_path_or_ignore( const char *unit, diff --git a/src/shared/password-quality-util-passwdqc.c b/src/shared/password-quality-util-passwdqc.c index adfc14d..764b772 100644 --- a/src/shared/password-quality-util-passwdqc.c +++ b/src/shared/password-quality-util-passwdqc.c @@ -12,14 +12,19 @@ static void *passwdqc_dl = NULL; -void (*sym_passwdqc_params_reset)(passwdqc_params_t *params); -int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname); -int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv); -void (*sym_passwdqc_params_free)(passwdqc_params_t *params); -const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw); -char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params); +DLSYM_FUNCTION(passwdqc_params_reset); +DLSYM_FUNCTION(passwdqc_params_load); +DLSYM_FUNCTION(passwdqc_params_parse); +DLSYM_FUNCTION(passwdqc_params_free); +DLSYM_FUNCTION(passwdqc_check); +DLSYM_FUNCTION(passwdqc_random); int dlopen_passwdqc(void) { + ELF_NOTE_DLOPEN("passwdqc", + "Support for password quality checks", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpasswdqc.so.1"); + return dlopen_many_sym_or_warn( &passwdqc_dl, "libpasswdqc.so.1", LOG_DEBUG, DLSYM_ARG(passwdqc_params_reset), diff --git a/src/shared/password-quality-util-passwdqc.h b/src/shared/password-quality-util-passwdqc.h index 0d528d2..9911888 100644 --- a/src/shared/password-quality-util-passwdqc.h +++ b/src/shared/password-quality-util-passwdqc.h @@ -6,12 +6,14 @@ #if HAVE_PASSWDQC #include -extern void (*sym_passwdqc_params_reset)(passwdqc_params_t *params); -extern int (*sym_passwdqc_params_load)(passwdqc_params_t *params, char **reason, const char *pathname); -extern int (*sym_passwdqc_params_parse)(passwdqc_params_t *params, char **reason, int argc, const char *const *argv); -extern void (*sym_passwdqc_params_free)(passwdqc_params_t *params); -extern const char *(*sym_passwdqc_check)(const passwdqc_params_qc_t *params, const char *newpass, const char *oldpass, const struct passwd *pw); -extern char *(*sym_passwdqc_random)(const passwdqc_params_qc_t *params); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(passwdqc_params_reset); +DLSYM_PROTOTYPE(passwdqc_params_load); +DLSYM_PROTOTYPE(passwdqc_params_parse); +DLSYM_PROTOTYPE(passwdqc_params_free); +DLSYM_PROTOTYPE(passwdqc_check); +DLSYM_PROTOTYPE(passwdqc_random); int dlopen_passwdqc(void); diff --git a/src/shared/password-quality-util-pwquality.c b/src/shared/password-quality-util-pwquality.c index 80f7d58..7456469 100644 --- a/src/shared/password-quality-util-pwquality.c +++ b/src/shared/password-quality-util-pwquality.c @@ -14,16 +14,21 @@ static void *pwquality_dl = NULL; -int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); -pwquality_settings_t *(*sym_pwquality_default_settings)(void); -void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); -int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); -int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); -int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); -int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); -const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); +DLSYM_FUNCTION(pwquality_check); +DLSYM_FUNCTION(pwquality_default_settings); +DLSYM_FUNCTION(pwquality_free_settings); +DLSYM_FUNCTION(pwquality_generate); +DLSYM_FUNCTION(pwquality_get_str_value); +DLSYM_FUNCTION(pwquality_read_config); +DLSYM_FUNCTION(pwquality_set_int_value); +DLSYM_FUNCTION(pwquality_strerror); int dlopen_pwquality(void) { + ELF_NOTE_DLOPEN("pwquality", + "Support for password quality checks", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpwquality.so.1"); + return dlopen_many_sym_or_warn( &pwquality_dl, "libpwquality.so.1", LOG_DEBUG, DLSYM_ARG(pwquality_check), @@ -101,7 +106,6 @@ int suggest_passwords(void) { _cleanup_strv_free_erase_ char **suggestions = NULL; _cleanup_(erase_and_freep) char *joined = NULL; char buf[PWQ_MAX_ERROR_MESSAGE_LEN]; - size_t i; int r; r = pwq_allocate_context(&pwq); @@ -115,7 +119,7 @@ int suggest_passwords(void) { if (!suggestions) return log_oom(); - for (i = 0; i < N_SUGGESTIONS; i++) { + for (size_t i = 0; i < N_SUGGESTIONS; i++) { r = sym_pwquality_generate(pwq, 64, suggestions + i); if (r < 0) return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s", @@ -145,13 +149,10 @@ int check_password_quality(const char *password, const char *old, const char *us r = sym_pwquality_check(pwq, password, old, username, &auxerror); if (r < 0) { if (ret_error) { - _cleanup_free_ char *e = NULL; - - e = strdup(sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); - if (!e) - return -ENOMEM; - - *ret_error = TAKE_PTR(e); + r = strdup_to(ret_error, + sym_pwquality_strerror(buf, sizeof(buf), r, auxerror)); + if (r < 0) + return r; } return 0; /* all bad */ diff --git a/src/shared/password-quality-util-pwquality.h b/src/shared/password-quality-util-pwquality.h index a420b0d..4c2517b 100644 --- a/src/shared/password-quality-util-pwquality.h +++ b/src/shared/password-quality-util-pwquality.h @@ -8,14 +8,16 @@ #include #include -extern int (*sym_pwquality_check)(pwquality_settings_t *pwq, const char *password, const char *oldpassword, const char *user, void **auxerror); -extern pwquality_settings_t *(*sym_pwquality_default_settings)(void); -extern void (*sym_pwquality_free_settings)(pwquality_settings_t *pwq); -extern int (*sym_pwquality_generate)(pwquality_settings_t *pwq, int entropy_bits, char **password); -extern int (*sym_pwquality_get_str_value)(pwquality_settings_t *pwq, int setting, const char **value); -extern int (*sym_pwquality_read_config)(pwquality_settings_t *pwq, const char *cfgfile, void **auxerror); -extern int (*sym_pwquality_set_int_value)(pwquality_settings_t *pwq, int setting, int value); -extern const char* (*sym_pwquality_strerror)(char *buf, size_t len, int errcode, void *auxerror); +#include "dlfcn-util.h" + +DLSYM_PROTOTYPE(pwquality_check); +DLSYM_PROTOTYPE(pwquality_default_settings); +DLSYM_PROTOTYPE(pwquality_free_settings); +DLSYM_PROTOTYPE(pwquality_generate); +DLSYM_PROTOTYPE(pwquality_get_str_value); +DLSYM_PROTOTYPE(pwquality_read_config); +DLSYM_PROTOTYPE(pwquality_set_int_value); +DLSYM_PROTOTYPE(pwquality_strerror); int dlopen_pwquality(void); diff --git a/src/shared/pcre2-util.c b/src/shared/pcre2-util.c index 578b02d..7deb64f 100644 --- a/src/shared/pcre2-util.c +++ b/src/shared/pcre2-util.c @@ -7,13 +7,13 @@ #if HAVE_PCRE2 static void *pcre2_dl = NULL; -pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *); -void (*sym_pcre2_match_data_free)(pcre2_match_data *); -void (*sym_pcre2_code_free)(pcre2_code *); -pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *); -int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE); -int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *); -PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *); +DLSYM_FUNCTION(pcre2_match_data_create); +DLSYM_FUNCTION(pcre2_match_data_free); +DLSYM_FUNCTION(pcre2_code_free); +DLSYM_FUNCTION(pcre2_compile); +DLSYM_FUNCTION(pcre2_get_error_message); +DLSYM_FUNCTION(pcre2_match); +DLSYM_FUNCTION(pcre2_get_ovector_pointer); DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR( pcre2_code_hash_ops_free, @@ -27,6 +27,11 @@ const struct hash_ops pcre2_code_hash_ops_free = {}; int dlopen_pcre2(void) { #if HAVE_PCRE2 + ELF_NOTE_DLOPEN("pcre2", + "Support for regular expressions", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libpcre2-8.so.0"); + /* So here's something weird: PCRE2 actually renames the symbols exported by the library via C * macros, so that the exported symbols carry a suffix "_8" but when used from C the suffix is * gone. In the argument list below we ignore this mangling. Surprisingly (at least to me), we diff --git a/src/shared/pcre2-util.h b/src/shared/pcre2-util.h index f1e744d..27e2b02 100644 --- a/src/shared/pcre2-util.h +++ b/src/shared/pcre2-util.h @@ -6,16 +6,18 @@ #if HAVE_PCRE2 +#include "dlfcn-util.h" + #define PCRE2_CODE_UNIT_WIDTH 8 #include -extern pcre2_match_data* (*sym_pcre2_match_data_create)(uint32_t, pcre2_general_context *); -extern void (*sym_pcre2_match_data_free)(pcre2_match_data *); -extern void (*sym_pcre2_code_free)(pcre2_code *); -extern pcre2_code* (*sym_pcre2_compile)(PCRE2_SPTR, PCRE2_SIZE, uint32_t, int *, PCRE2_SIZE *, pcre2_compile_context *); -extern int (*sym_pcre2_get_error_message)(int, PCRE2_UCHAR *, PCRE2_SIZE); -extern int (*sym_pcre2_match)(const pcre2_code *, PCRE2_SPTR, PCRE2_SIZE, PCRE2_SIZE, uint32_t, pcre2_match_data *, pcre2_match_context *); -extern PCRE2_SIZE* (*sym_pcre2_get_ovector_pointer)(pcre2_match_data *); +DLSYM_PROTOTYPE(pcre2_match_data_create); +DLSYM_PROTOTYPE(pcre2_match_data_free); +DLSYM_PROTOTYPE(pcre2_code_free); +DLSYM_PROTOTYPE(pcre2_compile); +DLSYM_PROTOTYPE(pcre2_get_error_message); +DLSYM_PROTOTYPE(pcre2_match); +DLSYM_PROTOTYPE(pcre2_get_ovector_pointer); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_match_data*, sym_pcre2_match_data_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(pcre2_code*, sym_pcre2_code_free, NULL); diff --git a/src/shared/pcrextend-util.c b/src/shared/pcrextend-util.c index fa066a4..5ec9a06 100644 --- a/src/shared/pcrextend-util.c +++ b/src/shared/pcrextend-util.c @@ -101,7 +101,7 @@ int pcrextend_file_system_word(const char *path, char **ret_word, char **ret_nor if (r < 0) return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized_path); if (r == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized_path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing.", normalized_path); normalized_escaped = xescape(normalized_path, ":"); /* Avoid ambiguity around ":" */ if (!normalized_escaped) diff --git a/src/shared/pe-binary.c b/src/shared/pe-binary.c index 4c05323..997e0e4 100644 --- a/src/shared/pe-binary.c +++ b/src/shared/pe-binary.c @@ -234,8 +234,9 @@ bool pe_is_uki(const PeHeader *pe_header, const IMAGE_SECTION_HEADER *sections) if (le16toh(pe_header->optional.Subsystem) != IMAGE_SUBSYSTEM_EFI_APPLICATION) return false; + /* Note that the UKI spec only requires .linux, but we are stricter here, and require .osrel too, + * since for sd-boot it just doesn't make sense to not have that. */ return pe_header_find_section(pe_header, sections, ".osrel") && - pe_header_find_section(pe_header, sections, ".linux") && - pe_header_find_section(pe_header, sections, ".initrd"); + pe_header_find_section(pe_header, sections, ".linux"); } diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 6e88dc3..b5cd9a3 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -43,22 +43,29 @@ bool pkcs11_uri_valid(const char *uri) { static void *p11kit_dl = NULL; -char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module); -void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules); -CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags); -const char *(*sym_p11_kit_strerror)(CK_RV rv); -int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string); -void (*sym_p11_kit_uri_free)(P11KitUri *uri); -CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs); -CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri); -CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri); -CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri); -int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info); -const char *(*sym_p11_kit_uri_message)(int code); -P11KitUri *(*sym_p11_kit_uri_new)(void); -int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri); +DLSYM_FUNCTION(p11_kit_module_get_name); +DLSYM_FUNCTION(p11_kit_modules_finalize_and_release); +DLSYM_FUNCTION(p11_kit_modules_load_and_initialize); +DLSYM_FUNCTION(p11_kit_strerror); +DLSYM_FUNCTION(p11_kit_uri_format); +DLSYM_FUNCTION(p11_kit_uri_free); +DLSYM_FUNCTION(p11_kit_uri_get_attributes); +DLSYM_FUNCTION(p11_kit_uri_get_attribute); +DLSYM_FUNCTION(p11_kit_uri_set_attribute); +DLSYM_FUNCTION(p11_kit_uri_get_module_info); +DLSYM_FUNCTION(p11_kit_uri_get_slot_info); +DLSYM_FUNCTION(p11_kit_uri_get_token_info); +DLSYM_FUNCTION(p11_kit_uri_match_token_info); +DLSYM_FUNCTION(p11_kit_uri_message); +DLSYM_FUNCTION(p11_kit_uri_new); +DLSYM_FUNCTION(p11_kit_uri_parse); int dlopen_p11kit(void) { + ELF_NOTE_DLOPEN("p11-kit", + "Support for PKCS11 hardware tokens", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libp11-kit.so.0"); + return dlopen_many_sym_or_warn( &p11kit_dl, "libp11-kit.so.0", LOG_DEBUG, @@ -69,6 +76,8 @@ int dlopen_p11kit(void) { DLSYM_ARG(p11_kit_uri_format), DLSYM_ARG(p11_kit_uri_free), DLSYM_ARG(p11_kit_uri_get_attributes), + DLSYM_ARG(p11_kit_uri_get_attribute), + DLSYM_ARG(p11_kit_uri_set_attribute), DLSYM_ARG(p11_kit_uri_get_module_info), DLSYM_ARG(p11_kit_uri_get_slot_info), DLSYM_ARG(p11_kit_uri_get_token_info), @@ -287,12 +296,11 @@ int pkcs11_token_login( CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, - const char *icon_name, - const char *key_name, - const char *credential_name, + const char *askpw_icon, + const char *askpw_keyring, + const char *askpw_credential, usec_t until, - AskPasswordFlags ask_password_flags, - bool headless, + AskPasswordFlags askpw_flags, char **ret_used_pin) { _cleanup_free_ char *token_uri_string = NULL, *token_uri_escaped = NULL, *id = NULL, *token_label = NULL; @@ -347,7 +355,7 @@ int pkcs11_token_login( if (!passwords) return log_oom(); - } else if (headless) + } else if (FLAGS_SET(askpw_flags, ASK_PASSWORD_HEADLESS)) return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "PIN querying disabled via 'headless' option. Use the 'PIN' environment variable."); else { _cleanup_free_ char *text = NULL; @@ -371,8 +379,16 @@ int pkcs11_token_login( if (r < 0) return log_oom(); + AskPasswordRequest req = { + .message = text, + .icon = askpw_icon, + .id = id, + .keyring = askpw_keyring, + .credential = askpw_credential, + }; + /* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */ - r = ask_password_auto(text, icon_name, id, key_name, credential_name, until, ask_password_flags, &passwords); + r = ask_password_auto(&req, until, askpw_flags, &passwords); if (r < 0) return log_error_errno(r, "Failed to query PIN for security token '%s': %m", token_label); } @@ -523,13 +539,294 @@ int pkcs11_token_find_x509_certificate( } #if HAVE_OPENSSL +static int read_public_key_info( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + EVP_PKEY **ret_pkey) { + + CK_ATTRIBUTE attribute = { CKA_PUBLIC_KEY_INFO, NULL_PTR, 0 }; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_RV rv; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Failed to get size of CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); + + if (attribute.ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "CKA_PUBLIC_KEY_INFO is empty"); + + _cleanup_free_ void *buffer = malloc(attribute.ulValueLen); + if (!buffer) + return log_oom_debug(); + + attribute.pValue = buffer; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to read CKA_PUBLIC_KEY_INFO: %s", sym_p11_kit_strerror(rv)); + + const unsigned char *value = attribute.pValue; + pkey = d2i_PUBKEY(NULL, &value, attribute.ulValueLen); + if (!pkey) + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse CKA_PUBLIC_KEY_INFO"); + + *ret_pkey = TAKE_PTR(pkey); + return 0; +} + +int pkcs11_token_read_public_key( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + EVP_PKEY **ret_pkey) { + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_RV rv; + int r; + + r = read_public_key_info(m, session, object, &pkey); + if (r >= 0) { + *ret_pkey = TAKE_PTR(pkey); + return 0; + } + + CK_KEY_TYPE key_type; + CK_ATTRIBUTE attribute = { CKA_KEY_TYPE, &key_type, sizeof(key_type) }; + + rv = m->C_GetAttributeValue(session, object, &attribute, 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get CKA_KEY_TYPE of a public key: %s", sym_p11_kit_strerror(rv)); + + switch (key_type) { + case CKK_RSA: { + CK_ATTRIBUTE rsa_attributes[] = { + { CKA_MODULUS, NULL_PTR, 0 }, + { CKA_PUBLIC_EXPONENT, NULL_PTR, 0 }, + }; + + rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get size of attributes of an RSA public key: %s", sym_p11_kit_strerror(rv)); + + if (rsa_attributes[0].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_MODULUS."); + + _cleanup_free_ void *modulus = malloc(rsa_attributes[0].ulValueLen); + if (!modulus) + return log_oom_debug(); + + rsa_attributes[0].pValue = modulus; + + if (rsa_attributes[1].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An RSA public key has empty CKA_PUBLIC_EXPONENT."); + + _cleanup_free_ void *public_exponent = malloc(rsa_attributes[1].ulValueLen); + if (!public_exponent) + return log_oom_debug(); + + rsa_attributes[1].pValue = public_exponent; + + rv = m->C_GetAttributeValue(session, object, rsa_attributes, ELEMENTSOF(rsa_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of an RSA public key: %s", sym_p11_kit_strerror(rv)); + + size_t n_size = rsa_attributes[0].ulValueLen, e_size = rsa_attributes[1].ulValueLen; + r = rsa_pkey_from_n_e(rsa_attributes[0].pValue, n_size, rsa_attributes[1].pValue, e_size, &pkey); + if (r < 0) + return log_debug_errno(r, "Failed to create an EVP_PKEY from RSA parameters."); + + break; + } + case CKK_EC: { + CK_ATTRIBUTE ec_attributes[] = { + { CKA_EC_PARAMS, NULL_PTR, 0 }, + { CKA_EC_POINT, NULL_PTR, 0 }, + }; + + rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get size of attributes of an EC public key: %s", sym_p11_kit_strerror(rv)); + + if (ec_attributes[0].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_PARAMS."); + + _cleanup_free_ void *ec_group = malloc(ec_attributes[0].ulValueLen); + if (!ec_group) + return log_oom_debug(); + + ec_attributes[0].pValue = ec_group; + + if (ec_attributes[1].ulValueLen == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "An EC public key has empty CKA_EC_POINT."); + + _cleanup_free_ void *ec_point = malloc(ec_attributes[1].ulValueLen); + if (!ec_point) + return log_oom_debug(); + + ec_attributes[1].pValue = ec_point; + + rv = m->C_GetAttributeValue(session, object, ec_attributes, ELEMENTSOF(ec_attributes)); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of an EC public key: %s", sym_p11_kit_strerror(rv)); + + _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL; + _cleanup_(ASN1_OCTET_STRING_freep) ASN1_OCTET_STRING *os = NULL; + + const unsigned char *ec_params_value = ec_attributes[0].pValue; + group = d2i_ECPKParameters(NULL, &ec_params_value, ec_attributes[0].ulValueLen); + if (!group) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS."); + + const unsigned char *ec_point_value = ec_attributes[1].pValue; + os = d2i_ASN1_OCTET_STRING(NULL, &ec_point_value, ec_attributes[1].ulValueLen); + if (!os) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); + +#if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); + if (!ctx) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create an EVP_PKEY_CTX for EC."); + + if (EVP_PKEY_fromdata_init(ctx) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to init an EVP_PKEY_CTX for EC."); + + OSSL_PARAM ec_params[8] = { + OSSL_PARAM_octet_string(OSSL_PKEY_PARAM_PUB_KEY, os->data, os->length) + }; + + _cleanup_free_ void *order = NULL, *p = NULL, *a = NULL, *b = NULL, *generator = NULL; + size_t order_size, p_size, a_size, b_size, generator_size; + + int nid = EC_GROUP_get_curve_name(group); + if (nid != NID_undef) { + const char* name = OSSL_EC_curve_nid2name(nid); + ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, (char*)name, strlen(name)); + ec_params[2] = OSSL_PARAM_construct_end(); + } else { + const char *field_type = EC_GROUP_get_field_type(group) == NID_X9_62_prime_field ? + "prime-field" : "characteristic-two-field"; + + const BIGNUM *bn_order = EC_GROUP_get0_order(group); + + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = BN_CTX_new(); + if (!bnctx) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_p = BN_new(); + if (!bn_p) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_a = BN_new(); + if (!bn_a) + return log_oom_debug(); + + _cleanup_(BN_freep) BIGNUM *bn_b = BN_new(); + if (!bn_b) + return log_oom_debug(); + + if (EC_GROUP_get_curve(group, bn_p, bn_a, bn_b, bnctx) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract EC parameters from EC_GROUP."); + + order_size = BN_num_bytes(bn_order); + p_size = BN_num_bytes(bn_p); + a_size = BN_num_bytes(bn_a); + b_size = BN_num_bytes(bn_b); + + order = malloc(order_size); + if (!order) + return log_oom_debug(); + + p = malloc(p_size); + if (!p) + return log_oom_debug(); + + a = malloc(a_size); + if (!a) + return log_oom_debug(); + + b = malloc(b_size); + if (!b) + return log_oom_debug(); + + if (BN_bn2nativepad(bn_order, order, order_size) <= 0 || + BN_bn2nativepad(bn_p, p, p_size) <= 0 || + BN_bn2nativepad(bn_a, a, a_size) <= 0 || + BN_bn2nativepad(bn_b, b, b_size) <= 0 ) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to store EC parameters in native byte order."); + + const EC_POINT *point_gen = EC_GROUP_get0_generator(group); + generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, NULL, 0, bnctx); + if (generator_size == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a EC generator."); + + generator = malloc(generator_size); + if (!generator) + return log_oom_debug(); + + generator_size = EC_POINT_point2oct(group, point_gen, POINT_CONVERSION_UNCOMPRESSED, generator, generator_size, bnctx); + if (generator_size == 0) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC generator to octet string."); + + ec_params[1] = OSSL_PARAM_construct_utf8_string(OSSL_PKEY_PARAM_EC_FIELD_TYPE, (char*)field_type, strlen(field_type)); + ec_params[2] = OSSL_PARAM_construct_octet_string(OSSL_PKEY_PARAM_EC_GENERATOR, generator, generator_size); + ec_params[3] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_ORDER, order, order_size); + ec_params[4] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_P, p, p_size); + ec_params[5] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_A, a, a_size); + ec_params[6] = OSSL_PARAM_construct_BN(OSSL_PKEY_PARAM_EC_B, b, b_size); + ec_params[7] = OSSL_PARAM_construct_end(); + } + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, ec_params) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create EVP_PKEY from EC parameters."); +#else + _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + if (!point) + return log_oom_debug(); + + if (EC_POINT_oct2point(group, point, os->data, os->length, NULL) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_POINT."); + + _cleanup_(EC_KEY_freep) EC_KEY *ec_key = EC_KEY_new(); + if (!ec_key) + return log_oom_debug(); + + if (EC_KEY_set_group(ec_key, group) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set group for EC_KEY."); + + if (EC_KEY_set_public_key(ec_key, point) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set public key for EC_KEY."); + + pkey = EVP_PKEY_new(); + if (!pkey) + return log_oom_debug(); + + if (EVP_PKEY_set1_EC_KEY(pkey, ec_key) != 1) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign EC_KEY to EVP_PKEY."); +#endif + break; + } + default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported type of public key: %lu", key_type); + } + + *ret_pkey = TAKE_PTR(pkey); + return 0; +} + int pkcs11_token_read_x509_certificate( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert) { - _cleanup_free_ void *buffer = NULL; _cleanup_free_ char *t = NULL; CK_ATTRIBUTE attribute = { .type = CKA_VALUE @@ -537,7 +834,6 @@ int pkcs11_token_read_x509_certificate( CK_RV rv; _cleanup_(X509_freep) X509 *x509 = NULL; X509_NAME *name = NULL; - const unsigned char *p; int r; r = dlopen_p11kit(); @@ -546,32 +842,32 @@ int pkcs11_token_read_x509_certificate( rv = m->C_GetAttributeValue(session, object, &attribute, 1); if (rv != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate size off token: %s", sym_p11_kit_strerror(rv)); - buffer = malloc(attribute.ulValueLen); + _cleanup_free_ void *buffer = malloc(attribute.ulValueLen); if (!buffer) - return log_oom(); + return log_oom_debug(); attribute.pValue = buffer; rv = m->C_GetAttributeValue(session, object, &attribute, 1); if (rv != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to read X.509 certificate data off token: %s", sym_p11_kit_strerror(rv)); - p = attribute.pValue; + const unsigned char *p = attribute.pValue; x509 = d2i_X509(NULL, &p, attribute.ulValueLen); if (!x509) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed parse X.509 certificate."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to parse X.509 certificate."); name = X509_get_subject_name(x509); if (!name) - return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); + return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Failed to acquire X.509 subject name."); t = X509_NAME_oneline(name, NULL, 0); if (!t) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to format X.509 subject name as string."); log_debug("Using X.509 certificate issued for '%s'.", t); @@ -586,143 +882,429 @@ int pkcs11_token_find_private_key( P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object) { - bool found_decrypt = false, found_class = false, found_key_type = false; + bool found_class = false; _cleanup_free_ CK_ATTRIBUTE *attributes_buffer = NULL; - CK_ULONG n_attributes, a, n_objects; - CK_ATTRIBUTE *attributes = NULL; - CK_OBJECT_HANDLE objects[2]; - CK_RV rv, rv2; - int r; + CK_KEY_TYPE key_type; + CK_BBOOL decrypt_value, derive_value; + CK_ATTRIBUTE optional_attributes[] = { + { CKA_KEY_TYPE, &key_type, sizeof(key_type) }, + { CKA_DECRYPT, &decrypt_value, sizeof(decrypt_value) }, + { CKA_DERIVE, &derive_value, sizeof(derive_value) }, + }; + uint8_t n_private_keys = 0; + CK_OBJECT_HANDLE private_key = CK_INVALID_HANDLE; + CK_RV rv; assert(m); assert(search_uri); assert(ret_object); - r = dlopen_p11kit(); - if (r < 0) - return r; - - attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes); - for (a = 0; a < n_attributes; a++) { + CK_ULONG n_attributes; + CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(search_uri, &n_attributes); + for (CK_ULONG i = 0; i < n_attributes; i++) { /* We use the URI's included match attributes, but make them more strict. This allows users * to specify a token URL instead of an object URL and the right thing should happen if * there's only one suitable key on the token. */ - switch (attributes[a].type) { - + switch (attributes[i].type) { case CKA_CLASS: { - CK_OBJECT_CLASS c; - - if (attributes[a].ulValueLen != sizeof(c)) + if (attributes[i].ulValueLen != sizeof(CK_OBJECT_CLASS)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_CLASS attribute size."); - memcpy(&c, attributes[a].pValue, sizeof(c)); - if (c != CKO_PRIVATE_KEY) + CK_OBJECT_CLASS *class = (CK_OBJECT_CLASS*) attributes[i].pValue; + if (*class != CKO_PRIVATE_KEY) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not a private key, refusing."); found_class = true; break; - } + }} + } - case CKA_DECRYPT: { - CK_BBOOL b; + if (!found_class) { + /* Hmm, let's slightly extend the attribute list we search for */ + static const CK_OBJECT_CLASS required_class = CKO_PRIVATE_KEY; - if (attributes[a].ulValueLen != sizeof(b)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_DECRYPT attribute size."); + attributes_buffer = new(CK_ATTRIBUTE, n_attributes + 1); + if (!attributes_buffer) + return log_oom(); - memcpy(&b, attributes[a].pValue, sizeof(b)); - if (!b) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Selected PKCS#11 object is not suitable for decryption, refusing."); + memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes); + + attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { + .type = CKA_CLASS, + .pValue = (CK_OBJECT_CLASS*) &required_class, + .ulValueLen = sizeof(required_class), + }; + + attributes = attributes_buffer; + } - found_decrypt = true; + rv = m->C_FindObjectsInit(session, attributes, n_attributes); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + + for (;;) { + CK_ULONG b; + CK_OBJECT_HANDLE candidate; + rv = m->C_FindObjects(session, &candidate, 1, &b); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + if (b == 0) break; - } - case CKA_KEY_TYPE: { - CK_KEY_TYPE t; + optional_attributes[0].ulValueLen = sizeof(key_type); + optional_attributes[1].ulValueLen = sizeof(decrypt_value); + optional_attributes[2].ulValueLen = sizeof(derive_value); - if (attributes[a].ulValueLen != sizeof(t)) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PKCS#11 CKA_KEY_TYPE attribute size."); + rv = m->C_GetAttributeValue(session, candidate, optional_attributes, ELEMENTSOF(optional_attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of a found private key: %s", sym_p11_kit_strerror(rv)); - memcpy(&t, attributes[a].pValue, sizeof(t)); - if (t != CKK_RSA) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Selected PKCS#11 object is not an RSA key, refusing."); + if (optional_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("A found private key does not have CKA_KEY_TYPE, rejecting the key."); + continue; + } + + if (key_type == CKK_RSA) + if (optional_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION || decrypt_value == CK_FALSE) { + log_debug("A found private RSA key can't decrypt, rejecting the key."); + continue; + } - found_key_type = true; + if (key_type == CKK_EC) + if (optional_attributes[2].ulValueLen == CK_UNAVAILABLE_INFORMATION || derive_value == CK_FALSE) { + log_debug("A found private EC key can't derive, rejecting the key."); + continue; + } + + n_private_keys++; + if (n_private_keys > 1) break; - }} + private_key = candidate; } - if (!found_decrypt || !found_class || !found_key_type) { - /* Hmm, let's slightly extend the attribute list we search for */ + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); - attributes_buffer = new(CK_ATTRIBUTE, n_attributes + !found_decrypt + !found_class + !found_key_type); - if (!attributes_buffer) + if (n_private_keys == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find selected private key suitable for decryption or derivation on token."); + + if (n_private_keys > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Configured private key URI matches multiple keys, refusing."); + + *ret_object = private_key; + return 0; +} + +static const char* object_class_to_string(CK_OBJECT_CLASS class) { + switch (class) { + case CKO_CERTIFICATE: + return "CKO_CERTIFICATE"; + case CKO_PUBLIC_KEY: + return "CKO_PUBLIC_KEY"; + case CKO_PRIVATE_KEY: + return "CKO_PRIVATE_KEY"; + case CKO_SECRET_KEY: + return "CKO_SECRET_KEY"; + default: + return NULL; + } +} + +/* Returns an object with the given class and the same CKA_ID or CKA_LABEL as prototype */ +int pkcs11_token_find_related_object( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE prototype, + CK_OBJECT_CLASS class, + CK_OBJECT_HANDLE *ret_object ) { + + _cleanup_free_ void *buffer = NULL; + CK_ATTRIBUTE attributes[] = { + { CKA_ID, NULL_PTR, 0 }, + { CKA_LABEL, NULL_PTR, 0 } + }; + CK_OBJECT_CLASS search_class = class; + CK_ATTRIBUTE search_attributes[2] = { + { CKA_CLASS, &search_class, sizeof(search_class) } + }; + CK_ULONG n_objects; + CK_OBJECT_HANDLE objects[2]; + CK_RV rv; + + rv = m->C_GetAttributeValue(session, prototype, attributes, ELEMENTSOF(attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve length of attributes: %s", sym_p11_kit_strerror(rv)); + + if (attributes[0].ulValueLen != CK_UNAVAILABLE_INFORMATION) { + buffer = malloc(attributes[0].ulValueLen); + if (!buffer) return log_oom(); - memcpy(attributes_buffer, attributes, sizeof(CK_ATTRIBUTE) * n_attributes); + attributes[0].pValue = buffer; + rv = m->C_GetAttributeValue(session, prototype, &attributes[0], 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_ID: %s", sym_p11_kit_strerror(rv)); - if (!found_decrypt) { - static const CK_BBOOL yes = true; + search_attributes[1] = attributes[0]; - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_DECRYPT, - .pValue = (CK_BBOOL*) &yes, - .ulValueLen = sizeof(yes), - }; - } + } else if (attributes[1].ulValueLen != CK_UNAVAILABLE_INFORMATION) { + buffer = malloc(attributes[1].ulValueLen); + if (!buffer) + return log_oom(); - if (!found_class) { - static const CK_OBJECT_CLASS class = CKO_PRIVATE_KEY; + attributes[1].pValue = buffer; + rv = m->C_GetAttributeValue(session, prototype, &attributes[1], 1); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_LABEL: %s", sym_p11_kit_strerror(rv)); - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_CLASS, - .pValue = (CK_OBJECT_CLASS*) &class, - .ulValueLen = sizeof(class), - }; - } + search_attributes[1] = attributes[1]; - if (!found_key_type) { - static const CK_KEY_TYPE type = CKK_RSA; + } else + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "The prototype does not have CKA_ID or CKA_LABEL"); - attributes_buffer[n_attributes++] = (CK_ATTRIBUTE) { - .type = CKA_KEY_TYPE, - .pValue = (CK_KEY_TYPE*) &type, - .ulValueLen = sizeof(type), - }; - } + rv = m->C_FindObjectsInit(session, search_attributes, 2); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); - attributes = attributes_buffer; + rv = m->C_FindObjects(session, objects, 2, &n_objects); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); + + if (n_objects == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find a related object with class %s", object_class_to_string(class)); + + if (n_objects > 1) + log_warning("Found multiple related objects with class %s, using the first object.", + object_class_to_string(class)); + + *ret_object = objects[0]; + return 0; +} + +#if HAVE_OPENSSL +static int ecc_convert_to_compressed( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *uncompressed_point, + size_t uncompressed_point_size, + void **ret_compressed_point, + size_t *ret_compressed_point_size) { + + _cleanup_free_ void *ec_params_buffer = NULL; + CK_ATTRIBUTE ec_params_attr = { CKA_EC_PARAMS, NULL_PTR, 0 }; + CK_RV rv; + int r; + + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv)); + + if (ec_params_attr.ulValueLen != CK_UNAVAILABLE_INFORMATION) { + ec_params_buffer = malloc(ec_params_attr.ulValueLen); + if (!ec_params_buffer) + return log_oom(); + + ec_params_attr.pValue = ec_params_buffer; + rv = m->C_GetAttributeValue(session, object, &ec_params_attr, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_EC_PARAMS from a private key: %s", sym_p11_kit_strerror(rv)); + } else { + CK_OBJECT_HANDLE public_key; + r = pkcs11_token_find_related_object(m, session, object, CKO_PUBLIC_KEY, &public_key); + if (r < 0) + return log_error_errno(r, "Failed to find a public key for compressing a EC point"); + + ec_params_attr.ulValueLen = 0; + rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve length of CKA_EC_PARAMS: %s", sym_p11_kit_strerror(rv)); + + if (ec_params_attr.ulValueLen == CK_UNAVAILABLE_INFORMATION) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The public key does not have CKA_EC_PARAMS"); + + ec_params_buffer = malloc(ec_params_attr.ulValueLen); + if (!ec_params_buffer) + return log_oom(); + + ec_params_attr.pValue = ec_params_buffer; + rv = m->C_GetAttributeValue(session, public_key, &ec_params_attr, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to retrieve CKA_EC_PARAMS from a public key: %s", sym_p11_kit_strerror(rv)); } - rv = m->C_FindObjectsInit(session, attributes, n_attributes); + _cleanup_(EC_GROUP_freep) EC_GROUP *group = NULL; + _cleanup_(EC_POINT_freep) EC_POINT *point = NULL; + _cleanup_(BN_CTX_freep) BN_CTX *bnctx = NULL; + _cleanup_free_ void *compressed_point = NULL; + size_t compressed_point_size; + + const unsigned char *ec_params_value = ec_params_attr.pValue; + group = d2i_ECPKParameters(NULL, &ec_params_value, ec_params_attr.ulValueLen); + if (!group) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode CKA_EC_PARAMS"); + + point = EC_POINT_new(group); + if (!point) + return log_oom(); + + bnctx = BN_CTX_new(); + if (!bnctx) + return log_oom(); + + if (EC_POINT_oct2point(group, point, uncompressed_point, uncompressed_point_size, bnctx) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to decode an uncompressed EC point"); + + compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, NULL, 0, bnctx); + if (compressed_point_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine size of a compressed EC point"); + + compressed_point = malloc(compressed_point_size); + if (!compressed_point) + return log_oom(); + + compressed_point_size = EC_POINT_point2oct(group, point, POINT_CONVERSION_COMPRESSED, compressed_point, compressed_point_size, bnctx); + if (compressed_point_size == 0) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert a EC point to compressed format"); + + *ret_compressed_point = TAKE_PTR(compressed_point); + *ret_compressed_point_size = compressed_point_size; + return 0; +} +#endif + +/* Since EC keys doesn't support encryption directly, we use ECDH protocol to derive shared secret here. + * We use PKCS#11 C_DeriveKey function to derive a shared secret with a private key stored in the token and + * a public key saved on enrollment. */ +static int pkcs11_token_decrypt_data_ecc( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *encrypted_data, + size_t encrypted_data_size, + void **ret_decrypted_data, + size_t *ret_decrypted_data_size) { + + static const CK_BBOOL yes = CK_TRUE, no = CK_FALSE; + static const CK_OBJECT_CLASS shared_secret_class = CKO_SECRET_KEY; + static const CK_KEY_TYPE shared_secret_type = CKK_GENERIC_SECRET; + static const CK_ATTRIBUTE shared_secret_template[] = { + { CKA_TOKEN, (void*) &no, sizeof(no) }, + { CKA_CLASS, (void*) &shared_secret_class, sizeof(shared_secret_class) }, + { CKA_KEY_TYPE, (void*) &shared_secret_type, sizeof(shared_secret_type) }, + { CKA_SENSITIVE, (void*) &no, sizeof(no) }, + { CKA_EXTRACTABLE, (void*) &yes, sizeof(yes) } + }; + CK_ECDH1_DERIVE_PARAMS params = { + .kdf = CKD_NULL, + .pPublicData = (void*) encrypted_data, + .ulPublicDataLen = encrypted_data_size + }; + CK_MECHANISM mechanism = { + .mechanism = CKM_ECDH1_DERIVE, + .pParameter = ¶ms, + .ulParameterLen = sizeof(params) + }; + CK_OBJECT_HANDLE shared_secret_handle; + CK_SESSION_INFO session_info; + CK_MECHANISM_INFO mechanism_info; + CK_RV rv, rv2; +#if HAVE_OPENSSL + _cleanup_free_ void *compressed_point = NULL; + int r; +#endif + + rv = m->C_GetSessionInfo(session, &session_info); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + "Failed to get information about the PKCS#11 session: %s", sym_p11_kit_strerror(rv)); - rv = m->C_FindObjects(session, objects, ELEMENTSOF(objects), &n_objects); - rv2 = m->C_FindObjectsFinal(session); + rv = m->C_GetMechanismInfo(session_info.slotID, CKM_ECDH1_DERIVE, &mechanism_info); if (rv != CKR_OK) return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + "Failed to get information about CKM_ECDH1_DERIVE: %s", sym_p11_kit_strerror(rv)); + + if (!(mechanism_info.flags & CKF_EC_UNCOMPRESS)) { + if (mechanism_info.flags & CKF_EC_COMPRESS) { +#if HAVE_OPENSSL + log_debug("CKM_ECDH1_DERIVE accepts compressed EC points only, trying to convert."); + size_t compressed_point_size = 0; /* Explicit initialization to appease gcc */ + r = ecc_convert_to_compressed(m, session, object, encrypted_data, encrypted_data_size, &compressed_point, &compressed_point_size); + if (r < 0) + return r; + + params.pPublicData = compressed_point; + params.ulPublicDataLen = compressed_point_size; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "CKM_ECDH1_DERIVE does not support uncompressed format of EC points"); +#endif + } else + log_debug("Both CKF_EC_UNCOMPRESS and CKF_EC_COMPRESS are false for CKM_ECDH1_DERIVE, ignoring."); + } + + rv = m->C_DeriveKey(session, &mechanism, object, (CK_ATTRIBUTE*) shared_secret_template, ELEMENTSOF(shared_secret_template), &shared_secret_handle); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to derive a shared secret: %s", sym_p11_kit_strerror(rv)); + + CK_ATTRIBUTE shared_secret_attr = { CKA_VALUE, NULL_PTR, 0}; + + rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1); + if (rv != CKR_OK) { + rv2 = m->C_DestroyObject(session, shared_secret_handle); + if (rv2 != CKR_OK) + log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2)); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve shared secret length: %s", sym_p11_kit_strerror(rv)); + } + + shared_secret_attr.pValue = malloc(shared_secret_attr.ulValueLen); + if (!shared_secret_attr.pValue) + return log_oom(); + + rv = m->C_GetAttributeValue(session, shared_secret_handle, &shared_secret_attr, 1); + rv2 = m->C_DestroyObject(session, shared_secret_handle); if (rv2 != CKR_OK) - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); - if (n_objects == 0) - return log_error_errno(SYNTHETIC_ERRNO(ENOENT), - "Failed to find selected private key suitable for decryption on token."); - if (n_objects > 1) - return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), - "Configured private key URI matches multiple keys, refusing."); + log_warning("Failed to destroy a shared secret, ignoring: %s", sym_p11_kit_strerror(rv2)); - *ret_object = objects[0]; + if (rv != CKR_OK) { + erase_and_free(shared_secret_attr.pValue); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve a shared secret: %s", sym_p11_kit_strerror(rv)); + } + + log_info("Successfully derived key with security token."); + + *ret_decrypted_data = shared_secret_attr.pValue; + *ret_decrypted_data_size = shared_secret_attr.ulValueLen; return 0; } -int pkcs11_token_decrypt_data( +static int pkcs11_token_decrypt_data_rsa( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, @@ -737,17 +1319,6 @@ int pkcs11_token_decrypt_data( _cleanup_(erase_and_freep) CK_BYTE *dbuffer = NULL; CK_ULONG dbuffer_size = 0; CK_RV rv; - int r; - - assert(m); - assert(encrypted_data); - assert(encrypted_data_size > 0); - assert(ret_decrypted_data); - assert(ret_decrypted_data_size); - - r = dlopen_p11kit(); - if (r < 0) - return r; rv = m->C_DecryptInit(session, (CK_MECHANISM*) &mechanism, object); if (rv != CKR_OK) @@ -780,6 +1351,42 @@ int pkcs11_token_decrypt_data( return 0; } +int pkcs11_token_decrypt_data( + CK_FUNCTION_LIST *m, + CK_SESSION_HANDLE session, + CK_OBJECT_HANDLE object, + const void *encrypted_data, + size_t encrypted_data_size, + void **ret_decrypted_data, + size_t *ret_decrypted_data_size) { + + CK_KEY_TYPE key_type; + CK_ATTRIBUTE key_type_template = { CKA_KEY_TYPE, &key_type, sizeof(key_type) }; + CK_RV rv; + + assert(m); + assert(encrypted_data); + assert(encrypted_data_size > 0); + assert(ret_decrypted_data); + assert(ret_decrypted_data_size); + + rv = m->C_GetAttributeValue(session, object, &key_type_template, 1); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to retrieve private key type"); + + switch (key_type) { + + case CKK_RSA: + return pkcs11_token_decrypt_data_rsa(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); + + case CKK_EC: + return pkcs11_token_decrypt_data_ecc(m, session, object, encrypted_data, encrypted_data_size, ret_decrypted_data, ret_decrypted_data_size); + + default: + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported private key type: %lu", key_type); + } +} + int pkcs11_token_acquire_rng( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session) { @@ -1055,20 +1662,19 @@ int pkcs11_find_token( } #if HAVE_OPENSSL -struct pkcs11_acquire_certificate_callback_data { +struct pkcs11_acquire_public_key_callback_data { char *pin_used; - X509 *cert; - const char *askpw_friendly_name, *askpw_icon_name; + EVP_PKEY *pkey; + const char *askpw_friendly_name, *askpw_icon, *askpw_credential; AskPasswordFlags askpw_flags; - bool headless; }; -static void pkcs11_acquire_certificate_callback_data_release(struct pkcs11_acquire_certificate_callback_data *data) { +static void pkcs11_acquire_public_key_callback_data_release(struct pkcs11_acquire_public_key_callback_data *data) { erase_and_free(data->pin_used); - X509_free(data->cert); + EVP_PKEY_free(data->pkey); } -static int pkcs11_acquire_certificate_callback( +static int pkcs11_acquire_public_key_callback( CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slot_id, @@ -1078,8 +1684,16 @@ static int pkcs11_acquire_certificate_callback( void *userdata) { _cleanup_(erase_and_freep) char *pin_used = NULL; - struct pkcs11_acquire_certificate_callback_data *data = ASSERT_PTR(userdata); - CK_OBJECT_HANDLE object; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + CK_OBJECT_CLASS class; + CK_CERTIFICATE_TYPE type; + CK_ATTRIBUTE candidate_attributes[] = { + { CKA_CLASS, &class, sizeof(class) }, + { CKA_CERTIFICATE_TYPE, &type, sizeof(type) }, + }; + CK_OBJECT_HANDLE candidate, public_key = CK_INVALID_HANDLE, certificate = CK_INVALID_HANDLE; + uint8_t n_public_keys = 0, n_certificates = 0; + CK_RV rv; int r; assert(m); @@ -1087,6 +1701,8 @@ static int pkcs11_acquire_certificate_callback( assert(token_info); assert(uri); + struct pkcs11_acquire_public_key_callback_data *data = ASSERT_PTR(userdata); + /* Called for every token matching our URI */ r = pkcs11_token_login( @@ -1095,50 +1711,154 @@ static int pkcs11_acquire_certificate_callback( slot_id, token_info, data->askpw_friendly_name, - data->askpw_icon_name, - "pkcs11-pin", + data->askpw_icon, "pkcs11-pin", + data->askpw_credential, UINT64_MAX, data->askpw_flags, - data->headless, &pin_used); if (r < 0) return r; - r = pkcs11_token_find_x509_certificate(m, session, uri, &object); - if (r < 0) - return r; + CK_ULONG n_attributes; + CK_ATTRIBUTE *attributes = sym_p11_kit_uri_get_attributes(uri, &n_attributes); + for (CK_ULONG i = 0; i < n_attributes; i++) { + switch (attributes[i].type) { + case CKA_CLASS: { + CK_OBJECT_CLASS requested_class = *((CK_OBJECT_CLASS*) attributes[i].pValue); + if (!IN_SET(requested_class, CKO_PUBLIC_KEY, CKO_CERTIFICATE)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Selected PKCS#11 object is not a public key or certificate, refusing."); + break; + } - r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert); - if (r < 0) - return r; + case CKA_CERTIFICATE_TYPE: { + CK_CERTIFICATE_TYPE requested_type = *((CK_CERTIFICATE_TYPE*) attributes[i].pValue); + if (requested_type != CKC_X_509) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected PKCS#11 object is not an X.509 certificate, refusing."); + break; + }} + } + + rv = m->C_FindObjectsInit(session, attributes, n_attributes); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to initialize object find call: %s", sym_p11_kit_strerror(rv)); + + for (;;) { + CK_ULONG n; + rv = m->C_FindObjects(session, &candidate, 1, &n); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to find objects: %s", sym_p11_kit_strerror(rv)); + + if (n == 0) + break; + + candidate_attributes[0].ulValueLen = sizeof(class); + candidate_attributes[1].ulValueLen = sizeof(type); + rv = m->C_GetAttributeValue(session, candidate, candidate_attributes, ELEMENTSOF(candidate_attributes)); + if (!IN_SET(rv, CKR_OK, CKR_ATTRIBUTE_TYPE_INVALID)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to get attributes of a selected candidate: %s", sym_p11_kit_strerror(rv)); + + if (candidate_attributes[0].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("Failed to get CKA_CLASS of a selected candidate"); + continue; + } + + if (class == CKO_PUBLIC_KEY) { + n_public_keys++; + if (n_public_keys > 1) + break; + public_key = candidate; + continue; + } + + if (class == CKO_CERTIFICATE) { + if (candidate_attributes[1].ulValueLen == CK_UNAVAILABLE_INFORMATION) { + log_debug("Failed to get CKA_CERTIFICATE_TYPE of a selected candidate"); + continue; + } + if (type != CKC_X_509) + continue; + n_certificates++; + if (n_certificates > 1) + break; + certificate = candidate; + continue; + } + } + + rv = m->C_FindObjectsFinal(session); + if (rv != CKR_OK) + return log_error_errno(SYNTHETIC_ERRNO(EIO), + "Failed to finalize object find call: %s", sym_p11_kit_strerror(rv)); + + if (n_public_keys == 0 && n_certificates == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Failed to find selected public key or X.509 certificate."); + + if (n_public_keys > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Provided URI matches multiple public keys, refusing."); + if (n_certificates > 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Provided URI matches multiple certificates, refusing."); + + if (n_public_keys != 0) { + r = pkcs11_token_read_public_key(m, session, public_key, &pkey); + if (r >= 0) + goto success; + } + + if (n_certificates == 0) + return log_error_errno(r, "Failed to read a found public key."); + + { + _cleanup_(X509_freep) X509 *cert = NULL; + + r = pkcs11_token_read_x509_certificate(m, session, certificate, &cert); + if (r < 0) + return log_error_errno(r, "Failed to read a found X.509 certificate."); + + pkey = X509_get_pubkey(cert); + if (!pkey) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to extract public key from X.509 certificate."); + } +success: /* Let's read some random data off the token and write it to the kernel pool before we generate our * random key from it. This way we can claim the quality of the RNG is at least as good as the * kernel's and the token's pool */ (void) pkcs11_token_acquire_rng(m, session); data->pin_used = TAKE_PTR(pin_used); - return 1; + data->pkey = TAKE_PTR(pkey); + return 0; } -int pkcs11_acquire_certificate( +int pkcs11_acquire_public_key( const char *uri, const char *askpw_friendly_name, - const char *askpw_icon_name, - X509 **ret_cert, + const char *askpw_icon, + const char *askpw_credential, + AskPasswordFlags askpw_flags, + EVP_PKEY **ret_pkey, char **ret_pin_used) { - _cleanup_(pkcs11_acquire_certificate_callback_data_release) struct pkcs11_acquire_certificate_callback_data data = { + _cleanup_(pkcs11_acquire_public_key_callback_data_release) struct pkcs11_acquire_public_key_callback_data data = { .askpw_friendly_name = askpw_friendly_name, - .askpw_icon_name = askpw_icon_name, + .askpw_icon = askpw_icon, + .askpw_credential = askpw_credential, + .askpw_flags = askpw_flags, }; int r; assert(uri); - assert(ret_cert); + assert(ret_pkey); - r = pkcs11_find_token(uri, pkcs11_acquire_certificate_callback, &data); + r = pkcs11_find_token(uri, pkcs11_acquire_public_key_callback, &data); if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */ return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Specified PKCS#11 token with URI '%s' not found.", @@ -1146,11 +1866,9 @@ int pkcs11_acquire_certificate( if (r < 0) return r; - *ret_cert = TAKE_PTR(data.cert); - + *ret_pkey = TAKE_PTR(data.pkey); if (ret_pin_used) *ret_pin_used = TAKE_PTR(data.pin_used); - return 0; } #endif @@ -1229,7 +1947,7 @@ int pkcs11_list_tokens(void) { if (r < 0 && r != -EAGAIN) return r; - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No suitable PKCS#11 tokens found."); return 0; } @@ -1338,10 +2056,9 @@ int pkcs11_crypt_device_callback( data->friendly_name, "drive-harddisk", "pkcs11-pin", - "cryptsetup.pkcs11-pin", + data->askpw_credential, data->until, data->askpw_flags, - data->headless, NULL); if (r < 0) return r; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index 5bc23c1..23ab823 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -1,6 +1,10 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#if HAVE_OPENSSL +# include +# include +#endif #include #if HAVE_P11KIT @@ -9,28 +13,29 @@ #endif #include "ask-password-api.h" +#include "dlfcn-util.h" #include "macro.h" -#include "openssl-util.h" #include "time-util.h" bool pkcs11_uri_valid(const char *uri); #if HAVE_P11KIT - -extern char *(*sym_p11_kit_module_get_name)(CK_FUNCTION_LIST *module); -extern void (*sym_p11_kit_modules_finalize_and_release)(CK_FUNCTION_LIST **modules); -extern CK_FUNCTION_LIST **(*sym_p11_kit_modules_load_and_initialize)(int flags); -extern const char *(*sym_p11_kit_strerror)(CK_RV rv); -extern int (*sym_p11_kit_uri_format)(P11KitUri *uri, P11KitUriType uri_type, char **string); -extern void (*sym_p11_kit_uri_free)(P11KitUri *uri); -extern CK_ATTRIBUTE_PTR (*sym_p11_kit_uri_get_attributes)(P11KitUri *uri, CK_ULONG *n_attrs); -extern CK_INFO_PTR (*sym_p11_kit_uri_get_module_info)(P11KitUri *uri); -extern CK_SLOT_INFO_PTR (*sym_p11_kit_uri_get_slot_info)(P11KitUri *uri); -extern CK_TOKEN_INFO_PTR (*sym_p11_kit_uri_get_token_info)(P11KitUri *uri); -extern int (*sym_p11_kit_uri_match_token_info)(const P11KitUri *uri, const CK_TOKEN_INFO *token_info); -extern const char *(*sym_p11_kit_uri_message)(int code); -extern P11KitUri *(*sym_p11_kit_uri_new)(void); -extern int (*sym_p11_kit_uri_parse)(const char *string, P11KitUriType uri_type, P11KitUri *uri); +DLSYM_PROTOTYPE(p11_kit_module_get_name); +DLSYM_PROTOTYPE(p11_kit_modules_finalize_and_release); +DLSYM_PROTOTYPE(p11_kit_modules_load_and_initialize); +DLSYM_PROTOTYPE(p11_kit_strerror); +DLSYM_PROTOTYPE(p11_kit_uri_format); +DLSYM_PROTOTYPE(p11_kit_uri_free); +DLSYM_PROTOTYPE(p11_kit_uri_get_attributes); +DLSYM_PROTOTYPE(p11_kit_uri_get_attribute); +DLSYM_PROTOTYPE(p11_kit_uri_set_attribute); +DLSYM_PROTOTYPE(p11_kit_uri_get_module_info); +DLSYM_PROTOTYPE(p11_kit_uri_get_slot_info); +DLSYM_PROTOTYPE(p11_kit_uri_get_token_info); +DLSYM_PROTOTYPE(p11_kit_uri_match_token_info); +DLSYM_PROTOTYPE(p11_kit_uri_message); +DLSYM_PROTOTYPE(p11_kit_uri_new); +DLSYM_PROTOTYPE(p11_kit_uri_parse); int uri_from_string(const char *p, P11KitUri **ret); @@ -48,10 +53,12 @@ char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info); char *pkcs11_token_model(const CK_TOKEN_INFO *token_info); int pkcs11_token_login_by_pin(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, const CK_TOKEN_INFO *token_info, const char *token_label, const void *pin, size_t pin_size); -int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_used_pin); +int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags ask_password_flags, char **ret_used_pin); +int pkcs11_token_find_related_object(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE prototype, CK_OBJECT_CLASS class, CK_OBJECT_HANDLE *ret_object); int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object); #if HAVE_OPENSSL +int pkcs11_token_read_public_key(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, EVP_PKEY **ret_pkey); int pkcs11_token_read_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_OBJECT_HANDLE object, X509 **ret_cert); #endif @@ -64,7 +71,7 @@ typedef int (*pkcs11_find_token_callback_t)(CK_FUNCTION_LIST *m, CK_SESSION_HAND int pkcs11_find_token(const char *pkcs11_uri, pkcs11_find_token_callback_t callback, void *userdata); #if HAVE_OPENSSL -int pkcs11_acquire_certificate(const char *uri, const char *askpw_friendly_name, const char *askpw_icon_name, X509 **ret_cert, char **ret_pin_used); +int pkcs11_acquire_public_key(const char *uri, const char *askpw_friendly_name, const char *askpw_icon, const char *askpw_credential, AskPasswordFlags askpw_flags, EVP_PKEY **ret_pkey, char **ret_pin_used); #endif typedef struct { @@ -75,7 +82,7 @@ typedef struct { void *decrypted_key; size_t decrypted_key_size; bool free_encrypted_key; - bool headless; + const char *askpw_credential; AskPasswordFlags askpw_flags; } pkcs11_crypt_device_callback_data; @@ -103,7 +110,7 @@ static inline int dlopen_p11kit(void) { typedef struct { const char *friendly_name; usec_t until; - bool headless; + const char *askpw_credential; AskPasswordFlags askpw_flags; } systemd_pkcs11_plugin_params; diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c index 2833063..c75f74a 100644 --- a/src/shared/pretty-print.c +++ b/src/shared/pretty-print.c @@ -5,6 +5,7 @@ #include #include "alloc-util.h" +#include "color-util.h" #include "conf-files.h" #include "constants.h" #include "env-util.h" @@ -140,16 +141,8 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) { if (isempty(text)) text = path; - if (!urlify_enabled()) { - char *n; - - n = strdup(text); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; - } + if (!urlify_enabled()) + return strdup_to(ret, text); r = file_url_from_path(path, &url); if (r < 0) @@ -213,7 +206,7 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { break; LineType line_type = classify_line_type(line, flags); - if (flags & CAT_TLDR) { + if (FLAGS_SET(flags, CAT_TLDR)) { if (line_type == LINE_SECTION) { /* The start of a section, let's not print it yet. */ free_and_replace(section, line); @@ -236,6 +229,28 @@ static int cat_file(const char *filename, bool newline, CatFlags flags) { } } + /* Highlight the left side (directive) of a Foo=bar assignment */ + if (FLAGS_SET(flags, CAT_FORMAT_HAS_SECTIONS) && line_type == LINE_NORMAL) { + const char *p = strchr(line, '='); + if (p) { + _cleanup_free_ char *highlighted = NULL, *directive = NULL; + + directive = strndup(line, p - line); + if (!directive) + return log_oom(); + + highlighted = strjoin(ansi_highlight_green(), + directive, + "=", + ansi_normal(), + p + 1); + if (!highlighted) + return log_oom(); + + free_and_replace(line, highlighted); + } + } + printf("%s%s%s\n", line_type == LINE_SECTION ? ansi_highlight_cyan() : line_type == LINE_COMMENT ? ansi_highlight_grey() : @@ -271,14 +286,12 @@ void print_separator(void) { * one line filled with spaces with ANSI underline set, followed by a second (empty) line. */ if (underline_enabled()) { - size_t i, c; - - c = columns(); + size_t c = columns(); flockfile(stdout); fputs_unlocked(ANSI_UNDERLINE, stdout); - for (i = 0; i < c; i++) + for (size_t i = 0; i < c; i++) fputc_unlocked(' ', stdout); fputs_unlocked(ANSI_NORMAL "\n\n", stdout); @@ -287,7 +300,7 @@ void print_separator(void) { fputs("\n\n", stdout); } -static int guess_type(const char **name, char ***prefixes, bool *is_collection, const char **extension) { +static int guess_type(const char **name, char ***ret_prefixes, bool *ret_is_collection, const char **ret_extension) { /* Try to figure out if name is like tmpfiles.d/ or systemd/system-presets/, * i.e. a collection of directories without a main config file. * Incidentally, all those formats don't use sections. So we return a single @@ -295,11 +308,10 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection, */ _cleanup_free_ char *n = NULL; - bool usr = false, run = false, coll = false; + bool run = false, coll = false; const char *ext = ".conf"; /* This is static so that the array doesn't get deallocated when we exit the function */ static const char* const std_prefixes[] = { CONF_PATHS(""), NULL }; - static const char* const usr_prefixes[] = { CONF_PATHS_USR(""), NULL }; static const char* const run_prefixes[] = { "/run/", NULL }; if (path_equal(*name, "environment.d")) @@ -311,50 +323,33 @@ static int guess_type(const char **name, char ***prefixes, bool *is_collection, if (!n) return log_oom(); - /* All systemd-style config files should support the /usr-/etc-/run split and - * dropins. Let's add a blanket rule that allows us to support them without keeping - * an explicit list. */ - if (path_startswith(n, "systemd") && endswith(n, ".conf")) - usr = true; - delete_trailing_chars(n, "/"); + /* We assume systemd-style config files support the /usr-/run-/etc split and dropins. */ + if (endswith(n, ".d")) coll = true; - if (path_equal(n, "environment")) - usr = true; - if (path_equal(n, "udev/hwdb.d")) ext = ".hwdb"; - - if (path_equal(n, "udev/rules.d")) + else if (path_equal(n, "udev/rules.d")) ext = ".rules"; - - if (path_equal(n, "kernel/install.d")) + else if (path_equal(n, "kernel/install.d")) ext = ".install"; - - if (path_equal(n, "systemd/ntp-units.d")) { + else if (path_equal(n, "systemd/ntp-units.d")) { coll = true; ext = ".list"; - } - - if (path_equal(n, "systemd/relabel-extra.d")) { + } else if (path_equal(n, "systemd/relabel-extra.d")) { coll = run = true; ext = ".relabel"; - } - - if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { + } else if (PATH_IN_SET(n, "systemd/system-preset", "systemd/user-preset")) { coll = true; ext = ".preset"; } - if (path_equal(n, "systemd/user-preset")) - usr = true; - - *prefixes = (char**) (usr ? usr_prefixes : run ? run_prefixes : std_prefixes); - *is_collection = coll; - *extension = ext; + *ret_prefixes = (char**) (run ? run_prefixes : std_prefixes); + *ret_is_collection = coll; + *ret_extension = ext; return 0; } @@ -419,3 +414,116 @@ int conf_files_cat(const char *root, const char *name, CatFlags flags) { return cat_files(path, files, flags); } + +int terminal_tint_color(double hue, char **ret) { + double red, green, blue; + int r; + + assert(ret); + + r = get_default_background_color(&red, &green, &blue); + if (r < 0) + return log_debug_errno(r, "Unable to get terminal background color: %m"); + + double s, v; + rgb_to_hsv(red, green, blue, /* h= */ NULL, &s, &v); + + if (v > 50) /* If the background is bright, then pull down saturation */ + s = 25; + else /* otherwise pump it up */ + s = 75; + + v = MAX(20, v); /* Make sure we don't hide the color in black */ + + uint8_t r8, g8, b8; + hsv_to_rgb(hue, s, v, &r8, &g8, &b8); + + if (asprintf(ret, "48;2;%u;%u;%u", r8, g8, b8) < 0) + return -ENOMEM; + + return 0; +} + +bool shall_tint_background(void) { + static int cache = -1; + + if (cache >= 0) + return cache; + + cache = getenv_bool("SYSTEMD_TINT_BACKGROUND"); + if (cache == -ENXIO) + return (cache = true); + if (cache < 0) + log_debug_errno(cache, "Failed to parse $SYSTEMD_TINT_BACKGROUND, leaving background tinting enabled: %m"); + + return cache != 0; +} + +void draw_progress_bar(const char *prefix, double percentage) { + + fputc('\r', stderr); + if (prefix) + fputs(prefix, stderr); + + if (!terminal_is_dumb()) { + size_t cols = columns(); + size_t prefix_length = strlen_ptr(prefix); + size_t length = cols > prefix_length + 6 ? cols - prefix_length - 6 : 0; + + if (length > 5 && percentage >= 0.0 && percentage <= 100.0) { + size_t p = (size_t) (length * percentage / 100.0); + bool separator_done = false; + + fputs(ansi_highlight_green(), stderr); + + for (size_t i = 0; i < length; i++) { + + if (i <= p) { + if (get_color_mode() == COLOR_24BIT) { + uint8_t r8, g8, b8; + double z = i == 0 ? 0 : (((double) i / p) * 100); + hsv_to_rgb(145 /* green */, z, 33 + z*2/3, &r8, &g8, &b8); + fprintf(stderr, "\x1B[38;2;%u;%u;%um", r8, g8, b8); + } + + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT), stderr); + } else if (i+1 < length && !separator_done) { + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + separator_done = true; + fputs(ansi_grey(), stderr); + } else + fputs(special_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED), stderr); + } + + fputs(ansi_normal(), stderr); + fputc(' ', stderr); + } + } + + fprintf(stderr, + "%s%3.0f%%%s", + ansi_highlight(), + percentage, + ansi_normal()); + + if (!terminal_is_dumb()) + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} + +void clear_progress_bar(const char *prefix) { + + fputc('\r', stderr); + + if (terminal_is_dumb()) + fputs(strrepa(" ", strlen_ptr(prefix) + 4), /* 4: %3.0f%% */ + stderr); + else + fputs(ANSI_ERASE_TO_END_OF_LINE, stderr); + + fputc('\r', stderr); + fflush(stderr); +} diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h index c17e976..c166054 100644 --- a/src/shared/pretty-print.h +++ b/src/shared/pretty-print.h @@ -47,3 +47,10 @@ static inline const char *green_check_mark_internal(char buffer[static GREEN_CHE #define GREEN_CHECK_MARK() green_check_mark_internal((char[GREEN_CHECK_MARK_MAX]) {}) #define COLOR_MARK_BOOL(b) ((b) ? GREEN_CHECK_MARK() : RED_CROSS_MARK()) + +int terminal_tint_color(double hue, char **ret); + +bool shall_tint_background(void); + +void draw_progress_bar(const char *prefix, double percentage); +void clear_progress_bar(const char *prefix); diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c index 195e603..998ce96 100644 --- a/src/shared/ptyfwd.c +++ b/src/shared/ptyfwd.c @@ -18,13 +18,28 @@ #include "alloc-util.h" #include "errno-util.h" +#include "extract-word.h" #include "fd-util.h" +#include "io-util.h" #include "log.h" #include "macro.h" #include "ptyfwd.h" +#include "stat-util.h" +#include "strv.h" #include "terminal-util.h" #include "time-util.h" +typedef enum AnsiColorState { + ANSI_COLOR_STATE_TEXT, + ANSI_COLOR_STATE_ESC, + ANSI_COLOR_STATE_CSI_SEQUENCE, + ANSI_COLOR_STATE_OSC_SEQUENCE, + ANSI_COLOR_STATE_NEWLINE, + ANSI_COLOR_STATE_CARRIAGE_RETURN, + _ANSI_COLOR_STATE_MAX, + _ANSI_COLOR_STATE_INVALID = -EINVAL, +} AnsiColorState; + struct PTYForward { sd_event *event; @@ -65,7 +80,8 @@ struct PTYForward { bool last_char_set:1; char last_char; - char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; + char in_buffer[LINE_MAX], *out_buffer; + size_t out_buffer_size; size_t in_buffer_full, out_buffer_full; usec_t escape_timestamp; @@ -73,6 +89,14 @@ struct PTYForward { PTYForwardHandler handler; void *userdata; + + char *background_color; + AnsiColorState ansi_color_state; + char *csi_sequence; + char *osc_sequence; + + char *title; /* Window title to show by default */ + char *title_prefix; /* If terminal client overrides window title, prefix this string */ }; #define ESCAPE_USEC (1*USEC_PER_SEC) @@ -95,6 +119,14 @@ static void pty_forward_disconnect(PTYForward *f) { /* STDIN/STDOUT should not be non-blocking normally, so let's reset it */ (void) fd_nonblock(f->output_fd, false); + + if (colors_enabled()) { + (void) loop_write(f->output_fd, ANSI_NORMAL ANSI_ERASE_TO_END_OF_SCREEN, SIZE_MAX); + + if (f->title) + (void) loop_write(f->output_fd, ANSI_WINDOW_TITLE_POP, SIZE_MAX); + } + if (f->close_output_fd) f->output_fd = safe_close(f->output_fd); } @@ -109,6 +141,15 @@ static void pty_forward_disconnect(PTYForward *f) { } f->saved_stdout = f->saved_stdin = false; + + f->out_buffer = mfree(f->out_buffer); + f->out_buffer_size = 0; + f->out_buffer_full = 0; + f->in_buffer_full = 0; + + f->csi_sequence = mfree(f->csi_sequence); + f->osc_sequence = mfree(f->osc_sequence); + f->ansi_color_state = _ANSI_COLOR_STATE_INVALID; } static int pty_forward_done(PTYForward *f, int rcode) { @@ -142,7 +183,7 @@ static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { if (*p == 0x1D) { usec_t nw = now(CLOCK_MONOTONIC); - if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { + if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { f->escape_timestamp = nw; f->escape_counter = 1; } else { @@ -196,11 +237,321 @@ static bool drained(PTYForward *f) { return true; } -static int shovel(PTYForward *f) { +static char *background_color_sequence(PTYForward *f) { + assert(f); + assert(f->background_color); + + return strjoin("\x1B[", f->background_color, "m"); +} + +static int insert_string(PTYForward *f, size_t offset, const char *s) { + assert(f); + assert(offset <= f->out_buffer_full); + assert(s); + + size_t l = strlen(s); + assert(l <= INT_MAX); /* Make sure we can still return this */ + + void *p = realloc(f->out_buffer, MAX(f->out_buffer_full + l, (size_t) LINE_MAX)); + if (!p) + return -ENOMEM; + + f->out_buffer = p; + f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer); + + memmove(f->out_buffer + offset + l, f->out_buffer + offset, f->out_buffer_full - offset); + memcpy(f->out_buffer + offset, s, l); + f->out_buffer_full += l; + + return (int) l; +} + +static int insert_background_color(PTYForward *f, size_t offset) { + _cleanup_free_ char *s = NULL; + + assert(f); + + if (!f->background_color) + return 0; + + s = background_color_sequence(f); + if (!s) + return -ENOMEM; + + return insert_string(f, offset, s); +} + +static int is_csi_background_reset_sequence(const char *seq) { + enum { + COLOR_TOKEN_NO, + COLOR_TOKEN_START, + COLOR_TOKEN_8BIT, + COLOR_TOKEN_24BIT_R, + COLOR_TOKEN_24BIT_G, + COLOR_TOKEN_24BIT_B, + } token_state = COLOR_TOKEN_NO; + + bool b = false; + int r; + + assert(seq); + + /* This parses CSI "m" sequences, and determines if they reset the background color. If so returns + * 1. This can then be used to insert another sequence that sets the color to the desired + * replacement. */ + + for (;;) { + _cleanup_free_ char *token = NULL; + + r = extract_first_word(&seq, &token, ";", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + switch (token_state) { + + case COLOR_TOKEN_NO: + + if (STR_IN_SET(token, "", "0", "00", "49")) + b = true; /* These tokens set the background back to normal */ + else if (STR_IN_SET(token, "40", "41", "42", "43", "44", "45", "46", "47", "48")) + b = false; /* And these tokens set them to something other than normal */ + + if (STR_IN_SET(token, "38", "48", "58")) + token_state = COLOR_TOKEN_START; /* These tokens mean an 8bit or 24bit color will follow */ + break; + + case COLOR_TOKEN_START: + + if (STR_IN_SET(token, "5", "05")) + token_state = COLOR_TOKEN_8BIT; /* 8bit color */ + else if (STR_IN_SET(token, "2", "02")) + token_state = COLOR_TOKEN_24BIT_R; /* 24bit color */ + else + token_state = COLOR_TOKEN_NO; /* something weird? */ + break; + + case COLOR_TOKEN_24BIT_R: + token_state = COLOR_TOKEN_24BIT_G; + break; + + case COLOR_TOKEN_24BIT_G: + token_state = COLOR_TOKEN_24BIT_B; + break; + + case COLOR_TOKEN_8BIT: + case COLOR_TOKEN_24BIT_B: + token_state = COLOR_TOKEN_NO; + break; + } + } + + return b; +} + +static int insert_background_fix(PTYForward *f, size_t offset) { + assert(f); + + if (!f->background_color) + return 0; + + if (!is_csi_background_reset_sequence(strempty(f->csi_sequence))) + return 0; + + _cleanup_free_ char *s = NULL; + s = strjoin(";", f->background_color); + if (!s) + return -ENOMEM; + + return insert_string(f, offset, s); +} + +static int insert_window_title_fix(PTYForward *f, size_t offset) { + assert(f); + + if (!f->title_prefix) + return 0; + + if (!f->osc_sequence) + return 0; + + const char *t = startswith(f->osc_sequence, "0;"); /* Set window title OSC sequence*/ + if (!t) + return 0; + + _cleanup_free_ char *joined = strjoin("\x1b]0;", f->title_prefix, t, "\a"); + if (!joined) + return -ENOMEM; + + return insert_string(f, offset, joined); +} + +static int pty_forward_ansi_process(PTYForward *f, size_t offset) { + int r; + + assert(f); + assert(offset <= f->out_buffer_full); + + if (!f->background_color && !f->title_prefix) + return 0; + + if (FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) + return 0; + + for (size_t i = offset; i < f->out_buffer_full; i++) { + char c = f->out_buffer[i]; + + switch (f->ansi_color_state) { + + case ANSI_COLOR_STATE_TEXT: + break; + + case ANSI_COLOR_STATE_NEWLINE: + case ANSI_COLOR_STATE_CARRIAGE_RETURN: + /* Immediately after a newline (ASCII 10) or carriage return (ASCII 13) insert an + * ANSI sequence set the background color back. */ + + r = insert_background_color(f, i); + if (r < 0) + return r; + + i += r; + break; + + case ANSI_COLOR_STATE_ESC: + + if (c == '[') { + f->ansi_color_state = ANSI_COLOR_STATE_CSI_SEQUENCE; + continue; + } else if (c == ']') { + f->ansi_color_state = ANSI_COLOR_STATE_OSC_SEQUENCE; + continue; + } + break; + + case ANSI_COLOR_STATE_CSI_SEQUENCE: + + if (c >= 0x20 && c <= 0x3F) { + /* If this is a "parameter" or "intermediary" byte (i.e. ranges 0x20…0x2F and + * 0x30…0x3F) then we are still in the CSI sequence */ + + if (strlen_ptr(f->csi_sequence) >= 64) { + /* Safety check: lets not accept unbounded CSI sequences */ + + f->csi_sequence = mfree(f->csi_sequence); + break; + } else if (!strextend(&f->csi_sequence, CHAR_TO_STR(c))) + return -ENOMEM; + } else { + /* Otherwise, the CSI sequence is over */ + + if (c == 'm') { + /* This is an "SGR" (Select Graphic Rendition) sequence. Patch in our background color. */ + r = insert_background_fix(f, i); + if (r < 0) + return r; + + i += r; + } + + f->csi_sequence = mfree(f->csi_sequence); + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + continue; + + case ANSI_COLOR_STATE_OSC_SEQUENCE: + + if ((uint8_t) c >= ' ') { + if (strlen_ptr(f->osc_sequence) >= 64) { + /* Safety check: lets not accept unbounded OSC sequences */ + f->osc_sequence = mfree(f->osc_sequence); + break; + } else if (!strextend(&f->osc_sequence, CHAR_TO_STR(c))) + return -ENOMEM; + } else { + /* Otherwise, the OSC sequence is over + * + * There are two allowed ways to end an OSC sequence: + * BEL '\x07' + * String Terminator (ST): \ - "\x1b\x5c" + * since we cannot lookahead to see if the Esc is followed by a \ + * we cut a corner here and assume it will be \. */ + + if (IN_SET(c, '\x07', '\x1b')) { + r = insert_window_title_fix(f, i+1); + if (r < 0) + return r; + + i += r; + } + + f->osc_sequence = mfree(f->osc_sequence); + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + continue; + + default: + assert_not_reached(); + } + + if (c == '\n') + f->ansi_color_state = ANSI_COLOR_STATE_NEWLINE; + else if (c == '\r') + f->ansi_color_state = ANSI_COLOR_STATE_CARRIAGE_RETURN; + else if (c == 0x1B) /* ESC */ + f->ansi_color_state = ANSI_COLOR_STATE_ESC; + else + f->ansi_color_state = ANSI_COLOR_STATE_TEXT; + } + + return 0; +} + +static int do_shovel(PTYForward *f) { ssize_t k; + int r; assert(f); + if (f->out_buffer_size == 0 && !FLAGS_SET(f->flags, PTY_FORWARD_DUMB_TERMINAL)) { + /* If the output hasn't been allocated yet, we are at the beginning of the first + * shovelling. Hence, possibly send some initial ANSI sequences. But do so only if we are + * talking to an actual TTY. */ + + if (f->background_color) { + /* Erase the first line when we start */ + f->out_buffer = background_color_sequence(f); + if (!f->out_buffer) + return log_oom(); + + if (!strextend(&f->out_buffer, ANSI_ERASE_TO_END_OF_LINE)) + return log_oom(); + } + + if (f->title) { + if (!strextend(&f->out_buffer, + ANSI_WINDOW_TITLE_PUSH + "\x1b]2;", f->title, "\a")) + return log_oom(); + } + + if (f->out_buffer) { + f->out_buffer_full = strlen(f->out_buffer); + f->out_buffer_size = MALLOC_SIZEOF_SAFE(f->out_buffer); + } + } + + if (f->out_buffer_size < LINE_MAX) { + /* Make sure we always have room for at least one "line" */ + void *p = realloc(f->out_buffer, LINE_MAX); + if (!p) + return log_oom(); + + f->out_buffer = p; + f->out_buffer_size = MALLOC_SIZEOF_SAFE(p); + } + while ((f->stdin_readable && f->in_buffer_full <= 0) || (f->master_writable && f->in_buffer_full > 0) || (f->master_readable && f->out_buffer_full <= 0) || @@ -218,21 +569,19 @@ static int shovel(PTYForward *f) { f->stdin_hangup = true; f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "Failed to read from pty input fd: %m"); } else if (k == 0) { /* EOF on stdin */ f->stdin_readable = false; f->stdin_hangup = true; f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { + } else { /* Check if ^] has been pressed three times within one second. If we get this we quite * immediately. */ if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) - return pty_forward_done(f, -ECANCELED); + return -ECANCELED; f->in_buffer_full += (size_t) k; } @@ -250,10 +599,8 @@ static int shovel(PTYForward *f) { f->master_hangup = true; f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "write(): %m"); } else { assert(f->in_buffer_full >= (size_t) k); memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); @@ -261,17 +608,14 @@ static int shovel(PTYForward *f) { } } - if (f->master_readable && f->out_buffer_full < LINE_MAX) { + if (f->master_readable && f->out_buffer_full < MIN(f->out_buffer_size, (size_t) LINE_MAX)) { - k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); + k = read(f->master, f->out_buffer + f->out_buffer_full, f->out_buffer_size - f->out_buffer_full); if (k < 0) { - /* Note that EIO on the master device - * might be caused by vhangup() or - * temporary closing of everything on - * the other side, we treat it like - * EAGAIN here and try again, unless - * ignore_vhangup is off. */ + /* Note that EIO on the master device might be caused by vhangup() or + * temporary closing of everything on the other side, we treat it like EAGAIN + * here and try again, unless ignore_vhangup is off. */ if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) f->master_readable = false; @@ -280,13 +624,16 @@ static int shovel(PTYForward *f) { f->master_hangup = true; f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return pty_forward_done(f, -errno); - } - } else { + } else + return log_error_errno(errno, "Failed to read from pty master fd: %m"); + } else { f->read_from_master = true; + size_t scan_index = f->out_buffer_full; f->out_buffer_full += (size_t) k; + + r = pty_forward_ansi_process(f, scan_index); + if (r < 0) + return log_error_errno(r, "Failed to scan for ANSI sequences: %m"); } } @@ -301,10 +648,8 @@ static int shovel(PTYForward *f) { f->stdout_writable = false; f->stdout_hangup = true; f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return pty_forward_done(f, -errno); - } + } else + return log_error_errno(errno, "Failed to write to pty output fd: %m"); } else { @@ -337,6 +682,18 @@ static int shovel(PTYForward *f) { return 0; } +static int shovel(PTYForward *f) { + int r; + + assert(f); + + r = do_shovel(f); + if (r < 0) + return pty_forward_done(f, r); + + return r; +} + static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { PTYForward *f = ASSERT_PTR(userdata); @@ -406,11 +763,14 @@ int pty_forward_new( struct winsize ws; int r; + assert(master >= 0); + assert(ret); + f = new(PTYForward, 1); if (!f) return -ENOMEM; - *f = (struct PTYForward) { + *f = (PTYForward) { .flags = flags, .master = -EBADF, .input_fd = -EBADF, @@ -430,13 +790,17 @@ int pty_forward_new( else { /* If we shall be invoked in interactive mode, let's switch on non-blocking mode, so that we * never end up staving one direction while we block on the other. However, let's be careful - * here and not turn on O_NONBLOCK for stdin/stdout directly, but of re-opened copies of + * here and not turn on O_NONBLOCK for stdin/stdout directly, but of reopened copies of * them. This has two advantages: when we are killed abruptly the stdin/stdout fds won't be * left in O_NONBLOCK state for the next process using them. In addition, if some process * running in the background wants to continue writing to our stdout it can do so without - * being confused by O_NONBLOCK. */ + * being confused by O_NONBLOCK. + * We keep O_APPEND (if present) on the output FD and (try to) keep current file position on + * both input and output FD (principle of least surprise). + */ - f->input_fd = fd_reopen(STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + f->input_fd = fd_reopen_propagate_append_and_position( + STDIN_FILENO, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (f->input_fd < 0) { /* Handle failures gracefully, after all certain fd types cannot be reopened * (sockets, …) */ @@ -450,7 +814,8 @@ int pty_forward_new( } else f->close_input_fd = true; - f->output_fd = fd_reopen(STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + f->output_fd = fd_reopen_propagate_append_and_position( + STDOUT_FILENO, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); if (f->output_fd < 0) { log_debug_errno(f->output_fd, "Failed to reopen stdout, using original fd: %m"); @@ -469,6 +834,10 @@ int pty_forward_new( f->master = master; + /* Disable color/window title setting unless we talk to a good TTY */ + if (!isatty_safe(f->output_fd) || get_color_mode() == COLOR_OFF) + f->flags |= PTY_FORWARD_DUMB_TERMINAL; + if (ioctl(f->output_fd, TIOCGWINSZ, &ws) < 0) /* If we can't get the resolution from the output fd, then use our internal, regular width/height, * i.e. something derived from $COLUMNS and $LINES if set. */ @@ -479,9 +848,16 @@ int pty_forward_new( (void) ioctl(master, TIOCSWINSZ, &ws); - if (!(flags & PTY_FORWARD_READ_ONLY)) { + if (!FLAGS_SET(flags, PTY_FORWARD_READ_ONLY)) { + bool same; + assert(f->input_fd >= 0); + r = fd_inode_same(f->input_fd, f->output_fd); + if (r < 0) + return r; + same = r > 0; + if (tcgetattr(f->input_fd, &f->saved_stdin_attr) >= 0) { struct termios raw_stdin_attr; @@ -489,11 +865,14 @@ int pty_forward_new( raw_stdin_attr = f->saved_stdin_attr; cfmakeraw(&raw_stdin_attr); - raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; - tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr); + + if (!same) + raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; + + (void) tcsetattr(f->input_fd, TCSANOW, &raw_stdin_attr); } - if (tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) { + if (!same && tcgetattr(f->output_fd, &f->saved_stdout_attr) >= 0) { struct termios raw_stdout_attr; f->saved_stdout = true; @@ -502,7 +881,7 @@ int pty_forward_new( cfmakeraw(&raw_stdout_attr); raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; - tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr); + (void) tcsetattr(f->output_fd, TCSANOW, &raw_stdout_attr); } r = sd_event_add_io(f->event, &f->stdin_event_source, f->input_fd, EPOLLIN|EPOLLET, on_stdin_event, f); @@ -540,7 +919,14 @@ int pty_forward_new( } PTYForward *pty_forward_free(PTYForward *f) { + if (!f) + return NULL; + pty_forward_disconnect(f); + free(f->background_color); + free(f->title); + free(f->title_prefix); + return mfree(f); } @@ -560,15 +946,14 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { assert(f); - if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) + if (FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP) == b) return 0; SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b); if (!ignore_vhangup(f)) { - /* We shall now react to vhangup()s? Let's check - * immediately if we might be in one */ + /* We shall now react to vhangup()s? Let's check immediately if we might be in one. */ f->master_readable = true; r = shovel(f); @@ -582,7 +967,7 @@ int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { bool pty_forward_get_ignore_vhangup(PTYForward *f) { assert(f); - return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); + return FLAGS_SET(f->flags, PTY_FORWARD_IGNORE_VHANGUP); } bool pty_forward_is_done(PTYForward *f) { @@ -614,6 +999,7 @@ bool pty_forward_drain(PTYForward *f) { int pty_forward_set_priority(PTYForward *f, int64_t priority) { int r; + assert(f); if (f->stdin_event_source) { @@ -675,3 +1061,45 @@ int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height) return 0; } + +int pty_forward_set_background_color(PTYForward *f, const char *color) { + assert(f); + + return free_and_strdup(&f->background_color, color); +} + +int pty_forward_set_title(PTYForward *f, const char *title) { + assert(f); + + /* Refuse accepting a title when we already started shoveling */ + if (f->out_buffer_size > 0) + return -EBUSY; + + return free_and_strdup(&f->title, title); +} + +int pty_forward_set_titlef(PTYForward *f, const char *format, ...) { + _cleanup_free_ char *title = NULL; + va_list ap; + int r; + + assert(f); + assert(format); + + if (f->out_buffer_size > 0) + return -EBUSY; + + va_start(ap, format); + r = vasprintf(&title, format, ap); + va_end(ap); + if (r < 0) + return -ENOMEM; + + return free_and_replace(f->title, title); +} + +int pty_forward_set_title_prefix(PTYForward *f, const char *title_prefix) { + assert(f); + + return free_and_strdup(&f->title_prefix, title_prefix); +} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h index f0ae6e9..248646d 100644 --- a/src/shared/ptyfwd.h +++ b/src/shared/ptyfwd.h @@ -10,13 +10,17 @@ typedef struct PTYForward PTYForward; typedef enum PTYForwardFlags { - PTY_FORWARD_READ_ONLY = 1, + /* Only output to STDOUT, never try to read from STDIN */ + PTY_FORWARD_READ_ONLY = 1 << 0, /* Continue reading after hangup? */ - PTY_FORWARD_IGNORE_VHANGUP = 2, + PTY_FORWARD_IGNORE_VHANGUP = 1 << 1, /* Continue reading after hangup but only if we never read anything else? */ - PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, + PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 1 << 2, + + /* Don't tint the background, or set window title */ + PTY_FORWARD_DUMB_TERMINAL = 1 << 3, } PTYForwardFlags; typedef int (*PTYForwardHandler)(PTYForward *f, int rcode, void *userdata); @@ -39,4 +43,11 @@ int pty_forward_set_priority(PTYForward *f, int64_t priority); int pty_forward_set_width_height(PTYForward *f, unsigned width, unsigned height); +int pty_forward_set_background_color(PTYForward *f, const char *color); + +int pty_forward_set_title(PTYForward *f, const char *title); +int pty_forward_set_titlef(PTYForward *f, const char *format, ...) _printf_(2,3); + +int pty_forward_set_title_prefix(PTYForward *f, const char *prefix); + DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/shared/qrcode-util.c b/src/shared/qrcode-util.c index b0dd90a..e62a5a8 100644 --- a/src/shared/qrcode-util.c +++ b/src/shared/qrcode-util.c @@ -18,12 +18,17 @@ static void *qrcode_dl = NULL; -static QRcode* (*sym_QRcode_encodeString)(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) = NULL; -static void (*sym_QRcode_free)(QRcode *qrcode) = NULL; +static DLSYM_FUNCTION(QRcode_encodeString); +static DLSYM_FUNCTION(QRcode_free); int dlopen_qrencode(void) { int r; + ELF_NOTE_DLOPEN("qrencode", + "Support for generating QR codes", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libqrencode.so.4", "libqrencode.so.3"); + FOREACH_STRING(s, "libqrencode.so.4", "libqrencode.so.3") { r = dlopen_many_sym_or_warn( &qrcode_dl, s, LOG_DEBUG, diff --git a/src/shared/reboot-util.c b/src/shared/reboot-util.c index 62ff697..b1c1aa7 100644 --- a/src/shared/reboot-util.c +++ b/src/shared/reboot-util.c @@ -23,8 +23,15 @@ #include "reboot-util.h" #include "string-util.h" #include "umask-util.h" +#include "utf8.h" #include "virt.h" +bool reboot_parameter_is_valid(const char *parameter) { + assert(parameter); + + return ascii_is_valid(parameter) && strlen(parameter) <= NAME_MAX; +} + int update_reboot_parameter_and_warn(const char *parameter, bool keep) { int r; @@ -42,6 +49,9 @@ int update_reboot_parameter_and_warn(const char *parameter, bool keep) { return 0; } + if (!reboot_parameter_is_valid(parameter)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid reboot parameter '%s'.", parameter); + WITH_UMASK(0022) { r = write_string_file("/run/systemd/reboot-param", parameter, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); diff --git a/src/shared/reboot-util.h b/src/shared/reboot-util.h index ccd15c7..2ac478f 100644 --- a/src/shared/reboot-util.h +++ b/src/shared/reboot-util.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +bool reboot_parameter_is_valid(const char *parameter); int update_reboot_parameter_and_warn(const char *parameter, bool keep); typedef enum RebootFlags { diff --git a/src/shared/resize-fs.c b/src/shared/resize-fs.c index 178aefa..b1c4a49 100644 --- a/src/shared/resize-fs.c +++ b/src/shared/resize-fs.c @@ -61,7 +61,7 @@ int resize_fs(int fd, uint64_t sz, uint64_t *ret_size) { if (ret_size) *ret_size = sz; - } else if (is_fs_type(&sfs, XFS_SB_MAGIC)) { + } else if (is_fs_type(&sfs, XFS_SUPER_MAGIC)) { xfs_fsop_geom_t geo; xfs_growfs_data_t d; @@ -95,7 +95,7 @@ uint64_t minimal_size_by_fs_magic(statfs_f_type_t magic) { case (statfs_f_type_t) EXT4_SUPER_MAGIC: return EXT4_MINIMAL_SIZE; - case (statfs_f_type_t) XFS_SB_MAGIC: + case (statfs_f_type_t) XFS_SUPER_MAGIC: return XFS_MINIMAL_SIZE; case (statfs_f_type_t) BTRFS_SUPER_MAGIC: diff --git a/src/shared/rm-rf.c b/src/shared/rm-rf.c index 4664215..767db48 100644 --- a/src/shared/rm-rf.c +++ b/src/shared/rm-rf.c @@ -326,7 +326,7 @@ static int rm_rf_children_impl( dirname = mfree(dirname); /* And now let's back out one level up */ - n_todo --; + n_todo--; d = TAKE_PTR(todos[n_todo].dir); dirname = TAKE_PTR(todos[n_todo].dirname); old_mode = todos[n_todo].old_mode; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 00a8ced..2469e24 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -298,7 +298,7 @@ bool is_seccomp_available(void) { if (cached_enabled < 0) { int b; - b = getenv_bool_secure("SYSTEMD_SECCOMP"); + b = secure_getenv_bool("SYSTEMD_SECCOMP"); if (b != 0) { if (b < 0 && b != -ENXIO) /* ENXIO: env var unset */ log_debug_errno(b, "Failed to parse $SYSTEMD_SECCOMP value, ignoring."); diff --git a/src/shared/selinux-util.c b/src/shared/selinux-util.c index 2fef29c..d2b1a3e 100644 --- a/src/shared/selinux-util.c +++ b/src/shared/selinux-util.c @@ -39,8 +39,6 @@ typedef enum Initialized { LAZY_INITIALIZED, } Initialized; -static int mac_selinux_reload(int seqno); - static int cached_use = -1; static Initialized initialized = UNINITIALIZED; static int last_policyload = 0; @@ -214,6 +212,16 @@ int mac_selinux_init_lazy(void) { return 0; } +#if HAVE_SELINUX +static int mac_selinux_reload(int seqno) { + log_debug("SELinux reload %d", seqno); + + (void) open_label_db(); + + return 0; +} +#endif + void mac_selinux_maybe_reload(void) { #if HAVE_SELINUX int policyload; @@ -256,16 +264,6 @@ void mac_selinux_finish(void) { #endif } -#if HAVE_SELINUX -static int mac_selinux_reload(int seqno) { - log_debug("SELinux reload %d", seqno); - - (void) open_label_db(); - - return 0; -} -#endif - #if HAVE_SELINUX static int selinux_fix_fd( int fd, diff --git a/src/shared/serialize.c b/src/shared/serialize.c index 344b102..735caf4 100644 --- a/src/shared/serialize.c +++ b/src/shared/serialize.c @@ -485,7 +485,7 @@ int deserialize_pidref(FDSet *fds, const char *value, PidRef *ret) { _cleanup_free_ char *fdstr = NULL, *pidstr = NULL; _cleanup_close_ int fd = -EBADF; - r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr, NULL); + r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr); if (r < 0) return log_debug_errno(r, "Failed to deserialize pidref '%s': %m", e); if (r == 0) diff --git a/src/shared/service-util.c b/src/shared/service-util.c index b0585ba..7d46ee7 100644 --- a/src/shared/service-util.c +++ b/src/shared/service-util.c @@ -17,19 +17,22 @@ static int help(const char *program_path, const char *service, const char *descr if (r < 0) return log_oom(); - printf("%s [OPTIONS...]\n\n" - "%s%s%s\n\n" - "This program takes no positional arguments.\n\n" - "%sOptions%s:\n" + printf("%1$s [OPTIONS...]\n" + "\n%5$s%7$s%6$s\n" + "\nThis program takes no positional arguments.\n" + "\n%3$sOptions:%4$s\n" " -h --help Show this help\n" " --version Show package version\n" - " --bus-introspect=PATH Write D-Bus XML introspection data\n" - "\nSee the %s for details.\n" - , program_path - , ansi_highlight(), description, ansi_normal() - , ansi_underline(), ansi_normal() - , link - ); + "%8$s" + "\nSee the %2$s for details.\n", + program_path, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal(), + description, + bus_introspect ? " --bus-introspect=PATH Write D-Bus XML introspection data\n" : ""); return 0; /* No further action */ } diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c index 7282111..3d4d331 100644 --- a/src/shared/sleep-config.c +++ b/src/shared/sleep-config.c @@ -54,6 +54,8 @@ SleepConfig* sleep_config_free(SleepConfig *sc) { strv_free(sc->modes[i]); } + strv_free(sc->mem_modes); + return mfree(sc); } @@ -69,8 +71,8 @@ static int config_parse_sleep_mode( void *data, void *userdata) { - _cleanup_strv_free_ char **modes = NULL; char ***sv = ASSERT_PTR(data); + _cleanup_strv_free_ char **modes = NULL; int r; assert(filename); @@ -87,7 +89,7 @@ static int config_parse_sleep_mode( return log_oom(); } - return free_and_replace(*sv, modes); + return strv_free_and_replace(*sv, modes); } static void sleep_config_validate_state_and_mode(SleepConfig *sc) { @@ -140,14 +142,19 @@ int parse_sleep_config(SleepConfig **ret) { { "Sleep", "HybridSleepState", config_parse_warn_compat, DISABLED_LEGACY, NULL }, { "Sleep", "HybridSleepMode", config_parse_warn_compat, DISABLED_LEGACY, NULL }, + { "Sleep", "MemorySleepMode", config_parse_sleep_mode, 0, &sc->mem_modes }, + { "Sleep", "HibernateDelaySec", config_parse_sec, 0, &sc->hibernate_delay_usec }, { "Sleep", "SuspendEstimationSec", config_parse_sec, 0, &sc->suspend_estimation_usec }, {} }; - (void) config_parse_config_file("sleep.conf", "Sleep\0", - config_item_table_lookup, items, - CONFIG_PARSE_WARN, NULL); + (void) config_parse_standard_file_with_dropins( + "systemd/sleep.conf", + "Sleep\0", + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL); /* use default values unless set */ sc->allow[SLEEP_SUSPEND] = allow_suspend != 0; @@ -180,7 +187,7 @@ int parse_sleep_config(SleepConfig **ret) { return 0; } -int sleep_state_supported(char **states) { +int sleep_state_supported(char * const *states) { _cleanup_free_ char *supported_sysfs = NULL; const char *found; int r; @@ -210,22 +217,24 @@ int sleep_state_supported(char **states) { return false; } -int sleep_mode_supported(char **modes) { +int sleep_mode_supported(const char *path, char * const *modes) { _cleanup_free_ char *supported_sysfs = NULL; int r; + assert(path); + /* Unlike state, kernel has its own default choice if not configured */ if (strv_isempty(modes)) { - log_debug("No sleep mode configured, using kernel default."); + log_debug("No sleep mode configured, using kernel default for %s.", path); return true; } - if (access("/sys/power/disk", W_OK) < 0) - return log_debug_errno(errno, "/sys/power/disk is not writable: %m"); + if (access(path, W_OK) < 0) + return log_debug_errno(errno, "%s is not writable: %m", path); - r = read_one_line_file("/sys/power/disk", &supported_sysfs); + r = read_one_line_file(path, &supported_sysfs); if (r < 0) - return log_debug_errno(r, "Failed to read /sys/power/disk: %m"); + return log_debug_errno(r, "Failed to read %s: %m", path); for (const char *p = supported_sysfs;;) { _cleanup_free_ char *word = NULL; @@ -234,7 +243,7 @@ int sleep_mode_supported(char **modes) { r = extract_first_word(&p, &word, NULL, 0); if (r < 0) - return log_debug_errno(r, "Failed to parse /sys/power/disk: %m"); + return log_debug_errno(r, "Failed to parse %s: %m", path); if (r == 0) break; @@ -247,14 +256,15 @@ int sleep_mode_supported(char **modes) { } if (strv_contains(modes, mode)) { - log_debug("Disk sleep mode '%s' is supported by kernel.", mode); + log_debug("Sleep mode '%s' is supported by kernel (%s).", mode, path); return true; } } if (DEBUG_LOGGING) { _cleanup_free_ char *joined = strv_join(modes, " "); - log_debug("None of the configured hibernation power modes are supported by kernel: %s", strnull(joined)); + log_debug("None of the configured modes are supported by kernel (%s): %s", + path, strnull(joined)); } return false; } @@ -284,7 +294,7 @@ static int s2h_supported(const SleepConfig *sleep_config, SleepSupport *ret_supp return false; } - FOREACH_ARRAY(i, operations, ELEMENTSOF(operations)) { + FOREACH_ELEMENT(i, operations) { r = sleep_supported_internal(sleep_config, *i, /* check_allowed = */ false, &support); if (r < 0) return r; @@ -338,8 +348,18 @@ static int sleep_supported_internal( return false; } - if (sleep_operation_is_hibernation(operation)) { - r = sleep_mode_supported(sleep_config->modes[operation]); + if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) { + r = sleep_mode_supported("/sys/power/mem_sleep", sleep_config->mem_modes); + if (r < 0) + return r; + if (r == 0) { + *ret_support = SLEEP_STATE_OR_MODE_NOT_SUPPORTED; + return false; + } + } + + if (SLEEP_OPERATION_IS_HIBERNATION(operation)) { + r = sleep_mode_supported("/sys/power/disk", sleep_config->modes[operation]); if (r < 0) return r; if (r == 0) { @@ -348,16 +368,28 @@ static int sleep_supported_internal( } r = hibernation_is_safe(); - if (r == -ENOTRECOVERABLE) { + switch (r) { + + case -ENOTRECOVERABLE: *ret_support = SLEEP_RESUME_NOT_SUPPORTED; return false; - } - if (r == -ENOSPC) { + + case -ESTALE: + *ret_support = SLEEP_RESUME_DEVICE_MISSING; + return false; + + case -ENOMEDIUM: + *ret_support = SLEEP_RESUME_MISCONFIGURED; + return false; + + case -ENOSPC: *ret_support = SLEEP_NOT_ENOUGH_SWAP_SPACE; return false; + + default: + if (r < 0) + return r; } - if (r < 0) - return r; } else assert(!sleep_config->modes[operation]); diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h index bc5aeb9..b59bce8 100644 --- a/src/shared/sleep-config.h +++ b/src/shared/sleep-config.h @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include "strv.h" #include "time-util.h" typedef enum SleepOperation { @@ -20,7 +21,7 @@ typedef enum SleepOperation { const char* sleep_operation_to_string(SleepOperation s) _const_; SleepOperation sleep_operation_from_string(const char *s) _pure_; -static inline bool sleep_operation_is_hibernation(SleepOperation operation) { +static inline bool SLEEP_OPERATION_IS_HIBERNATION(SleepOperation operation) { return IN_SET(operation, SLEEP_HIBERNATE, SLEEP_HYBRID_SLEEP); } @@ -28,7 +29,8 @@ typedef struct SleepConfig { bool allow[_SLEEP_OPERATION_MAX]; char **states[_SLEEP_OPERATION_CONFIG_MAX]; - char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image */ + char **modes[_SLEEP_OPERATION_CONFIG_MAX]; /* Power mode after writing hibernation image (/sys/power/disk) */ + char **mem_modes; /* /sys/power/mem_sleep */ usec_t hibernate_delay_usec; usec_t suspend_estimation_usec; @@ -39,12 +41,26 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(SleepConfig*, sleep_config_free); int parse_sleep_config(SleepConfig **sleep_config); +static inline bool SLEEP_NEEDS_MEM_SLEEP(const SleepConfig *sc, SleepOperation operation) { + assert(sc); + assert(operation >= 0 && operation < _SLEEP_OPERATION_CONFIG_MAX); + + /* As per https://docs.kernel.org/admin-guide/pm/sleep-states.html#basic-sysfs-interfaces-for-system-suspend-and-hibernation, + * /sys/power/mem_sleep is honored if /sys/power/state is set to "mem" (common for suspend) + * or /sys/power/disk is set to "suspend" (hybrid-sleep). */ + + return strv_contains(sc->states[operation], "mem") || + strv_contains(sc->modes[operation], "suspend"); +} + typedef enum SleepSupport { SLEEP_SUPPORTED, SLEEP_DISABLED, /* Disabled in SleepConfig.allow */ SLEEP_NOT_CONFIGURED, /* SleepConfig.states is not configured */ SLEEP_STATE_OR_MODE_NOT_SUPPORTED, /* SleepConfig.states/modes are not supported by kernel */ SLEEP_RESUME_NOT_SUPPORTED, + SLEEP_RESUME_DEVICE_MISSING, /* resume= is specified, but the device cannot be found in /proc/swaps */ + SLEEP_RESUME_MISCONFIGURED, /* resume= is not set yet resume_offset= is configured */ SLEEP_NOT_ENOUGH_SWAP_SPACE, SLEEP_ALARM_NOT_SUPPORTED, /* CLOCK_BOOTTIME_ALARM is unsupported by kernel (only used by s2h) */ } SleepSupport; @@ -55,5 +71,5 @@ static inline int sleep_supported(SleepOperation operation) { } /* Only for test-sleep-config */ -int sleep_state_supported(char **states); -int sleep_mode_supported(char **modes); +int sleep_state_supported(char * const *states); +int sleep_mode_supported(const char *path, char * const *modes); diff --git a/src/shared/socket-netlink.c b/src/shared/socket-netlink.c index 0ba5762..a126d5d 100644 --- a/src/shared/socket-netlink.c +++ b/src/shared/socket-netlink.c @@ -1,15 +1,19 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include -#include +#include #include #include "alloc-util.h" #include "errno-util.h" #include "extract-word.h" +#include "fd-util.h" #include "log.h" #include "memory-util.h" +#include "namespace-util.h" #include "netlink-util.h" #include "parse-util.h" #include "socket-netlink.h" @@ -407,3 +411,69 @@ const char *in_addr_full_to_string(struct in_addr_full *a) { return a->cached_server_string; } + +int netns_get_nsid(int netnsfd, uint32_t *ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_close_ int _netns_fd = -EBADF; + int r; + + if (netnsfd < 0) { + r = namespace_open( + 0, + /* ret_pidns_fd = */ NULL, + /* ret_mntns_fd = */ NULL, + &_netns_fd, + /* ret_userns_fd = */ NULL, + /* ret_root_fd = */ NULL); + if (r < 0) + return r; + + netnsfd = _netns_fd; + } + + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + + r = sd_rtnl_message_new_nsid(rtnl, &req, RTM_GETNSID); + if (r < 0) + return r; + + r = sd_netlink_message_append_s32(req, NETNSA_FD, netnsfd); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWNSID) + continue; + + uint32_t u; + r = sd_netlink_message_read_u32(m, NETNSA_NSID, &u); + if (r < 0) + return r; + + if (u == UINT32_MAX) /* no NSID assigned yet */ + return -ENODATA; + + if (ret) + *ret = u; + + return 0; + } + + return -ENXIO; +} diff --git a/src/shared/socket-netlink.h b/src/shared/socket-netlink.h index 6256a83..2c06fbe 100644 --- a/src/shared/socket-netlink.h +++ b/src/shared/socket-netlink.h @@ -42,3 +42,5 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(struct in_addr_full*, in_addr_full_free); int in_addr_full_new(int family, const union in_addr_union *a, uint16_t port, int ifindex, const char *server_name, struct in_addr_full **ret); int in_addr_full_new_from_string(const char *s, struct in_addr_full **ret); const char *in_addr_full_to_string(struct in_addr_full *a); + +int netns_get_nsid(int netnsfd, uint32_t *ret); diff --git a/src/shared/specifier.c b/src/shared/specifier.c index e5a1f94..f6739f2 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -78,8 +78,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[ if (!GREEDY_REALLOC(result, j + k + l + 1)) return -ENOMEM; - memcpy(result + j, w, k); - t = result + j + k; + t = mempcpy(result + j, w, k); } else if (strchr(POSSIBLE_SPECIFIERS, *f)) /* Oops, an unknown specifier. */ return -EBADSLT; @@ -112,18 +111,7 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[ /* Generic handler for simple string replacements */ int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) { - char *n = NULL; - - assert(ret); - - if (!isempty(data)) { - n = strdup(data); - if (!n) - return -ENOMEM; - } - - *ret = n; - return 0; + return strdup_to(ASSERT_PTR(ret), empty_to_null(data)); } int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) { @@ -250,32 +238,18 @@ int specifier_pretty_hostname(char specifier, const void *data, const char *root int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) { struct utsname uts; - char *n; assert(ret); if (uname(&uts) < 0) return -errno; - n = strdup(uts.release); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; + return strdup_to(ret, uts.release); } int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) { - char *t; - - assert(ret); - - t = strdup(architecture_to_string(uname_architecture())); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; + return strdup_to(ASSERT_PTR(ret), + architecture_to_string(uname_architecture())); } /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid @@ -421,7 +395,6 @@ int specifier_user_shell(char specifier, const void *data, const char *root, con int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; - char *copy; int r; assert(ret); @@ -433,17 +406,12 @@ int specifier_tmp_dir(char specifier, const void *data, const char *root, const if (r < 0) return r; } - copy = strdup(p); - if (!copy) - return -ENOMEM; - *ret = copy; - return 0; + return strdup_to(ret, p); } int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) { const char *p; - char *copy; int r; assert(ret); @@ -455,12 +423,8 @@ int specifier_var_tmp_dir(char specifier, const void *data, const char *root, co if (r < 0) return r; } - copy = strdup(p); - if (!copy) - return -ENOMEM; - *ret = copy; - return 0; + return strdup_to(ret, p); } int specifier_escape_strv(char **l, char ***ret) { diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index b620156..e64b6f6 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -30,8 +30,8 @@ int switch_root(const char *new_root, const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */ SwitchRootFlags flags) { - /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance, i.e. - * credentials, removable media and such, we rather want that the new boot mounts this fresh. But on + /* Stuff mounted below /run/ we don't save on soft reboot, as it might have lost its relevance, + * e.g. removable media and such. We rather want that the new boot mounts this fresh. But on * the switch from initrd we do use MS_REC, as it is expected that mounts set up in /run/ are * maintained. */ static const struct { @@ -39,13 +39,12 @@ int switch_root(const char *new_root, unsigned long mount_flags; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is unset */ unsigned long mount_flags_recursive_run; /* Flags to apply if SWITCH_ROOT_RECURSIVE_RUN is set (0 if shall be skipped) */ } transfer_table[] = { - { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */ - { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ - { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */ - { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */ - { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Credentials passed into the system should survive */ - { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, 0 /* skip! */ }, /* Similar */ - { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */ + { "/dev", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm/ + /dev/pts/ and similar */ + { "/sys", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ + { "/proc", MS_BIND|MS_REC, MS_BIND|MS_REC }, /* Similar */ + { "/run", MS_BIND, MS_BIND|MS_REC }, /* Recursive except on soft reboot, see above */ + { "/run/credentials", MS_BIND|MS_REC, 0 /* skip! */ }, /* Credential mounts should survive */ + { "/run/host", MS_BIND|MS_REC, 0 /* skip! */ }, /* Host supplied hierarchy should also survive */ }; _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF; @@ -63,11 +62,11 @@ int switch_root(const char *new_root, if (new_root_fd < 0) return log_error_errno(errno, "Failed to open target directory '%s': %m", new_root); - r = inode_same_at(old_root_fd, "", new_root_fd, "", AT_EMPTY_PATH); + r = fds_are_same_mount(old_root_fd, new_root_fd); if (r < 0) - return log_error_errno(r, "Failed to determine if old and new root directory are the same: %m"); + return log_error_errno(r, "Failed to check if old and new root directory/mount are the same: %m"); if (r > 0) { - log_debug("Skipping switch root, as old and new root directory are the same."); + log_debug("Skipping switch root, as old and new root directories/mounts are the same."); return 0; } @@ -126,7 +125,7 @@ int switch_root(const char *new_root, * and switch_root() nevertheless. */ (void) base_filesystem_create_fd(new_root_fd, new_root, UID_INVALID, GID_INVALID); - FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) { + FOREACH_ELEMENT(transfer, transfer_table) { _cleanup_free_ char *chased = NULL; unsigned long mount_flags; @@ -144,7 +143,7 @@ int switch_root(const char *new_root, return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, transfer->path); /* Let's see if it is a mount point already. */ - r = path_is_mount_point(chased, NULL, 0); + r = path_is_mount_point(chased); if (r < 0) return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); if (r > 0) /* If it is already mounted, then do nothing */ diff --git a/src/shared/tests.c b/src/shared/tests.c index 3882a18..9169513 100644 --- a/src/shared/tests.c +++ b/src/shared/tests.c @@ -21,6 +21,7 @@ #include "fd-util.h" #include "fs-util.h" #include "log.h" +#include "mountpoint-util.h" #include "namespace-util.h" #include "path-util.h" #include "process-util.h" @@ -113,9 +114,9 @@ bool slow_tests_enabled(void) { } void test_setup_logging(int level) { + log_set_assert_return_is_critical(true); log_set_max_level(level); - log_parse_environment(); - log_open(); + log_setup(); } int write_tmpfile(char *pattern, const char *contents) { @@ -188,12 +189,16 @@ static int allocate_scope(void) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *scope = NULL; + _cleanup_free_ char *scope = NULL, *cgroup_root = NULL; const char *object; int r; /* Let's try to run this test in a scope of its own, with delegation turned on, so that PID 1 doesn't * interfere with our cgroup management. */ + if (cg_pid_get_path(NULL, 0, &cgroup_root) >= 0 && cg_is_delegated(cgroup_root) && stderr_is_journal()) { + log_debug("Already running as a unit with delegated cgroup, not allocating a cgroup subroot."); + return 0; + } r = sd_bus_default_system(&bus); if (r < 0) @@ -249,7 +254,7 @@ static int allocate_scope(void) { if (r < 0) return bus_log_parse_error(r); - r = bus_wait_for_jobs_one(w, object, false, NULL); + r = bus_wait_for_jobs_one(w, object, BUS_WAIT_JOBS_LOG_ERROR, NULL); if (r < 0) return r; @@ -266,7 +271,7 @@ static int enter_cgroup(char **ret_cgroup, bool enter_subroot) { log_warning_errno(r, "Couldn't allocate a scope unit for this test, proceeding without."); r = cg_pid_get_path(NULL, 0, &cgroup_root); - if (r == -ENOMEDIUM) + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) return log_warning_errno(r, "cg_pid_get_path(NULL, 0, ...) failed: %m"); assert(r >= 0); diff --git a/src/shared/tests.h b/src/shared/tests.h index d76cf2e..09fdfd6 100644 --- a/src/shared/tests.h +++ b/src/shared/tests.h @@ -2,14 +2,41 @@ #pragma once #include +#include +#include #include "sd-daemon.h" #include "argv-util.h" +#include "errno-util.h" #include "macro.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "signal-util.h" #include "static-destruct.h" #include "strv.h" +static inline void log_set_assert_return_is_criticalp(bool *p) { + log_set_assert_return_is_critical(*p); +} + +#define _SAVE_ASSERT_RETURN_IS_CRITICAL(b) \ + _unused_ _cleanup_(log_set_assert_return_is_criticalp) bool b = \ + log_get_assert_return_is_critical() + +#define SAVE_ASSERT_RETURN_IS_CRITICAL \ + _SAVE_ASSERT_RETURN_IS_CRITICAL(UNIQ_T(saved, UNIQ)) + +#define ASSERT_RETURN_IS_CRITICAL(b, expr) \ + ({ \ + SAVE_ASSERT_RETURN_IS_CRITICAL; \ + log_set_assert_return_is_critical(b); \ + expr; \ + }) + +#define ASSERT_RETURN_EXPECTED(expr) ASSERT_RETURN_IS_CRITICAL(false, expr) +#define ASSERT_RETURN_EXPECTED_SE(expr) ASSERT_RETURN_EXPECTED(assert_se(expr)); + static inline bool manager_errno_skip_test(int r) { return IN_SET(abs(r), EPERM, @@ -58,7 +85,7 @@ bool can_memlock(void); #define DEFINE_HEX_PTR(name, hex) \ _cleanup_free_ void *name = NULL; \ size_t name##_len = 0; \ - assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0); + assert_se(unhexmem_full(hex, strlen_ptr(hex), false, &name, &name##_len) >= 0); #define TEST_REQ_RUNNING_SYSTEMD(x) \ if (sd_booted() > 0) { \ @@ -179,3 +206,216 @@ static inline int run_test_table(void) { DEFINE_TEST_MAIN_FULL(log_level, intro, NULL) #define DEFINE_TEST_MAIN(log_level) \ DEFINE_TEST_MAIN_FULL(log_level, NULL, NULL) + +#define ASSERT_OK(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result < 0) { \ + log_error_errno(_result, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_OK_ERRNO(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result < 0) { \ + log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to succeed but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_ERROR(expr1, expr2) \ + ({ \ + int _expr1 = (expr1); \ + int _expr2 = (expr2); \ + if (_expr1 >= 0) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } else if (-_expr1 != _expr2) { \ + log_error_errno(_expr1, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } \ + }) + +#define ASSERT_ERROR_ERRNO(expr1, expr2) \ + ({ \ + int _expr1 = (expr1); \ + int _expr2 = (expr2); \ + if (_expr1 >= 0) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but it succeeded", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(_expr2)); \ + abort(); \ + } else if (errno != _expr2) { \ + log_error_errno(errno, "%s:%i: Assertion failed: expected \"%s\" to fail with error \"%s\", but got the following error: %m", \ + PROJECT_FILE, __LINE__, #expr1, STRERROR(errno)); \ + abort(); \ + } \ + }) + +#define ASSERT_TRUE(expr) \ + ({ \ + if (!(expr)) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be true", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_FALSE(expr) \ + ({ \ + if ((expr)) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be false", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_NULL(expr) \ + ({ \ + typeof(expr) _result = (expr); \ + if (_result != NULL) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be NULL, but \"%p\" != NULL", \ + PROJECT_FILE, __LINE__, #expr, _result); \ + abort(); \ + } \ + }) + +#define ASSERT_NOT_NULL(expr) \ + ({ \ + if ((expr) == NULL) { \ + log_error("%s:%i: Assertion failed: expected \"%s\" to be not NULL", \ + PROJECT_FILE, __LINE__, #expr); \ + abort(); \ + } \ + }) + +#define ASSERT_STREQ(expr1, expr2) \ + ({ \ + const char *_expr1 = (expr1), *_expr2 = (expr2); \ + if (!streq_ptr(_expr1, _expr2)) { \ + log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, strnull(_expr1), strnull(_expr2)); \ + abort(); \ + } \ + }) + +/* DECIMAL_STR_FMT() uses _Generic which cannot be used in string concatenation so we have to format the + * input into strings first and then format those into the final assertion message. */ + +#define ASSERT_EQ(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 != _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s == %s\", but \"%s != %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_GE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 < _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s >= %s\", but \"%s < %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_LE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 > _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s <= %s\", but \"%s > %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_NE(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (_expr1 == _expr2) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s != %s\", but \"%s == %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_GT(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (!(_expr1 > _expr2)) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s > %s\", but \"%s <= %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_LT(expr1, expr2) \ + ({ \ + typeof(expr1) _expr1 = (expr1); \ + typeof(expr2) _expr2 = (expr2); \ + if (!(_expr1 < _expr2)) { \ + char _sexpr1[DECIMAL_STR_MAX(typeof(expr1))]; \ + char _sexpr2[DECIMAL_STR_MAX(typeof(expr2))]; \ + xsprintf(_sexpr1, DECIMAL_STR_FMT(_expr1), _expr1); \ + xsprintf(_sexpr2, DECIMAL_STR_FMT(_expr2), _expr2); \ + log_error("%s:%i: Assertion failed: expected \"%s < %s\", but \"%s >= %s\"", \ + PROJECT_FILE, __LINE__, #expr1, #expr2, _sexpr1, _sexpr2); \ + abort(); \ + } \ + }) + +#define ASSERT_SIGNAL(expr, signal) \ + ({ \ + ASSERT_TRUE(SIGNAL_VALID(signal)); \ + siginfo_t _siginfo = {}; \ + int _pid = fork(); \ + ASSERT_OK(_pid); \ + if (_pid == 0) { \ + /* Speed things up by never even attempting to generate a coredump */ \ + (void) prctl(PR_SET_DUMPABLE, 0); \ + /* But still set an rlimit just in case */ \ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(0)); \ + expr; \ + _exit(EXIT_SUCCESS); \ + } \ + (void) wait_for_terminate(_pid, &_siginfo); \ + if (_siginfo.si_status != signal) { \ + log_error("%s:%i: Assertion failed: \"%s\" died with signal %s, but %s was expected", \ + PROJECT_FILE, __LINE__, #expr, signal_to_string(_siginfo.si_status), \ + signal_to_string(signal)); \ + abort(); \ + } \ + }) diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c index c7e0b24..87ce53c 100644 --- a/src/shared/tpm2-util.c +++ b/src/shared/tpm2-util.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "constants.h" +#include "creds-util.h" #include "cryptsetup-util.h" #include "dirent-util.h" #include "dlfcn-util.h" @@ -25,8 +26,10 @@ #include "nulstr-util.h" #include "parse-util.h" #include "random-util.h" +#include "recurse-dir.h" #include "sha256.h" #include "sort-util.h" +#include "sparse-endian.h" #include "stat-util.h" #include "string-table.h" #include "sync-util.h" @@ -34,76 +37,87 @@ #include "tpm2-util.h" #include "virt.h" +#if HAVE_OPENSSL +# include +#endif + #if HAVE_TPM2 static void *libtss2_esys_dl = NULL; static void *libtss2_rc_dl = NULL; static void *libtss2_mu_dl = NULL; -static 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; -static TSS2_RC (*sym_Esys_CreateLoaded)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_TEMPLATE *inPublic, ESYS_TR *objectHandle, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic) = NULL; -static 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; -static 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) = NULL; -static void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; -static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; -static void (*sym_Esys_Free)(void *ptr) = NULL; -static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL; -static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; -static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL; -static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; -static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; -static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; -static TSS2_RC (*sym_Esys_NV_DefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_AUTH *auth, const TPM2B_NV_PUBLIC *publicInfo, ESYS_TR *nvHandle); -static TSS2_RC (*sym_Esys_NV_UndefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); -static TSS2_RC (*sym_Esys_NV_Write)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_MAX_NV_BUFFER *data, UINT16 offset); -static TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests) = NULL; -static TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL; -static TSS2_RC (*sym_Esys_PolicyAuthorizeNV)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); -static TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; -static TSS2_RC (*sym_Esys_PolicyOR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST *pHashList) = NULL; -static 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; -static 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) = NULL; -static 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; -static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; -static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL; -static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = NULL; -static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; -static 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) = NULL; -static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; -static TSS2_RC (*sym_Esys_TR_GetTpmHandle)(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, TPM2_HANDLE *tpm_handle) = NULL; -static TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size) = NULL; -static TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; -static TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags) = NULL; -static TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask) = NULL; -static 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; -static 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) = NULL; - -static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; -static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; - -static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; +static DLSYM_FUNCTION(Esys_Create); +static DLSYM_FUNCTION(Esys_CreateLoaded); +static DLSYM_FUNCTION(Esys_CreatePrimary); +static DLSYM_FUNCTION(Esys_EvictControl); +static DLSYM_FUNCTION(Esys_Finalize); +static DLSYM_FUNCTION(Esys_FlushContext); +static DLSYM_FUNCTION(Esys_Free); +static DLSYM_FUNCTION(Esys_GetCapability); +static DLSYM_FUNCTION(Esys_GetRandom); +static DLSYM_FUNCTION(Esys_Import); +static DLSYM_FUNCTION(Esys_Initialize); +static DLSYM_FUNCTION(Esys_Load); +static DLSYM_FUNCTION(Esys_LoadExternal); +static DLSYM_FUNCTION(Esys_NV_DefineSpace); +static DLSYM_FUNCTION(Esys_NV_UndefineSpace); +static DLSYM_FUNCTION(Esys_NV_Write); +static DLSYM_FUNCTION(Esys_PCR_Extend); +static DLSYM_FUNCTION(Esys_PCR_Read); +static DLSYM_FUNCTION(Esys_PolicyAuthValue); +static DLSYM_FUNCTION(Esys_PolicyAuthorize); +static DLSYM_FUNCTION(Esys_PolicyAuthorizeNV); +static DLSYM_FUNCTION(Esys_PolicyGetDigest); +static DLSYM_FUNCTION(Esys_PolicyOR); +static DLSYM_FUNCTION(Esys_PolicyPCR); +static DLSYM_FUNCTION(Esys_PolicySigned); +static DLSYM_FUNCTION(Esys_ReadPublic); +static DLSYM_FUNCTION(Esys_StartAuthSession); +static DLSYM_FUNCTION(Esys_Startup); +static DLSYM_FUNCTION(Esys_TestParms); +static DLSYM_FUNCTION(Esys_TR_Close); +static DLSYM_FUNCTION(Esys_TR_Deserialize); +static DLSYM_FUNCTION(Esys_TR_FromTPMPublic); +static DLSYM_FUNCTION(Esys_TR_GetName); +static DLSYM_FUNCTION(Esys_TR_GetTpmHandle); +static DLSYM_FUNCTION(Esys_TR_Serialize); +static DLSYM_FUNCTION(Esys_TR_SetAuth); +static DLSYM_FUNCTION(Esys_TRSess_GetAttributes); +static DLSYM_FUNCTION(Esys_TRSess_GetNonceTPM); +static DLSYM_FUNCTION(Esys_TRSess_SetAttributes); +static DLSYM_FUNCTION(Esys_Unseal); +static DLSYM_FUNCTION(Esys_VerifySignature); + +static DLSYM_FUNCTION(Tss2_MU_TPM2_CC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2_HANDLE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_DIGEST_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NAME_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PRIVATE_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_PUBLIC_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_SENSITIVE_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPML_PCR_SELECTION_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMS_NV_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal); +static DLSYM_FUNCTION(Tss2_MU_TPMS_ECC_POINT_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMT_HA_Marshal); +static DLSYM_FUNCTION(Tss2_MU_TPMT_PUBLIC_Marshal); +static DLSYM_FUNCTION(Tss2_MU_UINT32_Marshal); + +static DLSYM_FUNCTION(Tss2_RC_Decode); int dlopen_tpm2(void) { int r; + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-esys.so.0"); + r = dlopen_many_sym_or_warn( &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, DLSYM_ARG(Esys_Create), @@ -130,6 +144,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_PolicyGetDigest), DLSYM_ARG(Esys_PolicyOR), DLSYM_ARG(Esys_PolicyPCR), + DLSYM_ARG(Esys_PolicySigned), DLSYM_ARG(Esys_ReadPublic), DLSYM_ARG(Esys_StartAuthSession), DLSYM_ARG(Esys_Startup), @@ -141,6 +156,7 @@ int dlopen_tpm2(void) { DLSYM_ARG(Esys_TR_Serialize), DLSYM_ARG(Esys_TR_SetAuth), DLSYM_ARG(Esys_TRSess_GetAttributes), + DLSYM_ARG(Esys_TRSess_GetNonceTPM), DLSYM_ARG(Esys_TRSess_SetAttributes), DLSYM_ARG(Esys_Unseal), DLSYM_ARG(Esys_VerifySignature)); @@ -153,12 +169,22 @@ int dlopen_tpm2(void) { if (r < 0) log_debug("libtss2-esys too old, does not include Esys_TR_GetTpmHandle."); + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-rc.so.0"); + r = dlopen_many_sym_or_warn( &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, DLSYM_ARG(Tss2_RC_Decode)); if (r < 0) return r; + ELF_NOTE_DLOPEN("tpm", + "Support for TPM", + ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED, + "libtss2-mu.so.0"); + return dlopen_many_sym_or_warn( &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), @@ -1895,7 +1921,7 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { _cleanup_free_ void *buf = NULL; size_t buf_size = 0; - r = unhexmem(p, SIZE_MAX, &buf, &buf_size); + r = unhexmem(p, &buf, &buf_size); if (r < 0) return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p); @@ -2084,7 +2110,7 @@ int tpm2_create_primary( session ? session->esys_handle : ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, - sensitive ? sensitive : &(TPM2B_SENSITIVE_CREATE) {}, + sensitive ?: &(TPM2B_SENSITIVE_CREATE) {}, template, /* outsideInfo= */ NULL, &(TPML_PCR_SELECTION) {}, @@ -2254,9 +2280,9 @@ static int tpm2_load_external( #if HAVE_TSS2_ESYS3 /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested * hierarchy, older versions need TPM2_RH_* instead. */ - ESYS_TR_RH_OWNER, + private ? ESYS_TR_RH_NULL : ESYS_TR_RH_OWNER, #else - TPM2_RH_OWNER, + private ? TPM2_RH_NULL : TPM2_RH_OWNER, #endif &handle->esys_handle); if (rc != TSS2_RC_SUCCESS) @@ -3074,7 +3100,7 @@ static void tpm2_trim_auth_value(TPM2B_AUTH *auth) { log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2."); } -int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { +int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { TPM2B_AUTH auth = {}; int r; @@ -3121,7 +3147,7 @@ int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) { CLEANUP_ERASE(auth); - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &auth); if (r < 0) return r; @@ -3408,7 +3434,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) * * The handle must reference a key already present in the TPM. It may be either a public key only, or a * public/private keypair. */ -static int tpm2_get_name( +int tpm2_get_name( Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name) { @@ -3546,6 +3572,150 @@ int tpm2_policy_auth_value( return tpm2_get_policy_digest(c, session, ret_policy_digest); } +/* Extend 'digest' with the PolicySigned calculated hash. */ +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name) { + TPM2_CC command = TPM2_CC_PolicySigned; + TSS2_RC rc; + int r; + + assert(digest); + assert(digest->size == SHA256_DIGEST_SIZE); + assert(name); + + r = dlopen_tpm2(); + if (r < 0) + return log_debug_errno(r, "TPM2 support not installed: %m"); + + uint8_t buf[sizeof(command)]; + size_t offset = 0; + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicySigned command: %s", sym_Tss2_RC_Decode(rc)); + + if (offset != sizeof(command)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Offset 0x%zx wrong after marshalling PolicySigned command", offset); + + struct iovec data[] = { + IOVEC_MAKE(buf, offset), + IOVEC_MAKE(name->name, name->size), + }; + + r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true); + if (r < 0) + return r; + + const TPM2B_NONCE policyRef = {}; /* For now, we do not make use of the policyRef stuff */ + + r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, policyRef.buffer, policyRef.size, /* extend= */ true); + if (r < 0) + return r; + + tpm2_log_debug_digest(digest, "PolicySigned calculated digest"); + + return 0; +} + +int tpm2_policy_signed_hmac_sha256( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *hmac_key_handle, + const struct iovec *hmac_key, + TPM2B_DIGEST **ret_policy_digest) { + +#if HAVE_OPENSSL + TSS2_RC rc; + int r; + + assert(c); + assert(session); + assert(hmac_key_handle); + assert(iovec_is_set(hmac_key)); + + /* This sends a TPM2_PolicySigned command to the tpm. As signature key we use an HMAC-SHA256 key + * specified in the hmac_key parameter. The secret key must be loaded into the TPM already and + * referenced in hmac_key_handle. */ + + log_debug("Submitting PolicySigned policy for HMAC-SHA256."); + + /* Acquire the nonce from the TPM that we shall sign */ + _cleanup_(Esys_Freep) TPM2B_NONCE *nonce = NULL; + rc = sym_Esys_TRSess_GetNonceTPM( + c->esys_context, + session->esys_handle, + &nonce); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to determine NoneTPM of auth session: %s", + sym_Tss2_RC_Decode(rc)); + + be32_t expiration = htobe64(0); + const TPM2B_DIGEST cpHashA = {}; /* For now, we do not make use of the cpHashA stuff */ + const TPM2B_NONCE policyRef = {}; /* ditto, we do not bother with policyRef */ + + /* Put together the data to sign, as per TPM2 Spec Part 3, 23.3.1 */ + struct iovec data_to_sign[] = { + IOVEC_MAKE(nonce->buffer, nonce->size), + IOVEC_MAKE(&expiration, sizeof(expiration)), + IOVEC_MAKE(cpHashA.buffer, cpHashA.size), + IOVEC_MAKE(policyRef.buffer, policyRef.size), + }; + + /* Now calculate the digest of the data we put together */ + TPM2B_DIGEST digest_to_sign; + r = tpm2_digest_many(TPM2_ALG_SHA256, &digest_to_sign, data_to_sign, ELEMENTSOF(data_to_sign), /* extend= */ false); + if (r < 0) + return r; + + unsigned char hmac_signature[SHA256_DIGEST_SIZE]; + unsigned hmac_signature_size = sizeof(hmac_signature); + + /* And sign this with our key */ + if (!HMAC(EVP_sha256(), + hmac_key->iov_base, + hmac_key->iov_len, + digest_to_sign.buffer, + digest_to_sign.size, + hmac_signature, + &hmac_signature_size)) + return -ENOTRECOVERABLE; + + /* Now bring the signature into a format that the TPM understands */ + TPMT_SIGNATURE sig = { + .sigAlg = TPM2_ALG_HMAC, + .signature.hmac.hashAlg = TPM2_ALG_SHA256, + }; + assert(hmac_signature_size == sizeof(sig.signature.hmac.digest.sha256)); + memcpy(sig.signature.hmac.digest.sha256, hmac_signature, hmac_signature_size); + + /* And submit the whole shebang to the TPM */ + rc = sym_Esys_PolicySigned( + c->esys_context, + hmac_key_handle->esys_handle, + session->esys_handle, + /* shandle1= */ ESYS_TR_NONE, + /* shandle2= */ ESYS_TR_NONE, + /* shandle3= */ ESYS_TR_NONE, + nonce, + &cpHashA, + &policyRef, + expiration, + &sig, + /* timeout= */ NULL, + /* policyTicket= */ NULL); + if (rc != TSS2_RC_SUCCESS) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add PolicySigned policy to TPM: %s", + sym_Tss2_RC_Decode(rc)); + + return tpm2_get_policy_digest(c, session, ret_policy_digest); +#else /* HAVE_OPENSSL */ + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +#endif +} + int tpm2_calculate_policy_authorize_nv( const TPM2B_NV_PUBLIC *public_info, TPM2B_DIGEST *digest) { @@ -4151,7 +4321,7 @@ static const struct { static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_ECC_CURVE *ret) { assert(ret); - FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) + FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table) if (t->openssl_ecc_curve_id == openssl_ecc_curve_id) { *ret = t->tpm2_ecc_curve_id; return 0; @@ -4164,7 +4334,7 @@ static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_E static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE tpm2_ecc_curve_id, int *ret) { assert(ret); - FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) + FOREACH_ELEMENT(t, tpm2_openssl_ecc_curve_table) if (t->tpm2_ecc_curve_id == tpm2_ecc_curve_id) { *ret = t->openssl_ecc_curve_id; return 0; @@ -4801,7 +4971,7 @@ static int tpm2_calculate_seal_private( TPM2B_AUTH auth = {}; if (pin) { - r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth); + r = tpm2_auth_value_from_pin(parent->publicArea.nameAlg, pin, &auth); if (r < 0) return r; } @@ -5035,7 +5205,7 @@ static int tpm2_calculate_seal_ecc_seed( size_t bits = (size_t) r * 8; _cleanup_free_ void *seed = NULL; - size_t seed_size; + size_t seed_size = 0; /* Explicit initialization to appease gcc */ r = tpm2_kdfe(parent->publicArea.nameAlg, shared_secret, shared_secret_size, @@ -5072,7 +5242,7 @@ static int tpm2_calculate_seal_seed( log_debug("Calculating encrypted seed for sealed object."); _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL; - size_t seed_size, encrypted_seed_size; + size_t seed_size = 0, encrypted_seed_size = 0; /* Explicit initialization to appease gcc */ if (parent->publicArea.type == TPM2_ALG_RSA) r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); else if (parent->publicArea.type == TPM2_ALG_ECC) @@ -5095,28 +5265,22 @@ int tpm2_calculate_seal( TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, - const void *secret, - size_t secret_size, + const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, - void **ret_secret, - size_t *ret_secret_size, - void **ret_blob, - size_t *ret_blob_size, - void **ret_serialized_parent, - size_t *ret_serialized_parent_size) { + struct iovec *ret_secret, + struct iovec *ret_blob, + struct iovec *ret_serialized_parent) { #if HAVE_OPENSSL int r; assert(parent_public); - assert(secret || secret_size == 0); + assert(iovec_is_valid(secret)); assert(secret || ret_secret); assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */ assert(ret_blob); - assert(ret_blob_size); assert(ret_serialized_parent); - assert(ret_serialized_parent_size); log_debug("Calculating sealed object."); @@ -5137,27 +5301,27 @@ int tpm2_calculate_seal( parent_handle); } - _cleanup_(erase_and_freep) void *generated_secret = NULL; + _cleanup_(iovec_done_erase) struct iovec generated_secret = {}; if (!secret) { /* No secret provided, generate a random secret. We use SHA256 digest length, though it can * be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash * size. */ - secret_size = TPM2_SHA256_DIGEST_SIZE; - generated_secret = malloc(secret_size); - if (!generated_secret) + generated_secret.iov_len = TPM2_SHA256_DIGEST_SIZE; + generated_secret.iov_base = malloc(generated_secret.iov_len); + if (!generated_secret.iov_base) return log_oom_debug(); - r = crypto_random_bytes(generated_secret, secret_size); + r = crypto_random_bytes(generated_secret.iov_base, generated_secret.iov_len); if (r < 0) return log_debug_errno(r, "Failed to generate secret key: %m"); - secret = generated_secret; + secret = &generated_secret; } - if (secret_size > TPM2_MAX_SEALED_DATA) + if (secret->iov_len > TPM2_MAX_SEALED_DATA) return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Secret size %zu too large, limit is %d bytes.", - secret_size, TPM2_MAX_SEALED_DATA); + secret->iov_len, TPM2_MAX_SEALED_DATA); TPM2B_DIGEST random_seed; TPM2B_ENCRYPTED_SECRET seed; @@ -5166,7 +5330,7 @@ int tpm2_calculate_seal( return r; TPM2B_PUBLIC public; - r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public); + r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret->iov_base, secret->iov_len, &public); if (r < 0) return r; @@ -5176,13 +5340,12 @@ int tpm2_calculate_seal( return r; TPM2B_PRIVATE private; - r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private); + r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret->iov_base, secret->iov_len, &private); if (r < 0) return r; - _cleanup_free_ void *blob = NULL; - size_t blob_size; - r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size); + _cleanup_(iovec_done) struct iovec blob = {}; + r = tpm2_marshal_blob(&public, &private, &seed, &blob.iov_base, &blob.iov_len); if (r < 0) return log_debug_errno(r, "Could not create sealed blob: %m"); @@ -5191,25 +5354,20 @@ int tpm2_calculate_seal( if (r < 0) return r; - _cleanup_free_ void *serialized_parent = NULL; - size_t serialized_parent_size; + _cleanup_(iovec_done) struct iovec serialized_parent = {}; r = tpm2_calculate_serialize( parent_handle, &parent_name, parent_public, - &serialized_parent, - &serialized_parent_size); + &serialized_parent.iov_base, + &serialized_parent.iov_len); if (r < 0) return r; if (ret_secret) - *ret_secret = TAKE_PTR(generated_secret); - if (ret_secret_size) - *ret_secret_size = secret_size; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; - *ret_serialized_parent = TAKE_PTR(serialized_parent); - *ret_serialized_parent_size = serialized_parent_size; + *ret_secret = TAKE_STRUCT(generated_secret); + *ret_blob = TAKE_STRUCT(blob); + *ret_serialized_parent = TAKE_STRUCT(serialized_parent); return 0; #else /* HAVE_OPENSSL */ @@ -5221,21 +5379,16 @@ int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, - void **ret_secret, - size_t *ret_secret_size, - void **ret_blob, - size_t *ret_blob_size, + struct iovec *ret_secret, + struct iovec *ret_blob, uint16_t *ret_primary_alg, - void **ret_srk_buf, - size_t *ret_srk_buf_size) { + struct iovec *ret_srk) { uint16_t primary_alg = 0; int r; assert(ret_secret); - assert(ret_secret_size); assert(ret_blob); - assert(ret_blob_size); /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We @@ -5255,13 +5408,22 @@ int tpm2_seal(Tpm2Context *c, usec_t start = now(CLOCK_MONOTONIC); + TPMA_OBJECT hmac_attributes = + TPMA_OBJECT_FIXEDTPM | + TPMA_OBJECT_FIXEDPARENT; + + /* If protected by PIN, a user-selected low-entropy password, enable DA protection. + Without a PIN, the key's left protected only by a PCR policy, which does not benefit + from DA protection. */ + hmac_attributes |= pin ? 0 : TPMA_OBJECT_NODA; + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ TPMT_PUBLIC hmac_template = { .type = TPM2_ALG_KEYEDHASH, .nameAlg = TPM2_ALG_SHA256, - .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, + .objectAttributes = hmac_attributes, .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, .unique.keyedHash.size = SHA256_DIGEST_SIZE, .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), @@ -5274,7 +5436,7 @@ int tpm2_seal(Tpm2Context *c, CLEANUP_ERASE(hmac_sensitive); if (pin) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth); + r = tpm2_auth_value_from_pin(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth); if (r < 0) return r; } @@ -5290,7 +5452,7 @@ int tpm2_seal(Tpm2Context *c, return log_debug_errno(r, "Failed to generate secret key: %m"); _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; - if (ret_srk_buf) { + if (ret_srk) { _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) { @@ -5328,7 +5490,7 @@ int tpm2_seal(Tpm2Context *c, if (seal_key_handle != 0) log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle."); - /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ + /* TODO: force all callers to provide ret_srk, so we can stop sealing with the legacy templates. */ primary_alg = TPM2_ALG_ECC; TPM2B_PUBLIC template = { @@ -5372,47 +5534,46 @@ int tpm2_seal(Tpm2Context *c, if (r < 0) return r; - _cleanup_(erase_and_freep) void *secret = NULL; - secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); - if (!secret) + _cleanup_(iovec_done_erase) struct iovec secret = {}; + secret.iov_base = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (!secret.iov_base) return log_oom_debug(); + secret.iov_len = hmac_sensitive.data.size; log_debug("Marshalling private and public part of HMAC key."); - _cleanup_free_ void *blob = NULL; - size_t blob_size = 0; - r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size); + _cleanup_(iovec_done) struct iovec blob = {}; + r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob.iov_base, &blob.iov_len); if (r < 0) return log_debug_errno(r, "Could not create sealed blob: %m"); if (DEBUG_LOGGING) log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); - _cleanup_free_ void *srk_buf = NULL; - size_t srk_buf_size = 0; - if (ret_srk_buf) { + if (ret_srk) { + _cleanup_(iovec_done) struct iovec srk = {}; _cleanup_(Esys_Freep) void *tmp = NULL; - r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size); + size_t tmp_size; + + r = tpm2_serialize(c, primary_handle, &tmp, &tmp_size); if (r < 0) return r; /* * 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. + * to use for srk in which context a PITA. */ - srk_buf = memdup(tmp, srk_buf_size); - if (!srk_buf) + srk.iov_base = memdup(tmp, tmp_size); + if (!srk.iov_base) return log_oom_debug(); + srk.iov_len = tmp_size; - *ret_srk_buf = TAKE_PTR(srk_buf); - *ret_srk_buf_size = srk_buf_size; + *ret_srk = TAKE_STRUCT(srk); } - *ret_secret = TAKE_PTR(secret); - *ret_secret_size = hmac_sensitive.data.size; - *ret_blob = TAKE_PTR(blob); - *ret_blob_size = blob_size; + *ret_secret = TAKE_STRUCT(secret); + *ret_blob = TAKE_STRUCT(blob); if (ret_primary_alg) *ret_primary_alg = primary_alg; @@ -5425,31 +5586,24 @@ int tpm2_seal(Tpm2Context *c, int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, - const void *blob, - size_t blob_size, - const void *known_policy_hash, - size_t known_policy_hash_size, - const void *srk_buf, - size_t srk_buf_size, - void **ret_secret, - size_t *ret_secret_size) { + const struct iovec *blob, + const struct iovec *known_policy_hash, + const struct iovec *srk, + struct iovec *ret_secret) { TSS2_RC rc; int r; - assert(blob); - assert(blob_size > 0); - assert(known_policy_hash_size == 0 || known_policy_hash); - assert(pubkey_size == 0 || pubkey); + assert(iovec_is_set(blob)); + assert(iovec_is_valid(known_policy_hash)); + assert(iovec_is_valid(pubkey)); assert(ret_secret); - assert(ret_secret_size); assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); @@ -5467,7 +5621,7 @@ int tpm2_unseal(Tpm2Context *c, TPM2B_PUBLIC public; TPM2B_PRIVATE private; TPM2B_ENCRYPTED_SECRET seed = {}; - r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed); + r = tpm2_unmarshal_blob(blob->iov_base, blob->iov_len, &public, &private, &seed); if (r < 0) return log_debug_errno(r, "Could not extract parts from blob: %m"); @@ -5480,8 +5634,8 @@ int tpm2_unseal(Tpm2Context *c, } _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; - if (srk_buf) { - r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle); + if (iovec_is_set(srk)) { + r = tpm2_deserialize(c, srk->iov_base, srk->iov_len, &primary_handle); if (r < 0) return r; } else if (primary_alg != 0) { @@ -5537,14 +5691,13 @@ int tpm2_unseal(Tpm2Context *c, return r; TPM2B_PUBLIC pubkey_tpm2b; - _cleanup_free_ void *fp = NULL; - size_t fp_size = 0; - if (pubkey) { - r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); + _cleanup_(iovec_done) struct iovec fp = {}; + if (iovec_is_set(pubkey)) { + r = tpm2_tpm2b_public_from_pem(pubkey->iov_base, pubkey->iov_len, &pubkey_tpm2b); if (r < 0) return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m"); - r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size); + r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp.iov_base, &fp.iov_len); if (r < 0) return log_debug_errno(r, "Could not get key fingerprint: %m"); } @@ -5582,8 +5735,8 @@ int tpm2_unseal(Tpm2Context *c, policy_session, hash_pcr_mask, pcr_bank, - pubkey ? &pubkey_tpm2b : NULL, - fp, fp_size, + iovec_is_set(pubkey) ? &pubkey_tpm2b : NULL, + fp.iov_base, fp.iov_len, pubkey_pcr_mask, signature, !!pin, @@ -5594,11 +5747,12 @@ int tpm2_unseal(Tpm2Context *c, /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not * wait until the TPM2 tells us to go away. */ - if (known_policy_hash_size > 0 && - memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) { - + if (iovec_is_set(known_policy_hash) && memcmp_nn(policy_digest->buffer, + policy_digest->size, + known_policy_hash->iov_base, + known_policy_hash->iov_len) != 0) { #if HAVE_OPENSSL - if (pubkey_size > 0 && + if (iovec_is_set(pubkey) && pubkey_tpm2b.publicArea.type == TPM2_ALG_RSA && pubkey_tpm2b.publicArea.parameters.rsaDetail.exponent == TPM2_RSA_DEFAULT_EXPONENT) { /* Due to bug #30546, if using RSA pubkey with the default exponent, we may @@ -5630,23 +5784,24 @@ int tpm2_unseal(Tpm2Context *c, log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); } - _cleanup_(erase_and_freep) char *secret = NULL; - secret = memdup(unsealed->buffer, unsealed->size); + _cleanup_(iovec_done_erase) struct iovec secret = {}; + secret.iov_base = memdup(unsealed->buffer, unsealed->size); explicit_bzero_safe(unsealed->buffer, unsealed->size); - if (!secret) + if (!secret.iov_base) return log_oom_debug(); + secret.iov_len = unsealed->size; if (DEBUG_LOGGING) log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); - *ret_secret = TAKE_PTR(secret); - *ret_secret_size = unsealed->size; + *ret_secret = TAKE_STRUCT(secret); return 0; } static TPM2_HANDLE generate_random_nv_index(void) { - return TPM2_NV_INDEX_FIRST + (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_LAST - TPM2_NV_INDEX_FIRST + 1); + return TPM2_NV_INDEX_UNASSIGNED_FIRST + + (TPM2_HANDLE) random_u64_range(TPM2_NV_INDEX_UNASSIGNED_LAST - TPM2_NV_INDEX_UNASSIGNED_FIRST + 1); } int tpm2_define_policy_nv_index( @@ -5654,8 +5809,6 @@ int tpm2_define_policy_nv_index( const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, - const char *pin, - const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public) { @@ -5665,7 +5818,10 @@ int tpm2_define_policy_nv_index( int r; assert(c); - assert(pin || auth); + + /* Allocates an nvindex to store a policy for use in PolicyAuthorizeNV in. This is where pcrlock then + * stores its predicted PCR policies in. If 'requested_nv_index' will try to allocate the specified + * nvindex, otherwise will find a free one, and use that. */ r = tpm2_handle_new(c, &new_handle); if (r < 0) @@ -5673,17 +5829,6 @@ int tpm2_define_policy_nv_index( new_handle->flush = false; /* This is a persistent NV index, don't flush hence */ - TPM2B_AUTH _auth = {}; - CLEANUP_ERASE(_auth); - - if (!auth) { - r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &_auth); - if (r < 0) - return r; - - auth = &_auth; - } - for (unsigned try = 0; try < 25U; try++) { TPM2_HANDLE nv_index; @@ -5711,7 +5856,7 @@ int tpm2_define_policy_nv_index( /* shandle1= */ session ? session->esys_handle : ESYS_TR_PASSWORD, /* shandle2= */ ESYS_TR_NONE, /* shandle3= */ ESYS_TR_NONE, - auth, + /* auth= */ NULL, &public_info, &new_handle->esys_handle); @@ -5937,10 +6082,7 @@ int tpm2_unseal_data( "Failed to unseal data: %s", sym_Tss2_RC_Decode(rc)); _cleanup_(iovec_done) struct iovec d = {}; - d = (struct iovec) { - .iov_base = memdup(unsealed->buffer, unsealed->size), - .iov_len = unsealed->size, - }; + d = IOVEC_MAKE(memdup(unsealed->buffer, unsealed->size), unsealed->size); explicit_bzero_safe(unsealed->buffer, unsealed->size); @@ -6011,7 +6153,7 @@ int tpm2_list_devices(void) { } } - if (table_get_rows(t) <= 1) { + if (table_isempty(t)) { log_info("No suitable TPM2 devices found."); return 0; } @@ -6839,6 +6981,43 @@ int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path) return 0; } +int tpm2_pcrlock_policy_from_json( + JsonVariant *v, + Tpm2PCRLockPolicy *ret_policy) { + + /* We use a type check of _JSON_VARIANT_TYPE_INVALID for the integer fields to allow + * json_dispatch_uint32() to parse strings as integers to work around the integer type weakness of + * JSON's design. */ + JsonDispatch policy_dispatch[] = { + { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY }, + { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY }, + { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY }, + { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY }, + { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY }, + { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY }, + { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY }, + { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY }, + {} + }; + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; + int r; + + assert(v); + assert(ret_policy); + + r = json_dispatch(v, policy_dispatch, JSON_LOG, &policy); + if (r < 0) + return r; + + r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json); + if (r < 0) + return r; + + *ret_policy = TAKE_STRUCT(policy); + return 1; +} + int tpm2_pcrlock_policy_load( const char *path, Tpm2PCRLockPolicy *ret_policy) { @@ -6855,41 +7034,141 @@ int tpm2_pcrlock_policy_load( if (r < 0) return log_error_errno(r, "Failed to load TPM2 pcrlock policy file: %m"); - _cleanup_(json_variant_unrefp) JsonVariant *configuration_json = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; r = json_parse_file( f, discovered_path, /* flags = */ 0, - &configuration_json, + &v, /* ret_line= */ NULL, /* ret_column= */ NULL); if (r < 0) return log_error_errno(r, "Failed to parse existing pcrlock policy file '%s': %m", discovered_path); - JsonDispatch policy_dispatch[] = { - { "pcrBank", JSON_VARIANT_STRING, json_dispatch_tpm2_algorithm, offsetof(Tpm2PCRLockPolicy, algorithm), JSON_MANDATORY }, - { "pcrValues", JSON_VARIANT_ARRAY, json_dispatch_variant, offsetof(Tpm2PCRLockPolicy, prediction_json), JSON_MANDATORY }, - { "nvIndex", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint32, offsetof(Tpm2PCRLockPolicy, nv_index), JSON_MANDATORY }, - { "nvHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_handle), JSON_MANDATORY }, - { "nvPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, nv_public), JSON_MANDATORY }, - { "srkHandle", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, srk_handle), JSON_MANDATORY }, - { "pinPublic", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_public), JSON_MANDATORY }, - { "pinPrivate", JSON_VARIANT_STRING, json_dispatch_unbase64_iovec, offsetof(Tpm2PCRLockPolicy, pin_private), JSON_MANDATORY }, - {} - }; + return tpm2_pcrlock_policy_from_json(v, ret_policy); +} - _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy policy = {}; +static int pcrlock_policy_load_credential( + const char *name, + const struct iovec *data, + Tpm2PCRLockPolicy *ret) { + + _cleanup_free_ char *c = NULL; + int r; + + assert(name); - r = json_dispatch(configuration_json, policy_dispatch, JSON_LOG, &policy); + c = strdup(name); + if (!c) + return log_oom(); + + ascii_strlower(c); /* Lowercase, to match what we did at encryption time */ + + _cleanup_(iovec_done) struct iovec decoded = {}; + r = decrypt_credential_and_warn( + c, + now(CLOCK_REALTIME), + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + UID_INVALID, + data, + CREDENTIAL_ALLOW_NULL, + &decoded); if (r < 0) return r; - r = tpm2_pcr_prediction_from_json(&policy.prediction, policy.algorithm, policy.prediction_json); + if (memchr(decoded.iov_base, 0, decoded.iov_len)) + return log_error_errno(r, "Credential '%s' contains embedded NUL byte, refusing.", name); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + r = json_parse(decoded.iov_base, + /* flags= */ 0, + &v, + /* ret_line= */ NULL, + /* ret_column= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse pcrlock policy: %m"); + + r = tpm2_pcrlock_policy_from_json(v, ret); if (r < 0) return r; - *ret_policy = TAKE_STRUCT(policy); - return 1; + return 0; +} + +int tpm2_pcrlock_policy_from_credentials( + const struct iovec *srk, + const struct iovec *nv, + Tpm2PCRLockPolicy *ret) { + + _cleanup_close_ int dfd = -EBADF; + int r; + + /* During boot we'll not have access to the pcrlock.json file in /var/. In order to support + * pcrlock-bound root file systems we'll store a copy of the JSON data, wrapped in an (plaintext) + * credential in the ESP or XBOOTLDR partition. There might be multiple of those however (because of + * multi-boot), hence we use the SRK and NV data from the LUKS2 header as search key, and parse all + * such JSON policies until we find a matching one. */ + + const char *cp = secure_getenv("SYSTEMD_ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY") ?: ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY; + + dfd = open(cp, O_CLOEXEC|O_DIRECTORY); + if (dfd < 0) { + if (errno == ENOENT) { + log_debug("No encrypted system credentials passed."); + return 0; + } + + return log_error_errno(errno, "Failed to open system credentials directory."); + } + + _cleanup_free_ DirectoryEntries *de = NULL; + r = readdir_all(dfd, RECURSE_DIR_IGNORE_DOT, &de); + if (r < 0) + return log_error_errno(r, "Failed to enumerate system credentials: %m"); + + FOREACH_ARRAY(i, de->entries, de->n_entries) { + _cleanup_(iovec_done) struct iovec data = {}; + struct dirent *d = *i; + + if (!startswith_no_case(d->d_name, "pcrlock.")) /* VFAT is case-insensitive, hence don't be too strict here */ + continue; + + r = read_full_file_full( + dfd, d->d_name, + /* offset= */ UINT64_MAX, + /* size= */ CREDENTIAL_ENCRYPTED_SIZE_MAX, + READ_FULL_FILE_UNBASE64|READ_FULL_FILE_FAIL_WHEN_LARGER, + /* bind_name= */ NULL, + (char**) &data.iov_base, + &data.iov_len); + if (r == -ENOENT) + continue; + if (r < 0) { + log_warning_errno(r, "Failed to read credentials file %s/%s, skipping: %m", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name); + continue; + } + + _cleanup_(tpm2_pcrlock_policy_done) Tpm2PCRLockPolicy loaded_policy = {}; + r = pcrlock_policy_load_credential( + d->d_name, + &data, + &loaded_policy); + if (r < 0) { + log_warning_errno(r, "Loading of pcrlock policy from credential '%s/%s' failed, skipping.", ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, d->d_name); + continue; + } + + if ((!srk || iovec_memcmp(srk, &loaded_policy.srk_handle) == 0) && + (!nv || iovec_memcmp(nv, &loaded_policy.nv_handle) == 0)) { + *ret = TAKE_STRUCT(loaded_policy); + return 1; + } + } + + log_info("No pcrlock policy found among system credentials."); + *ret = (Tpm2PCRLockPolicy) {}; + return 0; } int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { @@ -6929,6 +7208,75 @@ int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret) { *ret = device_key_public; return 0; } + +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret) { + int r; + + assert(c); + assert(pin); + assert(ret); + + log_debug("Converting PIN into TPM2 HMAC-SHA256 object."); + + /* Load the PIN (which we have stored in the "auth" TPM2B_AUTH) into the TPM as an HMAC key so that + * we can use it in a TPM2_PolicySigned() to write to the nvindex. For that we'll prep a pair of + * TPM2B_PUBLIC and TPM2B_SENSITIVE that defines an HMAC-SHA256 keyed hash function, and initialize + * it based on the provided PIN data. */ + + TPM2B_PUBLIC auth_hmac_public = { + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_SIGN_ENCRYPT, + .parameters.keyedHashDetail.scheme = { + .scheme = TPM2_ALG_HMAC, + .details.hmac.hashAlg = TPM2_ALG_SHA256, + }, + .unique.keyedHash.size = SHA256_DIGEST_SIZE, + }, + }; + + TPM2B_SENSITIVE auth_hmac_private = { + .sensitiveArea = { + .sensitiveType = TPM2_ALG_KEYEDHASH, + .sensitive.bits.size = pin->size, + .seedValue.size = SHA256_DIGEST_SIZE, + }, + }; + + /* Copy in the key data */ + memcpy_safe(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, pin->buffer, pin->size); + + /* NB: We initialize the seed of the TPMT_SENSITIVE structure to all zeroes, since we want a stable + * "name" of the PIN object */ + + /* Now calculate the "unique" field for the public area, based on the sensitive data, according to + * the algorithm in the TPM2 spec, part 1, Section 27.5.3.2 */ + struct iovec sensitive_data[] = { + IOVEC_MAKE(auth_hmac_private.sensitiveArea.seedValue.buffer, auth_hmac_private.sensitiveArea.seedValue.size), + IOVEC_MAKE(auth_hmac_private.sensitiveArea.sensitive.bits.buffer, auth_hmac_private.sensitiveArea.sensitive.bits.size), + }; + r = tpm2_digest_many( + auth_hmac_public.publicArea.nameAlg, + &auth_hmac_public.publicArea.unique.keyedHash, + sensitive_data, + ELEMENTSOF(sensitive_data), + /* extend= */ false); + if (r < 0) + return r; + + /* And now load the public/private parts into the TPM and get a handle back */ + r = tpm2_load_external( + c, + session, + &auth_hmac_public, + &auth_hmac_private, + ret); + if (r < 0) + return log_error_errno(r, "Failed to load PIN into TPM2: %m"); + + return 0; +} #endif char *tpm2_pcr_mask_to_string(uint32_t mask) { @@ -7002,18 +7350,14 @@ int tpm2_make_luks2_json( int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, - const void *pubkey, - size_t pubkey_size, + const struct iovec *pubkey, 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, + const struct iovec *blob, + const struct iovec *policy_hash, + const struct iovec *salt, + const struct iovec *srk, + const struct iovec *pcrlock_nv, TPM2Flags flags, JsonVariant **ret) { @@ -7021,9 +7365,9 @@ int tpm2_make_luks2_json( _cleanup_free_ char *keyslot_as_string = NULL; int r; - assert(blob || blob_size == 0); - assert(policy_hash || policy_hash_size == 0); - assert(pubkey || pubkey_size == 0); + assert(iovec_is_valid(pubkey)); + assert(iovec_is_valid(blob)); + assert(iovec_is_valid(policy_hash)); if (asprintf(&keyslot_as_string, "%i", keyslot) < 0) return -ENOMEM; @@ -7046,17 +7390,18 @@ int tpm2_make_luks2_json( JSON_BUILD_OBJECT( JSON_BUILD_PAIR("type", JSON_BUILD_CONST_STRING("systemd-tpm2")), JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), - JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), + JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_IOVEC_BASE64(blob)), JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)), - JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), - JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), - JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), - JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)), - JSON_BUILD_PAIR("tpm2_pcrlock", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PCRLOCK)), + JSON_BUILD_PAIR_CONDITION(pcr_bank != 0 && tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), + JSON_BUILD_PAIR_CONDITION(primary_alg != 0 && tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), + JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_IOVEC_HEX(policy_hash)), + JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PIN), "tpm2-pin", JSON_BUILD_BOOLEAN(true)), + JSON_BUILD_PAIR_CONDITION(FLAGS_SET(flags, TPM2_FLAGS_USE_PCRLOCK), "tpm2_pcrlock", JSON_BUILD_BOOLEAN(true)), 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(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size)))); + JSON_BUILD_PAIR_CONDITION(iovec_is_set(pubkey), "tpm2_pubkey", JSON_BUILD_IOVEC_BASE64(pubkey)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(salt), "tpm2_salt", JSON_BUILD_IOVEC_BASE64(salt)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(srk), "tpm2_srk", JSON_BUILD_IOVEC_BASE64(srk)), + JSON_BUILD_PAIR_CONDITION(iovec_is_set(pcrlock_nv), "tpm2_pcrlock_nv", JSON_BUILD_IOVEC_BASE64(pcrlock_nv)))); if (r < 0) return r; @@ -7071,22 +7416,17 @@ int tpm2_parse_luks2_json( int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, - void **ret_pubkey, - size_t *ret_pubkey_size, + struct iovec *ret_pubkey, 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, + struct iovec *ret_blob, + struct iovec *ret_policy_hash, + struct iovec *ret_salt, + struct iovec *ret_srk, + struct iovec *ret_pcrlock_nv, TPM2Flags *ret_flags) { - _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; + _cleanup_(iovec_done) struct iovec blob = {}, policy_hash = {}, pubkey = {}, salt = {}, srk = {}, pcrlock_nv = {}; 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 */ @@ -7151,7 +7491,7 @@ int tpm2_parse_luks2_json( if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-blob' field."); - r = json_variant_unbase64(w, &blob, &blob_size); + r = json_variant_unbase64_iovec(w, &blob); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2-blob' field."); @@ -7159,7 +7499,7 @@ int tpm2_parse_luks2_json( if (!w) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 token data lacks 'tpm2-policy-hash' field."); - r = json_variant_unhex(w, &policy_hash, &policy_hash_size); + r = json_variant_unhex_iovec(w, &policy_hash); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2-policy-hash' field."); @@ -7181,7 +7521,7 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_salt"); if (w) { - r = json_variant_unbase64(w, &salt, &salt_size); + r = json_variant_unbase64_iovec(w, &salt); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field."); } @@ -7195,7 +7535,7 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_pubkey"); if (w) { - r = json_variant_unbase64(w, &pubkey, &pubkey_size); + r = json_variant_unbase64_iovec(w, &pubkey); if (r < 0) return log_debug_errno(r, "Failed to decode PCR public key."); } else if (pubkey_pcr_mask != 0) @@ -7203,11 +7543,18 @@ int tpm2_parse_luks2_json( w = json_variant_by_key(v, "tpm2_srk"); if (w) { - r = json_variant_unbase64(w, &srk_buf, &srk_buf_size); + r = json_variant_unbase64_iovec(w, &srk); if (r < 0) return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field."); } + w = json_variant_by_key(v, "tpm2_pcrlock_nv"); + if (w) { + r = json_variant_unbase64_iovec(w, &pcrlock_nv); + if (r < 0) + return log_debug_errno(r, "Invalid base64 data in 'tpm2_pcrlock_nv' field."); + } + if (ret_keyslot) *ret_keyslot = keyslot; if (ret_hash_pcr_mask) @@ -7215,32 +7562,23 @@ int tpm2_parse_luks2_json( if (ret_pcr_bank) *ret_pcr_bank = pcr_bank; if (ret_pubkey) - *ret_pubkey = TAKE_PTR(pubkey); - if (ret_pubkey_size) - *ret_pubkey_size = pubkey_size; + *ret_pubkey = TAKE_STRUCT(pubkey); if (ret_pubkey_pcr_mask) *ret_pubkey_pcr_mask = pubkey_pcr_mask; if (ret_primary_alg) *ret_primary_alg = primary_alg; if (ret_blob) - *ret_blob = TAKE_PTR(blob); - if (ret_blob_size) - *ret_blob_size = blob_size; + *ret_blob = TAKE_STRUCT(blob); if (ret_policy_hash) - *ret_policy_hash = TAKE_PTR(policy_hash); - if (ret_policy_hash_size) - *ret_policy_hash_size = policy_hash_size; + *ret_policy_hash = TAKE_STRUCT(policy_hash); if (ret_salt) - *ret_salt = TAKE_PTR(salt); - if (ret_salt_size) - *ret_salt_size = salt_size; + *ret_salt = TAKE_STRUCT(salt); + if (ret_srk) + *ret_srk = TAKE_STRUCT(srk); + if (ret_pcrlock_nv) + *ret_pcrlock_nv = TAKE_STRUCT(pcrlock_nv); 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; } @@ -7553,7 +7891,7 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { /* Tries to load a JSON PCR signature file. Takes an absolute path, a simple file name or NULL. In * the latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */ - search = strv_split_nulstr(CONF_PATHS_NULSTR("systemd")); + search = strv_new(CONF_PATHS("systemd")); if (!search) return log_oom_debug(); @@ -7618,7 +7956,7 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass, size_t saltlen, uint8_t ret_key[static SHA256_DIGEST_SIZE]) { - uint8_t _cleanup_(erase_and_freep) *buffer = NULL; + _cleanup_(erase_and_freep) uint8_t *buffer = NULL; uint8_t u[SHA256_DIGEST_SIZE]; /* To keep this simple, since derived KeyLen (dkLen in docs) diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h index 911a3c7..ed306d4 100644 --- a/src/shared/tpm2-util.h +++ b/src/shared/tpm2-util.h @@ -132,6 +132,7 @@ int tpm2_marshal_nv_public(const TPM2B_NV_PUBLIC *nv_public, void **ret, size_t int tpm2_unmarshal_nv_public(const void *data, size_t size, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size); int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed); +int tpm2_get_name(Tpm2Context *c, const Tpm2Handle *handle, TPM2B_NAME **ret_name); bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); @@ -246,8 +247,10 @@ typedef struct Tpm2PCRLockPolicy { } Tpm2PCRLockPolicy; void tpm2_pcrlock_policy_done(Tpm2PCRLockPolicy *data); +int tpm2_pcrlock_policy_from_json(JsonVariant *v, Tpm2PCRLockPolicy *ret_policy); int tpm2_pcrlock_search_file(const char *path, FILE **ret_file, char **ret_path); int tpm2_pcrlock_policy_load(const char *path, Tpm2PCRLockPolicy *ret_policy); +int tpm2_pcrlock_policy_from_credentials(const struct iovec *srk, const struct iovec *nv, Tpm2PCRLockPolicy *ret); int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); @@ -255,7 +258,7 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); -int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth); +int tpm2_auth_value_from_pin(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth); int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin); int tpm2_set_auth_binary(Tpm2Context *c, const Tpm2Handle *handle, const TPM2B_AUTH *auth); @@ -266,6 +269,7 @@ int tpm2_policy_authorize_nv(Tpm2Context *c, const Tpm2Handle *session, const Tp int tpm2_policy_pcr(Tpm2Context *c, const Tpm2Handle *session, const TPML_PCR_SELECTION *pcr_selection, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_or(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST **ret_policy_digest); int tpm2_policy_super_pcr(Tpm2Context *c, const Tpm2Handle *session, const Tpm2PCRPrediction *prediction, uint16_t algorithm); +int tpm2_policy_signed_hmac_sha256(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *hmac_key_handle, const struct iovec *hmac_key, TPM2B_DIGEST **ret_policy_digest); int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); int tpm2_calculate_nv_index_name(const TPMS_NV_PUBLIC *nvpublic, TPM2B_NAME *ret_name); @@ -276,9 +280,10 @@ int tpm2_calculate_policy_authorize_nv(const TPM2B_NV_PUBLIC *public, TPM2B_DIGE int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); int tpm2_calculate_policy_or(const TPM2B_DIGEST *branches, size_t n_branches, TPM2B_DIGEST *digest); int tpm2_calculate_policy_super_pcr(Tpm2PCRPrediction *prediction, uint16_t algorithm, TPM2B_DIGEST *pcr_policy); +int tpm2_calculate_policy_signed(TPM2B_DIGEST *digest, const TPM2B_NAME *name); int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size); int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, const Tpm2PCRLockPolicy *policy, TPM2B_DIGEST *digest); -int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size); +int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const struct iovec *secret, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, struct iovec *ret_serialized_parent); int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); @@ -286,8 +291,8 @@ int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); -int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); -int tpm2_unseal(Tpm2Context *c, 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, const Tpm2PCRLockPolicy *pcrlock_policy, 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); +int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, struct iovec *ret_secret, struct iovec *ret_blob, uint16_t *ret_primary_alg, struct iovec *ret_srk); +int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, const Tpm2PCRLockPolicy *pcrlock_policy, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *srk, struct iovec *ret_secret); #if HAVE_OPENSSL int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); @@ -297,7 +302,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret); int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size); -int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, const char *pin, const TPM2B_AUTH *auth, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); +int tpm2_define_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE requested_nv_index, const TPM2B_DIGEST *write_policy, TPM2_HANDLE *ret_nv_index, Tpm2Handle **ret_nv_handle, TPM2B_NV_PUBLIC *ret_nv_public); int tpm2_write_policy_nv_index(Tpm2Context *c, const Tpm2Handle *policy_session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle, const TPM2B_DIGEST *policy_digest); int tpm2_undefine_policy_nv_index(Tpm2Context *c, const Tpm2Handle *session, TPM2_HANDLE nv_index, const Tpm2Handle *nv_handle); @@ -309,6 +314,8 @@ int tpm2_deserialize(Tpm2Context *c, const void *serialized, size_t serialized_s int tpm2_load_public_key_file(const char *path, TPM2B_PUBLIC *ret); +int tpm2_hmac_key_from_pin(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_AUTH *pin, Tpm2Handle **ret); + /* The tpm2-tss library has many structs that are simply a combination of an array (or object) and * size. These macros allow easily initializing or assigning instances of such structs from an existing * buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the @@ -384,8 +391,8 @@ int tpm2_find_device_auto(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, 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); +int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const struct iovec *pubkey, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const struct iovec *blob, const struct iovec *policy_hash, const struct iovec *salt, const struct iovec *srk, const struct iovec *pcrlock_nv, 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, struct iovec *ret_pubkey, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, struct iovec *ret_blob, struct iovec *ret_policy_hash, struct iovec *ret_salt, struct iovec *ret_srk, struct iovec *pcrlock_nv, TPM2Flags *ret_flags); /* Default to PCR 7 only */ #define TPM2_PCR_INDEX_DEFAULT UINT32_C(7) @@ -477,3 +484,14 @@ enum { int tpm2_pcr_index_from_string(const char *s) _pure_; const char *tpm2_pcr_index_to_string(int pcr) _const_; + +/* The first and last NV index handle that is not registered to any company, as per TCG's "Registry of + * Reserved TPM 2.0 Handles and Localities", section 2.2.2. */ +#define TPM2_NV_INDEX_UNASSIGNED_FIRST UINT32_C(0x01800000) +#define TPM2_NV_INDEX_UNASSIGNED_LAST UINT32_C(0x01BFFFFF) + +#if HAVE_TPM2 +/* Verify that the above is indeed a subset of the general NV Index range */ +assert_cc(TPM2_NV_INDEX_UNASSIGNED_FIRST >= TPM2_NV_INDEX_FIRST); +assert_cc(TPM2_NV_INDEX_UNASSIGNED_LAST <= TPM2_NV_INDEX_LAST); +#endif diff --git a/src/shared/udev-util.c b/src/shared/udev-util.c index cf28ba8..15996ca 100644 --- a/src/shared/udev-util.c +++ b/src/shared/udev-util.c @@ -14,6 +14,7 @@ #include "id128-util.h" #include "log.h" #include "macro.h" +#include "missing_threads.h" #include "parse-util.h" #include "path-util.h" #include "signal-util.h" @@ -22,43 +23,35 @@ #include "udev-util.h" #include "utf8.h" -int udev_set_max_log_level(char *str) { - size_t n; +int udev_parse_config_full(const ConfigTableItem config_table[]) { + int r; - /* This may modify input string. */ + assert(config_table); - if (isempty(str)) + r = config_parse_standard_file_with_dropins( + "udev/udev.conf", + /* sections = */ NULL, + config_item_table_lookup, config_table, + CONFIG_PARSE_WARN, + /* userdata = */ NULL); + if (r == -ENOENT) return 0; - - /* unquote */ - n = strlen(str); - if (n >= 2 && - ((str[0] == '"' && str[n - 1] == '"') || - (str[0] == '\'' && str[n - 1] == '\''))) { - str[n - 1] = '\0'; - str++; - } - - /* we set the udev log level here explicitly, this is supposed - * to regulate the code in libudev/ and udev/. */ - return log_set_max_level_from_string(str); + return r; } int udev_parse_config(void) { - _cleanup_free_ char *log_val = NULL; - int r; + int r, log_val = -1; + const ConfigTableItem config_table[] = { + { NULL, "udev_log", config_parse_log_level, 0, &log_val }, + {} + }; - r = parse_env_file(NULL, "/etc/udev/udev.conf", - "udev_log", &log_val); - if (r == -ENOENT) - return 0; + r = udev_parse_config_full(config_table); if (r < 0) return r; - r = udev_set_max_log_level(log_val); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to set udev log level '%s', ignoring: %m", log_val); + if (log_val >= 0) + log_set_max_level(log_val); return 0; } @@ -146,7 +139,7 @@ static int device_wait_for_initialization_internal( } if (device) { - if (sd_device_get_is_initialized(device) > 0) { + if (device_is_processed(device) > 0) { if (ret) *ret = sd_device_ref(device); return 0; @@ -209,7 +202,7 @@ static int device_wait_for_initialization_internal( if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) return log_error_errno(r, "Failed to create sd-device object from %s: %m", devlink); } - if (device && sd_device_get_is_initialized(device) > 0) { + if (device && device_is_processed(device) > 0) { if (ret) *ret = sd_device_ref(device); return 0; @@ -237,13 +230,34 @@ int device_is_renaming(sd_device *dev) { assert(dev); - r = sd_device_get_property_value(dev, "ID_RENAMING", NULL); + r = device_get_property_bool(dev, "ID_RENAMING"); if (r == -ENOENT) - return false; + return false; /* defaults to false */ + + return r; +} + +int device_is_processed(sd_device *dev) { + int r; + + assert(dev); + + /* sd_device_get_is_initialized() only checks if the udev database file exists. However, even if the + * database file exist, systemd-udevd may be still processing the device, e.g. when the udev rules + * for the device have RUN tokens. See issue #30056. Hence, to check if the device is really + * processed by systemd-udevd, we also need to read ID_PROCESSING property. */ + + r = sd_device_get_is_initialized(dev); + if (r <= 0) + return r; + + r = device_get_property_bool(dev, "ID_PROCESSING"); + if (r == -ENOENT) + return true; /* If the property does not exist, then it means that the device is processed. */ if (r < 0) return r; - return true; + return !r; } bool device_for_action(sd_device *dev, sd_device_action_t a) { @@ -369,18 +383,22 @@ int udev_queue_is_empty(void) { (errno == ENOENT ? true : -errno) : false; } -bool udev_available(void) { - static int cache = -1; +static int cached_udev_availability = -1; +void reset_cached_udev_availability(void) { + cached_udev_availability = -1; +} + +bool udev_available(void) { /* The service systemd-udevd is started only when /sys is read write. * See systemd-udevd.service: ConditionPathIsReadWrite=/sys * Also, our container interface (http://systemd.io/CONTAINER_INTERFACE/) states that /sys must * be mounted in read-only mode in containers. */ - if (cache >= 0) - return cache; + if (cached_udev_availability >= 0) + return cached_udev_availability; - return (cache = (path_is_read_only_fs("/sys/") <= 0)); + return (cached_udev_availability = (path_is_read_only_fs("/sys/") <= 0)); } int device_get_vendor_string(sd_device *device, const char **ret) { diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h index 651d335..c21c4c1 100644 --- a/src/shared/udev-util.h +++ b/src/shared/udev-util.h @@ -3,15 +3,17 @@ #include "sd-device.h" +#include "conf-parser.h" #include "hashmap.h" #include "time-util.h" -int udev_set_max_log_level(char *str); +int udev_parse_config_full(const ConfigTableItem config_table[]); int udev_parse_config(void); int device_wait_for_initialization(sd_device *device, const char *subsystem, usec_t timeout_usec, sd_device **ret); int device_wait_for_devlink(const char *path, const char *subsystem, usec_t timeout_usec, sd_device **ret); int device_is_renaming(sd_device *dev); +int device_is_processed(sd_device *dev); bool device_for_action(sd_device *dev, sd_device_action_t action); @@ -22,6 +24,7 @@ size_t udev_replace_chars(char *str, const char *allow); int udev_queue_is_empty(void); +void reset_cached_udev_availability(void); bool udev_available(void); int device_get_vendor_string(sd_device *device, const char **ret); diff --git a/src/shared/user-record-nss.c b/src/shared/user-record-nss.c index 414a493..ffb5721 100644 --- a/src/shared/user-record-nss.c +++ b/src/shared/user-record-nss.c @@ -208,39 +208,17 @@ int nss_user_record_by_name( bool with_shadow, UserRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct passwd pwd, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct passwd *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct spwd spwd, *sresult = NULL; int r; assert(name); - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getpwnam_r(name, &pwd, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwnam_r() returned a negative value"); - if (r != ERANGE) - return -r; - - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getpwnam_malloc(name, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_spwd_for_passwd(result, &spwd, &sbuf); @@ -266,36 +244,15 @@ int nss_user_record_by_uid( bool with_shadow, UserRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct passwd pwd, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct passwd *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct spwd spwd, *sresult = NULL; int r; - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getpwuid_r(uid, &pwd, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getpwuid_r() returned a negative value"); - if (r != ERANGE) - return -r; - - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getpwuid_malloc(uid, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_spwd_for_passwd(result, &spwd, &sbuf); @@ -422,38 +379,17 @@ int nss_group_record_by_name( bool with_shadow, GroupRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct group grp, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct group *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct sgrp sgrp, *sresult = NULL; int r; assert(name); - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getgrnam_r(name, &grp, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrnam_r() returned a negative value"); - if (r != ERANGE) - return -r; - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getgrnam_malloc(name, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_sgrp_for_group(result, &sgrp, &sbuf); @@ -479,35 +415,15 @@ int nss_group_record_by_gid( bool with_shadow, GroupRecord **ret) { - _cleanup_free_ char *buf = NULL, *sbuf = NULL; - struct group grp, *result; + _cleanup_free_ char *sbuf = NULL; + _cleanup_free_ struct group *result = NULL; bool incomplete = false; - size_t buflen = 4096; struct sgrp sgrp, *sresult = NULL; int r; - for (;;) { - buf = malloc(buflen); - if (!buf) - return -ENOMEM; - - r = getgrgid_r(gid, &grp, buf, buflen, &result); - if (r == 0) { - if (!result) - return -ESRCH; - break; - } - - if (r < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EIO), "getgrgid_r() returned a negative value"); - if (r != ERANGE) - return -r; - if (buflen > SIZE_MAX / 2) - return -ERANGE; - - buflen *= 2; - buf = mfree(buf); - } + r = getgrgid_malloc(gid, &result); + if (r < 0) + return r; if (with_shadow) { r = nss_sgrp_for_group(result, &sgrp, &sbuf); diff --git a/src/shared/user-record-show.c b/src/shared/user-record-show.c index 28fa7a8..23a4153 100644 --- a/src/shared/user-record-show.c +++ b/src/shared/user-record-show.c @@ -3,8 +3,14 @@ #include "cap-list.h" #include "format-util.h" #include "fs-util.h" +#include "glyph-util.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "path-util.h" +#include "pretty-print.h" #include "process-util.h" #include "rlimit-util.h" +#include "sha256.h" #include "strv.h" #include "terminal-util.h" #include "user-record-show.h" @@ -23,6 +29,7 @@ const char *user_record_state_color(const char *state) { } void user_record_show(UserRecord *hr, bool show_full_group_info) { + _cleanup_strv_free_ char **langs = NULL; const char *hd, *ip, *shell; UserStorage storage; usec_t t; @@ -203,8 +210,45 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Real Name: %s\n", hr->real_name); hd = user_record_home_directory(hr); - if (hd) - printf(" Directory: %s\n", hd); + if (hd) { + printf(" Directory: %s", hd); + + if (hr->fallback_home_directory && hr->use_fallback) + printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal()); + + printf("\n"); + } + + if (hr->blob_directory) { + _cleanup_free_ char **filenames = NULL; + size_t n_filenames = 0; + + r = hashmap_dump_keys_sorted(hr->blob_manifest, (void***) &filenames, &n_filenames); + if (r < 0) { + errno = -r; + printf(" Blob Dir.: %s (can't iterate: %m)\n", hr->blob_directory); + } else + printf(" Blob Dir.: %s\n", hr->blob_directory); + + for (size_t i = 0; i < n_filenames; i++) { + _cleanup_free_ char *path = NULL, *link = NULL, *hash = NULL; + const char *filename = filenames[i]; + const uint8_t *hash_bytes = hashmap_get(hr->blob_manifest, filename); + bool last = i == n_filenames - 1; + + path = path_join(hr->blob_directory, filename); + if (path) + (void) terminal_urlify_path(path, filename, &link); + hash = hexmem(hash_bytes, SHA256_DIGEST_SIZE); + + printf(" %s %s %s(%s)%s\n", + special_glyph(last ? SPECIAL_GLYPH_TREE_RIGHT : SPECIAL_GLYPH_TREE_BRANCH), + link ?: filename, + ansi_grey(), + hash ?: "can't display hash", + ansi_normal()); + } + } storage = user_record_storage(hr); if (storage >= 0) /* Let's be political, and clarify which storage we like, and which we don't. About CIFS we don't complain. */ @@ -222,8 +266,14 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { printf(" Removable: %s\n", yes_no(b)); shell = user_record_shell(hr); - if (shell) - printf(" Shell: %s\n", shell); + if (shell) { + printf(" Shell: %s", shell); + + if (hr->fallback_shell && hr->use_fallback) + printf(" %s(fallback)%s", ansi_highlight_yellow(), ansi_normal()); + + printf("\n"); + } if (hr->email_address) printf(" Email: %s\n", hr->email_address); @@ -237,15 +287,15 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->time_zone) printf(" Time Zone: %s\n", hr->time_zone); - if (hr->preferred_language) - printf(" Language: %s\n", hr->preferred_language); - - if (!strv_isempty(hr->environment)) - STRV_FOREACH(i, hr->environment) { - printf(i == hr->environment ? - " Environment: %s\n" : - " %s\n", *i); - } + r = user_record_languages(hr, &langs); + if (r < 0) { + errno = -r; + printf(" Languages: (can't acquire: %m)\n"); + } else if (!strv_isempty(langs)) { + STRV_FOREACH(i, langs) + printf(i == langs ? " Languages: %s" : ", %s", *i); + printf("\n"); + } if (hr->locked >= 0) printf(" Locked: %s\n", yes_no(hr->locked)); @@ -525,6 +575,11 @@ void user_record_show(UserRecord *hr, bool show_full_group_info) { if (hr->auto_login >= 0) printf("Autom. Login: %s\n", yes_no(hr->auto_login)); + if (hr->preferred_session_launcher) + printf("Sess. Launch: %s\n", hr->preferred_session_launcher); + if (hr->preferred_session_type) + printf("Session Type: %s\n", hr->preferred_session_type); + if (hr->kill_processes >= 0) printf(" Kill Proc.: %s\n", yes_no(hr->kill_processes)); diff --git a/src/shared/user-record.c b/src/shared/user-record.c index 3fe3e80..ec084de 100644 --- a/src/shared/user-record.c +++ b/src/shared/user-record.c @@ -10,15 +10,18 @@ #include "glyph-util.h" #include "hexdecoct.h" #include "hostname-util.h" +#include "locale-util.h" #include "memory-util.h" #include "path-util.h" #include "pkcs11-util.h" #include "rlimit-util.h" +#include "sha256.h" #include "string-table.h" #include "strv.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-record.h" #include "user-util.h" +#include "utf8.h" #define DEFAULT_RATELIMIT_BURST 30 #define DEFAULT_RATELIMIT_INTERVAL_USEC (1*USEC_PER_MINUTE) @@ -141,11 +144,15 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->location); free(h->icon_name); + free(h->blob_directory); + hashmap_free(h->blob_manifest); + free(h->shell); strv_free(h->environment); free(h->time_zone); free(h->preferred_language); + strv_free(h->additional_languages); rlimit_free_all(h->rlimits); free(h->skeleton_directory); @@ -165,6 +172,9 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->home_directory); free(h->home_directory_auto); + free(h->fallback_shell); + free(h->fallback_home_directory); + strv_free(h->member_of); strv_free(h->capability_bounding_set); strv_free(h->capability_ambient_set); @@ -179,6 +189,9 @@ static UserRecord* user_record_free(UserRecord *h) { free(h->state); free(h->service); + free(h->preferred_session_type); + free(h->preferred_session_launcher); + strv_free(h->pkcs11_token_uri); for (size_t i = 0; i < h->n_pkcs11_encrypted_key; i++) pkcs11_encrypted_key_done(h->pkcs11_encrypted_key + i); @@ -535,44 +548,65 @@ static int json_dispatch_environment(const char *name, JsonVariant *variant, Jso return strv_free_and_replace(*l, n); } -int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { - UserDisposition *disposition = userdata, k; +static int json_dispatch_locale(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **s = userdata; + const char *n; + int r; if (json_variant_is_null(variant)) { - *disposition = _USER_DISPOSITION_INVALID; + *s = mfree(*s); return 0; } if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - k = user_disposition_from_string(json_variant_string(variant)); - if (k < 0) - return json_log(variant, flags, k, "Disposition type '%s' not known.", json_variant_string(variant)); + n = json_variant_string(variant); + + if (!locale_is_valid(n)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid locale.", strna(name)); + + r = free_and_strdup(s, n); + if (r < 0) + return json_log(variant, flags, r, "Failed to allocate string: %m"); - *disposition = k; return 0; } -static int json_dispatch_storage(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { - UserStorage *storage = userdata, k; +static int json_dispatch_locales(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_strv_free_ char **n = NULL; + char ***l = userdata; + const char *locale; + JsonVariant *e; + int r; if (json_variant_is_null(variant)) { - *storage = _USER_STORAGE_INVALID; + *l = strv_free(*l); return 0; } - if (!json_variant_is_string(variant)) - return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); - k = user_storage_from_string(json_variant_string(variant)); - if (k < 0) - return json_log(variant, flags, k, "Storage type '%s' not known.", json_variant_string(variant)); + JSON_VARIANT_ARRAY_FOREACH(e, variant) { + if (!json_variant_is_string(e)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of strings.", strna(name)); - *storage = k; - return 0; + locale = json_variant_string(e); + if (!locale_is_valid(locale)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of valid locales.", strna(name)); + + r = strv_extend(&n, locale); + if (r < 0) + return json_log_oom(variant, flags); + } + + return strv_free_and_replace(*l, n); } +JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_disposition, UserDisposition, user_disposition_from_string); +static JSON_DISPATCH_ENUM_DEFINE(json_dispatch_user_storage, UserStorage, user_storage_from_string); + static int json_dispatch_tasks_or_memory_max(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { uint64_t *limit = userdata, k; @@ -746,7 +780,7 @@ static int dispatch_pkcs11_key_data(const char *name, JsonVariant *variant, Json if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode encrypted PKCS#11 key: %m"); @@ -813,7 +847,7 @@ static int dispatch_fido2_hmac_credential(const char *name, JsonVariant *variant if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m"); @@ -843,7 +877,7 @@ static int dispatch_fido2_hmac_credential_array(const char *name, JsonVariant *v if (!array) return log_oom(); - r = unbase64mem(json_variant_string(e), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(e), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 credential ID: %m"); @@ -873,7 +907,7 @@ static int dispatch_fido2_hmac_salt_value(const char *name, JsonVariant *variant if (!json_variant_is_string(variant)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); - r = unbase64mem(json_variant_string(variant), SIZE_MAX, &b, &l); + r = unbase64mem(json_variant_string(variant), &b, &l); if (r < 0) return json_log(variant, flags, r, "Failed to decode FIDO2 salt: %m"); @@ -1048,6 +1082,7 @@ static int dispatch_privileged(const char *name, JsonVariant *variant, JsonDispa static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch binding_dispatch_table[] = { + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, { "imagePath", JSON_VARIANT_STRING, json_dispatch_image_path, offsetof(UserRecord, image_path), 0 }, { "homeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, home_directory), 0 }, { "partitionUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, partition_uuid), 0 }, @@ -1055,7 +1090,7 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch { "fileSystemUuid", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(UserRecord, file_system_uuid), 0 }, { "uid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, uid), 0 }, { "gid", JSON_VARIANT_UNSIGNED, json_dispatch_uid_gid, offsetof(UserRecord, gid), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, { "luksCipher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher), JSON_SAFE }, { "luksCipherMode", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, luks_cipher_mode), JSON_SAFE }, @@ -1084,6 +1119,52 @@ static int dispatch_binding(const char *name, JsonVariant *variant, JsonDispatch return json_dispatch(m, binding_dispatch_table, flags, userdata); } +static int dispatch_blob_manifest(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + _cleanup_hashmap_free_ Hashmap *manifest = NULL; + Hashmap **ret = ASSERT_PTR(userdata); + JsonVariant *value; + const char *key; + int r; + + if (!variant) + return 0; + + if (!json_variant_is_object(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an object.", strna(name)); + + JSON_VARIANT_OBJECT_FOREACH(key, value, variant) { + _cleanup_free_ char *filename = NULL; + _cleanup_free_ uint8_t *hash = NULL; + + if (!json_variant_is_string(value)) + return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid hash.", key); + + if (!suitable_blob_filename(key)) + return json_log(value, flags, SYNTHETIC_ERRNO(EINVAL), "Blob entry '%s' has invalid filename.", key); + + filename = strdup(key); + if (!filename) + return json_log_oom(value, flags); + + hash = malloc(SHA256_DIGEST_SIZE); + if (!hash) + return json_log_oom(value, flags); + + r = parse_sha256(json_variant_string(value), hash); + if (r < 0) + return json_log(value, flags, r, "Blob entry '%s' has invalid hash: %s", filename, json_variant_string(value)); + + r = hashmap_ensure_put(&manifest, &path_hash_ops_free_free, filename, hash); + if (r < 0) + return json_log(value, flags, r, "Failed to insert blob manifest entry '%s': %m", filename); + TAKE_PTR(filename); /* Ownership transfers to hashmap */ + TAKE_PTR(hash); + } + + hashmap_free_and_replace(*ret, manifest); + return 0; +} + int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags) { sd_id128_t mid; int r; @@ -1168,24 +1249,54 @@ int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags) { return false; } +int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags) { + JsonVariant *m; + int r; + + assert(json_variant_is_object(entry)); + + m = json_variant_by_key(entry, "matchMachineId"); + if (m) { + r = per_machine_id_match(m, flags); + if (r < 0) + return r; + if (r > 0) + return true; + } + + m = json_variant_by_key(entry, "matchHostname"); + if (m) { + r = per_machine_hostname_match(m, flags); + if (r < 0) + return r; + if (r > 0) + return true; + } + + return false; +} + static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch per_machine_dispatch_table[] = { { "matchMachineId", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, { "matchHostname", _JSON_VARIANT_TYPE_INVALID, NULL, 0, 0 }, + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, + { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 }, { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE }, { "location", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, location), 0 }, { "shell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, shell), 0 }, { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE }, - { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE }, + { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, + { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 }, { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 }, { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 }, { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 }, { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 }, { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 }, @@ -1232,6 +1343,8 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 }, + { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE }, + { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE }, { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 }, { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 }, { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 }, @@ -1254,33 +1367,13 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); JSON_VARIANT_ARRAY_FOREACH(e, variant) { - bool matching = false; - JsonVariant *m; - if (!json_variant_is_object(e)) return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array of objects.", strna(name)); - m = json_variant_by_key(e, "matchMachineId"); - if (m) { - r = per_machine_id_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - - if (!matching) { - m = json_variant_by_key(e, "matchHostname"); - if (m) { - r = per_machine_hostname_match(m, flags); - if (r < 0) - return r; - - matching = r > 0; - } - } - - if (!matching) + r = per_machine_match(e, flags); + if (r < 0) + return r; + if (r == 0) continue; r = json_dispatch(e, per_machine_dispatch_table, flags, userdata); @@ -1294,23 +1387,26 @@ static int dispatch_per_machine(const char *name, JsonVariant *variant, JsonDisp static int dispatch_status(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch status_dispatch_table[] = { - { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, - { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, - { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, - { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, - { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, - { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, - { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, - { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, - { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, - { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, - { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, - { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, - { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, - { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, - { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, removable), 0 }, - { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 }, - { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, + { "diskUsage", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_usage), 0 }, + { "diskFree", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_free), 0 }, + { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, + { "diskCeiling", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_ceiling), 0 }, + { "diskFloor", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_floor), 0 }, + { "state", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, state), JSON_SAFE }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, service), JSON_SAFE }, + { "signedLocally", _JSON_VARIANT_TYPE_INVALID, json_dispatch_tristate, offsetof(UserRecord, signed_locally), 0 }, + { "goodAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, good_authentication_counter), 0 }, + { "badAuthenticationCounter", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, bad_authentication_counter), 0 }, + { "lastGoodAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_good_authentication_usec), 0 }, + { "lastBadAuthenticationUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, last_bad_authentication_usec), 0 }, + { "rateLimitBeginUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_begin_usec), 0 }, + { "rateLimitCount", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_count), 0 }, + { "removable", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, removable), 0 }, + { "accessMode", JSON_VARIANT_UNSIGNED, json_dispatch_access_mode, offsetof(UserRecord, access_mode), 0 }, + { "fileSystemType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, file_system_type), JSON_SAFE }, + { "fallbackShell", JSON_VARIANT_STRING, json_dispatch_filename_or_path, offsetof(UserRecord, fallback_shell), 0 }, + { "fallbackHomeDirectory", JSON_VARIANT_STRING, json_dispatch_home_directory, offsetof(UserRecord, fallback_home_directory), 0 }, + { "useFallback", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(UserRecord, use_fallback), 0 }, {}, }; @@ -1523,6 +1619,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla static const JsonDispatch user_dispatch_table[] = { { "userName", JSON_VARIANT_STRING, json_dispatch_user_group_name, offsetof(UserRecord, user_name), JSON_RELAX}, { "realm", JSON_VARIANT_STRING, json_dispatch_realm, offsetof(UserRecord, realm), 0 }, + { "blobDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, blob_directory), 0 }, + { "blobManifest", JSON_VARIANT_OBJECT, dispatch_blob_manifest, offsetof(UserRecord, blob_manifest), 0 }, { "realName", JSON_VARIANT_STRING, json_dispatch_gecos, offsetof(UserRecord, real_name), 0 }, { "emailAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, email_address), JSON_SAFE }, { "iconName", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, icon_name), JSON_SAFE }, @@ -1534,13 +1632,14 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "umask", JSON_VARIANT_UNSIGNED, json_dispatch_umask, offsetof(UserRecord, umask), 0 }, { "environment", JSON_VARIANT_ARRAY, json_dispatch_environment, offsetof(UserRecord, environment), 0 }, { "timeZone", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, time_zone), JSON_SAFE }, - { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_language), JSON_SAFE }, + { "preferredLanguage", JSON_VARIANT_STRING, json_dispatch_locale, offsetof(UserRecord, preferred_language), 0 }, + { "additionalLanguages", JSON_VARIANT_ARRAY, json_dispatch_locales, offsetof(UserRecord, additional_languages), 0 }, { "niceLevel", _JSON_VARIANT_TYPE_INVALID, json_dispatch_nice, offsetof(UserRecord, nice_level), 0 }, { "resourceLimits", _JSON_VARIANT_TYPE_INVALID, json_dispatch_rlimits, offsetof(UserRecord, rlimits), 0 }, { "locked", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, locked), 0 }, { "notBeforeUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_before_usec), 0 }, { "notAfterUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, not_after_usec), 0 }, - { "storage", JSON_VARIANT_STRING, json_dispatch_storage, offsetof(UserRecord, storage), 0 }, + { "storage", JSON_VARIANT_STRING, json_dispatch_user_storage, offsetof(UserRecord, storage), 0 }, { "diskSize", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size), 0 }, { "diskSizeRelative", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, disk_size_relative), 0 }, { "skeletonDirectory", JSON_VARIANT_STRING, json_dispatch_path, offsetof(UserRecord, skeleton_directory), 0 }, @@ -1589,6 +1688,8 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla { "rateLimitBurst", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, ratelimit_burst), 0 }, { "enforcePasswordPolicy", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, enforce_password_policy), 0 }, { "autoLogin", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, auto_login), 0 }, + { "preferredSessionType", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_type), JSON_SAFE }, + { "preferredSessionLauncher", JSON_VARIANT_STRING, json_dispatch_string, offsetof(UserRecord, preferred_session_launcher), JSON_SAFE }, { "stopDelayUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, stop_delay_usec), 0 }, { "killProcesses", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(UserRecord, kill_processes), 0 }, { "passwordChangeMinUSec", _JSON_VARIANT_TYPE_INVALID, json_dispatch_uint64, offsetof(UserRecord, password_change_min_usec), 0 }, @@ -1625,7 +1726,7 @@ int user_record_load(UserRecord *h, JsonVariant *v, UserRecordLoadFlags load_fla if (r < 0) return r; - r = json_dispatch(h->json, user_dispatch_table, json_flags, h); + r = json_dispatch(h->json, user_dispatch_table, json_flags | JSON_ALLOW_EXTENSIONS, h); if (r < 0) return r; @@ -1720,7 +1821,7 @@ mode_t user_record_access_mode(UserRecord *h) { return h->access_mode != MODE_INVALID ? h->access_mode : 0700; } -const char* user_record_home_directory(UserRecord *h) { +static const char *user_record_home_directory_real(UserRecord *h) { assert(h); if (h->home_directory) @@ -1735,6 +1836,15 @@ const char* user_record_home_directory(UserRecord *h) { return "/"; } +const char* user_record_home_directory(UserRecord *h) { + assert(h); + + if (h->use_fallback && h->fallback_home_directory) + return h->fallback_home_directory; + + return user_record_home_directory_real(h); +} + const char *user_record_image_path(UserRecord *h) { assert(h); @@ -1743,7 +1853,9 @@ const char *user_record_image_path(UserRecord *h) { if (h->image_path_auto) return h->image_path_auto; - return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? user_record_home_directory(h) : NULL; + /* For some storage types the image is the home directory itself. (But let's ignore the fallback logic for it) */ + return IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT) ? + user_record_home_directory_real(h) : NULL; } const char *user_record_cifs_user_name(UserRecord *h) { @@ -1760,7 +1872,7 @@ unsigned long user_record_mount_flags(UserRecord *h) { (h->nodev ? MS_NODEV : 0); } -const char *user_record_shell(UserRecord *h) { +static const char *user_record_shell_real(UserRecord *h) { assert(h); if (h->shell) @@ -1775,6 +1887,21 @@ const char *user_record_shell(UserRecord *h) { return NOLOGIN; } +const char *user_record_shell(UserRecord *h) { + const char *shell; + + assert(h); + + shell = user_record_shell_real(h); + + /* Return fallback shall if we are told so — except if the primary shell is already a nologin shell, + * then let's not risk anything. */ + if (h->use_fallback && h->fallback_shell) + return is_nologin_shell(shell) ? NOLOGIN : h->fallback_shell; + + return shell; +} + const char *user_record_real_name(UserRecord *h) { assert(h); @@ -2062,6 +2189,27 @@ uint64_t user_record_capability_ambient_set(UserRecord *h) { return parse_caps_strv(h->capability_ambient_set) & user_record_capability_bounding_set(h); } +int user_record_languages(UserRecord *h, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + int r; + + assert(h); + assert(ret); + + if (h->preferred_language) { + l = strv_new(h->preferred_language); + if (!l) + return -ENOMEM; + } + + r = strv_extend_strv(&l, h->additional_languages, /* filter_duplicates= */ true); + if (r < 0) + return r; + + *ret = TAKE_PTR(l); + return 0; +} + uint64_t user_record_ratelimit_next_try(UserRecord *h) { assert(h); @@ -2288,6 +2436,13 @@ int user_record_test_password_change_required(UserRecord *h) { return change_permitted ? 0 : -EROFS; } +int suitable_blob_filename(const char *name) { + /* Enforces filename requirements as described in docs/USER_RECORD_BULK_DIRS.md */ + return filename_is_valid(name) && + in_charset(name, URI_UNRESERVED) && + name[0] != '.'; +} + static const char* const user_storage_table[_USER_STORAGE_MAX] = { [USER_CLASSIC] = "classic", [USER_LUKS] = "luks", diff --git a/src/shared/user-record.h b/src/shared/user-record.h index 298dc24..cca112f 100644 --- a/src/shared/user-record.h +++ b/src/shared/user-record.h @@ -6,6 +6,7 @@ #include "sd-id128.h" +#include "hashmap.h" #include "json.h" #include "missing_resource.h" #include "time-util.h" @@ -243,6 +244,9 @@ typedef struct UserRecord { char *icon_name; char *location; + char *blob_directory; + Hashmap *blob_manifest; + UserDisposition disposition; uint64_t last_change_usec; uint64_t last_password_change_usec; @@ -252,6 +256,7 @@ typedef struct UserRecord { char **environment; char *time_zone; char *preferred_language; + char **additional_languages; int nice_level; struct rlimit *rlimits[_RLIMIT_MAX]; @@ -292,6 +297,10 @@ typedef struct UserRecord { char *home_directory; char *home_directory_auto; /* when none is set explicitly, this is where we place the implicit home directory */ + /* fallback shell and home dir */ + char *fallback_shell; + char *fallback_home_directory; + uid_t uid; gid_t gid; @@ -321,6 +330,8 @@ typedef struct UserRecord { uint64_t disk_ceiling; uint64_t disk_floor; + bool use_fallback; /* if true → use fallback_shell + fallback_home_directory instead of the regular ones */ + char *state; char *service; int signed_locally; @@ -340,6 +351,9 @@ typedef struct UserRecord { int auto_login; int drop_caches; + char *preferred_session_type; + char *preferred_session_launcher; + uint64_t stop_delay_usec; /* How long to leave systemd --user around on log-out */ int kill_processes; /* Whether to kill user processes forcibly on log-out */ @@ -415,6 +429,7 @@ AutoResizeMode user_record_auto_resize_mode(UserRecord *h); uint64_t user_record_rebalance_weight(UserRecord *h); uint64_t user_record_capability_bounding_set(UserRecord *h); uint64_t user_record_capability_ambient_set(UserRecord *h); +int user_record_languages(UserRecord *h, char ***ret); int user_record_build_image_path(UserStorage storage, const char *user_name_and_realm, char **ret); @@ -438,8 +453,12 @@ int json_dispatch_user_disposition(const char *name, JsonVariant *variant, JsonD int per_machine_id_match(JsonVariant *ids, JsonDispatchFlags flags); int per_machine_hostname_match(JsonVariant *hns, JsonDispatchFlags flags); +int per_machine_match(JsonVariant *entry, JsonDispatchFlags flags); int user_group_record_mangle(JsonVariant *v, UserRecordLoadFlags load_flags, JsonVariant **ret_variant, UserRecordMask *ret_mask); +#define BLOB_DIR_MAX_SIZE (UINT64_C(64) * U64_MB) +int suitable_blob_filename(const char *name); + const char* user_storage_to_string(UserStorage t) _const_; UserStorage user_storage_from_string(const char *s) _pure_; diff --git a/src/shared/userdb.c b/src/shared/userdb.c index f60d48a..75dece3 100644 --- a/src/shared/userdb.c +++ b/src/shared/userdb.c @@ -199,7 +199,7 @@ static int userdb_on_query_reply( assert_se(!iterator->found_user); - r = json_dispatch(parameters, dispatch_table, 0, &user_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &user_data); if (r < 0) goto finish; @@ -256,7 +256,7 @@ static int userdb_on_query_reply( assert_se(!iterator->found_group); - r = json_dispatch(parameters, dispatch_table, 0, &group_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &group_data); if (r < 0) goto finish; @@ -309,7 +309,7 @@ static int userdb_on_query_reply( assert(!iterator->found_user_name); assert(!iterator->found_group_name); - r = json_dispatch(parameters, dispatch_table, 0, &membership_data); + r = json_dispatch(parameters, dispatch_table, JSON_ALLOW_EXTENSIONS, &membership_data); if (r < 0) goto finish; @@ -1455,7 +1455,9 @@ int userdb_block_nss_systemd(int b) { return 0; } - call = (int (*)(bool b)) dlsym(dl, "_nss_systemd_block"); + log_debug("Loaded '%s' via dlopen()", LIBDIR "/libnss_systemd.so.2"); + + call = dlsym(dl, "_nss_systemd_block"); if (!call) /* If the file is installed but lacks the symbol we expect, things are weird, let's complain */ return log_debug_errno(SYNTHETIC_ERRNO(ELIBBAD), diff --git a/src/shared/varlink-idl.c b/src/shared/varlink-idl.c index 655324c..6748343 100644 --- a/src/shared/varlink-idl.c +++ b/src/shared/varlink-idl.c @@ -17,6 +17,9 @@ enum { _COLOR_MAX, }; +#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__) +#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__) + static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]); static int varlink_idl_format_enum_values( @@ -401,7 +404,7 @@ static int varlink_interface_realloc(VarlinkInterface **interface, size_t n_symb assert(interface); - n_symbols ++; /* Space for trailing NULL end marker symbol */ + n_symbols++; /* Space for trailing NULL end marker symbol */ /* Overflow check */ if (n_symbols > (SIZE_MAX - offsetof(VarlinkInterface, symbols)) / sizeof(VarlinkSymbol*)) @@ -420,7 +423,7 @@ static int varlink_symbol_realloc(VarlinkSymbol **symbol, size_t n_fields) { assert(symbol); - n_fields ++; /* Space for trailing end marker field */ + n_fields++; /* Space for trailing end marker field */ /* Overflow check */ if (n_fields > (SIZE_MAX - offsetof(VarlinkSymbol, fields)) / sizeof(VarlinkField)) @@ -512,7 +515,7 @@ static int varlink_idl_subparse_token( l = token_match(*p, allowed_delimiters, allowed_chars); if (l == 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters)); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find token of allowed chars '%s' or allowed delimiters '%s'.", strempty(allowed_chars), strempty(allowed_delimiters)); } t = strndup(*p, l); @@ -662,7 +665,7 @@ static int varlink_idl_subparse_field_type( if (r < 0) return r; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); field->named_type = TAKE_PTR(token); field->field_type = VARLINK_NAMED_TYPE; @@ -704,7 +707,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(n_fields); if (depth > DEPTH_MAX) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Maximum nesting depth reached (%u).", *line, *column, DEPTH_MAX); while (state != STATE_DONE) { _cleanup_free_ char *token = NULL; @@ -723,9 +726,9 @@ static int varlink_idl_subparse_struct_or_enum( case STATE_OPEN: if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (!streq(token, "(")) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); state = STATE_NAME; allowed_delimiters = ")"; @@ -736,7 +739,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(!field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ")")) state = STATE_DONE; else { @@ -752,7 +755,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ":")) { VarlinkField *field; @@ -760,7 +763,7 @@ static int varlink_idl_subparse_struct_or_enum( if ((*symbol)->symbol_type < 0) (*symbol)->symbol_type = VARLINK_STRUCT_TYPE; if ((*symbol)->symbol_type == VARLINK_ENUM_TYPE) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Enum with struct fields, refusing.", *line, *column); r = varlink_symbol_realloc(symbol, *n_fields + 1); if (r < 0) @@ -787,7 +790,7 @@ static int varlink_idl_subparse_struct_or_enum( if ((*symbol)->symbol_type < 0) (*symbol)->symbol_type = VARLINK_ENUM_TYPE; if ((*symbol)->symbol_type != VARLINK_ENUM_TYPE) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Struct with enum fields, refusing.", *line, *column); r = varlink_symbol_realloc(symbol, *n_fields + 1); if (r < 0) @@ -808,7 +811,7 @@ static int varlink_idl_subparse_struct_or_enum( state = STATE_DONE; } } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; @@ -816,7 +819,7 @@ static int varlink_idl_subparse_struct_or_enum( assert(!field_name); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, ",")) { state = STATE_NAME; allowed_delimiters = NULL; @@ -824,7 +827,7 @@ static int varlink_idl_subparse_struct_or_enum( } else if (streq(token, ")")) state = STATE_DONE; else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; default: @@ -835,7 +838,7 @@ static int varlink_idl_subparse_struct_or_enum( /* If we don't know the type of the symbol by now it was an empty () which doesn't allow us to * determine if we look at an enum or a struct */ if ((*symbol)->symbol_type < 0) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Ambiguous empty () enum/struct is not permitted.", *line, *column); return 0; } @@ -854,14 +857,14 @@ static int varlink_idl_resolve_symbol_types(VarlinkInterface *interface, Varlink continue; if (!field->named_type) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name."); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Named type field lacking a type name."); found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->named_type); if (!found) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Failed to find type '%s'.", field->named_type); if (!IN_SET(found->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE)) - return log_debug_errno(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type); + return varlink_idl_log(SYNTHETIC_ERRNO(ENETUNREACH), "Symbol '%s' is referenced as type but is not a type.", field->named_type); field->symbol = found; } @@ -932,7 +935,7 @@ int varlink_idl_parse( case STATE_PRE_INTERFACE: if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (streq(token, "#")) { r = varlink_idl_subparse_comment(&text, line, column); if (r < 0) @@ -942,7 +945,7 @@ int varlink_idl_parse( allowed_delimiters = NULL; allowed_chars = VALID_CHARS_INTERFACE_NAME; } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; case STATE_INTERFACE: @@ -950,7 +953,7 @@ int varlink_idl_parse( assert(n_symbols == 0); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_interface_realloc(&interface, n_symbols); if (r < 0) @@ -982,7 +985,7 @@ int varlink_idl_parse( state = STATE_ERROR; allowed_chars = VALID_CHARS_IDENTIFIER; } else - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); break; @@ -991,7 +994,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1012,10 +1015,10 @@ int varlink_idl_parse( assert(symbol); if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); if (!streq(token, "->")) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Unexpected token '%s'.", *line, *column, token); r = varlink_idl_subparse_struct_or_enum(&text, line, column, &symbol, &n_fields, VARLINK_OUTPUT, 0); if (r < 0) @@ -1036,7 +1039,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1064,7 +1067,7 @@ int varlink_idl_parse( n_fields = 0; if (!token) - return log_debug_errno(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); + return varlink_idl_log(SYNTHETIC_ERRNO(EBADMSG), "%u:%u: Premature EOF.", *line, *column); r = varlink_symbol_realloc(&symbol, n_fields); if (r < 0) @@ -1213,48 +1216,48 @@ static int varlink_idl_field_consistent( symbol_name = symbol->name ?: ""; if (field->field_type <= 0 || field->field_type >= _VARLINK_FIELD_TYPE_MAX) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field type for '%s' in symbol '%s' is not valid, refusing.", field->name, symbol_name); if (field->field_type == VARLINK_ENUM_VALUE) { if (symbol->symbol_type != VARLINK_ENUM_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field type for '%s' in non-enum symbol '%s', refusing.", field->name, symbol_name); if (field->field_flags != 0) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Enum field '%s' in symbol '%s' has non-zero flags set, refusing.", field->name, symbol_name); } else { if (symbol->symbol_type == VARLINK_ENUM_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Non-enum field type for '%s' in enum symbol '%s', refusing.", field->name, symbol_name); if (!IN_SET(field->field_flags & ~VARLINK_NULLABLE, 0, VARLINK_ARRAY, VARLINK_MAP)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Flags of field '%s' in symbol '%s' is invalid, refusing.", field->name, symbol_name); } if (symbol->symbol_type != VARLINK_METHOD) { if (field->field_direction != VARLINK_REGULAR) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in non-method symbol '%s' not regular, refusing.", field->name, symbol_name); } else { if (!IN_SET(field->field_direction, VARLINK_INPUT, VARLINK_OUTPUT)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Direction of '%s' in method symbol '%s' is not input or output, refusing.", field->name, symbol_name); } if (field->symbol) { if (!IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); if (field->field_type == VARLINK_NAMED_TYPE) { const VarlinkSymbol *found; if (!field->symbol->name || !field->named_type || !streq(field->symbol->name, field->named_type)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do do not match, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol name and named type of field '%s' in symbol '%s' do not match, refusing.", field->name, symbol_name); /* If this is a named type, then check if it's properly part of the interface */ found = varlink_idl_find_symbol(interface, _VARLINK_SYMBOL_TYPE_INVALID, field->symbol->name); if (!found) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not part of the interface, refusing.", field->name, symbol_name); if (!IN_SET(found->symbol_type, VARLINK_ENUM_TYPE, VARLINK_STRUCT_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Resolved symbol of named type of field '%s' in symbol '%s' is not a type, refusing.", field->name, symbol_name); } else { /* If this is an anonymous type, then we recursively check if it's consistent, since * it's not part of the interface, and hence we won't validate it from there. */ @@ -1266,18 +1269,18 @@ static int varlink_idl_field_consistent( } else { if (IN_SET(field->field_type, VARLINK_STRUCT, VARLINK_ENUM, VARLINK_NAMED_TYPE)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No target symbol for field '%s' in symbol '%s' defined for elemental field, refusing.", field->name, symbol_name); if (field->named_type) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Unresolved symbol in field '%s' in symbol '%s', refusing.", field->name, symbol_name); } if (field->named_type) { if (field->field_type != VARLINK_NAMED_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Named type set for field '%s' in symbol '%s' but not a named type field, refusing.", field->name, symbol_name); } else { if (field->field_type == VARLINK_NAMED_TYPE) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "No named type set for field '%s' in symbol '%s' but field is a named type field, refusing.", field->name, symbol_name); } return 0; @@ -1304,19 +1307,19 @@ static int varlink_idl_symbol_consistent( symbol_name = symbol->name ?: ""; if (symbol->symbol_type < 0 || symbol->symbol_type >= _VARLINK_SYMBOL_TYPE_MAX) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol type for '%s' is not valid, refusing.", symbol_name); if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name); for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */ if (!varlink_idl_field_name_is_valid(field->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Field name '%s' in symbol '%s' not valid, refusing.", field->name, symbol_name); if (set_contains(*name_set, field->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Field '%s' defined twice in symbol '%s', refusing.", field->name, symbol_name); if (set_ensure_put(name_set, &string_hash_ops, field->name) < 0) return log_oom(); @@ -1336,15 +1339,15 @@ int varlink_idl_consistent(const VarlinkInterface *interface, int level) { assert(interface); if (!varlink_idl_interface_name_is_valid(interface->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Interface name '%s' is not valid, refusing.", interface->name); for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) { if (!varlink_idl_symbol_name_is_valid((*symbol)->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name)); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name)); if (set_contains(name_set, (*symbol)->name)) - return log_full_errno(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name); + return varlink_idl_log_full(level, SYNTHETIC_ERRNO(ENOTUNIQ), "Symbol '%s' defined twice in interface, refusing.", (*symbol)->name); if (set_ensure_put(&name_set, &string_hash_ops, (*symbol)->name) < 0) return log_oom(); @@ -1371,31 +1374,32 @@ static int varlink_idl_validate_field_element_type(const VarlinkField *field, Js case VARLINK_BOOL: if (!json_variant_is_boolean(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a bool, but it is not, refusing.", strna(field->name)); break; case VARLINK_INT: - if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name)); + /* Allow strings here too, since integers with > 53 bits are often passed in as strings */ + if (!json_variant_is_integer(v) && !json_variant_is_unsigned(v) && !json_variant_is_string(v)) + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an int, but it is not, refusing.", strna(field->name)); break; case VARLINK_FLOAT: if (!json_variant_is_number(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a float, but it is not, refusing.", strna(field->name)); break; case VARLINK_STRING: if (!json_variant_is_string(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be a string, but it is not, refusing.", strna(field->name)); break; case VARLINK_OBJECT: if (!json_variant_is_object(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); break; @@ -1414,13 +1418,13 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v) if (!v || json_variant_is_null(v)) { if (!FLAGS_SET(field->field_flags, VARLINK_NULLABLE)) - return log_debug_errno(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(ENOANO), "Mandatory field '%s' is null or missing on object, refusing.", strna(field->name)); } else if (FLAGS_SET(field->field_flags, VARLINK_ARRAY)) { JsonVariant *i; if (!json_variant_is_array(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an array, but it is not, refusing.", strna(field->name)); JSON_VARIANT_ARRAY_FOREACH(i, v) { r = varlink_idl_validate_field_element_type(field, i); @@ -1433,7 +1437,7 @@ static int varlink_idl_validate_field(const VarlinkField *field, JsonVariant *v) JsonVariant *e; if (!json_variant_is_object(v)) - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Field '%s' should be an object, but it is not, refusing.", strna(field->name)); JSON_VARIANT_OBJECT_FOREACH(k, e, v) { r = varlink_idl_validate_field_element_type(field, e); @@ -1458,7 +1462,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!v) { if (bad_field) *bad_field = NULL; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing."); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Null object passed, refusing."); } switch (symbol->symbol_type) { @@ -1470,7 +1474,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!json_variant_is_string(v)) { if (bad_field) *bad_field = symbol->name; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-string to enum field '%s', refusing.", strna(symbol->name)); } assert_se(s = json_variant_string(v)); @@ -1488,7 +1492,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!found) { if (bad_field) *bad_field = s; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed unrecognized string '%s' to enum field '%s', refusing.", s, strna(symbol->name)); } break; @@ -1500,7 +1504,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!json_variant_is_object(v)) { if (bad_field) *bad_field = symbol->name; - return log_debug_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name)); + return varlink_idl_log(SYNTHETIC_ERRNO(EMEDIUMTYPE), "Passed non-object to field '%s', refusing.", strna(symbol->name)); } for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) { @@ -1522,7 +1526,7 @@ static int varlink_idl_validate_symbol(const VarlinkSymbol *symbol, JsonVariant if (!varlink_idl_find_field(symbol, name)) { if (bad_field) *bad_field = name; - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name); + return varlink_idl_log(SYNTHETIC_ERRNO(EBUSY), "Field '%s' not defined for object, refusing.", name); } } diff --git a/src/shared/varlink-io.systemd.BootControl.c b/src/shared/varlink-io.systemd.BootControl.c new file mode 100644 index 0000000..500e072 --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.c @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.BootControl.h" + +static VARLINK_DEFINE_ENUM_TYPE( + BootEntryType, + VARLINK_DEFINE_ENUM_VALUE(type1), + VARLINK_DEFINE_ENUM_VALUE(type2), + VARLINK_DEFINE_ENUM_VALUE(loader), + VARLINK_DEFINE_ENUM_VALUE(auto)); + +static VARLINK_DEFINE_STRUCT_TYPE( + BootEntry, + VARLINK_DEFINE_FIELD_BY_TYPE(type, BootEntryType, 0), + VARLINK_DEFINE_FIELD(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(path, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(root, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(title, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(showTitle, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(sortKey, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(version, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(machineId, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(options, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(linux, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(efi, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(initrd, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(devicetree, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(devicetreeOverlay, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(isReported, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(triesLeft, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(triesDone, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isDefault, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(isSelected, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + ListBootEntries, + VARLINK_DEFINE_OUTPUT_BY_TYPE(entry, BootEntry, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + SetRebootToFirmware, + VARLINK_DEFINE_INPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_METHOD( + GetRebootToFirmware, + VARLINK_DEFINE_OUTPUT(state, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_ERROR( + RebootToFirmwareNotSupported); + +VARLINK_DEFINE_INTERFACE( + io_systemd_BootControl, + "io.systemd.BootControl", + &vl_type_BootEntryType, + &vl_type_BootEntry, + &vl_method_ListBootEntries, + &vl_method_SetRebootToFirmware, + &vl_method_GetRebootToFirmware, + &vl_error_RebootToFirmwareNotSupported); diff --git a/src/shared/varlink-io.systemd.BootControl.h b/src/shared/varlink-io.systemd.BootControl.h new file mode 100644 index 0000000..fa72b70 --- /dev/null +++ b/src/shared/varlink-io.systemd.BootControl.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_BootControl; diff --git a/src/shared/varlink-io.systemd.Credentials.c b/src/shared/varlink-io.systemd.Credentials.c new file mode 100644 index 0000000..03db0b3 --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.c @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Encrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(text, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(data, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(notAfter, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(blob, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + Decrypt, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(blob, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(timestamp, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(scope, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(uid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(data, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_ERROR(BadFormat); +static VARLINK_DEFINE_ERROR(NameMismatch); +static VARLINK_DEFINE_ERROR(TimeMismatch); +static VARLINK_DEFINE_ERROR(NoSuchUser); +static VARLINK_DEFINE_ERROR(BadScope); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Credentials, + "io.systemd.Credentials", + &vl_method_Encrypt, + &vl_method_Decrypt, + &vl_error_BadFormat, + &vl_error_NameMismatch, + &vl_error_TimeMismatch, + &vl_error_NoSuchUser, + &vl_error_BadScope); diff --git a/src/shared/varlink-io.systemd.Credentials.h b/src/shared/varlink-io.systemd.Credentials.h new file mode 100644 index 0000000..c0ecc3d --- /dev/null +++ b/src/shared/varlink-io.systemd.Credentials.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Credentials; diff --git a/src/shared/varlink-io.systemd.Hostname.c b/src/shared/varlink-io.systemd.Hostname.c new file mode 100644 index 0000000..a6c6aec --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.c @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Credentials.h" + +static VARLINK_DEFINE_METHOD( + Describe, + VARLINK_DEFINE_OUTPUT(Hostname, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(StaticHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(PrettyHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(DefaultHostname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HostnameSource, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IconName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Chassis, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Deployment, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(Location, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(KernelName, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelRelease, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(KernelVersion, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperatingSystemReleaseData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(MachineInformationData, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(HardwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareModel, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(HardwareSerial, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVersion, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareVendor, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(FirmwareDate, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(MachineID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(BootID, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(ProductUUID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(VSockCID, VARLINK_INT, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Hostname, + "io.systemd.Hostname", + &vl_method_Describe); diff --git a/src/shared/varlink-io.systemd.Hostname.h b/src/shared/varlink-io.systemd.Hostname.h new file mode 100644 index 0000000..29bb20e --- /dev/null +++ b/src/shared/varlink-io.systemd.Hostname.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Hostname; diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c new file mode 100644 index 0000000..2d25a34 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-idl.h" +#include "varlink-io.systemd.Machine.h" + +static VARLINK_DEFINE_METHOD( + Register, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(vSockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(MachineExists); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Machine, + "io.systemd.Machine", + &vl_method_Register, + &vl_error_MachineExists); diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h new file mode 100644 index 0000000..c9fc85f --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Machine; diff --git a/src/shared/varlink-io.systemd.MountFileSystem.c b/src/shared/varlink-io.systemd.MountFileSystem.c new file mode 100644 index 0000000..4a33578 --- /dev/null +++ b/src/shared/varlink-io.systemd.MountFileSystem.c @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.MountFileSystem.h" + +static VARLINK_DEFINE_ENUM_TYPE( + PartitionDesignator, + VARLINK_DEFINE_ENUM_VALUE(root), + VARLINK_DEFINE_ENUM_VALUE(usr), + VARLINK_DEFINE_ENUM_VALUE(home), + VARLINK_DEFINE_ENUM_VALUE(srv), + VARLINK_DEFINE_ENUM_VALUE(esp), + VARLINK_DEFINE_ENUM_VALUE(xbootldr), + VARLINK_DEFINE_ENUM_VALUE(swap), + VARLINK_DEFINE_ENUM_VALUE(root_verity), + VARLINK_DEFINE_ENUM_VALUE(usr_verity), + VARLINK_DEFINE_ENUM_VALUE(root_verity_sig), + VARLINK_DEFINE_ENUM_VALUE(usr_verity_sig), + VARLINK_DEFINE_ENUM_VALUE(tmp), + VARLINK_DEFINE_ENUM_VALUE(var)); + +static VARLINK_DEFINE_STRUCT_TYPE( + PartitionInfo, + VARLINK_DEFINE_FIELD(designator, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(writable, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(growFileSystem, VARLINK_BOOL, 0), + VARLINK_DEFINE_FIELD(partitionNumber, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(architecture, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(partitionUuid, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fileSystemType, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(partitionLabel, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(offset, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(mountFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + MountImage, + VARLINK_DEFINE_INPUT(imageFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(readOnly, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(growFileSystems, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(password, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(imagePolicy, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(partitions, PartitionInfo, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(imagePolicy, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(imageSize, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(sectorSize, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(imageName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(imageUuid, VARLINK_STRING, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_ERROR(IncompatibleImage); +static VARLINK_DEFINE_ERROR(MultipleRootPartitionsFound); +static VARLINK_DEFINE_ERROR(RootPartitionNotFound); +static VARLINK_DEFINE_ERROR(DeniedByImagePolicy); +static VARLINK_DEFINE_ERROR(KeyNotFound); +static VARLINK_DEFINE_ERROR(VerityFailure); + +VARLINK_DEFINE_INTERFACE( + io_systemd_MountFileSystem, + "io.systemd.MountFileSystem", + &vl_type_PartitionDesignator, + &vl_type_PartitionInfo, + &vl_method_MountImage, + &vl_error_IncompatibleImage, + &vl_error_MultipleRootPartitionsFound, + &vl_error_RootPartitionNotFound, + &vl_error_DeniedByImagePolicy, + &vl_error_KeyNotFound, + &vl_error_VerityFailure); diff --git a/src/shared/varlink-io.systemd.MountFileSystem.h b/src/shared/varlink-io.systemd.MountFileSystem.h new file mode 100644 index 0000000..dc75957 --- /dev/null +++ b/src/shared/varlink-io.systemd.MountFileSystem.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_MountFileSystem; diff --git a/src/shared/varlink-io.systemd.NamespaceResource.c b/src/shared/varlink-io.systemd.NamespaceResource.c new file mode 100644 index 0000000..e98c6c6 --- /dev/null +++ b/src/shared/varlink-io.systemd.NamespaceResource.c @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.NamespaceResource.h" + +static VARLINK_DEFINE_METHOD( + AllocateUserRange, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(size, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(target, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + RegisterUserNamespace, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddMountToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(mountFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddControlGroupToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(controlGroupFileDescriptor, VARLINK_INT, 0)); + +static VARLINK_DEFINE_METHOD( + AddNetworkToUserNamespace, + VARLINK_DEFINE_INPUT(userNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(networkNamespaceFileDescriptor, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(namespaceInterfaceName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(mode, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(hostInterfaceName, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(namespaceInterfaceName, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_ERROR(UserNamespaceInterfaceNotSupported); +static VARLINK_DEFINE_ERROR(NameExists); +static VARLINK_DEFINE_ERROR(UserNamespaceExists); +static VARLINK_DEFINE_ERROR(DynamicRangeUnavailable); +static VARLINK_DEFINE_ERROR(NoDynamicRange); +static VARLINK_DEFINE_ERROR(UserNamespaceNotRegistered); +static VARLINK_DEFINE_ERROR(UserNamespaceWithoutUserRange); +static VARLINK_DEFINE_ERROR(TooManyControlGroups); +static VARLINK_DEFINE_ERROR(ControlGroupAlreadyAdded); + +VARLINK_DEFINE_INTERFACE( + io_systemd_NamespaceResource, + "io.systemd.NamespaceResource", + &vl_method_AllocateUserRange, + &vl_method_RegisterUserNamespace, + &vl_method_AddMountToUserNamespace, + &vl_method_AddControlGroupToUserNamespace, + &vl_method_AddNetworkToUserNamespace, + &vl_error_UserNamespaceInterfaceNotSupported, + &vl_error_NameExists, + &vl_error_UserNamespaceExists, + &vl_error_DynamicRangeUnavailable, + &vl_error_NoDynamicRange, + &vl_error_UserNamespaceNotRegistered, + &vl_error_UserNamespaceWithoutUserRange, + &vl_error_TooManyControlGroups, + &vl_error_ControlGroupAlreadyAdded); diff --git a/src/shared/varlink-io.systemd.NamespaceResource.h b/src/shared/varlink-io.systemd.NamespaceResource.h new file mode 100644 index 0000000..443cb97 --- /dev/null +++ b/src/shared/varlink-io.systemd.NamespaceResource.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_NamespaceResource; diff --git a/src/shared/varlink-io.systemd.Network.c b/src/shared/varlink-io.systemd.Network.c new file mode 100644 index 0000000..394cc33 --- /dev/null +++ b/src/shared/varlink-io.systemd.Network.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.Network.h" + +static VARLINK_DEFINE_METHOD( + GetStates, + VARLINK_DEFINE_OUTPUT(AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IPv4AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(IPv6AddressState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(CarrierState, VARLINK_STRING, 0), + VARLINK_DEFINE_OUTPUT(OnlineState, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(OperationalState, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + GetNamespaceId, + VARLINK_DEFINE_OUTPUT(NamespaceId, VARLINK_INT, 0), + VARLINK_DEFINE_OUTPUT(NamespaceNSID, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighbor, + VARLINK_DEFINE_FIELD(ChassisID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawChassisID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortID, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(RawPortID, VARLINK_INT, VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(PortDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(SystemDescription, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(EnabledCapabilities, VARLINK_INT, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + LLDPNeighborsByInterface, + VARLINK_DEFINE_FIELD(InterfaceIndex, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(InterfaceName, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(InterfaceAlternativeNames, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(Neighbors, LLDPNeighbor, VARLINK_ARRAY)); + +static VARLINK_DEFINE_METHOD( + GetLLDPNeighbors, + VARLINK_DEFINE_INPUT(InterfaceIndex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(InterfaceName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(Neighbors, LLDPNeighborsByInterface, VARLINK_ARRAY)); + +static VARLINK_DEFINE_METHOD( + SetPersistentStorage, + VARLINK_DEFINE_INPUT(Ready, VARLINK_BOOL, 0)); + +static VARLINK_DEFINE_ERROR(StorageReadOnly); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Network, + "io.systemd.Network", + &vl_method_GetStates, + &vl_method_GetNamespaceId, + &vl_method_GetLLDPNeighbors, + &vl_method_SetPersistentStorage, + &vl_type_LLDPNeighbor, + &vl_type_LLDPNeighborsByInterface, + &vl_error_StorageReadOnly); diff --git a/src/shared/varlink-io.systemd.Network.h b/src/shared/varlink-io.systemd.Network.h new file mode 100644 index 0000000..12d532a --- /dev/null +++ b/src/shared/varlink-io.systemd.Network.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Network; diff --git a/src/shared/varlink-io.systemd.PCRLock.c b/src/shared/varlink-io.systemd.PCRLock.c new file mode 100644 index 0000000..22c6532 --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.c @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-io.systemd.PCRLock.h" + +static VARLINK_DEFINE_METHOD( + ReadEventLog, + VARLINK_DEFINE_OUTPUT(record, VARLINK_OBJECT, 0)); + +static VARLINK_DEFINE_METHOD( + MakePolicy, + VARLINK_DEFINE_INPUT(force, VARLINK_BOOL, VARLINK_NULLABLE)); + +static VARLINK_DEFINE_METHOD( + RemovePolicy); + +static VARLINK_DEFINE_ERROR( + NoChange); + +VARLINK_DEFINE_INTERFACE( + io_systemd_PCRLock, + "io.systemd.PCRLock", + &vl_method_ReadEventLog, + &vl_method_MakePolicy, + &vl_method_RemovePolicy, + &vl_error_NoChange); diff --git a/src/shared/varlink-io.systemd.PCRLock.h b/src/shared/varlink-io.systemd.PCRLock.h new file mode 100644 index 0000000..687f09e --- /dev/null +++ b/src/shared/varlink-io.systemd.PCRLock.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_PCRLock; diff --git a/src/shared/varlink-io.systemd.Resolve.Monitor.c b/src/shared/varlink-io.systemd.Resolve.Monitor.c index d95b613..ba928fa 100644 --- a/src/shared/varlink-io.systemd.Resolve.Monitor.c +++ b/src/shared/varlink-io.systemd.Resolve.Monitor.c @@ -2,96 +2,44 @@ #include "varlink-io.systemd.Resolve.Monitor.h" -VARLINK_DEFINE_STRUCT_TYPE( - ResourceKey, - VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0), - VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0), - VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0)); - -VARLINK_DEFINE_STRUCT_TYPE( - ResourceRecord, - VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), - VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), - VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE), - VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE)); - -VARLINK_DEFINE_STRUCT_TYPE( +/* We want to reuse the ResourceKey and ResourceRecord structures from the io.systemd.Resolve interface, + * hence import them here. */ +#include "varlink-io.systemd.Resolve.h" + +static VARLINK_DEFINE_STRUCT_TYPE( ResourceRecordArray, VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( Answer, VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( SubscribeQueryResults, /* First reply */ VARLINK_DEFINE_OUTPUT(ready, VARLINK_BOOL, VARLINK_NULLABLE), /* Subsequent replies */ VARLINK_DEFINE_OUTPUT(state, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(result, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(rcode, VARLINK_INT, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT(errno, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_OUTPUT_BY_TYPE(question, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT_BY_TYPE(collectedQuestions, ResourceKey, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT_BY_TYPE(answer, Answer, VARLINK_NULLABLE|VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( CacheEntry, VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), VARLINK_DEFINE_FIELD_BY_TYPE(rrs, ResourceRecordArray, VARLINK_NULLABLE|VARLINK_ARRAY), VARLINK_DEFINE_FIELD(type, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD(until, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( ScopeCache, VARLINK_DEFINE_FIELD(protocol, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(family, VARLINK_INT, VARLINK_NULLABLE), @@ -99,11 +47,11 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(ifname, VARLINK_STRING, VARLINK_NULLABLE), VARLINK_DEFINE_FIELD_BY_TYPE(cache, CacheEntry, VARLINK_ARRAY)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpCache, VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ScopeCache, VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( ServerState, VARLINK_DEFINE_FIELD(Server, VARLINK_STRING, 0), VARLINK_DEFINE_FIELD(Type, VARLINK_STRING, 0), @@ -122,11 +70,11 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(PacketInvalid, VARLINK_BOOL, 0), VARLINK_DEFINE_FIELD(PacketDoOff, VARLINK_BOOL, 0)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpServerState, VARLINK_DEFINE_OUTPUT_BY_TYPE(dump, ServerState, VARLINK_ARRAY)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( TransactionStatistics, VARLINK_DEFINE_FIELD(currentTransactions, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(totalTransactions, VARLINK_INT, 0), @@ -135,26 +83,26 @@ VARLINK_DEFINE_STRUCT_TYPE( VARLINK_DEFINE_FIELD(totalFailedResponses, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(totalFailedResponsesServedStale, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( CacheStatistics, VARLINK_DEFINE_FIELD(size, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(hits, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(misses, VARLINK_INT, 0)); -VARLINK_DEFINE_STRUCT_TYPE( +static VARLINK_DEFINE_STRUCT_TYPE( DnssecStatistics, VARLINK_DEFINE_FIELD(secure, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(insecure, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(bogus, VARLINK_INT, 0), VARLINK_DEFINE_FIELD(indeterminate, VARLINK_INT, 0)); -VARLINK_DEFINE_METHOD( +static VARLINK_DEFINE_METHOD( DumpStatistics, VARLINK_DEFINE_OUTPUT_BY_TYPE(transactions, TransactionStatistics, 0), VARLINK_DEFINE_OUTPUT_BY_TYPE(cache, CacheStatistics, 0), VARLINK_DEFINE_OUTPUT_BY_TYPE(dnssec, DnssecStatistics, 0)); -VARLINK_DEFINE_METHOD(ResetStatistics); +static VARLINK_DEFINE_METHOD(ResetStatistics); VARLINK_DEFINE_INTERFACE( io_systemd_Resolve_Monitor, diff --git a/src/shared/varlink-io.systemd.Resolve.c b/src/shared/varlink-io.systemd.Resolve.c index 0d8ad28..71f4dc8 100644 --- a/src/shared/varlink-io.systemd.Resolve.c +++ b/src/shared/varlink-io.systemd.Resolve.c @@ -2,6 +2,73 @@ #include "varlink-io.systemd.Resolve.h" +VARLINK_DEFINE_STRUCT_TYPE( + ResourceKey, + VARLINK_DEFINE_FIELD(class, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(type, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, 0)); + +VARLINK_DEFINE_STRUCT_TYPE( + ResourceRecord, + VARLINK_DEFINE_FIELD_BY_TYPE(key, ResourceKey, 0), + VARLINK_DEFINE_FIELD(priority, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(weight, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(port, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(cpu, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(os, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(items, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(address, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(mname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(rname, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(serial, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(refresh, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(expire, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(minimum, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(exchange, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(version, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(size, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(horiz_pre, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(vert_pre, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(latitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(longitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(altitude, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(keyTag, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(algorithm, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(digestType, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(digest, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fptype, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(fingerprint, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(protocol, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(dnskey, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(signer, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(typeCovered, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(labels, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(originalTtl, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(expiration, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(inception, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(signature, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(nextDomain, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(types, VARLINK_INT, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(iterations, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(salt, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(hash, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(certUsage, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(selector, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(matchingType, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(data, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(tag, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(value, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(target, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(params, VARLINK_STRING, VARLINK_NULLABLE|VARLINK_ARRAY), + VARLINK_DEFINE_FIELD(order, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(preference, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(naptrFlags, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(services, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(regexp, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(replacement, VARLINK_STRING, VARLINK_NULLABLE)); + static VARLINK_DEFINE_STRUCT_TYPE( ResolvedAddress, VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE), @@ -32,6 +99,50 @@ static VARLINK_DEFINE_METHOD( VARLINK_DEFINE_OUTPUT_BY_TYPE(names, ResolvedName, VARLINK_ARRAY), VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedService, + VARLINK_DEFINE_FIELD(priority, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(weight, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(port, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(hostname, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(canonicalName, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(addresses, ResolvedAddress, VARLINK_ARRAY|VARLINK_NULLABLE)); + +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedCanonical, + VARLINK_DEFINE_FIELD(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(type, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(domain, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + ResolveService, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(type, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(domain, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(family, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(services, ResolvedService, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(txt, VARLINK_STRING, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(canonical, ResolvedCanonical, 0), + VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); + +static VARLINK_DEFINE_STRUCT_TYPE( + ResolvedRecord, + VARLINK_DEFINE_FIELD(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD_BY_TYPE(rr, ResourceRecord, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(raw, VARLINK_STRING, 0)); + +static VARLINK_DEFINE_METHOD( + ResolveRecord, + VARLINK_DEFINE_INPUT(ifindex, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(class, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(type, VARLINK_INT, 0), + VARLINK_DEFINE_INPUT(flags, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_OUTPUT_BY_TYPE(rrs, ResolvedRecord, VARLINK_ARRAY), + VARLINK_DEFINE_OUTPUT(flags, VARLINK_INT, 0)); + static VARLINK_DEFINE_ERROR(NoNameServers); static VARLINK_DEFINE_ERROR(NoSuchResourceRecord); static VARLINK_DEFINE_ERROR(QueryTimedOut); @@ -40,7 +151,9 @@ static VARLINK_DEFINE_ERROR(InvalidReply); static VARLINK_DEFINE_ERROR(QueryAborted); static VARLINK_DEFINE_ERROR( DNSSECValidationFailed, - VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0)); + VARLINK_DEFINE_FIELD(result, VARLINK_STRING, 0), + VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_ERROR(NoTrustAnchor); static VARLINK_DEFINE_ERROR(ResourceRecordTypeUnsupported); static VARLINK_DEFINE_ERROR(NetworkDown); @@ -48,17 +161,29 @@ static VARLINK_DEFINE_ERROR(NoSource); static VARLINK_DEFINE_ERROR(StubLoop); static VARLINK_DEFINE_ERROR( DNSError, - VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0)); + VARLINK_DEFINE_FIELD(rcode, VARLINK_INT, 0), + VARLINK_DEFINE_FIELD(extendedDNSErrorCode, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_FIELD(extendedDNSErrorMessage, VARLINK_STRING, VARLINK_NULLABLE)); static VARLINK_DEFINE_ERROR(CNAMELoop); static VARLINK_DEFINE_ERROR(BadAddressSize); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeInvalidForQuery); +static VARLINK_DEFINE_ERROR(ZoneTransfersNotPermitted); +static VARLINK_DEFINE_ERROR(ResourceRecordTypeObsolete); VARLINK_DEFINE_INTERFACE( io_systemd_Resolve, "io.systemd.Resolve", &vl_method_ResolveHostname, &vl_method_ResolveAddress, + &vl_method_ResolveService, + &vl_method_ResolveRecord, &vl_type_ResolvedAddress, &vl_type_ResolvedName, + &vl_type_ResolvedService, + &vl_type_ResolvedCanonical, + &vl_type_ResourceKey, + &vl_type_ResourceRecord, + &vl_type_ResolvedRecord, &vl_error_NoNameServers, &vl_error_NoSuchResourceRecord, &vl_error_QueryTimedOut, @@ -73,4 +198,7 @@ VARLINK_DEFINE_INTERFACE( &vl_error_StubLoop, &vl_error_DNSError, &vl_error_CNAMELoop, - &vl_error_BadAddressSize); + &vl_error_BadAddressSize, + &vl_error_ResourceRecordTypeInvalidForQuery, + &vl_error_ZoneTransfersNotPermitted, + &vl_error_ResourceRecordTypeObsolete); diff --git a/src/shared/varlink-io.systemd.Resolve.h b/src/shared/varlink-io.systemd.Resolve.h index 5c7ed39..48a9241 100644 --- a/src/shared/varlink-io.systemd.Resolve.h +++ b/src/shared/varlink-io.systemd.Resolve.h @@ -3,4 +3,7 @@ #include "varlink-idl.h" +extern const VarlinkSymbol vl_type_ResourceKey; +extern const VarlinkSymbol vl_type_ResourceRecord; + extern const VarlinkInterface vl_interface_io_systemd_Resolve; diff --git a/src/shared/varlink.c b/src/shared/varlink.c index 749b644..034e72b 100644 --- a/src/shared/varlink.c +++ b/src/shared/varlink.c @@ -37,6 +37,7 @@ #define VARLINK_DEFAULT_TIMEOUT_USEC (45U*USEC_PER_SEC) #define VARLINK_BUFFER_MAX (16U*1024U*1024U) #define VARLINK_READ_SIZE (64U*1024U) +#define VARLINK_COLLECT_MAX 1024U typedef enum VarlinkState { /* Client side states */ @@ -45,6 +46,8 @@ typedef enum VarlinkState { VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_CALLED, + VARLINK_COLLECTING, + VARLINK_COLLECTING_REPLY, VARLINK_PROCESSING_REPLY, /* Server side states */ @@ -79,6 +82,8 @@ typedef enum VarlinkState { VARLINK_AWAITING_REPLY_MORE, \ VARLINK_CALLING, \ VARLINK_CALLED, \ + VARLINK_COLLECTING, \ + VARLINK_COLLECTING_REPLY, \ VARLINK_PROCESSING_REPLY, \ VARLINK_IDLE_SERVER, \ VARLINK_PROCESSING_METHOD, \ @@ -169,8 +174,11 @@ struct Varlink { VarlinkReply reply_callback; JsonVariant *current; + JsonVariant *current_collected; + VarlinkReplyFlags current_reply_flags; VarlinkSymbol *current_method; + int peer_pidfd; struct ucred ucred; bool ucred_acquired:1; @@ -183,6 +191,7 @@ struct Varlink { bool allow_fd_passing_output:1; bool output_buffer_sensitive:1; /* whether to erase the output buffer after writing it to the socket */ + bool input_sensitive:1; /* Whether incoming messages might be sensitive */ int af; /* address family if socket; AF_UNSPEC if not socket; negative if not known */ @@ -241,18 +250,14 @@ struct VarlinkServer { bool exit_on_idle; }; -typedef struct VarlinkCollectContext { - JsonVariant *parameters; - const char *error_id; - VarlinkReplyFlags flags; -} VarlinkCollectContext ; - static const char* const varlink_state_table[_VARLINK_STATE_MAX] = { [VARLINK_IDLE_CLIENT] = "idle-client", [VARLINK_AWAITING_REPLY] = "awaiting-reply", [VARLINK_AWAITING_REPLY_MORE] = "awaiting-reply-more", [VARLINK_CALLING] = "calling", [VARLINK_CALLED] = "called", + [VARLINK_COLLECTING] = "collecting", + [VARLINK_COLLECTING_REPLY] = "collecting-reply", [VARLINK_PROCESSING_REPLY] = "processing-reply", [VARLINK_IDLE_SERVER] = "idle-server", [VARLINK_PROCESSING_METHOD] = "processing-method", @@ -361,6 +366,8 @@ static int varlink_new(Varlink **ret) { .timeout = VARLINK_DEFAULT_TIMEOUT_USEC, .af = -1, + + .peer_pidfd = -EBADF, }; *ret = v; @@ -447,6 +454,10 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + r = fd_nonblock(pair[1], false); + if (r < 0) + return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m"); + r = safe_fork_full( "(sd-vlexec)", /* stdio_fds= */ NULL, @@ -504,39 +515,120 @@ int varlink_connect_exec(Varlink **ret, const char *_command, char **_argv) { return 0; } +static int varlink_connect_ssh(Varlink **ret, const char *where) { + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; + _cleanup_(sigkill_waitp) pid_t pid = 0; + int r; + + assert_return(ret, -EINVAL); + assert_return(where, -EINVAL); + + /* Connects to an SSH server via OpenSSH 9.4's -W switch to connect to a remote AF_UNIX socket. For + * now we do not expose this function directly, but only via varlink_connect_url(). */ + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + if (!path_is_valid(ssh)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH path is not valid, refusing: %s", ssh); + + const char *e = strchr(where, ':'); + if (!e) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "SSH specification lacks a : separator between host and path, refusing: %s", where); + + _cleanup_free_ char *h = strndup(where, e - where); + if (!h) + return log_oom_debug(); + + _cleanup_free_ char *c = strdup(e + 1); + if (!c) + return log_oom_debug(); + + if (!path_is_absolute(c)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Remote AF_UNIX socket path is not absolute, refusing: %s", c); + + _cleanup_free_ char *p = NULL; + r = path_simplify_alloc(c, &p); + if (r < 0) + return r; + + if (!path_is_normalized(p)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing: %s", p); + + log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h); + + if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0) + return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m"); + + r = safe_fork_full( + "(sd-vlssh)", + /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO }, + /* except_fds= */ NULL, + /* n_except_fds= */ 0, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, + &pid); + if (r < 0) + return log_debug_errno(r, "Failed to spawn process: %m"); + if (r == 0) { + /* Child */ + + execlp(ssh, "ssh", "-W", p, h, NULL); + log_debug_errno(errno, "Failed to invoke %s: %m", ssh); + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + Varlink *v; + r = varlink_new(&v); + if (r < 0) + return log_debug_errno(r, "Failed to create varlink object: %m"); + + v->fd = TAKE_FD(pair[0]); + v->af = AF_UNIX; + v->exec_pid = TAKE_PID(pid); + varlink_set_state(v, VARLINK_IDLE_CLIENT); + + *ret = v; + return 0; +} + int varlink_connect_url(Varlink **ret, const char *url) { _cleanup_free_ char *c = NULL; const char *p; - bool exec; + enum { + SCHEME_UNIX, + SCHEME_EXEC, + SCHEME_SSH, + } scheme; int r; assert_return(ret, -EINVAL); assert_return(url, -EINVAL); - // FIXME: Add support for vsock:, ssh-exec:, ssh-unix: URL schemes here. (The latter with OpenSSH - // 9.4's -W switch for referencing remote AF_UNIX sockets.) + // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here. - /* The Varlink URL scheme is a bit underdefined. We support only the unix: transport for now, plus an - * exec: transport we made up ourselves. Strictly speaking this shouldn't even be called URL, since - * it has nothing to do with Internet URLs by RFC. */ + /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for + * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be + * called "URL", since it has nothing to do with Internet URLs by RFC. */ p = startswith(url, "unix:"); if (p) - exec = false; - else { - p = startswith(url, "exec:"); - if (!p) - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); - - exec = true; - } + scheme = SCHEME_UNIX; + else if ((p = startswith(url, "exec:"))) + scheme = SCHEME_EXEC; + else if ((p = startswith(url, "ssh:"))) + scheme = SCHEME_SSH; + else + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); /* The varlink.org reference C library supports more than just file system paths. We might want to * support that one day too. For now simply refuse that. */ if (p[strcspn(p, ";?#")] != '\0') return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported."); - if (exec || p[0] != '@') { /* no validity checks for abstract namespace */ + if (scheme == SCHEME_SSH) + return varlink_connect_ssh(ret, p); + + if (scheme == SCHEME_EXEC || p[0] != '@') { /* no path validity checks for abstract namespace sockets */ if (!path_is_absolute(p)) return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path not absolute, refusing."); @@ -549,7 +641,7 @@ int varlink_connect_url(Varlink **ret, const char *url) { return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path is not normalized, refusing."); } - if (exec) + if (scheme == SCHEME_EXEC) return varlink_connect_exec(ret, c, NULL); return varlink_connect_address(ret, c ?: p); @@ -599,7 +691,9 @@ static void varlink_clear_current(Varlink *v) { /* Clears the currently processed incoming message */ v->current = json_variant_unref(v->current); + v->current_collected = json_variant_unref(v->current_collected); v->current_method = NULL; + v->current_reply_flags = 0; close_many(v->input_fds, v->n_input_fds); v->input_fds = mfree(v->input_fds); @@ -615,7 +709,7 @@ static void varlink_clear(Varlink *v) { varlink_clear_current(v); - v->input_buffer = mfree(v->input_buffer); + v->input_buffer = v->input_sensitive ? erase_and_free(v->input_buffer) : mfree(v->input_buffer); v->output_buffer = v->output_buffer_sensitive ? erase_and_free(v->output_buffer) : mfree(v->output_buffer); v->input_control_buffer = mfree(v->input_control_buffer); @@ -638,6 +732,8 @@ static void varlink_clear(Varlink *v) { sigterm_wait(v->exec_pid); v->exec_pid = 0; } + + v->peer_pidfd = safe_close(v->peer_pidfd); } static Varlink* varlink_destroy(Varlink *v) { @@ -680,7 +776,7 @@ static int varlink_test_disconnect(Varlink *v) { goto disconnect; /* If we are waiting for incoming data but the read side is shut down, disconnect. */ - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && v->read_disconnected) + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && v->read_disconnected) goto disconnect; /* Similar, if are a client that hasn't written anything yet but the write side is dead, also @@ -803,7 +899,7 @@ static int varlink_read(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER)) return 0; if (v->connecting) /* read() on a socket while we are in connect() will fail with EINVAL, hence exit early here */ return 0; @@ -932,7 +1028,8 @@ static int varlink_read(Varlink *v) { } static int varlink_parse_message(Varlink *v) { - const char *e, *begin; + const char *e; + char *begin; size_t sz; int r; @@ -956,11 +1053,9 @@ static int varlink_parse_message(Varlink *v) { sz = e - begin + 1; - varlink_log(v, "New incoming message: %s", begin); /* FIXME: should we output the whole message here before validation? - * This may produce a non-printable journal entry if the message - * is invalid. We may also expose privileged information. */ - r = json_parse(begin, 0, &v->current, NULL, NULL); + if (v->input_sensitive) + explicit_bzero_safe(begin, sz); if (r < 0) { /* If we encounter a parse failure flush all data. We cannot possibly recover from this, * hence drop all buffered data now. */ @@ -968,6 +1063,24 @@ static int varlink_parse_message(Varlink *v) { return varlink_log_errno(v, r, "Failed to parse JSON: %m"); } + if (v->input_sensitive) { + /* Mark the parameters subfield as sensitive right-away, if that's requested */ + JsonVariant *parameters = json_variant_by_key(v->current, "parameters"); + if (parameters) + json_variant_sensitive(parameters); + } + + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = json_variant_format(v->current, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r < 0) + return r; + + varlink_log(v, "Received message: %s", censored_text); + } + v->input_buffer_size -= sz; if (v->input_buffer_size == 0) @@ -982,7 +1095,7 @@ static int varlink_parse_message(Varlink *v) { static int varlink_test_timeout(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) return 0; if (v->timeout == USEC_INFINITY) return 0; @@ -1006,7 +1119,7 @@ static int varlink_dispatch_local_error(Varlink *v, const char *error) { r = v->reply_callback(v, NULL, error, VARLINK_REPLY_ERROR|VARLINK_REPLY_LOCAL, v->userdata); if (r < 0) - log_debug_errno(r, "Reply callback returned error, ignoring: %m"); + varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); return 1; } @@ -1072,7 +1185,7 @@ static int varlink_dispatch_reply(Varlink *v) { assert(v); - if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING)) return 0; if (!v->current) return 0; @@ -1115,7 +1228,7 @@ static int varlink_dispatch_reply(Varlink *v) { } /* Replies with 'continue' set are only OK if we set 'more' when the method call was initiated */ - if (v->state != VARLINK_AWAITING_REPLY_MORE && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) + if (!IN_SET(v->state, VARLINK_AWAITING_REPLY_MORE, VARLINK_COLLECTING) && FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) goto invalid; /* An error is final */ @@ -1126,19 +1239,20 @@ static int varlink_dispatch_reply(Varlink *v) { if (r < 0) goto invalid; + v->current_reply_flags = flags; + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE)) { varlink_set_state(v, VARLINK_PROCESSING_REPLY); if (v->reply_callback) { r = v->reply_callback(v, parameters, error, flags, v->userdata); if (r < 0) - log_debug_errno(r, "Reply callback returned error, ignoring: %m"); + varlink_log_errno(v, r, "Reply callback returned error, ignoring: %m"); } varlink_clear_current(v); if (v->state == VARLINK_PROCESSING_REPLY) { - assert(v->n_pending > 0); if (!FLAGS_SET(flags, VARLINK_REPLY_CONTINUES)) @@ -1148,7 +1262,9 @@ static int varlink_dispatch_reply(Varlink *v) { FLAGS_SET(flags, VARLINK_REPLY_CONTINUES) ? VARLINK_AWAITING_REPLY_MORE : v->n_pending == 0 ? VARLINK_IDLE_CLIENT : VARLINK_AWAITING_REPLY); } - } else { + } else if (v->state == VARLINK_COLLECTING) + varlink_set_state(v, VARLINK_COLLECTING_REPLY); + else { assert(v->state == VARLINK_CALLING); varlink_set_state(v, VARLINK_CALLED); } @@ -1176,9 +1292,7 @@ static int generic_method_get_info( assert(link); if (json_variant_elements(parameters) != 0) - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, - JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_VARIANT("parameter", json_variant_by_index(parameters, 0)))); + return varlink_error_invalid_parameter(link, parameters); product = strjoin("systemd (", program_invocation_short_name, ")"); if (!product) @@ -1196,7 +1310,7 @@ static int generic_method_get_info( return varlink_replyb(link, JSON_BUILD_OBJECT( JSON_BUILD_PAIR_STRING("vendor", "The systemd Project"), JSON_BUILD_PAIR_STRING("product", product), - JSON_BUILD_PAIR_STRING("version", STRINGIFY(PROJECT_VERSION) " (" GIT_VERSION ")"), + JSON_BUILD_PAIR_STRING("version", PROJECT_VERSION_FULL " (" GIT_VERSION ")"), JSON_BUILD_PAIR_STRING("url", "https://systemd.io/"), JSON_BUILD_PAIR_STRV("interfaces", interfaces))); } @@ -1327,16 +1441,18 @@ static int varlink_dispatch_method(Varlink *v) { v->current_method = hashmap_get(v->server->symbols, method); if (!v->current_method) - log_debug("No interface description defined for method '%s', not validating.", method); + varlink_log(v, "No interface description defined for method '%s', not validating.", method); else { const char *bad_field; r = varlink_idl_validate_method_call(v->current_method, parameters, &bad_field); if (r < 0) { - log_debug_errno(r, "Parameters for method %s() didn't pass validation on field '%s': %m", method, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Parameters for method %s() didn't pass validation on field '%s': %m", + method, strna(bad_field)); - if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { - r = varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", bad_field))); + if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { + r = varlink_error_invalid_parameter_name(v, bad_field); if (r < 0) return r; } @@ -1347,24 +1463,21 @@ static int varlink_dispatch_method(Varlink *v) { if (!invalid) { r = callback(v, parameters, flags, v->userdata); if (r < 0) { - log_debug_errno(r, "Callback for %s returned error: %m", method); + varlink_log_errno(v, r, "Callback for %s returned error: %m", method); /* We got an error back from the callback. Propagate it to the client if the method call remains unanswered. */ - if (v->state == VARLINK_PROCESSED_METHOD) - r = 0; /* already processed */ - else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { + if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { r = varlink_error_errno(v, r); if (r < 0) return r; } } } - } else if (!FLAGS_SET(flags, VARLINK_METHOD_ONEWAY)) { + } else if (IN_SET(v->state, VARLINK_PROCESSING_METHOD, VARLINK_PROCESSING_METHOD_MORE)) { r = varlink_errorb(v, VARLINK_ERROR_METHOD_NOT_FOUND, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)))); if (r < 0) return r; - } else - r = 0; + } switch (v->state) { @@ -1386,7 +1499,7 @@ static int varlink_dispatch_method(Varlink *v) { assert_not_reached(); } - return r; + return 1; invalid: r = -EINVAL; @@ -1482,6 +1595,48 @@ finish: return r; } +int varlink_dispatch_again(Varlink *v) { + int r; + + assert_return(v, -EINVAL); + + /* If a method call handler could not process the method call just yet (for example because it needed + * some Polkit authentication first), then it can leave the call unanswered, do its thing, and then + * ask to be dispatched a second time, via this call. It will then be called again, for the same + * message */ + + if (v->state == VARLINK_DISCONNECTED) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); + if (v->state != VARLINK_PENDING_METHOD) + return varlink_log_errno(v, SYNTHETIC_ERRNO(EBUSY), "Connection has no pending method."); + + varlink_set_state(v, VARLINK_IDLE_SERVER); + + r = sd_event_source_set_enabled(v->defer_event_source, SD_EVENT_ON); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enable deferred event source: %m"); + + return 0; +} + +int varlink_get_current_parameters(Varlink *v, JsonVariant **ret) { + JsonVariant *p; + + assert_return(v, -EINVAL); + + if (!v->current) + return -ENODATA; + + p = json_variant_by_key(v->current, "parameters"); + if (!p) + return -ENODATA; + + if (ret) + *ret = json_variant_ref(p); + + return 0; +} + static void handle_revents(Varlink *v, int revents) { assert(v); @@ -1587,7 +1742,7 @@ int varlink_get_events(Varlink *v) { return EPOLLOUT; if (!v->read_disconnected && - IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_IDLE_SERVER) && + IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING, VARLINK_IDLE_SERVER) && !v->current && v->input_buffer_unscanned <= 0) ret |= EPOLLIN; @@ -1605,7 +1760,7 @@ int varlink_get_timeout(Varlink *v, usec_t *ret) { if (v->state == VARLINK_DISCONNECTED) return varlink_log_errno(v, SYNTHETIC_ERRNO(ENOTCONN), "Not connected."); - if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING) && + if (IN_SET(v->state, VARLINK_AWAITING_REPLY, VARLINK_AWAITING_REPLY_MORE, VARLINK_CALLING, VARLINK_COLLECTING) && v->timeout != USEC_INFINITY) { if (ret) *ret = usec_add(v->timestamp, v->timeout); @@ -1726,51 +1881,60 @@ Varlink* varlink_flush_close_unref(Varlink *v) { static int varlink_format_json(Varlink *v, JsonVariant *m) { _cleanup_(erase_and_freep) char *text = NULL; - int r; + int sz, r; assert(v); assert(m); - r = json_variant_format(m, 0, &text); - if (r < 0) - return r; - assert(text[r] == '\0'); + sz = json_variant_format(m, /* flags= */ 0, &text); + if (sz < 0) + return sz; + assert(text[sz] == '\0'); - if (v->output_buffer_size + r + 1 > VARLINK_BUFFER_MAX) + if (v->output_buffer_size + sz + 1 > VARLINK_BUFFER_MAX) return -ENOBUFS; - varlink_log(v, "Sending message: %s", text); + if (DEBUG_LOGGING) { + _cleanup_(erase_and_freep) char *censored_text = NULL; + + /* Suppress sensitive fields in the debug output */ + r = json_variant_format(m, /* flags= */ JSON_FORMAT_CENSOR_SENSITIVE, &censored_text); + if (r < 0) + return r; + + varlink_log(v, "Sending message: %s", censored_text); + } if (v->output_buffer_size == 0) { free_and_replace(v->output_buffer, text); - v->output_buffer_size = r + 1; + v->output_buffer_size = sz + 1; v->output_buffer_index = 0; } else if (v->output_buffer_index == 0) { - if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + r + 1)) + if (!GREEDY_REALLOC(v->output_buffer, v->output_buffer_size + sz + 1)) return -ENOMEM; - memcpy(v->output_buffer + v->output_buffer_size, text, r + 1); - v->output_buffer_size += r + 1; + memcpy(v->output_buffer + v->output_buffer_size, text, sz + 1); + v->output_buffer_size += sz + 1; } else { char *n; - const size_t new_size = v->output_buffer_size + r + 1; + const size_t new_size = v->output_buffer_size + sz + 1; n = new(char, new_size); if (!n) return -ENOMEM; - memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, r + 1); + memcpy(mempcpy(n, v->output_buffer + v->output_buffer_index, v->output_buffer_size), text, sz + 1); free_and_replace(v->output_buffer, n); v->output_buffer_size = new_size; v->output_buffer_index = 0; } - if (json_variant_is_sensitive(m)) + if (json_variant_is_sensitive_recursive(m)) v->output_buffer_sensitive = true; /* Propagate sensitive flag */ else text = mfree(text); /* No point in the erase_and_free() destructor declared above */ @@ -1999,7 +2163,7 @@ int varlink_observeb(Varlink *v, const char *method, ...) { return varlink_observe(v, method, parameters); } -int varlink_call( +int varlink_call_full( Varlink *v, const char *method, JsonVariant *parameters, @@ -2043,7 +2207,6 @@ int varlink_call( v->timestamp = now(CLOCK_MONOTONIC); while (v->state == VARLINK_CALLING) { - r = varlink_process(v); if (r < 0) return r; @@ -2057,21 +2220,29 @@ int varlink_call( switch (v->state) { - case VARLINK_CALLED: + case VARLINK_CALLED: { assert(v->current); varlink_set_state(v, VARLINK_IDLE_CLIENT); assert(v->n_pending == 1); v->n_pending--; + JsonVariant *e = json_variant_by_key(v->current, "error"), + *p = json_variant_by_key(v->current, "parameters"); + + /* If caller doesn't ask for the error string, then let's return an error code in case of failure */ + if (!ret_error_id && e) + return varlink_error_to_errno(json_variant_string(e), p); + if (ret_parameters) - *ret_parameters = json_variant_by_key(v->current, "parameters"); + *ret_parameters = p; if (ret_error_id) - *ret_error_id = json_variant_string(json_variant_by_key(v->current, "error")); + *ret_error_id = e ? json_variant_string(e) : NULL; if (ret_flags) - *ret_flags = 0; + *ret_flags = v->current_reply_flags; return 1; + } case VARLINK_PENDING_DISCONNECT: case VARLINK_DISCONNECTED: @@ -2085,63 +2256,76 @@ int varlink_call( } } -int varlink_callb( +int varlink_callb_ap( Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, - VarlinkReplyFlags *ret_flags, ...) { + VarlinkReplyFlags *ret_flags, + va_list ap) { _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; - va_list ap; int r; assert_return(v, -EINVAL); + assert_return(method, -EINVAL); - va_start(ap, ret_flags); r = json_buildv(¶meters, ap); - va_end(ap); - if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - return varlink_call(v, method, parameters, ret_parameters, ret_error_id, ret_flags); + return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, ret_flags); } -static void varlink_collect_context_free(VarlinkCollectContext *cc) { - assert(cc); +int varlink_call_and_log( + Varlink *v, + const char *method, + JsonVariant *parameters, + JsonVariant **ret_parameters) { + + JsonVariant *reply = NULL; + const char *error_id = NULL; + int r; + + assert_return(v, -EINVAL); + assert_return(method, -EINVAL); + + r = varlink_call(v, method, parameters, &reply, &error_id); + if (r < 0) + return log_error_errno(r, "Failed to issue %s() varlink call: %m", method); + if (error_id) + return log_error_errno(varlink_error_to_errno(error_id, reply), + "Failed to issue %s() varlink call: %s", method, error_id); - json_variant_unref(cc->parameters); - free((char *)cc->error_id); + if (ret_parameters) + *ret_parameters = TAKE_PTR(reply); + + return 0; } -static int collect_callback( +int varlink_callb_and_log( Varlink *v, - JsonVariant *parameters, - const char *error_id, - VarlinkReplyFlags flags, - void *userdata) { + const char *method, + JsonVariant **ret_parameters, + ...) { - VarlinkCollectContext *context = ASSERT_PTR(userdata); + _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; + va_list ap; int r; - assert(v); - - context->flags = flags; - /* If we hit an error, we will drop all collected replies and just return the error_id and flags in varlink_collect() */ - if (error_id) { - context->error_id = error_id; - return 0; - } + assert_return(v, -EINVAL); + assert_return(method, -EINVAL); - r = json_variant_append_array(&context->parameters, parameters); + va_start(ap, ret_parameters); + r = json_buildv(¶meters, ap); + va_end(ap); if (r < 0) - return varlink_log_errno(v, r, "Failed to append JSON object to array: %m"); + return log_error_errno(r, "Failed to build JSON message: %m"); - return 1; + return varlink_call_and_log(v, method, parameters, ret_parameters); } -int varlink_collect( +int varlink_collect_full( Varlink *v, const char *method, JsonVariant *parameters, @@ -2149,7 +2333,7 @@ int varlink_collect( const char **ret_error_id, VarlinkReplyFlags *ret_flags) { - _cleanup_(varlink_collect_context_free) VarlinkCollectContext context = {}; + _cleanup_(json_variant_unrefp) JsonVariant *m = NULL, *collected = NULL; int r; assert_return(v, -EINVAL); @@ -2166,61 +2350,105 @@ int varlink_collect( * that we can assign a new reply shortly. */ varlink_clear_current(v); - r = varlink_bind_reply(v, collect_callback); + r = varlink_sanitize_parameters(¶meters); if (r < 0) - return varlink_log_errno(v, r, "Failed to bind collect callback"); + return varlink_log_errno(v, r, "Failed to sanitize parameters: %m"); - varlink_set_userdata(v, &context); - r = varlink_observe(v, method, parameters); + r = json_build(&m, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("method", JSON_BUILD_STRING(method)), + JSON_BUILD_PAIR("parameters", JSON_BUILD_VARIANT(parameters)), + JSON_BUILD_PAIR("more", JSON_BUILD_BOOLEAN(true)))); if (r < 0) - return varlink_log_errno(v, r, "Failed to collect varlink method: %m"); + return varlink_log_errno(v, r, "Failed to build json message: %m"); - while (v->state == VARLINK_AWAITING_REPLY_MORE) { + r = varlink_enqueue_json(v, m); + if (r < 0) + return varlink_log_errno(v, r, "Failed to enqueue json message: %m"); - r = varlink_process(v); - if (r < 0) - return r; + varlink_set_state(v, VARLINK_COLLECTING); + v->n_pending++; + v->timestamp = now(CLOCK_MONOTONIC); - /* If we get an error from any of the replies, return immediately with just the error_id and flags*/ - if (context.error_id) { - if (ret_error_id) - *ret_error_id = TAKE_PTR(context.error_id); - if (ret_flags) - *ret_flags = context.flags; - return 0; + for (;;) { + while (v->state == VARLINK_COLLECTING) { + r = varlink_process(v); + if (r < 0) + return r; + if (r > 0) + continue; + + r = varlink_wait(v, USEC_INFINITY); + if (r < 0) + return r; } - if (r > 0) - continue; + switch (v->state) { - r = varlink_wait(v, USEC_INFINITY); - if (r < 0) - return r; - } + case VARLINK_COLLECTING_REPLY: { + assert(v->current); - switch (v->state) { + JsonVariant *e = json_variant_by_key(v->current, "error"), + *p = json_variant_by_key(v->current, "parameters"); - case VARLINK_IDLE_CLIENT: - break; + /* Unless there is more to collect we reset state to idle */ + if (!FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) { + varlink_set_state(v, VARLINK_IDLE_CLIENT); + assert(v->n_pending == 1); + v->n_pending--; + } - case VARLINK_PENDING_DISCONNECT: - case VARLINK_DISCONNECTED: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + if (e) { + if (!ret_error_id) + return varlink_error_to_errno(json_variant_string(e), p); - case VARLINK_PENDING_TIMEOUT: - return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + if (ret_parameters) + *ret_parameters = p; + if (ret_error_id) + *ret_error_id = json_variant_string(e); + if (ret_flags) + *ret_flags = v->current_reply_flags; - default: - assert_not_reached(); - } + return 1; + } - if (ret_parameters) - *ret_parameters = TAKE_PTR(context.parameters); - if (ret_error_id) - *ret_error_id = TAKE_PTR(context.error_id); - if (ret_flags) - *ret_flags = context.flags; - return 1; + if (json_variant_elements(collected) >= VARLINK_COLLECT_MAX) + return varlink_log_errno(v, SYNTHETIC_ERRNO(E2BIG), "Number of reply messages grew too large (%zu) while collecting.", json_variant_elements(collected)); + + r = json_variant_append_array(&collected, p); + if (r < 0) + return varlink_log_errno(v, r, "Failed to append JSON object to array: %m"); + + if (FLAGS_SET(v->current_reply_flags, VARLINK_REPLY_CONTINUES)) { + /* There's more to collect, continue */ + varlink_clear_current(v); + varlink_set_state(v, VARLINK_COLLECTING); + continue; + } + + if (ret_parameters) + /* Install the collection array in the connection object, so that we can hand + * out a pointer to it without passing over ownership, to make it work more + * alike regular method call replies */ + *ret_parameters = v->current_collected = TAKE_PTR(collected); + if (ret_error_id) + *ret_error_id = NULL; + if (ret_flags) + *ret_flags = v->current_reply_flags; + + return 1; + } + + case VARLINK_PENDING_DISCONNECT: + case VARLINK_DISCONNECTED: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ECONNRESET), "Connection was closed."); + + case VARLINK_PENDING_TIMEOUT: + return varlink_log_errno(v, SYNTHETIC_ERRNO(ETIME), "Connection timed out."); + + default: + assert_not_reached(); + } + } } int varlink_collectb( @@ -2228,7 +2456,7 @@ int varlink_collectb( const char *method, JsonVariant **ret_parameters, const char **ret_error_id, - VarlinkReplyFlags *ret_flags, ...) { + ...) { _cleanup_(json_variant_unrefp) JsonVariant *parameters = NULL; va_list ap; @@ -2236,14 +2464,14 @@ int varlink_collectb( assert_return(v, -EINVAL); - va_start(ap, ret_flags); + va_start(ap, ret_error_id); r = json_buildv(¶meters, ap); va_end(ap); if (r < 0) return varlink_log_errno(v, r, "Failed to build json message: %m"); - return varlink_collect(v, method, parameters, ret_parameters, ret_error_id, ret_flags); + return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL); } int varlink_reply(Varlink *v, JsonVariant *parameters) { @@ -2272,7 +2500,9 @@ int varlink_reply(Varlink *v, JsonVariant *parameters) { r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2343,13 +2573,15 @@ int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters) { VarlinkSymbol *symbol = hashmap_get(v->server->symbols, error_id); if (!symbol) - log_debug("No interface description defined for error '%s', not validating.", error_id); + varlink_log(v, "No interface description defined for error '%s', not validating.", error_id); else { const char *bad_field = NULL; r = varlink_idl_validate_error(symbol, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", error_id, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Parameters for error %s didn't pass validation on field '%s', ignoring: %m", + error_id, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2425,6 +2657,13 @@ int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters) { return -EINVAL; } +int varlink_error_invalid_parameter_name(Varlink *v, const char *name) { + return varlink_errorb( + v, + VARLINK_ERROR_INVALID_PARAMETER, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(name)))); +} + int varlink_error_errno(Varlink *v, int error) { return varlink_errorb( v, @@ -2464,7 +2703,9 @@ int varlink_notify(Varlink *v, JsonVariant *parameters) { r = varlink_idl_validate_method_reply(v->current_method, parameters, &bad_field); if (r < 0) - log_debug_errno(r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", v->current_method->name, strna(bad_field)); + /* Please adjust test/units/end.sh when updating the log message. */ + varlink_log_errno(v, r, "Return parameters for method reply %s() didn't pass validation on field '%s', ignoring: %m", + v->current_method->name, strna(bad_field)); } r = varlink_enqueue_json(v, m); @@ -2504,8 +2745,7 @@ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch tab r = json_dispatch_full(parameters, table, /* bad= */ NULL, /* flags= */ 0, userdata, &bad_field); if (r < 0) { if (bad_field) - return varlink_errorb(v, VARLINK_ERROR_INVALID_PARAMETER, - JSON_BUILD_OBJECT(JSON_BUILD_PAIR("parameter", JSON_BUILD_STRING(bad_field)))); + return varlink_error_invalid_parameter_name(v, bad_field); return r; } @@ -2567,12 +2807,29 @@ int varlink_get_peer_uid(Varlink *v, uid_t *ret) { return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); if (!uid_is_valid(v->ucred.uid)) - return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer uid is invalid."); + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer UID is invalid."); *ret = v->ucred.uid; return 0; } +int varlink_get_peer_gid(Varlink *v, gid_t *ret) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret, -EINVAL); + + r = varlink_acquire_ucred(v); + if (r < 0) + return varlink_log_errno(v, r, "Failed to acquire credentials: %m"); + + if (!gid_is_valid(v->ucred.gid)) + return varlink_log_errno(v, SYNTHETIC_ERRNO(ENODATA), "Peer GID is invalid."); + + *ret = v->ucred.gid; + return 0; +} + int varlink_get_peer_pid(Varlink *v, pid_t *ret) { int r; @@ -2590,6 +2847,54 @@ int varlink_get_peer_pid(Varlink *v, pid_t *ret) { return 0; } +static int varlink_acquire_pidfd(Varlink *v) { + assert(v); + + if (v->peer_pidfd >= 0) + return 0; + + v->peer_pidfd = getpeerpidfd(v->fd); + if (v->peer_pidfd < 0) + return v->peer_pidfd; + + return 0; +} + +int varlink_get_peer_pidref(Varlink *v, PidRef *ret) { + int r; + + assert_return(v, -EINVAL); + assert_return(ret, -EINVAL); + + /* Returns r > 0 if we acquired the pidref via SO_PEERPIDFD (i.e. if we can use it for + * authentication). Returns == 0 if we didn't, and the pidref should not be used for + * authentication. */ + + r = varlink_acquire_pidfd(v); + if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) + return r; + + if (v->peer_pidfd < 0) { + pid_t pid; + + r = varlink_get_peer_pid(v, &pid); + if (r < 0) + return r; + + r = pidref_set_pid(ret, pid); + if (r < 0) + return r; + + return 0; /* didn't get pidfd securely */ + } + + r = pidref_set_pidfd(ret, v->peer_pidfd); + if (r < 0) + return r; + + return 1; /* got pidfd securely */ +} + int varlink_set_relative_timeout(Varlink *v, usec_t timeout) { assert_return(v, -EINVAL); assert_return(timeout > 0, -EINVAL); @@ -2788,7 +3093,7 @@ int varlink_push_fd(Varlink *v, int fd) { return i; } -int varlink_dup_fd(Varlink *v, int fd) { +int varlink_push_dup_fd(Varlink *v, int fd) { _cleanup_close_ int dp = -1; int r; @@ -2836,6 +3141,16 @@ int varlink_peek_fd(Varlink *v, size_t i) { return v->input_fds[i]; } +int varlink_peek_dup_fd(Varlink *v, size_t i) { + int fd; + + fd = varlink_peek_fd(v, i); + if (fd < 0) + return fd; + + return RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); +} + int varlink_take_fd(Varlink *v, size_t i) { assert_return(v, -EINVAL); @@ -2855,6 +3170,16 @@ int varlink_take_fd(Varlink *v, size_t i) { static int verify_unix_socket(Varlink *v) { assert(v); + /* Returns: + * • 0 if this is an AF_UNIX socket + * • -ENOTSOCK if this is not a socket at all + * • -ENOMEDIUM if this is a socket, but not an AF_UNIX socket + * + * Reminder: + * • v->af is < 0 if we haven't checked what kind of address family the thing is yet. + * • v->af == AF_UNSPEC if we checked but it's not a socket + * • otherwise: v->af contains the address family we determined */ + if (v->af < 0) { struct stat st; @@ -2870,7 +3195,8 @@ static int verify_unix_socket(Varlink *v) { return v->af; } - return v->af == AF_UNIX ? 0 : -ENOMEDIUM; + return v->af == AF_UNIX ? 0 : + v->af == AF_UNSPEC ? -ENOTSOCK : -ENOMEDIUM; } int varlink_set_allow_fd_passing_input(Varlink *v, bool b) { @@ -2915,6 +3241,13 @@ int varlink_set_allow_fd_passing_output(Varlink *v, bool b) { return 0; } +int varlink_set_input_sensitive(Varlink *v) { + assert_return(v, -EINVAL); + + v->input_sensitive = true; + return 0; +} + int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; @@ -3021,9 +3354,11 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) { server->n_connections++; if (FLAGS_SET(server->flags, VARLINK_SERVER_ACCOUNT_UID)) { + assert(uid_is_valid(ucred->uid)); + r = hashmap_ensure_allocated(&server->by_uid, NULL); if (r < 0) - return log_debug_errno(r, "Failed to allocate UID hash table: %m"); + return varlink_server_log_errno(server, r, "Failed to allocate UID hash table: %m"); c = PTR_TO_UINT(hashmap_get(server->by_uid, UID_TO_PTR(ucred->uid))); @@ -3032,7 +3367,7 @@ static int count_connection(VarlinkServer *server, const struct ucred *ucred) { r = hashmap_replace(server->by_uid, UID_TO_PTR(ucred->uid), UINT_TO_PTR(c + 1)); if (r < 0) - return log_debug_errno(r, "Failed to increment counter in UID hash table: %m"); + return varlink_server_log_errno(server, r, "Failed to increment counter in UID hash table: %m"); } return 0; @@ -3080,7 +3415,7 @@ int varlink_server_add_connection(VarlinkServer *server, int fd, Varlink **ret) } _cleanup_free_ char *desc = NULL; - if (asprintf(&desc, "%s-%i", server->description ?: "varlink", v->fd) >= 0) + if (asprintf(&desc, "%s-%i", varlink_server_description(server), v->fd) >= 0) v->description = TAKE_PTR(desc); /* Link up the server and the connection, and take reference in both directions. Note that the @@ -3141,6 +3476,9 @@ static int connect_callback(sd_event_source *source, int fd, uint32_t revents, v TAKE_FD(cfd); + if (FLAGS_SET(ss->server->flags, VARLINK_SERVER_INPUT_SENSITIVE)) + varlink_set_input_sensitive(v); + if (ss->server->connect_callback) { r = ss->server->connect_callback(ss->server, v, ss->server->userdata); if (r < 0) { @@ -3293,6 +3631,14 @@ int varlink_server_listen_auto(VarlinkServer *s) { n++; } + /* For debug purposes let's listen on an explicitly specified address */ + const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); + if (e) { + r = varlink_server_listen_address(s, e, FLAGS_SET(s->flags, VARLINK_SERVER_ROOT_ONLY) ? 0600 : 0666); + if (r < 0) + return r; + } + return n; } @@ -3436,7 +3782,7 @@ int varlink_server_detach_event(VarlinkServer *s) { LIST_FOREACH(sockets, ss, s->sockets) ss->event_source = sd_event_source_disable_unref(ss->event_source); - sd_event_unref(s->event); + s->event = sd_event_unref(s->event); return 0; } @@ -3472,7 +3818,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth if (varlink_symbol_in_interface(method, "org.varlink.service") || varlink_symbol_in_interface(method, "io.systemd")) - return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Cannot bind server to '%s'.", method); m = strdup(method); if (!m) @@ -3482,7 +3828,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth if (r == -ENOMEM) return log_oom_debug(); if (r < 0) - return log_debug_errno(r, "Failed to register callback: %m"); + return varlink_server_log_errno(s, r, "Failed to register callback: %m"); if (r > 0) TAKE_PTR(m); @@ -3519,7 +3865,7 @@ int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) { assert_return(s, -EINVAL); if (callback && s->connect_callback && callback != s->connect_callback) - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); s->connect_callback = callback; return 0; @@ -3529,7 +3875,7 @@ int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback) assert_return(s, -EINVAL); if (callback && s->disconnect_callback && callback != s->disconnect_callback) - return log_debug_errno(SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBUSY), "A different callback was already set."); s->disconnect_callback = callback; return 0; @@ -3543,7 +3889,7 @@ int varlink_server_add_interface(VarlinkServer *s, const VarlinkInterface *inter assert_return(interface->name, -EINVAL); if (hashmap_contains(s->interfaces, interface->name)) - return log_debug_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name); + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EEXIST), "Duplicate registration of interface '%s'.", interface->name); r = hashmap_ensure_put(&s->interfaces, &string_hash_ops, interface->name, (void*) interface); if (r < 0) @@ -3696,11 +4042,11 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f return log_oom_debug(); if (v[n] != ' ') - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL), "Failed to deserialize VarlinkServerSocket: %s: %m", value); v = startswith(v + n + 1, "varlink-server-socket-fd="); if (!v) - return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EINVAL), "Failed to deserialize VarlinkServerSocket fd %s: %m", value); n = strcspn(v, " "); @@ -3708,9 +4054,9 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f fd = parse_fd(buf); if (fd < 0) - return log_debug_errno(fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf); + return varlink_server_log_errno(s, fd, "Unable to parse VarlinkServerSocket varlink-server-socket-fd=%s: %m", buf); if (!fdset_contains(fds, fd)) - return log_debug_errno(SYNTHETIC_ERRNO(EBADF), + return varlink_server_log_errno(s, SYNTHETIC_ERRNO(EBADF), "VarlinkServerSocket varlink-server-socket-fd= has unknown fd %d: %m", fd); ss = new(VarlinkServerSocket, 1); @@ -3725,7 +4071,7 @@ int varlink_server_deserialize_one(VarlinkServer *s, const char *value, FDSet *f r = varlink_server_add_socket_event_source(s, ss, SD_EVENT_PRIORITY_NORMAL); if (r < 0) - return log_debug_errno(r, "Failed to add VarlinkServerSocket event source to the event loop: %m"); + return varlink_server_log_errno(s, r, "Failed to add VarlinkServerSocket event source to the event loop: %m"); LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss)); return 0; @@ -3738,6 +4084,10 @@ int varlink_invocation(VarlinkInvocationFlags flags) { /* Returns true if this is a "pure" varlink server invocation, i.e. with one fd passed. */ + const char *e = secure_getenv("SYSTEMD_VARLINK_LISTEN"); /* Permit a manual override for testing purposes */ + if (e) + return true; + r = sd_listen_fds_with_names(/* unset_environment= */ false, &names); if (r < 0) return r; @@ -3765,3 +4115,42 @@ int varlink_invocation(VarlinkInvocationFlags flags) { return true; } + +int varlink_error_to_errno(const char *error, JsonVariant *parameters) { + static const struct { + const char *error; + int value; + } table[] = { + { VARLINK_ERROR_DISCONNECTED, -ECONNRESET }, + { VARLINK_ERROR_TIMEOUT, -ETIMEDOUT }, + { VARLINK_ERROR_PROTOCOL, -EPROTO }, + { VARLINK_ERROR_INTERFACE_NOT_FOUND, -EADDRNOTAVAIL }, + { VARLINK_ERROR_METHOD_NOT_FOUND, -ENXIO }, + { VARLINK_ERROR_METHOD_NOT_IMPLEMENTED, -ENOTTY }, + { VARLINK_ERROR_INVALID_PARAMETER, -EINVAL }, + { VARLINK_ERROR_PERMISSION_DENIED, -EACCES }, + { VARLINK_ERROR_EXPECTED_MORE, -EBADE }, + }; + + if (!error) + return 0; + + FOREACH_ELEMENT(t, table) + if (streq(error, t->error)) + return t->value; + + if (streq(error, VARLINK_ERROR_SYSTEM) && parameters) { + JsonVariant *e; + + e = json_variant_by_key(parameters, "errno"); + if (json_variant_is_integer(e)) { + int64_t i; + + i = json_variant_integer(e); + if (i > 0 && i < ERRNO_MAX) + return -i; + } + } + + return -EBADR; /* Catch-all */ +} diff --git a/src/shared/varlink.h b/src/shared/varlink.h index 6ec708a..f6dda63 100644 --- a/src/shared/varlink.h +++ b/src/shared/varlink.h @@ -4,6 +4,7 @@ #include "sd-event.h" #include "json.h" +#include "pidref.h" #include "time-util.h" #include "varlink-idl.h" @@ -46,7 +47,8 @@ typedef enum VarlinkServerFlags { VARLINK_SERVER_MYSELF_ONLY = 1 << 1, /* Only accessible by our own UID */ VARLINK_SERVER_ACCOUNT_UID = 1 << 2, /* Do per user accounting */ VARLINK_SERVER_INHERIT_USERDATA = 1 << 3, /* Initialize Varlink connection userdata from VarlinkServer userdata */ - _VARLINK_SERVER_FLAGS_ALL = (1 << 4) - 1, + VARLINK_SERVER_INPUT_SENSITIVE = 1 << 4, /* Automatically mark al connection input as sensitive */ + _VARLINK_SERVER_FLAGS_ALL = (1 << 5) - 1, } VarlinkServerFlags; typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); @@ -86,12 +88,39 @@ int varlink_send(Varlink *v, const char *method, JsonVariant *parameters); int varlink_sendb(Varlink *v, const char *method, ...); /* Send method call and wait for reply */ -int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); -int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...); +int varlink_call_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); +static inline int varlink_call(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) { + return varlink_call_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +} +int varlink_call_and_log(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters); + +int varlink_callb_ap(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, va_list ap); +static inline int varlink_callb_full(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...) { + va_list ap; + int r; + + va_start(ap, ret_flags); + r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, ret_flags, ap); + va_end(ap); + return r; +} +static inline int varlink_callb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...) { + va_list ap; + int r; + + va_start(ap, ret_error_id); + r = varlink_callb_ap(v, method, ret_parameters, ret_error_id, NULL, ap); + va_end(ap); + return r; +} +int varlink_callb_and_log(Varlink *v, const char *method, JsonVariant **ret_parameters, ...); /* Send method call and begin collecting all 'more' replies into an array, finishing when a final reply is sent */ -int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); -int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags, ...); +int varlink_collect_full(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id, VarlinkReplyFlags *ret_flags); +static inline int varlink_collect(Varlink *v, const char *method, JsonVariant *parameters, JsonVariant **ret_parameters, const char **ret_error_id) { + return varlink_collect_full(v, method, parameters, ret_parameters, ret_error_id, NULL); +} +int varlink_collectb(Varlink *v, const char *method, JsonVariant **ret_parameters, const char **ret_error_id, ...); /* Enqueue method call, expect a reply, which is eventually delivered to the reply callback */ int varlink_invoke(Varlink *v, const char *method, JsonVariant *parameters); @@ -109,22 +138,30 @@ int varlink_replyb(Varlink *v, ...); int varlink_error(Varlink *v, const char *error_id, JsonVariant *parameters); int varlink_errorb(Varlink *v, const char *error_id, ...); int varlink_error_invalid_parameter(Varlink *v, JsonVariant *parameters); +int varlink_error_invalid_parameter_name(Varlink *v, const char *name); int varlink_error_errno(Varlink *v, int error); /* Enqueue a "more" reply */ int varlink_notify(Varlink *v, JsonVariant *parameters); int varlink_notifyb(Varlink *v, ...); +/* Ask for the current message to be dispatched again */ +int varlink_dispatch_again(Varlink *v); + +/* Get the currently processed incoming message */ +int varlink_get_current_parameters(Varlink *v, JsonVariant **ret); + /* Parsing incoming data via json_dispatch() and generate a nice error on parse errors */ int varlink_dispatch(Varlink *v, JsonVariant *parameters, const JsonDispatch table[], void *userdata); /* 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_push_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_peek_dup_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); @@ -137,7 +174,9 @@ void* varlink_set_userdata(Varlink *v, void *userdata); void* varlink_get_userdata(Varlink *v); int varlink_get_peer_uid(Varlink *v, uid_t *ret); +int varlink_get_peer_gid(Varlink *v, gid_t *ret); int varlink_get_peer_pid(Varlink *v, pid_t *ret); +int varlink_get_peer_pidref(Varlink *v, PidRef *ret); int varlink_set_relative_timeout(Varlink *v, usec_t usec); @@ -145,6 +184,9 @@ VarlinkServer* varlink_get_server(Varlink *v); int varlink_set_description(Varlink *v, const char *d); +/* Automatically mark the parameters part of incoming messages as security sensitive */ +int varlink_set_input_sensitive(Varlink *v); + /* Create a varlink server */ int varlink_server_new(VarlinkServer **ret, VarlinkServerFlags flags); VarlinkServer *varlink_server_ref(VarlinkServer *s); @@ -200,6 +242,8 @@ typedef enum VarlinkInvocationFlags { int varlink_invocation(VarlinkInvocationFlags flags); +int varlink_error_to_errno(const char *error, JsonVariant *parameters); + DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_close_unref); DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_flush_close_unref); @@ -213,12 +257,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(VarlinkServer *, varlink_server_unref); /* This one we invented, and use for generically propagating system errors (errno) to clients */ #define VARLINK_ERROR_SYSTEM "io.systemd.System" +/* This one we invented and is a weaker version of "org.varlink.service.PermissionDenied", and indicates that if user would allow interactive auth, we might allow access */ +#define VARLINK_ERROR_INTERACTIVE_AUTHENTICATION_REQUIRED "io.systemd.InteractiveAuthenticationRequired" + /* 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_PERMISSION_DENIED "org.varlink.service.PermissionDenied" #define VARLINK_ERROR_EXPECTED_MORE "org.varlink.service.ExpectedMore" diff --git a/src/shared/verbs.c b/src/shared/verbs.c index a38591d..7e7ac2a 100644 --- a/src/shared/verbs.c +++ b/src/shared/verbs.c @@ -177,4 +177,4 @@ int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { return verb->dispatch(1, STRV_MAKE(verb->verb), userdata); return verb->dispatch(left, argv, userdata); - } +} diff --git a/src/shared/vpick.c b/src/shared/vpick.c new file mode 100644 index 0000000..4720e74 --- /dev/null +++ b/src/shared/vpick.c @@ -0,0 +1,699 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "architecture.h" +#include "chase.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "recurse-dir.h" +#include "vpick.h" + +void pick_result_done(PickResult *p) { + assert(p); + + free(p->path); + safe_close(p->fd); + free(p->version); + + *p = PICK_RESULT_NULL; +} + +static int format_fname( + const PickFilter *filter, + PickFlags flags, + char **ret) { + + _cleanup_free_ char *fn = NULL; + int r; + + assert(filter); + assert(ret); + + if (FLAGS_SET(flags, PICK_TRIES) || !filter->version) /* Underspecified? */ + return -ENOEXEC; + if (strv_length(filter->suffix) > 1) /* suffix is not deterministic? */ + return -ENOEXEC; + + /* The format for names we match goes like this: + * + * + * or: + * _ + * or: + * __ + * or: + * _ + * + * (Note that basename can be empty, in which case the leading "_" is suppressed) + * + * Examples: foo.raw, foo_1.3-7.raw, foo_1.3-7_x86-64.raw, foo_x86-64.raw + * + * Why use "_" as separator here? Primarily because it is not used by Semver 2.0. In RPM it is used + * for "unsortable" versions, i.e. doesn't show up in "sortable" versions, which we matter for this + * usecase here. In Debian the underscore is not allowed (and it uses it itself for separating + * fields). + * + * This is very close to Debian's way to name packages, but allows arbitrary suffixes, and makes the + * architecture field redundant. + * + * Compare with RPM's "NEVRA" concept. Here we have "BVAS" (basename, version, architecture, suffix). + */ + + if (filter->basename) { + fn = strdup(filter->basename); + if (!fn) + return -ENOMEM; + } + + if (filter->version) { + if (isempty(fn)) { + r = free_and_strdup(&fn, filter->version); + if (r < 0) + return r; + } else if (!strextend(&fn, "_", filter->version)) + return -ENOMEM; + } + + if (FLAGS_SET(flags, PICK_ARCHITECTURE) && filter->architecture >= 0) { + const char *as = ASSERT_PTR(architecture_to_string(filter->architecture)); + if (isempty(fn)) { + r = free_and_strdup(&fn, as); + if (r < 0) + return r; + } else if (!strextend(&fn, "_", as)) + return -ENOMEM; + } + + if (!strv_isempty(filter->suffix)) + if (!strextend(&fn, filter->suffix[0])) + return -ENOMEM; + + if (!filename_is_valid(fn)) + return -EINVAL; + + *ret = TAKE_PTR(fn); + return 0; +} + +static int errno_from_mode(uint32_t type_mask, mode_t found) { + /* Returns the most appropriate error code if we are lookging for an inode of type of those in the + * 'type_mask' but found 'found' instead. + * + * type_mask is a mask of 1U << DT_REG, 1U << DT_DIR, … flags, while found is a S_IFREG, S_IFDIR, … + * mode value. */ + + if (type_mask == 0) /* type doesn't matter */ + return 0; + + if (FLAGS_SET(type_mask, UINT32_C(1) << IFTODT(found))) + return 0; + + if (type_mask == (UINT32_C(1) << DT_BLK)) + return -ENOTBLK; + if (type_mask == (UINT32_C(1) << DT_DIR)) + return -ENOTDIR; + if (type_mask == (UINT32_C(1) << DT_SOCK)) + return -ENOTSOCK; + + if (S_ISLNK(found)) + return -ELOOP; + if (S_ISDIR(found)) + return -EISDIR; + + return -EBADF; +} + +static int pin_choice( + const char *toplevel_path, + int toplevel_fd, + const char *inode_path, + int _inode_fd, /* we always take ownership of the fd, even on failure */ + unsigned tries_left, + unsigned tries_done, + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + _cleanup_close_ int inode_fd = TAKE_FD(_inode_fd); + _cleanup_free_ char *resolved_path = NULL; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(inode_path); + assert(filter); + assert(ret); + + if (inode_fd < 0 || FLAGS_SET(flags, PICK_RESOLVE)) { + r = chaseat(toplevel_fd, + inode_path, + CHASE_AT_RESOLVE_IN_ROOT, + FLAGS_SET(flags, PICK_RESOLVE) ? &resolved_path : 0, + inode_fd < 0 ? &inode_fd : NULL); + if (r < 0) + return r; + + if (resolved_path) + inode_path = resolved_path; + } + + struct stat st; + if (fstat(inode_fd, &st) < 0) + return log_debug_errno(errno, "Failed to stat discovered inode '%s': %m", prefix_roota(toplevel_path, inode_path)); + + if (filter->type_mask != 0 && + !FLAGS_SET(filter->type_mask, UINT32_C(1) << IFTODT(st.st_mode))) + return log_debug_errno( + SYNTHETIC_ERRNO(errno_from_mode(filter->type_mask, st.st_mode)), + "Inode '%s' has wrong type, found '%s'.", + prefix_roota(toplevel_path, inode_path), + inode_type_to_string(st.st_mode)); + + _cleanup_(pick_result_done) PickResult result = { + .fd = TAKE_FD(inode_fd), + .st = st, + .architecture = filter->architecture, + .tries_left = tries_left, + .tries_done = tries_done, + }; + + result.path = strdup(inode_path); + if (!result.path) + return log_oom_debug(); + + r = strdup_to(&result.version, filter->version); + if (r < 0) + return r; + + *ret = TAKE_PICK_RESULT(result); + return 1; +} + +static int parse_tries(const char *s, unsigned *ret_tries_left, unsigned *ret_tries_done) { + unsigned left, done; + size_t n; + + assert(s); + assert(ret_tries_left); + assert(ret_tries_done); + + if (s[0] != '+') + goto nomatch; + + s++; + + n = strspn(s, DIGITS); + if (n == 0) + goto nomatch; + + if (s[n] == 0) { + if (safe_atou(s, &left) < 0) + goto nomatch; + + done = 0; + } else if (s[n] == '-') { + _cleanup_free_ char *c = NULL; + + c = strndup(s, n); + if (!c) + return -ENOMEM; + + if (safe_atou(c, &left) < 0) + goto nomatch; + + s += n + 1; + + if (!in_charset(s, DIGITS)) + goto nomatch; + + if (safe_atou(s, &done) < 0) + goto nomatch; + } else + goto nomatch; + + *ret_tries_left = left; + *ret_tries_done = done; + return 1; + +nomatch: + *ret_tries_left = *ret_tries_done = UINT_MAX; + return 0; +} + +static int make_choice( + const char *toplevel_path, + int toplevel_fd, + const char *inode_path, + int _inode_fd, /* we always take ownership of the fd, even on failure */ + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + static const Architecture local_architectures[] = { + /* In order of preference */ + native_architecture(), +#ifdef ARCHITECTURE_SECONDARY + ARCHITECTURE_SECONDARY, +#endif + _ARCHITECTURE_INVALID, /* accept any arch, as last resort */ + }; + + _cleanup_free_ DirectoryEntries *de = NULL; + _cleanup_free_ char *best_version = NULL, *best_filename = NULL, *p = NULL, *j = NULL; + _cleanup_close_ int dir_fd = -EBADF, object_fd = -EBADF, inode_fd = TAKE_FD(_inode_fd); + const Architecture *architectures; + unsigned best_tries_left = UINT_MAX, best_tries_done = UINT_MAX; + size_t n_architectures, best_architecture_index = SIZE_MAX; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(inode_path); + assert(filter); + assert(ret); + + if (inode_fd < 0) { + r = chaseat(toplevel_fd, inode_path, CHASE_AT_RESOLVE_IN_ROOT, NULL, &inode_fd); + if (r < 0) + return r; + } + + /* Maybe the filter is fully specified? Then we can generate the file name directly */ + r = format_fname(filter, flags, &j); + if (r >= 0) { + _cleanup_free_ char *object_path = NULL; + + /* Yay! This worked! */ + p = path_join(inode_path, j); + if (!p) + return log_oom_debug(); + + r = chaseat(toplevel_fd, p, CHASE_AT_RESOLVE_IN_ROOT, &object_path, &object_fd); + if (r == -ENOENT) { + *ret = PICK_RESULT_NULL; + return 0; + } + if (r < 0) + return log_debug_errno(r, "Failed to open '%s': %m", prefix_roota(toplevel_path, p)); + + return pin_choice( + toplevel_path, + toplevel_fd, + FLAGS_SET(flags, PICK_RESOLVE) ? object_path : p, + TAKE_FD(object_fd), /* unconditionally pass ownership of the fd */ + /* tries_left= */ UINT_MAX, + /* tries_done= */ UINT_MAX, + filter, + flags & ~PICK_RESOLVE, + ret); + + } else if (r != -ENOEXEC) + return log_debug_errno(r, "Failed to format file name: %m"); + + /* Underspecified, so we do our enumeration dance */ + + /* Convert O_PATH to a regular directory fd */ + dir_fd = fd_reopen(inode_fd, O_DIRECTORY|O_RDONLY|O_CLOEXEC); + if (dir_fd < 0) + return log_debug_errno(dir_fd, "Failed to reopen '%s' as directory: %m", prefix_roota(toplevel_path, inode_path)); + + r = readdir_all(dir_fd, 0, &de); + if (r < 0) + return log_debug_errno(r, "Failed to read directory '%s': %m", prefix_roota(toplevel_path, inode_path)); + + if (filter->architecture < 0) { + architectures = local_architectures; + n_architectures = ELEMENTSOF(local_architectures); + } else { + architectures = &filter->architecture; + n_architectures = 1; + } + + FOREACH_ARRAY(entry, de->entries, de->n_entries) { + unsigned found_tries_done = UINT_MAX, found_tries_left = UINT_MAX; + _cleanup_free_ char *dname = NULL; + size_t found_architecture_index = SIZE_MAX; + const char *e; + + dname = strdup((*entry)->d_name); + if (!dname) + return log_oom_debug(); + + if (!isempty(filter->basename)) { + e = startswith(dname, filter->basename); + if (!e) + continue; + + if (e[0] != '_') + continue; + + e++; + } else + e = dname; + + if (!strv_isempty(filter->suffix)) { + char *sfx = endswith_strv(e, filter->suffix); + if (!sfx) + continue; + + *sfx = 0; + } + + if (FLAGS_SET(flags, PICK_TRIES)) { + char *plus = strrchr(e, '+'); + if (plus) { + r = parse_tries(plus, &found_tries_left, &found_tries_done); + if (r < 0) + return r; + if (r > 0) /* Found and parsed, now chop off */ + *plus = 0; + } + } + + if (FLAGS_SET(flags, PICK_ARCHITECTURE)) { + char *underscore = strrchr(e, '_'); + Architecture a; + + a = underscore ? architecture_from_string(underscore + 1) : _ARCHITECTURE_INVALID; + + for (size_t i = 0; i < n_architectures; i++) + if (architectures[i] == a) { + found_architecture_index = i; + break; + } + + if (found_architecture_index == SIZE_MAX) { /* No matching arch found */ + log_debug("Found entry with architecture '%s' which is not what we are looking for, ignoring entry.", a < 0 ? "any" : architecture_to_string(a)); + continue; + } + + /* Chop off architecture from string */ + if (underscore) + *underscore = 0; + } + + if (!version_is_valid(e)) { + log_debug("Version string '%s' of entry '%s' is invalid, ignoring entry.", e, (*entry)->d_name); + continue; + } + + if (filter->version && !streq(filter->version, e)) { + log_debug("Found entry with version string '%s', but was looking for '%s', ignoring entry.", e, filter->version); + continue; + } + + if (best_filename) { /* Already found one matching entry? Then figure out the better one */ + int d = 0; + + /* First, prefer entries with tries left over those without */ + if (FLAGS_SET(flags, PICK_TRIES)) + d = CMP(found_tries_left != 0, best_tries_left != 0); + + /* Second, prefer newer versions */ + if (d == 0) + d = strverscmp_improved(e, best_version); + + /* Third, prefer native architectures over secondary architectures */ + if (d == 0 && + FLAGS_SET(flags, PICK_ARCHITECTURE) && + found_architecture_index != SIZE_MAX && best_architecture_index != SIZE_MAX) + d = -CMP(found_architecture_index, best_architecture_index); + + /* Fourth, prefer entries with more tries left */ + if (FLAGS_SET(flags, PICK_TRIES)) { + if (d == 0) + d = CMP(found_tries_left, best_tries_left); + + /* Fifth, prefer entries with fewer attempts done so far */ + if (d == 0) + d = -CMP(found_tries_done, best_tries_done); + } + + /* Last, just compare the filenames as strings */ + if (d == 0) + d = strcmp((*entry)->d_name, best_filename); + + if (d < 0) { + log_debug("Found entry '%s' but previously found entry '%s' matches better, hence skipping entry.", (*entry)->d_name, best_filename); + continue; + } + } + + r = free_and_strdup_warn(&best_version, e); + if (r < 0) + return r; + + r = free_and_strdup_warn(&best_filename, (*entry)->d_name); + if (r < 0) + return r; + + best_architecture_index = found_architecture_index; + best_tries_left = found_tries_left; + best_tries_done = found_tries_done; + } + + if (!best_filename) { /* Everything was good, but we didn't find any suitable entry */ + *ret = PICK_RESULT_NULL; + return 0; + } + + p = path_join(inode_path, best_filename); + if (!p) + return log_oom_debug(); + + object_fd = openat(dir_fd, best_filename, O_CLOEXEC|O_PATH); + if (object_fd < 0) + return log_debug_errno(errno, "Failed to open '%s': %m", prefix_roota(toplevel_path, p)); + + return pin_choice( + toplevel_path, + toplevel_fd, + p, + TAKE_FD(object_fd), + best_tries_left, + best_tries_done, + &(const PickFilter) { + .type_mask = filter->type_mask, + .basename = filter->basename, + .version = empty_to_null(best_version), + .architecture = best_architecture_index != SIZE_MAX ? architectures[best_architecture_index] : _ARCHITECTURE_INVALID, + .suffix = filter->suffix, + }, + flags, + ret); +} + +int path_pick( + const char *toplevel_path, + int toplevel_fd, + const char *path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret) { + + _cleanup_free_ char *filter_bname = NULL, *dir = NULL, *parent = NULL, *fname = NULL; + char * const *filter_suffix_strv = NULL; + const char *filter_suffix = NULL, *enumeration_path; + uint32_t filter_type_mask; + int r; + + assert(toplevel_fd >= 0 || toplevel_fd == AT_FDCWD); + assert(path); + assert(filter); + assert(ret); + + /* Given a path, resolve .v/ subdir logic (if used!), and returns the choice made. This supports + * three ways to be called: + * + * • with a path referring a directory of any name, and filter→basename *explicitly* specified, in + * which case we'll use a pattern "_*" on the directory's files. + * + * • with no filter→basename explicitly specified and a path referring to a directory named in format + * ".v" . In this case the filter basename to search for inside the dir + * is derived from the directory name. Example: "/foo/bar/baz.suffix.v" → we'll search for + * "/foo/bar/baz.suffix.v/baz_*.suffix". + * + * • with a path whose penultimate component ends in ".v/". In this case the final component of the + * path refers to the pattern. Example: "/foo/bar/baz.v/waldo__.suffix" → we'll search for + * "/foo/bar/baz.v/waldo_*.suffix". + */ + + /* Explicit basename specified, then shortcut things and do .v mode regardless of the path name. */ + if (filter->basename) + return make_choice( + toplevel_path, + toplevel_fd, + path, + /* inode_fd= */ -EBADF, + filter, + flags, + ret); + + r = path_extract_filename(path, &fname); + if (r < 0) { + if (r != -EADDRNOTAVAIL) /* root dir or "." */ + return r; + + /* If there's no path element we can derive a pattern off, the don't */ + goto bypass; + } + + /* Remember if the path ends in a dash suffix */ + bool slash_suffix = r == O_DIRECTORY; + + const char *e = endswith(fname, ".v"); + if (e) { + /* So a path in the form /foo/bar/baz.v is specified. In this case our search basename is + * "baz", possibly with a suffix chopped off if there's one specified. */ + filter_bname = strndup(fname, e - fname); + if (!filter_bname) + return -ENOMEM; + + /* Chop off suffix, if specified */ + char *f = endswith_strv(filter_bname, filter->suffix); + if (f) + *f = 0; + + filter_suffix_strv = filter->suffix; + filter_type_mask = filter->type_mask; + + enumeration_path = path; + } else { + /* The path does not end in '.v', hence see if the last element is a pattern. */ + + char *wildcard = strrstr(fname, "___"); + if (!wildcard) + goto bypass; /* Not a pattern, then bypass */ + + /* We found the '___' wildcard, hence everything after it is our filter suffix, and + * everything before is our filter basename */ + *wildcard = 0; + filter_suffix = empty_to_null(wildcard + 3); + + filter_bname = TAKE_PTR(fname); + + r = path_extract_directory(path, &dir); + if (r < 0) { + if (!IN_SET(r, -EDESTADDRREQ, -EADDRNOTAVAIL)) /* only filename specified (no dir), or root or "." */ + return r; + + goto bypass; /* No dir extractable, can't check if parent ends in ".v" */ + } + + r = path_extract_filename(dir, &parent); + if (r < 0) { + if (r != -EADDRNOTAVAIL) /* root dir or "." */ + return r; + + goto bypass; /* Cannot extract fname from parent dir, can't check if it ends in ".v" */ + } + + e = endswith(parent, ".v"); + if (!e) + goto bypass; /* Doesn't end in ".v", shortcut */ + + filter_type_mask = filter->type_mask; + if (slash_suffix) { + /* If the pattern is suffixed by a / then we are looking for directories apparently. */ + if (filter_type_mask != 0 && !FLAGS_SET(filter_type_mask, UINT32_C(1) << DT_DIR)) + return log_debug_errno(SYNTHETIC_ERRNO(errno_from_mode(filter_type_mask, S_IFDIR)), + "Specified pattern ends in '/', but not looking for directories, refusing."); + filter_type_mask = UINT32_C(1) << DT_DIR; + } + + enumeration_path = dir; + } + + return make_choice( + toplevel_path, + toplevel_fd, + enumeration_path, + /* inode_fd= */ -EBADF, + &(const PickFilter) { + .type_mask = filter_type_mask, + .basename = filter_bname, + .version = filter->version, + .architecture = filter->architecture, + .suffix = filter_suffix_strv ?: STRV_MAKE(filter_suffix), + }, + flags, + ret); + +bypass: + /* Don't make any choice, but just use the passed path literally */ + return pin_choice( + toplevel_path, + toplevel_fd, + path, + /* inode_fd= */ -EBADF, + /* tries_left= */ UINT_MAX, + /* tries_done= */ UINT_MAX, + filter, + flags, + ret); +} + +int path_pick_update_warn( + char **path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret_result) { + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + int r; + + assert(path); + assert(*path); + assert(filter); + + /* This updates the first argument if needed! */ + + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + *path, + filter, + flags, + &result); + if (r == -ENOENT) { + log_debug("Path '%s' doesn't exist, leaving as is.", *path); + + if (ret_result) + *ret_result = PICK_RESULT_NULL; + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to pick version on path '%s': %m", *path); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching entries in versioned directory '%s' found.", *path); + + log_debug("Resolved versioned directory pattern '%s' to file '%s' as version '%s'.", result.path, *path, strna(result.version)); + + if (ret_result) { + r = free_and_strdup_warn(path, result.path); + if (r < 0) + return r; + + *ret_result = TAKE_PICK_RESULT(result); + } else + free_and_replace(*path, result.path); + + return 1; +} + +const PickFilter pick_filter_image_raw = { + .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK), + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(".raw"), +}; + +const PickFilter pick_filter_image_dir = { + .type_mask = UINT32_C(1) << DT_DIR, + .architecture = _ARCHITECTURE_INVALID, +}; + +const PickFilter pick_filter_image_any = { + .type_mask = (UINT32_C(1) << DT_REG) | (UINT32_C(1) << DT_BLK) | (UINT32_C(1) << DT_DIR), + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(".raw", ""), +}; diff --git a/src/shared/vpick.h b/src/shared/vpick.h new file mode 100644 index 0000000..38251c8 --- /dev/null +++ b/src/shared/vpick.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "architecture.h" + +typedef enum PickFlags { + PICK_ARCHITECTURE = 1 << 0, /* Look for an architecture suffix */ + PICK_TRIES = 1 << 1, /* Look for tries left/tries done counters */ + PICK_RESOLVE = 1 << 2, /* Return the fully resolved (chased) path, rather than the path to the entry */ +} PickFlags; + +typedef struct PickFilter { + uint32_t type_mask; /* A mask of 1U << DT_REG, 1U << DT_DIR, … */ + const char *basename; /* Can be overridden by search pattern */ + const char *version; + Architecture architecture; + char * const *suffix; /* Can be overridden by search pattern */ +} PickFilter; + +typedef struct PickResult { + char *path; + int fd; /* O_PATH */ + struct stat st; + char *version; + Architecture architecture; + unsigned tries_left; + unsigned tries_done; +} PickResult; + +#define PICK_RESULT_NULL \ + (const PickResult) { \ + .fd = -EBADF, \ + .st.st_mode = MODE_INVALID, \ + .architecture = _ARCHITECTURE_INVALID, \ + .tries_left = UINT_MAX, \ + .tries_done = UINT_MAX, \ + } + +#define TAKE_PICK_RESULT(pick) TAKE_GENERIC(pick, PickResult, PICK_RESULT_NULL) + +void pick_result_done(PickResult *p); + +int path_pick( + const char *toplevel_path, + int toplevel_fd, + const char *path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret); + +int path_pick_update_warn( + char **path, + const PickFilter *filter, + PickFlags flags, + PickResult *ret); + +extern const PickFilter pick_filter_image_raw; +extern const PickFilter pick_filter_image_dir; +extern const PickFilter pick_filter_image_any; diff --git a/src/shared/wall.c b/src/shared/wall.c index d5900ef..c5d6439 100644 --- a/src/shared/wall.c +++ b/src/shared/wall.c @@ -30,8 +30,9 @@ static int write_to_terminal(const char *tty, const char *message) { fd = open(tty, O_WRONLY|O_NONBLOCK|O_NOCTTY|O_CLOEXEC); if (fd < 0) return -errno; - if (!isatty(fd)) - return -ENOTTY; + + if (!isatty_safe(fd)) + return -errno; return loop_write_full(fd, message, SIZE_MAX, TIMEOUT_USEC); } diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c index 99ccefb..810c5b5 100644 --- a/src/shared/watchdog.c +++ b/src/shared/watchdog.c @@ -41,7 +41,7 @@ static int saturated_usec_to_sec(usec_t val) { return MIN(t, (usec_t) WATCHDOG_TIMEOUT_MAX_SEC); /* Saturate to watchdog max */ } -static int get_watchdog_sysfs_path(const char *filename, char **ret_path) { +static int watchdog_get_sysfs_path(const char *filename, char **ret_path) { struct stat st; if (watchdog_fd < 0) @@ -59,11 +59,11 @@ static int get_watchdog_sysfs_path(const char *filename, char **ret_path) { return 0; } -static int get_pretimeout_governor(char **ret_gov) { +static int watchdog_get_pretimeout_governor(char **ret_gov) { _cleanup_free_ char *sys_fn = NULL; int r; - r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; @@ -78,14 +78,14 @@ static int get_pretimeout_governor(char **ret_gov) { return 0; } -static int set_pretimeout_governor(const char *governor) { +static int watchdog_set_pretimeout_governor(const char *governor) { _cleanup_free_ char *sys_fn = NULL; int r; if (isempty(governor)) return 0; /* Nothing to do */ - r = get_watchdog_sysfs_path("pretimeout_governor", &sys_fn); + r = watchdog_get_sysfs_path("pretimeout_governor", &sys_fn); if (r < 0) return r; @@ -205,7 +205,7 @@ static int watchdog_ping_now(void) { return 0; } -static int update_pretimeout(void) { +static int watchdog_update_pretimeout(void) { _cleanup_free_ char *governor = NULL; int r, t_sec, pt_sec; @@ -223,9 +223,9 @@ static int update_pretimeout(void) { watchdog_supports_pretimeout = false; /* Update the pretimeout governor as well */ - (void) set_pretimeout_governor(watchdog_pretimeout_governor); + (void) watchdog_set_pretimeout_governor(watchdog_pretimeout_governor); - r = get_pretimeout_governor(&governor); + r = watchdog_get_pretimeout_governor(&governor); if (r < 0) return log_warning_errno(r, "Watchdog: failed to read pretimeout governor: %m"); if (isempty(governor)) @@ -259,7 +259,7 @@ static int update_pretimeout(void) { return r; } -static int update_timeout(void) { +static int watchdog_update_timeout(void) { int r; usec_t previous_timeout; @@ -296,7 +296,7 @@ static int update_timeout(void) { * changed as well by the driver or the kernel so we need to update the * pretimeout now. Or if the watchdog is being configured for the first * time, we want to configure the pretimeout before it is enabled. */ - (void) update_pretimeout(); + (void) watchdog_update_pretimeout(); r = watchdog_set_enable(true); if (r < 0) @@ -307,7 +307,7 @@ static int update_timeout(void) { return watchdog_ping_now(); } -static int open_watchdog(void) { +static int watchdog_open(void) { struct watchdog_info ident; char **try_order; int r; @@ -338,7 +338,9 @@ static int open_watchdog(void) { } if (watchdog_fd < 0) - return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s: %m", watchdog_device ?: "auto"); + return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to open watchdog device %s.", watchdog_device ?: "auto"); + + watchdog_last_ping = USEC_INFINITY; if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) < 0) log_debug_errno(errno, "Hardware watchdog %s does not support WDIOC_GETSUPPORT ioctl, ignoring: %m", watchdog_device); @@ -348,7 +350,7 @@ static int open_watchdog(void) { ident.firmware_version, watchdog_device); - r = update_timeout(); + r = watchdog_update_timeout(); if (r < 0) goto close_and_fail; @@ -396,9 +398,9 @@ int watchdog_setup(usec_t timeout) { watchdog_timeout = timeout; if (watchdog_fd < 0) - return open_watchdog(); + return watchdog_open(); - r = update_timeout(); + r = watchdog_update_timeout(); if (r < 0) watchdog_timeout = previous_timeout; @@ -415,17 +417,17 @@ int watchdog_setup_pretimeout(usec_t timeout) { * even if it fails to update the timeout. */ watchdog_pretimeout = timeout; - return update_pretimeout(); + return watchdog_update_pretimeout(); } int watchdog_setup_pretimeout_governor(const char *governor) { if (free_and_strdup(&watchdog_pretimeout_governor, governor) < 0) return -ENOMEM; - return set_pretimeout_governor(watchdog_pretimeout_governor); + return watchdog_set_pretimeout_governor(watchdog_pretimeout_governor); } -static usec_t calc_timeout(void) { +static usec_t watchdog_calc_timeout(void) { /* Calculate the effective timeout which accounts for the watchdog * pretimeout if configured and supported. */ if (watchdog_supports_pretimeout && timestamp_is_set(watchdog_pretimeout) && watchdog_timeout >= watchdog_pretimeout) @@ -435,7 +437,7 @@ static usec_t calc_timeout(void) { } usec_t watchdog_runtime_wait(void) { - usec_t timeout = calc_timeout(); + usec_t timeout = watchdog_calc_timeout(); if (!timestamp_is_set(timeout)) return USEC_INFINITY; @@ -458,10 +460,10 @@ int watchdog_ping(void) { if (watchdog_fd < 0) /* open_watchdog() will automatically ping the device for us if necessary */ - return open_watchdog(); + return watchdog_open(); ntime = now(CLOCK_BOOTTIME); - timeout = calc_timeout(); + timeout = watchdog_calc_timeout(); /* Never ping earlier than watchdog_timeout/4 and try to ping * by watchdog_timeout/2 plus scheduling latencies at the latest */ diff --git a/src/shared/wifi-util.c b/src/shared/wifi-util.c index d4e6dca..052f560 100644 --- a/src/shared/wifi-util.c +++ b/src/shared/wifi-util.c @@ -55,7 +55,7 @@ int wifi_get_interface(sd_netlink *genl, int ifindex, enum nl80211_iftype *ret_i if (r < 0) return log_debug_errno(r, "Failed to get NL80211_ATTR_IFTYPE attribute: %m"); - r = sd_netlink_message_read_data_suffix0(reply, NL80211_ATTR_SSID, &len, (void**) &ssid); + r = sd_netlink_message_read_data(reply, NL80211_ATTR_SSID, &len, (void**) &ssid); if (r < 0 && r != -ENODATA) return log_debug_errno(r, "Failed to get NL80211_ATTR_SSID attribute: %m"); if (r >= 0) { diff --git a/src/shutdown/detach-dm.c b/src/shutdown/detach-dm.c index 8b8f72d..f6f672c 100644 --- a/src/shutdown/detach-dm.c +++ b/src/shutdown/detach-dm.c @@ -132,7 +132,7 @@ static int dm_points_list_detach(DeviceMapper **head, bool *changed, bool last_t if ((major(rootdev) != 0 && rootdev == m->devnum) || (major(usrdev) != 0 && usrdev == m->devnum)) { log_debug("Not detaching DM %s that backs the OS itself, skipping.", m->path); - n_failed ++; + n_failed++; continue; } diff --git a/src/shutdown/detach-md.c b/src/shutdown/detach-md.c index cf3130d..ac46670 100644 --- a/src/shutdown/detach-md.c +++ b/src/shutdown/detach-md.c @@ -153,7 +153,7 @@ static int md_points_list_detach(RaidDevice **head, bool *changed, bool last_try if ((major(rootdev) != 0 && rootdev == m->devnum) || (major(usrdev) != 0 && usrdev == m->devnum)) { log_debug("Not detaching MD %s that backs the OS itself, skipping.", m->path); - n_failed ++; + n_failed++; continue; } diff --git a/src/shutdown/shutdown.c b/src/shutdown/shutdown.c index b709078..d998bc5 100644 --- a/src/shutdown/shutdown.c +++ b/src/shutdown/shutdown.c @@ -333,6 +333,28 @@ static void init_watchdog(void) { } } +static void notify_supervisor(void) { + /* Notify VMM/container manager of the desired mode of reboot and the boot parameter */ + _cleanup_free_ char *reboot_parameter = NULL; + int r; + + r = read_reboot_parameter(&reboot_parameter); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to read reboot parameter, ignoring: %m"); + + if (reboot_parameter) + (void) sd_notifyf(/* unset_environment= */ false, + "EXIT_STATUS=%i\n" + "X_SYSTEMD_SHUTDOWN=%s\n" + "X_SYSTEMD_REBOOT_PARAMETER=%s", + arg_exit_code, arg_verb, reboot_parameter); + else + (void) sd_notifyf(/* unset_environment= */ false, + "EXIT_STATUS=%i\n" + "X_SYSTEMD_SHUTDOWN=%s", + arg_exit_code, arg_verb); +} + int main(int argc, char *argv[]) { static const char* const dirs[] = { SYSTEM_SHUTDOWN_PATH, @@ -387,13 +409,6 @@ int main(int argc, char *argv[]) { goto error; } - /* 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. Note that we execute this as early as - * possible since otherwise we might shut down the VM before the AF_VSOCK buffers have been flushed. - * While this doesn't guarantee the message will arrive, in practice we do enough work after this - * that the message should always arrive on the host */ - (void) sd_notifyf(0, "EXIT_STATUS=%i", arg_exit_code); - (void) cg_get_root_path(&cgroup); bool in_container = detect_container() > 0; @@ -546,7 +561,10 @@ int main(int argc, char *argv[]) { } /* We're done with the watchdog. Note that the watchdog is explicitly not stopped here. It remains - * active to guard against any issues during the rest of the shutdown sequence. */ + * active to guard against any issues during the rest of the shutdown sequence. Note that we + * explicitly close the device with disarm=false here, before releasing the rest of the watchdog + * data. */ + watchdog_close(/* disarm= */ false); watchdog_free_device(); arguments[0] = NULL; /* Filled in by execute_directories(), when needed */ @@ -589,6 +607,8 @@ int main(int argc, char *argv[]) { if (!in_container) sync_with_progress(); + notify_supervisor(); + if (streq(arg_verb, "exit")) { if (in_container) { log_info("Exiting container."); diff --git a/src/shutdown/umount.c b/src/shutdown/umount.c index 1a9b99d..ca6d36e 100644 --- a/src/shutdown/umount.c +++ b/src/shutdown/umount.c @@ -95,7 +95,7 @@ int mount_points_list_get(const char *mountinfo, MountPoint **head) { * we might lack the rights to unmount these things, hence don't bother. */ if (mount_point_is_api(path) || mount_point_ignore(path) || - PATH_STARTSWITH_SET(path, "/dev", "/sys", "/proc")) + path_below_api_vfs(path)) continue; is_api_vfs = fstype_is_api_vfs(fstype); diff --git a/src/sleep/battery-capacity.c b/src/sleep/battery-capacity.c index 62a0746..5667a59 100644 --- a/src/sleep/battery-capacity.c +++ b/src/sleep/battery-capacity.c @@ -68,7 +68,7 @@ static int siphash24_compress_id128( if (r < 0) return log_debug_errno(r, "Failed to get %s ID: %m", name); - siphash24_compress(&id, sizeof(sd_id128_t), state); + siphash24_compress_typesafe(id, state); return 0; } @@ -123,7 +123,7 @@ static int get_battery_discharge_rate(sd_device *dev, int *ret) { break; p = line; - r = extract_many_words(&p, NULL, 0, &stored_hash_id, &stored_discharge_rate, NULL); + r = extract_many_words(&p, NULL, 0, &stored_hash_id, &stored_discharge_rate); if (r < 0) return log_debug_errno(r, "Failed to parse hash_id and discharge_rate read from " DISCHARGE_RATE_FILEPATH ": %m"); if (r != 2) @@ -142,7 +142,7 @@ static int get_battery_discharge_rate(sd_device *dev, int *ret) { return log_device_debug_errno(dev, r, "Failed to parse discharge rate read from " DISCHARGE_RATE_FILEPATH ": %m"); if (!battery_discharge_rate_is_valid(discharge_rate)) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour: %m"); + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(ERANGE), "Invalid battery discharge percentage rate per hour."); *ret = discharge_rate; return 0; /* matching device found, exit iteration */ @@ -157,7 +157,7 @@ static int put_battery_discharge_rate(int estimated_battery_discharge_rate, uint if (!battery_discharge_rate_is_valid(estimated_battery_discharge_rate)) return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), - "Invalid battery discharge rate %d%% per hour: %m", + "Invalid battery discharge rate %d%% per hour.", estimated_battery_discharge_rate); r = write_string_filef( diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index 21062b2..0402bb0 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -23,10 +23,12 @@ #include "build.h" #include "bus-error.h" #include "bus-locator.h" +#include "bus-unit-util.h" #include "bus-util.h" #include "constants.h" #include "devnum-util.h" #include "efivars.h" +#include "env-util.h" #include "exec-util.h" #include "fd-util.h" #include "fileio.h" @@ -149,22 +151,22 @@ static int write_state(int fd, char * const *states) { return r; } -static int write_mode(char * const *modes) { - int r = 0; +static int write_mode(const char *path, char * const *modes) { + int r, ret = 0; - STRV_FOREACH(mode, modes) { - int k; + assert(path); - k = write_string_file("/sys/power/disk", *mode, WRITE_STRING_FILE_DISABLE_BUFFER); - if (k >= 0) { - log_debug("Using sleep disk mode '%s'.", *mode); + STRV_FOREACH(mode, modes) { + r = write_string_file(path, *mode, WRITE_STRING_FILE_DISABLE_BUFFER); + if (r >= 0) { + log_debug("Using sleep mode '%s' for %s.", *mode, path); return 0; } - RET_GATHER(r, log_debug_errno(k, "Failed to write '%s' to /sys/power/disk: %m", *mode)); + RET_GATHER(ret, log_debug_errno(r, "Failed to write '%s' to %s: %m", *mode, path)); } - return r; + return ret; } static int lock_all_homes(void) { @@ -218,7 +220,6 @@ static int execute( NULL }; - _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {}; _cleanup_close_ int state_fd = -EBADF; int r; @@ -234,10 +235,17 @@ static int execute( /* This file is opened first, so that if we hit an error, we can abort before modifying any state. */ state_fd = open("/sys/power/state", O_WRONLY|O_CLOEXEC); if (state_fd < 0) - return -errno; + return log_error_errno(errno, "Failed to open /sys/power/state: %m"); + + if (SLEEP_NEEDS_MEM_SLEEP(sleep_config, operation)) { + r = write_mode("/sys/power/mem_sleep", sleep_config->mem_modes); + if (r < 0) + return log_error_errno(r, "Failed to write mode to /sys/power/mem_sleep: %m"); + } /* Configure hibernation settings if we are supposed to hibernate */ - if (sleep_operation_is_hibernation(operation)) { + if (SLEEP_OPERATION_IS_HIBERNATION(operation)) { + _cleanup_(hibernation_device_done) HibernationDevice hibernation_device = {}; bool resume_set; r = find_suitable_hibernation_device(&hibernation_device); @@ -257,7 +265,7 @@ static int execute( goto fail; } - r = write_mode(sleep_config->modes[operation]); + r = write_mode("/sys/power/disk", sleep_config->modes[operation]); if (r < 0) { log_error_errno(r, "Failed to write mode to /sys/power/disk: %m"); goto fail; @@ -299,8 +307,8 @@ static int execute( return 0; fail: - if (sleep_operation_is_hibernation(operation) && is_efi_boot()) - (void) efi_set_variable(EFI_SYSTEMD_VARIABLE(HibernateLocation), NULL, 0); + if (SLEEP_OPERATION_IS_HIBERNATION(operation)) + (void) clear_efi_hibernate_location_and_warn(); return r; } @@ -427,15 +435,13 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { if (r < 0) log_warning_errno(r, "Failed to estimate and update battery discharge rate, ignoring: %m"); } else - log_debug("System woke up too early to estimate discharge rate"); + log_debug("System woke up too early to estimate discharge rate."); if (!woken_by_timer) /* Return as manual wakeup done. This also will return in case battery was charged during suspension */ return 0; r = check_wakeup_type(); - if (r < 0) - log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m"); if (r > 0) { log_debug("wakeup type is APM timer"); /* system should hibernate */ @@ -446,48 +452,21 @@ static int custom_timer_suspend(const SleepConfig *sleep_config) { return 1; } -/* Freeze when invoked and thaw on cleanup */ -static int freeze_thaw_user_slice(const char **method) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - if (!method || !*method) - return 0; - - r = bus_connect_system_systemd(&bus); - if (r < 0) - return log_debug_errno(r, "Failed to open connection to systemd: %m"); - - (void) sd_bus_set_method_call_timeout(bus, FREEZE_TIMEOUT); - - r = bus_call_method(bus, bus_systemd_mgr, *method, &error, NULL, "s", SPECIAL_USER_SLICE); - if (r < 0) - return log_debug_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); - - return 1; -} - static int execute_s2h(const SleepConfig *sleep_config) { - _unused_ _cleanup_(freeze_thaw_user_slice) const char *auto_method_thaw = "ThawUnit"; int r; assert(sleep_config); - r = freeze_thaw_user_slice(&(const char*) { "FreezeUnit" }); - if (r < 0) - log_debug_errno(r, "Failed to freeze unit user.slice, ignoring: %m"); - /* Only check if we have automated battery alarms if HibernateDelaySec= is not set, as in that case * we'll busy poll for the configured interval instead */ if (!timestamp_is_set(sleep_config->hibernate_delay_usec)) { r = check_wakeup_type(); if (r < 0) - log_debug_errno(r, "Failed to check hardware wakeup type, ignoring: %m"); + log_warning_errno(r, "Failed to check hardware wakeup type, ignoring: %m"); else { r = battery_trip_point_alarm_exists(); if (r < 0) - log_debug_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m"); + log_warning_errno(r, "Failed to check whether acpi_btp support is enabled or not, ignoring: %m"); } } else r = 0; /* Force fallback path */ @@ -500,7 +479,7 @@ static int execute_s2h(const SleepConfig *sleep_config) { r = check_wakeup_type(); if (r < 0) - return log_debug_errno(r, "Failed to check hardware wakeup type: %m"); + return log_error_errno(r, "Failed to check hardware wakeup type: %m"); if (r == 0) /* For APM Timer wakeup, system should hibernate else wakeup */ @@ -601,6 +580,7 @@ static int parse_argv(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { + _cleanup_(unit_freezer_freep) UnitFreezer *user_slice_freezer = NULL; _cleanup_(sleep_config_freep) SleepConfig *sleep_config = NULL; int r; @@ -619,6 +599,17 @@ static int run(int argc, char *argv[]) { "Sleep operation \"%s\" is disabled by configuration, refusing.", sleep_operation_to_string(arg_operation)); + /* Freeze the user sessions */ + r = getenv_bool("SYSTEMD_SLEEP_FREEZE_USER_SESSIONS"); + if (r < 0 && r != -ENXIO) + log_warning_errno(r, "Cannot parse value of $SYSTEMD_SLEEP_FREEZE_USER_SESSIONS, ignoring."); + if (r != 0) + (void) unit_freezer_new_freeze(SPECIAL_USER_SLICE, &user_slice_freezer); + else + log_notice("User sessions remain unfrozen on explicit request ($SYSTEMD_SLEEP_FREEZE_USER_SESSIONS=0).\n" + "This is not recommended, and might result in unexpected behavior, particularly\n" + "in suspend-then-hibernate operations or setups with encrypted home directories."); + switch (arg_operation) { case SLEEP_SUSPEND_THEN_HIBERNATE: @@ -645,6 +636,9 @@ static int run(int argc, char *argv[]) { } + if (user_slice_freezer) + RET_GATHER(r, unit_freezer_thaw(user_slice_freezer)); + return r; } diff --git a/src/sleep/sleep.conf b/src/sleep/sleep.conf index fad95b3..9843034 100644 --- a/src/sleep/sleep.conf +++ b/src/sleep/sleep.conf @@ -23,5 +23,6 @@ #AllowHybridSleep=yes #SuspendState=mem standby freeze #HibernateMode=platform shutdown +#MemorySleepMode= #HibernateDelaySec= #SuspendEstimationSec=60min diff --git a/src/socket-activate/socket-activate.c b/src/socket-activate/socket-activate.c index 78ecb29..9234bb3 100644 --- a/src/socket-activate/socket-activate.c +++ b/src/socket-activate/socket-activate.c @@ -446,9 +446,7 @@ static int run(int argc, char **argv) { _cleanup_strv_free_ char **exec_argv = NULL; int r, n; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 287fd6c..dd9fa07 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -676,8 +676,7 @@ static int run(int argc, char *argv[]) { _unused_ _cleanup_(notify_on_cleanup) const char *notify_stop = NULL; int r, n, fd; - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/ssh-generator/20-systemd-ssh-proxy.conf.in b/src/ssh-generator/20-systemd-ssh-proxy.conf.in new file mode 100644 index 0000000..b97e0f5 --- /dev/null +++ b/src/ssh-generator/20-systemd-ssh-proxy.conf.in @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Make sure unix/* and vsock/* can be used to connect to AF_UNIX and AF_VSOCK paths +# +Host unix/* vsock/* + ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy %h %p + ProxyUseFdpass yes + CheckHostIP no + + # Disable all kinds of host identity checks, since these addresses are generally ephemeral. + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + +# Allow connecting to the local host directly via ".host" +Host .host + ProxyCommand {{LIBEXECDIR}}/systemd-ssh-proxy unix/run/ssh-unix-local/socket %p + ProxyUseFdpass yes + CheckHostIP no diff --git a/src/ssh-generator/meson.build b/src/ssh-generator/meson.build new file mode 100644 index 0000000..d5413f7 --- /dev/null +++ b/src/ssh-generator/meson.build @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + generator_template + { + 'name' : 'systemd-ssh-generator', + 'sources' : files('ssh-generator.c'), + }, + libexec_template + { + 'name' : 'systemd-ssh-proxy', + 'sources' : files('ssh-proxy.c'), + }, +] + +if conf.get('ENABLE_SSH_PROXY_CONFIG') == 1 + custom_target( + '20-systemd-ssh-proxy.conf', + input : '20-systemd-ssh-proxy.conf.in', + output : '20-systemd-ssh-proxy.conf', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : true, + install_dir : sshconfdir.startswith('/usr/') ? sshconfdir : libexecdir / 'ssh_config.d') + + if not sshconfdir.startswith('/usr/') + install_emptydir(sshconfdir) + + meson.add_install_script(sh, '-c', + ln_s.format(libexecdir / 'ssh_config.d' / '20-systemd-ssh-proxy.conf', sshconfdir / '20-systemd-ssh-proxy.conf')) + endif +endif diff --git a/src/ssh-generator/ssh-generator.c b/src/ssh-generator/ssh-generator.c new file mode 100644 index 0000000..c671b41 --- /dev/null +++ b/src/ssh-generator/ssh-generator.c @@ -0,0 +1,495 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "creds-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "generator.h" +#include "install.h" +#include "missing_socket.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "socket-netlink.h" +#include "socket-util.h" +#include "special.h" +#include "virt.h" + +/* A small generator binding potentially five or more SSH sockets: + * + * 1. Listen on AF_VSOCK port 22 if we run in a VM with AF_VSOCK enabled + * 2. Listen on AF_UNIX socket /run/host/unix-export/ssh if we run in a container with /run/host/ support + * 3. Listen on AF_UNIX socket /run/ssh-unix-local/socket (always) + * 4. Listen on any socket specified via kernel command line option systemd.ssh_listen= + * 5. Similar, but from system credential ssh.listen + * + * The first two provide a nice way for hosts to connect to containers and VMs they invoke via the usual SSH + * logic, but without waiting for networking or suchlike. The third allows the same for local clients. */ + +static const char *arg_dest = NULL; +static bool arg_auto = true; +static char **arg_listen_extra = NULL; + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (proc_cmdline_key_streq(key, "systemd.ssh_auto")) { + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.ssh_auto switch \"%s\", ignoring: %m", value); + else + arg_auto = r; + + } else if (proc_cmdline_key_streq(key, "systemd.ssh_listen")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + SocketAddress sa; + r = socket_address_parse(&sa, value); + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", value); + else { + _cleanup_free_ char *s = NULL; + r = socket_address_print(&sa, &s); + if (r < 0) + return log_error_errno(r, "Failed to format socket address: %m"); + + if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0) + return log_oom(); + } + } + + return 0; +} + +static int make_sshd_template_unit( + const char *dest, + const char *template, + const char *sshd_binary, + const char *found_sshd_template_service, + char **generated_sshd_template_unit) { + + int r; + + assert(dest); + assert(template); + assert(sshd_binary); + assert(generated_sshd_template_unit); + + /* If the system has a suitable template already, symlink it to the name we want to reuse it */ + if (found_sshd_template_service) + return generator_add_symlink( + dest, + template, + /* dep_type= */ NULL, + found_sshd_template_service); + + if (!*generated_sshd_template_unit) { + _cleanup_fclose_ FILE *f = NULL; + + r = generator_open_unit_file_full( + dest, + /* source= */ NULL, + "sshd-generated@.service", /* Give this generated unit a generic name, since we want to use it for both AF_UNIX and AF_VSOCK */ + &f, + generated_sshd_template_unit, + /* ret_temp_path= */ NULL); + if (r < 0) + return r; + + fprintf(f, + "[Unit]\n" + "Description=OpenSSH Per-Connection Server Daemon\n" + "Documentation=man:systemd-ssh-generator(8) man:sshd(8)\n" + "[Service]\n" + "ExecStart=-%s -i -o \"AuthorizedKeysFile ${CREDENTIALS_DIRECTORY}/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys\"\n" + "StandardInput=socket\n" + "ImportCredential=ssh.ephemeral-authorized_keys-all", + sshd_binary); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write sshd template: %m"); + } + + return generator_add_symlink( + dest, + template, + /* dep_type= */ NULL, + *generated_sshd_template_unit); +} + +static int write_socket_unit( + const char *dest, + const char *unit, + const char *listen_stream, + const char *comment, + bool with_ssh_access_target_dependency) { + + int r; + + assert(dest); + assert(unit); + assert(listen_stream); + assert(comment); + + _cleanup_fclose_ FILE *f = NULL; + r = generator_open_unit_file( + dest, + /* source= */ NULL, + unit, + &f); + if (r < 0) + return r; + + fprintf(f, + "[Unit]\n" + "Description=OpenSSH Server Socket (systemd-ssh-generator, %s)\n" + "Documentation=man:systemd-ssh-generator(8)\n", + comment); + + /* When this is a remotely accessible socket let's mark this with a milestone: ssh-access.target */ + if (with_ssh_access_target_dependency) + fputs("Wants=ssh-access.target\n" + "Before=ssh-access.target\n", + f); + + fprintf(f, + "\n[Socket]\n" + "ListenStream=%s\n" + "Accept=yes\n" + "PollLimitIntervalSec=30s\n" + "PollLimitBurst=50\n", + listen_stream); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write %s SSH socket unit: %m", comment); + + r = generator_add_symlink( + dest, + SPECIAL_SOCKETS_TARGET, + "wants", + unit); + if (r < 0) + return r; + + return 0; +} + +static int add_vsock_socket( + const char *dest, + const char *sshd_binary, + const char *found_sshd_template_unit, + char **generated_sshd_template_unit) { + + int r; + + assert(dest); + assert(generated_sshd_template_unit); + + Virtualization v = detect_virtualization(); + if (v < 0) + return log_error_errno(v, "Failed to detect if we run in a VM: %m"); + if (!VIRTUALIZATION_IS_VM(v)) { + /* NB: if we are running in a container inside a VM, then we'll *not* do AF_VSOCK stuff */ + log_debug("Not running in a VM, not listening on AF_VSOCK."); + return 0; + } + + _cleanup_close_ int vsock_fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (vsock_fd < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) { + log_debug("Not creating AF_VSOCK ssh listener, since AF_VSOCK is not available."); + return 0; + } + + return log_error_errno(errno, "Unable to test if AF_VSOCK is available: %m"); + } + + vsock_fd = safe_close(vsock_fd); + + /* Determine the local CID so that we can log it to help users to connect to this VM */ + unsigned local_cid; + r = vsock_get_local_cid(&local_cid); + if (r < 0) { + if (ERRNO_IS_DEVICE_ABSENT(r)) { + log_debug("Not creating AF_VSOCK ssh listener, since /dev/vsock is not available (even though AF_VSOCK is)."); + return 0; + } + + return log_error_errno(r, "Failed to query local AF_VSOCK CID: %m"); + } + + r = make_sshd_template_unit( + dest, + "sshd-vsock@.service", + sshd_binary, + found_sshd_template_unit, + generated_sshd_template_unit); + if (r < 0) + return r; + + r = write_socket_unit( + dest, + "sshd-vsock.socket", + "vsock::22", + "AF_VSOCK", + /* with_ssh_access_target_dependency= */ true); + if (r < 0) + return r; + + log_info("Binding SSH to AF_VSOCK vsock::22.\n" + "→ connect via 'ssh vsock/%u' from host", local_cid); + return 0; +} + +static int add_local_unix_socket( + const char *dest, + const char *sshd_binary, + const char *found_sshd_template_unit, + char **generated_sshd_template_unit) { + + int r; + + assert(dest); + assert(sshd_binary); + assert(generated_sshd_template_unit); + + r = make_sshd_template_unit( + dest, + "sshd-unix-local@.service", + sshd_binary, + found_sshd_template_unit, + generated_sshd_template_unit); + if (r < 0) + return r; + + r = write_socket_unit( + dest, + "sshd-unix-local.socket", + "/run/ssh-unix-local/socket", + "AF_UNIX Local", + /* with_ssh_access_target_dependency= */ false); + if (r < 0) + return r; + + + log_info("Binding SSH to AF_UNIX socket /run/ssh-unix-local/socket.\n" + "→ connect via 'ssh .host' locally"); + return 0; +} + +static int add_export_unix_socket( + const char *dest, + const char *sshd_binary, + const char *found_sshd_template_unit, + char **generated_sshd_template_unit) { + + int r; + + assert(dest); + assert(sshd_binary); + assert(generated_sshd_template_unit); + + Virtualization v = detect_container(); + if (v < 0) + return log_error_errno(v, "Failed to detect if we run in a container: %m"); + if (v == VIRTUALIZATION_NONE) { + log_debug("Not running in container, not listening on /run/host/unix-export/ssh"); + return 0; + } + + if (access("/run/host/unix-export/", W_OK) < 0) { + if (errno == ENOENT) { + log_debug("Container manager does not provide /run/host/unix-export/ mount, not binding AF_UNIX socket there."); + return 0; + } + if (errno == EROFS || ERRNO_IS_PRIVILEGE(errno)) { + log_debug("Container manager does not provide write access to /run/host/unix-export/, not binding AF_UNIX socket there."); + return 0; + } + + return log_error_errno(errno, "Unable to check if /run/host/unix-export exists: %m"); + } + + r = make_sshd_template_unit( + dest, + "sshd-unix-export@.service", + sshd_binary, + found_sshd_template_unit, + generated_sshd_template_unit); + if (r < 0) + return r; + + r = write_socket_unit( + dest, + "sshd-unix-export.socket", + "/run/host/unix-export/ssh", + "AF_UNIX Export", + /* with_ssh_access_target_dependency= */ true); + if (r < 0) + return r; + + log_info("Binding SSH to AF_UNIX socket /run/host/unix-export/ssh\n" + "→ connect via 'ssh unix/run/systemd/nspawn/unix-export/\?\?\?/ssh' from host"); + + return 0; +} + +static int add_extra_sockets( + const char *dest, + const char *sshd_binary, + const char *found_sshd_template_unit, + char **generated_sshd_template_unit) { + + unsigned n = 1; + int r; + + assert(dest); + assert(sshd_binary); + assert(generated_sshd_template_unit); + + if (strv_isempty(arg_listen_extra)) + return 0; + + STRV_FOREACH(i, arg_listen_extra) { + _cleanup_free_ char *service = NULL, *socket = NULL; + + if (n > 1) { + if (asprintf(&service, "sshd-extra-%u@.service", n) < 0) + return log_oom(); + + if (asprintf(&socket, "sshd-extra-%u.socket", n) < 0) + return log_oom(); + } + + r = make_sshd_template_unit( + dest, + service ?: "sshd-extra@.service", + sshd_binary, + found_sshd_template_unit, + generated_sshd_template_unit); + if (r < 0) + return r; + + r = write_socket_unit( + dest, + socket ?: "sshd-extra.socket", + *i, + *i, + /* with_ssh_access_target_dependency= */ true); + if (r < 0) + return r; + + log_info("Binding SSH to socket %s.", *i); + n++; + } + + return 0; +} + +static int parse_credentials(void) { + _cleanup_free_ char *b = NULL; + size_t sz = 0; + int r; + + r = read_credential_with_decryption("ssh.listen", (void**) &b, &sz); + if (r <= 0) + return r; + + _cleanup_fclose_ FILE *f = NULL; + f = fmemopen_unlocked(b, sz, "r"); + if (!f) + return log_oom(); + + for (;;) { + _cleanup_free_ char *item = NULL; + + r = read_stripped_line(f, LINE_MAX, &item); + if (r == 0) + break; + if (r < 0) { + log_error_errno(r, "Failed to parse credential 'ssh.listen': %m"); + break; + } + + if (startswith(item, "#")) + continue; + + SocketAddress sa; + r = socket_address_parse(&sa, item); + if (r < 0) { + log_warning_errno(r, "Failed to parse systemd.ssh_listen= expression, ignoring: %s", item); + continue; + } + + _cleanup_free_ char *s = NULL; + r = socket_address_print(&sa, &s); + if (r < 0) + return log_error_errno(r, "Failed to format socket address: %m"); + + if (strv_consume(&arg_listen_extra, TAKE_PTR(s)) < 0) + return log_oom(); + } + + return 0; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + assert_se(arg_dest = dest); + + r = proc_cmdline_parse(parse_proc_cmdline_item, /* userdata= */ NULL, /* flags= */ 0); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + (void) parse_credentials(); + + strv_sort(arg_listen_extra); + strv_uniq(arg_listen_extra); + + if (!arg_auto && strv_isempty(arg_listen_extra)) { + log_debug("Disabling SSH generator logic, because as it has been turned off explicitly."); + return 0; + } + + _cleanup_free_ char *sshd_binary = NULL; + r = find_executable("sshd", &sshd_binary); + if (r == -ENOENT) { + log_info("Disabling SSH generator logic, since sshd is not installed."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to determine if sshd is installed: %m"); + + _cleanup_(lookup_paths_done) LookupPaths lp = {}; + r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, /* root_dir= */ NULL); + if (r < 0) + return r; + + _cleanup_free_ char *found_sshd_template_unit = NULL; + r = unit_file_exists_full(RUNTIME_SCOPE_SYSTEM, &lp, "sshd@.service", &found_sshd_template_unit); + if (r < 0) + return log_error_errno(r, "Unable to detect if sshd@.service exists: %m"); + + _cleanup_free_ char *generated_sshd_template_unit = NULL; + RET_GATHER(r, add_extra_sockets(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit)); + + if (arg_auto) { + RET_GATHER(r, add_vsock_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit)); + RET_GATHER(r, add_local_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit)); + RET_GATHER(r, add_export_unix_socket(dest, sshd_binary, found_sshd_template_unit, &generated_sshd_template_unit)); + } + + return r; +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/ssh-generator/ssh-proxy.c b/src/ssh-generator/ssh-proxy.c new file mode 100644 index 0000000..4884c93 --- /dev/null +++ b/src/ssh-generator/ssh-proxy.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "fd-util.h" +#include "iovec-util.h" +#include "log.h" +#include "main-func.h" +#include "missing_socket.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" + +static int process_vsock(const char *host, const char *port) { + int r; + + assert(host); + assert(port); + + union sockaddr_union sa = { + .vm.svm_family = AF_VSOCK, + }; + + r = vsock_parse_cid(host, &sa.vm.svm_cid); + if (r < 0) + return log_error_errno(r, "Failed to parse vsock cid: %s", host); + + r = vsock_parse_port(port, &sa.vm.svm_port); + if (r < 0) + return log_error_errno(r, "Failed to parse vsock port: %s", port); + + _cleanup_close_ int fd = socket(AF_VSOCK, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate AF_VSOCK socket: %m"); + + if (connect(fd, &sa.sa, SOCKADDR_LEN(sa)) < 0) + return log_error_errno(errno, "Failed to connect to vsock:%u:%u: %m", sa.vm.svm_cid, sa.vm.svm_port); + + /* OpenSSH wants us to send a single byte along with the file descriptor, hence do so */ + r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to send socket via STDOUT: %m"); + + log_debug("Successfully sent AF_VSOCK socket via STDOUT."); + return 0; +} + +static int process_unix(const char *path) { + int r; + + assert(path); + + /* We assume the path is absolute unless it starts with a dot (or is already explicitly absolute) */ + _cleanup_free_ char *prefixed = NULL; + if (!STARTSWITH_SET(path, "/", "./")) { + prefixed = strjoin("/", path); + if (!prefixed) + return log_oom(); + + path = prefixed; + } + + _cleanup_close_ int fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate AF_UNIX socket: %m"); + + r = connect_unix_path(fd, AT_FDCWD, path); + if (r < 0) + return log_error_errno(r, "Failed to connect to AF_UNIX socket %s: %m", path); + + r = send_one_fd_iov(STDOUT_FILENO, fd, &IOVEC_NUL_BYTE, /* n_iovec= */ 1, /* flags= */ 0); + if (r < 0) + return log_error_errno(r, "Failed to send socket via STDOUT: %m"); + + log_debug("Successfully sent AF_UNIX socket via STDOUT."); + return 0; +} + +static int run(int argc, char* argv[]) { + + log_setup(); + + if (argc != 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected two arguments: host and port."); + + const char *host = argv[1], *port = argv[2]; + + const char *p = startswith(host, "vsock/"); + if (p) + return process_vsock(p, port); + + p = startswith(host, "unix/"); + if (p) + return process_unix(p); + + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Don't know how to parse host name specification: %s", host); +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/stdio-bridge/stdio-bridge.c b/src/stdio-bridge/stdio-bridge.c index fe551cf..d3629f5 100644 --- a/src/stdio-bridge/stdio-bridge.c +++ b/src/stdio-bridge/stdio-bridge.c @@ -105,9 +105,7 @@ static int run(int argc, char *argv[]) { bool is_unix; int r, in_fd, out_fd; - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/storagetm/storagetm.c b/src/storagetm/storagetm.c index 16d4fb0..1bb8eec 100644 --- a/src/storagetm/storagetm.c +++ b/src/storagetm/storagetm.c @@ -587,7 +587,7 @@ static uint16_t calculate_start_port(const char *name, int ip_family) { /* Use some fixed key Lennart pulled from /dev/urandom, so that we are deterministic */ siphash24_init(&state, SD_ID128_MAKE(d1,0b,67,b5,e2,b7,4a,91,8d,6b,27,b6,35,c1,9f,d9).bytes); siphash24_compress_string(name, &state); - siphash24_compress(&ip_family, sizeof(ip_family), &state); + siphash24_compress_typesafe(ip_family, &state); nr = 1024U + siphash24_finalize(&state) % (0xFFFFU - 1024U); SET_FLAG(nr, 1, ip_family == AF_INET6); /* Lowest bit reflects family */ @@ -789,10 +789,10 @@ static void device_hash_func(const struct stat *q, struct siphash *state) { assert(q); mode_t m = q->st_mode & S_IFMT; - siphash24_compress(&m, sizeof(m), state); + siphash24_compress_typesafe(m, state); if (S_ISBLK(q->st_mode) || S_ISCHR(q->st_mode)) { - siphash24_compress(&q->st_rdev, sizeof(q->st_rdev), state); + siphash24_compress_typesafe(q->st_rdev, state); return; } @@ -951,9 +951,14 @@ static int device_added(Context *c, sd_device *device) { .st_mode = S_IFBLK, }; - r = sd_device_get_devnum(device, &lookup_key.st_rdev); + /* MIPS OABI declares st_rdev as unsigned long instead of dev_t. + * Use a temp var to avoid passing an incompatible pointer. + * https://sourceware.org/bugzilla/show_bug.cgi?id=21278 */ + dev_t devnum; + r = sd_device_get_devnum(device, &devnum); if (r < 0) return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); + lookup_key.st_rdev = devnum; if (hashmap_contains(c->subsystems, &lookup_key)) { log_debug("Device '%s' already seen.", devname); @@ -1007,9 +1012,14 @@ static int device_removed(Context *c, sd_device *device) { .st_mode = S_IFBLK, }; - r = sd_device_get_devnum(device, &lookup_key.st_rdev); + /* MIPS OABI declares st_rdev as unsigned long instead of dev_t. + * Use a temp var to avoid passing an incompatible pointer. + * https://sourceware.org/bugzilla/show_bug.cgi?id=21278 */ + dev_t devnum; + r = sd_device_get_devnum(device, &devnum); if (r < 0) return log_device_error_errno(device, r, "Failed to get major/minor from device: %m"); + lookup_key.st_rdev = devnum; NvmeSubsystem *s = hashmap_remove(c->subsystems, &lookup_key); if (!s) @@ -1044,7 +1054,7 @@ static int on_display_refresh(sd_event_source *s, uint64_t usec, void *userdata) c->display_refresh_scheduled = false; - if (isatty(STDERR_FILENO) > 0) + if (isatty(STDERR_FILENO)) fputs(ANSI_HOME_CLEAR, stderr); /* If we have both IPv4 and IPv6, we display IPv4 info via Plymouth, since it doesn't have much @@ -1101,9 +1111,7 @@ static int run(int argc, char* argv[]) { _cleanup_(context_done) Context context = {}; int r; - log_show_color(true); - log_parse_environment(); - log_open(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -1204,8 +1212,11 @@ static int run(int argc, char* argv[]) { if (r < 0) return log_error_errno(r, "Failed to exclude loop devices: %m"); - FOREACH_DEVICE(enumerator, device) + FOREACH_DEVICE(enumerator, device) { + if (device_is_processed(device) <= 0) + continue; device_added(&context, device); + } } _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; @@ -1225,7 +1236,7 @@ static int run(int argc, char* argv[]) { if (r < 0) return log_error_errno(r, "Failed to subscribe to RTM_DELADDR events: %m"); - if (isatty(0) > 0) + if (isatty(STDIN_FILENO)) log_info("Hit Ctrl-C to exit target mode."); _unused_ _cleanup_(notify_on_cleanup) const char *notify_message = diff --git a/src/sysext/sysext.c b/src/sysext/sysext.c index 8dc515e..c55c24f 100644 --- a/src/sysext/sysext.c +++ b/src/sysext/sysext.c @@ -39,15 +39,41 @@ #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-util.h" #include "pretty-print.h" #include "process-util.h" +#include "rm-rf.h" #include "sort-util.h" +#include "string-table.h" +#include "string-util.h" #include "terminal-util.h" #include "user-util.h" #include "varlink.h" #include "varlink-io.systemd.sysext.h" #include "verbs.h" +typedef enum MutableMode { + MUTABLE_NO, + MUTABLE_YES, + MUTABLE_AUTO, + MUTABLE_IMPORT, + MUTABLE_EPHEMERAL, + MUTABLE_EPHEMERAL_IMPORT, + _MUTABLE_MAX, + _MUTABLE_INVALID = -EINVAL, +} MutableMode; + +static const char* const mutable_mode_table[_MUTABLE_MAX] = { + [MUTABLE_NO] = "no", + [MUTABLE_YES] = "yes", + [MUTABLE_AUTO] = "auto", + [MUTABLE_IMPORT] = "import", + [MUTABLE_EPHEMERAL] = "ephemeral", + [MUTABLE_EPHEMERAL_IMPORT] = "ephemeral-import", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(mutable_mode, MutableMode, MUTABLE_YES); + 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; @@ -58,50 +84,64 @@ static bool arg_no_reload = false; static int arg_noexec = -1; static ImagePolicy *arg_image_policy = NULL; static bool arg_varlink = false; +static MutableMode arg_mutable = MUTABLE_NO; /* Is set to IMAGE_CONFEXT when systemd is called with the confext functionality instead of the default */ static ImageClass arg_image_class = IMAGE_SYSEXT; +#define MUTABLE_EXTENSIONS_BASE_DIR "/var/lib/extensions.mutable" + 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 *full_identifier; const char *short_identifier; const char *short_identifier_plural; + const char *blurb; + const char *dot_directory_name; + const char *directory_name; const char *level_env; const char *scope_env; const char *name_env; + const char *mode_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", + .full_identifier = "systemd-sysext", .short_identifier = "sysext", .short_identifier_plural = "extensions", + .blurb = "Merge system extension images into /usr/ and /opt/.", + .dot_directory_name = ".systemd-sysext", .level_env = "SYSEXT_LEVEL", .scope_env = "SYSEXT_SCOPE", .name_env = "SYSTEMD_SYSEXT_HIERARCHIES", + .mode_env = "SYSTEMD_SYSEXT_MUTABLE_MODE", .default_image_policy = &image_policy_sysext, .default_mount_flags = MS_RDONLY|MS_NODEV, }, [IMAGE_CONFEXT] = { - .dot_directory_name = ".systemd-confext", - .directory_name = "systemd-confext", + .full_identifier = "systemd-confext", .short_identifier = "confext", .short_identifier_plural = "confexts", + .blurb = "Merge configuration extension images into /etc/.", + .dot_directory_name = ".systemd-confext", .level_env = "CONFEXT_LEVEL", .scope_env = "CONFEXT_SCOPE", .name_env = "SYSTEMD_CONFEXT_HIERARCHIES", + .mode_env = "SYSTEMD_CONFEXT_MUTABLE_MODE", .default_image_policy = &image_policy_confext, .default_mount_flags = MS_RDONLY|MS_NODEV|MS_NOSUID|MS_NOEXEC, } }; +static int parse_mutable_mode(const char *p) { + return mutable_mode_from_string(p); +} + static int is_our_mount_point( ImageClass image_class, const char *p) { @@ -113,7 +153,7 @@ static int is_our_mount_point( assert(p); - r = path_is_mount_point(p, NULL, 0); + r = path_is_mount_point(p); if (r == -ENOENT) { log_debug_errno(r, "Hierarchy '%s' doesn't exist.", p); return false; @@ -150,7 +190,7 @@ static int is_our_mount_point( return log_error_errno(r, "Failed to parse device major/minor stored in '%s/dev' file on '%s': %m", image_class_info[image_class].dot_directory_name, p); if (lstat(p, &st) < 0) - return log_error_errno(r, "Failed to stat %s: %m", p); + return log_error_errno(errno, "Failed to stat %s: %m", p); if (st.st_dev != dev) { log_debug("Hierarchy '%s' reports a different device major/minor than what we are seeing, assuming offline copy.", p); @@ -248,11 +288,22 @@ static int unmerge_hierarchy( ImageClass image_class, const char *p) { + _cleanup_free_ char *dot_dir = NULL, *work_dir_info_file = NULL; int r; assert(p); + dot_dir = path_join(p, image_class_info[image_class].dot_directory_name); + if (!dot_dir) + return log_oom(); + + work_dir_info_file = path_join(dot_dir, "work_dir"); + if (!work_dir_info_file) + return log_oom(); + for (;;) { + _cleanup_free_ char *escaped_work_dir_in_root = NULL, *work_dir = NULL; + /* We only unmount /usr/ if it is a mount point and really one of ours, in order not to break * systems where /usr/ is a mount point of its own already. */ @@ -262,9 +313,40 @@ static int unmerge_hierarchy( if (r == 0) break; + r = read_one_line_file(work_dir_info_file, &escaped_work_dir_in_root); + if (r < 0) { + if (r != -ENOENT) + return log_error_errno(r, "Failed to read '%s': %m", work_dir_info_file); + } else { + _cleanup_free_ char *work_dir_in_root = NULL; + ssize_t l; + + l = cunescape_length(escaped_work_dir_in_root, r, 0, &work_dir_in_root); + if (l < 0) + return log_error_errno(l, "Failed to unescape work directory path: %m"); + work_dir = path_join(arg_root, work_dir_in_root); + if (!work_dir) + return log_oom(); + } + + r = umount_verbose(LOG_DEBUG, dot_dir, MNT_DETACH|UMOUNT_NOFOLLOW); + if (r < 0) { + /* EINVAL is possibly "not a mount point". Let it slide as it's expected to occur if + * the whole hierarchy was read-only, so the dot directory inside it was not + * bind-mounted as read-only. */ + if (r != -EINVAL) + return log_error_errno(r, "Failed to unmount '%s': %m", dot_dir); + } + r = umount_verbose(LOG_ERR, p, MNT_DETACH|UMOUNT_NOFOLLOW); if (r < 0) - return log_error_errno(r, "Failed to unmount file system '%s': %m", p); + return r; + + if (work_dir) { + r = rm_rf(work_dir, REMOVE_ROOT | REMOVE_MISSING_OK | REMOVE_PHYSICAL); + if (r < 0) + return log_error_errno(r, "Failed to remove '%s': %m", work_dir); + } log_info("Unmerged '%s'.", p); } @@ -342,7 +424,7 @@ static int parse_image_class_parameter(Varlink *link, const char *value, ImageCl c = image_class_from_string(value); if (!IN_SET(c, IMAGE_SYSEXT, IMAGE_CONFEXT)) - return varlink_errorb(link, VARLINK_ERROR_INVALID_PARAMETER, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("parameter", "class"))); + return varlink_error_invalid_parameter_name(link, "class"); if (hierarchies) { r = parse_env_extension_hierarchies(&h, image_class_info[c].name_env); @@ -448,7 +530,7 @@ static int verb_status(int argc, char **argv, void *userdata) { return log_oom(); if (stat(*p, &st) < 0) - return log_error_errno(r, "Failed to stat() '%s': %m", *p); + return log_error_errno(errno, "Failed to stat() '%s': %m", *p); r = table_add_many( t, @@ -474,11 +556,38 @@ static int verb_status(int argc, char **argv, void *userdata) { return ret; } +static int append_overlayfs_path_option( + char **options, + const char *separator, + const char *option, + const char *path) { + + _cleanup_free_ char *escaped = NULL; + + assert(options); + assert(separator); + assert(path); + + escaped = shell_escape(path, ",:"); + if (!escaped) + return log_oom(); + + if (option) { + if (!strextend(options, separator, option, "=", escaped)) + return log_oom(); + } else if (!strextend(options, separator, escaped)) + return log_oom(); + + return 0; +} + static int mount_overlayfs( ImageClass image_class, int noexec, const char *where, - char **layers) { + char **layers, + const char *upper_dir, + const char *work_dir) { _cleanup_free_ char *options = NULL; bool separator = false; @@ -486,20 +595,16 @@ static int mount_overlayfs( int r; assert(where); + assert((upper_dir && work_dir) || (!upper_dir && !work_dir)); options = strdup("lowerdir="); if (!options) return log_oom(); STRV_FOREACH(l, layers) { - _cleanup_free_ char *escaped = NULL; - - escaped = shell_escape(*l, ",:"); - if (!escaped) - return log_oom(); - - if (!strextend(&options, separator ? ":" : "", escaped)) - return log_oom(); + r = append_overlayfs_path_option(&options, separator ? ":" : "", NULL, *l); + if (r < 0) + return r; separator = true; } @@ -508,6 +613,22 @@ static int mount_overlayfs( if (noexec >= 0) SET_FLAG(flags, MS_NOEXEC, noexec); + if (upper_dir && work_dir) { + r = append_overlayfs_path_option(&options, ",", "upperdir", upper_dir); + if (r < 0) + return r; + + flags &= ~MS_RDONLY; + + r = append_overlayfs_path_option(&options, ",", "workdir", work_dir); + if (r < 0) + return r; + /* redirect_dir=on and noatime prevent unnecessary upcopies, metacopy=off prevents broken + * files from partial upcopies after umount. */ + if (!strextend(&options, ",redirect_dir=on,noatime,metacopy=off")) + return log_oom(); + } + /* Now mount the actual overlayfs */ r = mount_nofollow_verbose(LOG_ERR, image_class_info[image_class].short_identifier, where, "overlay", flags, options); if (r < 0) @@ -516,62 +637,276 @@ static int mount_overlayfs( return 0; } -static int merge_hierarchy( - ImageClass image_class, +static char *hierarchy_as_single_path_component(const char *hierarchy) { + /* We normally expect hierarchy to be /usr, /opt or /etc, but for debugging purposes the hierarchy + * could very well be like /foo/bar/baz/. So for a given hierarchy we generate a directory name by + * stripping the leading and trailing separators and replacing the rest of separators with dots. This + * makes the generated name to be the same for /foo/bar/baz and for /foo/bar.baz, but, again, + * specifying a different hierarchy is a debugging feature, so non-unique mapping should not be an + * issue in general case. */ + const char *stripped = hierarchy; + _cleanup_free_ char *dir_name = NULL; + + assert(hierarchy); + + stripped += strspn(stripped, "/"); + + dir_name = strdup(stripped); + if (!dir_name) + return NULL; + delete_trailing_chars(dir_name, "/"); + string_replace_char(dir_name, '/', '.'); + return TAKE_PTR(dir_name); +} + +static int paths_on_same_fs(const char *path1, const char *path2) { + struct stat st1, st2; + + assert(path1); + assert(path2); + + if (stat(path1, &st1) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", path1); + + if (stat(path2, &st2) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", path2); + + return st1.st_dev == st2.st_dev; +} + +static int work_dir_for_hierarchy( const char *hierarchy, - int noexec, - char **extensions, - char **paths, - const char *meta_path, - const char *overlay_path) { + const char *resolved_upper_dir, + char **ret_work_dir) { + + _cleanup_free_ char *parent = NULL; + int r; + + assert(hierarchy); + assert(resolved_upper_dir); + assert(ret_work_dir); + + r = path_extract_directory(resolved_upper_dir, &parent); + if (r < 0) + return log_error_errno(r, "Failed to get parent directory of upperdir '%s': %m", resolved_upper_dir); + + /* TODO: paths_in_same_superblock? partition? device? */ + r = paths_on_same_fs(resolved_upper_dir, parent); + if (r < 0) + return r; + if (!r) + return log_error_errno(SYNTHETIC_ERRNO(EXDEV), "Unable to find a suitable workdir location for upperdir '%s' for host hierarchy '%s' - parent directory of the upperdir is in a different filesystem", resolved_upper_dir, hierarchy); + + _cleanup_free_ char *f = NULL, *dir_name = NULL; + + f = hierarchy_as_single_path_component(hierarchy); + if (!f) + return log_oom(); + dir_name = strjoin(".systemd-", f, "-workdir"); + if (!dir_name) + return log_oom(); + + free(f); + f = path_join(parent, dir_name); + if (!f) + return log_oom(); + + *ret_work_dir = TAKE_PTR(f); + return 0; +} + +typedef struct OverlayFSPaths { + char *hierarchy; + mode_t hierarchy_mode; + char *resolved_hierarchy; + char *resolved_mutable_directory; + + /* NULL if merged fs is read-only */ + char *upper_dir; + /* NULL if merged fs is read-only */ + char *work_dir; + /* lowest index is top lowerdir, highest index is bottom lowerdir */ + char **lower_dirs; +} OverlayFSPaths; + +static OverlayFSPaths *overlayfs_paths_free(OverlayFSPaths *op) { + if (!op) + return NULL; + + free(op->hierarchy); + free(op->resolved_hierarchy); + free(op->resolved_mutable_directory); + + free(op->upper_dir); + free(op->work_dir); + strv_free(op->lower_dirs); + + return mfree(op); +} +DEFINE_TRIVIAL_CLEANUP_FUNC(OverlayFSPaths *, overlayfs_paths_free); + +static int resolve_hierarchy(const char *hierarchy, char **ret_resolved_hierarchy) { + _cleanup_free_ char *resolved_path = NULL; + int r; + + assert(hierarchy); + assert(ret_resolved_hierarchy); + + r = chase(hierarchy, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to resolve hierarchy '%s': %m", hierarchy); + + *ret_resolved_hierarchy = TAKE_PTR(resolved_path); + return 0; +} + +static int mutable_directory_mode_matches_hierarchy( + const char *root_or_null, + const char *path, + mode_t hierarchy_mode) { - _cleanup_free_ char *resolved_hierarchy = NULL, *f = NULL, *buf = NULL; - _cleanup_strv_free_ char **layers = NULL; + _cleanup_free_ char *path_in_root = NULL; struct stat st; + mode_t actual_mode; + + assert(path); + + path_in_root = path_join(root_or_null, path); + if (!path_in_root) + return log_oom(); + + if (stat(path_in_root, &st) < 0) { + if (errno == ENOENT) + return 0; + return log_error_errno(errno, "Failed to stat mutable directory '%s': %m", path_in_root); + } + + actual_mode = st.st_mode & 0777; + if (actual_mode != hierarchy_mode) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Mutable directory '%s' has mode %04o, ought to have mode %04o", path_in_root, actual_mode, hierarchy_mode); + + return 0; +} + +static int resolve_mutable_directory( + const char *hierarchy, + mode_t hierarchy_mode, + const char *workspace, + char **ret_resolved_mutable_directory) { + + _cleanup_free_ char *path = NULL, *resolved_path = NULL, *dir_name = NULL; + const char *root = arg_root, *base = MUTABLE_EXTENSIONS_BASE_DIR; int r; assert(hierarchy); - assert(meta_path); - assert(overlay_path); + assert(ret_resolved_mutable_directory); - /* 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(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) - return log_error_errno(r, "Failed to resolve host hierarchy '%s': %m", hierarchy); - else { - r = dir_is_empty(resolved_hierarchy, /* ignore_hidden_or_backup= */ false); - if (r < 0) - return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", resolved_hierarchy); - if (r > 0) { - log_debug("Host hierarchy '%s' is empty, not merging.", resolved_hierarchy); - resolved_hierarchy = mfree(resolved_hierarchy); - } + if (arg_mutable == MUTABLE_NO) { + log_debug("Mutability for hierarchy '%s' is disabled, not resolving mutable directory.", hierarchy); + *ret_resolved_mutable_directory = NULL; + return 0; } - /* Let's generate a metadata file that lists all extensions we took into account for this - * hierarchy. We include this in the final fs, to make things nicely discoverable and - * recognizable. */ - f = path_join(meta_path, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural); - if (!f) + if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { + /* We create mutable directory inside the temporary tmpfs workspace, which is a fixed + * location that ignores arg_root. */ + root = NULL; + base = workspace; + } + + dir_name = hierarchy_as_single_path_component(hierarchy); + if (!dir_name) return log_oom(); - buf = strv_join(extensions, "\n"); - if (!buf) + path = path_join(base, dir_name); + if (!path) return log_oom(); - r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755); + if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_AUTO)) { + /* If there already is a mutable directory, check if its mode matches hierarchy. Merged + * hierarchy will have the same mode as the mutable directory, so we want no surprising mode + * changes here. */ + r = mutable_directory_mode_matches_hierarchy(root, path, hierarchy_mode); + if (r < 0) + return r; + } + + if (IN_SET(arg_mutable, MUTABLE_YES, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) { + _cleanup_free_ char *path_in_root = NULL; + + path_in_root = path_join(root, path); + if (!path_in_root) + return log_oom(); + + r = mkdir_p(path_in_root, 0700); + if (r < 0) + return log_error_errno(r, "Failed to create a directory '%s': %m", path_in_root); + } + + r = chase(path, root, CHASE_PREFIX_ROOT, &resolved_path, NULL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path); + + *ret_resolved_mutable_directory = TAKE_PTR(resolved_path); + return 0; +} + +static int overlayfs_paths_new(const char *hierarchy, const char *workspace_path, OverlayFSPaths **ret_op) { + _cleanup_free_ char *hierarchy_copy = NULL, *resolved_hierarchy = NULL, *resolved_mutable_directory = NULL; + mode_t hierarchy_mode; + + int r; + + assert (hierarchy); + assert (ret_op); + + hierarchy_copy = strdup(hierarchy); + if (!hierarchy_copy) + return log_oom(); + + r = resolve_hierarchy(hierarchy, &resolved_hierarchy); if (r < 0) - return log_error_errno(r, "Failed to write extension meta file '%s': %m", f); + return r; - /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */ - layers = strv_new(meta_path); - if (!layers) + if (resolved_hierarchy) { + struct stat st; + + if (stat(resolved_hierarchy, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", resolved_hierarchy); + hierarchy_mode = st.st_mode & 0777; + } else + hierarchy_mode = 0755; + + r = resolve_mutable_directory(hierarchy, hierarchy_mode, workspace_path, &resolved_mutable_directory); + if (r < 0) + return r; + + OverlayFSPaths *op; + op = new(OverlayFSPaths, 1); + if (!op) return log_oom(); - /* Put the extensions in the middle */ + *op = (OverlayFSPaths) { + .hierarchy = TAKE_PTR(hierarchy_copy), + .hierarchy_mode = hierarchy_mode, + .resolved_hierarchy = TAKE_PTR(resolved_hierarchy), + .resolved_mutable_directory = TAKE_PTR(resolved_mutable_directory), + }; + + *ret_op = TAKE_PTR(op); + return 0; +} + +static int determine_used_extensions(const char *hierarchy, char **paths, char ***ret_used_paths, size_t *ret_extensions_used) { + _cleanup_strv_free_ char **used_paths = NULL; + size_t n = 0; + int r; + + assert(hierarchy); + assert(paths); + assert(ret_used_paths); + assert(ret_extensions_used); + STRV_FOREACH(p, paths) { _cleanup_free_ char *resolved = NULL; @@ -591,54 +926,514 @@ static int merge_hierarchy( continue; } - r = strv_consume(&layers, TAKE_PTR(resolved)); + r = strv_consume_with_size (&used_paths, &n, TAKE_PTR(resolved)); if (r < 0) return log_oom(); } - if (!layers[1]) /* No extension with files in this hierarchy? Then don't do anything. */ + *ret_used_paths = TAKE_PTR(used_paths); + *ret_extensions_used = n; + return 0; +} + +static int maybe_import_mutable_directory(OverlayFSPaths *op) { + int r; + + assert(op); + + /* If importing mutable layer and it actually exists and is not a hierarchy itself, add it just below + * the meta path */ + + if (arg_mutable != MUTABLE_IMPORT || !op->resolved_mutable_directory) return 0; - if (resolved_hierarchy) { - /* Add the host hierarchy as last (lowest) layer in the stack */ - r = strv_consume(&layers, TAKE_PTR(resolved_hierarchy)); + r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0); + if (r < 0) + return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory); + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); + + r = strv_extend(&op->lower_dirs, op->resolved_mutable_directory); + if (r < 0) + return log_oom(); + + return 0; +} + +static int maybe_import_ignored_mutable_directory(OverlayFSPaths *op) { + _cleanup_free_ char *dir_name = NULL, *path = NULL, *resolved_path = NULL; + int r; + + assert(op); + + /* If importing the ignored mutable layer and it actually exists and is not a hierarchy itself, add + * it just below the meta path */ + if (arg_mutable != MUTABLE_EPHEMERAL_IMPORT) + return 0; + + dir_name = hierarchy_as_single_path_component(op->hierarchy); + if (!dir_name) + return log_oom(); + + path = path_join(MUTABLE_EXTENSIONS_BASE_DIR, dir_name); + if (!path) + return log_oom(); + + r = chase(path, arg_root, CHASE_PREFIX_ROOT, &resolved_path, NULL); + if (r == -ENOENT) { + log_debug("Mutable directory for %s does not exist, not importing", op->hierarchy); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to resolve mutable directory '%s': %m", path); + + r = path_equal_or_inode_same_full(op->resolved_hierarchy, resolved_path, 0); + if (r < 0) + return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory); + + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(ELOOP), "Not importing mutable directory for hierarchy %s as a lower dir, because it points to the hierarchy itself", op->hierarchy); + + r = strv_consume(&op->lower_dirs, TAKE_PTR(resolved_path)); + if (r < 0) + return log_oom(); + + return 0; +} + +static int determine_top_lower_dirs(OverlayFSPaths *op, const char *meta_path) { + int r; + + assert(op); + assert(meta_path); + + /* Put the meta path (i.e. our synthesized stuff) at the top of the layer stack */ + r = strv_extend(&op->lower_dirs, meta_path); + if (r < 0) + return log_oom(); + + r = maybe_import_mutable_directory(op); + if (r < 0) + return r; + + r = maybe_import_ignored_mutable_directory(op); + if (r < 0) + return r; + + return 0; +} + +static int determine_middle_lower_dirs(OverlayFSPaths *op, char **paths) { + int r; + + assert(op); + assert(paths); + + /* The paths were already determined in determine_used_extensions, so we just take them as is. */ + r = strv_extend_strv(&op->lower_dirs, paths, false); + if (r < 0) + return log_oom (); + + return 0; +} + +static int hierarchy_as_lower_dir(OverlayFSPaths *op) { + int r; + + /* return 0 if hierarchy should be used as lower dir, >0, if not */ + + assert(op); + + if (!op->resolved_hierarchy) { + log_debug("Host hierarchy '%s' does not exist, will not be used as lowerdir", op->hierarchy); + return 1; + } + + r = dir_is_empty(op->resolved_hierarchy, /* ignore_hidden_or_backup= */ false); + if (r < 0) + return log_error_errno(r, "Failed to check if host hierarchy '%s' is empty: %m", op->resolved_hierarchy); + if (r > 0) { + log_debug("Host hierarchy '%s' is empty, will not be used as lower dir.", op->resolved_hierarchy); + return 1; + } + + if (arg_mutable == MUTABLE_IMPORT) { + log_debug("Mutability for host hierarchy '%s' is disabled, so host hierarchy will be a lowerdir", op->resolved_hierarchy); + return 0; + } + + if (arg_mutable == MUTABLE_EPHEMERAL_IMPORT) { + log_debug("Mutability for host hierarchy '%s' is ephemeral, so host hierarchy will be a lowerdir", op->resolved_hierarchy); + return 0; + } + + if (!op->resolved_mutable_directory) { + log_debug("No mutable directory found, so host hierarchy '%s' will be used as lowerdir", op->resolved_hierarchy); + return 0; + } + + r = path_equal_or_inode_same_full(op->resolved_hierarchy, op->resolved_mutable_directory, 0); + if (r < 0) + return log_error_errno(r, "Failed to check equality of hierarchy %s and its mutable directory %s: %m", op->resolved_hierarchy, op->resolved_mutable_directory); + if (r > 0) { + log_debug("Host hierarchy '%s' will serve as upperdir.", op->resolved_hierarchy); + return 1; + } + + return 0; +} + +static int determine_bottom_lower_dirs(OverlayFSPaths *op) { + int r; + + assert(op); + + r = hierarchy_as_lower_dir(op); + if (r < 0) + return r; + if (!r) { + r = strv_extend(&op->lower_dirs, op->resolved_hierarchy); if (r < 0) return log_oom(); } + return 0; +} + +static int determine_lower_dirs( + OverlayFSPaths *op, + char **paths, + const char *meta_path) { + + int r; + + assert(op); + assert(paths); + assert(meta_path); + + r = determine_top_lower_dirs(op, meta_path); + if (r < 0) + return r; + + r = determine_middle_lower_dirs(op, paths); + if (r < 0) + return r; + + r = determine_bottom_lower_dirs(op); + if (r < 0) + return r; + + return 0; +} + +static int determine_upper_dir(OverlayFSPaths *op) { + int r; + + assert(op); + assert(!op->upper_dir); + + if (arg_mutable == MUTABLE_IMPORT) { + log_debug("Mutability is disabled, there will be no upperdir for host hierarchy '%s'", op->hierarchy); + return 0; + } + + if (!op->resolved_mutable_directory) { + log_debug("No mutable directory found for host hierarchy '%s', there will be no upperdir", op->hierarchy); + return 0; + } + + /* Require upper dir to be on writable filesystem if it's going to be used as an actual overlayfs + * upperdir, instead of a lowerdir as an imported path. */ + r = path_is_read_only_fs(op->resolved_mutable_directory); + if (r < 0) + return log_error_errno(r, "Failed to determine if mutable directory '%s' is on read-only filesystem: %m", op->resolved_mutable_directory); + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(EROFS), "Can't use '%s' as an upperdir as it is read-only.", op->resolved_mutable_directory); + + op->upper_dir = strdup(op->resolved_mutable_directory); + if (!op->upper_dir) + return log_oom(); + + return 0; +} + +static int determine_work_dir(OverlayFSPaths *op) { + _cleanup_free_ char *work_dir = NULL; + int r; + + assert(op); + assert(!op->work_dir); + + if (!op->upper_dir) + return 0; + + if (arg_mutable == MUTABLE_IMPORT) + return 0; + + r = work_dir_for_hierarchy(op->hierarchy, op->upper_dir, &work_dir); + if (r < 0) + return r; + + op->work_dir = TAKE_PTR(work_dir); + return 0; +} + +static int mount_overlayfs_with_op( + OverlayFSPaths *op, + ImageClass image_class, + int noexec, + const char *overlay_path, + const char *meta_path) { + + int r; + const char *top_layer = NULL; + + assert(op); + assert(overlay_path); + r = mkdir_p(overlay_path, 0700); if (r < 0) return log_error_errno(r, "Failed to make directory '%s': %m", overlay_path); - r = mount_overlayfs(image_class, noexec, overlay_path, layers); + r = mkdir_p(meta_path, 0700); + if (r < 0) + return log_error_errno(r, "Failed to make directory '%s': %m", meta_path); + + if (op->upper_dir && op->work_dir) { + r = mkdir_p(op->work_dir, 0700); + if (r < 0) + return log_error_errno(r, "Failed to make directory '%s': %m", op->work_dir); + top_layer = op->upper_dir; + } else { + assert(!strv_isempty(op->lower_dirs)); + top_layer = op->lower_dirs[0]; + } + + /* Overlayfs merged directory has the same mode as the top layer (either first lowerdir in options in + * read-only case, or upperdir for mutable case. Set up top overlayfs layer to the same mode as the + * unmerged hierarchy, otherwise we might end up with merged hierarchy owned by root and with mode + * being 0700. */ + if (chmod(top_layer, op->hierarchy_mode) < 0) + return log_error_errno(errno, "Failed to set permissions of '%s' to %04o: %m", top_layer, op->hierarchy_mode); + + r = mount_overlayfs(image_class, noexec, overlay_path, op->lower_dirs, op->upper_dir, op->work_dir); if (r < 0) return r; - /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra turbo safety 😎 */ - r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL); + return 0; +} + +static int write_extensions_file(ImageClass image_class, char **extensions, const char *meta_path) { + _cleanup_free_ char *f = NULL, *buf = NULL; + int r; + + assert(extensions); + assert(meta_path); + + /* 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, image_class_info[image_class].dot_directory_name, image_class_info[image_class].short_identifier_plural); + if (!f) + return log_oom(); + + buf = strv_join(extensions, "\n"); + if (!buf) + return log_oom(); + + r = write_string_file(f, buf, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755); if (r < 0) - return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path); + return log_error_errno(r, "Failed to write extension meta file '%s': %m", f); + + return 0; +} + +static int write_dev_file(ImageClass image_class, const char *meta_path, const char *overlay_path) { + _cleanup_free_ char *f = NULL; + struct stat st; + int r; + + assert(meta_path); + assert(overlay_path); /* 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 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); + return log_error_errno(errno, "Failed to stat mount '%s': %m", overlay_path); - free(f); f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "dev"); if (!f) return log_oom(); + /* Modifying the underlying layers while the overlayfs is mounted is technically undefined, but at + * least it won't crash or deadlock, as per the kernel docs about overlayfs: + * https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html#changes-to-underlying-filesystems */ r = write_string_file(f, FORMAT_DEVNUM(st.st_dev), WRITE_STRING_FILE_CREATE); if (r < 0) return log_error_errno(r, "Failed to write '%s': %m", f); + return 0; +} + +static int write_work_dir_file(ImageClass image_class, const char *meta_path, const char *work_dir) { + _cleanup_free_ char *escaped_work_dir_in_root = NULL, *f = NULL; + char *work_dir_in_root = NULL; + int r; + + assert(meta_path); + + if (!work_dir) + return 0; + + /* Do not store work dir path for ephemeral mode, it will be gone once this process is done. */ + if (IN_SET(arg_mutable, MUTABLE_EPHEMERAL, MUTABLE_EPHEMERAL_IMPORT)) + return 0; + + work_dir_in_root = path_startswith(work_dir, empty_to_root(arg_root)); + if (!work_dir_in_root) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Workdir '%s' must not be outside root '%s'", work_dir, empty_to_root(arg_root)); + + f = path_join(meta_path, image_class_info[image_class].dot_directory_name, "work_dir"); + if (!f) + return log_oom(); + + /* Paths can have newlines for whatever reason, so better escape them to really get a single + * line file. */ + escaped_work_dir_in_root = cescape(work_dir_in_root); + if (!escaped_work_dir_in_root) + return log_oom(); + r = write_string_file(f, escaped_work_dir_in_root, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write '%s': %m", f); + + return 0; +} + +static int store_info_in_meta( + ImageClass image_class, + char **extensions, + const char *meta_path, + const char *overlay_path, + const char *work_dir) { + + int r; + + assert(extensions); + assert(meta_path); + assert(overlay_path); + /* work_dir may be NULL */ + + r = write_extensions_file(image_class, extensions, meta_path); + if (r < 0) + return r; + + r = write_dev_file(image_class, meta_path, overlay_path); + if (r < 0) + return r; + + r = write_work_dir_file(image_class, meta_path, work_dir); + if (r < 0) + return r; + /* Make sure the top-level dir has an mtime marking the point we established the merge */ if (utimensat(AT_FDCWD, meta_path, NULL, AT_SYMLINK_NOFOLLOW) < 0) return log_error_errno(r, "Failed fix mtime of '%s': %m", meta_path); + return 0; +} + +static int make_mounts_read_only(ImageClass image_class, const char *overlay_path, bool mutable) { + int r; + + assert(overlay_path); + + if (mutable) { + /* Bind mount the meta path as read-only on mutable overlays to avoid accidental + * modifications of the contents of meta directory, which could lead to systemd thinking that + * this hierarchy is not our mount. */ + _cleanup_free_ char *f = NULL; + + f = path_join(overlay_path, image_class_info[image_class].dot_directory_name); + if (!f) + return log_oom(); + + r = mount_nofollow_verbose(LOG_ERR, f, f, NULL, MS_BIND, NULL); + if (r < 0) + return r; + + r = bind_remount_one(f, MS_RDONLY, MS_RDONLY); + if (r < 0) + return log_error_errno(r, "Failed to remount '%s' as read-only: %m", f); + } else { + /* The overlayfs superblock is read-only. Let's also mark the bind mount read-only. Extra + * turbo safety 😎 */ + r = bind_remount_recursive(overlay_path, MS_RDONLY, MS_RDONLY, NULL); + if (r < 0) + return log_error_errno(r, "Failed to make bind mount '%s' read-only: %m", overlay_path); + } + + return 0; +} + +static int merge_hierarchy( + ImageClass image_class, + const char *hierarchy, + int noexec, + char **extensions, + char **paths, + const char *meta_path, + const char *overlay_path, + const char *workspace_path) { + + _cleanup_(overlayfs_paths_freep) OverlayFSPaths *op = NULL; + _cleanup_strv_free_ char **used_paths = NULL; + size_t extensions_used = 0; + int r; + + assert(hierarchy); + assert(extensions); + assert(paths); + assert(meta_path); + assert(overlay_path); + assert(workspace_path); + + r = determine_used_extensions(hierarchy, paths, &used_paths, &extensions_used); + if (r < 0) + return r; + + if (extensions_used == 0) /* No extension with files in this hierarchy? Then don't do anything. */ + return 0; + + r = overlayfs_paths_new(hierarchy, workspace_path, &op); + if (r < 0) + return r; + + r = determine_lower_dirs(op, used_paths, meta_path); + if (r < 0) + return r; + + r = determine_upper_dir(op); + if (r < 0) + return r; + + r = determine_work_dir(op); + if (r < 0) + return r; + + r = mount_overlayfs_with_op(op, image_class, noexec, overlay_path, meta_path); + if (r < 0) + return r; + + r = store_info_in_meta(image_class, extensions, meta_path, overlay_path, op->work_dir); + if (r < 0) + return r; + + r = make_mounts_read_only(image_class, overlay_path, op->upper_dir && op->work_dir); + if (r < 0) + return r; + return 1; } @@ -659,8 +1454,16 @@ static const ImagePolicy *pick_image_policy(const Image *img) { * 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; + if (in_initrd()) { + if (path_startswith(img->path, "/.extra/sysext/")) + return &image_policy_sysext_strict; + if (path_startswith(img->path, "/.extra/confext/")) + return &image_policy_confext_strict; + + /* Better safe than sorry, refuse everything else passed in via the untrusted /.extra/ dir */ + if (path_startswith(img->path, "/.extra/")) + return &image_policy_deny; + } return image_class_info[img->class].default_image_policy; } @@ -761,7 +1564,8 @@ static int merge_subprocess( DISSECT_IMAGE_MOUNT_ROOT_ONLY | DISSECT_IMAGE_USR_NO_ROOT | DISSECT_IMAGE_ADD_PARTITION_DEVICES | - DISSECT_IMAGE_PIN_PARTITION_DEVICES; + DISSECT_IMAGE_PIN_PARTITION_DEVICES | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY; r = verity_settings_load(&verity_settings, img->path, NULL, NULL); if (r < 0) @@ -854,7 +1658,7 @@ static int merge_subprocess( if (r < 0) return log_oom(); - n_extensions ++; + n_extensions++; } /* Nothing left? Then shortcut things */ @@ -908,7 +1712,7 @@ static int merge_subprocess( /* Create overlayfs mounts for all hierarchies */ STRV_FOREACH(h, hierarchies) { - _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL; + _cleanup_free_ char *meta_path = NULL, *overlay_path = NULL, *merge_hierarchy_workspace = NULL; meta_path = path_join(workspace, "meta", *h); /* The place where to store metadata about this instance */ if (!meta_path) @@ -918,6 +1722,11 @@ static int merge_subprocess( if (!overlay_path) return log_oom(); + /* Temporary directory for merge_hierarchy needs, like ephemeral directories. */ + merge_hierarchy_workspace = path_join(workspace, "mh_workspace", *h); + if (!merge_hierarchy_workspace) + return log_oom(); + r = merge_hierarchy( image_class, *h, @@ -925,7 +1734,8 @@ static int merge_subprocess( extensions, paths, meta_path, - overlay_path); + overlay_path, + merge_hierarchy_workspace); if (r < 0) return r; } @@ -954,7 +1764,8 @@ static int merge_subprocess( if (r < 0) return log_error_errno(r, "Failed to create hierarchy mount point '%s': %m", resolved); - r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND, NULL); + /* Using MS_REC to potentially bring in our read-only bind mount of metadata. */ + r = mount_nofollow_verbose(LOG_ERR, p, resolved, NULL, MS_BIND|MS_REC, NULL); if (r < 0) return r; @@ -992,9 +1803,10 @@ static int merge(ImageClass image_class, r = wait_for_terminate_and_check("(sd-merge)", pid, WAIT_LOG_ABNORMAL); if (r < 0) return r; - if (r == 123) /* exit code 123 means: didn't do anything */ return 0; + if (r > 0) + return log_error_errno(SYNTHETIC_ERRNO(EPROTO), "Failed to merge hierarchies"); r = need_reload(image_class, hierarchies, no_reload); if (r < 0) @@ -1110,9 +1922,9 @@ static int parse_merge_parameters(Varlink *link, JsonVariant *parameters, Method static const JsonDispatch dispatch_table[] = { { "class", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(MethodMergeParameters, class), 0 }, - { "force", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, force), 0 }, - { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, no_reload), 0 }, - { "noexec", JSON_VARIANT_BOOLEAN, json_dispatch_boolean, offsetof(MethodMergeParameters, noexec), 0 }, + { "force", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MethodMergeParameters, force), 0 }, + { "noReload", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MethodMergeParameters, no_reload), 0 }, + { "noexec", JSON_VARIANT_BOOLEAN, json_dispatch_tristate, offsetof(MethodMergeParameters, noexec), 0 }, {} }; @@ -1207,7 +2019,7 @@ static int refresh( * 4. If there was no overlayfs mount so far, and no extensions installed, we implement a NOP. */ - return 0; + return r; } static int verb_refresh(int argc, char **argv, void *userdata) { @@ -1358,13 +2170,13 @@ static int verb_help(int argc, char **argv, void *userdata) { _cleanup_free_ char *link = NULL; int r; - r = terminal_urlify_man("systemd-sysext", "8", &link); + r = terminal_urlify_man(image_class_info[arg_image_class].full_identifier, "8", &link); if (r < 0) return log_oom(); printf("%1$s [OPTIONS...] COMMAND\n" - "\n%5$sMerge extension images into /usr/ and /opt/ hierarchies for\n" - " sysext and into the /etc/ hierarchy for confext.%6$s\n" + "\n%5$s%7$s%6$s\n" + "\n%3$sCommands:%4$s\n" " status Show current merge status (default)\n" " merge Merge extensions into relevant hierarchies\n" " unmerge Unmerge extensions from relevant hierarchies\n" @@ -1373,6 +2185,8 @@ static int verb_help(int argc, char **argv, void *userdata) { " -h --help Show this help\n" " --version Show package version\n" "\n%3$sOptions:%4$s\n" + " --mutable=yes|no|auto|import|ephemeral|ephemeral-import\n" + " Specify a mutability mode of the merged hierarchy\n" " --no-pager Do not pipe output into a pager\n" " --no-legend Do not show the headers and footers\n" " --root=PATH Operate relative to root path\n" @@ -1389,7 +2203,8 @@ static int verb_help(int argc, char **argv, void *userdata) { ansi_underline(), ansi_normal(), ansi_highlight(), - ansi_normal()); + ansi_normal(), + image_class_info[arg_image_class].blurb); return 0; } @@ -1406,6 +2221,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_POLICY, ARG_NOEXEC, ARG_NO_RELOAD, + ARG_MUTABLE, }; static const struct option options[] = { @@ -1419,6 +2235,7 @@ static int parse_argv(int argc, char *argv[]) { { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, { "noexec", required_argument, NULL, ARG_NOEXEC }, { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "mutable", required_argument, NULL, ARG_MUTABLE }, {} }; @@ -1482,6 +2299,13 @@ static int parse_argv(int argc, char *argv[]) { arg_no_reload = true; break; + case ARG_MUTABLE: + r = parse_mutable_mode(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse argument to --mutable=: %s", optarg); + arg_mutable = r; + break; + case '?': return -EINVAL; @@ -1514,12 +2338,23 @@ static int sysext_main(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { + const char *env_var; int r; log_setup(); arg_image_class = invoked_as(argv, "systemd-confext") ? IMAGE_CONFEXT : IMAGE_SYSEXT; + env_var = getenv(image_class_info[arg_image_class].mode_env); + if (env_var) { + r = parse_mutable_mode(env_var); + if (r < 0) + log_warning("Failed to parse %s environment variable value '%s'. Ignoring.", + image_class_info[arg_image_class].mode_env, env_var); + else + arg_mutable = r; + } + r = parse_argv(argc, argv); if (r <= 0) return r; diff --git a/src/systemctl/fuzz-systemctl-parse-argv.c b/src/systemctl/fuzz-systemctl-parse-argv.c index 9ea8f7a..99cf6c2 100644 --- a/src/systemctl/fuzz-systemctl-parse-argv.c +++ b/src/systemctl/fuzz-systemctl-parse-argv.c @@ -49,7 +49,11 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { opterr = 0; /* do not print errors */ } + /* We need to reset some global state manually here since libfuzzer feeds a single process with + * multiple inputs, so we might carry over state from previous invocations that can trigger + * certain asserts. */ optind = 0; /* this tells the getopt machinery to reinitialize */ + arg_transport = BUS_TRANSPORT_LOCAL; r = systemctl_dispatch_parse_argv(strv_length(argv), argv); if (r < 0) diff --git a/src/systemctl/meson.build b/src/systemctl/meson.build index 255c639..88f73bf 100644 --- a/src/systemctl/meson.build +++ b/src/systemctl/meson.build @@ -44,8 +44,7 @@ if get_option('link-systemctl-shared') systemctl_link_with = [libshared] else systemctl_link_with = [libsystemd_static, - libshared_static, - libbasic_gcrypt] + libshared_static] endif executables += [ @@ -56,10 +55,10 @@ executables += [ 'link_with' : systemctl_link_with, 'dependencies' : [ libcap, - liblz4, + liblz4_cflags, libselinux, - libxz, - libzstd, + libxz_cflags, + libzstd_cflags, threads, ], }, diff --git a/src/systemctl/systemctl-compat-shutdown.c b/src/systemctl/systemctl-compat-shutdown.c index 881d00e..c5b4cb4 100644 --- a/src/systemctl/systemctl-compat-shutdown.c +++ b/src/systemctl/systemctl-compat-shutdown.c @@ -35,7 +35,7 @@ static int shutdown_help(void) { " --no-wall Don't send wall message before halt/power-off/reboot\n" " -c Cancel a pending shutdown\n" " --show Show pending shutdown\n" - "\n%sThis is a compatibility interface, please use the more powerful 'systemctl reboot',\n" + "\n%sThis is a compatibility interface, please use the more powerful 'systemctl halt',\n" "'systemctl poweroff', 'systemctl reboot' commands instead.%s\n" "\nSee the %s for details.\n", program_invocation_short_name, diff --git a/src/systemctl/systemctl-compat-telinit.c b/src/systemctl/systemctl-compat-telinit.c index 20325e5..210d0a1 100644 --- a/src/systemctl/systemctl-compat-telinit.c +++ b/src/systemctl/systemctl-compat-telinit.c @@ -155,11 +155,3 @@ int reload_with_fallback(void) { return 0; } - -int exec_telinit(char *argv[]) { - (void) rlimit_nofile_safe(); - (void) execv(TELINIT, argv); - - return log_error_errno(SYNTHETIC_ERRNO(EIO), - "Couldn't find an alternative telinit implementation to spawn."); -} diff --git a/src/systemctl/systemctl-compat-telinit.h b/src/systemctl/systemctl-compat-telinit.h index 783c387..1a2bcd4 100644 --- a/src/systemctl/systemctl-compat-telinit.h +++ b/src/systemctl/systemctl-compat-telinit.h @@ -4,4 +4,3 @@ int telinit_parse_argv(int argc, char *argv[]); int start_with_fallback(void); int reload_with_fallback(void); -int exec_telinit(char *argv[]); diff --git a/src/systemctl/systemctl-edit.c b/src/systemctl/systemctl-edit.c index 367afa2..15398f8 100644 --- a/src/systemctl/systemctl-edit.c +++ b/src/systemctl/systemctl-edit.c @@ -14,8 +14,8 @@ #include "terminal-util.h" int verb_cat(int argc, char *argv[], void *userdata) { - _cleanup_hashmap_free_ Hashmap *cached_name_map = NULL, *cached_id_map = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_strv_free_ char **names = NULL; sd_bus *bus; bool first = true; @@ -50,7 +50,7 @@ int verb_cat(int argc, char *argv[], void *userdata) { _cleanup_free_ char *fragment_path = NULL; _cleanup_strv_free_ char **dropin_paths = NULL; - r = unit_find_paths(bus, *name, &lp, false, &cached_name_map, &cached_id_map, &fragment_path, &dropin_paths); + r = unit_find_paths(bus, *name, &lp, false, &cached_id_map, &cached_name_map, &fragment_path, &dropin_paths); if (r == -ERFKILL) { printf("%s# Unit %s is masked%s.\n", ansi_highlight_magenta(), @@ -197,8 +197,8 @@ static int find_paths_to_edit( EditFileContext *context, char **names) { - _cleanup_hashmap_free_ Hashmap *cached_name_map = NULL, *cached_id_map = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_hashmap_free_ Hashmap *cached_id_map = NULL, *cached_name_map = NULL; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_free_ char *drop_in_alloc = NULL, *suffix = NULL; const char *drop_in; int r; @@ -233,13 +233,13 @@ static int find_paths_to_edit( _cleanup_free_ char *path = NULL; _cleanup_strv_free_ char **unit_paths = NULL; - r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_name_map, &cached_id_map, &path, &unit_paths); + r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ false, &cached_id_map, &cached_name_map, &path, &unit_paths); if (r == -EKEYREJECTED) { /* If loading of the unit failed server side complete, then the server won't tell us * the unit file path. In that case, find the file client side. */ log_debug_errno(r, "Unit '%s' was not loaded correctly, retrying client-side.", *name); - r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_name_map, &cached_id_map, &path, &unit_paths); + r = unit_find_paths(bus, *name, &lp, /* force_client_side= */ true, &cached_id_map, &cached_name_map, &path, &unit_paths); } if (r == -ERFKILL) return log_error_errno(r, "Unit '%s' masked, cannot edit.", *name); @@ -247,13 +247,12 @@ static int find_paths_to_edit( return r; /* Already logged by unit_find_paths() */ if (!path) { - if (!arg_force) { - log_info("Run 'systemctl edit%s --force --full %s' to create a new unit.", - arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? " --global" : - arg_runtime_scope == RUNTIME_SCOPE_USER ? " --user" : "", - *name); - return -ENOENT; - } + if (!arg_force) + return log_info_errno(SYNTHETIC_ERRNO(ENOENT), + "Run 'systemctl edit%s --force --full %s' to create a new unit.", + arg_runtime_scope == RUNTIME_SCOPE_GLOBAL ? " --global" : + arg_runtime_scope == RUNTIME_SCOPE_USER ? " --user" : "", + *name); /* Create a new unit from scratch */ r = unit_file_create_new( @@ -317,12 +316,13 @@ int verb_edit(int argc, char *argv[], void *userdata) { .marker_end = DROPIN_MARKER_END, .remove_parent = !arg_full, .overwrite_with_origin = true, + .stdin = arg_stdin, }; _cleanup_strv_free_ char **names = NULL; sd_bus *bus; int r; - if (!on_tty()) + if (!on_tty() && !arg_stdin) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot edit units if not on a tty."); if (arg_transport != BUS_TRANSPORT_LOCAL) @@ -342,6 +342,10 @@ int verb_edit(int argc, char *argv[], void *userdata) { if (strv_isempty(names)) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No units matched the specified patterns."); + if (arg_stdin && arg_full && strv_length(names) != 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "With 'edit --stdin --full', exactly one unit for editing must be specified."); + STRV_FOREACH(tmp, names) { r = unit_is_masked(bus, *tmp); if (r < 0) diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c index 7d9b7c7..496a817 100644 --- a/src/systemctl/systemctl-enable.c +++ b/src/systemctl/systemctl-enable.c @@ -66,12 +66,14 @@ int verb_enable(int argc, char *argv[], void *userdata) { const char *verb = argv[0]; int carries_install_info = -1; bool ignore_carries_install_info = arg_quiet || arg_no_warn; + sd_bus *bus = NULL; int r; if (!argv[1]) return 0; - r = mangle_names("to enable", strv_skip(argv, 1), &names); + const char *operation = strjoina("to ", verb); + r = mangle_names(operation, strv_skip(argv, 1), &names); if (r < 0) return r; @@ -140,10 +142,9 @@ int verb_enable(int argc, char *argv[], void *userdata) { bool send_runtime = true, send_force = true, send_preset_mode = false; const char *method, *warn_trigger_operation = NULL; bool warn_trigger_ignore_masked = true; /* suppress "used uninitialized" warning */ - sd_bus *bus; if (STR_IN_SET(verb, "mask", "unmask")) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; r = lookup_paths_init_or_warn(&lp, arg_runtime_scope, 0, arg_root); if (r < 0) @@ -312,25 +313,51 @@ int verb_enable(int argc, char *argv[], void *userdata) { } } - if (arg_now && STR_IN_SET(argv[0], "enable", "disable", "mask")) { - sd_bus *bus; - size_t len, i; + if (arg_now) { + _cleanup_strv_free_ char **new_args = NULL; - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; + if (!STR_IN_SET(verb, "enable", "disable", "mask")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--now can only be used with verb enable, disable, or mask."); + + if (install_client_side()) + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), + "--now cannot be used when systemd is not running or in conjunction with --root=/--global, refusing."); + + assert(bus); + + if (strv_extend(&new_args, streq(verb, "enable") ? "start" : "stop") < 0) + return log_oom(); + + STRV_FOREACH(name, names) { + if (streq(verb, "enable")) { + char *fn; - len = strv_length(names); - { - char *new_args[len + 2]; + /* 'enable' accept path to unit files, so extract it first. Don't try to + * glob them though, as starting globbed unit seldom makes sense and + * actually changes the semantic (we're operating on DefaultInstance= + * when enabling). */ - new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); - for (i = 0; i < len; i++) - new_args[i + 1] = basename(names[i]); - new_args[i + 1] = NULL; + r = path_extract_filename(*name, &fn); + if (r < 0) + return log_error_errno(r, "Failed to extract filename of '%s': %m", *name); + + r = strv_consume(&new_args, fn); + } else if (unit_name_is_valid(*name, UNIT_NAME_TEMPLATE)) { + char *globbed; + + r = unit_name_replace_instance_full(*name, "*", /* accept_glob = */ true, &globbed); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name '%s': %m", *name); - r = verb_start(len + 1, new_args, userdata); + r = strv_consume(&new_args, globbed); + } else + r = strv_extend(&new_args, *name); + if (r < 0) + return log_oom(); } + + return verb_start(strv_length(new_args), new_args, userdata); } return 0; diff --git a/src/systemctl/systemctl-kill.c b/src/systemctl/systemctl-kill.c index c4c6096..4c1829e 100644 --- a/src/systemctl/systemctl-kill.c +++ b/src/systemctl/systemctl-kill.c @@ -2,11 +2,13 @@ #include "bus-error.h" #include "bus-locator.h" +#include "bus-wait-for-units.h" #include "systemctl-kill.h" #include "systemctl-util.h" #include "systemctl.h" int verb_kill(int argc, char *argv[], void *userdata) { + _cleanup_(bus_wait_for_units_freep) BusWaitForUnits *w = NULL; _cleanup_strv_free_ char **names = NULL; const char *kill_whom; sd_bus *bus; @@ -16,6 +18,12 @@ int verb_kill(int argc, char *argv[], void *userdata) { if (r < 0) return r; + if (arg_wait) { + r = bus_wait_for_units_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Failed to allocate unit watch context: %m"); + } + polkit_agent_open_maybe(); kill_whom = arg_kill_whom ?: "all"; @@ -48,11 +56,24 @@ int verb_kill(int argc, char *argv[], void *userdata) { NULL, "ssi", *name, kill_whom, arg_signal); if (q < 0) { - log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q)); - if (r == 0) - r = q; + RET_GATHER(r, log_error_errno(q, "Failed to kill unit %s: %s", *name, bus_error_message(&error, q))); + continue; + } + + if (w) { + q = bus_wait_for_units_add_unit(w, *name, BUS_WAIT_FOR_INACTIVE|BUS_WAIT_NO_JOB, NULL, NULL); + if (q < 0) + RET_GATHER(r, log_error_errno(q, "Failed to watch unit %s: %m", *name)); } } + if (w) { + q = bus_wait_for_units_run(w); + if (q < 0) + return log_error_errno(q, "Failed to wait for units: %m"); + if (q == BUS_WAIT_FAILURE) + RET_GATHER(r, -EIO); + } + return r; } diff --git a/src/systemctl/systemctl-list-jobs.c b/src/systemctl/systemctl-list-jobs.c index a752173..fcfe2ac 100644 --- a/src/systemctl/systemctl-list-jobs.c +++ b/src/systemctl/systemctl-list-jobs.c @@ -102,9 +102,9 @@ static int output_jobs_list(sd_bus *bus, const struct job_info* jobs, unsigned n return table_log_add_error(r); if (arg_jobs_after) - output_waiting_jobs(bus, table, j->id, "GetJobAfter", "\twaiting for job"); + output_waiting_jobs(bus, table, j->id, "GetJobAfter", "\tblocking job"); if (arg_jobs_before) - output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\tblocking job"); + output_waiting_jobs(bus, table, j->id, "GetJobBefore", "\twaiting for job"); } r = table_print(table, NULL); diff --git a/src/systemctl/systemctl-list-unit-files.c b/src/systemctl/systemctl-list-unit-files.c index fc1ad98..b8b1531 100644 --- a/src/systemctl/systemctl-list-unit-files.c +++ b/src/systemctl/systemctl-list-unit-files.c @@ -79,7 +79,7 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) { table_set_ersatz_string(table, TABLE_ERSATZ_DASH); - for (const UnitFileList *u = units; u < units + c; u++) { + FOREACH_ARRAY(u, units, c) { const char *on_underline = NULL, *on_unit_color = NULL, *id; bool underline; diff --git a/src/systemctl/systemctl-list-units.c b/src/systemctl/systemctl-list-units.c index fbc04b7..184468e 100644 --- a/src/systemctl/systemctl-list-units.c +++ b/src/systemctl/systemctl-list-units.c @@ -127,49 +127,69 @@ static int output_units_list(const UnitInfo *unit_infos, size_t c) { table_set_ersatz_string(table, TABLE_ERSATZ_DASH); FOREACH_ARRAY(u, unit_infos, c) { + const char *on_loaded = NULL, *on_active = NULL, *on_sub = NULL, *on_circle = NULL; _cleanup_free_ char *id = NULL; - const char *on_underline = "", *on_loaded = "", *on_active = "", *on_circle = ""; - bool circle = false, underline = false; + bool circle = false, underline; - if (u + 1 < unit_infos + c && - !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id))) { - on_underline = ansi_underline(); - underline = true; - } + underline = u + 1 < unit_infos + c && !streq(unit_type_suffix(u->id), unit_type_suffix((u + 1)->id)); - if (STR_IN_SET(u->load_state, "error", "not-found", "bad-setting", "masked") && !arg_plain) { - on_circle = underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow(); + if (streq(u->load_state, "not-found")) { + on_circle = on_loaded = ansi_highlight_yellow(); circle = true; - on_loaded = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); - } else if (streq(u->active_state, "failed") && !arg_plain) { - on_circle = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); + } else if (STR_IN_SET(u->load_state, "bad-setting", "error", "masked")) { + on_loaded = ansi_highlight_red(); + on_circle = ansi_highlight_yellow(); circle = true; - on_active = underline ? ansi_highlight_red_underline() : ansi_highlight_red(); - } else { - on_circle = on_underline; - on_active = on_underline; - on_loaded = on_underline; } + if (streq(u->active_state, "failed")) { + on_sub = on_active = ansi_highlight_red(); + + /* Here override any load_state highlighting */ + on_circle = ansi_highlight_red(); + circle = true; + } else if (STR_IN_SET(u->active_state, "reloading", "activating", "maintenance", "deactivating")) { + on_sub = on_active = ansi_highlight(); + + if (!circle) { /* Here we let load_state highlighting win */ + on_circle = ansi_highlight(); + circle = true; + } + } else if (streq(u->active_state, "inactive")) + on_sub = on_active = ansi_grey(); + + /* As a special case, when this is a service which has not process running, let's grey out + * its state, to highlight that a bit */ + if (!on_sub && endswith(u->id, ".service") && streq(u->sub_state, "exited")) + on_sub = ansi_grey(); + + if (arg_plain) + circle = false; + id = format_unit_id(u->id, u->machine); if (!id) return log_oom(); r = table_add_many(table, TABLE_STRING, circle ? special_glyph(SPECIAL_GLYPH_BLACK_CIRCLE) : " ", - TABLE_SET_BOTH_COLORS, on_circle, + TABLE_SET_COLOR, on_circle, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, id, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_active, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->load_state, - TABLE_SET_BOTH_COLORS, on_loaded, + TABLE_SET_COLOR, on_loaded, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->active_state, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_active, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->sub_state, - TABLE_SET_BOTH_COLORS, on_active, + TABLE_SET_COLOR, on_sub, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->job_id ? u->job_type: "", - TABLE_SET_BOTH_COLORS, on_underline, + TABLE_SET_BOTH_UNDERLINES, underline, TABLE_STRING, u->description, - TABLE_SET_BOTH_COLORS, on_underline); + TABLE_SET_BOTH_UNDERLINES, underline); if (r < 0) return table_log_add_error(r); diff --git a/src/systemctl/systemctl-logind.c b/src/systemctl/systemctl-logind.c index 7f97325..d6cdd97 100644 --- a/src/systemctl/systemctl-logind.c +++ b/src/systemctl/systemctl-logind.c @@ -7,6 +7,7 @@ #include "bus-error.h" #include "bus-locator.h" #include "login-util.h" +#include "mountpoint-util.h" #include "process-util.h" #include "systemctl-logind.h" #include "systemctl-start-unit.h" @@ -51,6 +52,7 @@ int logind_reboot(enum action a) { [ACTION_HIBERNATE] = "Hibernate", [ACTION_HYBRID_SLEEP] = "HybridSleep", [ACTION_SUSPEND_THEN_HIBERNATE] = "SuspendThenHibernate", + [ACTION_SLEEP] = "Sleep", }; _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; @@ -71,7 +73,7 @@ int logind_reboot(enum action a) { polkit_agent_open_maybe(); (void) logind_set_wall_message(bus); - const char *method_with_flags = strjoina(actions[a], "WithFlags"); + const char *method_with_flags = a == ACTION_SLEEP ? actions[a] : strjoina(actions[a], "WithFlags"); log_debug("%s org.freedesktop.login1.Manager %s dbus call.", arg_dry_run ? "Would execute" : "Executing", method_with_flags); @@ -83,9 +85,12 @@ int logind_reboot(enum action a) { SET_FLAG(flags, SD_LOGIND_REBOOT_VIA_KEXEC, a == ACTION_KEXEC || (a == ACTION_REBOOT && getenv_bool("SYSTEMCTL_SKIP_AUTO_KEXEC") <= 0)); + /* Try to soft-reboot if /run/nextroot/ is a valid OS tree, but only if it's also a mount point. + * Otherwise, if people store new rootfs directly on /run/ tmpfs, 'systemctl reboot' would always + * soft-reboot, as /run/nextroot/ can never go away. */ SET_FLAG(flags, SD_LOGIND_SOFT_REBOOT_IF_NEXTROOT_SET_UP, - a == ACTION_REBOOT && getenv_bool("SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT") <= 0); + a == ACTION_REBOOT && getenv_bool("SYSTEMCTL_SKIP_AUTO_SOFT_REBOOT") <= 0 && path_is_mount_point("/run/nextroot") > 0); SET_FLAG(flags, SD_LOGIND_SOFT_REBOOT, a == ACTION_SOFT_REBOOT); r = bus_call_method(bus, bus_login_mgr, method_with_flags, &error, NULL, "t", flags); @@ -103,7 +108,7 @@ int logind_reboot(enum action a) { } if (r >= 0) return 0; - if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) + if (!sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD) || a == ACTION_SLEEP) return log_error_errno(r, "Call to %s failed: %s", actions[a], bus_error_message(&error, r)); /* Fall back to original methods in case there is an older version of systemd-logind */ diff --git a/src/systemctl/systemctl-mount.c b/src/systemctl/systemctl-mount.c index d9ad332..61af218 100644 --- a/src/systemctl/systemctl-mount.c +++ b/src/systemctl/systemctl-mount.c @@ -86,7 +86,7 @@ int verb_mount_image(int argc, char *argv[], void *userdata) { _cleanup_free_ char *partition = NULL, *mount_options = NULL; const char *options = argv[4]; - r = extract_many_words(&options, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + r = extract_many_words(&options, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options); if (r < 0) return r; /* Single set of options, applying to the root partition/single filesystem */ diff --git a/src/systemctl/systemctl-show.c b/src/systemctl/systemctl-show.c index 5d1eb49..2fdf321 100644 --- a/src/systemctl/systemctl-show.c +++ b/src/systemctl/systemctl-show.c @@ -196,6 +196,8 @@ typedef struct UnitStatusInfo { uint64_t runtime_max_sec; + sd_id128_t invocation_id; + bool need_daemon_reload; bool transient; @@ -204,7 +206,7 @@ typedef struct UnitStatusInfo { pid_t control_pid; const char *status_text; const char *pid_file; - bool running:1; + bool running; int status_errno; uint32_t fd_store_max; @@ -468,6 +470,9 @@ static void print_status_info( } else printf("\n"); + if (!sd_id128_is_null(i->invocation_id)) + printf(" Invocation: " SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(i->invocation_id)); + STRV_FOREACH(t, i->triggered_by) { UnitActiveState state = _UNIT_ACTIVE_STATE_INVALID; @@ -703,17 +708,18 @@ static void print_status_info( 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()); + bool show_memory_peak = i->memory_peak != CGROUP_LIMIT_MAX, + show_memory_swap_peak = !IN_SET(i->memory_swap_peak, 0, CGROUP_LIMIT_MAX); + if (i->memory_current != UINT64_MAX) { printf(" Memory: %s", FORMAT_BYTES(i->memory_current)); - /* Only show current swap if it ever was non-zero or is currently non-zero. In both cases - memory_swap_peak will be non-zero (and not CGROUP_LIMIT_MAX). - Only show the available memory if it was artificially limited. */ - bool show_memory_swap = !IN_SET(i->memory_swap_peak, 0, CGROUP_LIMIT_MAX), - show_memory_zswap_current = !IN_SET(i->memory_zswap_current, 0, CGROUP_LIMIT_MAX), - show_memory_available = i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX; - if (i->memory_peak != CGROUP_LIMIT_MAX || - show_memory_swap || + bool show_memory_zswap_current = !IN_SET(i->memory_zswap_current, 0, CGROUP_LIMIT_MAX), + /* Only show the available memory if it was artificially limited. */ + show_memory_available = i->memory_available != CGROUP_LIMIT_MAX && + (i->memory_high != CGROUP_LIMIT_MAX || i->memory_max != CGROUP_LIMIT_MAX); + if (show_memory_peak || + show_memory_swap_peak || /* We don't need to check memory_swap_current, as if peak is 0 that must also be 0 */ show_memory_zswap_current || show_memory_available || i->memory_min > 0 || @@ -722,7 +728,6 @@ static void print_status_info( i->memory_max != CGROUP_LIMIT_MAX || i->startup_memory_max != CGROUP_LIMIT_MAX || i->memory_swap_max != CGROUP_LIMIT_MAX || i->startup_memory_swap_max != CGROUP_LIMIT_MAX || i->memory_zswap_max != CGROUP_LIMIT_MAX || i->startup_memory_zswap_max != CGROUP_LIMIT_MAX || - i->memory_available != CGROUP_LIMIT_MAX || i->memory_limit != CGROUP_LIMIT_MAX) { const char *prefix = ""; @@ -779,11 +784,11 @@ static void print_status_info( printf("%savailable: %s", prefix, FORMAT_BYTES(i->memory_available)); prefix = " "; } - if (i->memory_peak != CGROUP_LIMIT_MAX) { + if (show_memory_peak) { printf("%speak: %s", prefix, FORMAT_BYTES(i->memory_peak)); prefix = " "; } - if (show_memory_swap) { + if (show_memory_swap_peak) { printf("%sswap: %s swap peak: %s", prefix, FORMAT_BYTES(i->memory_swap_current), FORMAT_BYTES(i->memory_swap_peak)); prefix = " "; @@ -795,6 +800,14 @@ static void print_status_info( printf(")"); } printf("\n"); + + } else if (show_memory_peak) { + printf(" Mem peak: %s", FORMAT_BYTES(i->memory_peak)); + + if (show_memory_swap_peak) + printf(" (swap: %s)", FORMAT_BYTES(i->memory_swap_peak)); + + putchar('\n'); } if (i->cpu_usage_nsec != UINT64_MAX) @@ -834,10 +847,9 @@ static void print_status_info( i->id, i->log_namespace, arg_output, - 0, + /* n_columns = */ 0, i->inactive_exit_timestamp_monotonic, arg_lines, - getuid(), get_output_flags() | OUTPUT_BEGIN_NEWLINE, SD_JOURNAL_LOCAL_ONLY, arg_runtime_scope == RUNTIME_SCOPE_SYSTEM, @@ -922,11 +934,7 @@ static int map_listen(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) { - r = strv_extend(p, type); - if (r < 0) - return r; - - r = strv_extend(p, path); + r = strv_extend_many(p, type, path); if (r < 0) return r; } @@ -2025,8 +2033,9 @@ static int show_one( { "InactiveExitTimestampMonotonic", "t", NULL, offsetof(UnitStatusInfo, inactive_exit_timestamp_monotonic) }, { "ActiveEnterTimestamp", "t", NULL, offsetof(UnitStatusInfo, active_enter_timestamp) }, { "ActiveExitTimestamp", "t", NULL, offsetof(UnitStatusInfo, active_exit_timestamp) }, - { "RuntimeMaxUSec", "t", NULL, offsetof(UnitStatusInfo, runtime_max_sec) }, { "InactiveEnterTimestamp", "t", NULL, offsetof(UnitStatusInfo, inactive_enter_timestamp) }, + { "RuntimeMaxUSec", "t", NULL, offsetof(UnitStatusInfo, runtime_max_sec) }, + { "InvocationID", "s", bus_map_id128, offsetof(UnitStatusInfo, invocation_id) }, { "NeedDaemonReload", "b", NULL, offsetof(UnitStatusInfo, need_daemon_reload) }, { "Transient", "b", NULL, offsetof(UnitStatusInfo, transient) }, { "ExecMainPID", "u", NULL, offsetof(UnitStatusInfo, main_pid) }, @@ -2194,96 +2203,6 @@ static int show_one( return 0; } -static int get_unit_dbus_path_by_pid_fallback( - sd_bus *bus, - uint32_t pid, - char **ret_path, - char **ret_unit) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *path = NULL, *unit = NULL; - char *p; - int r; - - assert(bus); - assert(ret_path); - assert(ret_unit); - - r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPID", &error, &reply, "u", pid); - if (r < 0) - return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &p); - if (r < 0) - return bus_log_parse_error(r); - - path = strdup(p); - if (!path) - return log_oom(); - - r = unit_name_from_dbus_path(path, &unit); - if (r < 0) - return log_oom(); - - *ret_unit = TAKE_PTR(unit); - *ret_path = TAKE_PTR(path); - - return 0; -} - -static int get_unit_dbus_path_by_pid( - sd_bus *bus, - uint32_t pid, - char **ret_path, - char **ret_unit) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *path = NULL, *unit = NULL; - _cleanup_close_ int pidfd = -EBADF; - char *p, *u; - int r; - - assert(bus); - assert(ret_path); - assert(ret_unit); - - /* First, try to send a PIDFD across the wire, so that we can pin the process and there's no race - * condition possible while we wait for the D-Bus reply. If we either don't have PIDFD support in - * the kernel or the new D-Bus method is not available, then fallback to the older method that - * sends the numeric PID. */ - - pidfd = pidfd_open(pid, 0); - if (pidfd < 0 && (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))) - return get_unit_dbus_path_by_pid_fallback(bus, pid, ret_path, ret_unit); - if (pidfd < 0) - return log_error_errno(errno, "Failed to open PID %"PRIu32": %m", pid); - - r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPIDFD", &error, &reply, "h", pidfd); - if (r < 0 && sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) - return get_unit_dbus_path_by_pid_fallback(bus, pid, ret_path, ret_unit); - if (r < 0) - return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "os", &p, &u); - if (r < 0) - return bus_log_parse_error(r); - - path = strdup(p); - if (!path) - return log_oom(); - - unit = strdup(u); - if (!unit) - return log_oom(); - - *ret_unit = TAKE_PTR(unit); - *ret_path = TAKE_PTR(path); - - return 0; -} - static int show_all( sd_bus *bus, SystemctlShowMode show_mode, @@ -2455,9 +2374,9 @@ int verb_show(int argc, char *argv[], void *userdata) { } else { /* Interpret as PID */ - r = get_unit_dbus_path_by_pid(bus, id, &path, &unit); + r = lookup_unit_by_pidref(bus, (pid_t) id, &unit, &path); if (r < 0) { - ret = r; + RET_GATHER(ret, r); continue; } } diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index d23ce36..95cf00f 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -155,14 +155,16 @@ int verb_start_special(int argc, char *argv[], void *userdata) { return r; } - if (a == ACTION_REBOOT) { - if (arg_reboot_argument) { - r = update_reboot_parameter_and_warn(arg_reboot_argument, false); - if (r < 0) - return r; - } + if (arg_reboot_argument && IN_SET(a, ACTION_HALT, ACTION_POWEROFF, ACTION_REBOOT, ACTION_KEXEC)) { + /* If we are going through an action that involves systemd-shutdown, let's set the reboot + * parameter, even if it's not a regular reboot. After all we nowadays send the string to + * our supervisor via sd_notify() too. */ + r = update_reboot_parameter_and_warn(arg_reboot_argument, /* keep= */ false); + if (r < 0) + return r; + } - } else if (a == ACTION_KEXEC) { + if (a == ACTION_KEXEC) { r = load_kexec_kernel(); if (r < 0 && arg_force >= 1) log_notice("Failed to load kexec kernel, continuing without."); @@ -221,13 +223,19 @@ int verb_start_special(int argc, char *argv[], void *userdata) { case ACTION_HYBRID_SLEEP: case ACTION_SUSPEND_THEN_HIBERNATE: + /* For sleep operations, do not automatically fall back to low-level operation for + * errors other than logind not available. There's a high chance that logind did + * some extra sanity check and that didn't pass. */ r = logind_reboot(a); - if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) + if (r >= 0 || (r != -ENOSYS && arg_force == 0)) return r; arg_no_block = true; break; + case ACTION_SLEEP: + return logind_reboot(a); + case ACTION_EXIT: /* Since exit is so close in behaviour to power-off/reboot, let's also make * it asynchronous, in order to not confuse the user needlessly with unexpected diff --git a/src/systemctl/systemctl-start-unit.c b/src/systemctl/systemctl-start-unit.c index 6927e97..8068d77 100644 --- a/src/systemctl/systemctl-start-unit.c +++ b/src/systemctl/systemctl-start-unit.c @@ -35,20 +35,24 @@ static const struct { { "force-reload", "ReloadOrTryRestartUnit", "reload-or-try-restart" }, /* legacy alias */ }; -static const char *verb_to_method(const char *verb) { - for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].verb, verb)) - return unit_actions[i].method; +static const char* verb_to_method(const char *verb) { + assert(verb); - return "StartUnit"; + FOREACH_ELEMENT(i, unit_actions) + if (streq(i->verb, verb)) + return i->method; + + return "StartUnit"; } -static const char *verb_to_job_type(const char *verb) { - for (size_t i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].verb, verb)) - return unit_actions[i].job_type; +static const char* verb_to_job_type(const char *verb) { + assert(verb); + + FOREACH_ELEMENT(i, unit_actions) + if (streq(i->verb, verb)) + return i->job_type; - return "start"; + return "start"; } static int start_unit_one( @@ -236,9 +240,12 @@ const struct action_metadata action_table[_ACTION_MAX] = { [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, [ACTION_SUSPEND_THEN_HIBERNATE] = { SPECIAL_SUSPEND_THEN_HIBERNATE_TARGET, "suspend-then-hibernate", "replace-irreversibly" }, + [ACTION_SLEEP] = { NULL, /* handled only by logind */ "sleep", NULL }, }; enum action verb_to_action(const char *verb) { + assert(verb); + for (enum action i = 0; i < _ACTION_MAX; i++) if (streq_ptr(action_table[i].verb, verb)) return i; @@ -254,14 +261,29 @@ static const char** make_extra_args(const char *extra_args[static 4]) { if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) extra_args[n++] = "--user"; - if (arg_transport == BUS_TRANSPORT_REMOTE) { + switch (arg_transport) { + + case BUS_TRANSPORT_REMOTE: extra_args[n++] = "-H"; extra_args[n++] = arg_host; - } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + break; + + case BUS_TRANSPORT_MACHINE: extra_args[n++] = "-M"; extra_args[n++] = arg_host; - } else - assert(arg_transport == BUS_TRANSPORT_LOCAL); + break; + + case BUS_TRANSPORT_CAPSULE: + extra_args[n++] = "-C"; + extra_args[n++] = arg_host; + break; + + case BUS_TRANSPORT_LOCAL: + break; + + default: + assert_not_reached(); + } extra_args[n] = NULL; return extra_args; @@ -294,6 +316,8 @@ int verb_start(int argc, char *argv[], void *userdata) { action = verb_to_action(argv[0]); + assert(action != ACTION_SLEEP); + if (action != _ACTION_INVALID) { /* A command in style "systemctl reboot", "systemctl poweroff", … */ method = "StartUnit"; @@ -357,10 +381,6 @@ int verb_start(int argc, char *argv[], void *userdata) { } if (arg_wait) { - r = bus_call_method_async(bus, NULL, bus_systemd_mgr, "Subscribe", NULL, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to enable subscription: %m"); - r = bus_wait_for_units_new(bus, &wu); if (r < 0) return log_error_errno(r, "Failed to allocate unit watch context: %m"); @@ -384,9 +404,12 @@ int verb_start(int argc, char *argv[], void *userdata) { } if (!arg_no_block) { - const char* extra_args[4]; + const char *extra_args[4]; + WaitJobsFlags flags = 0; - r = bus_wait_for_jobs(w, arg_quiet, make_extra_args(extra_args)); + SET_FLAG(flags, BUS_WAIT_JOBS_LOG_ERROR, !arg_quiet); + SET_FLAG(flags, BUS_WAIT_JOBS_LOG_SUCCESS, arg_show_transaction); + r = bus_wait_for_jobs(w, flags, make_extra_args(extra_args)); if (r < 0) return r; diff --git a/src/systemctl/systemctl-sysv-compat.c b/src/systemctl/systemctl-sysv-compat.c index 2aa1ec6..8ee16eb 100644 --- a/src/systemctl/systemctl-sysv-compat.c +++ b/src/systemctl/systemctl-sysv-compat.c @@ -111,7 +111,7 @@ int enable_sysv_units(const char *verb, char **args) { int r = 0; #if HAVE_SYSV_COMPAT - _cleanup_(lookup_paths_free) LookupPaths paths = {}; + _cleanup_(lookup_paths_done) LookupPaths paths = {}; unsigned f = 0; SysVUnitEnableState enable_state = SYSV_UNIT_NOT_FOUND; diff --git a/src/systemctl/systemctl-util.c b/src/systemctl/systemctl-util.c index 2498725..2482b7c 100644 --- a/src/systemctl/systemctl-util.c +++ b/src/systemctl/systemctl-util.c @@ -18,6 +18,8 @@ #include "glob-util.h" #include "macro.h" #include "path-util.h" +#include "pidref.h" +#include "process-util.h" #include "reboot-util.h" #include "set.h" #include "spawn-ask-password-agent.h" @@ -40,7 +42,7 @@ int acquire_bus(BusFocus focus, sd_bus **ret) { return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--global is not supported for this operation."); /* We only go directly to the manager, if we are using a local transport */ - if (arg_transport != BUS_TRANSPORT_LOCAL) + if (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_CAPSULE)) focus = BUS_FULL; if (getenv_bool("SYSTEMCTL_FORCE_BUS") > 0) @@ -62,8 +64,8 @@ int acquire_bus(BusFocus focus, sd_bus **ret) { } void release_busses(void) { - for (BusFocus w = 0; w < _BUS_FOCUS_MAX; w++) - buses[w] = sd_bus_flush_close_unref(buses[w]); + FOREACH_ARRAY(w, buses, _BUS_FOCUS_MAX) + *w = sd_bus_flush_close_unref(*w); } void ask_password_agent_open_maybe(void) { @@ -260,7 +262,13 @@ int get_unit_list( return c; } -int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded) { +int expand_unit_names( + sd_bus *bus, + char * const *names, + const char *suffix, + char ***ret, + bool *ret_expanded) { + _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; int r; @@ -288,30 +296,20 @@ int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret if (expanded) { _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; _cleanup_free_ UnitInfo *unit_infos = NULL; - size_t n; r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); if (r < 0) return r; - n = strv_length(mangled); - - for (int i = 0; i < r; i++) { - if (!GREEDY_REALLOC(mangled, n+2)) - return log_oom(); - - mangled[n] = strdup(unit_infos[i].id); - if (!mangled[n]) + FOREACH_ARRAY(info, unit_infos, r) + if (strv_extend(&mangled, info->id) < 0) return log_oom(); - - mangled[++n] = NULL; - } } + *ret = TAKE_PTR(mangled); if (ret_expanded) *ret_expanded = expanded; - *ret = TAKE_PTR(mangled); return 0; } @@ -438,6 +436,9 @@ int need_daemon_reload(sd_bus *bus, const char *unit) { void warn_unit_file_changed(const char *unit) { assert(unit); + if (arg_no_warn) + return; + log_warning("Warning: The unit file, source configuration file or drop-ins of %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", unit, arg_runtime_scope == RUNTIME_SCOPE_SYSTEM ? "" : " --user"); @@ -480,8 +481,8 @@ int unit_find_paths( const char *unit_name, LookupPaths *lp, bool force_client_side, - Hashmap **cached_name_map, Hashmap **cached_id_map, + Hashmap **cached_name_map, char **ret_fragment_path, char ***ret_dropin_paths) { @@ -763,9 +764,7 @@ int maybe_extend_with_unit_dependencies(sd_bus *bus, char ***list) { if (r < 0) return log_error_errno(r, "Failed to append unit dependencies: %m"); - strv_free(*list); - *list = TAKE_PTR(list_with_deps); - return 0; + return strv_free_and_replace(*list, list_with_deps); } int unit_get_dependencies(sd_bus *bus, const char *name, char ***ret) { @@ -921,37 +920,31 @@ UnitFileFlags unit_file_flags_from_args(void) { (arg_force ? UNIT_FILE_FORCE : 0); } -int mangle_names(const char *operation, char **original_names, char ***ret_mangled_names) { +int mangle_names(const char *operation, char * const *original_names, char ***ret) { _cleanup_strv_free_ char **l = NULL; - char **i; int r; - assert(ret_mangled_names); - - l = i = new(char*, strv_length(original_names) + 1); - if (!l) - return log_oom(); + assert(operation); + assert(ret); STRV_FOREACH(name, original_names) { - - /* When enabling units qualified path names are OK, too, hence allow them explicitly. */ + char *mangled; if (is_path(*name)) - r = path_make_absolute_cwd(*name, i); + /* When enabling units qualified path names are OK, too, hence allow them explicitly. */ + r = path_make_absolute_cwd(*name, &mangled); else r = unit_name_mangle_with_suffix(*name, operation, arg_quiet ? 0 : UNIT_NAME_MANGLE_WARN, - ".service", i); - if (r < 0) { - *i = NULL; + ".service", &mangled); + if (r < 0) return log_error_errno(r, "Failed to mangle unit name or path '%s': %m", *name); - } - i++; + if (strv_consume(&l, mangled) < 0) + return log_oom(); } - *i = NULL; - *ret_mangled_names = TAKE_PTR(l); + *ret = TAKE_PTR(l); return 0; } @@ -994,3 +987,149 @@ int halt_now(enum action a) { assert_not_reached(); } } + +int get_unit_by_pid(sd_bus *bus, pid_t pid, char **ret_unit, char **ret_path) { + _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; + + assert(bus); + assert(pid >= 0); /* 0 is accepted by GetUnitByPID for querying our own process. */ + + r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPID", &error, &reply, "u", (uint32_t) pid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_UNIT_FOR_PID)) + return log_error_errno(r, "%s", bus_error_message(&error, r)); + + return log_error_errno(r, + "Failed to get unit that PID " PID_FMT " belongs to: %s", + pid > 0 ? pid : getpid_cached(), + bus_error_message(&error, r)); + } + + _cleanup_free_ char *u = NULL, *p = NULL; + const char *path; + + r = sd_bus_message_read_basic(reply, 'o', &path); + if (r < 0) + return bus_log_parse_error(r); + + if (ret_unit) { + r = unit_name_from_dbus_path(path, &u); + if (r < 0) + return log_error_errno(r, + "Failed to extract unit name from D-Bus object path '%s': %m", + path); + } + + if (ret_path) { + p = strdup(path); + if (!p) + return log_oom(); + } + + if (ret_unit) + *ret_unit = TAKE_PTR(u); + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} + +static int get_unit_by_pidfd(sd_bus *bus, const PidRef *pid, char **ret_unit, char **ret_path) { + _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; + + assert(bus); + assert(pidref_is_set(pid)); + + if (pid->fd < 0) + return -EOPNOTSUPP; + + r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPIDFD", &error, &reply, "h", pid->fd); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)) + return -EOPNOTSUPP; + + if (sd_bus_error_has_names(&error, BUS_ERROR_NO_UNIT_FOR_PID, BUS_ERROR_NO_SUCH_PROCESS)) + return log_error_errno(r, "%s", bus_error_message(&error, r)); + + return log_error_errno(r, + "Failed to get unit that PID " PID_FMT " belongs to: %s", + pid->pid, bus_error_message(&error, r)); + } + + _cleanup_free_ char *u = NULL, *p = NULL; + const char *path, *unit; + + r = sd_bus_message_read(reply, "os", &path, &unit); + if (r < 0) + return bus_log_parse_error(r); + + if (ret_unit) { + u = strdup(unit); + if (!u) + return log_oom(); + } + + if (ret_path) { + p = strdup(path); + if (!p) + return log_oom(); + } + + if (ret_unit) + *ret_unit = TAKE_PTR(u); + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} + +int lookup_unit_by_pidref(sd_bus *bus, pid_t pid, char **ret_unit, char **ret_path) { + int r; + + assert(bus); + assert(pid >= 0); /* 0 means our own process */ + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return get_unit_by_pid(bus, pid, ret_unit, ret_path); + + static bool use_pidfd = true; + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + r = pidref_set_pid(&pidref, pid); + if (r < 0) + return log_error_errno(r, + r == -ESRCH ? + "PID " PID_FMT " doesn't exist or is already gone." : + "Failed to create reference to PID " PID_FMT ": %m", + pid); + + if (use_pidfd) { + r = get_unit_by_pidfd(bus, &pidref, ret_unit, ret_path); + if (r != -EOPNOTSUPP) + return r; + + use_pidfd = false; + log_debug_errno(r, "Unable to look up process using pidfd, falling back to pid."); + } + + _cleanup_free_ char *u = NULL, *p = NULL; + + r = get_unit_by_pid(bus, pidref.pid, ret_unit ? &u : NULL, ret_path ? &p : NULL); + if (r < 0) + return r; + + r = pidref_verify(&pidref); + if (r < 0) + return log_error_errno(r, "Failed to verify our reference to PID " PID_FMT ": %m", pidref.pid); + + if (ret_unit) + *ret_unit = TAKE_PTR(u); + if (ret_path) + *ret_path = TAKE_PTR(p); + + return 0; +} diff --git a/src/systemctl/systemctl-util.h b/src/systemctl/systemctl-util.h index 7bddef0..a950dde 100644 --- a/src/systemctl/systemctl-util.h +++ b/src/systemctl/systemctl-util.h @@ -24,7 +24,7 @@ int translate_bus_error_to_exit_status(int r, const sd_bus_error *error); int get_state_one_unit(sd_bus *bus, const char *unit, UnitActiveState *ret_active_state); int get_sub_state_one_unit(sd_bus *bus, const char *unit, char **ret_sub_state); int get_unit_list(sd_bus *bus, const char *machine, char **patterns, UnitInfo **unit_infos, int c, sd_bus_message **ret_reply); -int expand_unit_names(sd_bus *bus, char **names, const char* suffix, char ***ret, bool *ret_expanded); +int expand_unit_names(sd_bus *bus, char * const *names, const char* suffix, char ***ret, bool *ret_expanded); int get_active_triggering_units(sd_bus *bus, const char *unit, bool ignore_masked, char ***ret); void warn_triggering_units(sd_bus *bus, const char *unit, const char *operation, bool ignore_masked); @@ -53,8 +53,11 @@ int output_table(Table *table); bool show_preset_for_state(UnitFileState state); -int mangle_names(const char *operation, char **original_names, char ***ret_mangled_names); +int mangle_names(const char *operation, char * const *original_names, char ***ret); UnitFileFlags unit_file_flags_from_args(void); int halt_now(enum action a); + +int get_unit_by_pid(sd_bus *bus, pid_t pid, char **ret_unit, char **ret_path); +int lookup_unit_by_pidref(sd_bus *bus, pid_t pid, char **ret_unit, char **ret_path); diff --git a/src/systemctl/systemctl-whoami.c b/src/systemctl/systemctl-whoami.c index 4ee6592..607f2db 100644 --- a/src/systemctl/systemctl-whoami.c +++ b/src/systemctl/systemctl-whoami.c @@ -1,70 +1,50 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ -#include "bus-error.h" -#include "bus-locator.h" +#include "format-util.h" +#include "parse-util.h" #include "systemctl.h" #include "systemctl-util.h" #include "systemctl-whoami.h" -#include "parse-util.h" - -static int lookup_pid(sd_bus *bus, pid_t pid) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *path; - int r; - - r = bus_call_method(bus, bus_systemd_mgr, "GetUnitByPID", &error, &reply, "u", (uint32_t) pid); - if (r < 0) - return log_error_errno(r, "Failed to get unit for ourselves: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = unit_name_from_dbus_path(path, &unit); - if (r < 0) - return log_error_errno(r, "Failed to extract unit name from D-Bus object path '%s': %m", path); - - printf("%s\n", unit); - return 0; -} int verb_whoami(int argc, char *argv[], void *userdata) { sd_bus *bus; - int r; + int r, ret = 0; r = acquire_bus(BUS_FULL, &bus); if (r < 0) return r; - char **pids = strv_skip(argv, 1); - - if (strv_isempty(pids)) { + if (argc <= 1) { + _cleanup_free_ char *unit = NULL; if (arg_transport != BUS_TRANSPORT_LOCAL) - return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Refusing to look up local PID on remote host."); + return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), + "Refusing to look up our local PID on remote host."); - return lookup_pid(bus, 0); - } else { - int ret = 0; + /* Our own process can never go away while querying, hence no need to go through pidfd. */ - STRV_FOREACH(p, pids) { - pid_t pid; + r = get_unit_by_pid(bus, 0, &unit, /* ret_path = */ NULL); + if (r < 0) + return r; - r = parse_pid(*p, &pid); - if (r < 0) { - log_error_errno(r, "Failed to parse PID: %s", *p); - if (ret >= 0) - ret = r; - continue; - } + puts(unit); + return 0; + } + + STRV_FOREACH(pidstr, strv_skip(argv, 1)) { + _cleanup_free_ char *unit = NULL; + pid_t pid; - r = lookup_pid(bus, pid); - if (r < 0 && ret >= 0) - ret = r; - } + r = parse_pid(*pidstr, &pid); + if (r < 0) + return log_error_errno(r, "Invalid PID specified: %s", *pidstr); - return ret; + r = lookup_unit_by_pidref(bus, pid, &unit, /* ret_path = */ NULL); + if (r < 0) + RET_GATHER(ret, r); + else + puts(unit); } + + return ret; } diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index dd6f6c9..0ca76ac 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -8,6 +8,7 @@ #include "build.h" #include "bus-util.h" +#include "capsule-util.h" #include "dissect-image.h" #include "install.h" #include "main-func.h" @@ -63,6 +64,7 @@ #include "systemctl.h" #include "terminal-util.h" #include "time-util.h" +#include "user-util.h" #include "verbs.h" #include "virt.h" @@ -103,6 +105,7 @@ bool arg_kill_value_set = false; char *arg_root = NULL; char *arg_image = NULL; usec_t arg_when = 0; +bool arg_stdin = false; const char *arg_reboot_argument = NULL; enum action arg_action = ACTION_SYSTEMCTL; BusTransport arg_transport = BUS_TRANSPORT_LOCAL; @@ -249,6 +252,8 @@ static int systemctl_help(void) { " soft-reboot Shut down and reboot userspace\n" " exit [EXIT_CODE] Request user instance or container exit\n" " switch-root [ROOT [INIT]] Change to a different root file system\n" + " sleep Put the system to sleep (through one of\n" + " the operations below)\n" " suspend Suspend the system\n" " hibernate Hibernate the system\n" " hybrid-sleep Hibernate and suspend the system\n" @@ -259,6 +264,7 @@ static int systemctl_help(void) { " --version Show package version\n" " --system Connect to system manager\n" " --user Connect to user service manager\n" + " -C --capsule=NAME Connect to service manager of specified capsule\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on a local container\n" " -t --type=TYPE List units of a particular type\n" @@ -272,6 +278,8 @@ static int systemctl_help(void) { " -l --full Don't ellipsize unit names on output\n" " -r --recursive Show unit list of host and local containers\n" " --reverse Show reverse dependencies with 'list-dependencies'\n" + " --before Show units ordered before with 'list-dependencies'\n" + " --after Show units ordered after with 'list-dependencies'\n" " --with-dependencies Show unit dependencies with 'status', 'cat',\n" " 'list-units', and 'list-unit-files'.\n" " --job-mode=MODE Specify how to deal with already queued jobs, when\n" @@ -297,8 +305,10 @@ static int systemctl_help(void) { " --no-warn Suppress several warnings shown by default\n" " --wait For (re)start, wait until service stopped again\n" " For is-system-running, wait until startup is completed\n" + " For kill, wait until service stopped\n" " --no-block Do not wait until operation finished\n" " --no-wall Don't send wall message before halt/power-off/reboot\n" + " --message=MESSAGE Specify human readable reason for system shutdown\n" " --no-reload Don't reload daemon after en-/dis-abling unit files\n" " --legend=BOOL Enable/disable the legend (column headers and hints)\n" " --no-pager Do not pipe output into a pager\n" @@ -326,6 +336,8 @@ static int systemctl_help(void) { " Boot into boot loader menu on next boot\n" " --boot-loader-entry=NAME\n" " Boot into a specific boot loader entry on next boot\n" + " --reboot-argument=ARG\n" + " Specify argument string to pass to reboot()\n" " --plain Print unit dependencies as a list instead of a tree\n" " --timestamp=FORMAT Change format of printed timestamps (pretty, unix,\n" " us, utc, us+utc)\n" @@ -335,6 +347,7 @@ static int systemctl_help(void) { " --drop-in=NAME Edit unit files using the specified drop-in file name\n" " --when=TIME Schedule halt/power-off/reboot/kexec action after\n" " a certain timestamp\n" + " --stdin Read contents of edited file from stdin\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -461,6 +474,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { ARG_NO_WARN, ARG_DROP_IN, ARG_WHEN, + ARG_STDIN, }; static const struct option options[] = { @@ -485,6 +499,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "user", no_argument, NULL, ARG_USER }, { "system", no_argument, NULL, ARG_SYSTEM }, { "global", no_argument, NULL, ARG_GLOBAL }, + { "capsule", required_argument, NULL, 'C' }, { "wait", no_argument, NULL, ARG_WAIT }, { "no-block", no_argument, NULL, ARG_NO_BLOCK }, { "legend", required_argument, NULL, ARG_LEGEND }, @@ -527,6 +542,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { { "marked", no_argument, NULL, ARG_MARKED }, { "drop-in", required_argument, NULL, ARG_DROP_IN }, { "when", required_argument, NULL, ARG_WHEN }, + { "stdin", no_argument, NULL, ARG_STDIN }, {} }; @@ -538,7 +554,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) { /* We default to allowing interactive authorization only in systemctl (not in the legacy commands) */ arg_ask_password = true; - while ((c = getopt_long(argc, argv, "ht:p:P:alqfs:H:M:n:o:iTr.::", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "hC:t:p:P:alqfs:H:M:n:o:iTr.::", options, NULL)) >= 0) switch (c) { @@ -673,6 +689,18 @@ static int systemctl_parse_argv(int argc, char *argv[]) { arg_runtime_scope = RUNTIME_SCOPE_GLOBAL; break; + case 'C': + r = capsule_name_is_valid(optarg); + if (r < 0) + return log_error_errno(r, "Unable to validate capsule name '%s': %m", optarg); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid capsule name: %s", optarg); + + arg_host = optarg; + arg_transport = BUS_TRANSPORT_CAPSULE; + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case ARG_WAIT: arg_wait = true; break; @@ -1017,6 +1045,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) { break; + case ARG_STDIN: + arg_stdin = true; + break; + case '.': /* Output an error mimicking getopt, and print a hint afterwards */ log_error("%s: invalid option -- '.'", program_invocation_name); @@ -1067,7 +1099,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) { } if (arg_image && arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); return 1; } @@ -1107,15 +1140,8 @@ int systemctl_dispatch_parse_argv(int argc, char *argv[]) { * * Also see redirect_telinit() in src/core/main.c. */ - if (sd_booted() > 0) { - arg_action = _ACTION_INVALID; - return telinit_parse_argv(argc, argv); - } else { - /* Hmm, so some other init system is running, we need to forward this request to it. - */ - arg_action = ACTION_TELINIT; - return 1; - } + arg_action = _ACTION_INVALID; /* telinit_parse_argv() will figure out the actual action we'll execute */ + return telinit_parse_argv(argc, argv); } else if (invoked_as(argv, "runlevel")) { arg_action = ACTION_RUNLEVEL; @@ -1179,6 +1205,7 @@ static int systemctl_main(int argc, char *argv[]) { { "reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "kexec", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "soft-reboot", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, + { "sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "suspend", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "hibernate", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, { "hybrid-sleep", VERB_ANY, 1, VERB_ONLINE_ONLY, verb_start_system_special }, @@ -1265,7 +1292,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_GENERIC_ROOT | DISSECT_IMAGE_REQUIRE_ROOT | DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_VALIDATE_OS, + DISSECT_IMAGE_VALIDATE_OS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -1318,11 +1346,8 @@ static int run(int argc, char *argv[]) { r = runlevel_main(); break; - case ACTION_TELINIT: - r = exec_telinit(argv); - break; - case ACTION_EXIT: + case ACTION_SLEEP: case ACTION_SUSPEND: case ACTION_HIBERNATE: case ACTION_HYBRID_SLEEP: diff --git a/src/systemctl/systemctl.h b/src/systemctl/systemctl.h index e8ba8f7..cc2b8c2 100644 --- a/src/systemctl/systemctl.h +++ b/src/systemctl/systemctl.h @@ -18,6 +18,7 @@ enum action { ACTION_KEXEC, ACTION_SOFT_REBOOT, ACTION_EXIT, + ACTION_SLEEP, ACTION_SUSPEND, ACTION_HIBERNATE, ACTION_HYBRID_SLEEP, @@ -32,7 +33,6 @@ enum action { ACTION_RELOAD, ACTION_REEXEC, ACTION_RUNLEVEL, - ACTION_TELINIT, ACTION_CANCEL_SHUTDOWN, ACTION_SHOW_SHUTDOWN, _ACTION_MAX, @@ -83,6 +83,7 @@ extern int arg_kill_value; extern bool arg_kill_value_set; extern char *arg_root; extern usec_t arg_when; +extern bool arg_stdin; extern const char *arg_reboot_argument; extern enum action arg_action; extern BusTransport arg_transport; diff --git a/src/systemd/meson.build b/src/systemd/meson.build index a9cdcd2..de58bff 100644 --- a/src/systemd/meson.build +++ b/src/systemd/meson.build @@ -20,10 +20,13 @@ _systemd_headers = [ systemd_headers = files(_systemd_headers) _not_installed_headers = [ + 'sd-dhcp-client-id.h', 'sd-dhcp-client.h', + 'sd-dhcp-duid.h', 'sd-dhcp-lease.h', 'sd-dhcp-option.h', 'sd-dhcp-protocol.h', + 'sd-dhcp-server-lease.h', 'sd-dhcp-server.h', 'sd-dhcp6-client.h', 'sd-dhcp6-lease.h', @@ -35,6 +38,11 @@ _not_installed_headers = [ 'sd-lldp-tx.h', 'sd-lldp.h', 'sd-ndisc.h', + 'sd-ndisc-neighbor.h', + 'sd-ndisc-protocol.h', + 'sd-ndisc-redirect.h', + 'sd-ndisc-router.h', + 'sd-ndisc-router-solicit.h', 'sd-netlink.h', 'sd-network.h', 'sd-radv.h', @@ -57,30 +65,29 @@ opts = [['c'], ['c', '-std=iso9899:1990'], ['c', '-std=iso9899:2011']] -if cc.has_argument('-std=iso9899:2017') - opts += [['c', '-std=iso9899:2017']] -endif - -if cc.has_argument('-std=c2x') - opts += [['c', '-std=c2x']] -endif +foreach opt : ['-std=iso9899:2017', + '-std=c23', + ] + if cc.has_argument(opt) + opts += [['c', opt]] + endif +endforeach if cxx_cmd != '' opts += [['c++'], ['c++', '-std=c++98'], ['c++', '-std=c++11']] - if cxx.has_argument('-std=c++14') - opts += [['c++', '-std=c++14']] - endif - if cxx.has_argument('-std=c++17') - opts += [['c++', '-std=c++17']] - endif - if cxx.has_argument('-std=c++20') - opts += [['c++', '-std=c++20']] - endif - if cxx.has_argument('-std=c++23') - opts += [['c++', '-std=c++23']] - endif + + foreach opt : ['-std=c++14', + '-std=c++17', + '-std=c++20', + '-std=c++23', + '-std=c++26', + ] + if cxx.has_argument(opt) + opts += [['c++', opt]] + endif + endforeach endif foreach header : _systemd_headers + _not_installed_headers + [libudev_h_path] diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h index bd3da36..f1a3311 100644 --- a/src/systemd/sd-bus.h +++ b/src/systemd/sd-bus.h @@ -90,8 +90,9 @@ __extension__ enum { SD_BUS_CREDS_UNIQUE_NAME = 1ULL << 31, SD_BUS_CREDS_WELL_KNOWN_NAMES = 1ULL << 32, SD_BUS_CREDS_DESCRIPTION = 1ULL << 33, + SD_BUS_CREDS_PIDFD = 1ULL << 34, SD_BUS_CREDS_AUGMENT = 1ULL << 63, /* special flag, if on sd-bus will augment creds struct, in a potentially race-full way. */ - _SD_BUS_CREDS_ALL = (1ULL << 34) -1 + _SD_BUS_CREDS_ALL = (1ULL << 35) -1 }; __extension__ enum { @@ -402,12 +403,14 @@ int sd_bus_match_signal_async(sd_bus *bus, sd_bus_slot **ret, const char *sender /* Credential handling */ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); +int sd_bus_creds_new_from_pidfd(sd_bus_creds **ret, int pidfd, uint64_t creds_mask); sd_bus_creds* sd_bus_creds_ref(sd_bus_creds *c); sd_bus_creds* sd_bus_creds_unref(sd_bus_creds *c); uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c); uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c); int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid); +int sd_bus_creds_get_pidfd_dup(sd_bus_creds *c, int *ret_fd); int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid); int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid); int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid); diff --git a/src/systemd/sd-dhcp-client-id.h b/src/systemd/sd-dhcp-client-id.h new file mode 100644 index 0000000..d6174c6 --- /dev/null +++ b/src/systemd/sd-dhcp-client-id.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpclientidhfoo +#define foosddhcpclientidhfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include + +#include "sd-dhcp-duid.h" + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_client_id sd_dhcp_client_id; + +int sd_dhcp_client_id_new(sd_dhcp_client_id **ret); +sd_dhcp_client_id* sd_dhcp_client_id_free(sd_dhcp_client_id *client_id); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client_id, sd_dhcp_client_id_free); + +int sd_dhcp_client_id_clear(sd_dhcp_client_id *client_id); + +int sd_dhcp_client_id_is_set(const sd_dhcp_client_id *client_id); + +int sd_dhcp_client_id_get(const sd_dhcp_client_id *client_id, uint8_t *ret_type, const void **ret_data, size_t *ret_size); +int sd_dhcp_client_id_get_raw(const sd_dhcp_client_id *client_id, const void **ret_data, size_t *ret_size); + +int sd_dhcp_client_id_set( + sd_dhcp_client_id *client_id, + uint8_t type, + const void *data, + size_t data_size); +int sd_dhcp_client_id_set_raw( + sd_dhcp_client_id *client_id, + const void *data, + size_t data_size); +int sd_dhcp_client_id_set_iaid_duid( + sd_dhcp_client_id *client_id, + uint32_t iaid, + sd_dhcp_duid *duid); + +int sd_dhcp_client_id_to_string(const sd_dhcp_client_id *client_id, char **ret); +int sd_dhcp_client_id_to_string_from_raw(const void *data, size_t data_size, char **ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index 3a8abc8..1483afa 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -26,6 +26,7 @@ #include #include "sd-device.h" +#include "sd-dhcp-client-id.h" #include "sd-dhcp-lease.h" #include "sd-dhcp-option.h" #include "sd-event.h" @@ -75,6 +76,9 @@ int sd_dhcp_client_set_mac( const uint8_t *bcast_addr, size_t addr_len, uint16_t arp_type); +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + const sd_dhcp_client_id **ret); int sd_dhcp_client_set_client_id( sd_dhcp_client *client, uint8_t type, @@ -107,11 +111,6 @@ __extension__ int sd_dhcp_client_set_iaid_duid_raw( __extension__ int sd_dhcp_client_set_rapid_commit( sd_dhcp_client *client, bool rapid_commit); -int sd_dhcp_client_get_client_id( - sd_dhcp_client *client, - uint8_t *ret_type, - const uint8_t **ret_data, - size_t *ret_data_len); int sd_dhcp_client_set_mtu( sd_dhcp_client *client, uint32_t mtu); @@ -121,6 +120,9 @@ int sd_dhcp_client_set_max_attempts( int sd_dhcp_client_set_client_port( sd_dhcp_client *client, uint16_t port); +int sd_dhcp_client_set_port( + sd_dhcp_client *client, + uint16_t port); int sd_dhcp_client_set_hostname( sd_dhcp_client *client, const char *hostname); @@ -165,8 +167,6 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); * options when using RFC7844 Anonymity Profiles */ int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); -int sd_dhcp_client_id_to_string(const void *data, size_t len, char **ret); - int sd_dhcp_client_attach_event( sd_dhcp_client *client, sd_event *event, diff --git a/src/systemd/sd-dhcp-duid.h b/src/systemd/sd-dhcp-duid.h new file mode 100644 index 0000000..555b40e --- /dev/null +++ b/src/systemd/sd-dhcp-duid.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpduidhfoo +#define foosddhcpduidhfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DUID_TYPE_LLT = 1, + SD_DUID_TYPE_EN = 2, + SD_DUID_TYPE_LL = 3, + SD_DUID_TYPE_UUID = 4 +}; + +typedef struct sd_dhcp_duid sd_dhcp_duid; + +int sd_dhcp_duid_clear(sd_dhcp_duid *duid); + +int sd_dhcp_duid_is_set(const sd_dhcp_duid *duid); + +int sd_dhcp_duid_get(const sd_dhcp_duid *duid, uint16_t *ret_type, const void **ret_data, size_t *ret_size); +int sd_dhcp_duid_get_raw(const sd_dhcp_duid *duid, const void **ret_data, size_t *ret_size); + +int sd_dhcp_duid_set( + sd_dhcp_duid *duid, + uint16_t duid_type, + const void *data, + size_t data_size); +int sd_dhcp_duid_set_raw( + sd_dhcp_duid *duid, + const void *data, + size_t data_size); +int sd_dhcp_duid_set_llt( + sd_dhcp_duid *duid, + const void *hw_addr, + size_t hw_addr_size, + uint16_t arp_type, + uint64_t usec); +int sd_dhcp_duid_set_ll( + sd_dhcp_duid *duid, + const void *hw_addr, + size_t hw_addr_size, + uint16_t arp_type); +int sd_dhcp_duid_set_en(sd_dhcp_duid *duid); +int sd_dhcp_duid_set_uuid(sd_dhcp_duid *duid); + +int sd_dhcp_duid_to_string(const sd_dhcp_duid *duid, char **ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h index 1ef53cc..95b0dfe 100644 --- a/src/systemd/sd-dhcp-lease.h +++ b/src/systemd/sd-dhcp-lease.h @@ -24,6 +24,8 @@ #include #include +#include "sd-dhcp-client-id.h" + #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; @@ -76,7 +78,7 @@ int sd_dhcp_lease_get_captive_portal(sd_dhcp_lease *lease, const char **captive_ int sd_dhcp_lease_get_static_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_classless_routes(sd_dhcp_lease *lease, sd_dhcp_route ***ret); int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const sd_dhcp_client_id **ret); int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); int sd_dhcp_lease_get_6rd( sd_dhcp_lease *lease, diff --git a/src/systemd/sd-dhcp-option.h b/src/systemd/sd-dhcp-option.h index 1486ec7..2c31ec2 100644 --- a/src/systemd/sd-dhcp-option.h +++ b/src/systemd/sd-dhcp-option.h @@ -3,7 +3,6 @@ #define foosddhcpoptionhfoo /*** - Copyright © 2013 Intel Corporation. All rights reserved. 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 diff --git a/src/systemd/sd-dhcp-server-lease.h b/src/systemd/sd-dhcp-server-lease.h new file mode 100644 index 0000000..b5c9ba7 --- /dev/null +++ b/src/systemd/sd-dhcp-server-lease.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosddhcpserverleasehfoo +#define foosddhcpserverleasehfoo + +/*** + 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 + Lesser 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_server_lease sd_dhcp_server_lease; + +sd_dhcp_server_lease *sd_dhcp_server_lease_ref(sd_dhcp_server_lease *lease); +sd_dhcp_server_lease *sd_dhcp_server_lease_unref(sd_dhcp_server_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server_lease, sd_dhcp_server_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h index feafa5d..1bba7a6 100644 --- a/src/systemd/sd-dhcp-server.h +++ b/src/systemd/sd-dhcp-server.h @@ -81,6 +81,7 @@ int sd_dhcp_server_set_smtp(sd_dhcp_server *server, const struct in_addr smtp[], int sd_dhcp_server_add_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_add_vendor_option(sd_dhcp_server *server, sd_dhcp_option *v); int sd_dhcp_server_set_static_lease(sd_dhcp_server *server, const struct in_addr *address, uint8_t *client_id, size_t client_id_size); +int sd_dhcp_server_set_lease_file(sd_dhcp_server *server, int dir_fd, const char *path); int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint64_t t); int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint64_t t); diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h index 0ceadb8..d551b4d 100644 --- a/src/systemd/sd-dhcp6-client.h +++ b/src/systemd/sd-dhcp6-client.h @@ -24,6 +24,7 @@ #include #include "sd-device.h" +#include "sd-dhcp-duid.h" #include "sd-dhcp6-lease.h" #include "sd-dhcp6-option.h" #include "sd-event.h" @@ -68,15 +69,15 @@ int sd_dhcp6_client_set_duid_ll(sd_dhcp6_client *client); int sd_dhcp6_client_set_duid_en(sd_dhcp6_client *client); int sd_dhcp6_client_set_duid_uuid(sd_dhcp6_client *client); int sd_dhcp6_client_set_duid_raw(sd_dhcp6_client *client, uint16_t duid_type, const uint8_t *duid, size_t duid_len); +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, const sd_dhcp_duid *duid); +int sd_dhcp6_client_get_duid(sd_dhcp6_client *client, const sd_dhcp_duid **ret); +int sd_dhcp6_client_get_duid_as_string(sd_dhcp6_client *client, char **ret); int sd_dhcp6_client_set_iaid( sd_dhcp6_client *client, uint32_t iaid); int sd_dhcp6_client_get_iaid( sd_dhcp6_client *client, uint32_t *iaid); -int sd_dhcp6_client_duid_as_string( - sd_dhcp6_client *client, - char **duid); int sd_dhcp6_client_set_fqdn( sd_dhcp6_client *client, const char *fqdn); diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h index 49d6975..a876add 100644 --- a/src/systemd/sd-event.h +++ b/src/systemd/sd-event.h @@ -161,6 +161,7 @@ int sd_event_source_send_child_signal(sd_event_source *s, int sig, const siginfo int sd_event_source_send_child_signal(sd_event_source *s, int sig, const void *si, unsigned flags); #endif int sd_event_source_get_inotify_mask(sd_event_source *s, uint32_t *ret); +int sd_event_source_get_inotify_path(sd_event_source *s, const char **ret); int sd_event_source_set_memory_pressure_type(sd_event_source *e, const char *ty); int sd_event_source_set_memory_pressure_period(sd_event_source *s, uint64_t threshold_usec, uint64_t window_usec); int sd_event_source_set_destroy_callback(sd_event_source *s, sd_event_destroy_t callback); diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h index a984a9d..a921052 100644 --- a/src/systemd/sd-id128.h +++ b/src/systemd/sd-id128.h @@ -53,6 +53,7 @@ int sd_id128_get_invocation(sd_id128_t *ret); int sd_id128_get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret); int sd_id128_get_machine_app_specific(sd_id128_t app_id, sd_id128_t *ret); int sd_id128_get_boot_app_specific(sd_id128_t app_id, sd_id128_t *ret); +int sd_id128_get_invocation_app_specific(sd_id128_t app_id, sd_id128_t *ret); #define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h index 7d2d75d..7434051 100644 --- a/src/systemd/sd-journal.h +++ b/src/systemd/sd-journal.h @@ -57,6 +57,7 @@ int sd_journal_perror_with_location(const char *file, const char *line, const ch #endif int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); +int sd_journal_stream_fd_with_namespace(const char *name_space, const char *identifier, int priority, int level_prefix); /* Browse journal stream */ @@ -72,6 +73,7 @@ enum { SD_JOURNAL_ALL_NAMESPACES = 1 << 5, /* Show all namespaces, not just the default or specified one */ SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE = 1 << 6, /* Show default namespace in addition to specified one */ SD_JOURNAL_TAKE_DIRECTORY_FD = 1 << 7, /* sd_journal_open_directory_fd() will take ownership of the provided file descriptor. */ + SD_JOURNAL_ASSUME_IMMUTABLE = 1 << 8, /* Assume the opened journal files are immutable. Journal entries added later may be ignored. */ SD_JOURNAL_SYSTEM_ONLY _sd_deprecated_ = SD_JOURNAL_SYSTEM /* old name */ }; diff --git a/src/systemd/sd-lldp-rx.h b/src/systemd/sd-lldp-rx.h index 504d7f5..a7e1a9f 100644 --- a/src/systemd/sd-lldp-rx.h +++ b/src/systemd/sd-lldp-rx.h @@ -68,7 +68,6 @@ int sd_lldp_rx_set_filter_address(sd_lldp_rx *lldp_rx, const struct ether_addr * int sd_lldp_rx_get_neighbors(sd_lldp_rx *lldp_rx, sd_lldp_neighbor ***neighbors); -int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); @@ -76,7 +75,6 @@ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); -int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); /* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); diff --git a/src/systemd/sd-ndisc-neighbor.h b/src/systemd/sd-ndisc-neighbor.h new file mode 100644 index 0000000..2ea0337 --- /dev/null +++ b/src/systemd/sd-ndisc-neighbor.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscneighborfoo +#define foosdndiscneighborfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_neighbor sd_ndisc_neighbor; + +sd_ndisc_neighbor *sd_ndisc_neighbor_ref(sd_ndisc_neighbor *na); +sd_ndisc_neighbor *sd_ndisc_neighbor_unref(sd_ndisc_neighbor *na); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_neighbor, sd_ndisc_neighbor_unref); + +int sd_ndisc_neighbor_get_sender_address(sd_ndisc_neighbor *na, struct in6_addr *ret); +/* RFC 4861 section 4.4: + * For solicited advertisements, the Target Address field in the Neighbor Solicitation message that prompted + * this advertisement. For an unsolicited advertisement, the address whose link-layer address has changed. + * The Target Address MUST NOT be a multicast address. */ +int sd_ndisc_neighbor_get_target_address(sd_ndisc_neighbor *na, struct in6_addr *ret); +int sd_ndisc_neighbor_get_target_mac(sd_ndisc_neighbor *na, struct ether_addr *ret); +int sd_ndisc_neighbor_get_flags(sd_ndisc_neighbor *na, uint32_t *ret); +int sd_ndisc_neighbor_is_router(sd_ndisc_neighbor *na); +int sd_ndisc_neighbor_is_solicited(sd_ndisc_neighbor *na); +int sd_ndisc_neighbor_is_override(sd_ndisc_neighbor *na); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc-protocol.h b/src/systemd/sd-ndisc-protocol.h new file mode 100644 index 0000000..3d2a188 --- /dev/null +++ b/src/systemd/sd-ndisc-protocol.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscprotocolfoo +#define foosdndiscprotocolfoo + +/*** + 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 + Lesser 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Neighbor Discovery Options, RFC 4861, Section 4.6 and + * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ +enum { + SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, /* RFC4861 */ + SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, /* RFC4861 */ + SD_NDISC_OPTION_PREFIX_INFORMATION = 3, /* RFC4861 */ + SD_NDISC_OPTION_REDIRECTED_HEADER = 4, /* RFC4861 */ + SD_NDISC_OPTION_MTU = 5, /* RFC4861 */ + SD_NDISC_OPTION_NBMA_SHORTCUT_LIMIT = 6, /* RFC2491 */ + SD_NDISC_OPTION_ADVERTISEMENT_INTERVAL = 7, /* RFC6275 */ + SD_NDISC_OPTION_HOME_AGENT = 8, /* RFC6275 */ + SD_NDISC_OPTION_SOURCE_ADDRESS_LIST = 9, /* RFC3122 */ + SD_NDISC_OPTION_TARGET_ADDRESS_LIST = 10, /* RFC3122 */ + SD_NDISC_OPTION_CGA = 11, /* RFC3971 */ + SD_NDISC_OPTION_RSA_SIGNATURE = 12, /* RFC3971 */ + SD_NDISC_OPTION_TIMESTAMP = 13, /* RFC3971 */ + SD_NDISC_OPTION_NONCE = 14, /* RFC3971 */ + SD_NDISC_OPTION_TRUST_ANCHOR = 15, /* RFC3971 */ + SD_NDISC_OPTION_CERTIFICATE = 16, /* RFC3971 */ + SD_NDISC_OPTION_IP_ADDRESS_PREFIX = 17, /* RFC5568 */ + SD_NDISC_OPTION_NEW_ROUTER_PREFIX = 18, /* RFC4068 */ + SD_NDISC_OPTION_LL_ADDRESS = 19, /* RFC5568 */ + SD_NDISC_OPTION_NEIGHBOR_ACKNOWLEDGMENT = 20, /* RFC5568 */ + SD_NDISC_OPTION_PVD_ID_ROUTER = 21, /* RFC8801 */ + /* 22 is unassigned yet */ + SD_NDISC_OPTION_MAP = 23, /* RFC4140 */ + SD_NDISC_OPTION_ROUTE_INFORMATION = 24, /* RFC4191 */ + SD_NDISC_OPTION_RDNSS = 25, /* RFC5006, RFC8106 */ + SD_NDISC_OPTION_FLAGS_EXTENSION = 26, /* RFC5175 */ + SD_NDISC_OPTION_HANDOVER_KEY_REQUEST = 27, /* RFC5269 */ + SD_NDISC_OPTION_HANDOVER_KEY_REPLY = 28, /* RFC5269 */ + SD_NDISC_OPTION_HANDOVER_ASSIST = 29, /* RFC5271 */ + SD_NDISC_OPTION_MOBILE_NODE_ID = 30, /* RFC5271 */ + SD_NDISC_OPTION_DNSSL = 31, /* RFC8106 */ + SD_NDISC_OPTION_PROXY_SIGNATURE = 32, /* RFC6496 */ + SD_NDISC_OPTION_REGISTRATION = 33, /* RFC6775 */ + SD_NDISC_OPTION_6LOWPAN = 34, /* RFC6775 */ + SD_NDISC_OPTION_AUTHORITATIVE_BORDER = 35, /* RFC6775 */ + SD_NDISC_OPTION_6LOWPAN_CAPABILITY = 36, /* RFC7400 */ + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, /* RFC8910 */ + SD_NDISC_OPTION_PREF64 = 38, /* RFC8781 */ + SD_NDISC_OPTION_CRYPTO_ID = 39, /* RFC8928 */ + SD_NDISC_OPTION_NDP_SIGNATURE = 40, /* RFC8928 */ + SD_NDISC_OPTION_RESOURCE_DIRECTORY = 41, /* RFC9176 */ + /* 42-137 are unassigned yet */ + SD_NDISC_OPTION_CARD_REQUEST = 138, /* RFC4065 */ + SD_NDISC_OPTION_CARD_REPLY = 139, /* RFC4065 */ + /* 140-143 are unassigned yet */ + SD_NDISC_OPTION_ENCRYPTED_DNS = 144 /* RFC9463 */ + /* 145-252 are unassigned yet */ + /* 253-254 are for experiment, see RFC4727 */ +}; + +/* Route preference, RFC 4191, Section 2.1 */ +enum { + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, + SD_NDISC_PREFERENCE_RESERVED = 2U, + SD_NDISC_PREFERENCE_LOW = 3U +}; + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc-redirect.h b/src/systemd/sd-ndisc-redirect.h new file mode 100644 index 0000000..60b43f7 --- /dev/null +++ b/src/systemd/sd-ndisc-redirect.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscredirectfoo +#define foosdndiscredirectfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_redirect sd_ndisc_redirect; + +sd_ndisc_redirect* sd_ndisc_redirect_ref(sd_ndisc_redirect *na); +sd_ndisc_redirect* sd_ndisc_redirect_unref(sd_ndisc_redirect *na); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_redirect, sd_ndisc_redirect_unref); + +int sd_ndisc_redirect_set_sender_address(sd_ndisc_redirect *rd, const struct in6_addr *addr); +int sd_ndisc_redirect_get_sender_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_target_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_destination_address(sd_ndisc_redirect *na, struct in6_addr *ret); +int sd_ndisc_redirect_get_target_mac(sd_ndisc_redirect *na, struct ether_addr *ret); +int sd_ndisc_redirect_get_redirected_header(sd_ndisc_redirect *na, struct ip6_hdr *ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc-router-solicit.h b/src/systemd/sd-ndisc-router-solicit.h new file mode 100644 index 0000000..ff8c903 --- /dev/null +++ b/src/systemd/sd-ndisc-router-solicit.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscroutersolicitfoo +#define foosdndiscroutersolicitfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_router_solicit sd_ndisc_router_solicit; + +sd_ndisc_router_solicit *sd_ndisc_router_solicit_ref(sd_ndisc_router_solicit *rs); +sd_ndisc_router_solicit *sd_ndisc_router_solicit_unref(sd_ndisc_router_solicit *rs); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router_solicit, sd_ndisc_router_solicit_unref); + +int sd_ndisc_router_solicit_get_sender_address(sd_ndisc_router_solicit *rs, struct in6_addr *ret); +int sd_ndisc_router_solicit_get_sender_mac(sd_ndisc_router_solicit *rs, struct ether_addr *ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc-router.h b/src/systemd/sd-ndisc-router.h new file mode 100644 index 0000000..b07fefb --- /dev/null +++ b/src/systemd/sd-ndisc-router.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#ifndef foosdndiscrouterfoo +#define foosdndiscrouterfoo + +/*** + 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 + Lesser 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 . +***/ + +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_ndisc_router sd_ndisc_router; + +sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); +sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); + +int sd_ndisc_router_set_sender_address(sd_ndisc_router *rt, const struct in6_addr *addr); +int sd_ndisc_router_get_sender_address(sd_ndisc_router *rt, struct in6_addr *ret); +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_reachable_time(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_get_retransmission_time(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_get_sender_mac(sd_ndisc_router *rt, struct ether_addr *ret); +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_get_captive_portal(sd_ndisc_router *rt, const char **ret); + +/* Generic option access */ +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); +int sd_ndisc_router_option_next(sd_ndisc_router *rt); +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const uint8_t **ret, size_t *ret_size); + +/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ +int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_prefix_get_valid_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); + +/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ +int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_route_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, uint8_t *ret); + +/* Specific option access: SD_NDISC_OPTION_RDNSS */ +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); +int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_rdnss_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); + +/* Specific option access: SD_NDISC_OPTION_DNSSL */ +int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); +int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_dnssl_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); + +/* Specific option access: SD_NDISC_OPTION_PREF64 */ +int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret); +int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); +int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h index 3f93e3a..cce592f 100644 --- a/src/systemd/sd-ndisc.h +++ b/src/systemd/sd-ndisc.h @@ -26,52 +26,37 @@ #include #include "sd-event.h" +#include "sd-ndisc-neighbor.h" +#include "sd-ndisc-protocol.h" +#include "sd-ndisc-redirect.h" +#include "sd-ndisc-router.h" #include "_sd-common.h" _SD_BEGIN_DECLARATIONS; -/* Neighbor Discovery Options, RFC 4861, Section 4.6 and - * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ -enum { - SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, - SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, - SD_NDISC_OPTION_PREFIX_INFORMATION = 3, - SD_NDISC_OPTION_MTU = 5, - SD_NDISC_OPTION_ROUTE_INFORMATION = 24, - SD_NDISC_OPTION_RDNSS = 25, - SD_NDISC_OPTION_FLAGS_EXTENSION = 26, - SD_NDISC_OPTION_DNSSL = 31, - SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, - SD_NDISC_OPTION_PREF64 = 38 -}; - -/* Route preference, RFC 4191, Section 2.1 */ -enum { - SD_NDISC_PREFERENCE_LOW = 3U, - SD_NDISC_PREFERENCE_MEDIUM = 0U, - SD_NDISC_PREFERENCE_HIGH = 1U -}; - typedef struct sd_ndisc sd_ndisc; -typedef struct sd_ndisc_router sd_ndisc_router; __extension__ typedef enum sd_ndisc_event_t { SD_NDISC_EVENT_TIMEOUT, SD_NDISC_EVENT_ROUTER, + SD_NDISC_EVENT_NEIGHBOR, + SD_NDISC_EVENT_REDIRECT, _SD_NDISC_EVENT_MAX, _SD_NDISC_EVENT_INVALID = -EINVAL, _SD_ENUM_FORCE_S64(NDISC_EVENT) } sd_ndisc_event_t; -typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, sd_ndisc_router *rt, void *userdata); +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event_t event, void *message, void *userdata); int sd_ndisc_new(sd_ndisc **ret); sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); int sd_ndisc_start(sd_ndisc *nd); int sd_ndisc_stop(sd_ndisc *nd); +int sd_ndisc_is_running(sd_ndisc *nd); int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); int sd_ndisc_detach_event(sd_ndisc *nd); @@ -81,68 +66,9 @@ int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); int sd_ndisc_set_ifname(sd_ndisc *nd, const char *interface_name); int sd_ndisc_get_ifname(sd_ndisc *nd, const char **ret); +int sd_ndisc_set_link_local_address(sd_ndisc *nd, const struct in6_addr *addr); int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); -sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); -sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); - -int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size); - -int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_get_icmp6_ratelimit(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); - -/* Generic option access */ -int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); -int sd_ndisc_router_option_next(sd_ndisc_router *rt); -int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); -int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *ret_size); - -/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ -int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix_get_valid_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix_get_preferred_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); -int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); - -/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ -int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_route_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); -int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); - -/* Specific option access: SD_NDISC_OPTION_RDNSS */ -int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); -int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_rdnss_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -/* Specific option access: SD_NDISC_OPTION_DNSSL */ -int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); -int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_dnssl_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -/* Specific option access: SD_NDISC_OPTION_CAPTIVE_PORTAL */ -int sd_ndisc_router_captive_portal_get_uri(sd_ndisc_router *rt, const char **ret, size_t *ret_size); - -/* Specific option access: SD_NDISC_OPTION_PREF64 */ -int sd_ndisc_router_prefix64_get_prefix(sd_ndisc_router *rt, struct in6_addr *ret); -int sd_ndisc_router_prefix64_get_prefixlen(sd_ndisc_router *rt, unsigned *ret); -int sd_ndisc_router_prefix64_get_lifetime(sd_ndisc_router *rt, uint64_t *ret); -int sd_ndisc_router_prefix64_get_lifetime_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); - _SD_END_DECLARATIONS; #endif diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h index 4119c45..34db2bb 100644 --- a/src/systemd/sd-netlink.h +++ b/src/systemd/sd-netlink.h @@ -106,7 +106,6 @@ int sd_netlink_message_cancel_array(sd_netlink_message *m); /* Reading messages */ int sd_netlink_message_read(sd_netlink_message *m, uint16_t attr_type, size_t size, void *data); int sd_netlink_message_read_data(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data); -int sd_netlink_message_read_data_suffix0(sd_netlink_message *m, uint16_t attr_type, size_t *ret_size, void **ret_data); int sd_netlink_message_read_string_strdup(sd_netlink_message *m, uint16_t attr_type, char **data); int sd_netlink_message_read_string(sd_netlink_message *m, uint16_t attr_type, const char **data); int sd_netlink_message_read_strv(sd_netlink_message *m, uint16_t container_type, uint16_t attr_type, char ***ret); @@ -162,6 +161,7 @@ int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned short *type); int sd_rtnl_message_new_route(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type, int rtm_family, unsigned char rtm_protocol); int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen); int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_route_set_tos(sd_netlink_message *m, unsigned char tos); int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope); int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags); int sd_rtnl_message_route_set_table(sd_netlink_message *m, unsigned char table); @@ -216,6 +216,8 @@ int sd_rtnl_message_traffic_control_get_parent(sd_netlink_message *m, uint32_t * int sd_rtnl_message_new_mdb(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int mdb_ifindex); +int sd_rtnl_message_new_nsid(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type); + /* genl */ int sd_genl_socket_open(sd_netlink **ret); int sd_genl_message_new(sd_netlink *genl, const char *family_name, uint8_t cmd, sd_netlink_message **ret); diff --git a/src/systemd/sd-radv.h b/src/systemd/sd-radv.h index 8ea0838..3120dd7 100644 --- a/src/systemd/sd-radv.h +++ b/src/systemd/sd-radv.h @@ -26,7 +26,8 @@ #include "_sd-common.h" #include "sd-event.h" -#include "sd-ndisc.h" +#include "sd-ndisc-protocol.h" +#include "sd-ndisc-router-solicit.h" _SD_BEGIN_DECLARATIONS; @@ -47,18 +48,21 @@ sd_event *sd_radv_get_event(sd_radv *ra); int sd_radv_start(sd_radv *ra); int sd_radv_stop(sd_radv *ra); int sd_radv_is_running(sd_radv *ra); +int sd_radv_send(sd_radv *ra); int sd_radv_set_ifindex(sd_radv *ra, int interface_index); int sd_radv_set_ifname(sd_radv *ra, const char *interface_name); int sd_radv_get_ifname(sd_radv *ra, const char **ret); +int sd_radv_set_link_local_address(sd_radv *ra, const struct in6_addr *addr); int sd_radv_set_mac(sd_radv *ra, const struct ether_addr *mac_addr); int sd_radv_set_mtu(sd_radv *ra, uint32_t mtu); int sd_radv_set_hop_limit(sd_radv *ra, uint8_t hop_limit); +int sd_radv_set_reachable_time(sd_radv *ra, uint64_t usec); int sd_radv_set_retransmit(sd_radv *ra, uint64_t usec); int sd_radv_set_router_lifetime(sd_radv *ra, uint64_t usec); -int sd_radv_set_managed_information(sd_radv *ra, int managed); -int sd_radv_set_other_information(sd_radv *ra, int other); -int sd_radv_set_preference(sd_radv *ra, unsigned preference); +int sd_radv_set_managed_information(sd_radv *ra, int b); +int sd_radv_set_other_information(sd_radv *ra, int b); +int sd_radv_set_preference(sd_radv *ra, uint8_t preference); int sd_radv_add_prefix(sd_radv *ra, sd_radv_prefix *p); int sd_radv_add_route_prefix(sd_radv *ra, sd_radv_route_prefix *p); int sd_radv_add_pref64_prefix(sd_radv *ra, sd_radv_pref64_prefix *p); diff --git a/src/sysupdate/sysupdate-partition.c b/src/sysupdate/sysupdate-partition.c index 6f8e072..f7fc17f 100644 --- a/src/sysupdate/sysupdate-partition.c +++ b/src/sysupdate/sysupdate-partition.c @@ -40,7 +40,7 @@ int read_partition_info( p = fdisk_table_get_partition(t, i); if (!p) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata."); if (fdisk_partition_is_used(p) <= 0) { *ret = (PartitionInfo) PARTITION_INFO_NULL; diff --git a/src/sysupdate/sysupdate-partition.h b/src/sysupdate/sysupdate-partition.h index 672eb93..094d8e0 100644 --- a/src/sysupdate/sysupdate-partition.h +++ b/src/sysupdate/sysupdate-partition.h @@ -29,9 +29,9 @@ struct PartitionInfo { sd_id128_t type, uuid; char *label; char *device; /* Note that this might point to some non-existing path in case we operate on a loopback file */ - bool no_auto:1; - bool read_only:1; - bool growfs:1; + bool no_auto; + bool read_only; + bool growfs; }; #define PARTITION_INFO_NULL \ diff --git a/src/sysupdate/sysupdate-pattern.c b/src/sysupdate/sysupdate-pattern.c index ff018d8..f5dc7cd 100644 --- a/src/sysupdate/sysupdate-pattern.c +++ b/src/sysupdate/sysupdate-pattern.c @@ -404,7 +404,7 @@ int pattern_match(const char *pattern, const char *s, InstanceMetadata *ret) { if (strlen(t) != sizeof(found.sha256sum) * 2) goto nope; - r = unhexmem(t, sizeof(found.sha256sum) * 2, &d, &l); + r = unhexmem_full(t, sizeof(found.sha256sum) * 2, /* secure = */ false, &d, &l); if (r == -ENOMEM) return r; if (r < 0) diff --git a/src/sysupdate/sysupdate-resource.c b/src/sysupdate/sysupdate-resource.c index e4bdd88..5b7aee2 100644 --- a/src/sysupdate/sysupdate-resource.c +++ b/src/sysupdate/sysupdate-resource.c @@ -6,6 +6,7 @@ #include "alloc-util.h" #include "blockdev-util.h" +#include "build-path.h" #include "chase.h" #include "device-util.h" #include "devnum-util.h" @@ -300,7 +301,7 @@ static int download_manifest( /* Child */ const char *cmdline[] = { - "systemd-pull", + SYSTEMD_PULL_PATH, "raw", "--direct", /* just download the specified URL, don't download anything else */ "--verify", verify_signature ? "signature" : "no", /* verify the manifest file */ @@ -309,8 +310,8 @@ static int download_manifest( NULL }; - execv(pull_binary_path(), (char *const*) cmdline); - log_error_errno(errno, "Failed to execute %s tool: %m", pull_binary_path()); + r = invoke_callout_binary(SYSTEMD_PULL_PATH, (char *const*) cmdline); + log_error_errno(r, "Failed to execute %s tool: %m", SYSTEMD_PULL_PATH); _exit(EXIT_FAILURE); }; @@ -398,7 +399,7 @@ static int resource_load_from_web( if (p[0] == '\\') return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "File names with escapes not supported in manifest at line %zu, refusing.", line_nr); - r = unhexmem(p, 64, &h, &hlen); + r = unhexmem_full(p, 64, /* secure = */ false, &h, &hlen); if (r < 0) return log_error_errno(r, "Failed to parse digest at manifest line %zu, refusing.", line_nr); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index f8f4a15..435c380 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -4,6 +4,7 @@ #include "alloc-util.h" #include "blockdev-util.h" +#include "build-path.h" #include "chase.h" #include "conf-parser.h" #include "dirent-util.h" @@ -531,6 +532,7 @@ int transfer_read_definition(Transfer *t, const char *path) { "Target path is not a normalized, absolute path: %s", t->target.path); if (strv_isempty(t->target.patterns)) { + log_syntax(NULL, LOG_INFO, path, 1, 0, "Target specification lacks MatchPattern= expression. Assuming same value as in source specification."); strv_free(t->target.patterns); t->target.patterns = strv_copy(t->source.patterns); if (!t->target.patterns) @@ -782,25 +784,23 @@ static void compile_pattern_fields( memcpy(ret->sha256sum, i->metadata.sha256sum, sizeof(ret->sha256sum)); } -static int run_helper( +static int run_callout( const char *name, - const char *path, - const char * const cmdline[]) { + char *cmdline[]) { int r; assert(name); - assert(path); assert(cmdline); + assert(cmdline[0]); r = safe_fork(name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); if (r < 0) return r; if (r == 0) { /* Child */ - - execv(path, (char *const*) cmdline); - log_error_errno(errno, "Failed to execute %s tool: %m", path); + r = invoke_callout_binary(cmdline[0], (char *const*) cmdline); + log_error_errno(r, "Failed to execute %s tool: %m", cmdline[0]); _exit(EXIT_FAILURE); } @@ -907,36 +907,30 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { * importer has some tricks up its sleeve, such as sparse file generation, which we * want to take benefit of, too.) */ - r = run_helper("(sd-import-raw)", - import_binary_path(), - (const char* const[]) { - "systemd-import", + r = run_callout("(sd-import-raw)", + STRV_MAKE( + SYSTEMD_IMPORT_PATH, "raw", "--direct", /* just copy/unpack the specified file, don't do anything else */ arg_sync ? "--sync=yes" : "--sync=no", i->path, - t->temporary_path, - NULL - }); + t->temporary_path)); break; case RESOURCE_PARTITION: /* regular file → partition */ - r = run_helper("(sd-import-raw)", - import_binary_path(), - (const char* const[]) { - "systemd-import", + r = run_callout("(sd-import-raw)", + STRV_MAKE( + SYSTEMD_IMPORT_PATH, "raw", "--direct", /* just copy/unpack the specified file, don't do anything else */ "--offset", offset, "--size-max", max_size, arg_sync ? "--sync=yes" : "--sync=no", i->path, - t->target.path, - NULL - }); + t->target.path)); break; default: @@ -951,18 +945,15 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { /* directory/subvolume → directory/subvolume */ - r = run_helper("(sd-import-fs)", - import_fs_binary_path(), - (const char* const[]) { - "systemd-import-fs", + r = run_callout("(sd-import-fs)", + STRV_MAKE( + SYSTEMD_IMPORT_FS_PATH, "run", "--direct", /* just untar the specified file, don't do anything else */ arg_sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, - t->temporary_path, - NULL - }); + t->temporary_path)); break; case RESOURCE_TAR: @@ -970,18 +961,15 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { /* tar → directory/subvolume */ - r = run_helper("(sd-import-tar)", - import_binary_path(), - (const char* const[]) { - "systemd-import", + r = run_callout("(sd-import-tar)", + STRV_MAKE( + SYSTEMD_IMPORT_PATH, "tar", "--direct", /* just untar the specified file, don't do anything else */ arg_sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, - t->temporary_path, - NULL - }); + t->temporary_path)); break; case RESOURCE_URL_FILE: @@ -992,28 +980,24 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { /* url file → regular file */ - r = run_helper("(sd-pull-raw)", - pull_binary_path(), - (const char* const[]) { - "systemd-pull", + r = run_callout("(sd-pull-raw)", + STRV_MAKE( + SYSTEMD_PULL_PATH, "raw", "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ arg_sync ? "--sync=yes" : "--sync=no", i->path, - t->temporary_path, - NULL - }); + t->temporary_path)); break; case RESOURCE_PARTITION: /* url file → partition */ - r = run_helper("(sd-pull-raw)", - pull_binary_path(), - (const char* const[]) { - "systemd-pull", + r = run_callout("(sd-pull-raw)", + STRV_MAKE( + SYSTEMD_PULL_PATH, "raw", "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ @@ -1021,9 +1005,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { "--size-max", max_size, arg_sync ? "--sync=yes" : "--sync=no", i->path, - t->target.path, - NULL - }); + t->target.path)); break; default: @@ -1035,19 +1017,16 @@ int transfer_acquire_instance(Transfer *t, Instance *i) { case RESOURCE_URL_TAR: assert(IN_SET(t->target.type, RESOURCE_DIRECTORY, RESOURCE_SUBVOLUME)); - r = run_helper("(sd-pull-tar)", - pull_binary_path(), - (const char*const[]) { - "systemd-pull", + r = run_callout("(sd-pull-tar)", + STRV_MAKE( + SYSTEMD_PULL_PATH, "tar", "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", arg_sync ? "--sync=yes" : "--sync=no", i->path, - t->temporary_path, - NULL - }); + t->temporary_path)); break; default: diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 023eaac..9565b68 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -676,7 +676,7 @@ static int context_vacuum( if (space == 0) log_info("Making room%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS)); else - log_info("Making room for %" PRIu64 " updates%s", space,special_glyph(SPECIAL_GLYPH_ELLIPSIS)); + log_info("Making room for %" PRIu64 " updates%s", space, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); for (size_t i = 0; i < c->n_transfers; i++) { r = transfer_vacuum(c->transfers[i], space, extra_protected_version); @@ -882,7 +882,8 @@ static int process_image( DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_USR_NO_ROOT | DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT, + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -1220,12 +1221,13 @@ static int verb_help(int argc, char **argv, void *userdata) { " --no-legend Do not show the headers and footers\n" " --json=pretty|short|off\n" " Generate JSON output\n" - "\nSee the %2$s for details.\n" - , program_invocation_short_name - , link - , ansi_underline(), ansi_normal() - , ansi_highlight(), ansi_normal() - ); + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), + ansi_normal(), + ansi_highlight(), + ansi_normal()); return 0; } diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h index 6d387b7..cba9bf4 100644 --- a/src/sysupdate/sysupdate.h +++ b/src/sysupdate/sysupdate.h @@ -7,15 +7,3 @@ extern bool arg_sync; extern uint64_t arg_instances_max; extern char *arg_root; - -static inline const char* import_binary_path(void) { - return secure_getenv("SYSTEMD_IMPORT_PATH") ?: SYSTEMD_IMPORT_PATH; -} - -static inline const char* import_fs_binary_path(void) { - return secure_getenv("SYSTEMD_IMPORT_FS_PATH") ?: SYSTEMD_IMPORT_FS_PATH; -} - -static inline const char *pull_binary_path(void) { - return secure_getenv("SYSTEMD_PULL_PATH") ?: SYSTEMD_PULL_PATH; -} diff --git a/src/sysusers/meson.build b/src/sysusers/meson.build index fcb291d..0f9c067 100644 --- a/src/sysusers/meson.build +++ b/src/sysusers/meson.build @@ -15,7 +15,6 @@ executables += [ 'c_args' : '-DSTANDALONE', 'link_with' : [ libbasic, - libbasic_gcrypt, libshared_static, libsystemd_static, ], diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 514f3c7..52f4a47 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -35,7 +35,7 @@ #include "strv.h" #include "sync-util.h" #include "tmpfile-util-label.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "uid-range.h" #include "user-util.h" #include "utf8.h" @@ -117,7 +117,7 @@ typedef struct Context { Set *names; uid_t search_uid; - UidRange *uid_range; + UIDRange *uid_range; UGIDAllocationRange login_defs; bool login_defs_need_warning; @@ -141,20 +141,6 @@ static void context_done(Context *c) { uid_range_free(c->uid_range); } -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 - * not found. */ - return IN_SET(code, 0, ENOENT, ESRCH, EBADF, EPERM); -} - -/* Note: the lifetime of the compound literal is the immediately surrounding block, - * see C11 §6.5.2.5, and - * https://stackoverflow.com/questions/34880638/compound-literal-lifetime-and-if-blocks */ -#define FORMAT_UID(is_set, uid) \ - ((is_set) ? snprintf_ok((char[DECIMAL_STR_MAX(uid_t)]){}, DECIMAL_STR_MAX(uid_t), UID_FMT, uid) : "(unset)") -#define FORMAT_GID(is_set, gid) \ - ((is_set) ? snprintf_ok((char[DECIMAL_STR_MAX(gid_t)]){}, DECIMAL_STR_MAX(gid_t), GID_FMT, gid) : "(unset)") - static void maybe_emit_login_defs_warning(Context *c) { assert(c); @@ -347,6 +333,7 @@ static int putgrent_with_members( FILE *group) { char **a; + int r; assert(c); assert(gr); @@ -365,15 +352,15 @@ static int putgrent_with_members( if (strv_contains(l, *i)) continue; - if (strv_extend(&l, *i) < 0) - return -ENOMEM; + r = strv_extend(&l, *i); + if (r < 0) + return r; added = true; } if (added) { struct group t; - int r; strv_uniq(l); strv_sort(l); @@ -396,6 +383,7 @@ static int putsgent_with_members( FILE *gshadow) { char **a; + int r; assert(sg); assert(gshadow); @@ -413,15 +401,15 @@ static int putsgent_with_members( if (strv_contains(l, *i)) continue; - if (strv_extend(&l, *i) < 0) - return -ENOMEM; + r = strv_extend(&l, *i); + if (r < 0) + return r; added = true; } if (added) { struct sgrp t; - int r; strv_uniq(l); strv_sort(l); @@ -581,7 +569,7 @@ static int write_temporary_passwd( static usec_t epoch_or_now(void) { uint64_t epoch; - if (getenv_uint64_secure("SOURCE_DATE_EPOCH", &epoch) >= 0) { + if (secure_getenv_uint64("SOURCE_DATE_EPOCH", &epoch) >= 0) { if (epoch > UINT64_MAX/USEC_PER_SEC) /* Overflow check */ return USEC_INFINITY; return (usec_t) epoch * USEC_PER_SEC; @@ -1026,6 +1014,7 @@ static int uid_is_ok( const char *name, bool check_with_gid) { + int r; assert(c); /* Let's see if we already have assigned the UID a second time */ @@ -1056,24 +1045,21 @@ static int uid_is_ok( /* Let's also check via NSS, to avoid UID clashes over LDAP and such, just in case */ if (!arg_root) { - struct passwd *p; - struct group *g; + _cleanup_free_ struct group *g = NULL; - errno = 0; - p = getpwuid(uid); - if (p) + r = getpwuid_malloc(uid, /* ret= */ NULL); + if (r >= 0) return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; + if (r != -ESRCH) + return r; if (check_with_gid) { - errno = 0; - g = getgrgid((gid_t) uid); - if (g) { + r = getgrgid_malloc((gid_t) uid, &g); + if (r >= 0) { if (!streq(g->gr_name, name)) return 0; - } else if (!IN_SET(errno, 0, ENOENT)) - return -errno; + } else if (r != -ESRCH) + return r; } } @@ -1162,12 +1148,11 @@ static int add_user(Context *c, Item *i) { } if (!arg_root) { - struct passwd *p; + _cleanup_free_ struct passwd *p = NULL; /* Also check NSS */ - errno = 0; - p = getpwnam(i->name); - if (p) { + r = getpwnam_malloc(i->name, &p); + if (r >= 0) { log_debug("User %s already exists.", i->name); i->uid = p->pw_uid; i->uid_set = true; @@ -1178,8 +1163,8 @@ static int add_user(Context *c, Item *i) { return 0; } - if (!errno_is_not_exists(errno)) - return log_error_errno(errno, "Failed to check if user %s already exists: %m", i->name); + if (r != -ESRCH) + return log_error_errno(r, "Failed to check if user %s already exists: %m", i->name); } /* Try to use the suggested numeric UID */ @@ -1268,10 +1253,9 @@ static int gid_is_ok( const char *groupname, bool check_with_uid) { - struct group *g; - struct passwd *p; Item *user; char *username; + int r; assert(c); assert(groupname); @@ -1296,20 +1280,18 @@ static int gid_is_ok( } if (!arg_root) { - errno = 0; - g = getgrgid(gid); - if (g) + r = getgrgid_malloc(gid, /* ret= */ NULL); + if (r >= 0) return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; + if (r != -ESRCH) + return r; if (check_with_uid) { - errno = 0; - p = getpwuid((uid_t) gid); - if (p) + r = getpwuid_malloc(gid, /* ret= */ NULL); + if (r >= 0) return 0; - if (!IN_SET(errno, 0, ENOENT)) - return -errno; + if (r != -ESRCH) + return r; } } @@ -1322,6 +1304,7 @@ static int get_gid_by_name( gid_t *ret_gid) { void *z; + int r; assert(c); assert(ret_gid); @@ -1335,16 +1318,15 @@ static int get_gid_by_name( /* Also check NSS */ if (!arg_root) { - struct group *g; + _cleanup_free_ struct group *g = NULL; - errno = 0; - g = getgrnam(name); - if (g) { + r = getgrnam_malloc(name, &g); + if (r >= 0) { *ret_gid = g->gr_gid; return 0; } - if (!errno_is_not_exists(errno)) - return log_error_errno(errno, "Failed to check if group %s already exists: %m", name); + if (r != -ESRCH) + return log_error_errno(r, "Failed to check if group %s already exists: %m", name); } return -ENOENT; @@ -1630,7 +1612,8 @@ static int item_equivalent(Item *a, Item *b) { (a->uid_set && a->uid != b->uid)) { log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0, "Item not equivalent because UIDs differ (%s vs. %s)", - FORMAT_UID(a->uid_set, a->uid), FORMAT_UID(b->uid_set, b->uid)); + a->uid_set ? FORMAT_UID(a->uid) : "(unset)", + b->uid_set ? FORMAT_UID(b->uid) : "(unset)"); return false; } @@ -1638,7 +1621,8 @@ static int item_equivalent(Item *a, Item *b) { (a->gid_set && a->gid != b->gid)) { log_syntax(NULL, LOG_DEBUG, a->filename, a->line, 0, "Item not equivalent because GIDs differ (%s vs. %s)", - FORMAT_GID(a->gid_set, a->gid), FORMAT_GID(b->gid_set, b->gid)); + a->gid_set ? FORMAT_GID(a->gid) : "(unset)", + b->gid_set ? FORMAT_GID(b->gid) : "(unset)"); return false; } @@ -1658,7 +1642,7 @@ static int item_equivalent(Item *a, Item *b) { const char *a_shell = pick_shell(a), *b_shell = pick_shell(b); - if (!path_equal_ptr(a_shell, b_shell) && + if (!path_equal(a_shell, b_shell) && !(is_nologin_shell(a_shell) && is_nologin_shell(b_shell))) { _cleanup_free_ char *pa = NULL, *pb = NULL; @@ -1690,11 +1674,13 @@ static int item_equivalent(Item *a, Item *b) { } static int parse_line( - Context *c, const char *fname, unsigned line, - const char *buffer) { + const char *buffer, + bool *invalid_config, + void *context) { + Context *c = ASSERT_PTR(context); _cleanup_free_ char *action = NULL, *name = NULL, *resolved_name = NULL, *id = NULL, *resolved_id = NULL, @@ -1707,15 +1693,15 @@ static int parse_line( int r; const char *p; - assert(c); assert(fname); assert(line >= 1); assert(buffer); + assert(!invalid_config); /* We don't support invalid_config yet. */ /* Parse columns */ p = buffer; r = extract_many_words(&p, NULL, EXTRACT_UNQUOTE, - &action, &name, &id, &description, &home, &shell, NULL); + &action, &name, &id, &description, &home, &shell); if (r < 0) return log_syntax(NULL, LOG_ERR, fname, line, r, "Syntax error."); if (r < 2) @@ -1979,57 +1965,14 @@ static int parse_line( } static int read_config_file(Context *c, const char *fn, bool ignore_enoent) { - _cleanup_fclose_ FILE *rf = NULL; - _cleanup_free_ char *pp = NULL; - FILE *f = NULL; - unsigned v = 0; - int r = 0; - - assert(c); - assert(fn); - - if (streq(fn, "-")) - f = stdin; - else { - r = search_and_fopen(fn, "re", arg_root, (const char**) CONF_PATHS_STRV("sysusers.d"), &rf, &pp); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn); - } - - f = rf; - fn = pp; - } - - for (;;) { - _cleanup_free_ char *line = NULL; - int k; - - k = read_stripped_line(f, LONG_LINE_MAX, &line); - if (k < 0) - return log_error_errno(k, "Failed to read '%s': %m", fn); - if (k == 0) - break; - - v++; - - if (IN_SET(line[0], 0, '#')) - continue; - - k = parse_line(c, fn, v, line); - if (k < 0 && r == 0) - r = k; - } - - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - - return r; + return conf_file_read( + arg_root, + (const char**) CONF_PATHS_STRV("sysusers.d"), + ASSERT_PTR(fn), + parse_line, + ASSERT_PTR(c), + ignore_enoent, + /* invalid_config= */ NULL); } static int cat_config(void) { @@ -2203,7 +2146,7 @@ static int parse_arguments(Context *c, char **args) { STRV_FOREACH(arg, args) { if (arg_inline) /* Use (argument):n, where n==1 for the first positional arg */ - r = parse_line(c, "(argument)", pos, *arg); + r = parse_line("(argument)", pos, *arg, /* invalid_config= */ NULL, c); else r = read_config_file(c, *arg, /* ignore_enoent= */ false); if (r < 0) @@ -2304,7 +2247,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_GROWFS, + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); diff --git a/src/sysv-generator/sysv-generator.c b/src/sysv-generator/sysv-generator.c index 4485e2e..f3b4470 100644 --- a/src/sysv-generator/sysv-generator.c +++ b/src/sysv-generator/sysv-generator.c @@ -807,7 +807,7 @@ static int set_dependencies_from_rcnd(const LookupPaths *lp, Hashmap *all_servic return r; STRV_FOREACH(p, sysvrcnd_path) - for (unsigned i = 0; i < ELEMENTSOF(rcnd_table); i ++) { + for (unsigned i = 0; i < ELEMENTSOF(rcnd_table); i++) { _cleanup_closedir_ DIR *d = NULL; _cleanup_free_ char *path = NULL; @@ -894,7 +894,7 @@ finish: static int run(const char *dest, const char *dest_early, const char *dest_late) { _cleanup_(free_sysvstub_hashmapp) Hashmap *all_services = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; SysvStub *service; int r; diff --git a/src/test/meson.build b/src/test/meson.build index cce90d7..3abbb94 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -14,7 +14,7 @@ test_env = environment() test_env.set('SYSTEMD_LANGUAGE_FALLBACK_MAP', language_fallback_map) test_env.set('PATH', project_build_root + ':' + path) test_env.set('PROJECT_BUILD_ROOT', project_build_root) -test_env.set('SYSTEMD_SLOW_TESTS', slow_tests ? '1' : '0') +test_env.set('SYSTEMD_SLOW_TESTS', want_slow_tests ? '1' : '0') if efi_addon != '' test_env.set('EFI_ADDON', efi_addon) @@ -51,6 +51,7 @@ simple_tests += files( 'test-bitmap.c', 'test-blockdev-util.c', 'test-bootspec.c', + 'test-build-path.c', 'test-bus-util.c', 'test-calendarspec.c', 'test-cgroup-setup.c', @@ -58,6 +59,7 @@ simple_tests += files( 'test-cgroup.c', 'test-chase.c', 'test-clock.c', + 'test-color-util.c', 'test-compare-operator.c', 'test-condition.c', 'test-conf-files.c', @@ -72,6 +74,7 @@ simple_tests += files( 'test-dev-setup.c', 'test-device-nodes.c', 'test-devnum-util.c', + 'test-dirent-util.c', 'test-dns-domain.c', 'test-ellipsize.c', 'test-env-file.c', @@ -107,8 +110,10 @@ simple_tests += files( 'test-install-file.c', 'test-install-root.c', 'test-io-util.c', + 'test-iovec-util.c', 'test-journal-importer.c', 'test-kbd-util.c', + 'test-label.c', 'test-limits-util.c', 'test-list.c', 'test-local-addresses.c', @@ -116,6 +121,7 @@ simple_tests += files( 'test-lock-util.c', 'test-log.c', 'test-logarithm.c', + 'test-login-util.c', 'test-macro.c', 'test-memfd-util.c', 'test-memory-util.c', @@ -134,6 +140,7 @@ simple_tests += files( 'test-path-lookup.c', 'test-path-util.c', 'test-percent-util.c', + 'test-pidref.c', 'test-pretty-print.c', 'test-prioq.c', 'test-proc-cmdline.c', @@ -141,6 +148,7 @@ simple_tests += files( 'test-psi-util.c', 'test-ratelimit.c', 'test-raw-clone.c', + 'test-recovery-key.c', 'test-recurse-dir.c', 'test-replace-var.c', 'test-rlimit-util.c', @@ -170,7 +178,7 @@ simple_tests += files( 'test-terminal-util.c', 'test-tmpfile-util.c', 'test-udev-util.c', - 'test-uid-alloc-range.c', + 'test-uid-classification.c', 'test-uid-range.c', 'test-umask-util.c', 'test-unaligned.c', @@ -178,6 +186,7 @@ simple_tests += files( 'test-user-util.c', 'test-utf8.c', 'test-verbs.c', + 'test-vpick.c', 'test-web-util.c', 'test-xattr-util.c', 'test-xml.c', @@ -242,18 +251,12 @@ executables += [ }, test_template + { 'sources' : files('test-compress-benchmark.c'), - 'link_with' : [ - libbasic_compress, - libshared, - ], + 'link_with' : [libshared], 'timeout' : 90, }, test_template + { 'sources' : files('test-compress.c'), - 'link_with' : [ - libbasic_compress, - libshared, - ], + 'link_with' : [libshared], }, test_template + { 'sources' : files('test-cryptolib.c'), @@ -262,7 +265,10 @@ executables += [ }, test_template + { 'sources' : files('test-dlopen-so.c'), - 'dependencies' : libp11kit_cflags + 'dependencies' : [ + libp11kit_cflags, + libkmod_cflags, + ], }, test_template + { # only static linking apart from libdl, to make sure that the @@ -332,7 +338,7 @@ executables += [ }, test_template + { 'sources' : files('test-netlink-manual.c'), - 'dependencies' : libkmod, + 'dependencies' : libkmod_cflags, 'conditions' : ['HAVE_KMOD'], 'type' : 'manual', }, @@ -375,6 +381,10 @@ executables += [ 'sources' : files('test-process-util.c'), 'dependencies' : threads, }, + test_template + { + 'sources' : files('test-progress-bar.c'), + 'type' : 'manual', + }, test_template + { 'sources' : files('test-qrcode-util.c'), 'dependencies' : libdl, @@ -475,7 +485,7 @@ executables += [ 'sources' : files('test-bpf-foreign-programs.c'), }, core_test_template + { - 'sources' : files('test-bpf-lsm.c'), + 'sources' : files('test-bpf-restrict-fs.c'), 'dependencies' : common_test_dependencies, }, core_test_template + { @@ -525,7 +535,7 @@ executables += [ 'parallel' : false, }, core_test_template + { - 'sources' : files('test-manager.c'), + 'sources' : files('test-taint.c'), }, core_test_template + { 'sources' : files('test-namespace.c'), @@ -579,12 +589,18 @@ executables += [ }, test_template + { 'sources' : files('../libsystemd/sd-device/test-sd-device-thread.c'), - 'link_with' : libsystemd, + 'link_with' : [ + libbasic, + libsystemd, + ], 'dependencies' : threads, }, test_template + { 'sources' : files('../libudev/test-udev-device-thread.c'), - 'link_with' : libudev, + 'link_with' : [ + libbasic, + libudev, + ], 'dependencies' : threads, }, test_template + { @@ -594,4 +610,8 @@ executables += [ libudev_basic, ], }, + test_template + { + 'sources' : files('test-aux-scope.c'), + 'type' : 'manual', + }, ] diff --git a/src/test/test-acl-util.c b/src/test/test-acl-util.c index eb9678a..0cc9afc 100644 --- a/src/test/test-acl-util.c +++ b/src/test/test-acl-util.c @@ -10,6 +10,7 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "path-util.h" #include "string-util.h" #include "tests.h" #include "tmpfile-util.h" @@ -22,6 +23,12 @@ TEST_RET(add_acls_for_user) { uid_t uid; int r; + FOREACH_STRING(s, "capsh", "getfacl", "ls") { + r = find_executable(s, NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Could not find %s binary: %m", s); + } + fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -69,11 +76,18 @@ TEST_RET(add_acls_for_user) { return 0; } -TEST(fd_acl_make_read_only) { +TEST_RET(fd_acl_make_read_only) { _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX"; _cleanup_close_ int fd = -EBADF; const char *cmd; struct stat st; + int r; + + FOREACH_STRING(s, "capsh", "getfacl", "ls", "stat") { + r = find_executable(s, NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Could not find %s binary: %m", s); + } fd = mkostemp_safe(fn); assert_se(fd >= 0); @@ -81,8 +95,8 @@ TEST(fd_acl_make_read_only) { /* make it more exciting */ (void) fd_add_uid_acl_permission(fd, 1, ACL_READ|ACL_WRITE|ACL_EXECUTE); - assert_se(fstat(fd, &st) >= 0); - assert_se((st.st_mode & 0200) == 0200); + ASSERT_OK_ERRNO(fstat(fd, &st)); + assert_se(FLAGS_SET(st.st_mode, 0200)); cmd = strjoina("getfacl -p ", fn); assert_se(system(cmd) == 0); @@ -93,7 +107,7 @@ TEST(fd_acl_make_read_only) { log_info("read-only"); assert_se(fd_acl_make_read_only(fd)); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); assert_se((st.st_mode & 0222) == 0000); cmd = strjoina("getfacl -p ", fn); @@ -105,7 +119,7 @@ TEST(fd_acl_make_read_only) { log_info("writable"); assert_se(fd_acl_make_writable(fd)); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); assert_se((st.st_mode & 0222) == 0200); cmd = strjoina("getfacl -p ", fn); @@ -117,7 +131,7 @@ TEST(fd_acl_make_read_only) { log_info("read-only"); assert_se(fd_acl_make_read_only(fd)); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); assert_se((st.st_mode & 0222) == 0000); cmd = strjoina("getfacl -p ", fn); @@ -125,6 +139,8 @@ TEST(fd_acl_make_read_only) { cmd = strjoina("stat ", fn); assert_se(system(cmd) == 0); + + return 0; } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-af-list.c b/src/test/test-af-list.c index 45655d7..1623c48 100644 --- a/src/test/test-af-list.c +++ b/src/test/test-af-list.c @@ -16,13 +16,14 @@ static const struct af_name* lookup_af(register const char *str, register GPERF_ TEST(af_list) { for (unsigned i = 0; i < ELEMENTSOF(af_names); i++) { if (af_names[i]) { - assert_se(streq(af_to_name(i), af_names[i])); + ASSERT_STREQ(af_to_name(i), af_names[i]); assert_se(af_from_name(af_names[i]) == (int) i); } } - assert_se(af_to_name(af_max()) == NULL); - assert_se(af_to_name(-1) == NULL); + ASSERT_NULL(af_to_name(af_max())); + ASSERT_NULL(af_to_name(0)); + ASSERT_NULL(af_to_name(-1)); assert_se(af_from_name("huddlduddl") == -EINVAL); assert_se(af_from_name("") == -EINVAL); } diff --git a/src/test/test-architecture.c b/src/test/test-architecture.c index 8731e1c..6b7777d 100644 --- a/src/test/test-architecture.c +++ b/src/test/test-architecture.c @@ -3,6 +3,7 @@ #include "architecture.h" #include "errno-util.h" #include "log.h" +#include "path-util.h" #include "tests.h" #include "virt.h" @@ -13,18 +14,18 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_INFO); - assert_se(architecture_from_string("") < 0); - assert_se(architecture_from_string(NULL) < 0); - assert_se(architecture_from_string("hoge") < 0); - assert_se(architecture_to_string(-1) == NULL); - assert_se(architecture_from_string(architecture_to_string(0)) == 0); - assert_se(architecture_from_string(architecture_to_string(1)) == 1); + ASSERT_LT(architecture_from_string(""), 0); + ASSERT_LT(architecture_from_string(NULL), 0); + ASSERT_LT(architecture_from_string("hoge"), 0); + ASSERT_NULL(architecture_to_string(-1)); + ASSERT_EQ(architecture_from_string(architecture_to_string(0)), 0); + ASSERT_EQ(architecture_from_string(architecture_to_string(1)), 1); v = detect_virtualization(); if (ERRNO_IS_NEG_PRIVILEGE(v)) return log_tests_skipped("Cannot detect virtualization"); - assert_se(v >= 0); + ASSERT_OK(v); log_info("virtualization=%s id=%s", VIRTUALIZATION_IS_CONTAINER(v) ? "container" : @@ -32,22 +33,34 @@ int main(int argc, char *argv[]) { virtualization_to_string(v)); a = uname_architecture(); - assert_se(a >= 0); + ASSERT_OK(a); p = architecture_to_string(a); assert_se(p); log_info("uname architecture=%s", p); - assert_se(architecture_from_string(p) == a); + ASSERT_EQ(architecture_from_string(p), a); a = native_architecture(); - assert_se(a >= 0); + ASSERT_OK(a); p = architecture_to_string(a); assert_se(p); log_info("native architecture=%s", p); - assert_se(architecture_from_string(p) == a); + ASSERT_EQ(architecture_from_string(p), a); log_info("primary library architecture=" LIB_ARCH_TUPLE); + for (Architecture i = 0; i < _ARCHITECTURE_MAX; i++) { + const char *n = ASSERT_PTR(architecture_to_string(i)); + + /* Let's validate that all architecture names we define are good for inclusion in .v/ + * filename patterns which use "." and "_" as field separators in the filenames. */ + assert(filename_part_is_valid(n)); + assert(!strchr(n, '_')); + assert(!strchr(n, '.')); + + log_info("Good for inclusion in .v/ filenames: %s", n); + } + return 0; } diff --git a/src/test/test-argv-util.c b/src/test/test-argv-util.c index 5bf2903..f7b8f94 100644 --- a/src/test/test-argv-util.c +++ b/src/test/test-argv-util.c @@ -122,6 +122,11 @@ TEST(argv_help) { assert_se(argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "-h"))); assert_se(!argv_looks_like_help(2, STRV_MAKE("program", "arg1"))); assert_se(!argv_looks_like_help(4, STRV_MAKE("program", "arg1", "arg2", "--h"))); + assert_se(!argv_looks_like_help(3, STRV_MAKE("program", "Help", "arg2"))); + assert_se(argv_looks_like_help(5, STRV_MAKE("program", "--help", "arg1", "-h", "--help"))); + assert_se(!argv_looks_like_help(4, STRV_MAKE("program","arg1", "arg2", "-H"))); + assert_se(!argv_looks_like_help(3, STRV_MAKE("program", "--Help", "arg2"))); + } static int intro(void) { diff --git a/src/test/test-arphrd-util.c b/src/test/test-arphrd-util.c index d8dd464..15b7997 100644 --- a/src/test/test-arphrd-util.c +++ b/src/test/test-arphrd-util.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include "arphrd-util.h" @@ -14,11 +16,11 @@ TEST(arphrd) { if (name) { log_info("%i: %s", i, name); - assert_se(arphrd_from_name(name) == i); + ASSERT_EQ(arphrd_from_name(name), i); } } - assert_se(arphrd_to_name(ARPHRD_VOID + 1) == NULL); + ASSERT_NULL(arphrd_to_name(ARPHRD_VOID + 1)); assert_se(arphrd_from_name("huddlduddl") == -EINVAL); assert_se(arphrd_from_name("") == -EINVAL); } diff --git a/src/test/test-ask-password-api.c b/src/test/test-ask-password-api.c index b24159e..e58e868 100644 --- a/src/test/test-ask-password-api.c +++ b/src/test/test-ask-password-api.c @@ -5,14 +5,19 @@ #include "tests.h" TEST(ask_password) { - int r; _cleanup_strv_free_ char **ret = NULL; + int r; + + static const AskPasswordRequest req = { + .message = "hello?", + .keyring = "da key", + }; - r = ask_password_tty(-1, "hello?", "da key", 0, ASK_PASSWORD_CONSOLE_COLOR, NULL, &ret); + r = ask_password_tty(-EBADF, &req, /* until= */ 0, /* flags= */ ASK_PASSWORD_CONSOLE_COLOR, /* flag_file= */ NULL, &ret); if (r == -ECANCELED) - assert_se(ret == NULL); + ASSERT_NULL(ret); else { - assert_se(r >= 0); + ASSERT_OK(r); assert_se(strv_length(ret) == 1); log_info("Got \"%s\"", *ret); } diff --git a/src/test/test-async.c b/src/test/test-async.c index 75bc4d8..ee6bae2 100644 --- a/src/test/test-async.c +++ b/src/test/test-async.c @@ -10,11 +10,24 @@ #include "path-util.h" #include "process-util.h" #include "signal-util.h" +#include "time-util.h" #include "tests.h" #include "tmpfile-util.h" TEST(asynchronous_sync) { - assert_se(asynchronous_sync(NULL) >= 0); + ASSERT_OK(asynchronous_sync(NULL)); +} + +static void wait_fd_closed(int fd) { + for (unsigned trial = 0; trial < 100; trial++) { + usleep_safe(100 * USEC_PER_MSEC); + if (fcntl(fd, F_GETFD) < 0) { + assert_se(errno == EBADF); + return; + } + } + + assert_not_reached(); } TEST(asynchronous_close) { @@ -22,67 +35,72 @@ TEST(asynchronous_close) { int fd, r; fd = mkostemp_safe(name); - assert_se(fd >= 0); + ASSERT_OK(fd); asynchronous_close(fd); - - sleep(1); - - assert_se(fcntl(fd, F_GETFD) == -1); - assert_se(errno == EBADF); + wait_fd_closed(fd); r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, NULL); - assert(r >= 0); + ASSERT_OK(r); if (r == 0) { /* child */ - assert(make_reaper_process(true) >= 0); + ASSERT_OK(make_reaper_process(true)); fd = open("/dev/null", O_RDONLY|O_CLOEXEC); - assert_se(fd >= 0); + ASSERT_OK(fd); asynchronous_close(fd); - - sleep(1); - - assert_se(fcntl(fd, F_GETFD) == -1); - assert_se(errno == EBADF); + wait_fd_closed(fd); _exit(EXIT_SUCCESS); } } +static void wait_rm_rf(const char *path) { + for (unsigned trial = 0; trial < 100; trial++) { + usleep_safe(100 * USEC_PER_MSEC); + if (access(path, F_OK) < 0) { + assert_se(errno == ENOENT); + return; + } + } + + assert_not_reached(); +} + TEST(asynchronous_rm_rf) { _cleanup_free_ char *t = NULL, *k = NULL; int r; - assert_se(mkdtemp_malloc(NULL, &t) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &t)); assert_se(k = path_join(t, "somefile")); - assert_se(touch(k) >= 0); - assert_se(asynchronous_rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(touch(k)); + ASSERT_OK(asynchronous_rm_rf(t, REMOVE_ROOT|REMOVE_PHYSICAL)); + wait_rm_rf(t); - /* Do this once more, form a subreaper. Which is nice, because we can watch the async child even + /* Do this once more, from a subreaper. Which is nice, because we can watch the async child even * though detached */ r = safe_fork("(subreaper)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT, NULL); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) { _cleanup_free_ char *tt = NULL, *kk = NULL; /* child */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - assert_se(make_reaper_process(true) >= 0); + ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD)); + ASSERT_OK(make_reaper_process(true)); - assert_se(mkdtemp_malloc(NULL, &tt) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &tt)); assert_se(kk = path_join(tt, "somefile")); - assert_se(touch(kk) >= 0); - assert_se(asynchronous_rm_rf(tt, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(touch(kk)); + ASSERT_OK(asynchronous_rm_rf(tt, REMOVE_ROOT|REMOVE_PHYSICAL)); for (;;) { siginfo_t si = {}; - assert_se(waitid(P_ALL, 0, &si, WEXITED) >= 0); + ASSERT_OK(waitid(P_ALL, 0, &si, WEXITED)); if (access(tt, F_OK) < 0) { assert_se(errno == ENOENT); diff --git a/src/test/test-aux-scope.c b/src/test/test-aux-scope.c new file mode 100644 index 0000000..0170314 --- /dev/null +++ b/src/test/test-aux-scope.c @@ -0,0 +1,168 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#if HAVE_PIDFD_OPEN +#include +#endif +#include + +#include "sd-event.h" + +#include "bus-error.h" +#include "bus-message.h" +#include "bus-wait-for-jobs.h" +#include "daemon-util.h" +#include "fd-util.h" +#include "log.h" +#include "missing_syscall.h" +#include "process-util.h" +#include "tests.h" + +static int on_sigusr1(sd_event_source *s, const struct signalfd_siginfo *ssi, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = 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; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + PidRef *pids = (PidRef *) userdata; + const char *job; + int r; + + assert(pids); + + r = sd_bus_open_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to acquire bus: %m"); + + r = sd_bus_message_new_method_call(bus, &message, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartAuxiliaryScope"); + if (r < 0) + return log_error_errno(r, "Failed to create bus message: %m"); + + r = sd_bus_message_append_basic(message, 's', "test-aux-scope.scope"); + if (r < 0) + return log_error_errno(r, "Failed to attach scope name: %m"); + + r = sd_bus_message_open_container(message, 'a', "h"); + if (r < 0) + return log_error_errno(r, "Failed to create array of FDs: %m"); + + for (size_t i = 0; i < 10; i++) { + r = sd_bus_message_append_basic(message, 'h', &pids[i].fd); + if (r < 0) + return log_error_errno(r, "Failed to append PIDFD to message: %m"); + } + + r = sd_bus_message_close_container(message); + if (r < 0) + return log_error_errno(r, "Failed to close container: %m"); + + r = sd_bus_message_append(message, "ta(sv)", UINT64_C(0), 1, "Description", "s", "Test auxiliary scope"); + if (r < 0) + return log_error_errno(r, "Failed to append unit properties: %m"); + + r = sd_bus_call(bus, message, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start auxiliary scope: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &job); + if (r < 0) + return log_error_errno(r, "Failed to read reply: %m"); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + + r = bus_wait_for_jobs_one(w, job, false, NULL); + if (r < 0) + return r; + + return 0; +} + +static void destroy_pidrefs(PidRef *pids, size_t npids) { + assert(pids || npids == 0); + + for (size_t i = 0; i < npids; i++) + pidref_done(&pids[i]); + + free(pids); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + PidRef *pids = NULL; + size_t npids = 0; + int r, fd; + + CLEANUP_ARRAY(pids, npids, destroy_pidrefs); + + test_setup_logging(LOG_INFO); + + fd = pidfd_open(getpid_cached(), 0); + if (fd < 0 && (ERRNO_IS_NOT_SUPPORTED(errno) || ERRNO_IS_PRIVILEGE(errno))) + return log_tests_skipped("pidfds are not available"); + else if (fd < 0) { + log_error_errno(errno, "pidfd_open() failed: %m"); + return EXIT_FAILURE; + } + safe_close(fd); + + r = sd_event_new(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + return EXIT_FAILURE; + } + + npids = 10; + pids = new0(PidRef, npids); + assert(pids); + + r = sd_event_add_signal(event, NULL, SIGUSR1|SD_EVENT_SIGNAL_PROCMASK, on_sigusr1, pids); + if (r < 0) { + log_error_errno(r, "Failed to setup SIGUSR1 signal handling: %m"); + return EXIT_FAILURE; + } + + for (size_t i = 0; i < npids; i++) { + PidRef pidref = PIDREF_NULL; + pid_t pid; + + r = safe_fork("(worker)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_CLOSE_ALL_FDS, &pid); + if (r < 0) { + log_error_errno(r, "Failed to fork(): %m"); + return EXIT_FAILURE; + } + + if (r == 0) { + /* Worker */ + sleep(3600); + _exit(EXIT_SUCCESS); + } + + r = pidref_set_pid(&pidref, pid); + if (r < 0) { + log_error_errno(r, "Failed to initialize PID ref: %m"); + return EXIT_FAILURE; + } + + assert_se(pidref.pid == pid); + assert_se(pidref.fd != -EBADF); + + pids[i] = TAKE_PIDREF(pidref); + } + + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + return log_error_errno(r, "Failed to send readiness notification: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return 0; +} diff --git a/src/test/test-barrier.c b/src/test/test-barrier.c index 7e8bfc0..1e0da59 100644 --- a/src/test/test-barrier.c +++ b/src/test/test-barrier.c @@ -30,7 +30,7 @@ static void set_alarm(usec_t usecs) { struct itimerval v = { }; timeval_store(&v.it_value, usecs); - assert_se(setitimer(ITIMER_REAL, &v, NULL) >= 0); + ASSERT_OK(setitimer(ITIMER_REAL, &v, NULL)); } #define TEST_BARRIER(_FUNCTION, _CHILD_CODE, _WAIT_CHILD, _PARENT_CODE, _WAIT_PARENT) \ @@ -38,14 +38,14 @@ static void set_alarm(usec_t usecs) { Barrier b = BARRIER_NULL; \ pid_t pid1, pid2; \ \ - assert_se(barrier_create(&b) >= 0); \ - assert_se(b.me > 0); \ - assert_se(b.them > 0); \ - assert_se(b.pipe[0] > 0); \ - assert_se(b.pipe[1] > 0); \ + ASSERT_OK(barrier_create(&b)); \ + ASSERT_GT(b.me, 0); \ + ASSERT_GT(b.them, 0); \ + ASSERT_GT(b.pipe[0], 0); \ + ASSERT_GT(b.pipe[1], 0); \ \ pid1 = fork(); \ - assert_se(pid1 >= 0); \ + ASSERT_OK(pid1); \ if (pid1 == 0) { \ barrier_set_role(&b, BARRIER_CHILD); \ { _CHILD_CODE; } \ @@ -53,7 +53,7 @@ static void set_alarm(usec_t usecs) { } \ \ pid2 = fork(); \ - assert_se(pid2 >= 0); \ + ASSERT_OK(pid2); \ if (pid2 == 0) { \ barrier_set_role(&b, BARRIER_PARENT); \ { _PARENT_CODE; } \ @@ -71,16 +71,16 @@ static void set_alarm(usec_t usecs) { ({ \ int pidr, status; \ pidr = waitpid(_pid, &status, 0); \ - assert_se(pidr == _pid); \ + ASSERT_EQ(pidr, _pid); \ assert_se(WIFEXITED(status)); \ - assert_se(WEXITSTATUS(status) == 42); \ + ASSERT_EQ(WEXITSTATUS(status), 42); \ }) #define TEST_BARRIER_WAIT_ALARM(_pid) \ ({ \ int pidr, status; \ pidr = waitpid(_pid, &status, 0); \ - assert_se(pidr == _pid); \ + ASSERT_EQ(pidr, _pid); \ assert_se(WIFSIGNALED(status)); \ assert_se(WTERMSIG(status) == SIGALRM); \ }) diff --git a/src/test/test-bitmap.c b/src/test/test-bitmap.c index 8acf833..b91b293 100644 --- a/src/test/test-bitmap.c +++ b/src/test/test-bitmap.c @@ -12,50 +12,50 @@ int main(int argc, const char *argv[]) { b = bitmap_new(); assert_se(b); - assert_se(bitmap_ensure_allocated(&b) == 0); + ASSERT_EQ(bitmap_ensure_allocated(&b), 0); b = bitmap_free(b); - assert_se(bitmap_ensure_allocated(&b) == 0); + ASSERT_EQ(bitmap_ensure_allocated(&b), 0); - assert_se(bitmap_isset(b, 0) == false); - assert_se(bitmap_isset(b, 1) == false); - assert_se(bitmap_isset(b, 256) == false); - assert_se(bitmap_isclear(b) == true); + ASSERT_FALSE(bitmap_isset(b, 0)); + ASSERT_FALSE(bitmap_isset(b, 1)); + ASSERT_FALSE(bitmap_isset(b, 256)); + ASSERT_TRUE(bitmap_isclear(b)); - assert_se(bitmap_set(b, 0) == 0); - assert_se(bitmap_isset(b, 0) == true); - assert_se(bitmap_isclear(b) == false); + ASSERT_EQ(bitmap_set(b, 0), 0); + ASSERT_TRUE(bitmap_isset(b, 0)); + ASSERT_FALSE(bitmap_isclear(b)); bitmap_unset(b, 0); - assert_se(bitmap_isset(b, 0) == false); - assert_se(bitmap_isclear(b) == true); + ASSERT_FALSE(bitmap_isset(b, 0)); + ASSERT_TRUE(bitmap_isclear(b)); - assert_se(bitmap_set(b, 1) == 0); - assert_se(bitmap_isset(b, 1) == true); - assert_se(bitmap_isclear(b) == false); + ASSERT_EQ(bitmap_set(b, 1), 0); + ASSERT_TRUE(bitmap_isset(b, 1)); + ASSERT_FALSE(bitmap_isclear(b)); bitmap_unset(b, 1); - assert_se(bitmap_isset(b, 1) == false); - assert_se(bitmap_isclear(b) == true); + ASSERT_FALSE(bitmap_isset(b, 1)); + ASSERT_TRUE(bitmap_isclear(b)); - assert_se(bitmap_set(b, 256) == 0); - assert_se(bitmap_isset(b, 256) == true); - assert_se(bitmap_isclear(b) == false); + ASSERT_EQ(bitmap_set(b, 256), 0); + ASSERT_TRUE(bitmap_isset(b, 256)); + ASSERT_FALSE(bitmap_isclear(b)); bitmap_unset(b, 256); - assert_se(bitmap_isset(b, 256) == false); - assert_se(bitmap_isclear(b) == true); + ASSERT_FALSE(bitmap_isset(b, 256)); + ASSERT_TRUE(bitmap_isclear(b)); - assert_se(bitmap_set(b, 32) == 0); + ASSERT_EQ(bitmap_set(b, 32), 0); bitmap_unset(b, 0); - assert_se(bitmap_isset(b, 32) == true); + ASSERT_TRUE(bitmap_isset(b, 32)); bitmap_unset(b, 32); BITMAP_FOREACH(n, NULL) assert_not_reached(); - assert_se(bitmap_set(b, 0) == 0); - assert_se(bitmap_set(b, 1) == 0); - assert_se(bitmap_set(b, 256) == 0); + ASSERT_EQ(bitmap_set(b, 0), 0); + ASSERT_EQ(bitmap_set(b, 1), 0); + ASSERT_EQ(bitmap_set(b, 256), 0); BITMAP_FOREACH(n, b) { - assert_se(n == i); + ASSERT_EQ(n, i); if (i == 0) i = 1; else if (i == 1) @@ -64,12 +64,12 @@ int main(int argc, const char *argv[]) { i = UINT_MAX; } - assert_se(i == UINT_MAX); + ASSERT_EQ(i, UINT_MAX); i = 0; BITMAP_FOREACH(n, b) { - assert_se(n == i); + ASSERT_EQ(n, i); if (i == 0) i = 1; else if (i == 1) @@ -78,38 +78,38 @@ int main(int argc, const char *argv[]) { i = UINT_MAX; } - assert_se(i == UINT_MAX); + ASSERT_EQ(i, UINT_MAX); b2 = bitmap_copy(b); assert_se(b2); - assert_se(bitmap_equal(b, b2) == true); - assert_se(bitmap_equal(b, b) == true); - assert_se(bitmap_equal(b, NULL) == false); - assert_se(bitmap_equal(NULL, b) == false); - assert_se(bitmap_equal(NULL, NULL) == true); + ASSERT_TRUE(bitmap_equal(b, b2)); + ASSERT_TRUE(bitmap_equal(b, b)); + ASSERT_FALSE(bitmap_equal(b, NULL)); + ASSERT_FALSE(bitmap_equal(NULL, b)); + ASSERT_TRUE(bitmap_equal(NULL, NULL)); bitmap_clear(b); - assert_se(bitmap_isclear(b) == true); - assert_se(bitmap_equal(b, b2) == false); + ASSERT_TRUE(bitmap_isclear(b)); + ASSERT_FALSE(bitmap_equal(b, b2)); b2 = bitmap_free(b2); assert_se(bitmap_set(b, UINT_MAX) == -ERANGE); b = bitmap_free(b); - assert_se(bitmap_ensure_allocated(&b) == 0); - assert_se(bitmap_ensure_allocated(&b2) == 0); + ASSERT_EQ(bitmap_ensure_allocated(&b), 0); + ASSERT_EQ(bitmap_ensure_allocated(&b2), 0); assert_se(bitmap_equal(b, b2)); - assert_se(bitmap_set(b, 0) == 0); + ASSERT_EQ(bitmap_set(b, 0), 0); bitmap_unset(b, 0); assert_se(bitmap_equal(b, b2)); - assert_se(bitmap_set(b, 1) == 0); + ASSERT_EQ(bitmap_set(b, 1), 0); bitmap_clear(b); assert_se(bitmap_equal(b, b2)); - assert_se(bitmap_set(b, 0) == 0); - assert_se(bitmap_set(b2, 0) == 0); + ASSERT_EQ(bitmap_set(b, 0), 0); + ASSERT_EQ(bitmap_set(b2, 0), 0); assert_se(bitmap_equal(b, b2)); return 0; diff --git a/src/test/test-blockdev-util.c b/src/test/test-blockdev-util.c index 134386c..19626e0 100644 --- a/src/test/test-blockdev-util.c +++ b/src/test/test-blockdev-util.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "blockdev-util.h" +#include "device-util.h" #include "errno-util.h" +#include "fd-util.h" #include "tests.h" static void test_path_is_encrypted_one(const char *p, int expect) { @@ -38,4 +40,39 @@ TEST(path_is_encrypted) { test_path_is_encrypted_one("/dev", booted > 0 ? false : -1); } +TEST(partscan_enabled) { + + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + int r; + + assert_se(sd_device_enumerator_new(&e) >= 0); + assert_se(sd_device_enumerator_allow_uninitialized(e) >= 0); + assert_se(sd_device_enumerator_add_match_subsystem(e, "block", /* match = */ true) >= 0); + + FOREACH_DEVICE(e, dev) { + _cleanup_close_ int fd = -EBADF; + const char *name; + + r = sd_device_get_devname(dev, &name); + if (r < 0) { + log_warning_errno(r, "Found block device without a name, skipping."); + continue; + } + + fd = sd_device_open(dev, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_warning_errno(fd, "Found block device '%s' which we cannot open, skipping: %m", name); + continue; + } + + r = blockdev_partscan_enabled(fd); + if (r < 0) { + log_warning_errno(r, "Failed to determine if block device '%s' has partition scanning enabled, skipping: %m", name); + continue; + } + + log_info("%s has partition scanning enabled: %s", name, yes_no(r)); + } +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-boot-timestamps.c b/src/test/test-boot-timestamps.c index c3e4876..6771729 100644 --- a/src/test/test-boot-timestamps.c +++ b/src/test/test-boot-timestamps.c @@ -76,11 +76,11 @@ int main(int argc, char* argv[]) { test_setup_logging(LOG_DEBUG); p = test_acpi_fpdt(); - assert_se(p >= 0); + ASSERT_OK(p); q = test_efi_loader(); - assert_se(q >= 0); + ASSERT_OK(q); r = test_boot_timestamps(); - assert_se(r >= 0); + ASSERT_OK(r); if (p == 0 && q == 0 && r == 0) return log_tests_skipped("access to firmware variables not possible"); diff --git a/src/test/test-bootspec.c b/src/test/test-bootspec.c index 18611fc..88faa1e 100644 --- a/src/test/test-bootspec.c +++ b/src/test/test-bootspec.c @@ -63,7 +63,7 @@ TEST_RET(bootspec_sort) { _cleanup_(rm_rf_physical_and_freep) char *d = NULL; _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; - assert_se(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d) >= 0); + ASSERT_OK(mkdtemp_malloc("/tmp/bootspec-testXXXXXX", &d)); for (size_t i = 0; i < ELEMENTSOF(entries); i++) { _cleanup_free_ char *j = NULL; @@ -71,24 +71,24 @@ TEST_RET(bootspec_sort) { j = path_join(d, "/loader/entries/", entries[i].fname); assert_se(j); - assert_se(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) >= 0); + ASSERT_OK(write_string_file(j, entries[i].contents, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755)); } - assert_se(boot_config_load(&config, d, NULL) >= 0); + ASSERT_OK(boot_config_load(&config, d, NULL)); assert_se(config.n_entries == 6); /* First, because has sort key, and its the lowest one */ - assert_se(streq(config.entries[0].id, "d.conf")); + ASSERT_STREQ(config.entries[0].id, "d.conf"); /* These two have a sort key, and newest must be first */ - assert_se(streq(config.entries[1].id, "cx.conf")); - assert_se(streq(config.entries[2].id, "c.conf")); + ASSERT_STREQ(config.entries[1].id, "cx.conf"); + ASSERT_STREQ(config.entries[2].id, "c.conf"); /* The following ones have no sort key, hence order by version compared ids, lowest first */ - assert_se(streq(config.entries[3].id, "b.conf")); - assert_se(streq(config.entries[4].id, "a-10.conf")); - assert_se(streq(config.entries[5].id, "a-5.conf")); + ASSERT_STREQ(config.entries[3].id, "b.conf"); + ASSERT_STREQ(config.entries[4].id, "a-10.conf"); + ASSERT_STREQ(config.entries[5].id, "a-5.conf"); return 0; } @@ -101,7 +101,7 @@ static void test_extract_tries_one(const char *fname, int ret, const char *strip if (ret < 0) return; - assert_se(streq_ptr(p, stripped)); + ASSERT_STREQ(p, stripped); assert_se(l == tries_left); assert_se(d == tries_done); } @@ -188,23 +188,22 @@ TEST_RET(bootspec_boot_config_find_entry) { assert_se(boot_config_load(&config, d, NULL) >= 0); assert_se(config.n_entries == 2); - // Test finding the first entry + /* Test finding the first entry */ BootEntry *entry = boot_config_find_entry(&config, "a-10.conf"); assert_se(entry && streq(entry->id, "a-10.conf")); - // Test finding the second entry + /* Test finding the second entry */ entry = boot_config_find_entry(&config, "a-05.conf"); assert_se(entry && streq(entry->id, "a-05.conf")); - // Test finding a non-existent entry + /* Test finding a non-existent entry */ entry = boot_config_find_entry(&config, "nonexistent.conf"); - assert_se(entry == NULL); + ASSERT_NULL(entry); - // Test case-insensitivity + /* Test case-insensitivity */ entry = boot_config_find_entry(&config, "A-10.CONF"); assert_se(entry && streq(entry->id, "a-10.conf")); - return 0; } diff --git a/src/test/test-bpf-devices.c b/src/test/test-bpf-devices.c index 4bd606e..f1db021 100644 --- a/src/test/test-bpf-devices.c +++ b/src/test/test-bpf-devices.c @@ -22,13 +22,13 @@ static void test_policy_closed(const char *cgroup_path, BPFProgram **installed_p log_info("/* %s */", __func__); r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_CLOSED, true); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_static(prog, cgroup_path); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_CLOSED, true, cgroup_path, installed_prog); - assert_se(r >= 0); + ASSERT_OK(r); FOREACH_STRING(s, "/dev/null", "/dev/zero", @@ -59,19 +59,19 @@ static void test_policy_strict(const char *cgroup_path, BPFProgram **installed_p log_info("/* %s */", __func__); r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/null", CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/random", CGROUP_DEVICE_READ); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_device(prog, cgroup_path, "/dev/zero", CGROUP_DEVICE_WRITE); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); - assert_se(r >= 0); + ASSERT_OK(r); { _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; @@ -136,13 +136,13 @@ static void test_policy_allow_list_major(const char *pattern, const char *cgroup log_info("/* %s(%s) */", __func__, pattern); r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_major(prog, cgroup_path, pattern, 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); - assert_se(r >= 0); + ASSERT_OK(r); /* /dev/null, /dev/full have major==1, /dev/tty has major==5 */ { @@ -195,13 +195,13 @@ static void test_policy_allow_list_major_star(char type, const char *cgroup_path log_info("/* %s(type=%c) */", __func__, type); r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, true); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_allow_list_major(prog, cgroup_path, "*", type, CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); - assert_se(r >= 0); + ASSERT_OK(r); r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, true, cgroup_path, installed_prog); - assert_se(r >= 0); + ASSERT_OK(r); { _cleanup_close_ int fd = -EBADF; @@ -226,7 +226,7 @@ static void test_policy_empty(bool add_mismatched, const char *cgroup_path, BPFP log_info("/* %s(add_mismatched=%s) */", __func__, yes_no(add_mismatched)); r = bpf_devices_cgroup_init(&prog, CGROUP_DEVICE_POLICY_STRICT, add_mismatched); - assert_se(r >= 0); + ASSERT_OK(r); if (add_mismatched) { r = bpf_devices_allow_list_major(prog, cgroup_path, "foobarxxx", 'c', CGROUP_DEVICE_READ|CGROUP_DEVICE_WRITE); @@ -234,7 +234,7 @@ static void test_policy_empty(bool add_mismatched, const char *cgroup_path, BPFP } r = bpf_devices_apply_policy(&prog, CGROUP_DEVICE_POLICY_STRICT, false, cgroup_path, installed_prog); - assert_se(r >= 0); + ASSERT_OK(r); { _cleanup_close_ int fd = -EBADF; @@ -258,7 +258,7 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + ASSERT_OK(getrlimit(RLIMIT_MEMLOCK, &rl)); rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); (void) setrlimit(RLIMIT_MEMLOCK, &rl); @@ -278,10 +278,10 @@ int main(int argc, char *argv[]) { r = bpf_devices_supported(); if (r == 0) return log_tests_skipped("BPF device filter not supported"); - assert_se(r == 1); + ASSERT_EQ(r, 1); r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, cgroup, NULL, &controller_path); - assert_se(r >= 0); + ASSERT_OK(r); _cleanup_(bpf_program_freep) BPFProgram *prog = NULL; @@ -297,11 +297,11 @@ int main(int argc, char *argv[]) { test_policy_empty(false, cgroup, &prog); test_policy_empty(true, cgroup, &prog); - assert_se(path_extract_directory(cgroup, &parent) >= 0); + ASSERT_OK(path_extract_directory(cgroup, &parent)); - assert_se(cg_mask_supported(&supported) >= 0); + ASSERT_OK(cg_mask_supported(&supported)); r = cg_attach_everywhere(supported, parent, 0, NULL, NULL); - assert_se(r >= 0); + ASSERT_OK(r); return 0; } diff --git a/src/test/test-bpf-firewall.c b/src/test/test-bpf-firewall.c index c4175bc..cc67774 100644 --- a/src/test/test-bpf-firewall.c +++ b/src/test/test-bpf-firewall.c @@ -39,7 +39,7 @@ int main(int argc, char *argv[]) { if (detect_container() > 0) return log_tests_skipped("test-bpf-firewall fails inside LXC and Docker containers: https://github.com/systemd/systemd/issues/9666"); - assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + ASSERT_OK(getrlimit(RLIMIT_MEMLOCK, &rl)); rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); (void) setrlimit(RLIMIT_MEMLOCK, &rl); @@ -50,21 +50,25 @@ int main(int argc, char *argv[]) { if (r == -ENOMEDIUM) return log_tests_skipped("cgroupfs not available"); + r = find_executable("ping", NULL); + if (r < 0) + return log_tests_skipped_errno(r, "Can't find ping binary: %m"); + _cleanup_free_ char *unit_dir = NULL; - assert_se(get_testdata_dir("units", &unit_dir) >= 0); - assert_se(set_unit_path(unit_dir) >= 0); + ASSERT_OK(get_testdata_dir("units", &unit_dir)); + ASSERT_OK(set_unit_path(unit_dir)); assert_se(runtime_dir = setup_fake_runtime_dir()); r = bpf_program_new(BPF_PROG_TYPE_CGROUP_SKB, "sd_trivial", &p); - assert_se(r == 0); + ASSERT_EQ(r, 0); r = bpf_program_add_instructions(p, exit_insn, ELEMENTSOF(exit_insn)); - assert_se(r == 0); + ASSERT_EQ(r, 0); r = bpf_firewall_supported(); if (r == BPF_FIREWALL_UNSUPPORTED) return log_tests_skipped("BPF firewalling not supported"); - assert_se(r > 0); + ASSERT_GT(r, 0); if (r == BPF_FIREWALL_SUPPORTED_WITH_MULTI) { log_notice("BPF firewalling with BPF_F_ALLOW_MULTI supported. Yay!"); @@ -73,7 +77,7 @@ int main(int argc, char *argv[]) { log_notice("BPF firewalling (though without BPF_F_ALLOW_MULTI) supported. Good."); r = bpf_program_load_kernel(p, log_buf, ELEMENTSOF(log_buf)); - assert_se(r >= 0); + ASSERT_OK(r); if (test_custom_filter) { zero(attr); @@ -94,29 +98,29 @@ int main(int argc, char *argv[]) { /* The simple tests succeeded. Now let's try full unit-based use-case. */ - assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); assert_se(u = unit_new(m, sizeof(Service))); - assert_se(unit_add_name(u, "foo.service") == 0); + ASSERT_EQ(unit_add_name(u, "foo.service"), 0); assert_se(cc = unit_get_cgroup_context(u)); u->perpetual = true; cc->ip_accounting = true; - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "10.0.1.0/24", &cc->ip_address_allow, NULL) == 0); - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "127.0.0.2", &cc->ip_address_allow, NULL) == 0); - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.3", &cc->ip_address_deny, NULL) == 0); - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "10.0.3.2/24", &cc->ip_address_deny, NULL) == 0); - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.1/25", &cc->ip_address_deny, NULL) == 0); - assert_se(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.4", &cc->ip_address_deny, NULL) == 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "10.0.1.0/24", &cc->ip_address_allow, NULL), 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressAllow", 0, "127.0.0.2", &cc->ip_address_allow, NULL), 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.3", &cc->ip_address_deny, NULL), 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "10.0.3.2/24", &cc->ip_address_deny, NULL), 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.1/25", &cc->ip_address_deny, NULL), 0); + ASSERT_EQ(config_parse_in_addr_prefixes(u->id, "filename", 1, "Service", 1, "IPAddressDeny", 0, "127.0.0.4", &cc->ip_address_deny, NULL), 0); assert_se(set_size(cc->ip_address_allow) == 2); assert_se(set_size(cc->ip_address_deny) == 4); /* The deny list is defined redundantly, let's ensure it will be properly reduced */ - assert_se(in_addr_prefixes_reduce(cc->ip_address_allow) >= 0); - assert_se(in_addr_prefixes_reduce(cc->ip_address_deny) >= 0); + ASSERT_OK(in_addr_prefixes_reduce(cc->ip_address_allow)); + ASSERT_OK(in_addr_prefixes_reduce(cc->ip_address_deny)); assert_se(set_size(cc->ip_address_allow) == 2); assert_se(set_size(cc->ip_address_deny) == 2); @@ -155,28 +159,29 @@ int main(int argc, char *argv[]) { return log_tests_skipped("Kernel doesn't support the necessary bpf bits (masked out via seccomp?)"); assert_se(r >= 0); - assert_se(u->ip_bpf_ingress); - assert_se(u->ip_bpf_egress); + CGroupRuntime *crt = ASSERT_PTR(unit_get_cgroup_runtime(u)); + assert_se(crt->ip_bpf_ingress); + assert_se(crt->ip_bpf_egress); - r = bpf_program_load_kernel(u->ip_bpf_ingress, log_buf, ELEMENTSOF(log_buf)); + r = bpf_program_load_kernel(crt->ip_bpf_ingress, log_buf, ELEMENTSOF(log_buf)); log_notice("log:"); log_notice("-------"); log_notice("%s", log_buf); log_notice("-------"); - assert_se(r >= 0); + ASSERT_OK(r); - r = bpf_program_load_kernel(u->ip_bpf_egress, log_buf, ELEMENTSOF(log_buf)); + r = bpf_program_load_kernel(crt->ip_bpf_egress, log_buf, ELEMENTSOF(log_buf)); log_notice("log:"); log_notice("-------"); log_notice("%s", log_buf); log_notice("-------"); - assert_se(r >= 0); + ASSERT_OK(r); - assert_se(unit_start(u, NULL) >= 0); + ASSERT_OK(unit_start(u, NULL)); while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) assert_se(sd_event_run(m->event, UINT64_MAX) >= 0); @@ -201,7 +206,7 @@ int main(int argc, char *argv[]) { SERVICE(u)->type = SERVICE_ONESHOT; u->load_state = UNIT_LOADED; - assert_se(unit_start(u, NULL) >= 0); + ASSERT_OK(unit_start(u, NULL)); while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) assert_se(sd_event_run(m->event, UINT64_MAX) >= 0); diff --git a/src/test/test-bpf-foreign-programs.c b/src/test/test-bpf-foreign-programs.c index 35c7e0d..34ccb74 100644 --- a/src/test/test-bpf-foreign-programs.c +++ b/src/test/test-bpf-foreign-programs.c @@ -253,7 +253,7 @@ static int test_bpf_cgroup_programs(Manager *m, const char *unit_name, const Tes while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { r = sd_event_run(m->event, UINT64_MAX); if (r < 0) - return log_error_errno(errno, "Event run failed %m"); + return log_error_errno(r, "Event run failed %m"); } cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; @@ -282,7 +282,7 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return log_tests_skipped("not running as root"); - assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); + ASSERT_OK(getrlimit(RLIMIT_MEMLOCK, &rl)); rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); @@ -297,34 +297,34 @@ int main(int argc, char *argv[]) { if (r == -ENOMEDIUM) return log_tests_skipped("cgroupfs not available"); - assert_se(get_testdata_dir("units", &unit_dir) >= 0); - assert_se(set_unit_path(unit_dir) >= 0); + ASSERT_OK(get_testdata_dir("units", &unit_dir)); + ASSERT_OK(set_unit_path(unit_dir)); assert_se(runtime_dir = setup_fake_runtime_dir()); - assert_se(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m) >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + ASSERT_OK(manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); - assert_se(test_bpf_cgroup_programs(m, - "single_prog.service", single_prog, ELEMENTSOF(single_prog)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + ASSERT_OK(test_bpf_cgroup_programs(m, + "single_prog.service", single_prog, ELEMENTSOF(single_prog))); + ASSERT_OK(test_bpf_cgroup_programs(m, "multi_prog_same_hook.service", - multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + multi_prog_same_hook, ELEMENTSOF(multi_prog_same_hook))); + ASSERT_OK(test_bpf_cgroup_programs(m, "same_prog_multi_hook.service", - same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + same_prog_multi_hook, ELEMENTSOF(same_prog_multi_hook))); + ASSERT_OK(test_bpf_cgroup_programs(m, "same_prog_multi_option_0.service", - same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + same_prog_multi_option_0, ELEMENTSOF(same_prog_multi_option_0))); + ASSERT_OK(test_bpf_cgroup_programs(m, "same_prog_multi_option_1.service", - same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + same_prog_multi_option_1, ELEMENTSOF(same_prog_multi_option_1))); + ASSERT_OK(test_bpf_cgroup_programs(m, "same_prog_same_hook.service", same_prog_same_hook, - ELEMENTSOF(same_prog_same_hook)) >= 0); - assert_se(test_bpf_cgroup_programs(m, + ELEMENTSOF(same_prog_same_hook))); + ASSERT_OK(test_bpf_cgroup_programs(m, "path_split_test.service", path_split_test, - ELEMENTSOF(path_split_test)) >= 0); + ELEMENTSOF(path_split_test))); return 0; } diff --git a/src/test/test-bpf-lsm.c b/src/test/test-bpf-lsm.c deleted file mode 100644 index 42ea64c..0000000 --- a/src/test/test-bpf-lsm.c +++ /dev/null @@ -1,102 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "bpf-lsm.h" -#include "load-fragment.h" -#include "manager.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "rm-rf.h" -#include "service.h" -#include "strv.h" -#include "tests.h" -#include "unit.h" -#include "virt.h" - -static int test_restrict_filesystems(Manager *m, const char *unit_name, const char *file_path, char **allowed_filesystems) { - _cleanup_free_ char *exec_start = NULL; - _cleanup_(unit_freep) Unit *u = NULL; - ExecContext *ec = NULL; - int cld_code, r; - - assert_se(u = unit_new(m, sizeof(Service))); - assert_se(unit_add_name(u, unit_name) == 0); - assert_se(ec = unit_get_exec_context(u)); - - STRV_FOREACH(allow_filesystem, allowed_filesystems) { - r = config_parse_restrict_filesystems( - u->id, "filename", 1, "Service", 1, "RestrictFileSystems", 0, - *allow_filesystem, ec, u); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to parse RestrictFileSystems: %m"); - } - - assert_se(exec_start = strjoin("cat ", file_path)); - r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", - SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u); - if (r < 0) - return log_error_errno(r, "Failed to parse ExecStart"); - - SERVICE(u)->type = SERVICE_ONESHOT; - u->load_state = UNIT_LOADED; - - r = unit_start(u, NULL); - if (r < 0) - return log_error_errno(r, "Unit start failed %m"); - - while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { - r = sd_event_run(m->event, UINT64_MAX); - if (r < 0) - return log_error_errno(errno, "Event run failed %m"); - } - - cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; - if (cld_code != CLD_EXITED) - return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code)); - - if (SERVICE(u)->state != SERVICE_DEAD) - return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; - _cleanup_(manager_freep) Manager *m = NULL; - _cleanup_free_ char *unit_dir = NULL; - struct rlimit rl; - int r; - - test_setup_logging(LOG_DEBUG); - - assert_se(getrlimit(RLIMIT_MEMLOCK, &rl) >= 0); - rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); - (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); - - if (!can_memlock()) - return log_tests_skipped("Can't use mlock()"); - - if (!lsm_bpf_supported(/* initialize = */ true)) - return log_tests_skipped("LSM BPF hooks are not supported"); - - r = enter_cgroup_subroot(NULL); - if (r == -ENOMEDIUM) - return log_tests_skipped("cgroupfs not available"); - - assert_se(get_testdata_dir("units", &unit_dir) >= 0); - assert_se(set_unit_path(unit_dir) >= 0); - assert_se(runtime_dir = setup_fake_runtime_dir()); - - assert_se(manager_new(RUNTIME_SCOPE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m) >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); - - /* We need to enable access to the filesystem where the binary is so we - * add @common-block */ - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block")) < 0); - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block")) >= 0); - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "~tracefs")) < 0); - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block")) < 0); - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block")) >= 0); - assert_se(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("~debugfs")) < 0); - - return 0; -} diff --git a/src/test/test-bpf-restrict-fs.c b/src/test/test-bpf-restrict-fs.c new file mode 100644 index 0000000..7ece337 --- /dev/null +++ b/src/test/test-bpf-restrict-fs.c @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bpf-restrict-fs.h" +#include "load-fragment.h" +#include "manager.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#include "service.h" +#include "strv.h" +#include "tests.h" +#include "unit.h" +#include "virt.h" + +static int test_restrict_filesystems(Manager *m, const char *unit_name, const char *file_path, char **allowed_filesystems) { + _cleanup_free_ char *exec_start = NULL; + _cleanup_(unit_freep) Unit *u = NULL; + ExecContext *ec = NULL; + int cld_code, r; + + assert_se(u = unit_new(m, sizeof(Service))); + assert_se(unit_add_name(u, unit_name) == 0); + assert_se(ec = unit_get_exec_context(u)); + + STRV_FOREACH(allow_filesystem, allowed_filesystems) { + r = config_parse_restrict_filesystems( + u->id, "filename", 1, "Service", 1, "RestrictFileSystems", 0, + *allow_filesystem, ec, u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to parse RestrictFileSystems: %m"); + } + + assert_se(exec_start = strjoin("cat ", file_path)); + r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", + SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u); + if (r < 0) + return log_error_errno(r, "Failed to parse ExecStart"); + + SERVICE(u)->type = SERVICE_ONESHOT; + u->load_state = UNIT_LOADED; + + r = unit_start(u, NULL); + if (r < 0) + return log_error_errno(r, "Unit start failed %m"); + + while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { + r = sd_event_run(m->event, UINT64_MAX); + if (r < 0) + return log_error_errno(r, "Event run failed %m"); + } + + cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; + if (cld_code != CLD_EXITED) + return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "ExecStart didn't exited, code='%s'", sigchld_code_to_string(cld_code)); + + if (SERVICE(u)->state != SERVICE_DEAD) + return log_error_errno(-SYNTHETIC_ERRNO(EBUSY), "Service is not dead"); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(rm_rf_physical_and_freep) char *runtime_dir = NULL; + _cleanup_(manager_freep) Manager *m = NULL; + _cleanup_free_ char *unit_dir = NULL; + struct rlimit rl; + int r; + + test_setup_logging(LOG_DEBUG); + + ASSERT_OK(getrlimit(RLIMIT_MEMLOCK, &rl)); + rl.rlim_cur = rl.rlim_max = MAX(rl.rlim_max, CAN_MEMLOCK_SIZE); + (void) setrlimit_closest(RLIMIT_MEMLOCK, &rl); + + if (!can_memlock()) + return log_tests_skipped("Can't use mlock()"); + + if (!bpf_restrict_fs_supported(/* initialize = */ true)) + return log_tests_skipped("LSM BPF hooks are not supported"); + + r = enter_cgroup_subroot(NULL); + if (r == -ENOMEDIUM) + return log_tests_skipped("cgroupfs not available"); + + ASSERT_OK(get_testdata_dir("units", &unit_dir)); + ASSERT_OK(set_unit_path(unit_dir)); + assert_se(runtime_dir = setup_fake_runtime_dir()); + + ASSERT_OK(manager_new(RUNTIME_SCOPE_SYSTEM, MANAGER_TEST_RUN_BASIC, &m)); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); + + /* We need to enable access to the filesystem where the binary is so we + * add @common-block and @application */ + ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("@common-block", "@application")), 0); + ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "@application"))); + ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/tracing/printk_formats", STRV_MAKE("tracefs", "@common-block", "@application", "~tracefs")), 0); + ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("@common-block", "@application")), 0); + ASSERT_OK(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("debugfs", "@common-block", "@application"))); + ASSERT_LT(test_restrict_filesystems(m, "restrict_filesystems_test.service", "/sys/kernel/debug/sleep_time", STRV_MAKE("~debugfs")), 0); + + return 0; +} diff --git a/src/test/test-build-path.c b/src/test/test-build-path.c new file mode 100644 index 0000000..661b5fc --- /dev/null +++ b/src/test/test-build-path.c @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "build-path.h" +#include "log.h" +#include "string-util.h" + +int main(int argc, char* argv[]) { + _cleanup_free_ char *p = NULL; + int r; + + r = get_build_exec_dir(&p); + if (r == -ENOEXEC) + log_info("Not run from build dir."); + else if (r < 0) + log_error_errno(r, "Failed to find build dir: %m"); + else + log_info("%s", strna(p)); + + return 0; +} diff --git a/src/test/test-bus-util.c b/src/test/test-bus-util.c index 2f52bca..2cf44d6 100644 --- a/src/test/test-bus-util.c +++ b/src/test/test-bus-util.c @@ -11,7 +11,7 @@ static int callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) static void destroy_callback(void *userdata) { int *n_called = userdata; - (*n_called) ++; + (*n_called)++; } TEST(destroy_callback) { diff --git a/src/test/test-calendarspec.c b/src/test/test-calendarspec.c index 18a0f8f..7cda114 100644 --- a/src/test/test-calendarspec.c +++ b/src/test/test-calendarspec.c @@ -16,24 +16,24 @@ static void _test_one(int line, const char *input, const char *output) { r = calendar_spec_from_string(input, &c); if (r < 0) log_error_errno(r, "Failed to parse \"%s\": %m", input); - assert_se(r >= 0); + ASSERT_OK(r); - assert_se(calendar_spec_to_string(c, &p) >= 0); + ASSERT_OK(calendar_spec_to_string(c, &p)); log_info("line %d: \"%s\" → \"%s\"%s%s", line, input, p, !streq(p, output) ? " expected:" : "", !streq(p, output) ? output : ""); - assert_se(streq(p, output)); + ASSERT_STREQ(p, output); u = now(CLOCK_REALTIME); r = calendar_spec_next_usec(c, u, &u); log_info("Next: %s", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP(u)); c = calendar_spec_free(c); - assert_se(calendar_spec_from_string(p, &c) >= 0); - assert_se(calendar_spec_to_string(c, &q) >= 0); + ASSERT_OK(calendar_spec_from_string(p, &c)); + ASSERT_OK(calendar_spec_to_string(c, &q)); - assert_se(streq(q, p)); + ASSERT_STREQ(q, p); } #define test_one(input, output) _test_one(__LINE__, input, output) @@ -53,7 +53,7 @@ static void _test_next(int line, const char *input, const char *new_tz, usec_t a assert_se(set_unset_env("TZ", new_tz, true) == 0); tzset(); - assert_se(calendar_spec_from_string(input, &c) >= 0); + ASSERT_OK(calendar_spec_from_string(input, &c)); log_info("line %d: \"%s\" new_tz=%s", line, input, strnull(new_tz)); @@ -82,11 +82,11 @@ TEST(timestamp) { assert_se(format_timestamp_style(buf, sizeof buf, x, TIMESTAMP_US)); log_info("%s", buf); - assert_se(calendar_spec_from_string(buf, &c) >= 0); - assert_se(calendar_spec_to_string(c, &t) >= 0); + ASSERT_OK(calendar_spec_from_string(buf, &c)); + ASSERT_OK(calendar_spec_to_string(c, &t)); log_info("%s", t); - assert_se(parse_timestamp(t, &y) >= 0); + ASSERT_OK(parse_timestamp(t, &y)); assert_se(y == x); } @@ -95,9 +95,9 @@ TEST(hourly_bug_4031) { usec_t n, u, w; int r; - assert_se(calendar_spec_from_string("hourly", &c) >= 0); + ASSERT_OK(calendar_spec_from_string("hourly", &c)); n = now(CLOCK_REALTIME); - assert_se((r = calendar_spec_next_usec(c, n, &u)) >= 0); + ASSERT_OK((r = calendar_spec_next_usec(c, n, &u))); log_info("Now: %s (%"PRIu64")", FORMAT_TIMESTAMP_STYLE(n, TIMESTAMP_US), n); log_info("Next hourly: %s (%"PRIu64")", r < 0 ? STRERROR(r) : FORMAT_TIMESTAMP_STYLE(u, TIMESTAMP_US), u); @@ -256,7 +256,7 @@ TEST(calendar_spec_from_string) { static int intro(void) { /* Tests have hard-coded results that do not expect a specific timezone to be set by the caller */ - assert_se(unsetenv("TZ") >= 0); + ASSERT_OK(unsetenv("TZ")); return EXIT_SUCCESS; } diff --git a/src/test/test-cap-list.c b/src/test/test-cap-list.c index a9cbf69..49be4a2 100644 --- a/src/test/test-cap-list.c +++ b/src/test/test-cap-list.c @@ -21,7 +21,7 @@ TEST(cap_list) { assert_se(!CAPABILITY_TO_STRING(-1)); if (capability_list_length() <= 62) - assert_se(streq(CAPABILITY_TO_STRING(62), "0x3e")); + ASSERT_STREQ(CAPABILITY_TO_STRING(62), "0x3e"); assert_se(!CAPABILITY_TO_STRING(64)); for (int i = 0; i < capability_list_length(); i++) { @@ -31,7 +31,7 @@ TEST(cap_list) { assert_se(capability_from_name(n) == i); printf("%s = %i\n", n, i); - assert_se(streq(CAPABILITY_TO_STRING(i), n)); + ASSERT_STREQ(CAPABILITY_TO_STRING(i), n); } assert_se(capability_from_name("asdfbsd") == -EINVAL); @@ -70,7 +70,7 @@ static void test_capability_set_one(uint64_t c, const char *t) { uint64_t c1, c_masked = c & all_capabilities(); assert_se(capability_set_to_string(c, &t1) == 0); - assert_se(streq(t1, t)); + ASSERT_STREQ(t1, t); assert_se(capability_set_from_string(t1, &c1) > 0); assert_se(c1 == c_masked); @@ -160,8 +160,8 @@ TEST(capability_set_to_string_negative) { uint64_t m = random_u64() % (UINT64_C(1) << (cap_last_cap() + 1)); - assert_se(capability_set_to_string(m, &a) >= 0); - assert_se(capability_set_to_string_negative(m, &b) >= 0); + ASSERT_OK(capability_set_to_string(m, &a)); + ASSERT_OK(capability_set_to_string_negative(m, &b)); printf("%s (%zu) → ", a, strlen(a)); @@ -170,7 +170,7 @@ TEST(capability_set_to_string_negative) { else printf("%s (%zu)\n", b, strlen(b)); - assert_se(strlen(b) <= strlen(a)); + ASSERT_LE(strlen(b), strlen(a)); } } diff --git a/src/test/test-capability.c b/src/test/test-capability.c index e8a0569..34f3a91 100644 --- a/src/test/test-capability.c +++ b/src/test/test-capability.c @@ -41,12 +41,12 @@ static void test_last_cap_file(void) { r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) /* kernel pre 3.2 or no access */ return; - assert_se(r >= 0); + ASSERT_OK(r); r = safe_atolu(content, &val); - assert_se(r >= 0); + ASSERT_OK(r); assert_se(val != 0); - assert_se(val == cap_last_cap()); + ASSERT_EQ(val, cap_last_cap()); } /* verify cap_last_cap() against syscall probing */ @@ -54,7 +54,7 @@ static void test_last_cap_probe(void) { unsigned long p = (unsigned long)CAP_LAST_CAP; if (prctl(PR_CAPBSET_READ, p) < 0) { - for (p--; p > 0; p --) + for (p--; p > 0; p--) if (prctl(PR_CAPBSET_READ, p) >= 0) break; } else { @@ -64,7 +64,7 @@ static void test_last_cap_probe(void) { } assert_se(p != 0); - assert_se(p == cap_last_cap()); + ASSERT_EQ(p, cap_last_cap()); } static void fork_test(void (*test_func)(void)) { @@ -104,7 +104,7 @@ static int setup_tests(bool *run_ambient) { nobody = getpwnam(NOBODY_USER_NAME); if (!nobody) - return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find 'nobody' user: %m"); + return log_warning_errno(SYNTHETIC_ERRNO(ENOENT), "Couldn't find 'nobody' user."); test_uid = nobody->pw_uid; test_gid = nobody->pw_gid; @@ -130,7 +130,7 @@ static void test_drop_privileges_keep_net_raw(void) { show_capabilities(); sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); - assert_se(sock >= 0); + ASSERT_OK(sock); safe_close(sock); } @@ -138,7 +138,7 @@ static void test_drop_privileges_dontkeep_net_raw(void) { int sock; sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); - assert_se(sock >= 0); + ASSERT_OK(sock); safe_close(sock); assert_se(drop_privileges(test_uid, test_gid, test_flags) >= 0); @@ -147,7 +147,7 @@ static void test_drop_privileges_dontkeep_net_raw(void) { show_capabilities(); sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP); - assert_se(sock < 0); + ASSERT_LT(sock, 0); } static void test_drop_privileges_fail(void) { @@ -155,8 +155,8 @@ static void test_drop_privileges_fail(void) { assert_se(getuid() == test_uid); assert_se(getgid() == test_gid); - assert_se(drop_privileges(test_uid, test_gid, test_flags) < 0); - assert_se(drop_privileges(0, 0, test_flags) < 0); + ASSERT_LT(drop_privileges(test_uid, test_gid, test_flags), 0); + ASSERT_LT(drop_privileges(0, 0, test_flags), 0); } static void test_drop_privileges(void) { @@ -172,14 +172,14 @@ static void test_drop_privileges(void) { } static void test_have_effective_cap(void) { - assert_se(have_effective_cap(CAP_KILL) > 0); - assert_se(have_effective_cap(CAP_CHOWN) > 0); + ASSERT_GT(have_effective_cap(CAP_KILL), 0); + ASSERT_GT(have_effective_cap(CAP_CHOWN), 0); - assert_se(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_KILL)) >= 0); + ASSERT_OK(drop_privileges(test_uid, test_gid, test_flags | (1ULL << CAP_KILL))); assert_se(getuid() == test_uid); assert_se(getgid() == test_gid); - assert_se(have_effective_cap(CAP_KILL) > 0); + ASSERT_GT(have_effective_cap(CAP_KILL), 0); assert_se(have_effective_cap(CAP_CHOWN) == 0); } @@ -237,9 +237,9 @@ static void test_ensure_cap_64_bit(void) { r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); if (r == -ENOENT || ERRNO_IS_NEG_PRIVILEGE(r)) /* kernel pre 3.2 or no access */ return; - assert_se(r >= 0); + ASSERT_OK(r); - assert_se(safe_atolu(content, &p) >= 0); + ASSERT_OK(safe_atolu(content, &p)); /* If caps don't fit into 64-bit anymore, we have a problem, fail the test. */ assert_se(p <= 63); @@ -252,10 +252,10 @@ static void test_capability_get_ambient(void) { uint64_t c; int r; - assert_se(capability_get_ambient(&c) >= 0); + ASSERT_OK(capability_get_ambient(&c)); r = safe_fork("(getambient)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_WAIT|FORK_LOG, NULL); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) { int x, y; diff --git a/src/test/test-cgroup-mask.c b/src/test/test-cgroup-mask.c index bfc8fac..857102f 100644 --- a/src/test/test-cgroup-mask.c +++ b/src/test/test-cgroup-mask.c @@ -20,9 +20,9 @@ static void log_cgroup_mask(CGroupMask got, CGroupMask expected) { _cleanup_free_ char *e_store = NULL, *g_store = NULL; - assert_se(cg_mask_to_string(expected, &e_store) >= 0); + ASSERT_OK(cg_mask_to_string(expected, &e_store)); log_info("Expected mask: %s", e_store); - assert_se(cg_mask_to_string(got, &g_store) >= 0); + ASSERT_OK(cg_mask_to_string(got, &g_store)); log_info("Got mask: %s", g_store); } @@ -39,8 +39,8 @@ TEST_RET(cgroup_mask, .sd_booted = true) { /* Prepare the manager. */ _cleanup_free_ char *unit_dir = NULL; - assert_se(get_testdata_dir("units", &unit_dir) >= 0); - assert_se(set_unit_path(unit_dir) >= 0); + ASSERT_OK(get_testdata_dir("units", &unit_dir)); + ASSERT_OK(set_unit_path(unit_dir)); assert_se(runtime_dir = setup_fake_runtime_dir()); r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); if (IN_SET(r, -EPERM, -EACCES)) { @@ -63,13 +63,13 @@ TEST_RET(cgroup_mask, .sd_booted = true) { assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); /* Load units and verify hierarchy. */ - assert_se(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "son.service", NULL, &son) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "daughter.service", NULL, &daughter) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "grandchild.service", NULL, &grandchild) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, &parent_deep) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, &nomem_parent) >= 0); - assert_se(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, &nomem_leaf) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent.slice", NULL, &parent)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "son.service", NULL, &son)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "daughter.service", NULL, &daughter)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "grandchild.service", NULL, &grandchild)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "parent-deep.slice", NULL, &parent_deep)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomem.slice", NULL, &nomem_parent)); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "nomemleaf.service", NULL, &nomem_leaf)); assert_se(UNIT_GET_SLICE(son) == parent); assert_se(UNIT_GET_SLICE(daughter) == parent); assert_se(UNIT_GET_SLICE(parent_deep) == parent); @@ -135,7 +135,7 @@ static void test_cg_mask_to_string_one(CGroupMask mask, const char *t) { _cleanup_free_ char *b = NULL; assert_se(cg_mask_to_string(mask, &b) >= 0); - assert_se(streq_ptr(b, t)); + ASSERT_STREQ(b, t); } TEST(cg_mask_to_string) { @@ -157,7 +157,7 @@ TEST(cg_mask_to_string) { } static void cgroup_device_permissions_test_normalize(const char *a, const char *b) { - assert_se(streq_ptr(cgroup_device_permissions_to_string(cgroup_device_permissions_from_string(a)), b)); + ASSERT_STREQ(cgroup_device_permissions_to_string(cgroup_device_permissions_from_string(a)), b); } TEST(cgroup_device_permissions) { diff --git a/src/test/test-cgroup-setup.c b/src/test/test-cgroup-setup.c index e669e9b..8b5d02d 100644 --- a/src/test/test-cgroup-setup.c +++ b/src/test/test-cgroup-setup.c @@ -14,12 +14,10 @@ static void test_is_wanted_print_one(bool header) { _cleanup_free_ char *cmdline = NULL; log_info("-- %s --", __func__); - assert_se(proc_cmdline(&cmdline) >= 0); + ASSERT_OK(proc_cmdline(&cmdline)); log_info("cmdline: %s", cmdline); - if (header) { - log_info("default-hierarchy=" DEFAULT_HIERARCHY_NAME); + if (header) (void) system("findmnt -n /sys/fs/cgroup"); - } log_info("is_unified_wanted() → %s", yes_no(cg_is_unified_wanted())); log_info("is_hybrid_wanted() → %s", yes_no(cg_is_hybrid_wanted())); @@ -33,33 +31,33 @@ TEST(is_wanted_print) { } TEST(is_wanted) { - assert_se(setenv("SYSTEMD_PROC_CMDLINE", - "systemd.unified_cgroup_hierarchy", 1) >= 0); + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy", 1)); test_is_wanted_print_one(false); - assert_se(setenv("SYSTEMD_PROC_CMDLINE", - "systemd.unified_cgroup_hierarchy=0", 1) >= 0); + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", + "systemd.unified_cgroup_hierarchy=0", 1)); test_is_wanted_print_one(false); - assert_se(setenv("SYSTEMD_PROC_CMDLINE", + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", "systemd.unified_cgroup_hierarchy=0 " - "systemd.legacy_systemd_cgroup_controller", 1) >= 0); + "systemd.legacy_systemd_cgroup_controller", 1)); test_is_wanted_print_one(false); - assert_se(setenv("SYSTEMD_PROC_CMDLINE", + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", "systemd.unified_cgroup_hierarchy=0 " - "systemd.legacy_systemd_cgroup_controller=0", 1) >= 0); + "systemd.legacy_systemd_cgroup_controller=0", 1)); test_is_wanted_print_one(false); /* cgroup_no_v1=all implies unified cgroup hierarchy, unless otherwise * explicitly specified. */ - assert_se(setenv("SYSTEMD_PROC_CMDLINE", - "cgroup_no_v1=all", 1) >= 0); + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", + "cgroup_no_v1=all", 1)); test_is_wanted_print_one(false); - assert_se(setenv("SYSTEMD_PROC_CMDLINE", + ASSERT_OK(setenv("SYSTEMD_PROC_CMDLINE", "cgroup_no_v1=all " - "systemd.unified_cgroup_hierarchy=0", 1) >= 0); + "systemd.unified_cgroup_hierarchy=0", 1)); test_is_wanted_print_one(false); } diff --git a/src/test/test-cgroup-unit-default.c b/src/test/test-cgroup-unit-default.c index 62618ce..97101d4 100644 --- a/src/test/test-cgroup-unit-default.c +++ b/src/test/test-cgroup-unit-default.c @@ -23,8 +23,8 @@ TEST_RET(default_memory_low, .sd_booted = true) { return log_tests_skipped("cgroupfs not available"); _cleanup_free_ char *unit_dir = NULL; - assert_se(get_testdata_dir("units", &unit_dir) >= 0); - assert_se(set_unit_path(unit_dir) >= 0); + ASSERT_OK(get_testdata_dir("units", &unit_dir)); + ASSERT_OK(set_unit_path(unit_dir)); assert_se(runtime_dir = setup_fake_runtime_dir()); r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_BASIC, &m); if (IN_SET(r, -EPERM, -EACCES)) { @@ -32,8 +32,8 @@ TEST_RET(default_memory_low, .sd_booted = true) { return log_tests_skipped("cannot create manager"); } - assert_se(r >= 0); - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + ASSERT_OK(r); + ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); /* dml.slice has DefaultMemoryLow=50. Beyond that, individual subhierarchies look like this: * @@ -88,27 +88,27 @@ TEST_RET(default_memory_low, .sd_booted = true) { * │ dml-discard-empty.service │ │ dml-discard-set-ml.service │ * └───────────────────────────┘ └────────────────────────────┘ */ - assert_se(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml.slice", NULL, &dml)); - assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-passthrough.slice", NULL, &dml_passthrough)); assert_se(UNIT_GET_SLICE(dml_passthrough) == dml); - assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-passthrough-empty.service", NULL, &dml_passthrough_empty)); assert_se(UNIT_GET_SLICE(dml_passthrough_empty) == dml_passthrough); - assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-dml.service", NULL, &dml_passthrough_set_dml)); assert_se(UNIT_GET_SLICE(dml_passthrough_set_dml) == dml_passthrough); - assert_se(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-passthrough-set-ml.service", NULL, &dml_passthrough_set_ml)); assert_se(UNIT_GET_SLICE(dml_passthrough_set_ml) == dml_passthrough); - assert_se(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-override.slice", NULL, &dml_override)); assert_se(UNIT_GET_SLICE(dml_override) == dml); - assert_se(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-override-empty.service", NULL, &dml_override_empty)); assert_se(UNIT_GET_SLICE(dml_override_empty) == dml_override); - assert_se(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-discard.slice", NULL, &dml_discard)); assert_se(UNIT_GET_SLICE(dml_discard) == dml); - assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-discard-empty.service", NULL, &dml_discard_empty)); assert_se(UNIT_GET_SLICE(dml_discard_empty) == dml_discard); - assert_se(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, "dml-discard-set-ml.service", NULL, &dml_discard_set_ml)); assert_se(UNIT_GET_SLICE(dml_discard_set_ml) == dml_discard); assert_se(root = UNIT_GET_SLICE(dml)); diff --git a/src/test/test-cgroup-util.c b/src/test/test-cgroup-util.c index 51f52d9..1d8f99c 100644 --- a/src/test/test-cgroup-util.c +++ b/src/test/test-cgroup-util.c @@ -23,7 +23,7 @@ static void check_p_d_u(const char *path, int code, const char *result) { r = cg_path_decode_unit(path, &unit); printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); assert_se(r == code); - assert_se(streq_ptr(unit, result)); + ASSERT_STREQ(unit, result); } TEST(path_decode_unit) { @@ -45,7 +45,7 @@ static void check_p_g_u(const char *path, int code, const char *result) { r = cg_path_get_unit(path, &unit); printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); assert_se(r == code); - assert_se(streq_ptr(unit, result)); + ASSERT_STREQ(unit, result); } TEST(path_get_unit) { @@ -69,7 +69,7 @@ static void check_p_g_u_p(const char *path, int code, const char *result) { r = cg_path_get_unit_path(path, &unit_path); printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit_path, r, strnull(result), code); assert_se(r == code); - assert_se(streq_ptr(unit_path, result)); + ASSERT_STREQ(unit_path, result); } TEST(path_get_unit_path) { @@ -96,7 +96,7 @@ static void check_p_g_u_u(const char *path, int code, const char *result) { r = cg_path_get_user_unit(path, &unit); printf("%s: %s → %s %d expected %s %d\n", __func__, path, unit, r, strnull(result), code); assert_se(r == code); - assert_se(streq_ptr(unit, result)); + ASSERT_STREQ(unit, result); } TEST(path_get_user_unit) { @@ -119,7 +119,7 @@ static void check_p_g_s(const char *path, int code, const char *result) { _cleanup_free_ char *s = NULL; assert_se(cg_path_get_session(path, &s) == code); - assert_se(streq_ptr(s, result)); + ASSERT_STREQ(s, result); } TEST(path_get_session) { @@ -146,7 +146,7 @@ static void check_p_g_slice(const char *path, int code, const char *result) { _cleanup_free_ char *s = NULL; assert_se(cg_path_get_slice(path, &s) == code); - assert_se(streq_ptr(s, result)); + ASSERT_STREQ(s, result); } TEST(path_get_slice) { @@ -163,7 +163,7 @@ static void check_p_g_u_slice(const char *path, int code, const char *result) { _cleanup_free_ char *s = NULL; assert_se(cg_path_get_user_slice(path, &s) == code); - assert_se(streq_ptr(s, result)); + ASSERT_STREQ(s, result); } TEST(path_get_user_slice) { @@ -194,7 +194,7 @@ TEST(proc) { _cleanup_closedir_ DIR *d = NULL; int r; - assert_se(proc_dir_open(&d) >= 0); + ASSERT_OK(proc_dir_open(&d)); for (;;) { _cleanup_free_ char *path = NULL, *path_shifted = NULL, *session = NULL, *unit = NULL, *user_unit = NULL, *machine = NULL, *slice = NULL; @@ -238,10 +238,10 @@ static void test_escape_one(const char *s, const char *expected) { assert_se(s); assert_se(expected); - assert_se(cg_escape(s, &b) >= 0); - assert_se(streq(b, expected)); + ASSERT_OK(cg_escape(s, &b)); + ASSERT_STREQ(b, expected); - assert_se(streq(cg_unescape(b), s)); + ASSERT_STREQ(cg_unescape(b), s); assert_se(filename_is_valid(b)); assert_se(!cg_needs_escape(s) || b[0] == '_'); @@ -284,7 +284,7 @@ static void test_slice_to_path_one(const char *unit, const char *path, int error log_info("actual: %s / %d", strnull(ret), r); log_info("expect: %s / %d", strnull(path), error); assert_se(r == error); - assert_se(streq_ptr(ret, path)); + ASSERT_STREQ(ret, path); } TEST(slice_to_path) { @@ -315,8 +315,8 @@ TEST(slice_to_path) { static void test_shift_path_one(const char *raw, const char *root, const char *shifted) { const char *s = NULL; - assert_se(cg_shift_path(raw, root, &s) >= 0); - assert_se(streq(s, shifted)); + ASSERT_OK(cg_shift_path(raw, root, &s)); + ASSERT_STREQ(s, shifted); } TEST(shift_path) { @@ -328,12 +328,13 @@ TEST(shift_path) { TEST(mask_supported, .sd_booted = true) { CGroupMask m; - CGroupController c; - assert_se(cg_mask_supported(&m) >= 0); + ASSERT_OK(cg_mask_supported(&m)); - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) - printf("'%s' is supported: %s\n", cgroup_controller_to_string(c), yes_no(m & CGROUP_CONTROLLER_TO_MASK(c))); + for (CGroupController c = 0; c < _CGROUP_CONTROLLER_MAX; c++) + printf("'%s' is supported: %s\n", + cgroup_controller_to_string(c), + yes_no(m & CGROUP_CONTROLLER_TO_MASK(c))); } TEST(is_cgroup_fs, .sd_booted = true) { @@ -362,7 +363,7 @@ TEST(cg_tests) { int all, hybrid, systemd, r; r = cg_unified(); - if (r == -ENOMEDIUM) { + if (IN_SET(r, -ENOENT, -ENOMEDIUM)) { log_tests_skipped("cgroup not mounted"); return; } @@ -392,16 +393,16 @@ TEST(cg_tests) { TEST(cg_get_keyed_attribute) { _cleanup_free_ char *val = NULL; char *vals3[3] = {}, *vals3a[3] = {}; - int i, r; + int r; r = cg_get_keyed_attribute("cpu", "/init.scope", "no_such_file", STRV_MAKE("no_such_attr"), &val); - if (r == -ENOMEDIUM || ERRNO_IS_PRIVILEGE(r)) { + if (IN_SET(r, -ENOMEDIUM, -ENOENT) || ERRNO_IS_PRIVILEGE(r)) { log_info_errno(r, "Skipping most of %s, /sys/fs/cgroup not accessible: %m", __func__); return; } assert_se(r == -ENOENT); - assert_se(val == NULL); + ASSERT_NULL(val); if (access("/sys/fs/cgroup/init.scope/cpu.stat", R_OK) < 0) { log_info_errno(errno, "Skipping most of %s, /init.scope/cpu.stat not accessible: %m", __func__); @@ -410,7 +411,7 @@ TEST(cg_get_keyed_attribute) { assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == -ENXIO); assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", STRV_MAKE("no_such_attr"), &val) == 0); - assert_se(val == NULL); + ASSERT_NULL(val); assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec"), &val) == 0); val = mfree(val); @@ -430,7 +431,7 @@ TEST(cg_get_keyed_attribute) { assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("usage_usec", "user_usec", "system_usec"), vals3) == 0); - for (i = 0; i < 3; i++) + for (size_t i = 0; i < 3; i++) free(vals3[i]); assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", @@ -440,7 +441,7 @@ TEST(cg_get_keyed_attribute) { assert_se(cg_get_keyed_attribute("cpu", "/init.scope", "cpu.stat", STRV_MAKE("system_usec", "user_usec", "usage_usec"), vals3a) == 0); - for (i = 0; i < 3; i++) + for (size_t i = 0; i < 3; i++) free(vals3a[i]); assert_se(cg_get_keyed_attribute_graceful("cpu", "/init.scope", "cpu.stat", @@ -448,7 +449,7 @@ TEST(cg_get_keyed_attribute) { log_info("cpu /init.scope cpu.stat [system_usec user_usec usage_usec] → \"%s\", \"%s\", \"%s\"", vals3a[0], vals3a[1], vals3a[2]); - for (i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { free(vals3[i]); free(vals3a[i]); } diff --git a/src/test/test-cgroup.c b/src/test/test-cgroup.c index 0fbd635..8bd4af9 100644 --- a/src/test/test-cgroup.c +++ b/src/test/test-cgroup.c @@ -5,6 +5,7 @@ #include "cgroup-setup.h" #include "cgroup-util.h" #include "errno-util.h" +#include "fd-util.h" #include "path-util.h" #include "process-util.h" #include "string-util.h" @@ -14,8 +15,8 @@ TEST(cg_split_spec) { char *c, *p; assert_se(cg_split_spec("foobar:/", &c, &p) == 0); - assert_se(streq(c, "foobar")); - assert_se(streq(p, "/")); + ASSERT_STREQ(c, "foobar"); + ASSERT_STREQ(p, "/"); c = mfree(c); p = mfree(p); @@ -30,13 +31,13 @@ TEST(cg_split_spec) { assert_se(cg_split_spec("fo/obar:/", &c, &p) < 0); assert_se(cg_split_spec("/", &c, &p) >= 0); - assert_se(c == NULL); - assert_se(streq(p, "/")); + ASSERT_NULL(c); + ASSERT_STREQ(p, "/"); p = mfree(p); assert_se(cg_split_spec("foo", &c, &p) >= 0); - assert_se(streq(c, "foo")); - assert_se(p == NULL); + ASSERT_STREQ(c, "foo"); + ASSERT_NULL(p); c = mfree(c); } @@ -44,8 +45,8 @@ TEST(cg_create) { int r; r = cg_unified_cached(false); - if (r == -ENOMEDIUM) { - log_tests_skipped("cgroup not mounted"); + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) { + log_tests_skipped("cgroupfs is not mounted"); return; } assert_se(r >= 0); @@ -78,7 +79,7 @@ TEST(cg_create) { assert_se(cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, test_b, 0) == 0); assert_se(cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, getpid_cached(), &path) == 0); - assert_se(streq(path, test_b)); + ASSERT_STREQ(path, test_b); free(path); assert_se(cg_attach(SYSTEMD_CGROUP_CONTROLLER, test_a, 0) == 0); @@ -129,4 +130,47 @@ TEST(cg_create) { assert_se(cg_rmdir(SYSTEMD_CGROUP_CONTROLLER, test_a) == 0); } +TEST(id) { + _cleanup_free_ char *p = NULL, *p2 = NULL; + _cleanup_close_ int fd = -EBADF, fd2 = -EBADF; + uint64_t id, id2; + int r; + + r = cg_all_unified(); + if (r == 0) { + log_tests_skipped("skipping cgroupid test, not running in unified mode"); + return; + } + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) { + log_tests_skipped("cgroupfs is not mounted"); + return; + } + assert_se(r > 0); + + fd = cg_path_open(SYSTEMD_CGROUP_CONTROLLER, "/"); + assert_se(fd >= 0); + + assert_se(fd_get_path(fd, &p) >= 0); + assert_se(path_equal(p, "/sys/fs/cgroup")); + + assert_se(cg_fd_get_cgroupid(fd, &id) >= 0); + + fd2 = cg_cgroupid_open(fd, id); + + if (ERRNO_IS_NEG_PRIVILEGE(fd2)) + log_notice("Skipping open-by-cgroup-id test because lacking privs."); + else { + assert_se(fd2 >= 0); + + assert_se(fd_get_path(fd2, &p2) >= 0); + assert_se(path_equal(p2, "/sys/fs/cgroup")); + + assert_se(cg_fd_get_cgroupid(fd2, &id2) >= 0); + + assert_se(id == id2); + + assert_se(inode_same_at(fd, NULL, fd2, NULL, AT_EMPTY_PATH) > 0); + } +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-chase.c b/src/test/test-chase.c index dbbc99b..13ee702 100644 --- a/src/test/test-chase.c +++ b/src/test/test-chase.c @@ -23,11 +23,11 @@ static void test_chase_extract_filename_one(const char *path, const char *root, log_debug("/* %s(path=%s, root=%s) */", __func__, path, strnull(root)); assert_se(chase(path, root, CHASE_EXTRACT_FILENAME, &ret1, NULL) > 0); - assert_se(streq(ret1, expected)); + ASSERT_STREQ(ret1, expected); assert_se(chase(path, root, 0, &ret2, NULL) > 0); - assert_se(chase_extract_filename(ret2, root, &fname) >= 0); - assert_se(streq(fname, expected)); + ASSERT_OK(chase_extract_filename(ret2, root, &fname)); + ASSERT_STREQ(fname, expected); } TEST(chase) { @@ -42,7 +42,7 @@ TEST(chase) { assert_se(mkdtemp(temp)); top = strjoina(temp, "/top"); - assert_se(mkdir(top, 0700) >= 0); + ASSERT_OK(mkdir(top, 0700)); p = strjoina(top, "/dot"); if (symlink(".", p) < 0) { @@ -52,19 +52,19 @@ TEST(chase) { }; p = strjoina(top, "/dotdot"); - assert_se(symlink("..", p) >= 0); + ASSERT_OK(symlink("..", p)); p = strjoina(top, "/dotdota"); - assert_se(symlink("../a", p) >= 0); + ASSERT_OK(symlink("../a", p)); p = strjoina(temp, "/a"); - assert_se(symlink("b", p) >= 0); + ASSERT_OK(symlink("b", p)); p = strjoina(temp, "/b"); - assert_se(symlink("/usr", p) >= 0); + ASSERT_OK(symlink("/usr", p)); p = strjoina(temp, "/start"); - assert_se(symlink("top/dot/dotdota", p) >= 0); + ASSERT_OK(symlink("top/dot/dotdota", p)); /* Paths that use symlinks underneath the "root" */ @@ -104,7 +104,7 @@ TEST(chase) { assert_se(path_equal(result, qslash)); result = mfree(result); - assert_se(mkdir(q, 0700) >= 0); + ASSERT_OK(mkdir(q, 0700)); r = chase(p, temp, 0, &result, NULL); assert_se(r > 0); @@ -145,21 +145,21 @@ TEST(chase) { /* Paths that would "escape" outside of the "root" */ p = strjoina(temp, "/6dots"); - assert_se(symlink("../../..", p) >= 0); + ASSERT_OK(symlink("../../..", p)); 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); + ASSERT_OK(symlink("../../../usr", p)); 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); + ASSERT_OK(symlink("../../../../usr", p)); r = chase(p, temp, 0, &result, NULL); assert_se(r > 0 && path_equal(result, q)); @@ -168,12 +168,12 @@ TEST(chase) { /* Paths that contain repeated slashes */ p = strjoina(temp, "/slashslash"); - assert_se(symlink("///usr///", p) >= 0); + ASSERT_OK(symlink("///usr///", p)); 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 */ + ASSERT_STREQ(result, "/usr"); /* we guarantee that we drop redundant slashes */ result = mfree(result); r = chase(p, temp, 0, &result, NULL); @@ -185,14 +185,14 @@ TEST(chase) { if (geteuid() == 0) { p = strjoina(temp, "/user"); - assert_se(mkdir(p, 0755) >= 0); - assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0); + ASSERT_OK(mkdir(p, 0755)); + ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); q = strjoina(temp, "/user/root"); - assert_se(mkdir(q, 0755) >= 0); + ASSERT_OK(mkdir(q, 0755)); p = strjoina(q, "/link"); - assert_se(symlink("/", p) >= 0); + ASSERT_OK(symlink("/", p)); /* Fail when user-owned directories contain root-owned subdirectories. */ r = chase(p, temp, CHASE_SAFE, &result, NULL); @@ -218,22 +218,28 @@ TEST(chase) { r = chase("/../.././//../../etc", NULL, 0, &result, NULL); assert_se(r > 0); - assert_se(streq(result, "/etc")); + ASSERT_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")); + ASSERT_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")); + ASSERT_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")); + ASSERT_STREQ(result, "/test-chase.fsldajfl"); + result = mfree(result); + + r = chase("/.path/with/dot", temp, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result, NULL); + ASSERT_OK(r); + q = strjoina(temp, "/.path/with/dot"); + ASSERT_STREQ(result, q); result = mfree(result); r = chase("/etc/machine-id/foo", NULL, 0, &result, NULL); @@ -243,7 +249,7 @@ TEST(chase) { /* Path that loops back to self */ p = strjoina(temp, "/recursive-symlink"); - assert_se(symlink("recursive-symlink", p) >= 0); + ASSERT_OK(symlink("recursive-symlink", p)); r = chase(p, NULL, 0, &result, NULL); assert_se(r == -ELOOP); @@ -269,9 +275,9 @@ TEST(chase) { /* Relative paths */ - assert_se(safe_getcwd(&pwd) >= 0); + ASSERT_OK(safe_getcwd(&pwd)); - assert_se(chdir(temp) >= 0); + ASSERT_OK(chdir(temp)); p = "this/is/a/relative/path"; r = chase(p, NULL, CHASE_NONEXISTENT, &result, NULL); @@ -309,46 +315,46 @@ TEST(chase) { if (geteuid() == 0) { p = strjoina(temp, "/priv1"); - assert_se(mkdir(p, 0755) >= 0); + ASSERT_OK(mkdir(p, 0755)); q = strjoina(p, "/priv2"); - assert_se(mkdir(q, 0755) >= 0); + ASSERT_OK(mkdir(q, 0755)); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + ASSERT_OK(chown(q, UID_NOBODY, GID_NOBODY)); + ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); - assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0); - assert_se(chase(q, NULL, CHASE_SAFE, NULL, NULL) >= 0); + ASSERT_OK(chown(p, UID_NOBODY, GID_NOBODY)); + ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); 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_OK(rmdir(q)); + ASSERT_OK(symlink("/etc/passwd", q)); 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); + ASSERT_OK(chase(q, NULL, CHASE_SAFE, NULL, NULL)); } p = strjoina(temp, "/machine-id-test"); - assert_se(symlink("/usr/../etc/./machine-id", p) >= 0); + ASSERT_OK(symlink("/usr/../etc/./machine-id", p)); 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); + ASSERT_OK(pfd); fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC); - assert_se(fd >= 0); + ASSERT_OK(fd); safe_close(pfd); - assert_se(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a) >= 0); - assert_se(sd_id128_get_machine(&b) >= 0); + ASSERT_OK(id128_read_fd(fd, ID128_FORMAT_PLAIN, &a)); + ASSERT_OK(sd_id128_get_machine(&b)); assert_se(sd_id128_equal(a, b)); } @@ -365,24 +371,24 @@ TEST(chase) { 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_OK(r); + ASSERT_OK(pfd); assert_se(path_equal(result, q)); - assert_se(fstat(pfd, &st) >= 0); + ASSERT_OK(fstat(pfd, &st)); 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); + ASSERT_OK(symlink("s2", q)); p = strjoina(temp, "/s2"); - assert_se(symlink("nonexistent", p) >= 0); + ASSERT_OK(symlink("nonexistent", p)); r = chase(q, NULL, CHASE_NOFOLLOW, &result, &pfd); - assert_se(r >= 0); - assert_se(pfd >= 0); + ASSERT_OK(r); + ASSERT_OK(pfd); assert_se(path_equal(result, q)); - assert_se(fstat(pfd, &st) >= 0); + ASSERT_OK(fstat(pfd, &st)); assert_se(S_ISLNK(st.st_mode)); result = mfree(result); pfd = safe_close(pfd); @@ -393,41 +399,41 @@ TEST(chase) { r = chase(p, NULL, CHASE_STEP, &result, NULL); assert_se(r == 0); p = strjoina(temp, "/top/dot/dotdota"); - assert_se(streq(p, result)); + ASSERT_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)); + ASSERT_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)); + ASSERT_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)); + ASSERT_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)); + ASSERT_STREQ(p, result); result = mfree(result); r = chase(p, NULL, CHASE_STEP, &result, NULL); assert_se(r == 0); - assert_se(streq("/usr", result)); + ASSERT_STREQ("/usr", result); result = mfree(result); r = chase("/usr", NULL, CHASE_STEP, &result, NULL); assert_se(r > 0); - assert_se(streq("/usr", result)); + ASSERT_STREQ("/usr", result); result = mfree(result); /* Make sure that symlinks in the "root" path are not resolved, but those below are */ @@ -449,7 +455,7 @@ TEST(chase) { 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); + ASSERT_OK(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL)); } TEST(chaseat) { @@ -461,29 +467,29 @@ TEST(chaseat) { struct stat st; const char *p; - assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + ASSERT_OK((tfd = mkdtemp_open(NULL, 0, &t))); /* 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); + ASSERT_OK(symlinkat("/usr", tfd, "abc")); p = strjoina(t, "/abc"); - assert_se(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); - assert_se(streq(result, "/usr")); + ASSERT_OK(chaseat(AT_FDCWD, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_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_OK(fd); - assert_se(chaseat(fd, p, 0, &result, NULL) >= 0); - assert_se(streq(result, "/usr")); + ASSERT_OK(chaseat(fd, p, 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); result = mfree(result); - assert_se(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); - assert_se(streq(result, "/usr")); + ASSERT_OK(chaseat(fd, p, CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_STREQ(result, "/usr"); result = mfree(result); fd = safe_close(fd); @@ -491,36 +497,36 @@ TEST(chaseat) { /* 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")); + ASSERT_OK(chaseat(tfd, "abc", 0, &result, NULL)); + ASSERT_STREQ(result, "/usr"); result = mfree(result); - assert_se(chaseat(tfd, "/abc", 0, &result, NULL) >= 0); - assert_se(streq(result, "/usr")); + ASSERT_OK(chaseat(tfd, "/abc", 0, &result, NULL)); + ASSERT_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")); + ASSERT_OK(chaseat(tfd, "abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_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")); + ASSERT_OK(chaseat(tfd, "/abc", CHASE_AT_RESOLVE_IN_ROOT | CHASE_NONEXISTENT, &result, NULL)); + ASSERT_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); + ASSERT_OK(fd = openat(tfd, "def", O_CREAT|O_CLOEXEC, 0700)); 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")); + ASSERT_OK(symlinkat("/def", tfd, "qed")); + ASSERT_OK(chaseat(tfd, "qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_STREQ(result, "def"); result = mfree(result); - assert_se(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); - assert_se(streq(result, "def")); + ASSERT_OK(chaseat(tfd, "/qed", CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_STREQ(result, "def"); result = mfree(result); /* Valid directory file descriptor without CHASE_AT_RESOLVE_IN_ROOT should resolve symlinks against @@ -529,157 +535,157 @@ TEST(chaseat) { /* Test CHASE_PARENT */ - assert_se((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755)) >= 0); - assert_se(symlinkat("/def", fd, "parent") >= 0); + ASSERT_OK((fd = open_mkdir_at(tfd, "chase", O_CLOEXEC, 0755))); + ASSERT_OK(symlinkat("/def", fd, "parent")); 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")); + ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); + ASSERT_OK(faccessat(fd, "def", F_OK, 0)); + ASSERT_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")); + ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW, &result, &fd)); + ASSERT_OK(faccessat(fd, "parent", F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_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")); + ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, &fd)); + ASSERT_OK(faccessat(fd, "chase", F_OK, 0)); + ASSERT_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, ".")); + ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_STREQ(result, "."); result = mfree(result); - assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL) >= 0); - assert_se(streq(result, ".")); + assert_se(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT, &result, NULL)); + ASSERT_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_OK(chaseat(tfd, "m/k/d/i/r", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(faccessat(tfd, "m/k/d/i", F_OK, 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")); + ASSERT_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_OK(chaseat(tfd, "m/../q", CHASE_MKDIR_0755|CHASE_NONEXISTENT, &result, NULL)); + ASSERT_OK(faccessat(tfd, "m", F_OK, 0)); assert_se(RET_NERRNO(faccessat(tfd, "q", F_OK, 0)) == -ENOENT); - assert_se(streq(result, "q")); + ASSERT_STREQ(result, "q"); result = mfree(result); assert_se(chaseat(tfd, "i/../p", CHASE_MKDIR_0755|CHASE_NONEXISTENT, NULL, NULL) == -ENOENT); /* Test CHASE_EXTRACT_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")); + ASSERT_OK(chaseat(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_PARENT|CHASE_NOFOLLOW|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_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")); + ASSERT_OK(chaseat(tfd, "chase", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, &fd)); + ASSERT_OK(faccessat(fd, result, F_OK, 0)); + ASSERT_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, ".")); + ASSERT_OK(chaseat(tfd, "/", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_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, ".")); + ASSERT_OK(chaseat(tfd, ".", CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_STREQ(result, "."); result = mfree(result); - assert_se(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL) >= 0); - assert_se(streq(result, ".")); + ASSERT_OK(chaseat(tfd, NULL, CHASE_PARENT|CHASE_AT_RESOLVE_IN_ROOT|CHASE_EXTRACT_FILENAME, &result, NULL)); + ASSERT_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); + ASSERT_OK(fd); + ASSERT_OK(fd_verify_regular(fd)); 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); + ASSERT_OK(fd); + ASSERT_OK(fd_verify_directory(fd)); fd = safe_close(fd); fd = chase_and_openat(tfd, NULL, CHASE_PARENT|CHASE_EXTRACT_FILENAME, O_PATH|O_DIRECTORY|O_CLOEXEC, &result); - assert_se(fd >= 0); - assert_se(streq(result, ".")); + ASSERT_OK(fd); + ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); /* Test chase_and_openatdir() */ - assert_se(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir) >= 0); + ASSERT_OK(chase_and_opendirat(tfd, "o/p/e/n/d/i", 0, &result, &dir)); FOREACH_DIRENT(de, dir, assert_not_reached()) - assert_se(streq(de->d_name, "r")); - assert_se(streq(result, "o/p/e/n/d/i")); + ASSERT_STREQ(de->d_name, "r"); + ASSERT_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")); + ASSERT_OK(chase_and_statat(tfd, "o/p", 0, &result, &st)); + ASSERT_OK(stat_verify_directory(&st)); + ASSERT_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")); + ASSERT_OK(chase_and_accessat(tfd, "o/p/e", 0, F_OK, &result)); + ASSERT_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_OK(chase_and_fopenat_unlocked(tfd, "o/p/e/n/f/i/l/e", 0, "re", &result, &f)); 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")); + ASSERT_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")); + ASSERT_OK(chase_and_unlinkat(tfd, "o/p/e/n/f/i/l/e", 0, 0, &result)); + ASSERT_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")); + ASSERT_OK((fd = chase_and_open_parent_at(tfd, "chase/parent", CHASE_AT_RESOLVE_IN_ROOT|CHASE_NOFOLLOW, &result))); + ASSERT_OK(faccessat(fd, result, F_OK, AT_SYMLINK_NOFOLLOW)); + ASSERT_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")); + ASSERT_OK((fd = chase_and_open_parent_at(tfd, "chase", CHASE_AT_RESOLVE_IN_ROOT, &result))); + ASSERT_OK(faccessat(fd, result, F_OK, 0)); + ASSERT_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, ".")); + ASSERT_OK((fd = chase_and_open_parent_at(tfd, "/", CHASE_AT_RESOLVE_IN_ROOT, &result))); + ASSERT_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, ".")); + ASSERT_OK((fd = chase_and_open_parent_at(tfd, ".", CHASE_AT_RESOLVE_IN_ROOT, &result))); + ASSERT_STREQ(result, "."); fd = safe_close(fd); result = mfree(result); } @@ -687,47 +693,47 @@ TEST(chaseat) { TEST(chaseat_prefix_root) { _cleanup_free_ char *cwd = NULL, *ret = NULL, *expected = NULL; - assert_se(safe_getcwd(&cwd) >= 0); + ASSERT_OK(safe_getcwd(&cwd)); - assert_se(chaseat_prefix_root("/hoge", NULL, &ret) >= 0); - assert_se(streq(ret, "/hoge")); + ASSERT_OK(chaseat_prefix_root("/hoge", NULL, &ret)); + ASSERT_STREQ(ret, "/hoge"); ret = mfree(ret); - assert_se(chaseat_prefix_root("/hoge", "a/b/c", &ret) >= 0); - assert_se(streq(ret, "/hoge")); + ASSERT_OK(chaseat_prefix_root("/hoge", "a/b/c", &ret)); + ASSERT_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")); + ASSERT_OK(chaseat_prefix_root("hoge", "/a/b//./c///", &ret)); + ASSERT_STREQ(ret, "/a/b/c/hoge"); ret = mfree(ret); - assert_se(chaseat_prefix_root("hoge", "a/b//./c///", &ret) >= 0); + ASSERT_OK(chaseat_prefix_root("hoge", "a/b//./c///", &ret)); assert_se(expected = path_join(cwd, "a/b/c/hoge")); - assert_se(streq(ret, expected)); + ASSERT_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")); + ASSERT_OK(chaseat_prefix_root("./hoge/aaa/../././b", "/a/b//./c///", &ret)); + ASSERT_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)); + ASSERT_STREQ(ret, expected); } TEST(trailing_dot_dot) { _cleanup_free_ char *path = NULL, *fdpath = NULL; _cleanup_close_ int fd = -EBADF; - assert_se(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd) >= 0); + ASSERT_OK(chase("/usr/..", NULL, CHASE_PARENT, &path, &fd)); assert_se(path_equal(path, "/")); - assert_se(fd_get_path(fd, &fdpath) >= 0); + ASSERT_OK(fd_get_path(fd, &fdpath)); assert_se(path_equal(fdpath, "/")); path = mfree(path); @@ -735,16 +741,16 @@ TEST(trailing_dot_dot) { fd = safe_close(fd); _cleanup_(rm_rf_physical_and_freep) char *t = NULL; - assert_se(mkdtemp_malloc(NULL, &t) >= 0); + ASSERT_OK(mkdtemp_malloc(NULL, &t)); _cleanup_free_ char *sub = ASSERT_PTR(path_join(t, "a/b/c/d")); - assert_se(mkdir_p(sub, 0700) >= 0); + ASSERT_OK(mkdir_p(sub, 0700)); _cleanup_free_ char *suffixed = ASSERT_PTR(path_join(sub, "..")); - assert_se(chase(suffixed, NULL, CHASE_PARENT, &path, &fd) >= 0); + ASSERT_OK(chase(suffixed, NULL, CHASE_PARENT, &path, &fd)); _cleanup_free_ char *expected1 = ASSERT_PTR(path_join(t, "a/b/c")); _cleanup_free_ char *expected2 = ASSERT_PTR(path_join(t, "a/b")); assert_se(path_equal(path, expected1)); - assert_se(fd_get_path(fd, &fdpath) >= 0); + ASSERT_OK(fd_get_path(fd, &fdpath)); assert_se(path_equal(fdpath, expected2)); } diff --git a/src/test/test-color-util.c b/src/test/test-color-util.c new file mode 100644 index 0000000..3d00d87 --- /dev/null +++ b/src/test/test-color-util.c @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "color-util.h" +#include "tests.h" + +TEST(hsv_to_rgb) { + uint8_t r, g, b; + + hsv_to_rgb(0, 0, 0, &r, &g, &b); + assert(r == 0 && g == 0 && b == 0); + + hsv_to_rgb(60, 0, 0, &r, &g, &b); + assert(r == 0 && g == 0 && b == 0); + + hsv_to_rgb(0, 0, 100, &r, &g, &b); + assert(r == 255 && g == 255 && b == 255); + + hsv_to_rgb(0, 100, 100, &r, &g, &b); + assert(r == 255 && g == 0 && b == 0); + + hsv_to_rgb(120, 100, 100, &r, &g, &b); + assert(r == 0 && g == 255 && b == 0); + + hsv_to_rgb(240, 100, 100, &r, &g, &b); + assert(r == 0 && g == 0 && b == 255); + + hsv_to_rgb(311, 52, 62, &r, &g, &b); + assert(r == 158 && g == 75 && b == 143); +} + +TEST(rgb_to_hsv) { + + double h, s, v; + rgb_to_hsv(0, 0, 0, &h, &s, &v); + assert(s <= 0); + assert(v <= 0); + + rgb_to_hsv(1, 1, 1, &h, &s, &v); + assert(s <= 0); + assert(v >= 100); + + rgb_to_hsv(1, 0, 0, &h, &s, &v); + assert(h >= 359 || h <= 1); + assert(s >= 100); + assert(v >= 100); + + rgb_to_hsv(0, 1, 0, &h, &s, &v); + assert(h >= 119 && h <= 121); + assert(s >= 100); + assert(v >= 100); + + rgb_to_hsv(0, 0, 1, &h, &s, &v); + assert(h >= 239 && h <= 241); + assert(s >= 100); + assert(v >= 100); + + rgb_to_hsv(0.5, 0.6, 0.7, &h, &s, &v); + assert(h >= 209 && h <= 211); + assert(s >= 28 && s <= 31); + assert(v >= 69 && v <= 71); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-compress.c b/src/test/test-compress.c index 2f20d00..868b862 100644 --- a/src/test/test-compress.c +++ b/src/test/test-compress.c @@ -6,6 +6,7 @@ #include #endif +#include "dlfcn-util.h" #include "alloc-util.h" #include "compress.h" #include "fd-util.h" @@ -187,13 +188,13 @@ _unused_ static void test_compress_stream(const char *compression, log_debug("/* create source from %s */", srcfile); - assert_se((src = open(srcfile, O_RDONLY|O_CLOEXEC)) >= 0); + ASSERT_OK((src = open(srcfile, O_RDONLY|O_CLOEXEC))); log_debug("/* test compression */"); assert_se((dst = mkostemp_safe(pattern)) >= 0); - assert_se(compress(src, dst, -1, &uncompressed_size) >= 0); + ASSERT_OK(compress(src, dst, -1, &uncompressed_size)); if (cat) { assert_se(asprintf(&cmd, "%s %s | diff %s -", cat, pattern, srcfile) > 0); @@ -241,16 +242,16 @@ static void test_lz4_decompress_partial(void) { memset(&huge[STRLEN("HUGE=")], 'x', HUGE_SIZE - STRLEN("HUGE=") - 1); huge[HUGE_SIZE - 1] = '\0'; - r = LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); + r = sym_LZ4_compress_default(huge, buf, HUGE_SIZE, buf_size); assert_se(r >= 0); compressed = r; log_info("Compressed %i → %zu", HUGE_SIZE, compressed); - r = LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); + r = sym_LZ4_decompress_safe(buf, huge, r, HUGE_SIZE); assert_se(r >= 0); log_info("Decompressed → %i", r); - r = LZ4_decompress_safe_partial(buf, huge, + r = sym_LZ4_decompress_safe_partial(buf, huge, compressed, 12, HUGE_SIZE); assert_se(r >= 0); @@ -258,10 +259,10 @@ static void test_lz4_decompress_partial(void) { for (size_t size = 1; size < sizeof(buf2); size++) { /* This failed in older lz4s but works in newer ones. */ - r = LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); + r = sym_LZ4_decompress_safe_partial(buf, buf2, compressed, size, size); log_info("Decompressed partial %zu/%zu → %i (%s)", size, size, r, r < 0 ? "bad" : "good"); - if (r >= 0 && LZ4_versionNumber() >= 10803) + if (r >= 0 && sym_LZ4_versionNumber() >= 10803) /* lz4 <= 1.8.2 should fail that test, let's only check for newer ones */ assert_se(memcmp(buf2, huge, r) == 0); } @@ -316,28 +317,30 @@ int main(int argc, char *argv[]) { #endif #if HAVE_LZ4 - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - text, sizeof(text), false); - test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, - data, sizeof(data), true); - - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - text, sizeof(text), false); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - data, sizeof(data), true); - test_decompress_startswith("LZ4", - compress_blob_lz4, decompress_startswith_lz4, - huge, HUGE_SIZE, true); - - test_compress_stream("LZ4", "lz4cat", - compress_stream_lz4, decompress_stream_lz4, srcfile); - - test_lz4_decompress_partial(); - - test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); - + if (dlopen_lz4() >= 0) { + test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, + text, sizeof(text), false); + test_compress_decompress("LZ4", compress_blob_lz4, decompress_blob_lz4, + data, sizeof(data), true); + + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + text, sizeof(text), false); + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + data, sizeof(data), true); + test_decompress_startswith("LZ4", + compress_blob_lz4, decompress_startswith_lz4, + huge, HUGE_SIZE, true); + + test_compress_stream("LZ4", "lz4cat", + compress_stream_lz4, decompress_stream_lz4, srcfile); + + test_lz4_decompress_partial(); + + test_decompress_startswith_short("LZ4", compress_blob_lz4, decompress_startswith_lz4); + } else + log_error("/* Can't load liblz4 */"); #else log_info("/* LZ4 test skipped */"); #endif diff --git a/src/test/test-condition.c b/src/test/test-condition.c index bb98761..be83690 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -41,7 +41,7 @@ #include "tests.h" #include "tmpfile-util.h" #include "tomoyo-util.h" -#include "uid-alloc-range.h" +#include "uid-classification.h" #include "user-util.h" #include "virt.h" @@ -139,8 +139,8 @@ TEST(condition_test_control_group_hierarchy) { int r; r = cg_unified(); - if (r == -ENOMEDIUM) { - log_tests_skipped("cgroup not mounted"); + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) { + log_tests_skipped("cgroupfs is not mounted"); return; } assert_se(r >= 0); @@ -163,8 +163,8 @@ TEST(condition_test_control_group_controller) { int r; r = cg_unified(); - if (r == -ENOMEDIUM) { - log_tests_skipped("cgroup not mounted"); + if (IN_SET(r, -ENOMEDIUM, -ENOENT)) { + log_tests_skipped("cgroupfs is not mounted"); return; } assert_se(r >= 0); diff --git a/src/test/test-conf-parser.c b/src/test/test-conf-parser.c index 0acb413..4e236bd 100644 --- a/src/test/test-conf-parser.c +++ b/src/test/test-conf-parser.c @@ -3,8 +3,10 @@ #include "conf-parser.h" #include "fd-util.h" #include "fs-util.h" +#include "fileio.h" #include "log.h" #include "macro.h" +#include "mkdir.h" #include "string-util.h" #include "strv.h" #include "tests.h" @@ -14,7 +16,7 @@ static void test_config_parse_path_one(const char *rvalue, const char *expected) _cleanup_free_ char *path = NULL; assert_se(config_parse_path("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &path, NULL) >= 0); - assert_se(streq_ptr(expected, path)); + ASSERT_STREQ(expected, path); } static void test_config_parse_log_level_one(const char *rvalue, int expected) { @@ -350,37 +352,37 @@ static void test_config_parse_one(unsigned i, const char *s) { switch (i) { case 0 ... 4: assert_se(r == 1); - assert_se(streq(setting1, "1")); + ASSERT_STREQ(setting1, "1"); break; case 5 ... 10: assert_se(r == 1); - assert_se(streq(setting1, "1 2 3")); + ASSERT_STREQ(setting1, "1 2 3"); break; case 11: assert_se(r == 1); - assert_se(streq(setting1, "1\\\\ \\\\2")); + ASSERT_STREQ(setting1, "1\\\\ \\\\2"); break; case 12: assert_se(r == 1); - assert_se(streq(setting1, x1000("ABCD"))); + ASSERT_STREQ(setting1, x1000("ABCD")); break; case 13 ... 14: assert_se(r == 1); - assert_se(streq(setting1, x1000("ABCD") " foobar")); + ASSERT_STREQ(setting1, x1000("ABCD") " foobar"); break; case 15 ... 16: assert_se(r == -ENOBUFS); - assert_se(setting1 == NULL); + ASSERT_NULL(setting1); break; case 17: assert_se(r == 1); - assert_se(streq(setting1, "2")); + ASSERT_STREQ(setting1, "2"); break; } } @@ -390,4 +392,102 @@ TEST(config_parse) { test_config_parse_one(i, config_file[i]); } +TEST(config_parse_standard_file_with_dropins_full) { + _cleanup_(rmdir_and_freep) char *root = NULL; + _cleanup_close_ int rfd = -EBADF; + int r; + + assert_se(mkdtemp_malloc(NULL, &root) >= 0); + assert_se(mkdir_p_root(root, "/etc/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/run/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/usr/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install.conf.d", UID_INVALID, GID_INVALID, 0755)); + + rfd = open(root, O_CLOEXEC|O_DIRECTORY); + assert_se(rfd >= 0); + + assert_se(write_string_file_at(rfd, "usr/lib/kernel/install.conf", /* this one is ignored */ + "A=!!!", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "usr/local/lib/kernel/install.conf", + "A=aaa", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "usr/local/lib/kernel/install.conf.d/drop1.conf", + "B=bbb", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "usr/local/lib/kernel/install.conf.d/drop2.conf", + "C=c1", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "usr/lib/kernel/install.conf.d/drop2.conf", /* this one is ignored */ + "C=c2", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "run/kernel/install.conf.d/drop3.conf", + "D=ddd", WRITE_STRING_FILE_CREATE) == 0); + assert_se(write_string_file_at(rfd, "etc/kernel/install.conf.d/drop4.conf", + "E=eee", WRITE_STRING_FILE_CREATE) == 0); + + _cleanup_free_ char *A = NULL, *B = NULL, *C = NULL, *D = NULL, *E = NULL, *F = NULL; + _cleanup_strv_free_ char **dropins = NULL; + + const ConfigTableItem items[] = { + { NULL, "A", config_parse_string, 0, &A}, + { NULL, "B", config_parse_string, 0, &B}, + { NULL, "C", config_parse_string, 0, &C}, + { NULL, "D", config_parse_string, 0, &D}, + { NULL, "E", config_parse_string, 0, &E}, + { NULL, "F", config_parse_string, 0, &F}, + {} + }; + + r = config_parse_standard_file_with_dropins_full( + root, "kernel/install.conf", + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ &dropins); + assert_se(r >= 0); + ASSERT_STREQ(A, "aaa"); + ASSERT_STREQ(B, "bbb"); + ASSERT_STREQ(C, "c1"); + ASSERT_STREQ(D, "ddd"); + ASSERT_STREQ(E, "eee"); + ASSERT_STREQ(F, NULL); + + A = mfree(A); + B = mfree(B); + C = mfree(C); + D = mfree(D); + E = mfree(E); + + assert_se(strv_length(dropins) == 4); + + /* Make sure that we follow symlinks */ + assert_se(mkdir_p_root(root, "/etc/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/run/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/usr/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755)); + assert_se(mkdir_p_root(root, "/usr/local/lib/kernel/install2.conf.d", UID_INVALID, GID_INVALID, 0755)); + + /* (Those symlinks are only useful relative to . */ + assert_se(symlinkat("/usr/lib/kernel/install.conf", rfd, "usr/lib/kernel/install2.conf") == 0); + assert_se(symlinkat("/usr/local/lib/kernel/install.conf", rfd, "usr/local/lib/kernel/install2.conf") == 0); + assert_se(symlinkat("/usr/local/lib/kernel/install.conf.d/drop1.conf", rfd, "usr/local/lib/kernel/install2.conf.d/drop1.conf") == 0); + assert_se(symlinkat("/usr/local/lib/kernel/install.conf.d/drop2.conf", rfd, "usr/local/lib/kernel/install2.conf.d/drop2.conf") == 0); + assert_se(symlinkat("/usr/lib/kernel/install.conf.d/drop2.conf", rfd, "usr/lib/kernel/install2.conf.d/drop2.conf") == 0); + assert_se(symlinkat("/run/kernel/install.conf.d/drop3.conf", rfd, "run/kernel/install2.conf.d/drop3.conf") == 0); + assert_se(symlinkat("/etc/kernel/install.conf.d/drop4.conf", rfd, "etc/kernel/install2.conf.d/drop4.conf") == 0); + + r = config_parse_standard_file_with_dropins_full( + root, "kernel/install2.conf", + /* sections= */ NULL, + config_item_table_lookup, items, + CONFIG_PARSE_WARN, + /* userdata= */ NULL, + /* ret_stats_by_path= */ NULL, + /* ret_dropin_files= */ NULL); + assert_se(r >= 0); + ASSERT_STREQ(A, "aaa"); + ASSERT_STREQ(B, "bbb"); + ASSERT_STREQ(C, "c1"); + ASSERT_STREQ(D, "ddd"); + ASSERT_STREQ(E, "eee"); + ASSERT_STREQ(F, NULL); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 9674e78..7b16cf2 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -45,7 +45,7 @@ TEST(copy_file) { 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_STREQ(buf, "foo bar bar bar foo\n"); assert_se(sz == 20); } @@ -125,7 +125,7 @@ TEST(copy_file_fd) { assert_se(lseek(out_fd, SEEK_SET, 0) == 0); assert_se(read(out_fd, buf, sizeof buf) == (ssize_t) strlen(text)); - assert_se(streq(buf, text)); + ASSERT_STREQ(buf, text); } TEST(copy_tree) { @@ -202,7 +202,7 @@ TEST(copy_tree) { assert_se(access(f, F_OK) == 0); assert_se(read_full_file(f, &buf, &sz) == 0); - assert_se(streq(buf, "file\n")); + ASSERT_STREQ(buf, "file\n"); k = lgetxattr_malloc(f, "user.testxattr", &c); assert_se(xattr_worked < 0 || ((k >= 0) == !!xattr_worked)); @@ -211,7 +211,7 @@ TEST(copy_tree) { _cleanup_free_ char *d = NULL; assert_se(base64mem(*p, strlen(*p), &d) >= 0); - assert_se(streq(d, c)); + ASSERT_STREQ(d, c); } } @@ -253,7 +253,44 @@ TEST(copy_tree) { (void) rm_rf(original_dir, REMOVE_ROOT|REMOVE_PHYSICAL); } -TEST(copy_bytes) { +TEST(copy_tree_at_symlink) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF, fd = -EBADF; + _cleanup_free_ char *p = NULL, *q = NULL; + const char *expect = "hgoehogefoobar"; + + tfd = mkdtemp_open(NULL, O_PATH, &t); + assert_se(tfd >= 0); + + assert_se(symlinkat(expect, tfd, "from") >= 0); + + assert_se(copy_tree_at(tfd, "from", tfd, "to_1", UID_INVALID, GID_INVALID, 0, NULL, NULL) >= 0); + assert_se(readlinkat_malloc(tfd, "to_1", &p) >= 0); + ASSERT_STREQ(p, expect); + p = mfree(p); + + assert_se(q = path_join(t, "from")); + assert_se(copy_tree_at(AT_FDCWD, q, tfd, "to_2", UID_INVALID, GID_INVALID, 0, NULL, NULL) >= 0); + assert_se(readlinkat_malloc(tfd, "to_2", &p) >= 0); + ASSERT_STREQ(p, expect); + p = mfree(p); + q = mfree(q); + + fd = openat(tfd, "from", O_CLOEXEC | O_PATH | O_NOFOLLOW); + assert_se(fd >= 0); + assert_se(copy_tree_at(fd, NULL, tfd, "to_3", UID_INVALID, GID_INVALID, 0, NULL, NULL) >= 0); + assert_se(readlinkat_malloc(tfd, "to_3", &p) >= 0); + ASSERT_STREQ(p, expect); + p = mfree(p); + + assert_se(copy_tree_at(fd, "", tfd, "to_4", UID_INVALID, GID_INVALID, 0, NULL, NULL) >= 0); + assert_se(readlinkat_malloc(tfd, "to_4", &p) >= 0); + ASSERT_STREQ(p, expect); + p = mfree(p); + fd = safe_close(fd); +} + +TEST_RET(copy_bytes) { _cleanup_close_pair_ int pipefd[2] = EBADF_PAIR; _cleanup_close_ int infd = -EBADF; int r, r2; @@ -262,7 +299,8 @@ TEST(copy_bytes) { infd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); if (infd < 0) infd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); - assert_se(infd >= 0); + if (infd < 0) + return log_tests_skipped_errno(errno, "Could not open /usr/lib/os-release or /etc/os-release: %m"); assert_se(pipe2(pipefd, O_CLOEXEC) == 0); @@ -287,6 +325,8 @@ TEST(copy_bytes) { r = copy_bytes(pipefd[1], infd, 1, 0); assert_se(r == -EBADF); + + return 0; } static void test_copy_bytes_regular_file_one(const char *src, bool try_reflink, uint64_t max_bytes) { @@ -381,7 +421,7 @@ TEST(copy_proc) { assert_se(read_one_line_file("/proc/version", &a) >= 0); assert_se(read_one_line_file(f, &b) >= 0); - assert_se(streq(a, b)); + ASSERT_STREQ(a, b); assert_se(!isempty(a)); } @@ -404,7 +444,7 @@ TEST_RET(copy_holes) { return log_tests_skipped("Filesystem doesn't support hole punching"); assert_se(r >= 0); - assert_se(fstat(fd, &stat) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &stat)); blksz = stat.st_blksize; buf = alloca_safe(blksz); memset(buf, 1, blksz); @@ -429,7 +469,7 @@ TEST_RET(copy_holes) { assert_se(lseek(fd_copy, 2 * blksz, SEEK_DATA) < 0 && errno == ENXIO); /* Test that the copied file has the correct size. */ - assert_se(fstat(fd_copy, &stat) >= 0); + ASSERT_OK_ERRNO(fstat(fd_copy, &stat)); assert_se(stat.st_size == 3 * blksz); close(fd); @@ -450,7 +490,7 @@ TEST_RET(copy_holes_with_gaps) { assert_se((fd = openat(tfd, "src", O_CREAT | O_RDWR, 0600)) >= 0); assert_se((fd_copy = openat(tfd, "dst", O_CREAT | O_WRONLY, 0600)) >= 0); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); blksz = st.st_blksize; buf = alloca_safe(blksz); memset(buf, 1, blksz); @@ -479,7 +519,7 @@ TEST_RET(copy_holes_with_gaps) { /* Copy to the start of the second hole */ assert_se(copy_bytes(fd, fd_copy, 3 * blksz, COPY_HOLES) >= 0); - assert_se(fstat(fd_copy, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 3 * blksz); /* Copy to the middle of the second hole */ @@ -487,7 +527,7 @@ TEST_RET(copy_holes_with_gaps) { assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); assert_se(copy_bytes(fd, fd_copy, 4 * blksz, COPY_HOLES) >= 0); - assert_se(fstat(fd_copy, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 4 * blksz); /* Copy to the end of the second hole */ @@ -495,7 +535,7 @@ TEST_RET(copy_holes_with_gaps) { assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); assert_se(copy_bytes(fd, fd_copy, 5 * blksz, COPY_HOLES) >= 0); - assert_se(fstat(fd_copy, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 5 * blksz); /* Copy everything */ @@ -503,7 +543,7 @@ TEST_RET(copy_holes_with_gaps) { assert_se(lseek(fd_copy, 0, SEEK_SET) >= 0); assert_se(ftruncate(fd_copy, 0) >= 0); assert_se(copy_bytes(fd, fd_copy, UINT64_MAX, COPY_HOLES) >= 0); - assert_se(fstat(fd_copy, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd_copy, &st)); assert_se(st.st_size == 6 * blksz); return 0; diff --git a/src/test/test-core-unit.c b/src/test/test-core-unit.c index dc108cc..83c8674 100644 --- a/src/test/test-core-unit.c +++ b/src/test/test-core-unit.c @@ -26,26 +26,26 @@ static void test_unit_escape_setting_one( 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_NULL(a); 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_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_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)); + ASSERT_STREQ(t, expected_c); } TEST(unit_escape_setting) { @@ -81,22 +81,22 @@ static void test_unit_concat_strv_one( 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_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_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_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)); + ASSERT_STREQ(d, expected_c); } TEST(unit_concat_strv) { diff --git a/src/test/test-cpu-set-util.c b/src/test/test-cpu-set-util.c index a0660f5..ccb52c9 100644 --- a/src/test/test-cpu-set-util.c +++ b/src/test/test-cpu-set-util.c @@ -23,7 +23,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0")); + ASSERT_STREQ(str, "0"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "1"); str = mfree(str); cpu_set_reset(&c); @@ -41,7 +45,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "1-2 4")); + ASSERT_STREQ(str, "1-2 4"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "16"); str = mfree(str); cpu_set_reset(&c); @@ -59,7 +67,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0-3 8-11")); + ASSERT_STREQ(str, "0-3 8-11"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "f0f"); str = mfree(str); cpu_set_reset(&c); @@ -74,7 +86,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "8-11")); + ASSERT_STREQ(str, "8-11"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "f00"); str = mfree(str); cpu_set_reset(&c); @@ -104,7 +120,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0-7 63")); + ASSERT_STREQ(str, "0-7 63"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "80000000,000000ff"); str = mfree(str); cpu_set_reset(&c); @@ -120,6 +140,28 @@ TEST(parse_cpu_set) { log_info("cpu_set_to_string: %s", str); str = mfree(str); cpu_set_reset(&c); + assert_se(parse_cpu_set_full("36-39,44-47", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 36; cpu < 40; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + for (cpu = 44; cpu < 48; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "f0f0,00000000"); + str = mfree(str); + cpu_set_reset(&c); + assert_se(parse_cpu_set_full("64-71", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); + assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); + assert_se(CPU_COUNT_S(c.allocated, c.set) == 8); + for (cpu = 64; cpu < 72; cpu++) + assert_se(CPU_ISSET_S(cpu, c.allocated, c.set)); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "ff,00000000,00000000"); + str = mfree(str); + cpu_set_reset(&c); /* Ranges with trailing comma, space */ assert_se(parse_cpu_set_full("0-3 8-11, ", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); @@ -134,7 +176,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0-3 8-11")); + ASSERT_STREQ(str, "0-3 8-11"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "f0f"); str = mfree(str); cpu_set_reset(&c); @@ -142,6 +188,10 @@ TEST(parse_cpu_set) { assert_se(parse_cpu_set_full("3-0", &c, true, NULL, "fake", 1, "CPUAffinity") >= 0); assert_se(c.allocated >= DIV_ROUND_UP(sizeof(__cpu_mask), 8)); assert_se(CPU_COUNT_S(c.allocated, c.set) == 0); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "0"); + str = mfree(str); cpu_set_reset(&c); /* Overlapping ranges */ @@ -155,7 +205,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0-11")); + ASSERT_STREQ(str, "0-11"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "fff"); str = mfree(str); cpu_set_reset(&c); @@ -172,7 +226,11 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "0 2 4-11")); + ASSERT_STREQ(str, "0 2 4-11"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "ff5"); str = mfree(str); cpu_set_reset(&c); @@ -190,6 +248,10 @@ TEST(parse_cpu_set) { assert_se(parse_cpu_set_full("", &c, true, NULL, "fake", 1, "CPUAffinity") == 0); assert_se(!c.set); /* empty string returns NULL */ assert_se(c.allocated == 0); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + ASSERT_STREQ(str, "0"); + str = mfree(str); /* Runaway quoted string */ assert_se(parse_cpu_set_full("0 1 2 3 \"4 5 6 7 ", &c, true, NULL, "fake", 1, "CPUAffinity") == -EINVAL); @@ -204,7 +266,24 @@ TEST(parse_cpu_set) { str = mfree(str); assert_se(str = cpu_set_to_range_string(&c)); log_info("cpu_set_to_range_string: %s", str); - assert_se(streq(str, "8000-8191")); + ASSERT_STREQ(str, "8000-8191"); + str = mfree(str); + assert_se(str = cpu_set_to_mask_string(&c)); + log_info("cpu_set_to_mask_string: %s", str); + for (size_t i = 0; i < strlen(str); i++) { + if (i < 54) { + if (i >= 8 && (i + 1) % 9 == 0) + assert_se(str[i] == ','); + else + assert_se(str[i] == 'f'); + } + else { + if (i >= 8 && (i + 1) % 9 == 0) + assert_se(str[i] == ','); + else + assert_se(str[i] == '0'); + } + } str = mfree(str); cpu_set_reset(&c); } diff --git a/src/test/test-creds.c b/src/test/test-creds.c index acb198c..cc9cc73 100644 --- a/src/test/test-creds.c +++ b/src/test/test-creds.c @@ -2,10 +2,16 @@ #include "creds-util.h" #include "fileio.h" +#include "format-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "iovec-util.h" #include "path-util.h" #include "rm-rf.h" #include "tests.h" #include "tmpfile-util.h" +#include "tpm2-util.h" +#include "user-util.h" TEST(read_credential_strings) { _cleanup_free_ char *x = NULL, *y = NULL, *saved = NULL, *p = NULL; @@ -17,43 +23,37 @@ TEST(read_credential_strings) { assert_se(saved = strdup(e)); assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); - assert_se(x == NULL); - assert_se(y == NULL); + ASSERT_NULL(x); + ASSERT_NULL(y); assert_se(mkdtemp_malloc(NULL, &tmp) >= 0); assert_se(setenv("CREDENTIALS_DIRECTORY", tmp, /* override= */ true) >= 0); assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); - assert_se(x == NULL); - assert_se(y == NULL); + ASSERT_NULL(x); + ASSERT_NULL(y); assert_se(p = path_join(tmp, "bar")); assert_se(write_string_file(p, "piff", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); - assert_se(x == NULL); - assert_se(streq(y, "piff")); + ASSERT_NULL(x); + ASSERT_STREQ(y, "piff"); assert_se(write_string_file(p, "paff", WRITE_STRING_FILE_TRUNCATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); assert_se(read_credential_strings_many("foo", &x, "bar", &y) == 0); - assert_se(x == NULL); - assert_se(streq(y, "piff")); + ASSERT_NULL(x); + ASSERT_STREQ(y, "paff"); p = mfree(p); assert_se(p = path_join(tmp, "foo")); assert_se(write_string_file(p, "knurz", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_AVOID_NEWLINE) >= 0); assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0); - assert_se(streq(x, "knurz")); - assert_se(streq(y, "piff")); - - y = mfree(y); - - assert_se(read_credential_strings_many("foo", &x, "bar", &y) >= 0); - assert_se(streq(x, "knurz")); - assert_se(streq(y, "paff")); + ASSERT_STREQ(x, "knurz"); + ASSERT_STREQ(y, "paff"); p = mfree(p); assert_se(p = path_join(tmp, "bazz")); @@ -61,9 +61,11 @@ TEST(read_credential_strings) { assert_se(fwrite("x\0y", 1, 3, f) == 3); /* embedded NUL byte should result in EBADMSG when reading back with read_credential_strings_many() */ f = safe_fclose(f); - assert_se(read_credential_strings_many("bazz", &x, "foo", &y) == -EBADMSG); - assert_se(streq(x, "knurz")); - assert_se(streq(y, "paff")); + y = mfree(y); + + assert_se(read_credential_strings_many("bazz", &x, "bar", &y) == -EBADMSG); + ASSERT_STREQ(x, "knurz"); + ASSERT_STREQ(y, "paff"); if (saved) assert_se(setenv("CREDENTIALS_DIRECTORY", saved, /* override= */ 1) >= 0); @@ -118,4 +120,142 @@ TEST(credential_glob_valid) { assert_se(credential_glob_valid(buf)); } +static void test_encrypt_decrypt_with(sd_id128_t mode, uid_t uid) { + static const struct iovec plaintext = CONST_IOVEC_MAKE_STRING("this is a super secret string"); + int r; + + if (uid_is_valid(uid)) + log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR " for UID " UID_FMT ".", SD_ID128_FORMAT_VAL(mode), uid); + else + log_notice("Running encryption/decryption test with mode " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(mode)); + + _cleanup_(iovec_done) struct iovec encrypted = {}; + r = encrypt_credential_and_warn( + mode, + "foo", + /* timestamp= */ USEC_INFINITY, + /* not_after=*/ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_hash_pcr_mask= */ 0, + /* tpm2_pubkey_path= */ NULL, + /* tpm2_pubkey_pcr_mask= */ 0, + uid, + &plaintext, + CREDENTIAL_ALLOW_NULL, + &encrypted); + if (ERRNO_IS_NEG_MACHINE_ID_UNSET(r)) { + log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because /etc/machine-id is not initialized.", SD_ID128_FORMAT_VAL(mode)); + return; + } + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_notice_errno(r, "Skipping test encryption mode " SD_ID128_FORMAT_STR ", because encrypted credentials are not supported.", SD_ID128_FORMAT_VAL(mode)); + return; + } + + assert_se(r >= 0); + + _cleanup_(iovec_done) struct iovec decrypted = {}; + r = decrypt_credential_and_warn( + "bar", + /* validate_timestamp= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + uid, + &encrypted, + CREDENTIAL_ALLOW_NULL, + &decrypted); + assert_se(r == -EREMOTE); /* name didn't match */ + + r = decrypt_credential_and_warn( + "foo", + /* validate_timestamp= */ USEC_INFINITY, + /* tpm2_device= */ NULL, + /* tpm2_signature_path= */ NULL, + uid, + &encrypted, + CREDENTIAL_ALLOW_NULL, + &decrypted); + assert_se(r >= 0); + + assert_se(iovec_memcmp(&plaintext, &decrypted) == 0); +} + +static bool try_tpm2(void) { +#if HAVE_TPM2 + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + int r; + + r = tpm2_context_new(/* device= */ NULL, &tpm2_context); + if (r < 0) + log_notice_errno(r, "Failed to create TPM2 context, assuming no TPM2 support or privileges: %m"); + + return r >= 0; +#else + return false; +#endif +} + +TEST(credential_encrypt_decrypt) { + _cleanup_(rm_rf_physical_and_freep) char *d = NULL; + _cleanup_free_ char *j = NULL; + + log_set_max_level(LOG_DEBUG); + + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_NULL, UID_INVALID); + + assert_se(mkdtemp_malloc(NULL, &d) >= 0); + j = path_join(d, "secret"); + assert_se(j); + + const char *e = getenv("SYSTEMD_CREDENTIAL_SECRET"); + _cleanup_free_ char *ec = NULL; + + if (e) + assert_se(ec = strdup(e)); + + assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", j, true) >= 0); + + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST, UID_INVALID); + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_SCOPED, 0); + + if (try_tpm2()) { + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_TPM2_HMAC, UID_INVALID); + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, UID_INVALID); + test_encrypt_decrypt_with(CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, 0); + } + + if (ec) + assert_se(setenv("SYSTEMD_CREDENTIAL_SECRET", ec, true) >= 0); +} + +TEST(mime_type_matches) { + + static const sd_id128_t tags[] = { + CRED_AES256_GCM_BY_HOST, + CRED_AES256_GCM_BY_HOST_SCOPED, + CRED_AES256_GCM_BY_TPM2_HMAC, + CRED_AES256_GCM_BY_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_SCOPED, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK, + CRED_AES256_GCM_BY_HOST_AND_TPM2_HMAC_WITH_PK_SCOPED, + CRED_AES256_GCM_BY_NULL, + }; + + /* Generates the right expressions for these credentials according to the shared mime-info spec */ + FOREACH_ELEMENT(t, tags) { + _cleanup_free_ char *encoded = NULL; + + assert_se(base64mem(t, sizeof(sd_id128_t), &encoded) >= 0); + + /* Validate that the size matches expectations for the 4/3 factor size increase (rounding up) */ + assert_se(strlen(encoded) == DIV_ROUND_UP((128U / 8U), 3U) * 4U); + + /* Cut off rounded string where the ID ends, but now round down to get rid of characters that might contain follow-up data */ + encoded[128 / 6] = 0; + + printf("\n", encoded); + } +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-cryptolib.c b/src/test/test-cryptolib.c index 6202a5d..9f9be4d 100644 --- a/src/test/test-cryptolib.c +++ b/src/test/test-cryptolib.c @@ -14,25 +14,25 @@ TEST(string_hashsum) { OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), &out1) == 0); /* echo -n 'asdf' | sha224sum - */ - assert_se(streq(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a")); + ASSERT_STREQ(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a"); assert_se(string_hashsum("asdf", 4, OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), &out2) == 0); /* echo -n 'asdf' | sha256sum - */ - assert_se(streq(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); + ASSERT_STREQ(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"); assert_se(string_hashsum("", 0, OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), &out3) == 0); /* echo -n '' | sha224sum - */ - assert_se(streq(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f")); + ASSERT_STREQ(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"); assert_se(string_hashsum("", 0, OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), &out4) == 0); /* echo -n '' | sha256sum - */ - assert_se(streq(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); + ASSERT_STREQ(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-data-fd-util.c b/src/test/test-data-fd-util.c index aa68132..4abd7a6 100644 --- a/src/test/test-data-fd-util.c +++ b/src/test/test-data-fd-util.c @@ -17,27 +17,27 @@ static void test_acquire_data_fd_one(unsigned flags) { char rbuffer[sizeof(wbuffer)]; int fd; - fd = acquire_data_fd("foo", 3, flags); + fd = acquire_data_fd_full("foo", 3, flags); assert_se(fd >= 0); zero(rbuffer); assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 3); - assert_se(streq(rbuffer, "foo")); + ASSERT_STREQ(rbuffer, "foo"); fd = safe_close(fd); - fd = acquire_data_fd("", 0, flags); + fd = acquire_data_fd_full("", SIZE_MAX, flags); assert_se(fd >= 0); zero(rbuffer); assert_se(read(fd, rbuffer, sizeof(rbuffer)) == 0); - assert_se(streq(rbuffer, "")); + ASSERT_STREQ(rbuffer, ""); fd = safe_close(fd); random_bytes(wbuffer, sizeof(wbuffer)); - fd = acquire_data_fd(wbuffer, sizeof(wbuffer), flags); + fd = acquire_data_fd_full(wbuffer, sizeof(wbuffer), flags); assert_se(fd >= 0); zero(rbuffer); @@ -98,14 +98,14 @@ TEST(copy_data_fd) { fd1 = safe_close(fd1); fd2 = safe_close(fd2); - fd1 = acquire_data_fd("hallo", 6, 0); + fd1 = acquire_data_fd("hallo"); assert_se(fd1 >= 0); fd2 = copy_data_fd(fd1); assert_se(fd2 >= 0); safe_close(fd1); - fd1 = acquire_data_fd("hallo", 6, 0); + fd1 = acquire_data_fd("hallo"); assert_se(fd1 >= 0); assert_equal_fd(fd1, fd2); diff --git a/src/test/test-date.c b/src/test/test-date.c index 162ac34..c7d239e 100644 --- a/src/test/test-date.c +++ b/src/test/test-date.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include + #include "alloc-util.h" #include "string-util.h" #include "tests.h" @@ -83,9 +85,11 @@ int main(int argc, char *argv[]) { test_one("today"); test_one("tomorrow"); test_one_noutc("16:20 UTC"); - test_one_noutc("16:20 Asia/Seoul"); - test_one_noutc("tomorrow Asia/Seoul"); - test_one_noutc("2012-12-30 18:42 Asia/Seoul"); + if (access("/usr/share/zoneinfo/Asia/Seoul", F_OK) >= 0) { + test_one_noutc("16:20 Asia/Seoul"); + test_one_noutc("tomorrow Asia/Seoul"); + test_one_noutc("2012-12-30 18:42 Asia/Seoul"); + } test_one_noutc("now"); test_one_noutc("+2d"); test_one_noutc("+2y 4d"); @@ -96,9 +100,11 @@ int main(int argc, char *argv[]) { test_should_fail("1969-12-31 UTC"); test_should_fail("-1000y"); test_should_fail("today UTC UTC"); - test_should_fail("now Asia/Seoul"); - test_should_fail("+2d Asia/Seoul"); - test_should_fail("@1395716396 Asia/Seoul"); + if (access("/usr/share/zoneinfo/Asia/Seoul", F_OK) >= 0) { + test_should_fail("now Asia/Seoul"); + test_should_fail("+2d Asia/Seoul"); + test_should_fail("@1395716396 Asia/Seoul"); + } #if SIZEOF_TIME_T == 8 test_should_pass("9999-12-30 23:59:59 UTC"); test_should_fail("9999-12-31 00:00:00 UTC"); diff --git a/src/test/test-dev-setup.c b/src/test/test-dev-setup.c index b75576a..ed56710 100644 --- a/src/test/test-dev-setup.c +++ b/src/test/test-dev-setup.c @@ -25,6 +25,7 @@ int main(int argc, char *argv[]) { assert_se(mkdir_p(f, 0755) >= 0); assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); + assert_se(make_inaccessible_nodes(f, 1, 1) >= 0); /* 2nd call should be a clean NOP */ f = prefix_roota(p, "/run/systemd/inaccessible/reg"); assert_se(stat(f, &st) >= 0); diff --git a/src/test/test-devnum-util.c b/src/test/test-devnum-util.c index 2068e35..ebef794 100644 --- a/src/test/test-devnum-util.c +++ b/src/test/test-devnum-util.c @@ -109,7 +109,7 @@ TEST(device_path_make_canonical) { static void test_devnum_format_str_one(dev_t devnum, const char *s) { dev_t x; - assert_se(streq(FORMAT_DEVNUM(devnum), s)); + ASSERT_STREQ(FORMAT_DEVNUM(devnum), s); assert_se(parse_devnum(s, &x) >= 0); assert_se(x == devnum); } diff --git a/src/test/test-dirent-util.c b/src/test/test-dirent-util.c new file mode 100644 index 0000000..420ccd1 --- /dev/null +++ b/src/test/test-dirent-util.c @@ -0,0 +1,218 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" +#include "tmpfile-util.h" +#include "tests.h" + +TEST (test_dirent_ensure_type) { + int r, dir_fd; + static struct dirent de = { + .d_type = DT_UNKNOWN, + .d_name = "test", + }; + + dir_fd = 0; + assert_se(dirent_ensure_type(dir_fd, &de) == -ENOTDIR); + + /* Test when d_name is "." or ".." */ + strcpy(de.d_name, "."); + r = dirent_ensure_type(dir_fd, &de); + assert_se(r == 0); + assert_se(de.d_type == DT_DIR); + + strcpy(de.d_name, ".."); + r = dirent_ensure_type(dir_fd, &de); + assert_se(r == 0); + assert_se(de.d_type == DT_DIR); +} + +TEST (test_dirent_is_file) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + const char *name, *dotfile, *name_alias, *bakfile, *tilda; + const struct dirent *de_reg, *de_lnk, *de_dot, *de_bak, *de_tilda; + DIR *dir; + + static const struct dirent de_unknown = { + .d_type = DT_UNKNOWN, + .d_name = "test_unknown", + }; + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + name = strjoina(t, "/test.txt"); + dotfile = strjoina(t, "/.hidden_file"); + bakfile = strjoina(t, "/test.bak"); + tilda = strjoina(t, "/test~"); + name_alias = strjoina(t, "/test_link"); + + assert_se(touch(name) >= 0); + assert_se(touch(dotfile) >= 0); + assert_se(touch(bakfile) >= 0); + assert_se(touch(tilda) >= 0); + + if (symlink(name, name_alias) < 0) { + assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + log_tests_skipped_errno(errno, "symlink() not possible"); + } + + dir = opendir(t); + if (!dir) { + log_error_errno(errno, "Failed to open directory '%s': %m", t); + exit(EXIT_FAILURE); + } + + rewinddir(dir); + while ((de_reg = readdir_ensure_type(dir))) + if (streq(de_reg->d_name, "test.txt")) + break; + + rewinddir(dir); + while ((de_lnk = readdir_ensure_type(dir))) + if (streq(de_lnk->d_name, "test_link")) + break; + + rewinddir(dir); + while ((de_dot = readdir_ensure_type(dir))) + if (streq(de_dot->d_name, ".hidden_file")) + break; + + rewinddir(dir); + while ((de_bak = readdir(dir))) + if (streq(de_bak->d_name, "test.bak")) + break; + + rewinddir(dir); + while ((de_tilda = readdir(dir))) + if (streq(de_tilda->d_name, "test~")) + break; + + /* Test when d_type is DT_REG, DT_LNK, or DT_UNKNOWN */ + assert_se(dirent_is_file(de_reg) == true); + if (de_lnk) + assert_se(dirent_is_file(de_lnk) == true); + else + log_tests_skipped("de_lnk is NULL, skipping test"); + assert_se(dirent_is_file(&de_unknown) == true); + + /* Test for hidden files */ + assert_se(dirent_is_file(de_dot) == false); + assert_se(dirent_is_file(de_bak) == false); + assert_se(dirent_is_file(de_tilda) == false); + + closedir(dir); +} + +TEST (test_dirent_is_file_with_suffix) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + const char *name, *dotfile, *name_alias, *dotdot, *chr; + const struct dirent *de_reg, *de_lnk, *de_dot, *de_dotdot, *de_chr; + DIR *dir; + + static const struct dirent de_unknown = { + .d_type = DT_UNKNOWN, + .d_name = "test_unknown", + }; + + assert_se(mkdtemp_malloc(NULL, &t) >= 0); + + name = strjoina(t, "/test.txt"); + dotfile = strjoina(t, "/.hidden_file"); + dotdot = strjoina(t, "/..dotdot"); + chr = strjoina(t, "/test_chr"); + name_alias = strjoina(t, "/test_link"); + + assert_se(touch(name) >= 0); + assert_se(touch(dotfile) >= 0); + assert_se(touch(dotdot) >= 0); + /* This can fail in containers/build systems */ + if (mknod(chr, 0775 | S_IFCHR, makedev(0, 0)) < 0) { + assert(ERRNO_IS_PRIVILEGE(errno)); + chr = NULL; + } + + if (symlink(name, name_alias) < 0) { + assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM)); + log_tests_skipped_errno(errno, "symlink() not possible"); + } + + dir = opendir(t); + if (!dir) { + log_error_errno(errno, "Failed to open directory '%s': %m", t); + exit(EXIT_FAILURE); + } + + rewinddir(dir); + while ((de_reg = readdir_ensure_type(dir))) + if (streq(de_reg->d_name, "test.txt")) + break; + + rewinddir(dir); + while ((de_lnk = readdir_ensure_type(dir))) + if (streq(de_lnk->d_name, "test_link")) + break; + + rewinddir(dir); + while ((de_dot = readdir_ensure_type(dir))) + if (streq(de_dot->d_name, ".hidden_file")) + break; + + rewinddir(dir); + while ((de_dotdot = readdir(dir))) + if (streq(de_dotdot->d_name, "..dotdot")) + break; + + if (chr) { + rewinddir(dir); + while ((de_chr = readdir(dir))) + if (streq(de_chr->d_name, "test_chr")) + break; + + /* Test when d_type is not DT_REG, DT_LNK, or DT_UNKNOWN */ + assert(de_chr); + assert_se(!dirent_is_file_with_suffix(de_chr, NULL)); + } + + /* Test when suffix is NULL */ + assert_se(dirent_is_file_with_suffix(de_reg, NULL) == true); + if (de_lnk) + assert_se(dirent_is_file_with_suffix(de_lnk, NULL) == true); + else + log_tests_skipped("de_lnk is NULL, skipping test"); + assert_se(dirent_is_file_with_suffix(&de_unknown, NULL) == true); + + /* Test for present suffix */ + assert_se(dirent_is_file_with_suffix(de_reg, "txt") == true); + if (de_lnk) + assert_se(dirent_is_file_with_suffix(de_lnk, "link") == true); + else + log_tests_skipped("de_lnk is NULL, skipping test"); + assert_se(dirent_is_file_with_suffix(&de_unknown, "unknown") == true); + + /* Test for absent suffix */ + assert_se(dirent_is_file_with_suffix(de_reg, "svg") == false); + if (de_lnk) + assert_se(dirent_is_file_with_suffix(de_lnk, "pdf") == false); + else + log_tests_skipped("de_lnk is NULL, skipping test"); + assert_se(dirent_is_file_with_suffix(&de_unknown, "yes") == false); + + /* Test for dot and dot-dot */ + assert_se(dirent_is_file_with_suffix(de_dot, NULL) == false); + assert_se(dirent_is_file_with_suffix(de_dotdot, NULL) == false); + + closedir(dir); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-dlopen-so.c b/src/test/test-dlopen-so.c index e98b8da..d38dff1 100644 --- a/src/test/test-dlopen-so.c +++ b/src/test/test-dlopen-so.c @@ -4,12 +4,16 @@ #include #include "bpf-dlopen.h" +#include "compress.h" #include "cryptsetup-util.h" #include "elf-util.h" +#include "gcrypt-util.h" #include "idn-util.h" +#include "libarchive-util.h" #include "libfido2-util.h" #include "macro.h" #include "main-func.h" +#include "module-util.h" #include "password-quality-util-passwdqc.h" #include "password-quality-util-pwquality.h" #include "pcre2-util.h" @@ -70,6 +74,30 @@ static int run(int argc, char **argv) { assert_se(dlopen_p11kit() >= 0); #endif +#if HAVE_LIBARCHIVE + assert_se(dlopen_libarchive() >= 0); +#endif + +#if HAVE_LZ4 + assert_se(dlopen_lz4() >= 0); +#endif + +#if HAVE_ZSTD + assert_se(dlopen_zstd() >= 0); +#endif + +#if HAVE_XZ + assert_se(dlopen_lzma() >= 0); +#endif + +#if HAVE_GCRYPT + assert_se(initialize_libgcrypt(/* secmem= */ false) >= 0); +#endif + +#if HAVE_KMOD + assert_se(dlopen_libkmod() >= 0); +#endif + return 0; } diff --git a/src/test/test-dns-domain.c b/src/test/test-dns-domain.c index 6c107e2..d775601 100644 --- a/src/test/test-dns-domain.c +++ b/src/test/test-dns-domain.c @@ -16,20 +16,20 @@ static void test_dns_label_unescape_one(const char *what, const char *expect, si r = dns_label_unescape(&w, buffer, buffer_sz, 0); assert_se(r == ret); if (r >= 0) - assert_se(streq(buffer, expect)); + ASSERT_STREQ(buffer, expect); w = what; r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_LDH); assert_se(r == ret_ldh); if (r >= 0) - assert_se(streq(buffer, expect)); + ASSERT_STREQ(buffer, expect); w = what; r = dns_label_unescape(&w, buffer, buffer_sz, DNS_LABEL_NO_ESCAPES); const int ret_noe = strchr(what, '\\') ? -EINVAL : ret; assert_se(r == ret_noe); if (r >= 0) - assert_se(streq(buffer, expect)); + ASSERT_STREQ(buffer, expect); } TEST(dns_label_unescape) { @@ -131,12 +131,12 @@ static void test_dns_label_unescape_suffix_one(const char *what, const char *exp r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); assert_se(r == ret1); if (r >= 0) - assert_se(streq(buffer, expect1)); + ASSERT_STREQ(buffer, expect1); r = dns_label_unescape_suffix(what, &label, buffer, buffer_sz); assert_se(r == ret2); if (r >= 0) - assert_se(streq(buffer, expect2)); + ASSERT_STREQ(buffer, expect2); } TEST(dns_label_unescape_suffix) { @@ -173,7 +173,7 @@ static void test_dns_label_escape_one(const char *what, size_t l, const char *ex if (r < 0) return; - assert_se(streq_ptr(expect, t)); + ASSERT_STREQ(expect, t); } TEST(dns_label_escape) { @@ -193,7 +193,7 @@ static void test_dns_name_normalize_one(const char *what, const char *expect, in if (r < 0) return; - assert_se(streq_ptr(expect, t)); + ASSERT_STREQ(expect, t); } TEST(dns_name_normalize) { @@ -332,7 +332,7 @@ static void test_dns_name_reverse_one(const char *address, const char *name) { assert_se(in_addr_from_string_auto(address, &familya, &a) >= 0); assert_se(dns_name_reverse(familya, &a, &p) >= 0); - assert_se(streq(p, name)); + ASSERT_STREQ(p, name); assert_se(dns_name_address(p, &familyb, &b) > 0); assert_se(familya == familyb); assert_se(in_addr_equal(familya, &a, &b)); @@ -349,7 +349,7 @@ static void test_dns_name_concat_one(const char *a, const char *b, int r, const _cleanup_free_ char *p = NULL; assert_se(dns_name_concat(a, b, 0, &p) == r); - assert_se(streq_ptr(p, result)); + ASSERT_STREQ(p, result); } TEST(dns_name_concat) { @@ -486,14 +486,14 @@ static void test_dns_service_join_one(const char *a, const char *b, const char * log_info("%s, %s, %s, →%d, %s", strnull(a), strnull(b), strnull(c), r, strnull(d)); assert_se(dns_service_join(a, b, c, &t) == r); - assert_se(streq_ptr(t, d)); + ASSERT_STREQ(t, d); if (r < 0) return; assert_se(dns_service_split(t, &x, &y, &z) >= 0); - assert_se(streq_ptr(a, x)); - assert_se(streq_ptr(b, y)); + ASSERT_STREQ(a, x); + ASSERT_STREQ(b, y); assert_se(dns_name_equal(c, z) > 0); } @@ -518,9 +518,9 @@ static void test_dns_service_split_one(const char *joined, const char *a, const log_info("%s, %s, %s, %s, →%d", joined, strnull(a), strnull(b), strnull(c), r); assert_se(dns_service_split(joined, &x, &y, &z) == r); - assert_se(streq_ptr(x, a)); - assert_se(streq_ptr(y, b)); - assert_se(streq_ptr(z, c)); + ASSERT_STREQ(x, a); + ASSERT_STREQ(y, b); + ASSERT_STREQ(z, c); if (r < 0) return; @@ -549,7 +549,7 @@ static void test_dns_name_change_suffix_one(const char *name, const char *old_su log_info("%s, %s, %s, →%s", name, old_suffix, new_suffix, strnull(result)); assert_se(dns_name_change_suffix(name, old_suffix, new_suffix, &s) == r); - assert_se(streq_ptr(s, result)); + ASSERT_STREQ(s, result); } TEST(dns_name_change_suffix) { @@ -570,7 +570,7 @@ static void test_dns_name_suffix_one(const char *name, unsigned n_labels, const log_info("%s, %u, → %s, %d", name, n_labels, strnull(result), ret); assert_se(ret == dns_name_suffix(name, n_labels, &p)); - assert_se(streq_ptr(p, result)); + ASSERT_STREQ(p, result); } TEST(dns_name_suffix) { @@ -660,7 +660,7 @@ static void test_dns_name_common_suffix_one(const char *a, const char *b, const log_info("%s, %s, →%s", a, b, result); assert_se(dns_name_common_suffix(a, b, &c) >= 0); - assert_se(streq(c, result)); + ASSERT_STREQ(c, result); } TEST(dns_name_common_suffix) { diff --git a/src/test/test-ellipsize.c b/src/test/test-ellipsize.c index c272c56..f181476 100644 --- a/src/test/test-ellipsize.c +++ b/src/test/test-ellipsize.c @@ -57,9 +57,9 @@ static void test_ellipsize_mem_one(const char *s, size_t old_length, size_t new_ assert_se(utf8_console_width(t3) <= max_width); if (new_length >= old_length) { - assert_se(streq(t1, n)); - assert_se(streq(t2, n)); - assert_se(streq(t3, n)); + ASSERT_STREQ(t1, n); + ASSERT_STREQ(t2, n); + ASSERT_STREQ(t3, n); } } @@ -143,17 +143,17 @@ TEST(ellipsize_ansi_cats) { e = ellipsize("01" ANSI_NORMAL "23", 4, 0); puts(e); - assert_se(streq(e, "01" ANSI_NORMAL "23")); + ASSERT_STREQ(e, "01" ANSI_NORMAL "23"); f = ellipsize("ab" ANSI_NORMAL "cd", 4, 90); puts(f); - assert_se(streq(f, "ab" ANSI_NORMAL "cd")); + ASSERT_STREQ(f, "ab" ANSI_NORMAL "cd"); g = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 0); puts(g); - assert_se(streq(g, "…" ANSI_NORMAL "🐱🐱" ANSI_NORMAL)); + ASSERT_STREQ(g, "…" ANSI_NORMAL "🐱🐱" ANSI_NORMAL); h = ellipsize("🐱🐱" ANSI_NORMAL "🐱🐱" ANSI_NORMAL, 5, 90); puts(h); - assert_se(streq(h, "🐱…" ANSI_NORMAL "🐱" ANSI_NORMAL)); + ASSERT_STREQ(h, "🐱…" ANSI_NORMAL "🐱" ANSI_NORMAL); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-engine.c b/src/test/test-engine.c index cf77e7c..fa64fbe 100644 --- a/src/test/test-engine.c +++ b/src/test/test-engine.c @@ -214,7 +214,7 @@ int main(int argc, char *argv[]) { assert_se(manager_load_unit(m, "unit-with-multiple-dashes.service", NULL, NULL, &unit_with_multiple_dashes) >= 0); assert_se(strv_equal(unit_with_multiple_dashes->documentation, STRV_MAKE("man:test", "man:override2", "man:override3"))); - assert_se(streq_ptr(unit_with_multiple_dashes->description, "override4")); + ASSERT_STREQ(unit_with_multiple_dashes->description, "override4"); /* Now merge a synthetic unit into the existing one */ assert_se(unit_new_for_name(m, sizeof(Service), "merged.service", &stub) >= 0); @@ -241,10 +241,10 @@ int main(int argc, char *argv[]) { assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-failure.target"), UNIT_ATOM_ON_FAILURE_OF, a)); assert_se( unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "non-existing-on-success.target"))); assert_se( unit_has_dependency(manager_get_unit(m, "non-existing-on-success.target"), UNIT_ATOM_ON_SUCCESS_OF, a)); - assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, "basic.target"))); - assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, "basic.target"))); - assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, "basic.target"))); - assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, "basic.target"))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE, manager_get_unit(m, SPECIAL_BASIC_TARGET))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS, manager_get_unit(m, SPECIAL_BASIC_TARGET))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_FAILURE_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET))); + assert_se(!unit_has_dependency(a, UNIT_ATOM_ON_SUCCESS_OF, manager_get_unit(m, SPECIAL_BASIC_TARGET))); assert_se(!unit_has_dependency(a, UNIT_ATOM_PROPAGATES_RELOAD_TO, manager_get_unit(m, "non-existing-on-failure.target"))); assert_se(unit_has_name(a, "a.service")); diff --git a/src/test/test-env-file.c b/src/test/test-env-file.c index 3fc6d62..f34b2aa 100644 --- a/src/test/test-env-file.c +++ b/src/test/test-env-file.c @@ -65,13 +65,13 @@ TEST(load_env_file_1) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "a=a")); - assert_se(streq(data[1], "b=bc")); - assert_se(streq(data[2], "d=de f")); - assert_se(streq(data[3], "g=g ")); - assert_se(streq(data[4], "h=ąęół śćńźżμ")); - assert_se(streq(data[5], "i=i")); - assert_se(data[6] == NULL); + ASSERT_STREQ(data[0], "a=a"); + ASSERT_STREQ(data[1], "b=bc"); + ASSERT_STREQ(data[2], "d=de f"); + ASSERT_STREQ(data[3], "g=g "); + ASSERT_STREQ(data[4], "h=ąęół śćńźżμ"); + ASSERT_STREQ(data[5], "i=i"); + ASSERT_NULL(data[6]); } TEST(load_env_file_2) { @@ -80,8 +80,8 @@ TEST(load_env_file_2) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "a=a")); - assert_se(data[1] == NULL); + ASSERT_STREQ(data[0], "a=a"); + ASSERT_NULL(data[1]); } TEST(load_env_file_3) { @@ -90,9 +90,9 @@ TEST(load_env_file_3) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "normal1=line111")); - assert_se(streq(data[1], "normal2=line222")); - assert_se(data[2] == NULL); + ASSERT_STREQ(data[0], "normal1=line111"); + ASSERT_STREQ(data[1], "normal2=line222"); + ASSERT_NULL(data[2]); } TEST(load_env_file_4) { @@ -101,10 +101,10 @@ TEST(load_env_file_4) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "HWMON_MODULES=coretemp f71882fg")); - assert_se(streq(data[1], "MODULE_0=coretemp")); - assert_se(streq(data[2], "MODULE_1=f71882fg")); - assert_se(data[3] == NULL); + ASSERT_STREQ(data[0], "HWMON_MODULES=coretemp f71882fg"); + ASSERT_STREQ(data[1], "MODULE_0=coretemp"); + ASSERT_STREQ(data[2], "MODULE_1=f71882fg"); + ASSERT_NULL(data[3]); } TEST(load_env_file_5) { @@ -113,9 +113,9 @@ TEST(load_env_file_5) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "a=")); - assert_se(streq(data[1], "b=")); - assert_se(data[2] == NULL); + ASSERT_STREQ(data[0], "a="); + ASSERT_STREQ(data[1], "b="); + ASSERT_NULL(data[2]); } TEST(load_env_file_6) { @@ -124,11 +124,11 @@ TEST(load_env_file_6) { _cleanup_strv_free_ char **data = NULL; assert_se(load_env_file(NULL, name, &data) == 0); - assert_se(streq(data[0], "a= n t x y '")); - assert_se(streq(data[1], "b=$'")); - assert_se(streq(data[2], "c= \\n\\t\\$\\`\\\\\n")); - assert_se(streq(data[3], "d= \\n\\t$`\\\n")); - assert_se(data[4] == NULL); + ASSERT_STREQ(data[0], "a= n t x y '"); + ASSERT_STREQ(data[1], "b=$'"); + ASSERT_STREQ(data[2], "c= \\n\\t\\$\\`\\\\\n"); + ASSERT_STREQ(data[3], "d= \\n\\t$`\\\n"); + ASSERT_NULL(data[4]); } TEST(load_env_file_invalid_utf8) { @@ -178,13 +178,13 @@ TEST(write_and_load_env_file) { assert_se(f = popen(cmd, "re")); assert_se(read_full_stream(f, &from_shell, &sz) >= 0); assert_se(sz == strlen(v)); - assert_se(streq(from_shell, v)); + ASSERT_STREQ(from_shell, v); assert_se(load_env_file(NULL, p, &l) >= 0); assert_se(strv_equal(l, STRV_MAKE(j))); assert_se(parse_env_file(NULL, p, "TEST", &w) >= 0); - assert_se(streq_ptr(w, v)); + ASSERT_STREQ(w, v); } } diff --git a/src/test/test-env-util.c b/src/test/test-env-util.c index dffbad6..e2c009d 100644 --- a/src/test/test-env-util.c +++ b/src/test/test-env-util.c @@ -26,26 +26,26 @@ TEST(strv_env_delete) { d = strv_env_delete(a, 2, b, c); assert_se(d); - assert_se(streq(d[0], "WALDO=WALDO")); - assert_se(streq(d[1], "WALDO=")); + ASSERT_STREQ(d[0], "WALDO=WALDO"); + ASSERT_STREQ(d[1], "WALDO="); assert_se(strv_length(d) == 2); } TEST(strv_env_get) { char **l = STRV_MAKE("ONE_OR_TWO=1", "THREE=3", "ONE_OR_TWO=2", "FOUR=4"); - assert_se(streq(strv_env_get(l, "ONE_OR_TWO"), "2")); - assert_se(streq(strv_env_get(l, "THREE"), "3")); - assert_se(streq(strv_env_get(l, "FOUR"), "4")); + ASSERT_STREQ(strv_env_get(l, "ONE_OR_TWO"), "2"); + ASSERT_STREQ(strv_env_get(l, "THREE"), "3"); + ASSERT_STREQ(strv_env_get(l, "FOUR"), "4"); } TEST(strv_env_pairs_get) { char **l = STRV_MAKE("ONE_OR_TWO", "1", "THREE", "3", "ONE_OR_TWO", "2", "FOUR", "4", "FIVE", "5", "SIX", "FIVE", "SEVEN", "7"); - assert_se(streq(strv_env_pairs_get(l, "ONE_OR_TWO"), "2")); - assert_se(streq(strv_env_pairs_get(l, "THREE"), "3")); - assert_se(streq(strv_env_pairs_get(l, "FOUR"), "4")); - assert_se(streq(strv_env_pairs_get(l, "FIVE"), "5")); + ASSERT_STREQ(strv_env_pairs_get(l, "ONE_OR_TWO"), "2"); + ASSERT_STREQ(strv_env_pairs_get(l, "THREE"), "3"); + ASSERT_STREQ(strv_env_pairs_get(l, "FOUR"), "4"); + ASSERT_STREQ(strv_env_pairs_get(l, "FIVE"), "5"); } TEST(strv_env_unset) { @@ -56,8 +56,8 @@ TEST(strv_env_unset) { assert_se(strv_env_unset(l, "SCHLUMPF") == l); - assert_se(streq(l[0], "PIEP")); - assert_se(streq(l[1], "NANANANA=YES")); + ASSERT_STREQ(l[0], "PIEP"); + ASSERT_STREQ(l[1], "NANANANA=YES"); assert_se(strv_length(l) == 2); } @@ -67,22 +67,22 @@ TEST(strv_env_merge) { _cleanup_strv_free_ char **r = strv_env_merge(NULL, a, NULL, b, NULL, a, b, b, NULL); assert_se(r); - assert_se(streq(r[0], "FOO=")); - assert_se(streq(r[1], "WALDO=")); - assert_se(streq(r[2], "PIEP")); - assert_se(streq(r[3], "SCHLUMPF=SMURFF")); - assert_se(streq(r[4], "EQ===")); - assert_se(streq(r[5], "PIEP=")); - assert_se(streq(r[6], "NANANANA=YES")); + ASSERT_STREQ(r[0], "FOO="); + ASSERT_STREQ(r[1], "WALDO="); + ASSERT_STREQ(r[2], "PIEP"); + ASSERT_STREQ(r[3], "SCHLUMPF=SMURFF"); + ASSERT_STREQ(r[4], "EQ==="); + ASSERT_STREQ(r[5], "PIEP="); + ASSERT_STREQ(r[6], "NANANANA=YES"); assert_se(strv_length(r) == 7); assert_se(strv_env_clean(r) == r); - assert_se(streq(r[0], "FOO=")); - assert_se(streq(r[1], "WALDO=")); - assert_se(streq(r[2], "SCHLUMPF=SMURFF")); - assert_se(streq(r[3], "EQ===")); - assert_se(streq(r[4], "PIEP=")); - assert_se(streq(r[5], "NANANANA=YES")); + ASSERT_STREQ(r[0], "FOO="); + ASSERT_STREQ(r[1], "WALDO="); + ASSERT_STREQ(r[2], "SCHLUMPF=SMURFF"); + ASSERT_STREQ(r[3], "EQ==="); + ASSERT_STREQ(r[4], "PIEP="); + ASSERT_STREQ(r[5], "NANANANA=YES"); assert_se(strv_length(r) == 6); } @@ -96,8 +96,8 @@ TEST(strv_env_replace_strdup) { assert_se(strv_length(a) == 2); strv_sort(a); - assert_se(streq(a[0], "a=A")); - assert_se(streq(a[1], "b=b")); + ASSERT_STREQ(a[0], "a=A"); + ASSERT_STREQ(a[1], "b=b"); } TEST(strv_env_replace_strdup_passthrough) { @@ -114,9 +114,9 @@ TEST(strv_env_replace_strdup_passthrough) { assert_se(strv_env_replace_strdup_passthrough(&a, "$a") == -EINVAL); assert_se(strv_length(a) == 3); - assert_se(streq(a[0], "a=a")); - assert_se(streq(a[1], "b=")); - assert_se(streq(a[2], "c=")); + ASSERT_STREQ(a[0], "a=a"); + ASSERT_STREQ(a[1], "b="); + ASSERT_STREQ(a[2], "c="); } TEST(strv_env_assign) { @@ -130,7 +130,28 @@ TEST(strv_env_assign) { assert_se(strv_env_assign(&a, "a=", "B") == -EINVAL); assert_se(strv_length(a) == 1); - assert_se(streq(a[0], "a=A")); + ASSERT_STREQ(a[0], "a=A"); +} + +TEST(strv_env_assignf) { + _cleanup_strv_free_ char **a = NULL; + + assert_se(strv_env_assignf(&a, "a", "a") > 0); + assert_se(strv_env_assignf(&a, "a", "%c", 'a') == 0); + + assert_se(strv_env_assignf(&a, "c", "xxx%iyyy", 5) > 0); + assert_se(strv_length(a) == 2); + assert_se(strv_equal(a, STRV_MAKE("a=a", "c=xxx5yyy"))); + assert_se(strv_env_assignf(&a, "c", NULL) == 0); + + assert_se(strv_env_assignf(&a, "b", "b") > 0); + assert_se(strv_env_assignf(&a, "a", "A") == 0); + assert_se(strv_env_assignf(&a, "b", NULL) == 0); + + assert_se(strv_env_assignf(&a, "a=", "B") == -EINVAL); + + assert_se(strv_length(a) == 1); + ASSERT_STREQ(a[0], "a=A"); } TEST(strv_env_assign_many) { @@ -169,15 +190,15 @@ TEST(env_strv_get_n) { }; char **env = (char**) _env; - assert_se(streq(strv_env_get_n(env, "FOO__", 3, 0), "BAR BAR")); - assert_se(streq(strv_env_get_n(env, "FOO__", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); - assert_se(streq(strv_env_get_n(env, "FOO", 3, 0), "BAR BAR")); - assert_se(streq(strv_env_get_n(env, "FOO", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR")); + ASSERT_STREQ(strv_env_get_n(env, "FOO__", 3, 0), "BAR BAR"); + ASSERT_STREQ(strv_env_get_n(env, "FOO__", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR"); + ASSERT_STREQ(strv_env_get_n(env, "FOO", 3, 0), "BAR BAR"); + ASSERT_STREQ(strv_env_get_n(env, "FOO", 3, REPLACE_ENV_USE_ENVIRONMENT), "BAR BAR"); - assert_se(streq(strv_env_get_n(env, "PATH__", 4, 0), "unset")); - assert_se(streq(strv_env_get_n(env, "PATH", 4, 0), "unset")); - assert_se(streq(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); - assert_se(streq(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset")); + ASSERT_STREQ(strv_env_get_n(env, "PATH__", 4, 0), "unset"); + ASSERT_STREQ(strv_env_get_n(env, "PATH", 4, 0), "unset"); + ASSERT_STREQ(strv_env_get_n(env, "PATH__", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset"); + ASSERT_STREQ(strv_env_get_n(env, "PATH", 4, REPLACE_ENV_USE_ENVIRONMENT), "unset"); env[3] = NULL; /* kill our $PATH */ @@ -201,19 +222,19 @@ static void test_replace_env1(bool braceless) { unsigned flags = REPLACE_ENV_ALLOW_BRACELESS*braceless; assert_se(replace_env("FOO=$FOO=${FOO}", (char**) env, flags, &t) >= 0); - assert_se(streq(t, braceless ? "FOO=BAR BAR=BAR BAR" : "FOO=$FOO=BAR BAR")); + ASSERT_STREQ(t, braceless ? "FOO=BAR BAR=BAR BAR" : "FOO=$FOO=BAR BAR"); assert_se(replace_env("BAR=$BAR=${BAR}", (char**) env, flags, &s) >= 0); - assert_se(streq(s, braceless ? "BAR=waldo=waldo" : "BAR=$BAR=waldo")); + ASSERT_STREQ(s, braceless ? "BAR=waldo=waldo" : "BAR=$BAR=waldo"); assert_se(replace_env("BARBAR=$BARBAR=${BARBAR}", (char**) env, flags, &q) >= 0); - assert_se(streq(q, braceless ? "BARBAR==" : "BARBAR=$BARBAR=")); + ASSERT_STREQ(q, braceless ? "BARBAR==" : "BARBAR=$BARBAR="); assert_se(replace_env("BAR=$BAR$BAR${BAR}${BAR}", (char**) env, flags, &r) >= 0); - assert_se(streq(r, braceless ? "BAR=waldowaldowaldowaldo" : "BAR=$BAR$BARwaldowaldo")); + ASSERT_STREQ(r, braceless ? "BAR=waldowaldowaldowaldo" : "BAR=$BAR$BARwaldowaldo"); assert_se(replace_env("${BAR}$BAR$BAR", (char**) env, flags, &p) >= 0); - assert_se(streq(p, braceless ? "waldowaldowaldo" : "waldo$BAR$BAR")); + ASSERT_STREQ(p, braceless ? "waldowaldowaldo" : "waldo$BAR$BAR"); } static void test_replace_env2(bool extended) { @@ -228,25 +249,25 @@ static void test_replace_env2(bool extended) { unsigned flags = REPLACE_ENV_ALLOW_EXTENDED*extended; assert_se(replace_env("FOO=${FOO:-${BAR}}", (char**) env, flags, &t) >= 0); - assert_se(streq(t, extended ? "FOO=foo" : "FOO=${FOO:-bar}")); + ASSERT_STREQ(t, extended ? "FOO=foo" : "FOO=${FOO:-bar}"); assert_se(replace_env("BAR=${XXX:-${BAR}}", (char**) env, flags, &s) >= 0); - assert_se(streq(s, extended ? "BAR=bar" : "BAR=${XXX:-bar}")); + ASSERT_STREQ(s, extended ? "BAR=bar" : "BAR=${XXX:-bar}"); assert_se(replace_env("XXX=${XXX:+${BAR}}", (char**) env, flags, &q) >= 0); - assert_se(streq(q, extended ? "XXX=" : "XXX=${XXX:+bar}")); + ASSERT_STREQ(q, extended ? "XXX=" : "XXX=${XXX:+bar}"); assert_se(replace_env("FOO=${FOO:+${BAR}}", (char**) env, flags, &r) >= 0); - assert_se(streq(r, extended ? "FOO=bar" : "FOO=${FOO:+bar}")); + ASSERT_STREQ(r, extended ? "FOO=bar" : "FOO=${FOO:+bar}"); assert_se(replace_env("FOO=${FOO:-${BAR}post}", (char**) env, flags, &p) >= 0); - assert_se(streq(p, extended ? "FOO=foo" : "FOO=${FOO:-barpost}")); + ASSERT_STREQ(p, extended ? "FOO=foo" : "FOO=${FOO:-barpost}"); assert_se(replace_env("XXX=${XXX:+${BAR}post}", (char**) env, flags, &x) >= 0); - assert_se(streq(x, extended ? "XXX=" : "XXX=${XXX:+barpost}")); + ASSERT_STREQ(x, extended ? "XXX=" : "XXX=${XXX:+barpost}"); assert_se(replace_env("FOO=${FOO}between${BAR:-baz}", (char**) env, flags, &y) >= 0); - assert_se(streq(y, extended ? "FOO=foobetweenbar" : "FOO=foobetween${BAR:-baz}")); + ASSERT_STREQ(y, extended ? "FOO=foobetweenbar" : "FOO=foobetween${BAR:-baz}"); } TEST(replace_env) { @@ -286,23 +307,23 @@ TEST(replace_env_argv) { assert_se(replace_env_argv((char**) line, (char**) env, &r, NULL, NULL) >= 0); assert_se(r); - assert_se(streq(r[0], "FOO$FOO")); - assert_se(streq(r[1], "FOO$FOOFOO")); - assert_se(streq(r[2], "FOOBAR BAR$FOO")); - assert_se(streq(r[3], "FOOBAR BAR")); - assert_se(streq(r[4], "BAR BAR")); - assert_se(streq(r[5], "BAR")); - assert_se(streq(r[6], "BAR")); - assert_se(streq(r[7], "BAR BARwaldo")); - assert_se(streq(r[8], "${FOO")); - assert_se(streq(r[9], "FOO$BAR BAR")); - assert_se(streq(r[10], "$FOOBAR BAR")); - assert_se(streq(r[11], "${FOO:-waldo}")); - assert_se(streq(r[12], "${QUUX:-BAR BAR}")); - assert_se(streq(r[13], "${FOO:+waldo}")); - assert_se(streq(r[14], "${QUUX:+waldo}")); - assert_se(streq(r[15], "${FOO:+|waldo|}}")); - assert_se(streq(r[16], "${FOO:+|waldo{|}")); + ASSERT_STREQ(r[0], "FOO$FOO"); + ASSERT_STREQ(r[1], "FOO$FOOFOO"); + ASSERT_STREQ(r[2], "FOOBAR BAR$FOO"); + ASSERT_STREQ(r[3], "FOOBAR BAR"); + ASSERT_STREQ(r[4], "BAR BAR"); + ASSERT_STREQ(r[5], "BAR"); + ASSERT_STREQ(r[6], "BAR"); + ASSERT_STREQ(r[7], "BAR BARwaldo"); + ASSERT_STREQ(r[8], "${FOO"); + ASSERT_STREQ(r[9], "FOO$BAR BAR"); + ASSERT_STREQ(r[10], "$FOOBAR BAR"); + ASSERT_STREQ(r[11], "${FOO:-waldo}"); + ASSERT_STREQ(r[12], "${QUUX:-BAR BAR}"); + ASSERT_STREQ(r[13], "${FOO:+waldo}"); + ASSERT_STREQ(r[14], "${QUUX:+waldo}"); + ASSERT_STREQ(r[15], "${FOO:+|waldo|}}"); + ASSERT_STREQ(r[16], "${FOO:+|waldo{|}"); assert_se(strv_length(r) == 17); } @@ -372,15 +393,15 @@ TEST(env_clean) { assert_se(strv_env_clean(e) == e); assert_se(strv_env_is_valid(e)); - assert_se(streq(e[0], "FOOBAR=WALDO")); - assert_se(streq(e[1], "X=")); - assert_se(streq(e[2], "F=F")); - assert_se(streq(e[3], "abcd=äöüß")); - assert_se(streq(e[4], "xyz=xyz\n")); - assert_se(streq(e[5], "another=final one")); - assert_se(streq(e[6], "CRLF=\r\n")); - assert_se(streq(e[7], "LESS_TERMCAP_mb=\x1b[01;31m")); - assert_se(e[8] == NULL); + ASSERT_STREQ(e[0], "FOOBAR=WALDO"); + ASSERT_STREQ(e[1], "X="); + ASSERT_STREQ(e[2], "F=F"); + ASSERT_STREQ(e[3], "abcd=äöüß"); + ASSERT_STREQ(e[4], "xyz=xyz\n"); + ASSERT_STREQ(e[5], "another=final one"); + ASSERT_STREQ(e[6], "CRLF=\r\n"); + ASSERT_STREQ(e[7], "LESS_TERMCAP_mb=\x1b[01;31m"); + ASSERT_NULL(e[8]); } TEST(env_name_is_valid) { @@ -430,13 +451,13 @@ TEST(env_assignment_is_valid) { TEST(putenv_dup) { assert_se(putenv_dup("A=a1", true) == 0); - assert_se(streq_ptr(getenv("A"), "a1")); + ASSERT_STREQ(getenv("A"), "a1"); assert_se(putenv_dup("A=a1", true) == 0); - assert_se(streq_ptr(getenv("A"), "a1")); + ASSERT_STREQ(getenv("A"), "a1"); assert_se(putenv_dup("A=a2", false) == 0); - assert_se(streq_ptr(getenv("A"), "a1")); + ASSERT_STREQ(getenv("A"), "a1"); assert_se(putenv_dup("A=a2", true) == 0); - assert_se(streq_ptr(getenv("A"), "a2")); + ASSERT_STREQ(getenv("A"), "a2"); } TEST(setenv_systemd_exec_pid) { @@ -455,7 +476,7 @@ TEST(setenv_systemd_exec_pid) { assert_se(setenv("SYSTEMD_EXEC_PID", "*", 1) >= 0); assert_se(setenv_systemd_exec_pid(true) == 0); assert_se(e = getenv("SYSTEMD_EXEC_PID")); - assert_se(streq(e, "*")); + ASSERT_STREQ(e, "*"); assert_se(setenv("SYSTEMD_EXEC_PID", "123abc", 1) >= 0); assert_se(setenv_systemd_exec_pid(true) == 1); @@ -502,10 +523,10 @@ TEST(getenv_steal_erase) { copy1 = strdup(eq + 1); assert_se(copy1); - assert_se(streq_ptr(getenv(n), copy1)); + ASSERT_STREQ(getenv(n), copy1); assert_se(getenv(n) == eq + 1); assert_se(getenv_steal_erase(n, ©2) > 0); - assert_se(streq_ptr(copy1, copy2)); + ASSERT_STREQ(copy1, copy2); assert_se(isempty(eq + 1)); assert_se(!getenv(n)); } @@ -550,12 +571,12 @@ TEST(getenv_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_STREQ(path_list[0], "/foo"); + ASSERT_STREQ(path_list[1], "/bar/baz"); + ASSERT_STREQ(path_list[2], "/hello/world"); + ASSERT_STREQ(path_list[3], "/path with spaces"); + ASSERT_STREQ(path_list[4], "/final"); + ASSERT_NULL(path_list[5]); assert_se(unsetenv("TEST_GETENV_PATH_LIST") >= 0); } diff --git a/src/test/test-errno-list.c b/src/test/test-errno-list.c index f91a1f7..77f4187 100644 --- a/src/test/test-errno-list.c +++ b/src/test/test-errno-list.c @@ -11,22 +11,22 @@ TEST(errno_list) { for (size_t i = 0; i < ELEMENTSOF(errno_names); i++) { if (errno_names[i]) { - assert_se(streq(errno_to_name(i), errno_names[i])); + ASSERT_STREQ(errno_to_name(i), errno_names[i]); assert_se(errno_from_name(errno_names[i]) == (int) i); } } #ifdef ECANCELLED /* ECANCELLED is an alias of ECANCELED. */ - assert_se(streq(errno_to_name(ECANCELLED), "ECANCELED")); + ASSERT_STREQ(errno_to_name(ECANCELLED), "ECANCELED"); #endif - assert_se(streq(errno_to_name(ECANCELED), "ECANCELED")); + ASSERT_STREQ(errno_to_name(ECANCELED), "ECANCELED"); #ifdef EREFUSED /* EREFUSED is an alias of ECONNREFUSED. */ - assert_se(streq(errno_to_name(EREFUSED), "ECONNREFUSED")); + ASSERT_STREQ(errno_to_name(EREFUSED), "ECONNREFUSED"); #endif - assert_se(streq(errno_to_name(ECONNREFUSED), "ECONNREFUSED")); + ASSERT_STREQ(errno_to_name(ECONNREFUSED), "ECONNREFUSED"); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c index 376d532..ab463bd 100644 --- a/src/test/test-errno-util.c +++ b/src/test/test-errno-util.c @@ -31,8 +31,8 @@ TEST(STRERROR) { assert_se(strstr(b, "201")); /* Check with negative values */ - assert_se(streq(a, STRERROR(-200))); - assert_se(streq(b, STRERROR(-201))); + ASSERT_STREQ(a, STRERROR(-200)); + ASSERT_STREQ(b, STRERROR(-201)); const char *c = STRERROR(INT_MAX); char buf[DECIMAL_STR_MAX(int)]; diff --git a/src/test/test-escape.c b/src/test/test-escape.c index 21786ae..89a25fe 100644 --- a/src/test/test-escape.c +++ b/src/test/test-escape.c @@ -9,14 +9,14 @@ TEST(cescape) { _cleanup_free_ char *t = NULL; assert_se(t = cescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313")); - assert_se(streq(t, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\a\\003\\177\\234\\313")); + ASSERT_STREQ(t, "abc\\\\\\\"\\b\\f\\n\\r\\t\\v\\a\\003\\177\\234\\313"); } TEST(xescape) { _cleanup_free_ char *t = NULL; assert_se(t = xescape("abc\\\"\b\f\n\r\t\v\a\003\177\234\313", "")); - assert_se(streq(t, "abc\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb")); + ASSERT_STREQ(t, "abc\\x5c\"\\x08\\x0c\\x0a\\x0d\\x09\\x0b\\x07\\x03\\x7f\\x9c\\xcb"); } static void test_xescape_full_one(bool eight_bits) { @@ -36,7 +36,7 @@ static void test_xescape_full_one(bool eight_bits) { log_info("%02u: <%s>", i, t); if (i >= full_fit) - assert_se(streq(t, escaped)); + ASSERT_STREQ(t, escaped); else if (i >= 3) { /* We need up to four columns, so up to three columns may be wasted */ assert_se(strlen(t) == i || strlen(t) == i - 1 || strlen(t) == i - 2 || strlen(t) == i - 3); @@ -69,50 +69,50 @@ TEST(cunescape) { assert_se(cunescape("abc\\\\\\\"\\b\\f\\a\\n\\r\\t\\v\\003\\177\\234\\313\\000\\x00", 0, &unescaped) < 0); assert_se(cunescape("abc\\\\\\\"\\b\\f\\a\\n\\r\\t\\v\\003\\177\\234\\313\\000\\x00", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "abc\\\"\b\f\a\n\r\t\v\003\177\234\313\\000\\x00")); + ASSERT_STREQ(unescaped, "abc\\\"\b\f\a\n\r\t\v\003\177\234\313\\000\\x00"); unescaped = mfree(unescaped); /* incomplete sequences */ assert_se(cunescape("\\x0", 0, &unescaped) < 0); assert_se(cunescape("\\x0", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "\\x0")); + ASSERT_STREQ(unescaped, "\\x0"); unescaped = mfree(unescaped); assert_se(cunescape("\\x", 0, &unescaped) < 0); assert_se(cunescape("\\x", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "\\x")); + ASSERT_STREQ(unescaped, "\\x"); unescaped = mfree(unescaped); assert_se(cunescape("\\", 0, &unescaped) < 0); assert_se(cunescape("\\", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "\\")); + ASSERT_STREQ(unescaped, "\\"); unescaped = mfree(unescaped); assert_se(cunescape("\\11", 0, &unescaped) < 0); assert_se(cunescape("\\11", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "\\11")); + ASSERT_STREQ(unescaped, "\\11"); unescaped = mfree(unescaped); assert_se(cunescape("\\1", 0, &unescaped) < 0); assert_se(cunescape("\\1", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "\\1")); + ASSERT_STREQ(unescaped, "\\1"); unescaped = mfree(unescaped); assert_se(cunescape("\\u0000", 0, &unescaped) < 0); assert_se(cunescape("\\u00DF\\U000000df\\u03a0\\U00000041", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "ßßΠA")); + ASSERT_STREQ(unescaped, "ßßΠA"); unescaped = mfree(unescaped); assert_se(cunescape("\\073", 0, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, ";")); + ASSERT_STREQ(unescaped, ";"); unescaped = mfree(unescaped); assert_se(cunescape("A=A\\\\x0aB", 0, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "A=A\\x0aB")); + ASSERT_STREQ(unescaped, "A=A\\x0aB"); unescaped = mfree(unescaped); assert_se(cunescape("A=A\\\\x0aB", UNESCAPE_RELAX, &unescaped) >= 0); - assert_se(streq_ptr(unescaped, "A=A\\x0aB")); + ASSERT_STREQ(unescaped, "A=A\\x0aB"); unescaped = mfree(unescaped); assert_se(cunescape("\\x00\\x00\\x00", UNESCAPE_ACCEPT_NUL, &unescaped) == 3); @@ -136,7 +136,7 @@ static void test_shell_escape_one(const char *s, const char *bad, const char *ex assert_se(r = shell_escape(s, bad)); log_debug("%s → %s (expected %s)", s, r, expected); - assert_se(streq_ptr(r, expected)); + ASSERT_STREQ(r, expected); } TEST(shell_escape) { @@ -153,7 +153,7 @@ static void test_shell_maybe_quote_one(const char *s, ShellEscapeFlags flags, co assert_se(ret = shell_maybe_quote(s, flags)); log_debug("[%s] → [%s] (%s)", s, ret, expected); - assert_se(streq(ret, expected)); + ASSERT_STREQ(ret, expected); } TEST(shell_maybe_quote) { @@ -207,7 +207,7 @@ static void test_quote_command_line_one(char **argv, const char *expected) { assert_se(s = quote_command_line(argv, SHELL_ESCAPE_EMPTY)); log_info("%s", s); - assert_se(streq(s, expected)); + ASSERT_STREQ(s, expected); } TEST(quote_command_line) { @@ -228,7 +228,7 @@ static void test_octescape_one(const char *s, const char *expected) { assert_se(ret = octescape(s, strlen_ptr(s))); log_debug("octescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected); - assert_se(streq(ret, expected)); + ASSERT_STREQ(ret, expected); } TEST(octescape) { @@ -239,4 +239,22 @@ TEST(octescape) { test_octescape_one("\123\213\222", "\123\\213\\222"); } +static void test_decescape_one(const char *s, const char *bad, const char *expected) { + _cleanup_free_ char *ret = NULL; + + assert_se(ret = decescape(s, bad, strlen_ptr(s))); + log_debug("decescape(\"%s\") → \"%s\" (expected: \"%s\")", strnull(s), ret, expected); + ASSERT_STREQ(ret, expected); +} + +TEST(decescape) { + test_decescape_one(NULL, "bad", ""); + test_decescape_one("foo", "", "foo"); + test_decescape_one("foo", "f", "\\102oo"); + test_decescape_one("foo", "o", "f\\111\\111"); + test_decescape_one("go\"bb\\ledyg\x03ook\r\n", "", "go\\034bb\\092ledyg\\003ook\\013\\010"); + test_decescape_one("\\xff\xff" "f", "f", "\\092x\\102\\102\\255\\102"); + test_decescape_one("all", "all", "\\097\\108\\108"); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-ether-addr-util.c b/src/test/test-ether-addr-util.c index d680f80..4826a6f 100644 --- a/src/test/test-ether-addr-util.c +++ b/src/test/test-ether-addr-util.c @@ -72,7 +72,7 @@ static void test_parse_hw_addr_full_one(const char *in, size_t expected_len, con if (r >= 0) { if (!IN_SET(expected_len, 0, SIZE_MAX)) assert_se(h.length == expected_len); - assert_se(streq(HW_ADDR_TO_STR(&h), expected)); + ASSERT_STREQ(HW_ADDR_TO_STR(&h), expected); } } diff --git a/src/test/test-exec-util.c b/src/test/test-exec-util.c index 2304f6a..533c988 100644 --- a/src/test/test-exec-util.c +++ b/src/test/test-exec-util.c @@ -195,7 +195,7 @@ TEST(execution_order) { execute_directories(dirs, DEFAULT_TIMEOUT_USEC, ignore_stdout, ignore_stdout_args, NULL, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); assert_se(read_full_file(output, &contents, NULL) >= 0); - assert_se(streq(contents, "30-override\n80-foo\n90-bar\nlast\n")); + ASSERT_STREQ(contents, "30-override\n80-foo\n90-bar\nlast\n"); } static int gather_stdout_one(int fd, void *arg) { @@ -279,7 +279,7 @@ TEST(stdout_gathering) { log_info("got: %s", output); - assert_se(streq(output, "a\nb\nc\nd\n")); + ASSERT_STREQ(output, "a\nb\nc\nd\n"); } TEST(environment_gathering) { @@ -331,7 +331,7 @@ TEST(environment_gathering) { assert_se(chmod(name3, 0755) == 0); /* When booting in containers or without initrd there might not be any PATH in the environment and if - * there is no PATH /bin/sh built-in PATH may leak and override systemd's DEFAULT_PATH which is not + * there is no PATH /bin/sh built-in PATH may leak and override systemd's default path which is not * good. Force our own PATH in environment, to prevent expansion of sh built-in $PATH */ old = getenv("PATH"); r = setenv("PATH", "no-sh-built-in-path", 1); @@ -346,15 +346,14 @@ TEST(environment_gathering) { STRV_FOREACH(p, env) log_info("got env: \"%s\"", *p); - assert_se(streq(strv_env_get(env, "A"), "22:23:24")); - assert_se(streq(strv_env_get(env, "B"), "12")); - assert_se(streq(strv_env_get(env, "C"), "001")); - assert_se(streq(strv_env_get(env, "PATH"), "no-sh-built-in-path:/no/such/file")); + ASSERT_STREQ(strv_env_get(env, "A"), "22:23:24"); + ASSERT_STREQ(strv_env_get(env, "B"), "12"); + ASSERT_STREQ(strv_env_get(env, "C"), "001"); + ASSERT_STREQ(strv_env_get(env, "PATH"), "no-sh-built-in-path:/no/such/file"); - /* now retest with "default" path passed in, as created by - * manager_default_environment */ + /* Now retest with some "default" path passed. */ env = strv_free(env); - env = strv_new("PATH=" DEFAULT_PATH); + env = strv_new("PATH=" DEFAULT_PATH_WITHOUT_SBIN); assert_se(env); r = execute_directories(dirs, DEFAULT_TIMEOUT_USEC, gather_environment, args, NULL, env, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS); @@ -363,10 +362,10 @@ TEST(environment_gathering) { STRV_FOREACH(p, env) log_info("got env: \"%s\"", *p); - assert_se(streq(strv_env_get(env, "A"), "22:23:24")); - assert_se(streq(strv_env_get(env, "B"), "12")); - assert_se(streq(strv_env_get(env, "C"), "001")); - assert_se(streq(strv_env_get(env, "PATH"), DEFAULT_PATH ":/no/such/file")); + ASSERT_STREQ(strv_env_get(env, "A"), "22:23:24"); + ASSERT_STREQ(strv_env_get(env, "B"), "12"); + ASSERT_STREQ(strv_env_get(env, "C"), "001"); + ASSERT_STREQ(strv_env_get(env, "PATH"), DEFAULT_PATH_WITHOUT_SBIN ":/no/such/file"); /* reset environ PATH */ assert_se(set_unset_env("PATH", old, true) == 0); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 4f6ad5d..4b8daa4 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -7,6 +7,7 @@ #include "sd-event.h" +#include "build-path.h" #include "capability-util.h" #include "cpu-set-util.h" #include "copy.h" @@ -28,6 +29,7 @@ #include "signal-util.h" #include "static-destruct.h" #include "stat-util.h" +#include "sysctl-util.h" #include "tests.h" #include "tmpfile-util.h" #include "unit.h" @@ -57,8 +59,13 @@ static void wait_for_service_finish(Manager *m, Unit *unit) { usec_t ts; usec_t timeout = 2 * USEC_PER_MINUTE; - assert_se(m); - assert_se(unit); + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(unit); + + /* Bump the timeout when running in plain QEMU, as some more involved tests might start hitting the + * default 2m timeout (like exec-dynamicuser-statedir.service) */ + if (detect_virtualization() == VIRTUALIZATION_QEMU) + timeout *= 2; service = SERVICE(unit); printf("%s\n", unit->id); @@ -69,7 +76,7 @@ static void wait_for_service_finish(Manager *m, Unit *unit) { usec_t n; r = sd_event_run(m->event, 100 * USEC_PER_MSEC); - assert_se(r >= 0); + ASSERT_OK(r); n = now(CLOCK_MONOTONIC); if (ts + timeout < n) { @@ -86,8 +93,8 @@ static void check_main_result(const char *file, unsigned line, const char *func, Manager *m, Unit *unit, int status_expected, int code_expected) { Service *service = NULL; - assert_se(m); - assert_se(unit); + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(unit); wait_for_service_finish(m, unit); @@ -113,8 +120,8 @@ static void check_service_result(const char *file, unsigned line, const char *fu Manager *m, Unit *unit, ServiceResult result_expected) { Service *service = NULL; - assert_se(m); - assert_se(unit); + ASSERT_NOT_NULL(m); + ASSERT_NOT_NULL(unit); wait_for_service_finish(m, unit); @@ -178,7 +185,7 @@ static bool check_user_has_group_with_same_name(const char *name) { struct passwd *p; struct group *g; - assert_se(name); + ASSERT_NOT_NULL(name); p = getpwnam(name); if (!p || @@ -214,20 +221,41 @@ static void start_parent_slices(Unit *unit) { if (slice) { start_parent_slices(slice); int r = unit_start(slice, NULL); - assert_se(r >= 0 || r == -EALREADY); + if (r != -EALREADY) + ASSERT_OK(r); + } +} + +static bool apparmor_restrict_unprivileged_userns(void) { + _cleanup_free_ char *v = NULL; + int r; + + /* If kernel.apparmor_restrict_unprivileged_userns=1, then we cannot + * use unprivileged user namespaces. */ + r = sysctl_read("kernel/apparmor_restrict_unprivileged_userns", &v); + if (r < 0) { + if (r != -ENOENT) + log_debug_errno(r, "Failed to read kernel.apparmor_restrict_unprivileged_userns sysctl, ignoring: %m"); + + return false; } + + return streq(v, "1"); } static bool have_userns_privileges(void) { pid_t pid; int r; + if (apparmor_restrict_unprivileged_userns()) + return false; + r = safe_fork("(sd-test-check-userns)", FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | FORK_DEATHSIG_SIGKILL, &pid); - assert(r >= 0); + ASSERT_OK(r); if (r == 0) { /* Keep CAP_SYS_ADMIN if we have it to ensure we give an * accurate result to the caller. Some kernels have a @@ -263,13 +291,13 @@ static void _test(const char *file, unsigned line, const char *func, Manager *m, const char *unit_name, int status_expected, int code_expected) { Unit *unit; - assert_se(unit_name); + ASSERT_NOT_NULL(unit_name); - assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit)); /* We need to start the slices as well otherwise the slice cgroups might be pruned * in on_cgroup_empty_event. */ start_parent_slices(unit); - assert_se(unit_start(unit, NULL) >= 0); + ASSERT_OK(unit_start(unit, NULL)); check_main_result(file, line, func, m, unit, status_expected, code_expected); ++n_ran_tests; @@ -281,18 +309,18 @@ static void _test_service(const char *file, unsigned line, const char *func, Manager *m, const char *unit_name, ServiceResult result_expected) { Unit *unit; - assert_se(unit_name); + ASSERT_NOT_NULL(unit_name); - assert_se(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit) >= 0); - assert_se(unit_start(unit, NULL) >= 0); + ASSERT_OK(manager_load_startable_unit_or_warn(m, unit_name, NULL, &unit)); + ASSERT_OK(unit_start(unit, NULL)); check_service_result(file, line, func, m, unit, result_expected); } #define test_service(m, unit_name, result_expected) \ _test_service(PROJECT_FILE, __LINE__, __func__, m, unit_name, result_expected) static void test_exec_bindpaths(Manager *m) { - assert_se(mkdir_p("/tmp/test-exec-bindpaths", 0755) >= 0); - assert_se(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755) >= 0); + ASSERT_OK(mkdir_p("/tmp/test-exec-bindpaths", 0755)); + ASSERT_OK(mkdir_p("/tmp/test-exec-bindreadonlypaths", 0755)); test(m, "exec-bindpaths.service", can_unshare ? 0 : EXIT_NAMESPACE, CLD_EXITED); @@ -303,8 +331,8 @@ static void test_exec_bindpaths(Manager *m) { static void test_exec_cpuaffinity(Manager *m) { _cleanup_(cpu_set_reset) CPUSet c = {}; - assert_se(cpu_set_realloc(&c, 8192) >= 0); /* just allocate the maximum possible size */ - assert_se(sched_getaffinity(0, c.allocated, c.set) >= 0); + ASSERT_OK(cpu_set_realloc(&c, 8192)); /* just allocate the maximum possible size */ + ASSERT_OK_ERRNO(sched_getaffinity(0, c.allocated, c.set)); if (!CPU_ISSET_S(0, c.allocated, c.set)) { log_notice("Cannot use CPU 0, skipping %s", __func__); @@ -330,7 +358,7 @@ static void test_exec_credentials(Manager *m) { } static void test_exec_workingdirectory(Manager *m) { - assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0); + ASSERT_OK(mkdir_p("/tmp/test-exec_workingdirectory", 0755)); test(m, "exec-workingdirectory.service", 0, CLD_EXITED); test(m, "exec-workingdirectory-trailing-dot.service", 0, CLD_EXITED); @@ -339,13 +367,13 @@ 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_OK(mkdir_p("/tmp/test-exec_execsearchpath", 0755)); - assert_se(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE) >= 0); + ASSERT_OK(copy_file("/bin/ls", "/tmp/test-exec_execsearchpath/ls_temp", 0, 0777, COPY_REPLACE)); test(m, "exec-execsearchpath.service", 0, CLD_EXITED); - assert_se(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL) >= 0); + ASSERT_OK(rm_rf("/tmp/test-exec_execsearchpath", REMOVE_ROOT|REMOVE_PHYSICAL)); test(m, "exec-execsearchpath.service", EXIT_EXEC, CLD_EXITED); } @@ -388,8 +416,7 @@ static void test_exec_execsearchpath_environment_files(Manager *m) { int r; r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile.conf", path_not_set, WRITE_STRING_FILE_CREATE); - - assert_se(r == 0); + ASSERT_OK(r); test(m, "exec-execsearchpath-environmentfile.service", 0, CLD_EXITED); @@ -397,8 +424,7 @@ static void test_exec_execsearchpath_environment_files(Manager *m) { r = write_string_file("/tmp/test-exec_execsearchpath_environmentfile-set.conf", path_set, WRITE_STRING_FILE_CREATE); - - assert_se(r == 0); + ASSERT_OK(r); test(m, "exec-execsearchpath-environmentfile-set.service", 0, CLD_EXITED); @@ -406,29 +432,32 @@ static void test_exec_execsearchpath_environment_files(Manager *m) { } static void test_exec_execsearchpath_passenvironment(Manager *m) { - assert_se(setenv("VAR1", "word1 word2", 1) == 0); - assert_se(setenv("VAR2", "word3", 1) == 0); - assert_se(setenv("VAR3", "$word 5 6", 1) == 0); - assert_se(setenv("VAR4", "new\nline", 1) == 0); - assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); + ASSERT_OK_ERRNO(setenv("VAR1", "word1 word2", 1)); + ASSERT_OK_ERRNO(setenv("VAR2", "word3", 1)); + ASSERT_OK_ERRNO(setenv("VAR3", "$word 5 6", 1)); + ASSERT_OK_ERRNO(setenv("VAR4", "new\nline", 1)); + ASSERT_OK_ERRNO(setenv("VAR5", "passwordwithbackslashes", 1)); test(m, "exec-execsearchpath-passenvironment.service", 0, CLD_EXITED); - assert_se(setenv("PATH", "/usr", 1) == 0); + ASSERT_OK_ERRNO(setenv("PATH", "/usr", 1)); test(m, "exec-execsearchpath-passenvironment-set.service", 0, CLD_EXITED); - assert_se(unsetenv("VAR1") == 0); - assert_se(unsetenv("VAR2") == 0); - assert_se(unsetenv("VAR3") == 0); - assert_se(unsetenv("VAR4") == 0); - assert_se(unsetenv("VAR5") == 0); - assert_se(unsetenv("PATH") == 0); + ASSERT_OK_ERRNO(unsetenv("VAR1")); + ASSERT_OK_ERRNO(unsetenv("VAR2")); + ASSERT_OK_ERRNO(unsetenv("VAR3")); + ASSERT_OK_ERRNO(unsetenv("VAR4")); + ASSERT_OK_ERRNO(unsetenv("VAR5")); + ASSERT_OK_ERRNO(unsetenv("PATH")); } static void test_exec_personality(Manager *m) { #if defined(__x86_64__) test(m, "exec-personality-x86-64.service", 0, CLD_EXITED); +#elif defined(__s390x__) + test(m, "exec-personality-s390x.service", 0, CLD_EXITED); + #elif defined(__s390__) test(m, "exec-personality-s390.service", 0, CLD_EXITED); @@ -457,7 +486,7 @@ static void test_exec_ignoresigpipe(Manager *m) { } static void test_exec_privatetmp(Manager *m) { - assert_se(touch("/tmp/test-exec_privatetmp") >= 0); + ASSERT_OK(touch("/tmp/test-exec_privatetmp")); if (MANAGER_IS_SYSTEM(m) || have_userns_privileges()) { test(m, "exec-privatetmp-yes.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); @@ -586,13 +615,14 @@ static void test_exec_inaccessiblepaths(Manager *m) { test(m, "exec-inaccessiblepaths-mount-propagation.service", can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_FAILURE : EXIT_NAMESPACE, CLD_EXITED); } +#if !HAS_FEATURE_ADDRESS_SANITIZER static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userdata) { char **result = userdata; char buf[4096]; ssize_t l; - assert_se(s); - assert_se(fd >= 0); + ASSERT_NOT_NULL(s); + ASSERT_GT(fd, 0); l = read(fd, buf, sizeof(buf) - 1); if (l < 0) { @@ -606,20 +636,20 @@ static int on_spawn_io(sd_event_source *s, int fd, uint32_t revents, void *userd buf[l] = '\0'; if (result) - assert_se(strextend(result, buf)); + ASSERT_NOT_NULL(strextend(result, buf)); else log_error("ldd: %s", buf); reenable: /* Re-enable the event source if we did not encounter EOF */ - assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); + ASSERT_OK(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT)); return 0; } static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { pid_t *pid = userdata; - assert_se(pid); + ASSERT_NOT_NULL(pid); (void) kill(*pid, SIGKILL); @@ -629,7 +659,7 @@ static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { static int on_spawn_sigchld(sd_event_source *s, const siginfo_t *si, void *userdata) { int ret = -EIO; - assert_se(si); + ASSERT_NOT_NULL(si); if (si->si_code == CLD_EXITED) ret = si->si_status; @@ -649,19 +679,19 @@ static int find_libraries(const char *exec, char ***ret) { pid_t pid; int r; - assert_se(exec); - assert_se(ret); + ASSERT_NOT_NULL(exec); + ASSERT_NOT_NULL(ret); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + ASSERT_OK(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD)); - assert_se(pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) == 0); - assert_se(pipe2(errpipe, O_NONBLOCK|O_CLOEXEC) == 0); + ASSERT_OK_ERRNO(pipe2(outpipe, O_NONBLOCK|O_CLOEXEC)); + ASSERT_OK_ERRNO(pipe2(errpipe, O_NONBLOCK|O_CLOEXEC)); r = safe_fork_full("(spawn-ldd)", (int[]) { -EBADF, outpipe[1], errpipe[1] }, NULL, 0, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REARRANGE_STDIO|FORK_LOG, &pid); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) { execlp("ldd", "ldd", exec, NULL); _exit(EXIT_FAILURE); @@ -670,40 +700,40 @@ static int find_libraries(const char *exec, char ***ret) { outpipe[1] = safe_close(outpipe[1]); errpipe[1] = safe_close(errpipe[1]); - assert_se(sd_event_new(&e) >= 0); + ASSERT_OK(sd_event_new(&e)); - assert_se(sd_event_add_time_relative(e, NULL, CLOCK_MONOTONIC, - 10 * USEC_PER_SEC, USEC_PER_SEC, on_spawn_timeout, &pid) >= 0); - assert_se(sd_event_add_io(e, &stdout_source, outpipe[0], EPOLLIN, on_spawn_io, &result) >= 0); - assert_se(sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_add_io(e, &stderr_source, errpipe[0], EPOLLIN, on_spawn_io, NULL) >= 0); - assert_se(sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_add_child(e, &sigchld_source, pid, WEXITED, on_spawn_sigchld, NULL) >= 0); + ASSERT_OK(sd_event_add_time_relative(e, NULL, CLOCK_MONOTONIC, + 10 * USEC_PER_SEC, USEC_PER_SEC, on_spawn_timeout, &pid)); + ASSERT_OK(sd_event_add_io(e, &stdout_source, outpipe[0], EPOLLIN, on_spawn_io, &result)); + ASSERT_OK(sd_event_source_set_enabled(stdout_source, SD_EVENT_ONESHOT)); + ASSERT_OK(sd_event_add_io(e, &stderr_source, errpipe[0], EPOLLIN, on_spawn_io, NULL)); + ASSERT_OK(sd_event_source_set_enabled(stderr_source, SD_EVENT_ONESHOT)); + ASSERT_OK(sd_event_add_child(e, &sigchld_source, pid, WEXITED, on_spawn_sigchld, NULL)); /* SIGCHLD should be processed after IO is complete */ - assert_se(sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1) >= 0); + ASSERT_OK(sd_event_source_set_priority(sigchld_source, SD_EVENT_PRIORITY_NORMAL + 1)); - assert_se(sd_event_loop(e) >= 0); + ASSERT_OK(sd_event_loop(e)); _cleanup_strv_free_ char **v = NULL; - assert_se(strv_split_newlines_full(&v, result, 0) >= 0); + ASSERT_OK(strv_split_newlines_full(&v, result, 0)); STRV_FOREACH(q, v) { _cleanup_free_ char *word = NULL; const char *p = *q; r = extract_first_word(&p, &word, NULL, 0); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) continue; if (path_is_absolute(word)) { - assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0); + ASSERT_OK(strv_consume(&libraries, TAKE_PTR(word))); continue; } word = mfree(word); r = extract_first_word(&p, &word, NULL, 0); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) continue; @@ -712,12 +742,12 @@ static int find_libraries(const char *exec, char ***ret) { word = mfree(word); r = extract_first_word(&p, &word, NULL, 0); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) continue; if (path_is_absolute(word)) { - assert_se(strv_consume(&libraries, TAKE_PTR(word)) >= 0); + ASSERT_OK(strv_consume(&libraries, TAKE_PTR(word))); continue; } } @@ -725,13 +755,15 @@ static int find_libraries(const char *exec, char ***ret) { *ret = TAKE_PTR(libraries); return 0; } +#endif static void test_exec_mount_apivfs(Manager *m) { +#if !HAS_FEATURE_ADDRESS_SANITIZER _cleanup_free_ char *fullpath_touch = NULL, *fullpath_test = NULL, *data = NULL; _cleanup_strv_free_ char **libraries = NULL, **libraries_test = NULL; int r; - assert_se(user_runtime_unit_dir); + ASSERT_NOT_NULL(user_runtime_unit_dir); r = find_executable("ldd", NULL); if (r < 0) { @@ -752,26 +784,27 @@ static void test_exec_mount_apivfs(Manager *m) { if (MANAGER_IS_USER(m) && !have_userns_privileges()) return (void)log_notice("Skipping %s, do not have user namespace privileges", __func__); - assert_se(find_libraries(fullpath_touch, &libraries) >= 0); - assert_se(find_libraries(fullpath_test, &libraries_test) >= 0); - assert_se(strv_extend_strv(&libraries, libraries_test, true) >= 0); + ASSERT_OK(find_libraries(fullpath_touch, &libraries)); + ASSERT_OK(find_libraries(fullpath_test, &libraries_test)); + ASSERT_OK(strv_extend_strv(&libraries, libraries_test, true)); - assert_se(strextend(&data, "[Service]\n")); - assert_se(strextend(&data, "ExecStart=", fullpath_touch, " /aaa\n")); - assert_se(strextend(&data, "ExecStart=", fullpath_test, " -f /aaa\n")); - assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_touch, "\n")); - assert_se(strextend(&data, "BindReadOnlyPaths=", fullpath_test, "\n")); + ASSERT_NOT_NULL(strextend(&data, "[Service]\n")); + ASSERT_NOT_NULL(strextend(&data, "ExecStart=", fullpath_touch, " /aaa\n")); + ASSERT_NOT_NULL(strextend(&data, "ExecStart=", fullpath_test, " -f /aaa\n")); + ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", fullpath_touch, "\n")); + ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", fullpath_test, "\n")); STRV_FOREACH(p, libraries) - assert_se(strextend(&data, "BindReadOnlyPaths=", *p, "\n")); + ASSERT_NOT_NULL(strextend(&data, "BindReadOnlyPaths=", *p, "\n")); - assert_se(write_drop_in(user_runtime_unit_dir, "exec-mount-apivfs-no.service", 10, "bind-mount", data) >= 0); + ASSERT_OK(write_drop_in(user_runtime_unit_dir, "exec-mount-apivfs-no.service", 10, "bind-mount", data)); - assert_se(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755) >= 0); + ASSERT_OK(mkdir_p("/tmp/test-exec-mount-apivfs-no/root", 0755)); 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); +#endif } static void test_exec_noexecpaths(Manager *m) { @@ -791,7 +824,7 @@ static void test_exec_temporaryfilesystem(Manager *m) { } static void test_exec_systemcallfilter(Manager *m) { -#if HAVE_SECCOMP +#if HAVE_SECCOMP && !HAS_FEATURE_ADDRESS_SANITIZER int r; if (!is_seccomp_available()) { @@ -834,7 +867,7 @@ static void test_exec_systemcallfilter(Manager *m) { } static void test_exec_systemcallerrornumber(Manager *m) { -#if HAVE_SECCOMP +#if HAVE_SECCOMP && !HAS_FEATURE_ADDRESS_SANITIZER int r; if (!is_seccomp_available()) { @@ -947,7 +980,7 @@ static char* private_directory_bad(Manager *m) { _cleanup_free_ char *p = NULL; struct stat st; - assert_se(p = path_join(m->prefix[dt], "private")); + ASSERT_NOT_NULL(p = path_join(m->prefix[dt], "private")); if (stat(p, &st) >= 0 && (st.st_mode & (S_IRWXG|S_IRWXO))) @@ -958,6 +991,11 @@ static char* private_directory_bad(Manager *m) { } static void test_exec_dynamicuser(Manager *m) { + if (MANAGER_IS_USER(m)) { + log_notice("Skipping %s for user manager", __func__); + return; + } + _cleanup_free_ char *bad = private_directory_bad(m); if (bad) { log_warning("%s: %s has bad permissions, skipping test.", __func__, bad); @@ -969,7 +1007,7 @@ static void test_exec_dynamicuser(Manager *m) { return; } - int status = can_unshare ? 0 : MANAGER_IS_SYSTEM(m) ? EXIT_NAMESPACE : EXIT_GROUP; + int status = can_unshare ? 0 : EXIT_NAMESPACE; test(m, "exec-dynamicuser-fixeduser.service", status, CLD_EXITED); if (check_user_has_group_with_same_name("adm")) @@ -1025,7 +1063,7 @@ static void test_exec_environmentfile(Manager *m) { int r; r = write_string_file("/tmp/test-exec_environmentfile.conf", e, WRITE_STRING_FILE_CREATE); - assert_se(r == 0); + ASSERT_OK(r); test(m, "exec-environmentfile.service", 0, CLD_EXITED); @@ -1044,19 +1082,19 @@ static void test_exec_passenvironment(Manager *m) { * This is still a good approximation of how a test for MANAGER_SYSTEM * would work. */ - assert_se(setenv("VAR1", "word1 word2", 1) == 0); - assert_se(setenv("VAR2", "word3", 1) == 0); - assert_se(setenv("VAR3", "$word 5 6", 1) == 0); - assert_se(setenv("VAR4", "new\nline", 1) == 0); - assert_se(setenv("VAR5", "passwordwithbackslashes", 1) == 0); + ASSERT_OK_ERRNO(setenv("VAR1", "word1 word2", 1)); + ASSERT_OK_ERRNO(setenv("VAR2", "word3", 1)); + ASSERT_OK_ERRNO(setenv("VAR3", "$word 5 6", 1)); + ASSERT_OK_ERRNO(setenv("VAR4", "new\nline", 1)); + ASSERT_OK_ERRNO(setenv("VAR5", "passwordwithbackslashes", 1)); test(m, "exec-passenvironment.service", 0, CLD_EXITED); test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED); test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED); - assert_se(unsetenv("VAR1") == 0); - assert_se(unsetenv("VAR2") == 0); - assert_se(unsetenv("VAR3") == 0); - assert_se(unsetenv("VAR4") == 0); - assert_se(unsetenv("VAR5") == 0); + ASSERT_OK_ERRNO(unsetenv("VAR1")); + ASSERT_OK_ERRNO(unsetenv("VAR2")); + ASSERT_OK_ERRNO(unsetenv("VAR3")); + ASSERT_OK_ERRNO(unsetenv("VAR4")); + ASSERT_OK_ERRNO(unsetenv("VAR5")); test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED); } @@ -1237,7 +1275,11 @@ static void test_exec_specifier(Manager *m) { static void test_exec_standardinput(Manager *m) { test(m, "exec-standardinput-data.service", 0, CLD_EXITED); test(m, "exec-standardinput-file.service", 0, CLD_EXITED); + + ExecOutput saved = m->defaults.std_output; + m->defaults.std_output = EXEC_OUTPUT_NULL; test(m, "exec-standardinput-file-cat.service", 0, CLD_EXITED); + m->defaults.std_output = saved; } static void test_exec_standardoutput(Manager *m) { @@ -1334,34 +1376,34 @@ static void run_tests(RuntimeScope scope, char **patterns) { {}, }; - assert_se(unsetenv("USER") == 0); - assert_se(unsetenv("LOGNAME") == 0); - assert_se(unsetenv("SHELL") == 0); - assert_se(unsetenv("HOME") == 0); - assert_se(unsetenv("TMPDIR") == 0); + ASSERT_OK_ERRNO(unsetenv("USER")); + ASSERT_OK_ERRNO(unsetenv("LOGNAME")); + ASSERT_OK_ERRNO(unsetenv("SHELL")); + ASSERT_OK_ERRNO(unsetenv("HOME")); + ASSERT_OK_ERRNO(unsetenv("TMPDIR")); /* Unset VARx, especially, VAR1, VAR2 and VAR3, which are used in the PassEnvironment test cases, * otherwise (and if they are present in the environment), `manager_default_environment` will copy * them into the default environment which is passed to each created job, which will make the tests * that expect those not to be present to fail. */ - assert_se(unsetenv("VAR1") == 0); - assert_se(unsetenv("VAR2") == 0); - assert_se(unsetenv("VAR3") == 0); - assert_se(unsetenv("VAR4") == 0); - assert_se(unsetenv("VAR5") == 0); + ASSERT_OK_ERRNO(unsetenv("VAR1")); + ASSERT_OK_ERRNO(unsetenv("VAR2")); + ASSERT_OK_ERRNO(unsetenv("VAR3")); + ASSERT_OK_ERRNO(unsetenv("VAR4")); + ASSERT_OK_ERRNO(unsetenv("VAR5")); - assert_se(runtime_dir = setup_fake_runtime_dir()); - assert_se(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user")); - assert_se(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir)); - assert_se(set_unit_path(unit_paths) >= 0); + ASSERT_NOT_NULL(runtime_dir = setup_fake_runtime_dir()); + ASSERT_NOT_NULL(user_runtime_unit_dir = path_join(runtime_dir, "systemd/user")); + ASSERT_NOT_NULL(unit_paths = strjoin(PRIVATE_UNIT_DIR, ":", user_runtime_unit_dir)); + ASSERT_OK(set_unit_path(unit_paths)); r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m); if (manager_errno_skip_test(r)) return (void) log_tests_skipped_errno(r, "manager_new"); - assert_se(r >= 0); + ASSERT_OK(r); - m->defaults.std_output = EXEC_OUTPUT_NULL; /* don't rely on host journald */ - assert_se(manager_startup(m, NULL, NULL, NULL) >= 0); + m->defaults.std_output = EXEC_OUTPUT_INHERIT; /* don't rely on host journald */ + ASSERT_OK(manager_startup(m, NULL, NULL, NULL)); /* Uncomment below if you want to make debugging logs stored to journal. */ //manager_override_log_target(m, LOG_TARGET_AUTO); @@ -1400,36 +1442,66 @@ static int prepare_ns(const char *process_name) { FORK_NEW_MOUNTNS | FORK_MOUNTNS_SLAVE, NULL); - assert_se(r >= 0); + ASSERT_OK(r); if (r == 0) { - _cleanup_free_ char *unit_dir = NULL; + _cleanup_free_ char *unit_dir = NULL, *build_dir = NULL, *build_dir_mount = NULL; + int ret; /* Make "/" read-only. */ - assert_se(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) >= 0); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL)); /* Creating a new user namespace in the above means all MS_SHARED mounts become MS_SLAVE. * Let's put them back to MS_SHARED here, since that's what we want as defaults. (This will * not reconnect propagation, but simply create new peer groups for all our mounts). */ - assert_se(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL) >= 0); + ASSERT_OK(mount_follow_verbose(LOG_DEBUG, NULL, "/", NULL, MS_SHARED|MS_REC, NULL)); - assert_se(mkdir_p(PRIVATE_UNIT_DIR, 0755) >= 0); - assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", PRIVATE_UNIT_DIR, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); + ASSERT_OK(mkdir_p(PRIVATE_UNIT_DIR, 0755)); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", PRIVATE_UNIT_DIR, "tmpfs", MS_NOSUID|MS_NODEV, NULL)); + /* Mark our test "playground" as MS_SLAVE, so we can MS_MOVE mounts underneath it. */ + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, NULL, PRIVATE_UNIT_DIR, NULL, MS_SLAVE, NULL)); /* Copy unit files to make them accessible even when unprivileged. */ - assert_se(get_testdata_dir("test-execute/", &unit_dir) >= 0); - assert_se(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY) >= 0); + ASSERT_OK(get_testdata_dir("test-execute/", &unit_dir)); + ASSERT_OK(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY)); /* Mount tmpfs on the following directories to make not StateDirectory= or friends disturb the host. */ + ret = get_build_exec_dir(&build_dir); + if (ret != -ENOEXEC) + ASSERT_OK(ret); + + if (build_dir) { + /* Account for a build directory being in one of the soon-to-be-tmpfs directories. If we + * overmount it with an empty tmpfs, manager_new() will pin the wrong systemd-executor binary, + * which can then lead to unexpected (and painful to debug) test fails. */ + ASSERT_OK_ERRNO(access(build_dir, F_OK)); + ASSERT_NOT_NULL(build_dir_mount = path_join(PRIVATE_UNIT_DIR, "build_dir")); + ASSERT_OK(mkdir_p(build_dir_mount, 0755)); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, build_dir, build_dir_mount, NULL, MS_BIND, NULL)); + } + FOREACH_STRING(p, "/dev/shm", "/root", "/tmp", "/var/tmp", "/var/lib") - assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL) >= 0); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, NULL)); + + if (build_dir_mount) { + ret = RET_NERRNO(access(build_dir, F_OK)); + if (ret != -ENOENT) + ASSERT_OK(ret); + + if (ret == -ENOENT) { + /* The build directory got overmounted by tmpfs, so let's use the "backup" bind mount to + * bring it back. */ + ASSERT_OK(mkdir_p(build_dir, 0755)); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, build_dir_mount, build_dir, NULL, MS_MOVE, NULL)); + } + } /* Prepare credstore like tmpfiles.d/credstore.conf for LoadCredential= tests. */ FOREACH_STRING(p, "/run/credstore", "/run/credstore.encrypted") { - assert_se(mkdir_p(p, 0) >= 0); - assert_se(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000") >= 0); + ASSERT_OK(mkdir_p(p, 0)); + ASSERT_OK(mount_nofollow_verbose(LOG_DEBUG, "tmpfs", p, "tmpfs", MS_NOSUID|MS_NODEV, "mode=0000")); } - assert_se(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE) >= 0); + ASSERT_OK(write_string_file("/run/credstore/test-execute.load-credential", "foo", WRITE_STRING_FILE_CREATE)); } return r; @@ -1442,7 +1514,7 @@ TEST(run_tests_root) { return (void) log_tests_skipped("unshare() is disabled"); /* safe_fork() clears saved_argv in the child process. Let's copy it. */ - assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1))); if (prepare_ns("(test-execute-root)") == 0) { can_unshare = true; @@ -1468,19 +1540,18 @@ TEST(run_tests_without_unshare) { return (void) log_tests_skipped("Seccomp not available, cannot run unshare() filtered tests"); /* safe_fork() clears saved_argv in the child process. Let's copy it. */ - assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1))); if (prepare_ns("(test-execute-without-unshare)") == 0) { _cleanup_hashmap_free_ Hashmap *s = NULL; r = seccomp_syscall_resolve_name("unshare"); - assert_se(r != __NR_SCMP_ERROR); - assert_se(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1)) >= 0); - assert_se(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true) >= 0); + ASSERT_NE(r, __NR_SCMP_ERROR); + ASSERT_OK(hashmap_ensure_put(&s, NULL, UINT32_TO_PTR(r + 1), INT_TO_PTR(-1))); + ASSERT_OK(seccomp_load_syscall_filter_set_raw(SCMP_ACT_ALLOW, s, SCMP_ACT_ERRNO(EOPNOTSUPP), true)); /* Check unshare() is actually filtered. */ - assert_se(unshare(CLONE_NEWNS) < 0); - assert_se(errno == EOPNOTSUPP); + ASSERT_ERROR_ERRNO(unshare(CLONE_NEWNS), EOPNOTSUPP); can_unshare = false; run_tests(RUNTIME_SCOPE_SYSTEM, filters); @@ -1498,10 +1569,10 @@ TEST(run_tests_unprivileged) { return (void) log_tests_skipped("unshare() is disabled"); /* safe_fork() clears saved_argv in the child process. Let's copy it. */ - assert_se(filters = strv_copy(strv_skip(saved_argv, 1))); + ASSERT_NOT_NULL(filters = strv_copy(strv_skip(saved_argv, 1))); if (prepare_ns("(test-execute-unprivileged)") == 0) { - assert_se(capability_bounding_set_drop(0, /* right_now = */ true) >= 0); + ASSERT_OK(capability_bounding_set_drop(0, /* right_now = */ true)); can_unshare = false; run_tests(RUNTIME_SCOPE_USER, filters); diff --git a/src/test/test-exit-status.c b/src/test/test-exit-status.c index 86d3976..aab8193 100644 --- a/src/test/test-exit-status.c +++ b/src/test/test-exit-status.c @@ -29,8 +29,8 @@ TEST(exit_status_from_string) { } TEST(exit_status_NUMA_POLICY) { - assert_se(streq(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_FULL), "NUMA_POLICY")); - assert_se(streq(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_SYSTEMD), "NUMA_POLICY")); + ASSERT_STREQ(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_FULL), "NUMA_POLICY"); + ASSERT_STREQ(exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_SYSTEMD), "NUMA_POLICY"); assert_se(!exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_BSD)); assert_se(!exit_status_to_string(EXIT_NUMA_POLICY, EXIT_STATUS_LSB)); } diff --git a/src/test/test-extract-word.c b/src/test/test-extract-word.c index 6e12fbe..1bc4088 100644 --- a/src/test/test-extract-word.c +++ b/src/test/test-extract-word.c @@ -14,12 +14,12 @@ TEST(extract_first_word) { p = original = "foobar waldo"; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(p == original + 7); assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(isempty(p)); @@ -29,12 +29,12 @@ TEST(extract_first_word) { p = original = "\"foobar\" \'waldo\'"; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "\"foobar\"")); + ASSERT_STREQ(t, "\"foobar\""); free(t); assert_se(p == original + 9); assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "\'waldo\'")); + ASSERT_STREQ(t, "\'waldo\'"); free(t); assert_se(isempty(p)); @@ -44,12 +44,12 @@ TEST(extract_first_word) { p = original = "\"foobar\" \'waldo\'"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(p == original + 9); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(isempty(p)); @@ -59,7 +59,7 @@ TEST(extract_first_word) { p = original = "\""; assert_se(extract_first_word(&p, &t, NULL, 0) == 1); - assert_se(streq(t, "\"")); + ASSERT_STREQ(t, "\""); free(t); assert_se(isempty(p)); @@ -69,7 +69,7 @@ TEST(extract_first_word) { p = original = "\'"; assert_se(extract_first_word(&p, &t, NULL, 0) == 1); - assert_se(streq(t, "\'")); + ASSERT_STREQ(t, "\'"); free(t); assert_se(isempty(p)); @@ -79,31 +79,31 @@ TEST(extract_first_word) { p = original = "\'fooo"; assert_se(extract_first_word(&p, &t, NULL, 0) == 1); - assert_se(streq(t, "\'fooo")); + ASSERT_STREQ(t, "\'fooo"); free(t); assert_se(isempty(p)); p = original = "KEY=val \"KEY2=val with space\" \"KEY3=val with \\\"quotation\\\"\""; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); - assert_se(streq(t, "KEY=val")); + ASSERT_STREQ(t, "KEY=val"); free(t); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); - assert_se(streq(t, "KEY2=val with space")); + ASSERT_STREQ(t, "KEY2=val with space"); free(t); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) == 1); - assert_se(streq(t, "KEY3=val with \"quotation\"")); + ASSERT_STREQ(t, "KEY3=val with \"quotation\""); free(t); assert_se(isempty(p)); p = original = "KEY=val \"KEY2=val space\" \"KEY3=val with \\\"quotation\\\"\""; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); - assert_se(streq(t, "KEY=val")); + ASSERT_STREQ(t, "KEY=val"); free(t); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); - assert_se(streq(t, "\"KEY2=val")); + ASSERT_STREQ(t, "\"KEY2=val"); free(t); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) == 1); - assert_se(streq(t, "space\"")); + ASSERT_STREQ(t, "space\""); free(t); assert_se(startswith(p, "\"KEY3=")); @@ -113,78 +113,78 @@ TEST(extract_first_word) { p = original = "\'fooo"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo")); + ASSERT_STREQ(t, "fooo"); free(t); assert_se(isempty(p)); p = original = "\"fooo"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo")); + ASSERT_STREQ(t, "fooo"); free(t); assert_se(isempty(p)); p = original = "yay\'foo\'bar"; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "yay\'foo\'bar")); + ASSERT_STREQ(t, "yay\'foo\'bar"); free(t); assert_se(isempty(p)); p = original = "yay\'foo\'bar"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "yayfoobar")); + ASSERT_STREQ(t, "yayfoobar"); free(t); assert_se(isempty(p)); p = original = " foobar "; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(isempty(p)); p = original = " foo\\ba\\x6ar "; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0); - assert_se(streq(t, "foo\ba\x6ar")); + ASSERT_STREQ(t, "foo\ba\x6ar"); free(t); assert_se(isempty(p)); p = original = " foo\\ba\\x6ar "; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "foobax6ar")); + ASSERT_STREQ(t, "foobax6ar"); free(t); assert_se(isempty(p)); p = original = " f\\u00f6o \"pi\\U0001F4A9le\" "; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE) > 0); - assert_se(streq(t, "föo")); + ASSERT_STREQ(t, "föo"); free(t); assert_se(p == original + 13); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE) > 0); - assert_se(streq(t, "pi\360\237\222\251le")); + ASSERT_STREQ(t, "pi\360\237\222\251le"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo")); + ASSERT_STREQ(t, "fooo"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "fooo\\")); + ASSERT_STREQ(t, "fooo\\"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo\\")); + ASSERT_STREQ(t, "fooo\\"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "fooo\\")); + ASSERT_STREQ(t, "fooo\\"); free(t); assert_se(isempty(p)); @@ -194,18 +194,18 @@ TEST(extract_first_word) { p = original = "\"foo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX) > 0); - assert_se(streq(t, "foo")); + ASSERT_STREQ(t, "foo"); free(t); assert_se(isempty(p)); p = original = "foo::bar"; assert_se(extract_first_word(&p, &t, ":", 0) == 1); - assert_se(streq(t, "foo")); + ASSERT_STREQ(t, "foo"); free(t); assert_se(p == original + 5); assert_se(extract_first_word(&p, &t, ":", 0) == 1); - assert_se(streq(t, "bar")); + ASSERT_STREQ(t, "bar"); free(t); assert_se(isempty(p)); @@ -215,12 +215,12 @@ TEST(extract_first_word) { p = original = "foo\\:bar::waldo"; assert_se(extract_first_word(&p, &t, ":", 0) == 1); - assert_se(streq(t, "foo:bar")); + ASSERT_STREQ(t, "foo:bar"); free(t); assert_se(p == original + 10); assert_se(extract_first_word(&p, &t, ":", 0) == 1); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(isempty(p)); @@ -234,31 +234,31 @@ TEST(extract_first_word) { p = original = "\"foo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); - assert_se(streq(t, "foo\\")); + ASSERT_STREQ(t, "foo\\"); free(t); assert_se(isempty(p)); p = original = "\"foo\\"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); - assert_se(streq(t, "foo\\")); + ASSERT_STREQ(t, "foo\\"); free(t); assert_se(isempty(p)); p = original = "fooo\\ bar quux"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo bar")); + ASSERT_STREQ(t, "fooo bar"); free(t); assert_se(p == original + 10); p = original = "fooo\\ bar quux"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "fooo bar")); + ASSERT_STREQ(t, "fooo bar"); free(t); assert_se(p == original + 10); p = original = "fooo\\ bar quux"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNESCAPE_RELAX|EXTRACT_RELAX) > 0); - assert_se(streq(t, "fooo bar")); + ASSERT_STREQ(t, "fooo bar"); free(t); assert_se(p == original + 10); @@ -268,7 +268,7 @@ TEST(extract_first_word) { p = original = "fooo\\ bar quux"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "fooo\\ bar")); + ASSERT_STREQ(t, "fooo\\ bar"); free(t); assert_se(p == original + 10); @@ -278,54 +278,54 @@ TEST(extract_first_word) { p = original = "\\w+@\\K[\\d.]+"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "\\w+@\\K[\\d.]+")); + ASSERT_STREQ(t, "\\w+@\\K[\\d.]+"); free(t); assert_se(isempty(p)); p = original = "\\w+\\b"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_RELAX) > 0); - assert_se(streq(t, "\\w+\b")); + ASSERT_STREQ(t, "\\w+\b"); free(t); assert_se(isempty(p)); p = original = "-N ''"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "-N")); + ASSERT_STREQ(t, "-N"); free(t); assert_se(p == original + 3); assert_se(extract_first_word(&p, &t, NULL, EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "")); + ASSERT_STREQ(t, ""); free(t); assert_se(isempty(p)); p = original = ":foo\\:bar::waldo:"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); assert_se(t); - assert_se(streq(t, "")); + ASSERT_STREQ(t, ""); free(t); assert_se(p == original + 1); assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); - assert_se(streq(t, "foo:bar")); + ASSERT_STREQ(t, "foo:bar"); free(t); assert_se(p == original + 10); assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); assert_se(t); - assert_se(streq(t, "")); + ASSERT_STREQ(t, ""); free(t); assert_se(p == original + 11); assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(p == original + 17); assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 1); - assert_se(streq(t, "")); + ASSERT_STREQ(t, ""); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); assert_se(extract_first_word(&p, &t, ":", EXTRACT_DONT_COALESCE_SEPARATORS) == 0); assert_se(!t); @@ -333,81 +333,81 @@ TEST(extract_first_word) { p = "foo\\xbar"; assert_se(extract_first_word(&p, &t, NULL, 0) > 0); - assert_se(streq(t, "fooxbar")); + ASSERT_STREQ(t, "fooxbar"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "foo\\xbar"; assert_se(extract_first_word(&p, &t, NULL, EXTRACT_RETAIN_ESCAPE) > 0); - assert_se(streq(t, "foo\\xbar")); + ASSERT_STREQ(t, "foo\\xbar"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "\\:"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, ":")); + ASSERT_STREQ(t, ":"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a:b")); + ASSERT_STREQ(t, "a:b"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\ b:c"; assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a b")); + ASSERT_STREQ(t, "a b"); free(t); assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "c")); + ASSERT_STREQ(t, "c"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\ b:c\\x"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL); p = "a\\\\ b:c\\\\x"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a\\ b")); + ASSERT_STREQ(t, "a\\ b"); free(t); assert_se(extract_first_word(&p, &t, ":", EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "c\\x")); + ASSERT_STREQ(t, "c\\x"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "\\:"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, ":")); + ASSERT_STREQ(t, ":"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a:b")); + ASSERT_STREQ(t, "a:b"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\ b:c"; assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a b")); + ASSERT_STREQ(t, "a b"); free(t); assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "c")); + ASSERT_STREQ(t, "c"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "a\\ b:c\\x"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == -EINVAL); p = "a\\\\ b:c\\\\x"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a\\ b")); + ASSERT_STREQ(t, "a\\ b"); free(t); assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "c\\x")); + ASSERT_STREQ(t, "c\\x"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = "\\:"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == -EINVAL); @@ -415,124 +415,138 @@ TEST(extract_first_word) { p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == -EINVAL); assert_se(extract_first_word(&p, &t, ":", EXTRACT_CUNESCAPE) == 1); - assert_se(streq(t, "b")); + ASSERT_STREQ(t, "b"); free(t); p = "a\\ b:c"; assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == -EINVAL); assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == 1); - assert_se(streq(t, "b")); + ASSERT_STREQ(t, "b"); free(t); assert_se(extract_first_word(&p, &t, WHITESPACE ":", EXTRACT_CUNESCAPE) == 1); - assert_se(streq(t, "c")); + ASSERT_STREQ(t, "c"); free(t); - assert_se(p == NULL); + ASSERT_NULL(p); p = original = "foobar=\"waldo\"maldo, baldo"; assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); - assert_se(streq(t, "maldo")); + ASSERT_STREQ(t, "maldo"); free(t); assert_se(extract_first_word(&p, &t, "=\", ", 0) > 0); - assert_se(streq(t, "baldo")); + ASSERT_STREQ(t, "baldo"); free(t); p = original = "mode=\"1777\",size=\"10%\",nr_inodes=\"400\"k,uid=\"496,,107\"520,gi\"\"'d=49610,'\"\"7520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""; assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "mode=\"1777\"")); + ASSERT_STREQ(t, "mode=\"1777\""); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "size=\"10%\"")); + ASSERT_STREQ(t, "size=\"10%\""); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "nr_inodes=\"400\"k")); + ASSERT_STREQ(t, "nr_inodes=\"400\"k"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "uid=\"496,,107\"520")); + ASSERT_STREQ(t, "uid=\"496,,107\"520"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "gi\"\"'d=49610,'\"\"7520")); + ASSERT_STREQ(t, "gi\"\"'d=49610,'\"\"7520"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_KEEP_QUOTE) > 0); - assert_se(streq(t, "context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"")); + ASSERT_STREQ(t, "context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""); free(t); p = original = "mode=\"1777\",size=\"10%\",nr_inodes=\"400\"k,uid=\"496,,107\"520,gi\"\"'d=49610,'\"\"7520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""; assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "mode=1777")); + ASSERT_STREQ(t, "mode=1777"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "size=10%")); + ASSERT_STREQ(t, "size=10%"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "nr_inodes=400k")); + ASSERT_STREQ(t, "nr_inodes=400k"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "uid=496,,107520")); + ASSERT_STREQ(t, "uid=496,,107520"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "gid=49610,7520")); + ASSERT_STREQ(t, "gid=49610,7520"); free(t); assert_se(extract_first_word(&p, &t, ",", EXTRACT_UNQUOTE) > 0); - assert_se(streq(t, "context=system_u:object_r:svirt_sandbox_file_t:s0:c0,c1")); + ASSERT_STREQ(t, "context=system_u:object_r:svirt_sandbox_file_t:s0:c0,c1"); free(t); p = "a:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); - assert_se(streq(t, "a")); - assert_se(streq(p, ":b")); + ASSERT_STREQ(t, "a"); + ASSERT_STREQ(p, ":b"); free(t); assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); - assert_se(streq(t, "b")); + ASSERT_STREQ(t, "b"); free(t); p = "a>:b"; assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1); - assert_se(streq(t, "a")); - assert_se(streq(p, ">:b")); + ASSERT_STREQ(t, "a"); + ASSERT_STREQ(p, ">:b"); free(t); assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS) == 1); - assert_se(streq(t, "b")); + ASSERT_STREQ(t, "b"); free(t); p = "a>:b"; assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1); - assert_se(streq(t, "a")); - assert_se(streq(p, ">:b")); + ASSERT_STREQ(t, "a"); + ASSERT_STREQ(p, ">:b"); free(t); assert_se(extract_first_word(&p, &t, ">:", EXTRACT_RETAIN_SEPARATORS|EXTRACT_DONT_COALESCE_SEPARATORS) == 1); - assert_se(streq(t, "")); - assert_se(streq(p, ">:b")); + ASSERT_STREQ(t, ""); + ASSERT_STREQ(p, ">:b"); free(t); p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_RETAIN_ESCAPE) == 1); - assert_se(streq(t, "a\\")); - assert_se(streq(p, ":b")); + ASSERT_STREQ(t, "a\\"); + ASSERT_STREQ(p, ":b"); free(t); p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS) == 1); - assert_se(streq(t, "a:b")); + ASSERT_STREQ(t, "a:b"); assert_se(!p); free(t); p = "a\\:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a:b")); + ASSERT_STREQ(t, "a:b"); assert_se(!p); free(t); p = "a\\:a:b"; assert_se(extract_first_word(&p, &t, ":", EXTRACT_RETAIN_SEPARATORS|EXTRACT_UNESCAPE_SEPARATORS) == 1); - assert_se(streq(t, "a:a")); - assert_se(streq(p, ":b")); + ASSERT_STREQ(t, "a:a"); + ASSERT_STREQ(p, ":b"); free(t); + + p = original = "zażółcić 👊🔪💐 가너도루"; + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + ASSERT_STREQ(t, "zażółcić"); + free(t); + assert_se(p == original + 13); + + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + ASSERT_STREQ(t, "👊🔪💐"); + free(t); + assert_se(extract_first_word(&p, &t, NULL, 0) > 0); + ASSERT_STREQ(t, "가너도루"); + free(t); + assert_se(isempty(p)); } TEST(extract_first_word_and_warn) { @@ -541,12 +555,12 @@ TEST(extract_first_word_and_warn) { p = original = "foobar waldo"; assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(p == original + 7); assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(isempty(p)); @@ -556,12 +570,12 @@ TEST(extract_first_word_and_warn) { p = original = "\"foobar\" \'waldo\'"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foobar")); + ASSERT_STREQ(t, "foobar"); free(t); assert_se(p == original + 9); assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "waldo")); + ASSERT_STREQ(t, "waldo"); free(t); assert_se(isempty(p)); @@ -583,48 +597,48 @@ TEST(extract_first_word_and_warn) { p = original = "\'fooo"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo")); + ASSERT_STREQ(t, "fooo"); free(t); assert_se(isempty(p)); p = original = " foo\\ba\\x6ar "; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foo\ba\x6ar")); + ASSERT_STREQ(t, "foo\ba\x6ar"); free(t); assert_se(isempty(p)); p = original = " foo\\ba\\x6ar "; assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foobax6ar")); + ASSERT_STREQ(t, "foobax6ar"); free(t); assert_se(isempty(p)); p = original = " f\\u00f6o \"pi\\U0001F4A9le\" "; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "föo")); + ASSERT_STREQ(t, "föo"); free(t); assert_se(p == original + 13); assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "pi\360\237\222\251le")); + ASSERT_STREQ(t, "pi\360\237\222\251le"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo")); + ASSERT_STREQ(t, "fooo"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo\\")); + ASSERT_STREQ(t, "fooo\\"); free(t); assert_se(isempty(p)); p = original = "fooo\\"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo\\")); + ASSERT_STREQ(t, "fooo\\"); free(t); assert_se(isempty(p)); @@ -634,7 +648,7 @@ TEST(extract_first_word_and_warn) { p = original = "\"foo\\"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foo")); + ASSERT_STREQ(t, "foo"); free(t); assert_se(isempty(p)); @@ -644,37 +658,37 @@ TEST(extract_first_word_and_warn) { p = original = "\"foo\\"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE|EXTRACT_RELAX, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "foo")); + ASSERT_STREQ(t, "foo"); free(t); assert_se(isempty(p)); p = original = "fooo\\ bar quux"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_RELAX, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo bar")); + ASSERT_STREQ(t, "fooo bar"); free(t); assert_se(p == original + 10); p = original = "fooo\\ bar quux"; assert_se(extract_first_word_and_warn(&p, &t, NULL, 0, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo bar")); + ASSERT_STREQ(t, "fooo bar"); free(t); assert_se(p == original + 10); p = original = "fooo\\ bar quux"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "fooo\\ bar")); + ASSERT_STREQ(t, "fooo\\ bar"); free(t); assert_se(p == original + 10); p = original = "\\w+@\\K[\\d.]+"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "\\w+@\\K[\\d.]+")); + ASSERT_STREQ(t, "\\w+@\\K[\\d.]+"); free(t); assert_se(isempty(p)); p = original = "\\w+\\b"; assert_se(extract_first_word_and_warn(&p, &t, NULL, EXTRACT_CUNESCAPE, NULL, "fake", 1, original) > 0); - assert_se(streq(t, "\\w+\b")); + ASSERT_STREQ(t, "\\w+\b"); free(t); assert_se(isempty(p)); } @@ -684,25 +698,25 @@ TEST(extract_many_words) { char *a, *b, *c, *d, *e, *f; p = original = "foobar waldi piep"; - assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 3); + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c) == 3); assert_se(isempty(p)); - assert_se(streq_ptr(a, "foobar")); - assert_se(streq_ptr(b, "waldi")); - assert_se(streq_ptr(c, "piep")); + ASSERT_STREQ(a, "foobar"); + ASSERT_STREQ(b, "waldi"); + ASSERT_STREQ(c, "piep"); free(a); free(b); free(c); p = original = "foobar:waldi:piep ba1:ba2"; - assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &a, &b, &c, NULL) == 3); + assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &a, &b, &c) == 3); assert_se(!isempty(p)); - assert_se(streq_ptr(a, "foobar")); - assert_se(streq_ptr(b, "waldi")); - assert_se(streq_ptr(c, "piep")); - assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &d, &e, &f, NULL) == 2); + ASSERT_STREQ(a, "foobar"); + ASSERT_STREQ(b, "waldi"); + ASSERT_STREQ(c, "piep"); + assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &d, &e, &f) == 2); assert_se(isempty(p)); - assert_se(streq_ptr(d, "ba1")); - assert_se(streq_ptr(e, "ba2")); + ASSERT_STREQ(d, "ba1"); + ASSERT_STREQ(e, "ba2"); assert_se(isempty(f)); free(a); free(b); @@ -712,52 +726,62 @@ TEST(extract_many_words) { free(f); p = original = "'foobar' wa\"ld\"i "; - assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 2); + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c) == 2); assert_se(isempty(p)); - assert_se(streq_ptr(a, "'foobar'")); - assert_se(streq_ptr(b, "wa\"ld\"i")); - assert_se(streq_ptr(c, NULL)); + ASSERT_STREQ(a, "'foobar'"); + ASSERT_STREQ(b, "wa\"ld\"i"); + ASSERT_STREQ(c, NULL); free(a); free(b); p = original = "'foobar' wa\"ld\"i "; - assert_se(extract_many_words(&p, NULL, EXTRACT_UNQUOTE, &a, &b, &c, NULL) == 2); + assert_se(extract_many_words(&p, NULL, EXTRACT_UNQUOTE, &a, &b, &c) == 2); assert_se(isempty(p)); - assert_se(streq_ptr(a, "foobar")); - assert_se(streq_ptr(b, "waldi")); - assert_se(streq_ptr(c, NULL)); + ASSERT_STREQ(a, "foobar"); + ASSERT_STREQ(b, "waldi"); + ASSERT_STREQ(c, NULL); free(a); free(b); p = original = ""; - assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0); + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c) == 0); assert_se(isempty(p)); - assert_se(streq_ptr(a, NULL)); - assert_se(streq_ptr(b, NULL)); - assert_se(streq_ptr(c, NULL)); + ASSERT_STREQ(a, NULL); + ASSERT_STREQ(b, NULL); + ASSERT_STREQ(c, NULL); p = original = " "; - assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c, NULL) == 0); + assert_se(extract_many_words(&p, NULL, 0, &a, &b, &c) == 0); assert_se(isempty(p)); - assert_se(streq_ptr(a, NULL)); - assert_se(streq_ptr(b, NULL)); - assert_se(streq_ptr(c, NULL)); + ASSERT_STREQ(a, NULL); + ASSERT_STREQ(b, NULL); + ASSERT_STREQ(c, NULL); p = original = "foobar"; - assert_se(extract_many_words(&p, NULL, 0, NULL) == 0); + assert_se(extract_many_words(&p, NULL, 0) == 0); assert_se(p == original); p = original = "foobar waldi"; - assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1); + assert_se(extract_many_words(&p, NULL, 0, &a) == 1); assert_se(p == original+7); - assert_se(streq_ptr(a, "foobar")); + ASSERT_STREQ(a, "foobar"); free(a); p = original = " foobar "; - assert_se(extract_many_words(&p, NULL, 0, &a, NULL) == 1); + assert_se(extract_many_words(&p, NULL, 0, &a) == 1); assert_se(isempty(p)); - assert_se(streq_ptr(a, "foobar")); + ASSERT_STREQ(a, "foobar"); free(a); + + p = original = "gęślą:👊🔪💐 가너도루"; + assert_se(extract_many_words(&p, ":" WHITESPACE, 0, &a, &b, &c) == 3); + assert_se(isempty(p)); + ASSERT_STREQ(a, "gęślą"); + ASSERT_STREQ(b, "👊🔪💐"); + ASSERT_STREQ(c, "가너도루"); + free(a); + free(b); + free(c); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-fd-util.c b/src/test/test-fd-util.c index 021d4b4..f2b65d4 100644 --- a/src/test/test-fd-util.c +++ b/src/test/test-fd-util.c @@ -138,6 +138,7 @@ TEST(rearrange_stdio) { if (r == 0) { _cleanup_free_ char *path = NULL; + int pipe_read_fd, pair[2]; char buffer[10]; /* Child */ @@ -145,6 +146,10 @@ TEST(rearrange_stdio) { safe_close(STDERR_FILENO); /* Let's close an fd < 2, to make it more interesting */ assert_se(rearrange_stdio(-EBADF, -EBADF, -EBADF) >= 0); + /* Reconfigure logging after rearranging stdout/stderr, so we still log to somewhere if the + * following tests fail, making it slightly less annoying to debug */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_open(); assert_se(fd_get_path(STDIN_FILENO, &path) >= 0); assert_se(path_equal(path, "/dev/null")); @@ -162,21 +167,20 @@ TEST(rearrange_stdio) { safe_close(STDOUT_FILENO); safe_close(STDERR_FILENO); - { - int pair[2]; - assert_se(pipe(pair) >= 0); - assert_se(pair[0] == 0); - assert_se(pair[1] == 1); - assert_se(fd_move_above_stdio(0) == 3); - } + assert_se(pipe(pair) >= 0); + assert_se(pair[0] == 0); + assert_se(pair[1] == 1); + pipe_read_fd = fd_move_above_stdio(0); + assert_se(pipe_read_fd >= 3); + assert_se(open("/dev/full", O_WRONLY|O_CLOEXEC) == 0); - assert_se(acquire_data_fd("foobar", 6, 0) == 2); + assert_se(acquire_data_fd("foobar") == 2); assert_se(rearrange_stdio(2, 0, 1) >= 0); assert_se(write(1, "x", 1) < 0 && errno == ENOSPC); assert_se(write(2, "z", 1) == 1); - assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(read(pipe_read_fd, buffer, sizeof(buffer)) == 1); assert_se(buffer[0] == 'z'); assert_se(read(0, buffer, sizeof(buffer)) == 6); assert_se(memcmp(buffer, "foobar", 6) == 0); @@ -184,7 +188,7 @@ TEST(rearrange_stdio) { assert_se(rearrange_stdio(-EBADF, 1, 2) >= 0); assert_se(write(1, "a", 1) < 0 && errno == ENOSPC); assert_se(write(2, "y", 1) == 1); - assert_se(read(3, buffer, sizeof(buffer)) == 1); + assert_se(read(pipe_read_fd, buffer, sizeof(buffer)) == 1); assert_se(buffer[0] == 'y'); assert_se(fd_get_path(0, &path) >= 0); @@ -393,11 +397,11 @@ TEST(close_all_fds) { } TEST(format_proc_fd_path) { - assert_se(streq_ptr(FORMAT_PROC_FD_PATH(0), "/proc/self/fd/0")); - assert_se(streq_ptr(FORMAT_PROC_FD_PATH(1), "/proc/self/fd/1")); - assert_se(streq_ptr(FORMAT_PROC_FD_PATH(2), "/proc/self/fd/2")); - assert_se(streq_ptr(FORMAT_PROC_FD_PATH(3), "/proc/self/fd/3")); - assert_se(streq_ptr(FORMAT_PROC_FD_PATH(2147483647), "/proc/self/fd/2147483647")); + ASSERT_STREQ(FORMAT_PROC_FD_PATH(0), "/proc/self/fd/0"); + ASSERT_STREQ(FORMAT_PROC_FD_PATH(1), "/proc/self/fd/1"); + ASSERT_STREQ(FORMAT_PROC_FD_PATH(2), "/proc/self/fd/2"); + ASSERT_STREQ(FORMAT_PROC_FD_PATH(3), "/proc/self/fd/3"); + ASSERT_STREQ(FORMAT_PROC_FD_PATH(2147483647), "/proc/self/fd/2147483647"); } TEST(fd_reopen) { @@ -409,7 +413,7 @@ TEST(fd_reopen) { fd1 = open("/proc", O_DIRECTORY|O_PATH|O_CLOEXEC); assert_se(fd1 >= 0); - assert_se(fstat(fd1, &st1) >= 0); + ASSERT_OK_ERRNO(fstat(fd1, &st1)); assert_se(S_ISDIR(st1.st_mode)); fl = fcntl(fd1, F_GETFL); @@ -424,7 +428,7 @@ TEST(fd_reopen) { 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_OK_ERRNO(fstat(fd2, &st2)); assert_se(S_ISDIR(st2.st_mode)); assert_se(stat_inode_same(&st1, &st2)); @@ -438,7 +442,7 @@ TEST(fd_reopen) { fd1 = fd_reopen(fd2, O_DIRECTORY|O_PATH|O_CLOEXEC); /* reacquire the O_PATH */ assert_se(fd1 >= 0); - assert_se(fstat(fd1, &st1) >= 0); + ASSERT_OK_ERRNO(fstat(fd1, &st1)); assert_se(S_ISDIR(st1.st_mode)); assert_se(stat_inode_same(&st1, &st2)); @@ -453,7 +457,7 @@ TEST(fd_reopen) { fd1 = open("/proc/version", O_PATH|O_CLOEXEC); assert_se(fd1 >= 0); - assert_se(fstat(fd1, &st1) >= 0); + ASSERT_OK_ERRNO(fstat(fd1, &st1)); assert_se(S_ISREG(st1.st_mode)); fl = fcntl(fd1, F_GETFL); @@ -465,7 +469,7 @@ TEST(fd_reopen) { fd2 = fd_reopen(fd1, O_RDONLY|O_CLOEXEC); /* drop the O_PATH */ assert_se(fd2 >= 0); - assert_se(fstat(fd2, &st2) >= 0); + ASSERT_OK_ERRNO(fstat(fd2, &st2)); assert_se(S_ISREG(st2.st_mode)); assert_se(stat_inode_same(&st1, &st2)); @@ -480,7 +484,7 @@ TEST(fd_reopen) { fd1 = fd_reopen(fd2, O_PATH|O_CLOEXEC); /* reacquire the O_PATH */ assert_se(fd1 >= 0); - assert_se(fstat(fd1, &st1) >= 0); + ASSERT_OK_ERRNO(fstat(fd1, &st1)); assert_se(S_ISREG(st1.st_mode)); assert_se(stat_inode_same(&st1, &st2)); @@ -497,12 +501,12 @@ TEST(fd_reopen) { /* 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_OK_ERRNO(fstat(fd1, &st1)); 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_OK_ERRNO(fstat(fd2, &st2)); assert_se(S_ISLNK(st2.st_mode)); assert_se(stat_inode_same(&st1, &st2)); fd2 = safe_close(fd2); @@ -646,6 +650,24 @@ TEST(dir_fd_is_root) { assert_se(dir_fd_is_root_or_cwd(fd) == 0); } +TEST(fds_are_same_mount) { + _cleanup_close_ int fd1 = -EBADF, fd2 = -EBADF, fd3 = -EBADF, fd4 = -EBADF; + + fd1 = open("/sys", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd2 = open("/proc", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd3 = open("/proc", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + fd4 = open("/", O_CLOEXEC|O_PATH|O_DIRECTORY|O_NOFOLLOW); + + if (fd1 < 0 || fd2 < 0 || fd3 < 0 || fd4 < 0) + return (void) log_tests_skipped_errno(errno, "Failed to open /sys or /proc or /"); + + if (fds_are_same_mount(fd1, fd4) > 0 && fds_are_same_mount(fd2, fd4) > 0) + return (void) log_tests_skipped("Cannot test fds_are_same_mount() as /sys and /proc are not mounted"); + + assert_se(fds_are_same_mount(fd1, fd2) == 0); + assert_se(fds_are_same_mount(fd2, fd3) > 0); +} + TEST(fd_get_path) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; @@ -654,7 +676,7 @@ TEST(fd_get_path) { tfd = mkdtemp_open(NULL, O_PATH, &t); assert_se(tfd >= 0); assert_se(fd_get_path(tfd, &p) >= 0); - assert_se(streq(p, t)); + ASSERT_STREQ(p, t); p = mfree(p); @@ -662,7 +684,7 @@ TEST(fd_get_path) { assert_se(chdir(t) >= 0); assert_se(fd_get_path(AT_FDCWD, &p) >= 0); - assert_se(streq(p, t)); + ASSERT_STREQ(p, t); p = mfree(p); @@ -675,7 +697,7 @@ TEST(fd_get_path) { 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)); + ASSERT_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -683,7 +705,7 @@ TEST(fd_get_path) { 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)); + ASSERT_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -692,7 +714,7 @@ TEST(fd_get_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)); + ASSERT_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -701,7 +723,7 @@ TEST(fd_get_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)); + ASSERT_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -710,7 +732,7 @@ TEST(fd_get_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)); + ASSERT_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -719,7 +741,7 @@ TEST(fd_get_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)); + ASSERT_STREQ(p, q); p = mfree(p); q = mfree(q); @@ -730,7 +752,7 @@ TEST(fd_get_path) { 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_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -739,7 +761,7 @@ TEST(fd_get_path) { 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_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -748,7 +770,7 @@ TEST(fd_get_path) { 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_STREQ(p, q); p = mfree(p); fd = safe_close(fd); @@ -757,7 +779,7 @@ TEST(fd_get_path) { 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_STREQ(p, q); assert_se(chdir(saved_cwd) >= 0); } diff --git a/src/test/test-fdset.c b/src/test/test-fdset.c index 8f00e59..cfbd8e2 100644 --- a/src/test/test-fdset.c +++ b/src/test/test-fdset.c @@ -116,9 +116,18 @@ TEST(fdset_close_others) { copyfd = fdset_put_dup(fdset, fd); assert_se(copyfd >= 0); + /* fdset_close_others() will close any logging file descriptors as well, so close them beforehand + * and reopen them again afterwards. */ + log_close(); assert_se(fdset_close_others(fdset) >= 0); + flags = fcntl(fd, F_GETFD); assert_se(flags < 0); + + /* Open log again after checking that fd is invalid, since reopening the log might make fd a valid + * file descriptor again. */ + (void) log_open(); + flags = fcntl(copyfd, F_GETFD); assert_se(flags >= 0); } diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index ad98a92..474eaca 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -71,27 +71,27 @@ TEST(parse_env_file) { STRV_FOREACH(i, a) log_info("Got: <%s>", *i); - assert_se(streq_ptr(a[0], "one=BAR")); - assert_se(streq_ptr(a[1], "two=bar")); - assert_se(streq_ptr(a[2], "three=333\nxxxx")); - assert_se(streq_ptr(a[3], "four=44\\\"44")); - assert_se(streq_ptr(a[4], "five=55\"55FIVEcinco")); - assert_se(streq_ptr(a[5], "six=seis sechs sis")); - assert_se(streq_ptr(a[6], "seven=sevenval#nocomment")); - assert_se(streq_ptr(a[7], "eight=eightval #nocomment")); - assert_se(streq_ptr(a[8], "export nine=nineval")); - assert_se(streq_ptr(a[9], "ten=")); - assert_se(streq_ptr(a[10], "eleven=value")); - assert_se(streq_ptr(a[11], "twelve=\\value")); - assert_se(streq_ptr(a[12], "thirteen=\\value")); - assert_se(a[13] == NULL); + ASSERT_STREQ(a[0], "one=BAR"); + ASSERT_STREQ(a[1], "two=bar"); + ASSERT_STREQ(a[2], "three=333\nxxxx"); + ASSERT_STREQ(a[3], "four=44\\\"44"); + ASSERT_STREQ(a[4], "five=55\"55FIVEcinco"); + ASSERT_STREQ(a[5], "six=seis sechs sis"); + ASSERT_STREQ(a[6], "seven=sevenval#nocomment"); + ASSERT_STREQ(a[7], "eight=eightval #nocomment"); + ASSERT_STREQ(a[8], "export nine=nineval"); + ASSERT_STREQ(a[9], "ten="); + ASSERT_STREQ(a[10], "eleven=value"); + ASSERT_STREQ(a[11], "twelve=\\value"); + ASSERT_STREQ(a[12], "thirteen=\\value"); + ASSERT_NULL(a[13]); strv_env_clean(a); k = 0; STRV_FOREACH(i, b) { log_info("Got2: <%s>", *i); - assert_se(streq(*i, a[k++])); + ASSERT_STREQ(*i, a[k++]); } r = parse_env_file( @@ -125,19 +125,19 @@ TEST(parse_env_file) { log_info("twelve=[%s]", strna(twelve)); log_info("thirteen=[%s]", strna(thirteen)); - assert_se(streq(one, "BAR")); - assert_se(streq(two, "bar")); - assert_se(streq(three, "333\nxxxx")); - assert_se(streq(four, "44\\\"44")); - assert_se(streq(five, "55\"55FIVEcinco")); - assert_se(streq(six, "seis sechs sis")); - assert_se(streq(seven, "sevenval#nocomment")); - assert_se(streq(eight, "eightval #nocomment")); - assert_se(streq(nine, "nineval")); - assert_se(ten == NULL); - assert_se(streq(eleven, "value")); - assert_se(streq(twelve, "\\value")); - assert_se(streq(thirteen, "\\value")); + ASSERT_STREQ(one, "BAR"); + ASSERT_STREQ(two, "bar"); + ASSERT_STREQ(three, "333\nxxxx"); + ASSERT_STREQ(four, "44\\\"44"); + ASSERT_STREQ(five, "55\"55FIVEcinco"); + ASSERT_STREQ(six, "seis sechs sis"); + ASSERT_STREQ(seven, "sevenval#nocomment"); + ASSERT_STREQ(eight, "eightval #nocomment"); + ASSERT_STREQ(nine, "nineval"); + ASSERT_NULL(ten); + ASSERT_STREQ(eleven, "value"); + ASSERT_STREQ(twelve, "\\value"); + ASSERT_STREQ(thirteen, "\\value"); { /* prepare a temporary file to write the environment to */ @@ -161,7 +161,7 @@ static void test_one_shell_var(const char *file, const char *variable, const cha assert_se(f = popen(cmd, "re")); assert_se(read_full_stream(f, &from_shell, &sz) >= 0); assert_se(sz == strlen(value)); - assert_se(streq(from_shell, value)); + ASSERT_STREQ(from_shell, value); } TEST(parse_multiline_env_file) { @@ -198,10 +198,10 @@ TEST(parse_multiline_env_file) { STRV_FOREACH(i, a) log_info("Got: <%s>", *i); - assert_se(streq_ptr(a[0], "one=BAR VAR\tGAR")); - assert_se(streq_ptr(a[1], "two=bar var\tgar")); - assert_se(streq_ptr(a[2], "tri=bar var \tgar ")); - assert_se(a[3] == NULL); + ASSERT_STREQ(a[0], "one=BAR VAR\tGAR"); + ASSERT_STREQ(a[1], "two=bar var\tgar"); + ASSERT_STREQ(a[2], "tri=bar var \tgar "); + ASSERT_NULL(a[3]); { _cleanup_close_ int fd = mkostemp_safe(p); @@ -246,17 +246,17 @@ TEST(merge_env_file) { STRV_FOREACH(i, a) log_info("Got: <%s>", *i); - assert_se(streq(a[0], "one=2")); - assert_se(streq(a[1], "twelve=12")); - assert_se(streq(a[2], "twentyone=21")); - assert_se(streq(a[3], "twentytwo=22")); - assert_se(streq(a[4], "xxx=0x222")); - assert_se(streq(a[5], "xxx_minus_three= - 3")); - assert_se(streq(a[6], "yyy=2")); - assert_se(streq(a[7], "zzz=replacement")); - assert_se(streq(a[8], "zzzz=")); - assert_se(streq(a[9], "zzzzz=")); - assert_se(a[10] == NULL); + ASSERT_STREQ(a[0], "one=2"); + ASSERT_STREQ(a[1], "twelve=12"); + ASSERT_STREQ(a[2], "twentyone=21"); + ASSERT_STREQ(a[3], "twentytwo=22"); + ASSERT_STREQ(a[4], "xxx=0x222"); + ASSERT_STREQ(a[5], "xxx_minus_three= - 3"); + ASSERT_STREQ(a[6], "yyy=2"); + ASSERT_STREQ(a[7], "zzz=replacement"); + ASSERT_STREQ(a[8], "zzzz="); + ASSERT_STREQ(a[9], "zzzzz="); + ASSERT_NULL(a[10]); r = merge_env_file(&a, NULL, t); assert_se(r >= 0); @@ -265,17 +265,17 @@ TEST(merge_env_file) { STRV_FOREACH(i, a) log_info("Got2: <%s>", *i); - assert_se(streq(a[0], "one=2")); - assert_se(streq(a[1], "twelve=12")); - assert_se(streq(a[2], "twentyone=21")); - assert_se(streq(a[3], "twentytwo=22")); - assert_se(streq(a[4], "xxx=0x222")); - assert_se(streq(a[5], "xxx_minus_three=0x222 - 3")); - assert_se(streq(a[6], "yyy=2")); - assert_se(streq(a[7], "zzz=replacement")); - assert_se(streq(a[8], "zzzz=")); - assert_se(streq(a[9], "zzzzz=")); - assert_se(a[10] == NULL); + ASSERT_STREQ(a[0], "one=2"); + ASSERT_STREQ(a[1], "twelve=12"); + ASSERT_STREQ(a[2], "twentyone=21"); + ASSERT_STREQ(a[3], "twentytwo=22"); + ASSERT_STREQ(a[4], "xxx=0x222"); + ASSERT_STREQ(a[5], "xxx_minus_three=0x222 - 3"); + ASSERT_STREQ(a[6], "yyy=2"); + ASSERT_STREQ(a[7], "zzz=replacement"); + ASSERT_STREQ(a[8], "zzzz="); + ASSERT_STREQ(a[9], "zzzzz="); + ASSERT_NULL(a[10]); } TEST(merge_env_file_invalid) { @@ -323,7 +323,7 @@ TEST(executable_is_script) { r = executable_is_script(t, &command); assert_se(r > 0); - assert_se(streq(command, "/bin/script")); + ASSERT_STREQ(command, "/bin/script"); free(command); r = executable_is_script("/bin/sh", &command); @@ -398,27 +398,27 @@ TEST(read_one_line_file) { assert_se(f); assert_se(read_one_line_file(fn, &buf) == 0); - assert_se(streq_ptr(buf, "")); + ASSERT_STREQ(buf, ""); assert_se(read_one_line_file(fn, &buf2) == 0); - assert_se(streq_ptr(buf2, "")); + ASSERT_STREQ(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_STREQ(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_STREQ(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")); + ASSERT_STREQ(buf5, "x"); } TEST(write_string_stream) { @@ -442,7 +442,7 @@ TEST(write_string_stream) { rewind(f); assert_se(fgets(buf, sizeof(buf), f)); - assert_se(streq(buf, "boohoo\n")); + ASSERT_STREQ(buf, "boohoo\n"); f = safe_fclose(f); f = fopen(fn, "w+"); @@ -453,7 +453,7 @@ TEST(write_string_stream) { assert_se(fgets(buf, sizeof(buf), f)); printf(">%s<", buf); - assert_se(streq(buf, "boohoo")); + ASSERT_STREQ(buf, "boohoo"); } TEST(write_string_file) { @@ -467,7 +467,7 @@ TEST(write_string_file) { assert_se(write_string_file(fn, "boohoo", WRITE_STRING_FILE_CREATE) == 0); assert_se(read(fd, buf, sizeof(buf)) == 7); - assert_se(streq(buf, "boohoo\n")); + ASSERT_STREQ(buf, "boohoo\n"); } TEST(write_string_file_no_create) { @@ -482,7 +482,7 @@ TEST(write_string_file_no_create) { assert_se(write_string_file(fn, "boohoo", 0) == 0); assert_se(read(fd, buf, sizeof buf) == (ssize_t) strlen("boohoo\n")); - assert_se(streq(buf, "boohoo\n")); + ASSERT_STREQ(buf, "boohoo\n"); } TEST(write_string_file_verify) { @@ -578,14 +578,14 @@ TEST(search_and_fopen) { r = search_and_fopen(basename(name), "re", NULL, (const char**) dirs, &f, &p); assert_se(r >= 0); assert_se(e = path_startswith(p, "/tmp/")); - assert_se(streq(basename(name), e)); + ASSERT_STREQ(basename(name), e); f = safe_fclose(f); p = mfree(p); r = search_and_fopen(basename(name), NULL, NULL, (const char**) dirs, NULL, &p); assert_se(r >= 0); assert_se(e = path_startswith(p, "/tmp/")); - assert_se(streq(basename(name), e)); + ASSERT_STREQ(basename(name), e); p = mfree(p); r = search_and_fopen(name, "re", NULL, (const char**) dirs, &f, &p); @@ -602,14 +602,14 @@ TEST(search_and_fopen) { r = search_and_fopen(basename(name), "re", "/", (const char**) dirs, &f, &p); assert_se(r >= 0); assert_se(e = path_startswith(p, "/tmp/")); - assert_se(streq(basename(name), e)); + ASSERT_STREQ(basename(name), e); f = safe_fclose(f); p = mfree(p); r = search_and_fopen(basename(name), NULL, "/", (const char**) dirs, NULL, &p); assert_se(r >= 0); assert_se(e = path_startswith(p, "/tmp/")); - assert_se(streq(basename(name), e)); + ASSERT_STREQ(basename(name), e); p = mfree(p); r = search_and_fopen("/a/file/which/does/not/exist/i/guess", "re", NULL, (const char**) dirs, &f, &p); @@ -649,7 +649,7 @@ TEST(search_and_fopen_nulstr) { r = search_and_fopen_nulstr(basename(name), "re", NULL, dirs, &f, &p); assert_se(r >= 0); assert_se(e = path_startswith(p, "/tmp/")); - assert_se(streq(basename(name), e)); + ASSERT_STREQ(basename(name), e); f = safe_fclose(f); p = mfree(p); @@ -693,18 +693,18 @@ TEST(writing_tmpfile) { r = read_full_file(name, &contents, &size); assert_se(r == 0); printf("contents: %s", contents); - assert_se(streq(contents, "abc\n" ALPHANUMERICAL "\n")); + ASSERT_STREQ(contents, "abc\n" ALPHANUMERICAL "\n"); } TEST(tempfn) { char *ret = NULL, *p; assert_se(tempfn_xxxxxx("/foo/bar/waldo", NULL, &ret) >= 0); - assert_se(streq_ptr(ret, "/foo/bar/.#waldoXXXXXX")); + ASSERT_STREQ(ret, "/foo/bar/.#waldoXXXXXX"); free(ret); assert_se(tempfn_xxxxxx("/foo/bar/waldo", "[miau]", &ret) >= 0); - assert_se(streq_ptr(ret, "/foo/bar/.#[miau]waldoXXXXXX")); + ASSERT_STREQ(ret, "/foo/bar/.#[miau]waldoXXXXXX"); free(ret); assert_se(tempfn_random("/foo/bar/waldo", NULL, &ret) >= 0); @@ -900,7 +900,7 @@ TEST(read_line4) { r = read_line(f, SIZE_MAX, &s); assert_se((size_t) r == eof_endings[i].length); - assert_se(streq_ptr(s, "foo")); + ASSERT_STREQ(s, "foo"); assert_se(read_line(f, SIZE_MAX, NULL) == 0); /* Ensure we hit EOF */ } @@ -985,7 +985,7 @@ TEST(read_full_file_socket) { assert_se(peer.un.sun_family == AF_UNIX); assert_se(peerlen > offsetof(struct sockaddr_un, sun_path)); assert_se(peer.un.sun_path[0] == 0); - assert_se(streq(peer.un.sun_path + 1, clientname + 1)); + ASSERT_STREQ(peer.un.sun_path + 1, clientname + 1); #define TEST_STR "This is a test\nreally." @@ -996,7 +996,7 @@ TEST(read_full_file_socket) { assert_se(read_full_file_full(AT_FDCWD, jj, UINT64_MAX, SIZE_MAX, 0, NULL, &data, &size) == -ENXIO); assert_se(read_full_file_full(AT_FDCWD, jj, UINT64_MAX, SIZE_MAX, READ_FULL_FILE_CONNECT_SOCKET, clientname, &data, &size) >= 0); assert_se(size == strlen(TEST_STR)); - assert_se(streq(data, TEST_STR)); + ASSERT_STREQ(data, TEST_STR); assert_se(wait_for_terminate_and_check("(server)", pid, WAIT_LOG) >= 0); #undef TEST_STR @@ -1125,7 +1125,7 @@ TEST(fdopen_independent) { 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_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); @@ -1133,7 +1133,7 @@ TEST(fdopen_independent) { 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_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); @@ -1141,7 +1141,7 @@ TEST(fdopen_independent) { 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_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); diff --git a/src/test/test-format-table.c b/src/test/test-format-table.c index 7d544b1..3dbfda7 100644 --- a/src/test/test-format-table.c +++ b/src/test/test-format-table.c @@ -551,7 +551,7 @@ TEST(vertical) { assert_se(streq(formatted, " pfft aa: foo\n" - " uuu o: 1.0K\n" + " uuu o: 1K\n" "lllllllllllo: jjjjjjjjjjjjjjjjj\n")); _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL; @@ -580,7 +580,7 @@ TEST(path_basename) { assert_se(table_format(t, &formatted) >= 0); - assert_se(streq(formatted, "bar\nbar\nbaz\n")); + ASSERT_STREQ(formatted, "bar\nbar\nbaz\n"); } TEST(dup_cell) { @@ -626,6 +626,41 @@ TEST(dup_cell) { "aaa 0 65535 4294967295 100% ../ hello hello hello\n")); } +TEST(table_bps) { + _cleanup_(table_unrefp) Table *table = NULL; + _cleanup_free_ char *formatted = NULL; + + assert_se(table = table_new("uint64", "size", "bps")); + uint64_t v; + FOREACH_ARGUMENT(v, + 2500, + 10000000, + 20000000, + 25000000, + 1000000000, + 2000000000, + 2500000000) + assert_se(table_add_many(table, + TABLE_UINT64, v, + TABLE_SIZE, v, + TABLE_BPS, v) >= 0); + + table_set_width(table, 50); + assert_se(table_format(table, &formatted) >= 0); + + printf("%s", formatted); + assert_se(streq(formatted, + "UINT64 SIZE BPS\n" + "2500 2.4K 2.5Kbps\n" + "10000000 9.5M 10Mbps\n" + "20000000 19M 20Mbps\n" + "25000000 23.8M 25Mbps\n" + "1000000000 953.6M 1Gbps\n" + "2000000000 1.8G 2Gbps\n" + "2500000000 2.3G 2.5Gbps\n" + )); +} + static int intro(void) { assert_se(setenv("SYSTEMD_COLORS", "0", 1) >= 0); assert_se(setenv("COLUMNS", "40", 1) >= 0); diff --git a/src/test/test-format-util.c b/src/test/test-format-util.c index 3063509..8afba4e 100644 --- a/src/test/test-format-util.c +++ b/src/test/test-format-util.c @@ -34,26 +34,26 @@ static void test_format_bytes_one(uint64_t val, bool trailing_B, const char *iec const char *si_with_p, const char *si_without_p) { char buf[FORMAT_BYTES_MAX]; - assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_with_p)); - assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_without_p)); - assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), si_with_p)); - assert_se(streq_ptr(format_bytes_full(buf, sizeof buf, val, trailing_B ? FORMAT_BYTES_TRAILING_B : 0), si_without_p)); + ASSERT_STREQ(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_with_p); + ASSERT_STREQ(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_USE_IEC | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), iec_without_p); + ASSERT_STREQ(format_bytes_full(buf, sizeof buf, val, FORMAT_BYTES_BELOW_POINT | (trailing_B ? FORMAT_BYTES_TRAILING_B : 0)), si_with_p); + ASSERT_STREQ(format_bytes_full(buf, sizeof buf, val, trailing_B ? FORMAT_BYTES_TRAILING_B : 0), si_without_p); } TEST(format_bytes) { test_format_bytes_one(900, true, "900B", "900B", "900B", "900B"); test_format_bytes_one(900, false, "900", "900", "900", "900"); - test_format_bytes_one(1023, true, "1023B", "1023B", "1.0K", "1K"); - test_format_bytes_one(1023, false, "1023", "1023", "1.0K", "1K"); - test_format_bytes_one(1024, true, "1.0K", "1K", "1.0K", "1K"); - test_format_bytes_one(1024, false, "1.0K", "1K", "1.0K", "1K"); - test_format_bytes_one(1100, true, "1.0K", "1K", "1.1K", "1K"); + test_format_bytes_one(1023, true, "1023B", "1023B", "1K", "1K"); + test_format_bytes_one(1023, false, "1023", "1023", "1K", "1K"); + test_format_bytes_one(1024, true, "1K", "1K", "1K", "1K"); + test_format_bytes_one(1024, false, "1K", "1K", "1K", "1K"); + test_format_bytes_one(1100, true, "1K", "1K", "1.1K", "1K"); test_format_bytes_one(1500, true, "1.4K", "1K", "1.5K", "1K"); - test_format_bytes_one(UINT64_C(3)*1024*1024, true, "3.0M", "3M", "3.1M", "3M"); - test_format_bytes_one(UINT64_C(3)*1024*1024*1024, true, "3.0G", "3G", "3.2G", "3G"); - test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024, true, "3.0T", "3T", "3.2T", "3T"); - test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024, true, "3.0P", "3P", "3.3P", "3P"); - test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024*1024, true, "3.0E", "3E", "3.4E", "3E"); + test_format_bytes_one(UINT64_C(3)*1024*1024, true, "3M", "3M", "3.1M", "3M"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024, true, "3G", "3G", "3.2G", "3G"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024, true, "3T", "3T", "3.2T", "3T"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024, true, "3P", "3P", "3.3P", "3P"); + test_format_bytes_one(UINT64_C(3)*1024*1024*1024*1024*1024*1024, true, "3E", "3E", "3.4E", "3E"); test_format_bytes_one(UINT64_MAX, true, NULL, NULL, NULL, NULL); test_format_bytes_one(UINT64_MAX, false, NULL, NULL, NULL, NULL); } diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index b32feff..d44e043 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -45,7 +45,7 @@ TEST(readlink_and_make_absolute) { log_tests_skipped_errno(errno, "symlink() not possible"); } else { assert_se(readlink_and_make_absolute(name_alias, &r1) >= 0); - assert_se(streq(r1, name)); + ASSERT_STREQ(r1, name); assert_se(unlink(name_alias) >= 0); assert_se(safe_getcwd(&pwd) >= 0); @@ -53,7 +53,7 @@ TEST(readlink_and_make_absolute) { assert_se(chdir(tempdir) >= 0); assert_se(symlink(name2, name_alias) >= 0); assert_se(readlink_and_make_absolute(name_alias, &r2) >= 0); - assert_se(streq(r2, name)); + ASSERT_STREQ(r2, name); assert_se(unlink(name_alias) >= 0); assert_se(chdir(pwd) >= 0); @@ -97,33 +97,33 @@ TEST(var_tmp) { assert_se(unsetenv("TMP") >= 0); assert_se(var_tmp_dir(&tmp_dir) >= 0); - assert_se(streq(tmp_dir, "/var/tmp")); + ASSERT_STREQ(tmp_dir, "/var/tmp"); assert_se(setenv("TMPDIR", "/tmp", true) >= 0); - assert_se(streq(getenv("TMPDIR"), "/tmp")); + ASSERT_STREQ(getenv("TMPDIR"), "/tmp"); assert_se(var_tmp_dir(&tmp_dir) >= 0); - assert_se(streq(tmp_dir, "/tmp")); + ASSERT_STREQ(tmp_dir, "/tmp"); assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0); - assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88")); + ASSERT_STREQ(getenv("TMPDIR"), "/88_does_not_exist_88"); assert_se(var_tmp_dir(&tmp_dir) >= 0); - assert_se(streq(tmp_dir, "/var/tmp")); + ASSERT_STREQ(tmp_dir, "/var/tmp"); if (tmpdir_backup) { assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0); - assert_se(streq(getenv("TMPDIR"), tmpdir_backup)); + ASSERT_STREQ(getenv("TMPDIR"), tmpdir_backup); } if (temp_backup) { assert_se(setenv("TEMP", temp_backup, true) >= 0); - assert_se(streq(getenv("TEMP"), temp_backup)); + ASSERT_STREQ(getenv("TEMP"), temp_backup); } if (tmp_backup) { assert_se(setenv("TMP", tmp_backup, true) >= 0); - assert_se(streq(getenv("TMP"), tmp_backup)); + ASSERT_STREQ(getenv("TMP"), tmp_backup); } } @@ -275,14 +275,14 @@ TEST(unlinkat_deallocate) { assert_se(write(fd, "hallo\n", 6) == 6); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); assert_se(st.st_size == 6); assert_se(st.st_blocks > 0); assert_se(st.st_nlink == 1); assert_se(unlinkat_deallocate(AT_FDCWD, p, UNLINK_ERASE) >= 0); - assert_se(fstat(fd, &st) >= 0); + ASSERT_OK_ERRNO(fstat(fd, &st)); assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6 (it worked) or 0 (we had to resort to truncation) */ assert_se(st.st_blocks == 0); @@ -522,9 +522,9 @@ static void test_parse_cifs_service_one(const char *f, const char *h, const char _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL; assert_se(parse_cifs_service(f, &a, &b, &c) == ret); - assert_se(streq_ptr(a, h)); - assert_se(streq_ptr(b, s)); - assert_se(streq_ptr(c, d)); + ASSERT_STREQ(a, h); + ASSERT_STREQ(b, s); + ASSERT_STREQ(c, d); } TEST(parse_cifs_service) { @@ -557,13 +557,13 @@ TEST(open_mkdir_at) { 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_OK_ERRNO(fstat(fd, &stb)); 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_OK_ERRNO(fstat(fd, &stb)); assert_se(stat_inode_same(&sta, &stb)); fd = safe_close(fd); @@ -753,6 +753,36 @@ TEST(xopenat_lock_full) { assert_se(xopenat_lock_full(tfd, "def", O_DIRECTORY, 0, 0755, LOCK_POSIX, LOCK_EX) == -EBADF); } +TEST(linkat_replace) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_close_ int tfd = -EBADF; + + assert_se((tfd = mkdtemp_open(NULL, 0, &t)) >= 0); + + _cleanup_close_ int fd1 = openat(tfd, "foo", O_CREAT|O_RDWR|O_CLOEXEC, 0600); + assert_se(fd1 >= 0); + + assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0); + assert_se(linkat_replace(tfd, "foo", tfd, "bar") >= 0); + + _cleanup_close_ int fd1_check = openat(tfd, "bar", O_RDWR|O_CLOEXEC); + assert_se(fd1_check >= 0); + + assert_se(inode_same_at(fd1, NULL, fd1_check, NULL, AT_EMPTY_PATH) > 0); + + _cleanup_close_ int fd2 = openat(tfd, "baz", O_CREAT|O_RDWR|O_CLOEXEC, 0600); + assert_se(fd2 >= 0); + + assert_se(inode_same_at(fd1, NULL, fd2, NULL, AT_EMPTY_PATH) == 0); + + assert_se(linkat_replace(tfd, "foo", tfd, "baz") >= 0); + + _cleanup_close_ int fd2_check = openat(tfd, "baz", O_RDWR|O_CLOEXEC); + + assert_se(inode_same_at(fd2, NULL, fd2_check, NULL, AT_EMPTY_PATH) == 0); + assert_se(inode_same_at(fd1, NULL, fd2_check, NULL, AT_EMPTY_PATH) > 0); +} + static int intro(void) { arg_test_dir = saved_argv[1]; return EXIT_SUCCESS; @@ -770,25 +800,25 @@ TEST(readlinkat_malloc) { assert_se(symlinkat(expect, tfd, "linkname") >= 0); assert_se(readlinkat_malloc(tfd, "linkname", &p) >= 0); - assert_se(streq(p, expect)); + ASSERT_STREQ(p, expect); p = mfree(p); fd = openat(tfd, "linkname", O_PATH | O_NOFOLLOW | O_CLOEXEC); assert_se(fd >= 0); assert_se(readlinkat_malloc(fd, NULL, &p) >= 0); - assert_se(streq(p, expect)); + ASSERT_STREQ(p, expect); p = mfree(p); assert_se(readlinkat_malloc(fd, "", &p) >= 0); - assert_se(streq(p, expect)); + ASSERT_STREQ(p, expect); p = mfree(p); fd = safe_close(fd); assert_se(q = path_join(t, "linkname")); assert_se(readlinkat_malloc(AT_FDCWD, q, &p) >= 0); - assert_se(streq(p, expect)); + ASSERT_STREQ(p, expect); p = mfree(p); assert_se(readlinkat_malloc(INT_MAX, q, &p) >= 0); - assert_se(streq(p, expect)); + ASSERT_STREQ(p, expect); p = mfree(p); q = mfree(q); } diff --git a/src/test/test-fstab-util.c b/src/test/test-fstab-util.c index 89365b0..773b1f9 100644 --- a/src/test/test-fstab-util.c +++ b/src/test/test-fstab-util.c @@ -39,9 +39,9 @@ static void do_fstab_filter_options(const char *opts, opts, r, strnull(name), value, filtered, r_expected, strnull(name_expected), strnull(value_expected), filtered_expected ?: opts); assert_se(r == r_expected); - assert_se(streq_ptr(name, name_expected)); - assert_se(streq_ptr(value, value_expected)); - assert_se(streq_ptr(filtered, filtered_expected ?: opts)); + ASSERT_STREQ(name, name_expected); + ASSERT_STREQ(value, value_expected); + ASSERT_STREQ(filtered, filtered_expected ?: opts); /* test mode which returns all the values */ @@ -51,8 +51,8 @@ static void do_fstab_filter_options(const char *opts, opts, r, strnull(name), joined, r_values_expected, strnull(name_expected), strnull(values_expected)); assert_se(r == r_values_expected); - assert_se(streq_ptr(name, r_values_expected > 0 ? name_expected : NULL)); - assert_se(streq_ptr(joined, values_expected)); + ASSERT_STREQ(name, r_values_expected > 0 ? name_expected : NULL); + ASSERT_STREQ(joined, values_expected); /* also test the malloc-less mode */ r = fstab_filter_options(opts, remove, &name, NULL, NULL, NULL); @@ -60,7 +60,7 @@ static void do_fstab_filter_options(const char *opts, opts, r, strnull(name), r_expected, strnull(name_expected)); assert_se(r == r_expected); - assert_se(streq_ptr(name, name_expected)); + ASSERT_STREQ(name, name_expected); } TEST(fstab_filter_options) { @@ -116,7 +116,7 @@ TEST(fstab_filter_options) { do_fstab_filter_options(" opt ", "opt\0x-opt\0", 0, 0, NULL, NULL, "", NULL); /* check function with NULL args */ - do_fstab_filter_options(NULL, "opt\0", 0, 0, NULL, NULL, "", ""); + do_fstab_filter_options(NULL, "opt\0", 0, 0, NULL, NULL, "", NULL); do_fstab_filter_options("", "opt\0", 0, 0, NULL, NULL, "", ""); /* unnecessary comma separators */ @@ -160,32 +160,32 @@ TEST(fstab_node_to_udev_node) { n = fstab_node_to_udev_node("LABEL=applé/jack"); puts(n); - assert_se(streq(n, "/dev/disk/by-label/applé\\x2fjack")); + ASSERT_STREQ(n, "/dev/disk/by-label/applé\\x2fjack"); free(n); n = fstab_node_to_udev_node("PARTLABEL=pinkié pie"); puts(n); - assert_se(streq(n, "/dev/disk/by-partlabel/pinkié\\x20pie")); + ASSERT_STREQ(n, "/dev/disk/by-partlabel/pinkié\\x20pie"); free(n); n = fstab_node_to_udev_node("UUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); puts(n); - assert_se(streq(n, "/dev/disk/by-uuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + ASSERT_STREQ(n, "/dev/disk/by-uuid/037b9d94-148e-4ee4-8d38-67bfe15bb535"); free(n); n = fstab_node_to_udev_node("PARTUUID=037b9d94-148e-4ee4-8d38-67bfe15bb535"); puts(n); - assert_se(streq(n, "/dev/disk/by-partuuid/037b9d94-148e-4ee4-8d38-67bfe15bb535")); + ASSERT_STREQ(n, "/dev/disk/by-partuuid/037b9d94-148e-4ee4-8d38-67bfe15bb535"); free(n); n = fstab_node_to_udev_node("PONIES=awesome"); puts(n); - assert_se(streq(n, "PONIES=awesome")); + ASSERT_STREQ(n, "PONIES=awesome"); free(n); n = fstab_node_to_udev_node("/dev/xda1"); puts(n); - assert_se(streq(n, "/dev/xda1")); + ASSERT_STREQ(n, "/dev/xda1"); free(n); } diff --git a/src/test/test-glob-util.c b/src/test/test-glob-util.c index 9b3e73c..49d71f1 100644 --- a/src/test/test-glob-util.c +++ b/src/test/test-glob-util.c @@ -24,14 +24,14 @@ TEST(glob_first) { r = glob_first("/tmp/test-glob_first*", &first); assert_se(r == 1); - assert_se(streq(name, first)); + ASSERT_STREQ(name, first); first = mfree(first); r = unlink(name); assert_se(r == 0); r = glob_first("/tmp/test-glob_first*", &first); assert_se(r == 0); - assert_se(first == NULL); + ASSERT_NULL(first); } TEST(glob_exists) { @@ -109,8 +109,8 @@ TEST(safe_glob) { r = safe_glob(fn2, GLOB_NOSORT|GLOB_BRACE, &g); assert_se(r == 0); assert_se(g.gl_pathc == 1); - assert_se(streq(g.gl_pathv[0], fname)); - assert_se(g.gl_pathv[1] == NULL); + ASSERT_STREQ(g.gl_pathv[0], fname); + ASSERT_NULL(g.gl_pathv[1]); (void) rm_rf(template, REMOVE_ROOT|REMOVE_PHYSICAL); } @@ -119,7 +119,7 @@ static void test_glob_non_glob_prefix_one(const char *path, const char *expected _cleanup_free_ char *t; assert_se(glob_non_glob_prefix(path, &t) == 0); - assert_se(streq(t, expected)); + ASSERT_STREQ(t, expected); } TEST(glob_non_glob) { diff --git a/src/test/test-gpt.c b/src/test/test-gpt.c index fa5923e..28b53d4 100644 --- a/src/test/test-gpt.c +++ b/src/test/test-gpt.c @@ -34,15 +34,15 @@ TEST(gpt_types_against_architectures) { printf("%s %s\n", GREEN_CHECK_MARK(), joined); if (streq(prefix, "root-") && streq(suffix, "")) - assert_se(type.designator == PARTITION_ROOT); + ASSERT_EQ(type.designator, PARTITION_ROOT); if (streq(prefix, "root-") && streq(suffix, "-verity")) - assert_se(type.designator == PARTITION_ROOT_VERITY); + ASSERT_EQ(type.designator, PARTITION_ROOT_VERITY); if (streq(prefix, "usr-") && streq(suffix, "")) - assert_se(type.designator == PARTITION_USR); + ASSERT_EQ(type.designator, PARTITION_USR); if (streq(prefix, "usr-") && streq(suffix, "-verity")) - assert_se(type.designator == PARTITION_USR_VERITY); + ASSERT_EQ(type.designator, PARTITION_USR_VERITY); - assert_se(type.arch == a); + ASSERT_EQ(type.arch, a); } } @@ -72,40 +72,40 @@ TEST(type_alias_same) { GptPartitionType x, y; x = gpt_partition_type_from_uuid(t->uuid); /* search first by uuid */ - assert_se(gpt_partition_type_from_string(t->name, &y) >= 0); /* search first by name */ + ASSERT_GE(gpt_partition_type_from_string(t->name, &y), 0); /* search first by name */ - assert_se(t->arch == x.arch); - assert_se(t->arch == y.arch); - assert_se(t->designator == x.designator); - assert_se(t->designator == y.designator); + ASSERT_EQ(t->arch, x.arch); + ASSERT_EQ(t->arch, y.arch); + ASSERT_EQ(t->designator, x.designator); + ASSERT_EQ(t->designator, y.designator); } } TEST(override_architecture) { GptPartitionType x, y; - assert_se(gpt_partition_type_from_string("root-x86-64", &x) >= 0); - assert_se(x.arch == ARCHITECTURE_X86_64); + ASSERT_GE(gpt_partition_type_from_string("root-x86-64", &x), 0); + ASSERT_EQ(x.arch, ARCHITECTURE_X86_64); - assert_se(gpt_partition_type_from_string("root-arm64", &y) >= 0); - assert(y.arch == ARCHITECTURE_ARM64); + ASSERT_GE(gpt_partition_type_from_string("root-arm64", &y), 0); + ASSERT_EQ(y.arch, ARCHITECTURE_ARM64); x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); - assert_se(x.arch == y.arch); - assert_se(x.designator == y.designator); + ASSERT_EQ(x.arch, y.arch); + ASSERT_EQ(x.designator, y.designator); assert_se(sd_id128_equal(x.uuid, y.uuid)); - assert_se(streq(x.name, y.name)); + ASSERT_STREQ(x.name, y.name); /* If the partition type does not have an architecture, nothing should change. */ - assert_se(gpt_partition_type_from_string("esp", &x) >= 0); + ASSERT_GE(gpt_partition_type_from_string("esp", &x), 0); y = x; x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64); - assert_se(x.arch == y.arch); - assert_se(x.designator == y.designator); + ASSERT_EQ(x.arch, y.arch); + ASSERT_EQ(x.designator, y.designator); assert_se(sd_id128_equal(x.uuid, y.uuid)); - assert_se(streq(x.name, y.name)); + ASSERT_STREQ(x.name, y.name); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-hashmap-plain.c b/src/test/test-hashmap-plain.c index 152b1c0..f2d2d4f 100644 --- a/src/test/test-hashmap-plain.c +++ b/src/test/test-hashmap-plain.c @@ -10,6 +10,13 @@ #include "tests.h" #include "time-util.h" +/* PROJECT_FILE, which is used by ASSERT_XYZ(), cannot be used in generated files, as the build directory + * may be outside of the source directory. */ +#ifdef ORDERED +# undef PROJECT_FILE +# define PROJECT_FILE __FILE__ +#endif + TEST(hashmap_replace) { _cleanup_hashmap_free_ Hashmap *m = NULL; _cleanup_free_ char *val1 = NULL, *val2 = NULL, *val3 = NULL, *val4 = NULL, *val5 = NULL; @@ -35,11 +42,11 @@ TEST(hashmap_replace) { hashmap_replace(m, "key 3", val1); r = hashmap_get(m, "key 3"); - assert_se(streq(r, "val1")); + ASSERT_STREQ(r, "val1"); hashmap_replace(m, "key 5", val5); r = hashmap_get(m, "key 5"); - assert_se(streq(r, "val5")); + ASSERT_STREQ(r, "val5"); } TEST(hashmap_copy) { @@ -66,13 +73,13 @@ TEST(hashmap_copy) { copy = hashmap_copy(m); r = hashmap_get(copy, "key 1"); - assert_se(streq(r, "val1")); + ASSERT_STREQ(r, "val1"); r = hashmap_get(copy, "key 2"); - assert_se(streq(r, "val2")); + ASSERT_STREQ(r, "val2"); r = hashmap_get(copy, "key 3"); - assert_se(streq(r, "val3")); + ASSERT_STREQ(r, "val3"); r = hashmap_get(copy, "key 4"); - assert_se(streq(r, "val4")); + ASSERT_STREQ(r, "val4"); } TEST(hashmap_get_strv) { @@ -102,10 +109,10 @@ TEST(hashmap_get_strv) { strv = strv_sort(strv); #endif - assert_se(streq(strv[0], "val1")); - assert_se(streq(strv[1], "val2")); - assert_se(streq(strv[2], "val3")); - assert_se(streq(strv[3], "val4")); + ASSERT_STREQ(strv[0], "val1"); + ASSERT_STREQ(strv[1], "val2"); + ASSERT_STREQ(strv[2], "val3"); + ASSERT_STREQ(strv[3], "val4"); } TEST(hashmap_move_one) { @@ -196,15 +203,15 @@ TEST(hashmap_update) { hashmap_put(m, "key 1", val1); r = hashmap_get(m, "key 1"); - assert_se(streq(r, "old_value")); + ASSERT_STREQ(r, "old_value"); assert_se(hashmap_update(m, "key 2", val2) == -ENOENT); r = hashmap_get(m, "key 1"); - assert_se(streq(r, "old_value")); + ASSERT_STREQ(r, "old_value"); assert_se(hashmap_update(m, "key 1", val2) == 0); r = hashmap_get(m, "key 1"); - assert_se(streq(r, "new_value")); + ASSERT_STREQ(r, "new_value"); } TEST(hashmap_put) { @@ -231,22 +238,22 @@ TEST(hashmap_remove1) { char *r; r = hashmap_remove(NULL, "key 1"); - assert_se(r == NULL); + ASSERT_NULL(r); m = hashmap_new(&string_hash_ops); assert_se(m); r = hashmap_remove(m, "no such key"); - assert_se(r == NULL); + ASSERT_NULL(r); hashmap_put(m, "key 1", (void*) "val 1"); hashmap_put(m, "key 2", (void*) "val 2"); r = hashmap_remove(m, "key 1"); - assert_se(streq(r, "val 1")); + ASSERT_STREQ(r, "val 1"); r = hashmap_get(m, "key 2"); - assert_se(streq(r, "val 2")); + ASSERT_STREQ(r, "val 2"); assert_se(!hashmap_get(m, "key 1")); } @@ -259,25 +266,25 @@ TEST(hashmap_remove2) { void *r, *r2; r = hashmap_remove2(NULL, "key 1", &r2); - assert_se(r == NULL); + ASSERT_NULL(r); m = hashmap_new(&string_hash_ops); assert_se(m); r = hashmap_remove2(m, "no such key", &r2); - assert_se(r == NULL); + ASSERT_NULL(r); hashmap_put(m, strdup(key1), strdup(val1)); hashmap_put(m, strdup(key2), strdup(val2)); r = hashmap_remove2(m, key1, &r2); - assert_se(streq(r, val1)); - assert_se(streq(r2, key1)); + ASSERT_STREQ(r, val1); + ASSERT_STREQ(r2, key1); free(r); free(r2); r = hashmap_get(m, key2); - assert_se(streq(r, val2)); + ASSERT_STREQ(r, val2); assert_se(!hashmap_get(m, key1)); } @@ -289,29 +296,29 @@ TEST(hashmap_remove_value) { char val2[] = "val 2"; r = hashmap_remove_value(NULL, "key 1", val1); - assert_se(r == NULL); + ASSERT_NULL(r); m = hashmap_new(&string_hash_ops); assert_se(m); r = hashmap_remove_value(m, "key 1", val1); - assert_se(r == NULL); + ASSERT_NULL(r); hashmap_put(m, "key 1", val1); hashmap_put(m, "key 2", val2); r = hashmap_remove_value(m, "key 1", val1); - assert_se(streq(r, "val 1")); + ASSERT_STREQ(r, "val 1"); r = hashmap_get(m, "key 2"); - assert_se(streq(r, "val 2")); + ASSERT_STREQ(r, "val 2"); assert_se(!hashmap_get(m, "key 1")); r = hashmap_remove_value(m, "key 2", val1); - assert_se(r == NULL); + ASSERT_NULL(r); r = hashmap_get(m, "key 2"); - assert_se(streq(r, "val 2")); + ASSERT_STREQ(r, "val 2"); assert_se(!hashmap_get(m, "key 1")); } @@ -336,7 +343,7 @@ TEST(hashmap_remove_and_put) { assert_se(valid == 0); r = hashmap_get(m, "key 2"); - assert_se(streq(r, "val 2")); + ASSERT_STREQ(r, "val 2"); assert_se(!hashmap_get(m, "key 1")); valid = hashmap_put(m, "key 3", (void*) (const char *) "val 3"); @@ -599,17 +606,17 @@ TEST(hashmap_get) { assert_se(val); r = hashmap_get(NULL, "Key 1"); - assert_se(r == NULL); + ASSERT_NULL(r); m = hashmap_new(&string_hash_ops); hashmap_put(m, "Key 1", val); r = hashmap_get(m, "Key 1"); - assert_se(streq(r, val)); + ASSERT_STREQ(r, val); r = hashmap_get(m, "no such key"); - assert_se(r == NULL); + ASSERT_NULL(r); assert_se(m); } @@ -628,7 +635,7 @@ TEST(hashmap_get2) { assert_se(key_copy); r = hashmap_get2(NULL, key_orig, &key_copy); - assert_se(r == NULL); + ASSERT_NULL(r); m = hashmap_new(&string_hash_ops); @@ -636,12 +643,12 @@ TEST(hashmap_get2) { key_copy = NULL; r = hashmap_get2(m, key_orig, &key_copy); - assert_se(streq(r, val)); + ASSERT_STREQ(r, val); assert_se(key_orig != key_copy); - assert_se(streq(key_orig, key_copy)); + ASSERT_STREQ(key_orig, key_copy); r = hashmap_get2(m, "no such key", NULL); - assert_se(r == NULL); + ASSERT_NULL(r); assert_se(m); } @@ -780,12 +787,12 @@ TEST(hashmap_first) { assert_se(!hashmap_first(m)); assert_se(hashmap_put(m, "key 1", (void*) "val 1") == 1); - assert_se(streq(hashmap_first(m), "val 1")); + ASSERT_STREQ(hashmap_first(m), "val 1"); assert_se(hashmap_put(m, "key 2", (void*) "val 2") == 1); #ifdef ORDERED - assert_se(streq(hashmap_first(m), "val 1")); + ASSERT_STREQ(hashmap_first(m), "val 1"); assert_se(hashmap_remove(m, "key 1")); - assert_se(streq(hashmap_first(m), "val 2")); + ASSERT_STREQ(hashmap_first(m), "val 2"); #endif } @@ -797,12 +804,12 @@ TEST(hashmap_first_key) { assert_se(!hashmap_first_key(m)); assert_se(hashmap_put(m, "key 1", NULL) == 1); - assert_se(streq(hashmap_first_key(m), "key 1")); + ASSERT_STREQ(hashmap_first_key(m), "key 1"); assert_se(hashmap_put(m, "key 2", NULL) == 1); #ifdef ORDERED - assert_se(streq(hashmap_first_key(m), "key 1")); - assert_se(hashmap_remove(m, "key 1") == NULL); - assert_se(streq(hashmap_first_key(m), "key 2")); + ASSERT_STREQ(hashmap_first_key(m), "key 1"); + ASSERT_NULL(hashmap_remove(m, "key 1")); + ASSERT_STREQ(hashmap_first_key(m), "key 2"); #endif } @@ -814,7 +821,7 @@ TEST(hashmap_steal_first_key) { assert_se(!hashmap_steal_first_key(m)); assert_se(hashmap_put(m, "key 1", NULL) == 1); - assert_se(streq(hashmap_steal_first_key(m), "key 1")); + ASSERT_STREQ(hashmap_steal_first_key(m), "key 1"); assert_se(hashmap_isempty(m)); } @@ -965,6 +972,8 @@ TEST(string_strv_hashmap) { TEST(hashmap_dump_sorted) { static void * const expected[] = { UINT_TO_PTR(123U), UINT_TO_PTR(12U), UINT_TO_PTR(345U), }; + static const char *expected_keys[] = { "key 0", "key 1", "key 2", }; + static void * const expected_keys2[] = { UINT_TO_PTR(111U), UINT_TO_PTR(222U), UINT_TO_PTR(333U), }; _cleanup_hashmap_free_ Hashmap *m = NULL; _cleanup_free_ void **vals = NULL; size_t n; @@ -983,6 +992,13 @@ TEST(hashmap_dump_sorted) { assert_se(n == ELEMENTSOF(expected)); assert_se(memcmp(vals, expected, n * sizeof(void*)) == 0); + vals = mfree(vals); + + assert_se(hashmap_dump_keys_sorted(m, &vals, &n) >= 0); + assert_se(n == ELEMENTSOF(expected_keys)); + for (size_t i = 0; i < n; i++) + ASSERT_STREQ(vals[i], expected_keys[i]); + vals = mfree(vals); m = hashmap_free(m); @@ -999,6 +1015,12 @@ TEST(hashmap_dump_sorted) { assert_se(hashmap_dump_sorted(m, &vals, &n) >= 0); assert_se(n == ELEMENTSOF(expected)); assert_se(memcmp(vals, expected, n * sizeof(void*)) == 0); + + vals = mfree(vals); + + assert_se(hashmap_dump_keys_sorted(m, &vals, &n) >= 0); + assert_se(n == ELEMENTSOF(expected_keys2)); + assert_se(memcmp(vals, expected_keys2, n * sizeof(void*)) == 0); } /* Signal to test-hashmap.c that tests from this compilation unit were run. */ diff --git a/src/test/test-hashmap.c b/src/test/test-hashmap.c index 5daa0e6..74e68a4 100644 --- a/src/test/test-hashmap.c +++ b/src/test/test-hashmap.c @@ -42,7 +42,7 @@ TEST(trivial_compare_func) { } TEST(string_compare_func) { - assert_se(string_compare_func("fred", "wilma") != 0); + ASSERT_NE(string_compare_func("fred", "wilma"), 0); assert_se(string_compare_func("fred", "fred") == 0); } @@ -98,8 +98,8 @@ TEST(iterated_cache) { hashmap_clear(m); compare_cache(m, c); - assert_se(hashmap_free(m) == NULL); - assert_se(iterated_cache_free(c) == NULL); + ASSERT_NULL(hashmap_free(m)); + ASSERT_NULL(iterated_cache_free(c)); } TEST(hashmap_put_strdup) { @@ -116,7 +116,7 @@ TEST(hashmap_put_strdup) { assert_se(hashmap_contains(m, "foo")); s = hashmap_get(m, "foo"); - assert_se(streq(s, "bar")); + ASSERT_STREQ(s, "bar"); assert_se(hashmap_put_strdup(&m, "xxx", "bar") == 1); assert_se(hashmap_put_strdup(&m, "xxx", "bar") == 0); @@ -125,7 +125,7 @@ TEST(hashmap_put_strdup) { assert_se(hashmap_contains(m, "xxx")); s = hashmap_get(m, "xxx"); - assert_se(streq(s, "bar")); + ASSERT_STREQ(s, "bar"); } TEST(hashmap_put_strdup_null) { @@ -139,7 +139,7 @@ TEST(hashmap_put_strdup_null) { assert_se(hashmap_contains(m, "foo")); s = hashmap_get(m, "foo"); - assert_se(streq(s, "bar")); + ASSERT_STREQ(s, "bar"); assert_se(hashmap_put_strdup(&m, "xxx", NULL) == 1); assert_se(hashmap_put_strdup(&m, "xxx", "bar") == -EEXIST); @@ -147,7 +147,7 @@ TEST(hashmap_put_strdup_null) { assert_se(hashmap_contains(m, "xxx")); s = hashmap_get(m, "xxx"); - assert_se(s == NULL); + ASSERT_NULL(s); } /* This file tests in test-hashmap-plain.c, and tests in test-hashmap-ordered.c, which is generated diff --git a/src/test/test-hexdecoct.c b/src/test/test-hexdecoct.c index f884008..5c39fc7 100644 --- a/src/test/test-hexdecoct.c +++ b/src/test/test-hexdecoct.c @@ -80,9 +80,9 @@ static void test_hexmem_one(const char *in, const char *expected) { assert_se(result = hexmem(in, strlen_ptr(in))); log_debug("hexmem(\"%s\") → \"%s\" (expected: \"%s\")", strnull(in), result, expected); - assert_se(streq(result, expected)); + ASSERT_STREQ(result, expected); - assert_se(unhexmem(result, SIZE_MAX, &mem, &len) >= 0); + assert_se(unhexmem(result, &mem, &len) >= 0); assert_se(memcmp_safe(mem, in, len) == 0); } @@ -97,7 +97,7 @@ static void test_unhexmem_one(const char *s, size_t l, int retval) { _cleanup_free_ void *mem = NULL; size_t len; - assert_se(unhexmem(s, l, &mem, &len) == retval); + assert_se(unhexmem_full(s, l, /* secure = */ false, &mem, &len) == retval); if (retval == 0) { char *answer; @@ -106,7 +106,7 @@ static void test_unhexmem_one(const char *s, size_t l, int retval) { assert_se(hex = hexmem(mem, len)); answer = strndupa_safe(strempty(s), l); - assert_se(streq(delete_chars(answer, WHITESPACE), hex)); + ASSERT_STREQ(delete_chars(answer, WHITESPACE), hex); } } @@ -134,72 +134,72 @@ TEST(base32hexmem) { b32 = base32hexmem("", STRLEN(""), true); assert_se(b32); - assert_se(streq(b32, "")); + ASSERT_STREQ(b32, ""); free(b32); b32 = base32hexmem("f", STRLEN("f"), true); assert_se(b32); - assert_se(streq(b32, "CO======")); + ASSERT_STREQ(b32, "CO======"); free(b32); b32 = base32hexmem("fo", STRLEN("fo"), true); assert_se(b32); - assert_se(streq(b32, "CPNG====")); + ASSERT_STREQ(b32, "CPNG===="); free(b32); b32 = base32hexmem("foo", STRLEN("foo"), true); assert_se(b32); - assert_se(streq(b32, "CPNMU===")); + ASSERT_STREQ(b32, "CPNMU==="); free(b32); b32 = base32hexmem("foob", STRLEN("foob"), true); assert_se(b32); - assert_se(streq(b32, "CPNMUOG=")); + ASSERT_STREQ(b32, "CPNMUOG="); free(b32); b32 = base32hexmem("fooba", STRLEN("fooba"), true); assert_se(b32); - assert_se(streq(b32, "CPNMUOJ1")); + ASSERT_STREQ(b32, "CPNMUOJ1"); free(b32); b32 = base32hexmem("foobar", STRLEN("foobar"), true); assert_se(b32); - assert_se(streq(b32, "CPNMUOJ1E8======")); + ASSERT_STREQ(b32, "CPNMUOJ1E8======"); free(b32); b32 = base32hexmem("", STRLEN(""), false); assert_se(b32); - assert_se(streq(b32, "")); + ASSERT_STREQ(b32, ""); free(b32); b32 = base32hexmem("f", STRLEN("f"), false); assert_se(b32); - assert_se(streq(b32, "CO")); + ASSERT_STREQ(b32, "CO"); free(b32); b32 = base32hexmem("fo", STRLEN("fo"), false); assert_se(b32); - assert_se(streq(b32, "CPNG")); + ASSERT_STREQ(b32, "CPNG"); free(b32); b32 = base32hexmem("foo", STRLEN("foo"), false); assert_se(b32); - assert_se(streq(b32, "CPNMU")); + ASSERT_STREQ(b32, "CPNMU"); free(b32); b32 = base32hexmem("foob", STRLEN("foob"), false); assert_se(b32); - assert_se(streq(b32, "CPNMUOG")); + ASSERT_STREQ(b32, "CPNMUOG"); free(b32); b32 = base32hexmem("fooba", STRLEN("fooba"), false); assert_se(b32); - assert_se(streq(b32, "CPNMUOJ1")); + ASSERT_STREQ(b32, "CPNMUOJ1"); free(b32); b32 = base32hexmem("foobar", STRLEN("foobar"), false); assert_se(b32); - assert_se(streq(b32, "CPNMUOJ1E8")); + ASSERT_STREQ(b32, "CPNMUOJ1E8"); free(b32); } @@ -212,7 +212,7 @@ static void test_unbase32hexmem_one(const char *hex, bool padding, int retval, c char *str; str = strndupa_safe(mem, len); - assert_se(streq(str, ans)); + ASSERT_STREQ(str, ans); } } @@ -268,31 +268,31 @@ TEST(base64mem) { char *b64; assert_se(base64mem("", STRLEN(""), &b64) == 0); - assert_se(streq(b64, "")); + ASSERT_STREQ(b64, ""); free(b64); assert_se(base64mem("f", STRLEN("f"), &b64) == 4); - assert_se(streq(b64, "Zg==")); + ASSERT_STREQ(b64, "Zg=="); free(b64); assert_se(base64mem("fo", STRLEN("fo"), &b64) == 4); - assert_se(streq(b64, "Zm8=")); + ASSERT_STREQ(b64, "Zm8="); free(b64); assert_se(base64mem("foo", STRLEN("foo"), &b64) == 4); - assert_se(streq(b64, "Zm9v")); + ASSERT_STREQ(b64, "Zm9v"); free(b64); assert_se(base64mem("foob", STRLEN("foob"), &b64) == 8); - assert_se(streq(b64, "Zm9vYg==")); + ASSERT_STREQ(b64, "Zm9vYg=="); free(b64); assert_se(base64mem("fooba", STRLEN("fooba"), &b64) == 8); - assert_se(streq(b64, "Zm9vYmE=")); + ASSERT_STREQ(b64, "Zm9vYmE="); free(b64); assert_se(base64mem("foobar", STRLEN("foobar"), &b64) == 8); - assert_se(streq(b64, "Zm9vYmFy")); + ASSERT_STREQ(b64, "Zm9vYmFy"); free(b64); } @@ -318,7 +318,7 @@ TEST(base64mem_linebreak) { assert_se(encoded); assert_se((size_t) l == strlen(encoded)); - assert_se(unbase64mem(encoded, SIZE_MAX, &decoded, &decoded_size) >= 0); + assert_se(unbase64mem(encoded, &decoded, &decoded_size) >= 0); assert_se(decoded_size == n); assert_se(memcmp(data, decoded, n) == 0); @@ -341,7 +341,7 @@ static void test_base64_append_one(char **buf, size_t *len, const char *in, cons assert_se(new_len >= 0); log_debug("base64_append_one(\"%s\")\nresult:\n%s\nexpected:\n%s", in, strnull(*buf), strnull(expected)); assert_se((size_t) new_len == strlen_ptr(*buf)); - assert_se(streq_ptr(*buf, expected)); + ASSERT_STREQ(*buf, expected); *len = new_len; } @@ -452,7 +452,7 @@ static void test_unbase64mem_one(const char *input, const char *output, int ret) _cleanup_free_ void *buffer = NULL; size_t size = 0; - assert_se(unbase64mem(input, SIZE_MAX, &buffer, &size) == ret); + assert_se(unbase64mem(input, &buffer, &size) == ret); if (ret >= 0) { assert_se(size == strlen(output)); assert_se(memcmp(buffer, output, size) == 0); @@ -533,12 +533,12 @@ TEST(base64withwithouturl) { size_t size; /* This is regular base64 */ - assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0); + assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g/xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5+p8e6itqrIwzecu98+rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0); assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0); buffer = mfree(buffer); /* This is the same but in base64url */ - assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", SIZE_MAX, &buffer, &size) >= 0); + assert_se(unbase64mem("zKFyIq7aZn4EpuCCmpcF9jPgD8JFE1g_xfT0Mas8X4M0WycyigRsQ4IH4yysufus0AORQsuk3oeGhRC7t1tLyKD0Ih0VcYedv5-p8e6itqrIwzecu98-rNyUVDhWBzS0PMwxEw==", &buffer, &size) >= 0); assert_se(memcmp_nn(plaintext, sizeof(plaintext), buffer, size) == 0); /* Hint: use xxd -i to generate the static C array from some data, and basenc --base64 + basenc diff --git a/src/test/test-hmac.c b/src/test/test-hmac.c index 1b788b1..28f7ab9 100644 --- a/src/test/test-hmac.c +++ b/src/test/test-hmac.c @@ -19,49 +19,49 @@ TEST(hmac) { "", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad")); + ASSERT_STREQ(hex_result, "cadd5e42114351181f3abff477641d88efb57d2b5641a1e5c6d623363a6d3bad"); hex_result = mfree(hex_result); hmac_sha256_by_string("waldo", "baldohaldo", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda")); + ASSERT_STREQ(hex_result, "c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda"); hex_result = mfree(hex_result); hmac_sha256_by_string("waldo", "baldo haldo", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69")); + ASSERT_STREQ(hex_result, "4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69"); hex_result = mfree(hex_result); hmac_sha256_by_string("waldo", "baldo 4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69 haldo", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "039f3df430b19753ffb493e5b90708f75c5210b63c6bcbef3374eb3f0a3f97f7")); + ASSERT_STREQ(hex_result, "039f3df430b19753ffb493e5b90708f75c5210b63c6bcbef3374eb3f0a3f97f7"); hex_result = mfree(hex_result); hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", "baldo haldo", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "c4cfaf48077cbb0bbd177a09e59ec4c248f4ca771503410f5b54b98d88d2f47b")); + ASSERT_STREQ(hex_result, "c4cfaf48077cbb0bbd177a09e59ec4c248f4ca771503410f5b54b98d88d2f47b"); hex_result = mfree(hex_result); hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69", "supercalifragilisticexpialidocious", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "2c059e7a63c4c3b23f47966a65fd2f8a2f5d7161e2e90d78ff68866b5c375cb7")); + ASSERT_STREQ(hex_result, "2c059e7a63c4c3b23f47966a65fd2f8a2f5d7161e2e90d78ff68866b5c375cb7"); hex_result = mfree(hex_result); hmac_sha256_by_string("4e8974ad6c08b98cc2519cd1e27aa7195769fcf86db1dd7ceaab4d44c490ad69c47ad5031ba21605e52c6ca68090d66a2dd5ccf84efa4bace15361a8cba63cda", "supercalifragilisticexpialidocious", result); hex_result = hexmem(result, sizeof(result)); - assert_se(streq_ptr(hex_result, "1dd1d1d45b9d9f9673dc9983c968c46ff3168e03cfeb4156a219eba1af4cff5f")); + ASSERT_STREQ(hex_result, "1dd1d1d45b9d9f9673dc9983c968c46ff3168e03cfeb4156a219eba1af4cff5f"); hex_result = mfree(hex_result); } diff --git a/src/test/test-hostname-setup.c b/src/test/test-hostname-setup.c index 94e5ece..2365a5e 100644 --- a/src/test/test-hostname-setup.c +++ b/src/test/test-hostname-setup.c @@ -22,28 +22,28 @@ TEST(read_etc_hostname) { /* simple hostname */ assert_se(write_string_file(path, "foo", WRITE_STRING_FILE_CREATE) == 0); assert_se(read_etc_hostname(path, &hostname) == 0); - assert_se(streq(hostname, "foo")); + ASSERT_STREQ(hostname, "foo"); hostname = mfree(hostname); /* with comment */ assert_se(write_string_file(path, "# comment\nfoo", WRITE_STRING_FILE_CREATE) == 0); assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); - assert_se(streq(hostname, "foo")); + ASSERT_STREQ(hostname, "foo"); hostname = mfree(hostname); /* with comment and extra whitespace */ assert_se(write_string_file(path, "# comment\n\n foo ", WRITE_STRING_FILE_CREATE) == 0); assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); - assert_se(streq(hostname, "foo")); + ASSERT_STREQ(hostname, "foo"); hostname = mfree(hostname); /* cleans up name */ assert_se(write_string_file(path, "!foo/bar.com", WRITE_STRING_FILE_CREATE) == 0); assert_se(read_etc_hostname(path, &hostname) == 0); assert_se(hostname); - assert_se(streq(hostname, "foobar.com")); + ASSERT_STREQ(hostname, "foobar.com"); hostname = mfree(hostname); /* no value set */ diff --git a/src/test/test-hostname-util.c b/src/test/test-hostname-util.c index 77e9a19..a7eccf8 100644 --- a/src/test/test-hostname-util.c +++ b/src/test/test-hostname-util.c @@ -50,27 +50,27 @@ TEST(hostname_cleanup) { char *s; s = strdupa_safe("foobar"); - assert_se(streq(hostname_cleanup(s), "foobar")); + ASSERT_STREQ(hostname_cleanup(s), "foobar"); s = strdupa_safe("foobar.com"); - assert_se(streq(hostname_cleanup(s), "foobar.com")); + ASSERT_STREQ(hostname_cleanup(s), "foobar.com"); s = strdupa_safe("foobar.com."); - assert_se(streq(hostname_cleanup(s), "foobar.com")); + ASSERT_STREQ(hostname_cleanup(s), "foobar.com"); s = strdupa_safe("foo-bar.-com-."); - assert_se(streq(hostname_cleanup(s), "foo-bar.com")); + ASSERT_STREQ(hostname_cleanup(s), "foo-bar.com"); s = strdupa_safe("foo-bar-.-com-."); - assert_se(streq(hostname_cleanup(s), "foo-bar--com")); + ASSERT_STREQ(hostname_cleanup(s), "foo-bar--com"); s = strdupa_safe("--foo-bar.-com"); - assert_se(streq(hostname_cleanup(s), "foo-bar.com")); + ASSERT_STREQ(hostname_cleanup(s), "foo-bar.com"); s = strdupa_safe("fooBAR"); - assert_se(streq(hostname_cleanup(s), "fooBAR")); + ASSERT_STREQ(hostname_cleanup(s), "fooBAR"); s = strdupa_safe("fooBAR.com"); - assert_se(streq(hostname_cleanup(s), "fooBAR.com")); + ASSERT_STREQ(hostname_cleanup(s), "fooBAR.com"); s = strdupa_safe("fooBAR."); - assert_se(streq(hostname_cleanup(s), "fooBAR")); + ASSERT_STREQ(hostname_cleanup(s), "fooBAR"); s = strdupa_safe("fooBAR.com."); - assert_se(streq(hostname_cleanup(s), "fooBAR.com")); + ASSERT_STREQ(hostname_cleanup(s), "fooBAR.com"); s = strdupa_safe("fööbar"); - assert_se(streq(hostname_cleanup(s), "fbar")); + ASSERT_STREQ(hostname_cleanup(s), "fbar"); s = strdupa_safe(""); assert_se(isempty(hostname_cleanup(s))); s = strdupa_safe("."); @@ -78,17 +78,17 @@ TEST(hostname_cleanup) { s = strdupa_safe(".."); assert_se(isempty(hostname_cleanup(s))); s = strdupa_safe("foobar."); - assert_se(streq(hostname_cleanup(s), "foobar")); + ASSERT_STREQ(hostname_cleanup(s), "foobar"); s = strdupa_safe(".foobar"); - assert_se(streq(hostname_cleanup(s), "foobar")); + ASSERT_STREQ(hostname_cleanup(s), "foobar"); s = strdupa_safe("foo..bar"); - assert_se(streq(hostname_cleanup(s), "foo.bar")); + ASSERT_STREQ(hostname_cleanup(s), "foo.bar"); s = strdupa_safe("foo.bar.."); - assert_se(streq(hostname_cleanup(s), "foo.bar")); + ASSERT_STREQ(hostname_cleanup(s), "foo.bar"); s = strdupa_safe("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - assert_se(streq(hostname_cleanup(s), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); + ASSERT_STREQ(hostname_cleanup(s), "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); s = strdupa_safe("xxxx........xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - assert_se(streq(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); + ASSERT_STREQ(hostname_cleanup(s), "xxxx.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); } TEST(hostname_malloc) { diff --git a/src/test/test-id128.c b/src/test/test-id128.c index ae7df27..48fdbba 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -50,19 +50,19 @@ TEST(id128) { } printf("waldi: %s\n", sd_id128_to_string(ID128_WALDI, t)); - assert_se(streq(t, STR_WALDI)); + ASSERT_STREQ(t, STR_WALDI); assert_se(asprintf(&b, SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(ID128_WALDI)) == 32); printf("waldi2: %s\n", b); - assert_se(streq(t, b)); + ASSERT_STREQ(t, b); printf("waldi3: %s\n", sd_id128_to_uuid_string(ID128_WALDI, q)); - assert_se(streq(q, UUID_WALDI)); + ASSERT_STREQ(q, UUID_WALDI); b = mfree(b); assert_se(asprintf(&b, SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(ID128_WALDI)) == 36); printf("waldi4: %s\n", b); - assert_se(streq(q, b)); + ASSERT_STREQ(q, b); assert_se(sd_id128_from_string(STR_WALDI, &id) >= 0); assert_se(sd_id128_equal(id, ID128_WALDI)); @@ -194,12 +194,12 @@ TEST(id128) { } /* Check return values */ - assert_se(sd_id128_get_app_specific(SD_ID128_ALLF, SD_ID128_NULL, &id) == -ENXIO); - assert_se(sd_id128_get_app_specific(SD_ID128_NULL, SD_ID128_ALLF, &id) == 0); + ASSERT_RETURN_EXPECTED_SE(sd_id128_get_app_specific(SD_ID128_ALLF, SD_ID128_NULL, &id) == -ENXIO); + ASSERT_RETURN_EXPECTED_SE(sd_id128_get_app_specific(SD_ID128_NULL, SD_ID128_ALLF, &id) == 0); } TEST(sd_id128_get_invocation) { - sd_id128_t id; + sd_id128_t id = SD_ID128_NULL; int r; /* Query the invocation ID */ @@ -208,6 +208,36 @@ TEST(sd_id128_get_invocation) { log_warning_errno(r, "Failed to get invocation ID, ignoring: %m"); else log_info("Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id)); + + sd_id128_t appid = SD_ID128_NULL; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,04), &appid); + if (r < 0) + log_warning_errno(r, "Failed to get invocation ID, ignoring: %m"); + else { + assert(!sd_id128_equal(id, appid)); + log_info("Per-App Invocation ID: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid)); + } + + sd_id128_t appid2 = SD_ID128_NULL; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,05), &appid2); /* slightly different appid */ + if (r < 0) + log_warning_errno(r, "Failed to get invocation ID, ignoring: %m"); + else { + assert(!sd_id128_equal(id, appid2)); + assert(!sd_id128_equal(appid, appid2)); + log_info("Per-App Invocation ID 2: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid2)); + } + + sd_id128_t appid3 = SD_ID128_NULL; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(59,36,e9,92,fd,11,42,fe,87,c9,e9,b5,6c,9e,4f,04), &appid3); /* same appid as before */ + if (r < 0) + log_warning_errno(r, "Failed to get invocation ID, ignoring: %m"); + else { + assert(!sd_id128_equal(id, appid3)); + assert(sd_id128_equal(appid, appid3)); + assert(!sd_id128_equal(appid2, appid3)); + log_info("Per-App Invocation ID 3: " SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(appid3)); + } } TEST(benchmark_sd_id128_get_machine_app_specific) { diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index d9fe556..12fc10a 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -79,6 +79,7 @@ TEST_RET(test_image_policy_to_string) { test_policy(&image_policy_sysext, "sysext"); test_policy(&image_policy_sysext_strict, "sysext-strict"); test_policy(&image_policy_confext, "confext"); + test_policy(&image_policy_confext_strict, "confext-strict"); test_policy(&image_policy_container, "container"); test_policy(&image_policy_host, "host"); test_policy(&image_policy_service, "service"); @@ -129,4 +130,36 @@ TEST(extend) { assert_se(partition_policy_flags_extend(PARTITION_POLICY_GROWFS_ON) == (PARTITION_POLICY_GROWFS_ON|_PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK)); } +static void test_policy_intersect_one(const char *a, const char *b, const char *c) { + _cleanup_(image_policy_freep) ImagePolicy *x = NULL, *y = NULL, *z = NULL, *t = NULL; + + assert_se(image_policy_from_string(a, &x) >= 0); + assert_se(image_policy_from_string(b, &y) >= 0); + assert_se(image_policy_from_string(c, &z) >= 0); + + assert_se(image_policy_intersect(x, y, &t) >= 0); + + _cleanup_free_ char *s1 = NULL, *s2 = NULL, *s3 = NULL, *s4 = NULL; + assert_se(image_policy_to_string(x, false, &s1) >= 0); + assert_se(image_policy_to_string(y, false, &s2) >= 0); + assert_se(image_policy_to_string(z, false, &s3) >= 0); + assert_se(image_policy_to_string(t, false, &s4) >= 0); + + log_info("%s ^ %s → %s vs. %s", s1, s2, s3, s4); + + assert_se(image_policy_equivalent(z, t) > 0); +} + +TEST(image_policy_intersect) { + test_policy_intersect_one("", "", ""); + test_policy_intersect_one("-", "-", "-"); + test_policy_intersect_one("*", "*", "*"); + test_policy_intersect_one("~", "~", "~"); + test_policy_intersect_one("root=verity+signed", "root=signed+verity", "root=verity+signed"); + test_policy_intersect_one("root=verity+signed", "root=signed", "root=signed"); + test_policy_intersect_one("root=verity+signed", "root=verity", "root=verity"); + test_policy_intersect_one("root=open", "root=verity", "root=verity"); + test_policy_intersect_one("root=open", "=verity+ignore", "root=verity+ignore:=ignore"); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-import-util.c b/src/test/test-import-util.c index 7930fe5..cc7b139 100644 --- a/src/test/test-import-util.c +++ b/src/test/test-import-util.c @@ -10,7 +10,7 @@ static void test_import_url_last_component_one(const char *input, const char *ou _cleanup_free_ char *s = NULL; assert_se(import_url_last_component(input, &s) == ret); - assert_se(streq_ptr(output, s)); + ASSERT_STREQ(output, s); } TEST(import_url_last_component) { @@ -37,7 +37,7 @@ static void test_import_url_change_suffix_one(const char *input, size_t n, const _cleanup_free_ char *s = NULL; assert_se(import_url_change_suffix(input, n, suffix, &s) == ret); - assert_se(streq_ptr(output, s)); + ASSERT_STREQ(output, s); } TEST(import_url_change_suffix) { diff --git a/src/test/test-in-addr-prefix-util.c b/src/test/test-in-addr-prefix-util.c index 661ca8f..2b44bab 100644 --- a/src/test/test-in-addr-prefix-util.c +++ b/src/test/test-in-addr-prefix-util.c @@ -12,8 +12,8 @@ static void test_in_addr_prefix_to_string_one(int f, const char *addr, unsigned printf("%s: %s/%u == %s\n", __func__, addr, prefixlen, r); assert_se(startswith(r, addr)); - assert_se(streq(r, IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen))); - assert_se(streq(IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen), r)); + ASSERT_STREQ(r, IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen)); + ASSERT_STREQ(IN_ADDR_PREFIX_TO_STRING(f, &ua, prefixlen), r); } TEST(in_addr_to_string_prefix) { @@ -35,8 +35,8 @@ static void test_config_parse_in_addr_prefixes_one(int family, const union in_ad assert_se(config_parse_in_addr_prefixes("unit", "filename", 1, "Service", 1, "IPAddressAllow", 0, str, prefixes, NULL) >= 0); - assert_se(streq(str, IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen))); - assert_se(streq(IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen), str)); + ASSERT_STREQ(str, IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen)); + ASSERT_STREQ(IN_ADDR_PREFIX_TO_STRING(family, addr, prefixlen), str); } static void test_config_parse_in_addr_prefixes(Set **ret) { diff --git a/src/test/test-in-addr-util.c b/src/test/test-in-addr-util.c index 93ab1c5..fcc9a0a 100644 --- a/src/test/test-in-addr-util.c +++ b/src/test/test-in-addr-util.c @@ -81,7 +81,7 @@ static void test_in_addr_prefix_to_string_valid(int family, const char *p) { log_info("%s: %s", __func__, p); assert_se(in_addr_prefix_from_string(p, family, &u, &l) >= 0); - assert_se(streq(p, IN_ADDR_PREFIX_TO_STRING(family, &u, l))); + ASSERT_STREQ(p, IN_ADDR_PREFIX_TO_STRING(family, &u, l)); } static void test_in_addr_prefix_to_string_unoptimized(int family, const char *p) { @@ -97,7 +97,7 @@ static void test_in_addr_prefix_to_string_unoptimized(int family, const char *p) const char *str2 = IN_ADDR_PREFIX_TO_STRING(family, &u2, len2); assert_se(str2); - assert_se(streq(str1, str2)); + ASSERT_STREQ(str1, str2); assert_se(len1 == len2); assert_se(in_addr_equal(family, &u1, &u2) > 0); } @@ -340,9 +340,9 @@ static void test_in_addr_to_string_one(int f, const char *addr) { assert_se(in_addr_from_string(f, addr, &ua) >= 0); assert_se(in_addr_to_string(f, &ua, &r) >= 0); printf("%s: %s == %s\n", __func__, addr, r); - assert_se(streq(addr, r)); + ASSERT_STREQ(addr, r); - assert_se(streq(r, IN_ADDR_TO_STRING(f, &ua))); + ASSERT_STREQ(r, IN_ADDR_TO_STRING(f, &ua)); } TEST(in_addr_to_string) { @@ -391,7 +391,7 @@ TEST(in_addr_prefixlen_to_netmask) { assert_se(in_addr_prefixlen_to_netmask(AF_INET, &addr, prefixlen) >= 0); assert_se(in_addr_to_string(AF_INET, &addr, &result) >= 0); printf("test_in_addr_prefixlen_to_netmask: %s == %s\n", ipv4_netmasks[prefixlen], result); - assert_se(streq(ipv4_netmasks[prefixlen], result)); + ASSERT_STREQ(ipv4_netmasks[prefixlen], result); } for (unsigned char prefixlen = 0; prefixlen <= 128; prefixlen++) { @@ -401,7 +401,58 @@ TEST(in_addr_prefixlen_to_netmask) { assert_se(in_addr_to_string(AF_INET6, &addr, &result) >= 0); printf("test_in_addr_prefixlen_to_netmask: %s\n", result); if (ipv6_netmasks[prefixlen]) - assert_se(streq(ipv6_netmasks[prefixlen], result)); + ASSERT_STREQ(ipv6_netmasks[prefixlen], result); + } +} + +static void in_addr_prefix_covers_full_one(const char *prefix, const char *address, int expected) { + union in_addr_union p, a; + unsigned char plen, alen; + int family, r; + + assert_se(in_addr_prefix_from_string_auto(prefix, &family, &p, &plen) >= 0); + assert_se(in_addr_prefix_from_string(address, family, &a, &alen) >= 0); + r = in_addr_prefix_covers_full(family, &p, plen, &a, alen); + if (r != expected) + log_error("in_addr_prefix_covers_full(%s, %s)=%i (expected=%i)", prefix, address, r, expected); + assert_se(r == expected); +} + +TEST(in_addr_prefix_covers_full) { + /* From issue #32715. */ + in_addr_prefix_covers_full_one("192.168.235.129/32", "192.168.0.128/32", 0); + in_addr_prefix_covers_full_one("192.168.235.130/32", "192.168.0.128/32", 0); + in_addr_prefix_covers_full_one("169.254.0.0/17", "192.168.0.128/32", 0); + in_addr_prefix_covers_full_one("169.254.128.0/17", "192.168.0.128/32", 0); + in_addr_prefix_covers_full_one("0.0.0.0/1", "192.168.0.128/32", 0); + in_addr_prefix_covers_full_one("128.0.0.0/1", "192.168.0.128/32", 1); + in_addr_prefix_covers_full_one("0.0.0.0/0", "192.168.0.128/32", 1); + + for (unsigned i = 0; i <= 32; i++) { + _cleanup_free_ char *prefix = NULL; + + assert_se(asprintf(&prefix, "192.168.0.128/%u", i) >= 0); + + for (unsigned j = 0; j <= 32; j++) { + _cleanup_free_ char *address = NULL; + + assert_se(asprintf(&address, "192.168.0.128/%u", j) >= 0); + in_addr_prefix_covers_full_one(prefix, address, i <= j); + } + } + + for (unsigned i = 0; i <= 32; i++) { + _cleanup_free_ char *prefix = NULL; + + assert_se(asprintf(&prefix, "192.168.235.129/%u", i) >= 0); + in_addr_prefix_covers_full_one(prefix, "192.168.0.128/32", i <= 16); + } + + for (unsigned i = 0; i <= 128; i++) { + _cleanup_free_ char *prefix = NULL; + + assert_se(asprintf(&prefix, "dead:beef::/%u", i) >= 0); + in_addr_prefix_covers_full_one(prefix, "dead:0:beef::1/128", i <= 16); } } diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c index efd75b2..1e7ed27 100644 --- a/src/test/test-install-root.c +++ b/src/test/test-install-root.c @@ -60,9 +60,9 @@ TEST(basic_mask_and_enable) { assert_se(unit_file_mask(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/dev/null")); + ASSERT_STREQ(changes[0].source, "/dev/null"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -81,16 +81,16 @@ TEST(basic_mask_and_enable) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("a.service"), &changes, &n_changes) == 1); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/a.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -109,7 +109,7 @@ TEST(basic_mask_and_enable) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -128,9 +128,9 @@ TEST(basic_mask_and_enable) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("d.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/a.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/a.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -145,10 +145,10 @@ TEST(basic_mask_and_enable) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/a.service")); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/a.service"); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -186,13 +186,13 @@ TEST(basic_mask_and_enable) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("f.service"), &changes, &n_changes) == 1); assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/f.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/f.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/x.target.wants/f.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); assert_se(changes[1].type == INSTALL_CHANGE_DESTINATION_NOT_PRESENT); p = strjoina(root, "/usr/lib/systemd/system/f.service"); - assert_se(streq(changes[1].source, p)); - assert_se(streq(changes[1].path, "x.target")); + ASSERT_STREQ(changes[1].source, p); + ASSERT_STREQ(changes[1].path, "x.target"); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -200,7 +200,7 @@ TEST(basic_mask_and_enable) { } TEST(linked_units) { - const char *p, *q; + const char *p, *q, *s; UnitFileState state; InstallChange *changes = NULL; size_t n_changes = 0, i; @@ -224,6 +224,7 @@ TEST(linked_units) { p = strjoina(root, "/opt/linked.service"); assert_se(write_string_file(p, "[Install]\n" + "Alias=linked-alias.service\n" "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0); p = strjoina(root, "/opt/linked2.service"); @@ -254,9 +255,9 @@ TEST(linked_units) { assert_se(unit_file_link(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/opt/linked.service")); + ASSERT_STREQ(changes[0].source, "/opt/linked.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -267,7 +268,7 @@ TEST(linked_units) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -275,31 +276,41 @@ TEST(linked_units) { /* Now, let's not just link it, but also enable it */ assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("/opt/linked.service"), &changes, &n_changes) >= 0); - assert_se(n_changes == 2); + assert_se(n_changes == 3); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/linked.service"); q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + s = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked-alias.service"); for (i = 0 ; i < n_changes; i++) { assert_se(changes[i].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[i].source, "/opt/linked.service")); + + if (s && streq(changes[i].path, s)) + /* The alias symlink should point within the search path. */ + ASSERT_STREQ(changes[i].source, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + else + ASSERT_STREQ(changes[i].source, "/opt/linked.service"); if (p && streq(changes[i].path, p)) p = NULL; else if (q && streq(changes[i].path, q)) q = NULL; + else if (s && streq(changes[i].path, s)) + s = NULL; else assert_not_reached(); } - assert_se(!p && !q); + assert_se(!p && !q && !s); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked.service", &state) >= 0 && state == UNIT_FILE_ENABLED); + assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "linked-alias.service", &state) >= 0 && state == UNIT_FILE_ALIAS); /* And let's unlink it again */ assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("linked.service"), &changes, &n_changes) >= 0); - assert_se(n_changes == 2); + assert_se(n_changes == 3); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/linked.service"); q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked.service"); + s = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked-alias.service"); for (i = 0; i < n_changes; i++) { assert_se(changes[i].type == INSTALL_CHANGE_UNLINK); @@ -307,10 +318,12 @@ TEST(linked_units) { p = NULL; else if (q && streq(changes[i].path, q)) q = NULL; + else if (s && streq(changes[i].path, s)) + s = NULL; else assert_not_reached(); } - assert_se(!p && !q); + assert_se(!p && !q && !s); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -322,7 +335,7 @@ TEST(linked_units) { q = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/linked2.service"); for (i = 0 ; i < n_changes; i++) { assert_se(changes[i].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[i].source, "/opt/linked2.service")); + ASSERT_STREQ(changes[i].source, "/opt/linked2.service"); if (p && streq(changes[i].path, p)) p = NULL; @@ -340,7 +353,7 @@ TEST(linked_units) { assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(startswith(changes[0].path, root)); assert_se(endswith(changes[0].path, "linked3.service")); - assert_se(streq(changes[0].source, "/opt/linked3.service")); + ASSERT_STREQ(changes[0].source, "/opt/linked3.service"); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; } @@ -362,7 +375,7 @@ TEST(default) { assert_se(unit_file_set_default(RUNTIME_SCOPE_SYSTEM, 0, root, "idontexist.target", &changes, &n_changes) == -ENOENT); assert_se(n_changes == 1); assert_se(changes[0].type == -ENOENT); - assert_se(streq_ptr(changes[0].path, "idontexist.target")); + ASSERT_STREQ(changes[0].path, "idontexist.target"); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -371,14 +384,14 @@ TEST(default) { assert_se(unit_file_set_default(RUNTIME_SCOPE_SYSTEM, 0, root, "test-default.target", &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/test-default-real.target")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/test-default-real.target"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR "/" SPECIAL_DEFAULT_TARGET); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_get_default(RUNTIME_SCOPE_SYSTEM, root, &def) >= 0); - assert_se(streq_ptr(def, "test-default-real.target")); + ASSERT_STREQ(def, "test-default-real.target"); } TEST(add_dependency) { @@ -401,9 +414,9 @@ TEST(add_dependency) { assert_se(unit_file_add_dependency(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("add-dependency-test-service.service"), "add-dependency-test-target.target", UNIT_WANTS, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/real-add-dependency-test-service.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/real-add-dependency-test-service.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/real-add-dependency-test-target.target.wants/real-add-dependency-test-service.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; } @@ -442,9 +455,9 @@ TEST(template_enable) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/template@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@def.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -458,7 +471,7 @@ TEST(template_enable) { assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -473,9 +486,9 @@ TEST(template_enable) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/template@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@foo.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -489,7 +502,7 @@ TEST(template_enable) { assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template@foo.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -506,9 +519,9 @@ TEST(template_enable) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("template-symlink@quux.service"), &changes, &n_changes) >= 0); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/template@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/template@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/template@quux.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -552,9 +565,9 @@ TEST(indirect) { assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("indirectc.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/indirectb.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/indirectb.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/indirectb.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -566,7 +579,7 @@ TEST(indirect) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/indirectb.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; } @@ -614,9 +627,9 @@ TEST(preset_and_list) { assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/preset-yes.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/preset-yes.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/preset-yes.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -628,7 +641,7 @@ TEST(preset_and_list) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/preset-yes.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -654,8 +667,8 @@ TEST(preset_and_list) { for (i = 0; i < n_changes; i++) { if (changes[i].type == INSTALL_CHANGE_SYMLINK) { - assert_se(streq(changes[i].source, "/usr/lib/systemd/system/preset-yes.service")); - assert_se(streq(changes[i].path, p)); + ASSERT_STREQ(changes[i].source, "/usr/lib/systemd/system/preset-yes.service"); + ASSERT_STREQ(changes[i].path, p); } else assert_se(changes[i].type == INSTALL_CHANGE_UNLINK); } @@ -722,7 +735,7 @@ TEST(revert) { assert_se(unit_file_revert(RUNTIME_SCOPE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -733,11 +746,11 @@ TEST(revert) { assert_se(unit_file_revert(RUNTIME_SCOPE_SYSTEM, root, STRV_MAKE("xx.service"), &changes, &n_changes) >= 0); assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/xx.service.d"); assert_se(changes[1].type == INSTALL_CHANGE_UNLINK); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; } @@ -773,9 +786,9 @@ TEST(preset_order) { assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("prefix-1.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/prefix-1.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/prefix-1.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/prefix-1.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -882,12 +895,12 @@ TEST(with_dropin) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-1.service")); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-1.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-1.service"); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/with-dropin-1.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-1.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-1.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -896,12 +909,12 @@ TEST(with_dropin) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service")); - assert_se(streq(changes[1].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service")); + ASSERT_STREQ(changes[0].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service"); + ASSERT_STREQ(changes[1].source, SYSTEM_CONFIG_UNIT_DIR"/with-dropin-2.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-2.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -910,12 +923,12 @@ TEST(with_dropin) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-3.service")); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-3.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-3.service"); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/with-dropin-3.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-3.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-3.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -924,12 +937,12 @@ TEST(with_dropin) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-4a.service")); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-4b.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-4a.service"); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/with-dropin-4b.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-4a.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-4b.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -991,12 +1004,12 @@ TEST(with_dropin_template) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-1@.service")); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-1@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-1@.service"); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/with-dropin-1@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-1@instance-1.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-1@instance-1.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -1004,30 +1017,30 @@ TEST(with_dropin_template) { assert_se(n_changes == 2); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); assert_se(changes[1].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service")); - assert_se(streq(changes[1].source, "/usr/lib/systemd/system/with-dropin-2@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service"); + ASSERT_STREQ(changes[1].source, "/usr/lib/systemd/system/with-dropin-2@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2@instance-1.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/graphical.target.wants/with-dropin-2@instance-1.service"); - assert_se(streq(changes[1].path, p)); + ASSERT_STREQ(changes[1].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-2@instance-2.service"), &changes, &n_changes) == 1); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-2@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-2@instance-2.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("with-dropin-3@.service"), &changes, &n_changes) == 1); assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); - assert_se(streq(changes[0].source, "/usr/lib/systemd/system/with-dropin-3@.service")); + ASSERT_STREQ(changes[0].source, "/usr/lib/systemd/system/with-dropin-3@.service"); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/with-dropin-3@instance-2.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -1067,7 +1080,7 @@ TEST(preset_multiple_instances) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_SYMLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/foo@bar0.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -1075,7 +1088,7 @@ TEST(preset_multiple_instances) { assert_se(n_changes == 1); assert_se(changes[0].type == INSTALL_CHANGE_UNLINK); p = strjoina(root, SYSTEM_CONFIG_UNIT_DIR"/multi-user.target.wants/foo@bar0.service"); - assert_se(streq(changes[0].path, p)); + ASSERT_STREQ(changes[0].path, p); install_changes_free(changes, n_changes); changes = NULL; n_changes = 0; @@ -1118,7 +1131,7 @@ static void verify_one( * RequiredBy= settings, and less so for Alias=. The only case where it should happen is when we have * an Alias=alias@.service an instantiated template template@instance. In that case the instance name * should be propagated into the alias as alias@instance. */ - assert_se(streq_ptr(alias2, updated_name)); + ASSERT_STREQ(alias2, updated_name); } TEST(verify_alias) { diff --git a/src/test/test-iovec-util.c b/src/test/test-iovec-util.c new file mode 100644 index 0000000..e7cc6e4 --- /dev/null +++ b/src/test/test-iovec-util.c @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "iovec-util.h" +#include "tests.h" + +TEST(iovec_memcmp) { + struct iovec iov1 = CONST_IOVEC_MAKE_STRING("abcdef"), iov2 = IOVEC_MAKE_STRING("bcdefg"), empty = {}; + + struct iovec iov1_truncated = iov1; + iov1_truncated.iov_len /= 2; + + assert_se(iovec_memcmp(NULL, NULL) == 0); + assert_se(iovec_memcmp(&iov1, &iov1) == 0); + assert_se(iovec_memcmp(&iov2, &iov2) == 0); + assert_se(iovec_memcmp(&empty, &empty) == 0); + assert_se(iovec_memcmp(&iov1_truncated, &iov1_truncated) == 0); + assert_se(iovec_memcmp(&empty, NULL) == 0); + assert_se(iovec_memcmp(NULL, &empty) == 0); + assert_se(iovec_memcmp(&iov1, &iov2) < 0); + assert_se(iovec_memcmp(&iov2, &iov1) > 0); + assert_se(iovec_memcmp(&iov1, &empty) > 0); + assert_se(iovec_memcmp(&empty, &iov1) < 0); + assert_se(iovec_memcmp(&iov2, &empty) > 0); + assert_se(iovec_memcmp(&empty, &iov2) < 0); + assert_se(iovec_memcmp(&iov1_truncated, &empty) > 0); + assert_se(iovec_memcmp(&empty, &iov1_truncated) < 0); + assert_se(iovec_memcmp(&iov1, &iov1_truncated) > 0); + assert_se(iovec_memcmp(&iov1_truncated, &iov1) < 0); + assert_se(iovec_memcmp(&iov2, &iov1_truncated) > 0); + assert_se(iovec_memcmp(&iov1_truncated, &iov2) < 0); + + _cleanup_(iovec_done) struct iovec copy = {}; + + assert_se(iovec_memdup(&iov1, ©)); + assert_se(iovec_memcmp(&iov1, ©) == 0); +} + +TEST(iovec_set_and_valid) { + struct iovec empty = {}, + filled = CONST_IOVEC_MAKE_STRING("waldo"), + half = { .iov_base = (char*) "piff", .iov_len = 0 }, + invalid = { .iov_base = NULL, .iov_len = 47 }; + + assert_se(!iovec_is_set(NULL)); + assert_se(!iovec_is_set(&empty)); + assert_se(iovec_is_set(&filled)); + assert_se(!iovec_is_set(&half)); + assert_se(!iovec_is_set(&invalid)); + + assert_se(iovec_is_valid(NULL)); + assert_se(iovec_is_valid(&empty)); + assert_se(iovec_is_valid(&filled)); + assert_se(iovec_is_valid(&half)); + assert_se(!iovec_is_valid(&invalid)); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-ip-protocol-list.c b/src/test/test-ip-protocol-list.c index dfff015..9d0403c 100644 --- a/src/test/test-ip-protocol-list.c +++ b/src/test/test-ip-protocol-list.c @@ -27,8 +27,8 @@ static void test_int_fail(int i, int error) { } static void test_str(const char *s) { - assert_se(streq(ip_protocol_to_name(ip_protocol_from_name(s)), s)); - assert_se(streq(ip_protocol_to_name(parse_ip_protocol(s)), s)); + ASSERT_STREQ(ip_protocol_to_name(ip_protocol_from_name(s)), s); + ASSERT_STREQ(ip_protocol_to_name(parse_ip_protocol(s)), s); } static void test_str_fail(const char *s, int error) { diff --git a/src/test/test-json.c b/src/test/test-json.c index c120a70..7d9a785 100644 --- a/src/test/test-json.c +++ b/src/test/test-json.c @@ -6,9 +6,11 @@ #include "escape.h" #include "fd-util.h" #include "fileio.h" +#include "iovec-util.h" #include "json-internal.h" #include "json.h" #include "math-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "tests.h" @@ -42,7 +44,7 @@ static void test_tokenizer_one(const char *data, ...) { const char *nn; nn = va_arg(ap, const char *); - assert_se(streq_ptr(nn, str)); + ASSERT_STREQ(nn, str); } else if (t == JSON_TOKEN_REAL) { double d; @@ -104,6 +106,17 @@ static void test_variant_one(const char *data, Test test) { assert_se(json_variant_has_type(w, json_variant_type(v))); assert_se(json_variant_equal(v, w)); + s = mfree(s); + r = json_variant_format(w, JSON_FORMAT_CENSOR_SENSITIVE, &s); + assert_se(s); + ASSERT_STREQ(s, "\"\""); + + s = mfree(s); + r = json_variant_format(w, JSON_FORMAT_PRETTY, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + s = mfree(s); w = json_variant_unref(w); @@ -154,7 +167,7 @@ static void test_1(JsonVariant *v) { assert_se(p && json_variant_type(p) == JSON_VARIANT_STRING); /* k equals v */ - assert_se(streq(json_variant_string(p), "v")); + ASSERT_STREQ(json_variant_string(p), "v"); /* has foo */ p = json_variant_by_key(v, "foo"); @@ -321,7 +334,7 @@ TEST(build) { assert_se(json_variant_format(b, 0, &t) >= 0); log_info("GOT: %s", t); - assert_se(streq(s, t)); + ASSERT_STREQ(s, t); a = json_variant_unref(a); b = json_variant_unref(b); @@ -349,7 +362,7 @@ TEST(json_parse_file_empty) { assert_se(fopen_unlocked("/dev/null", "re", &f) >= 0); assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) == -ENODATA); - assert_se(v == NULL); + ASSERT_NULL(v); } TEST(json_parse_file_invalid) { @@ -358,7 +371,7 @@ TEST(json_parse_file_invalid) { assert_se(f = fmemopen_unlocked((void*) "kookoo", 6, "r")); assert_se(json_parse_file(f, "waldo", 0, &v, NULL, NULL) == -EINVAL); - assert_se(v == NULL); + ASSERT_NULL(v); } TEST(source) { @@ -449,7 +462,7 @@ TEST(normalize) { assert_se(!json_variant_is_normalized(v)); assert_se(json_variant_format(v, 0, &t) >= 0); - assert_se(streq(t, "{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}")); + ASSERT_STREQ(t, "{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}"); t = mfree(t); assert_se(json_build(&w, JSON_BUILD_OBJECT( @@ -460,7 +473,7 @@ TEST(normalize) { assert_se(!json_variant_is_normalized(w)); assert_se(json_variant_format(w, 0, &t) >= 0); - assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}}")); + ASSERT_STREQ(t, "{\"bar\":\"zzz\",\"foo\":{\"b\":\"x\",\"c\":\"y\",\"a\":\"z\"}}"); t = mfree(t); assert_se(json_variant_sort(&v) >= 0); @@ -468,7 +481,7 @@ TEST(normalize) { assert_se(json_variant_is_normalized(v)); assert_se(json_variant_format(v, 0, &t) >= 0); - assert_se(streq(t, "{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}")); + ASSERT_STREQ(t, "{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}"); t = mfree(t); assert_se(json_variant_normalize(&w) >= 0); @@ -476,7 +489,7 @@ TEST(normalize) { assert_se(json_variant_is_normalized(w)); assert_se(json_variant_format(w, 0, &t) >= 0); - assert_se(streq(t, "{\"bar\":\"zzz\",\"foo\":{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}}")); + ASSERT_STREQ(t, "{\"bar\":\"zzz\",\"foo\":{\"a\":\"z\",\"b\":\"x\",\"c\":\"y\"}}"); t = mfree(t); } @@ -518,7 +531,7 @@ TEST(bisect) { assert_se(json_variant_is_string(k)); z = (char[5]){ '<', c, c, '>', 0}; - assert_se(streq(json_variant_string(k), z)); + ASSERT_STREQ(json_variant_string(k), z); } } @@ -685,8 +698,8 @@ static void json_array_append_with_source_one(bool source) { assert_se(json_variant_get_source(a, &s1, &line1, &col1) >= 0); assert_se(json_variant_get_source(b, &s2, &line2, &col2) >= 0); - assert_se(streq_ptr(s1, source ? "string 1" : NULL)); - assert_se(streq_ptr(s2, source ? "string 2" : NULL)); + ASSERT_STREQ(s1, source ? "string 1" : NULL); + ASSERT_STREQ(s2, source ? "string 2" : NULL); assert_se(line1 == 1); assert_se(col1 == 2); assert_se(line2 == 3); @@ -710,8 +723,8 @@ static void json_array_append_with_source_one(bool source) { assert_se(elem = json_variant_by_index(a, 1)); assert_se(json_variant_get_source(elem, &s2, &line2, &col2) >= 0); - assert_se(streq_ptr(s1, source ? "string 2" : NULL)); - assert_se(streq_ptr(s2, source ? "string 2" : NULL)); + ASSERT_STREQ(s1, source ? "string 2" : NULL); + ASSERT_STREQ(s2, source ? "string 2" : NULL); assert_se(line1 == 3); assert_se(col1 == 5); assert_se(line2 == 3); @@ -813,4 +826,172 @@ TEST(json_dispatch) { assert_se(foobar.l == INT16_MIN); } +typedef enum mytestenum { + myfoo, mybar, mybaz, _mymax, _myinvalid = -EINVAL, +} mytestenum; + +static const char *mytestenum_table[_mymax] = { + [myfoo] = "myfoo", + [mybar] = "mybar", + [mybaz] = "mybaz", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(mytestenum, mytestenum); + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_mytestenum, mytestenum, mytestenum_from_string); + +TEST(json_dispatch_enum_define) { + + struct data { + mytestenum a, b, c, d; + } data = { + .a = _myinvalid, + .b = _myinvalid, + .c = _myinvalid, + .d = mybar, + }; + + _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; + + assert_se(json_build(&j, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("a", JSON_BUILD_STRING("mybaz")), + JSON_BUILD_PAIR("b", JSON_BUILD_STRING("mybar")), + JSON_BUILD_PAIR("c", JSON_BUILD_STRING("myfoo")), + JSON_BUILD_PAIR("d", JSON_BUILD_NULL))) >= 0); + + assert_se(json_dispatch(j, + (const JsonDispatch[]) { + { "a", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, a), 0 }, + { "b", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, b), 0 }, + { "c", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, c), 0 }, + { "d", _JSON_VARIANT_TYPE_INVALID, dispatch_mytestenum, offsetof(struct data, d), 0 }, + {}, + }, + /* flags= */ 0, + &data) >= 0); + + assert(data.a == mybaz); + assert(data.b == mybar); + assert(data.c == myfoo); + assert(data.d < 0); +} + +TEST(json_sensitive) { + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL, *b = NULL, *v = NULL; + _cleanup_free_ char *s = NULL; + int r; + + assert_se(json_build(&a, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "bar", "baz", "foo", "qux", "baz"))) >= 0); + assert_se(json_build(&b, JSON_BUILD_STRV(STRV_MAKE("foo", "bar", "baz", "qux"))) >= 0); + + json_variant_sensitive(a); + + assert_se(json_variant_format(a, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0); + ASSERT_STREQ(s, "\"\""); + s = mfree(s); + + r = json_variant_format(b, JSON_FORMAT_CENSOR_SENSITIVE, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + s = mfree(s); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + s = mfree(s); + v = json_variant_unref(v); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("b", b), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + r = json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s); + assert_se(r >= 0); + assert_se(s); + assert_se((size_t) r == strlen(s)); + s = mfree(s); + v = json_variant_unref(v); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("b", b), + JSON_BUILD_PAIR_VARIANT("a", a), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0); + ASSERT_STREQ(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"a\":\"\",\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{}}"); + s = mfree(s); + v = json_variant_unref(v); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("b", b), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR_VARIANT("a", a), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0); + ASSERT_STREQ(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"a\":\"\",\"d\":\"-9223372036854775808\",\"e\":{}}"); + s = mfree(s); + v = json_variant_unref(v); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("b", b), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR_VARIANT("a", a), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0); + ASSERT_STREQ(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"a\":\"\",\"e\":{}}"); + s = mfree(s); + v = json_variant_unref(v); + + assert_se(json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_VARIANT("b", b), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(INT64_MIN)), + JSON_BUILD_PAIR("d", JSON_BUILD_STRING("-9223372036854775808")), + JSON_BUILD_PAIR("e", JSON_BUILD_EMPTY_OBJECT), + JSON_BUILD_PAIR_VARIANT("a", a))) >= 0); + json_variant_dump(v, JSON_FORMAT_COLOR|JSON_FORMAT_PRETTY, NULL, NULL); + + assert_se(json_variant_format(v, JSON_FORMAT_CENSOR_SENSITIVE, &s) >= 0); + ASSERT_STREQ(s, "{\"b\":[\"foo\",\"bar\",\"baz\",\"qux\"],\"c\":-9223372036854775808,\"d\":\"-9223372036854775808\",\"e\":{},\"a\":\"\"}"); +} + +TEST(json_iovec) { + struct iovec iov1 = CONST_IOVEC_MAKE_STRING("üxknürz"), iov2 = CONST_IOVEC_MAKE_STRING("wuffwuffmiau"); + + _cleanup_(json_variant_unrefp) JsonVariant *j = NULL; + assert_se(json_build(&j, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("nr1", JSON_BUILD_IOVEC_BASE64(&iov1)), + JSON_BUILD_PAIR("nr2", JSON_BUILD_IOVEC_HEX(&iov2)))) >= 0); + + json_variant_dump(j, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, /* f= */ NULL, /* prefix= */ NULL); + + _cleanup_(iovec_done) struct iovec a = {}, b = {}; + assert_se(json_variant_unbase64_iovec(json_variant_by_key(j, "nr1"), &a) >= 0); + assert_se(json_variant_unhex_iovec(json_variant_by_key(j, "nr2"), &b) >= 0); + + assert_se(iovec_memcmp(&iov1, &a) == 0); + assert_se(iovec_memcmp(&iov2, &b) == 0); + assert_se(iovec_memcmp(&iov2, &a) < 0); + assert_se(iovec_memcmp(&iov1, &b) > 0); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-label.c b/src/test/test-label.c new file mode 100644 index 0000000..9d7ac18 --- /dev/null +++ b/src/test/test-label.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "errno-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "label.h" +#include "path-util.h" +#include "string-util.h" +#include "tests.h" + +static struct stat buf; +static int check_path(int dir_fd, const char *path) { + assert(path); + assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + + if (isempty(path)) + return -EINVAL; + + /* assume length of pathname is not greater than 40*/ + if (strlen(path) > 40) + return -ENAMETOOLONG; + + /* assume a case where a specific label isn't allowed */ + if (path_equal(path, "/restricted_directory")) + return -EACCES; + return 0; +} + +static int pre_labelling_func(int dir_fd, const char *path, mode_t mode) { + int r; + + assert(mode != MODE_INVALID); + r = check_path(dir_fd, path); + if (r < 0) + return log_error_errno(r, "Error in pathname =>: %m"); + + return 0; +} + +static int post_labelling_func(int dir_fd, const char *path) { + int r; + + /* assume label policies that restrict certain labels */ + r = check_path(dir_fd, path); + if (r < 0) + return log_error_errno(r, "Error in pathname =>: %m"); + + /* Set file data to buf */ + r = RET_NERRNO(fstatat(dir_fd, path, &buf, 0)); + if (r < 0) + return log_error_errno(r, "Error in getting file status =>: %m"); + + return 0; /* on success */ +} + +static int get_dir_fd(const char *dir_path, mode_t mode) { + /* create a new directory and return its descriptor*/ + int dir_fd = -EBADF; + + assert(dir_path); + dir_fd = RET_NERRNO(open_mkdir_at(AT_FDCWD, dir_path, O_CLOEXEC, mode)); + if (dir_fd < 0) + return log_error_errno(dir_fd, "Error occurred while opening directory =>: %m"); + + return dir_fd; +} + +static int labelling_op(int dir_fd, const char *text, const char *path, mode_t mode) { + /* Write some content into the file */ + ssize_t count; + _cleanup_close_ int write_fd = -EBADF; + int r; + + assert(text); + assert(mode != MODE_INVALID); + r = check_path(dir_fd, path); + if (r < 0) + return log_error_errno(r, "Error in pathname =>: %m"); + + /* Open the file within the directory for writing*/ + write_fd = RET_NERRNO(openat(dir_fd, path, O_CLOEXEC|O_WRONLY|O_TRUNC|O_CREAT, 0644)); + if (write_fd < 0) + return log_error_errno(write_fd, "Error in opening directory for writing =>: %m"); + + /* Write data to the file*/ + count = RET_NERRNO(write(write_fd, text, strlen(text))); + if (count < 0) + return log_error_errno(count, "Error occurred while opening file for writing =>: %m"); + return 0; +} + +TEST(label_ops_set) { + static const LabelOps test_label_ops = { + .pre = NULL, + .post = NULL, + }; + + label_ops_reset(); + assert_se(label_ops_set(&test_label_ops) == 0); + /* attempt to reset label_ops when already set */ + assert_se(label_ops_set(&test_label_ops) == -EBUSY); +} + +TEST(label_ops_pre) { + _cleanup_close_ int fd; + static const LabelOps test_label_ops = { + .pre = pre_labelling_func, + .post = NULL, + }; + + label_ops_reset(); + label_ops_set(&test_label_ops); + fd = get_dir_fd("file1.txt", 0755); + assert_se(label_ops_pre(fd, "file1.txt", 0644) == 0); + assert_se(label_ops_pre(fd, "/restricted_directory", 0644) == -EACCES); + assert_se(label_ops_pre(fd, "", 0700) == -EINVAL); + assert_se(label_ops_pre(fd, "/tmp", 0700) == 0); + assert_se(label_ops_pre(fd, "wekrgoierhgoierhqgherhgwklegnlweehgorwfkryrit", 0644) == -ENAMETOOLONG); +} + +TEST(label_ops_post) { + _cleanup_close_ int fd = -EBADF; + const char *text1, *text2; + static const LabelOps test_label_ops = { + .pre = NULL, + .post = post_labelling_func, + }; + + label_ops_reset(); + label_ops_set(&test_label_ops); + + /* Open directory */ + fd = RET_NERRNO(get_dir_fd("label_test_dir", 0755)); + text1 = "Add initial texts to file for testing label operations to file1\n"; + + assert(labelling_op(fd, text1, "file1.txt", 0644) == 0); + assert_se(label_ops_post(fd, "file1.txt") == 0); + assert_se(strlen(text1) == (size_t)buf.st_size); + text2 = "Add text2 data to file2\n"; + + assert(labelling_op(fd, text2, "file2.txt", 0644) == 0); + assert_se(label_ops_post(fd, "file2.txt") == 0); + assert_se(strlen(text2) == (size_t)buf.st_size); + assert_se(label_ops_post(fd, "file3.txt") == -ENOENT); + assert_se(label_ops_post(fd, "/abcd") == -ENOENT); + assert_se(label_ops_post(fd, "/restricted_directory") == -EACCES); + assert_se(label_ops_post(fd, "") == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO) diff --git a/src/test/test-list.c b/src/test/test-list.c index 87f7c7b..6c64070 100644 --- a/src/test/test-list.c +++ b/src/test/test-list.c @@ -17,8 +17,8 @@ int main(int argc, const char *argv[]) { LIST_HEAD_INIT(head); LIST_HEAD_INIT(head2); - assert_se(head == NULL); - assert_se(head2 == NULL); + ASSERT_NULL(head); + ASSERT_NULL(head2); for (i = 0; i < ELEMENTSOF(items); i++) { LIST_INIT(item_list, &items[i]); @@ -49,7 +49,7 @@ int main(int argc, const char *argv[]) { assert_se(!LIST_JUST_US(item_list, head)); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[1].item_list_next == &items[0]); assert_se(items[2].item_list_next == &items[1]); assert_se(items[3].item_list_next == &items[2]); @@ -57,7 +57,7 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); list_item *cursor = LIST_FIND_HEAD(item_list, &items[0]); assert_se(cursor == &items[3]); @@ -68,16 +68,16 @@ int main(int argc, const char *argv[]) { assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); assert_se(LIST_JUST_US(item_list, &items[1])); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[2].item_list_next == &items[0]); assert_se(items[3].item_list_next == &items[2]); assert_se(items[0].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_INSERT_AFTER(item_list, head, &items[3], &items[1]) == &items[1]); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[2].item_list_next == &items[0]); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); @@ -85,21 +85,21 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); assert_se(LIST_JUST_US(item_list, &items[1])); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[2].item_list_next == &items[0]); assert_se(items[3].item_list_next == &items[2]); assert_se(items[0].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_INSERT_BEFORE(item_list, head, &items[2], &items[1]) == &items[1]); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[2].item_list_next == &items[0]); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); @@ -107,21 +107,21 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); assert_se(LIST_JUST_US(item_list, &items[0])); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_INSERT_BEFORE(item_list, head, &items[3], &items[0]) == &items[0]); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); assert_se(items[0].item_list_next == &items[3]); @@ -129,22 +129,22 @@ int main(int argc, const char *argv[]) { assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); assert_se(items[3].item_list_prev == &items[0]); - assert_se(items[0].item_list_prev == NULL); + ASSERT_NULL(items[0].item_list_prev); assert_se(head == &items[0]); assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); assert_se(LIST_JUST_US(item_list, &items[0])); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_INSERT_BEFORE(item_list, head, NULL, &items[0]) == &items[0]); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[2].item_list_next == &items[0]); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); @@ -152,27 +152,27 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_REMOVE(item_list, head, &items[0]) == &items[0]); assert_se(LIST_JUST_US(item_list, &items[0])); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[1].item_list_next == &items[2]); assert_se(items[3].item_list_next == &items[1]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_REMOVE(item_list, head, &items[1]) == &items[1]); assert_se(LIST_JUST_US(item_list, &items[1])); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[3].item_list_next == &items[2]); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_REMOVE(item_list, head, &items[2]) == &items[2]); assert_se(LIST_JUST_US(item_list, &items[2])); @@ -181,7 +181,7 @@ int main(int argc, const char *argv[]) { assert_se(LIST_REMOVE(item_list, head, &items[3]) == &items[3]); assert_se(LIST_JUST_US(item_list, &items[3])); - assert_se(head == NULL); + ASSERT_NULL(head); for (i = 0; i < ELEMENTSOF(items); i++) { assert_se(LIST_JUST_US(item_list, &items[i])); @@ -193,9 +193,9 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_next == &items[1]); assert_se(items[1].item_list_next == &items[2]); assert_se(items[2].item_list_next == &items[3]); - assert_se(items[3].item_list_next == NULL); + ASSERT_NULL(items[3].item_list_next); - assert_se(items[0].item_list_prev == NULL); + ASSERT_NULL(items[0].item_list_prev); assert_se(items[1].item_list_prev == &items[0]); assert_se(items[2].item_list_prev == &items[1]); assert_se(items[3].item_list_prev == &items[2]); @@ -203,7 +203,7 @@ int main(int argc, const char *argv[]) { for (i = 0; i < ELEMENTSOF(items); i++) assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]); - assert_se(head == NULL); + ASSERT_NULL(head); for (i = 0; i < ELEMENTSOF(items) / 2; i++) { LIST_INIT(item_list, &items[i]); @@ -217,20 +217,20 @@ int main(int argc, const char *argv[]) { assert_se(LIST_PREPEND(item_list, head2, &items[i]) == &items[i]); } - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[1].item_list_next == &items[0]); - assert_se(items[2].item_list_next == NULL); + ASSERT_NULL(items[2].item_list_next); assert_se(items[3].item_list_next == &items[2]); assert_se(items[0].item_list_prev == &items[1]); - assert_se(items[1].item_list_prev == NULL); + ASSERT_NULL(items[1].item_list_prev); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_JOIN(item_list, head2, head) == head2); - assert_se(head == NULL); + ASSERT_NULL(head); - assert_se(items[0].item_list_next == NULL); + ASSERT_NULL(items[0].item_list_next); assert_se(items[1].item_list_next == &items[0]); assert_se(items[2].item_list_next == &items[1]); assert_se(items[3].item_list_next == &items[2]); @@ -238,16 +238,16 @@ int main(int argc, const char *argv[]) { assert_se(items[0].item_list_prev == &items[1]); assert_se(items[1].item_list_prev == &items[2]); assert_se(items[2].item_list_prev == &items[3]); - assert_se(items[3].item_list_prev == NULL); + ASSERT_NULL(items[3].item_list_prev); assert_se(LIST_JOIN(item_list, head, head2) == head); - assert_se(head2 == NULL); + ASSERT_NULL(head2); assert_se(head); for (i = 0; i < ELEMENTSOF(items); i++) assert_se(LIST_REMOVE(item_list, head, &items[i]) == &items[i]); - assert_se(head == NULL); + ASSERT_NULL(head); assert_se(LIST_PREPEND(item_list, head, items + 0) == items + 0); assert_se(LIST_PREPEND(item_list, head, items + 1) == items + 1); @@ -256,7 +256,7 @@ int main(int argc, const char *argv[]) { assert_se(LIST_POP(item_list, head) == items + 2); assert_se(LIST_POP(item_list, head) == items + 1); assert_se(LIST_POP(item_list, head) == items + 0); - assert_se(LIST_POP(item_list, head) == NULL); + ASSERT_NULL(LIST_POP(item_list, head)); /* No-op on an empty list */ @@ -269,7 +269,7 @@ int main(int argc, const char *argv[]) { LIST_CLEAR(item_list, head, free); - assert_se(head == NULL); + ASSERT_NULL(head); /* A list can be cleared partially */ @@ -280,7 +280,7 @@ int main(int argc, const char *argv[]) { LIST_CLEAR(item_list, head->item_list_next, free); assert_se(head == items + 0); - assert_se(head->item_list_next == NULL); + ASSERT_NULL(head->item_list_next); return 0; } diff --git a/src/test/test-load-fragment.c b/src/test/test-load-fragment.c index 8d2cec0..94904e5 100644 --- a/src/test/test-load-fragment.c +++ b/src/test/test-load-fragment.c @@ -79,12 +79,12 @@ static void check_execcommand(ExecCommand *c, n = strv_length(c->argv); log_info("actual: \"%s\" [\"%s\" \"%s\" \"%s\"]", c->path, c->argv[0], n > 0 ? c->argv[1] : "(null)", n > 1 ? c->argv[2] : "(null)"); - assert_se(streq(c->path, path)); - assert_se(streq(c->argv[0], argv0 ?: path)); + ASSERT_STREQ(c->path, path); + ASSERT_STREQ(c->argv[0], argv0 ?: path); if (n > 0) - assert_se(streq_ptr(c->argv[1], argv1)); + ASSERT_STREQ(c->argv[1], argv1); if (n > 1) - assert_se(streq_ptr(c->argv[2], argv2)); + ASSERT_STREQ(c->argv[2], argv2); assert_se(!!(c->flags & EXEC_COMMAND_IGNORE_FAILURE) == ignore); } @@ -139,7 +139,7 @@ TEST(config_parse_exec) { "LValue", 0, "/RValue/ argv0 r1", &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* honour_argv0 */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, @@ -154,14 +154,14 @@ TEST(config_parse_exec) { "LValue", 0, "@/RValue", &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* no command, whitespace only, reset */"); r = config_parse_exec(NULL, "fake", 3, "section", 1, "LValue", 0, "", &c, u); assert_se(r == 0); - assert_se(c == NULL); + ASSERT_NULL(c); log_info("/* ignore && honour_argv0 */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, @@ -184,14 +184,14 @@ TEST(config_parse_exec) { "LValue", 0, "--/RValue argv0 r1", &c, u); assert_se(r == 0); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* ignore && ignore (2) */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-@-/RValue argv0 r1", &c, u); assert_se(r == 0); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* semicolon */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, @@ -227,7 +227,7 @@ TEST(config_parse_exec) { c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* trailing semicolon, no whitespace */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, @@ -238,7 +238,7 @@ TEST(config_parse_exec) { c1 = c1->command_next; check_execcommand(c1, "/RValue", "argv0", "r1", NULL, true); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* trailing semicolon in single quotes */"); r = config_parse_exec(NULL, "fake", 5, "section", 1, @@ -366,7 +366,7 @@ TEST(config_parse_exec) { "LValue", 0, path, &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); } log_info("/* valid character: \\s */"); @@ -392,35 +392,35 @@ TEST(config_parse_exec) { "LValue", 0, "/path\\", &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* missing ending ' */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path 'foo", &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* missing ending ' with trailing backslash */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "/path 'foo\\", &c, u); assert_se(r == -ENOEXEC); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* invalid space between modifiers */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "- /path", &c, u); assert_se(r == 0); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* only modifiers, no path */"); r = config_parse_exec(NULL, "fake", 4, "section", 1, "LValue", 0, "-", &c, u); assert_se(r == 0); - assert_se(c1->command_next == NULL); + ASSERT_NULL(c1->command_next); log_info("/* long arg */"); /* See issue #22957. */ @@ -442,7 +442,7 @@ TEST(config_parse_exec) { "LValue", 0, "", &c, u); assert_se(r == 0); - assert_se(c == NULL); + ASSERT_NULL(c); exec_command_free_list(c); } @@ -544,7 +544,7 @@ TEST(install_printf, .sd_booted = true) { memzero(i.path, strlen(i.path)); \ if (result) { \ printf("%s\n", t); \ - assert_se(streq(t, result)); \ + ASSERT_STREQ(t, result); \ } else \ assert_se(!t); \ strcpy(i.name, d1); \ @@ -794,8 +794,8 @@ TEST(config_parse_pass_environ) { &passenv, NULL); assert_se(r >= 0); assert_se(strv_length(passenv) == 2); - assert_se(streq(passenv[0], "A")); - assert_se(streq(passenv[1], "B")); + ASSERT_STREQ(passenv[0], "A"); + ASSERT_STREQ(passenv[1], "B"); r = config_parse_pass_environ(NULL, "fake", 1, "section", 1, "PassEnvironment", 0, "", @@ -808,7 +808,7 @@ TEST(config_parse_pass_environ) { &passenv, NULL); assert_se(r >= 0); assert_se(strv_length(passenv) == 1); - assert_se(streq(passenv[0], "normal_name")); + ASSERT_STREQ(passenv[0], "normal_name"); } TEST(config_parse_unit_env_file) { @@ -858,8 +858,8 @@ TEST(config_parse_unit_env_file) { &files, u); assert_se(r == 0); assert_se(strv_length(files) == 2); - assert_se(streq(files[0], "/absolute1")); - assert_se(streq(files[1], "/absolute2")); + ASSERT_STREQ(files[0], "/absolute1"); + ASSERT_STREQ(files[1], "/absolute2"); r = config_parse_unit_env_file(u->id, "fake", 1, "section", 1, "EnvironmentFile", 0, "", @@ -872,7 +872,7 @@ TEST(config_parse_unit_env_file) { &files, u); assert_se(r == 0); assert_se(strv_length(files) == 1); - assert_se(streq(files[0], "/path/foobar.service.conf")); + ASSERT_STREQ(files[0], "/path/foobar.service.conf"); } TEST(unit_dump_config_items) { @@ -1073,8 +1073,8 @@ TEST(config_parse_open_file) { &of, u); assert_se(r >= 0); assert_se(of); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == OPENFILE_READ_ONLY); of = open_file_free(of); @@ -1083,8 +1083,8 @@ TEST(config_parse_open_file) { &of, u); assert_se(r >= 0); assert_se(of); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "mnt")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "mnt"); assert_se(of->flags == OPENFILE_READ_ONLY); r = config_parse_open_file(NULL, "fake", 1, "section", 1, diff --git a/src/test/test-local-addresses.c b/src/test/test-local-addresses.c index 5a02465..f418f37 100644 --- a/src/test/test-local-addresses.c +++ b/src/test/test-local-addresses.c @@ -1,20 +1,25 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include +#include #include #include "af-list.h" #include "alloc-util.h" +#include "capability-util.h" #include "in-addr-util.h" #include "local-addresses.h" +#include "netlink-util.h" #include "tests.h" -static void print_local_addresses(struct local_address *a, unsigned n) { - for (unsigned i = 0; i < n; i++) { - _cleanup_free_ char *b = NULL; +static bool support_rta_via = false; - assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); - log_debug("%s if%i scope=%i metric=%u address=%s", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); - } +static void print_local_addresses(const struct local_address *a, size_t n) { + FOREACH_ARRAY(i, a, n) + log_debug("%s ifindex=%i scope=%u priority=%"PRIu32" weight=%"PRIu32" address=%s", + af_to_name(i->family), i->ifindex, i->scope, i->priority, i->weight, + IN_ADDR_TO_STRING(i->family, &i->address)); } TEST(local_addresses) { @@ -24,50 +29,300 @@ TEST(local_addresses) { n = local_addresses(NULL, 0, AF_INET, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:0, AF_INET) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_addresses(NULL, 0, AF_INET6, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:0, AF_INET6) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_addresses(NULL, 0, AF_UNSPEC, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:0, AF_UNSPEC) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_addresses(NULL, 1, AF_INET, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:1, AF_INET) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_addresses(NULL, 1, AF_INET6, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:1, AF_INET6) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_addresses(NULL, 1, AF_UNSPEC, &a); assert_se(n >= 0); log_debug("/* Local Addresses(ifindex:1, AF_UNSPEC) */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_gateways(NULL, 0, AF_UNSPEC, &a); assert_se(n >= 0); log_debug("/* Local Gateways */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); a = mfree(a); n = local_outbounds(NULL, 0, AF_UNSPEC, &a); assert_se(n >= 0); log_debug("/* Local Outbounds */"); - print_local_addresses(a, (unsigned) n); + print_local_addresses(a, n); free(a); } +static void check_local_addresses(sd_netlink *rtnl, int ifindex, int request_ifindex, int family) { + _cleanup_free_ struct local_address *a = NULL; + union in_addr_union u; + int n; + + log_debug("/* Local Addresses (ifindex:%i, %s) */", request_ifindex, family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family)); + + n = local_addresses(rtnl, request_ifindex, family, &a); + assert_se(n >= 0); + print_local_addresses(a, n); + + assert_se(in_addr_from_string(AF_INET, "10.123.123.123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .scope = RT_SCOPE_UNIVERSE, + .family = AF_INET, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:0:123::123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .scope = RT_SCOPE_UNIVERSE, + .family = AF_INET6, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET6)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:1:123::123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .scope = RT_SCOPE_UNIVERSE, + .family = AF_INET6, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET6)); +} + +static void check_local_gateways(sd_netlink *rtnl, int ifindex, int request_ifindex, int family) { + _cleanup_free_ struct local_address *a = NULL; + union in_addr_union u; + int n; + + log_debug("/* Local Gateways (ifindex:%i, %s) */", request_ifindex, family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family)); + + n = local_gateways(rtnl, request_ifindex, family, &a); + assert_se(n >= 0); + print_local_addresses(a, n); + + assert_se(in_addr_from_string(AF_INET, "10.123.0.1", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .priority = 1234, + .family = AF_INET, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:0:123::1", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .priority = 1234, + .family = AF_INET6, + .address = u, + }) == (family == AF_UNSPEC && support_rta_via)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:1:123::1", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .priority = 1234, + .family = AF_INET6, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET6)); +} + +static void check_local_outbounds(sd_netlink *rtnl, int ifindex, int request_ifindex, int family) { + _cleanup_free_ struct local_address *a = NULL; + union in_addr_union u; + int n; + + log_debug("/* Local Outbounds (ifindex:%i, %s) */", request_ifindex, family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family)); + + n = local_outbounds(rtnl, request_ifindex, family, &a); + assert_se(n >= 0); + print_local_addresses(a, n); + + assert_se(in_addr_from_string(AF_INET, "10.123.123.123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .family = AF_INET, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:0:123::123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .family = AF_INET6, + .address = u, + }) == (family == AF_UNSPEC && support_rta_via)); + + assert_se(in_addr_from_string(AF_INET6, "2001:db8:1:123::123", &u) >= 0); + assert_se(has_local_address(a, n, + &(struct local_address) { + .ifindex = ifindex, + .family = AF_INET6, + .address = u, + }) == IN_SET(family, AF_UNSPEC, AF_INET6)); +} + +TEST(local_addresses_with_dummy) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; + union in_addr_union u; + int r, ifindex; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + /* Create a dummy interface */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-local-addr") >= 0); + assert_se(sd_netlink_message_open_container(message, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_INFO_KIND, "dummy") >= 0); + r = sd_netlink_call(rtnl, message, 0, NULL); + if (r == -EPERM) + return (void) log_tests_skipped("missing required capabilities"); + if (r == -EOPNOTSUPP) + return (void) log_tests_skipped("dummy network interface is not supported"); + assert_se(r >= 0); + message = sd_netlink_message_unref(message); + + /* Get ifindex */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-local-addr") >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, &reply) >= 0); + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(ifindex > 0); + message = sd_netlink_message_unref(message); + reply = sd_netlink_message_unref(reply); + + /* Bring the interface up */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_SETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_link_set_flags(message, IFF_UP, IFF_UP) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + /* Add an IPv4 address */ + assert_se(sd_rtnl_message_new_addr_update(rtnl, &message, ifindex, AF_INET) >= 0); + assert_se(sd_rtnl_message_addr_set_scope(message, RT_SCOPE_UNIVERSE) >= 0); + assert_se(sd_rtnl_message_addr_set_prefixlen(message, 16) >= 0); + assert_se(in_addr_from_string(AF_INET, "10.123.123.123", &u) >= 0); + assert_se(sd_netlink_message_append_in_addr(message, IFA_LOCAL, &u.in) >= 0); + assert_se(in_addr_from_string(AF_INET, "10.123.255.255", &u) >= 0); + assert_se(sd_netlink_message_append_in_addr(message, IFA_BROADCAST, &u.in) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + /* Add IPv6 addresses */ + assert_se(sd_rtnl_message_new_addr_update(rtnl, &message, ifindex, AF_INET6) >= 0); + assert_se(sd_rtnl_message_addr_set_scope(message, RT_SCOPE_UNIVERSE) >= 0); + assert_se(sd_rtnl_message_addr_set_prefixlen(message, 64) >= 0); + assert_se(in_addr_from_string(AF_INET6, "2001:db8:0:123::123", &u) >= 0); + assert_se(sd_netlink_message_append_in6_addr(message, IFA_LOCAL, &u.in6) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFA_FLAGS, IFA_F_NODAD) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + assert_se(sd_rtnl_message_new_addr_update(rtnl, &message, ifindex, AF_INET6) >= 0); + assert_se(sd_rtnl_message_addr_set_scope(message, RT_SCOPE_UNIVERSE) >= 0); + assert_se(sd_rtnl_message_addr_set_prefixlen(message, 64) >= 0); + assert_se(in_addr_from_string(AF_INET6, "2001:db8:1:123::123", &u) >= 0); + assert_se(sd_netlink_message_append_in6_addr(message, IFA_LOCAL, &u.in6) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFA_FLAGS, IFA_F_NODAD) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + /* Add an IPv4 default gateway (RTA_GATEWAY) */ + assert_se(sd_rtnl_message_new_route(rtnl, &message, RTM_NEWROUTE, AF_INET, RTPROT_STATIC) >= 0); + assert_se(sd_rtnl_message_route_set_scope(message, RT_SCOPE_UNIVERSE) >= 0); + assert_se(sd_rtnl_message_route_set_type(message, RTN_UNICAST) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_PRIORITY, 1234) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_TABLE, RT_TABLE_MAIN) >= 0); + assert_se(in_addr_from_string(AF_INET, "10.123.0.1", &u) >= 0); + assert_se(sd_netlink_message_append_in_addr(message, RTA_GATEWAY, &u.in) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_OIF, ifindex) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + /* Add an IPv4 default gateway (RTA_VIA) */ + assert_se(sd_rtnl_message_new_route(rtnl, &message, RTM_NEWROUTE, AF_INET, RTPROT_STATIC) >= 0); + assert_se(sd_rtnl_message_route_set_scope(message, RT_SCOPE_UNIVERSE) >= 0); + assert_se(sd_rtnl_message_route_set_type(message, RTN_UNICAST) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_PRIORITY, 1234) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_TABLE, RT_TABLE_MAIN) >= 0); + assert_se(in_addr_from_string(AF_INET6, "2001:db8:0:123::1", &u) >= 0); + assert_se(sd_netlink_message_append_data(message, RTA_VIA, + &(RouteVia) { + .family = AF_INET6, + .address = u, + }, sizeof(RouteVia)) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_OIF, ifindex) >= 0); + r = sd_netlink_call(rtnl, message, 0, NULL); + if (r == -EINVAL) + log_debug_errno(r, "RTA_VIA is not supported, ignoring: %m"); + else + assert_se(r >= 0); + support_rta_via = r >= 0; + message = sd_netlink_message_unref(message); + + /* Add an IPv6 default gateway */ + assert_se(sd_rtnl_message_new_route(rtnl, &message, RTM_NEWROUTE, AF_INET6, RTPROT_STATIC) >= 0); + assert_se(sd_rtnl_message_route_set_type(message, RTN_UNICAST) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_PRIORITY, 1234) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_TABLE, RT_TABLE_MAIN) >= 0); + assert_se(in_addr_from_string(AF_INET6, "2001:db8:1:123::1", &u) >= 0); + assert_se(sd_netlink_message_append_in6_addr(message, RTA_GATEWAY, &u.in6) >= 0); + assert_se(sd_netlink_message_append_u32(message, RTA_OIF, ifindex) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); + + /* Check */ + check_local_addresses(rtnl, ifindex, 0, AF_UNSPEC); + check_local_addresses(rtnl, ifindex, 0, AF_INET); + check_local_addresses(rtnl, ifindex, 0, AF_INET6); + check_local_addresses(rtnl, ifindex, ifindex, AF_UNSPEC); + check_local_addresses(rtnl, ifindex, ifindex, AF_INET); + check_local_addresses(rtnl, ifindex, ifindex, AF_INET6); + check_local_gateways(rtnl, ifindex, 0, AF_UNSPEC); + check_local_gateways(rtnl, ifindex, 0, AF_INET); + check_local_gateways(rtnl, ifindex, 0, AF_INET6); + check_local_gateways(rtnl, ifindex, ifindex, AF_UNSPEC); + check_local_gateways(rtnl, ifindex, ifindex, AF_INET); + check_local_gateways(rtnl, ifindex, ifindex, AF_INET6); + check_local_outbounds(rtnl, ifindex, 0, AF_UNSPEC); + check_local_outbounds(rtnl, ifindex, 0, AF_INET); + check_local_outbounds(rtnl, ifindex, 0, AF_INET6); + check_local_outbounds(rtnl, ifindex, ifindex, AF_UNSPEC); + check_local_outbounds(rtnl, ifindex, ifindex, AF_INET); + check_local_outbounds(rtnl, ifindex, ifindex, AF_INET6); + + /* Cleanup */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_DELLINK, ifindex) >= 0); + assert_se(sd_netlink_call(rtnl, message, 0, NULL) >= 0); + message = sd_netlink_message_unref(message); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c index 39f71c6..ab2d1f5 100644 --- a/src/test/test-locale-util.c +++ b/src/test/test-locale-util.c @@ -82,7 +82,7 @@ TEST(keymaps) { #define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x)) TEST(dump_special_glyphs) { - assert_cc(SPECIAL_GLYPH_WORLD + 1 == _SPECIAL_GLYPH_MAX); + assert_cc(SPECIAL_GLYPH_GREEN_CIRCLE + 1 == _SPECIAL_GLYPH_MAX); log_info("is_locale_utf8: %s", yes_no(is_locale_utf8())); @@ -92,6 +92,8 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_TREE_SPACE); dump_glyph(SPECIAL_GLYPH_TREE_TOP); dump_glyph(SPECIAL_GLYPH_VERTICAL_DOTTED); + dump_glyph(SPECIAL_GLYPH_HORIZONTAL_DOTTED); + dump_glyph(SPECIAL_GLYPH_HORIZONTAL_FAT); dump_glyph(SPECIAL_GLYPH_TRIANGULAR_BULLET); dump_glyph(SPECIAL_GLYPH_BLACK_CIRCLE); dump_glyph(SPECIAL_GLYPH_WHITE_CIRCLE); @@ -127,6 +129,10 @@ TEST(dump_special_glyphs) { dump_glyph(SPECIAL_GLYPH_WARNING_SIGN); dump_glyph(SPECIAL_GLYPH_COMPUTER_DISK); dump_glyph(SPECIAL_GLYPH_WORLD); + dump_glyph(SPECIAL_GLYPH_RED_CIRCLE); + dump_glyph(SPECIAL_GLYPH_YELLOW_CIRCLE); + dump_glyph(SPECIAL_GLYPH_BLUE_CIRCLE); + dump_glyph(SPECIAL_GLYPH_GREEN_CIRCLE); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-log.c b/src/test/test-log.c index b5ba67b..97eb5e0 100644 --- a/src/test/test-log.c +++ b/src/test/test-log.c @@ -22,6 +22,26 @@ assert_cc(!IS_SYNTHETIC_ERRNO(0)); #define X100(x) X10(X10(x)) #define X1000(x) X100(X10(x)) +static int fail_with_EINVAL(void) { + assert_return(false, -EINVAL); + return 0; +} + +static void test_assert_return_is_critical(void) { + SAVE_ASSERT_RETURN_IS_CRITICAL; + + log_set_assert_return_is_critical(false); + assert_se(fail_with_EINVAL() == -EINVAL); + + log_set_assert_return_is_critical(true); + ASSERT_RETURN_IS_CRITICAL(false, assert_se(fail_with_EINVAL() == -EINVAL)); + assert_se(log_get_assert_return_is_critical() == true); + ASSERT_RETURN_EXPECTED(assert_se(fail_with_EINVAL() == -EINVAL)); + assert_se(log_get_assert_return_is_critical() == true); + ASSERT_RETURN_EXPECTED_SE(fail_with_EINVAL() == -EINVAL); + assert_se(log_get_assert_return_is_critical() == true); +} + static void test_file(void) { log_info("__FILE__: %s", __FILE__); log_info("RELATIVE_SOURCE_PATH: %s", RELATIVE_SOURCE_PATH); @@ -207,6 +227,7 @@ static void test_log_prefix(void) { int main(int argc, char* argv[]) { test_setup_logging(LOG_DEBUG); + test_assert_return_is_critical(); test_file(); assert_se(log_info_errno(SYNTHETIC_ERRNO(EUCLEAN), "foo") == -EUCLEAN); diff --git a/src/test/test-login-util.c b/src/test/test-login-util.c new file mode 100644 index 0000000..2d20e23 --- /dev/null +++ b/src/test/test-login-util.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "login-util.h" +#include "string-util.h" +#include "tests.h" + +TEST(session_id_valid) { + /* Invalid Session ID */ + assert_se(!session_id_valid("")); + assert_se(!session_id_valid(NULL)); + assert_se(!session_id_valid("abc-123")); + assert_se(!session_id_valid("abc_123")); + assert_se(!session_id_valid("abc123*")); + + /* Valid Session ID */ + assert_se(session_id_valid("abc123")); + assert_se(session_id_valid("AbCdEfG123456")); + assert_se(session_id_valid("1234567890")); + assert_se(session_id_valid("ABCDEFGHI")); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c index 1bd00d1..15c6357 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-block.c @@ -49,10 +49,10 @@ static void verify_dissected_image(DissectedImage *dissected) { static void verify_dissected_image_harder(DissectedImage *dissected) { verify_dissected_image(dissected); - assert_se(streq(dissected->partitions[PARTITION_ESP].fstype, "vfat")); - assert_se(streq(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat")); - assert_se(streq(dissected->partitions[PARTITION_ROOT].fstype, "ext4")); - assert_se(streq(dissected->partitions[PARTITION_HOME].fstype, "ext4")); + ASSERT_STREQ(dissected->partitions[PARTITION_ESP].fstype, "vfat"); + ASSERT_STREQ(dissected->partitions[PARTITION_XBOOTLDR].fstype, "vfat"); + ASSERT_STREQ(dissected->partitions[PARTITION_ROOT].fstype, "ext4"); + ASSERT_STREQ(dissected->partitions[PARTITION_HOME].fstype, "ext4"); } static void* thread_func(void *ptr) { @@ -328,7 +328,7 @@ static int run(int argc, char *argv[]) { log_notice("All threads started now."); if (arg_n_threads == 1) - assert_se(thread_func(FD_TO_PTR(fd)) == NULL); + ASSERT_NULL(thread_func(FD_TO_PTR(fd))); else for (unsigned i = 0; i < arg_n_threads; i++) { log_notice("Joining thread #%u.", i); diff --git a/src/test/test-loopback.c b/src/test/test-loopback.c index 48869ae..db6ee0f 100644 --- a/src/test/test-loopback.c +++ b/src/test/test-loopback.c @@ -19,6 +19,8 @@ TEST_RET(loopback_setup) { } r = loopback_setup(); + if (ERRNO_IS_NEG_PRIVILEGE(r)) + return log_tests_skipped("lacking privileges"); if (r < 0) return log_error_errno(r, "loopback: %m"); diff --git a/src/test/test-macro.c b/src/test/test-macro.c index b91a1f9..9e2875d 100644 --- a/src/test/test-macro.c +++ b/src/test/test-macro.c @@ -1,7 +1,9 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include +#include "errno-util.h" #include "log.h" #include "macro.h" #include "tests.h" @@ -159,6 +161,153 @@ TEST(container_of) { #pragma GCC diagnostic pop +#define TEST_OVERFLOW_MATH_BY_TYPE(type, min, max, lit) \ + ({ \ + type x; \ + \ + assert_se(ADD_SAFE(&x, lit(5), lit(10))); \ + assert_se(x == lit(15)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(ADD_SAFE(&x, lit(5), lit(-10))); \ + assert_se(x == lit(-5)); \ + } \ + assert_se(ADD_SAFE(&x, min, lit(0))); \ + assert_se(x == min); \ + assert_se(ADD_SAFE(&x, max, lit(0))); \ + assert_se(x == max); \ + if (IS_SIGNED_INTEGER_TYPE(type)) \ + assert_se(!ADD_SAFE(&x, min, lit(-1))); \ + assert_se(!ADD_SAFE(&x, max, lit(1))); \ + \ + x = lit(5); \ + assert_se(INC_SAFE(&x, lit(10))); \ + assert_se(x == lit(15)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(INC_SAFE(&x, lit(-20))); \ + assert_se(x == lit(-5)); \ + } \ + x = min; \ + assert_se(INC_SAFE(&x, lit(0))); \ + assert_se(x == min); \ + if (IS_SIGNED_INTEGER_TYPE(type)) \ + assert_se(!INC_SAFE(&x, lit(-1))); \ + x = max; \ + assert_se(INC_SAFE(&x, lit(0))); \ + assert_se(x == max); \ + assert_se(!INC_SAFE(&x, lit(1))); \ + \ + assert_se(SUB_SAFE(&x, lit(10), lit(5))); \ + assert_se(x == lit(5)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(SUB_SAFE(&x, lit(5), lit(10))); \ + assert_se(x == lit(-5)); \ + \ + assert_se(SUB_SAFE(&x, lit(5), lit(-10))); \ + assert_se(x == lit(15)); \ + } else \ + assert_se(!SUB_SAFE(&x, lit(5), lit(10))); \ + assert_se(SUB_SAFE(&x, min, lit(0))); \ + assert_se(x == min); \ + assert_se(SUB_SAFE(&x, max, lit(0))); \ + assert_se(x == max); \ + assert_se(!SUB_SAFE(&x, min, lit(1))); \ + if (IS_SIGNED_INTEGER_TYPE(type)) \ + assert_se(!SUB_SAFE(&x, max, lit(-1))); \ + \ + x = lit(10); \ + assert_se(DEC_SAFE(&x, lit(5))); \ + assert_se(x == lit(5)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(DEC_SAFE(&x, lit(10))); \ + assert_se(x == lit(-5)); \ + \ + x = lit(5); \ + assert_se(DEC_SAFE(&x, lit(-10))); \ + assert_se(x == lit(15)); \ + } else \ + assert_se(!DEC_SAFE(&x, lit(10))); \ + x = min; \ + assert_se(DEC_SAFE(&x, lit(0))); \ + assert_se(x == min); \ + assert_se(!DEC_SAFE(&x, lit(1))); \ + x = max; \ + assert_se(DEC_SAFE(&x, lit(0))); \ + if (IS_SIGNED_INTEGER_TYPE(type)) \ + assert_se(!DEC_SAFE(&x, lit(-1))); \ + \ + assert_se(MUL_SAFE(&x, lit(2), lit(4))); \ + assert_se(x == lit(8)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(MUL_SAFE(&x, lit(2), lit(-4))); \ + assert_se(x == lit(-8)); \ + } \ + assert_se(MUL_SAFE(&x, lit(5), lit(0))); \ + assert_se(x == lit(0)); \ + assert_se(MUL_SAFE(&x, min, lit(1))); \ + assert_se(x == min); \ + if (IS_SIGNED_INTEGER_TYPE(type)) \ + assert_se(!MUL_SAFE(&x, min, lit(2))); \ + assert_se(MUL_SAFE(&x, max, lit(1))); \ + assert_se(x == max); \ + assert_se(!MUL_SAFE(&x, max, lit(2))); \ + \ + x = lit(2); \ + assert_se(MUL_ASSIGN_SAFE(&x, lit(4))); \ + assert_se(x == lit(8)); \ + if (IS_SIGNED_INTEGER_TYPE(type)) { \ + assert_se(MUL_ASSIGN_SAFE(&x, lit(-1))); \ + assert_se(x == lit(-8)); \ + } \ + assert_se(MUL_ASSIGN_SAFE(&x, lit(0))); \ + assert_se(x == lit(0)); \ + x = min; \ + assert_se(MUL_ASSIGN_SAFE(&x, lit(1))); \ + assert_se(x == min); \ + if IS_SIGNED_INTEGER_TYPE(type) \ + assert_se(!MUL_ASSIGN_SAFE(&x, lit(2))); \ + x = max; \ + assert_se(MUL_ASSIGN_SAFE(&x, lit(1))); \ + assert_se(x == max); \ + assert_se(!MUL_ASSIGN_SAFE(&x, lit(2))); \ + }) + +TEST(overflow_safe_math) { + int64_t i; + uint64_t u, *p; + + /* basic tests */ + TEST_OVERFLOW_MATH_BY_TYPE(int8_t, INT8_MIN, INT8_MAX, INT8_C); + TEST_OVERFLOW_MATH_BY_TYPE(int16_t, INT16_MIN, INT16_MAX, INT16_C); + TEST_OVERFLOW_MATH_BY_TYPE(int32_t, INT32_MIN, INT32_MAX, INT32_C); + TEST_OVERFLOW_MATH_BY_TYPE(int64_t, INT64_MIN, INT64_MAX, INT64_C); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wtype-limits" /* Otherwise the compiler complains about comparisons to negative numbers always being false */ + TEST_OVERFLOW_MATH_BY_TYPE(uint8_t, UINT8_C(0), UINT8_MAX, UINT8_C); + TEST_OVERFLOW_MATH_BY_TYPE(uint16_t, UINT16_C(0), UINT16_MAX, UINT16_C); + TEST_OVERFLOW_MATH_BY_TYPE(uint32_t, UINT32_C(0), UINT32_MAX, UINT32_C); + TEST_OVERFLOW_MATH_BY_TYPE(uint64_t, UINT64_C(0), UINT64_MAX, UINT64_C); +#pragma GCC diagnostic pop + + /* make sure we handle pointers correctly */ + p = &u; + assert_se(ADD_SAFE(p, 35, 15) && (u == 50)); + assert_se(SUB_SAFE(p, 35, 15) && (u == 20)); + assert_se(MUL_SAFE(p, 5, 10) && (u == 50)); + assert_se(INC_SAFE(p, 10) && (u == 60)); + assert_se(DEC_SAFE(p, 10) && (u == 50)); + assert_se(MUL_ASSIGN_SAFE(p, 3) && (u == 150)); + assert_se(!ADD_SAFE(p, UINT64_MAX, 1)); + assert_se(!SUB_SAFE(p, 0, 1)); + + /* cross-type sanity checks */ + assert_se(ADD_SAFE(&i, INT32_MAX, 1)); + assert_se(SUB_SAFE(&i, INT32_MIN, 1)); + assert_se(!ADD_SAFE(&i, UINT64_MAX, 0)); + assert_se(ADD_SAFE(&u, INT32_MAX, 1)); + assert_se(MUL_SAFE(&u, INT32_MAX, 2)); +} + TEST(DIV_ROUND_UP) { int div; @@ -221,92 +370,13 @@ TEST(IN_SET) { assert_se(!IN_SET(t.x, 2, 3, 4)); } -TEST(FOREACH_POINTER) { - int a, b, c, *i; - size_t k = 0; - - FOREACH_POINTER(i, &a, &b, &c) { - switch (k) { - - case 0: - assert_se(i == &a); - break; - - case 1: - assert_se(i == &b); - break; - - case 2: - assert_se(i == &c); - break; - - default: - assert_not_reached(); - break; - } - - k++; - } - - assert_se(k == 3); - - FOREACH_POINTER(i, &b) { - assert_se(k == 3); - assert_se(i == &b); - k = 4; - } - - assert_se(k == 4); - - FOREACH_POINTER(i, NULL, &c, NULL, &b, NULL, &a, NULL) { - switch (k) { - - case 4: - assert_se(i == NULL); - break; - - case 5: - assert_se(i == &c); - break; - - case 6: - assert_se(i == NULL); - break; - - case 7: - assert_se(i == &b); - break; - - case 8: - assert_se(i == NULL); - break; - - case 9: - assert_se(i == &a); - break; - - case 10: - assert_se(i == NULL); - break; - - default: - assert_not_reached(); - break; - } - - k++; - } - - assert_se(k == 11); -} - -TEST(FOREACH_VA_ARGS) { +TEST(FOREACH_ARGUMENT) { size_t i; i = 0; uint8_t u8, u8_1 = 1, u8_2 = 2, u8_3 = 3; - VA_ARGS_FOREACH(u8, u8_2, 8, 0xff, u8_1, u8_3, 0, 1) { - switch(i++) { + FOREACH_ARGUMENT(u8, u8_2, 8, 0xff, u8_1, u8_3, 0, 1) { + switch (i++) { case 0: assert_se(u8 == u8_2); break; case 1: assert_se(u8 == 8); break; case 2: assert_se(u8 == 0xff); break; @@ -319,24 +389,24 @@ TEST(FOREACH_VA_ARGS) { } assert_se(i == 7); i = 0; - VA_ARGS_FOREACH(u8, 0) { + FOREACH_ARGUMENT(u8, 0) { assert_se(u8 == 0); assert_se(i++ == 0); } assert_se(i == 1); i = 0; - VA_ARGS_FOREACH(u8, 0xff) { + FOREACH_ARGUMENT(u8, 0xff) { assert_se(u8 == 0xff); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(u8) + FOREACH_ARGUMENT(u8) assert_se(false); i = 0; uint32_t u32, u32_1 = 0xffff0000, u32_2 = 10, u32_3 = 0xffff; - VA_ARGS_FOREACH(u32, 1, 100, u32_2, 1000, u32_3, u32_1, 1, 0) { - switch(i++) { + FOREACH_ARGUMENT(u32, 1, 100, u32_2, 1000, u32_3, u32_1, 1, 0) { + switch (i++) { case 0: assert_se(u32 == 1); break; case 1: assert_se(u32 == 100); break; case 2: assert_se(u32 == u32_2); break; @@ -350,24 +420,24 @@ TEST(FOREACH_VA_ARGS) { } assert_se(i == 8); i = 0; - VA_ARGS_FOREACH(u32, 0) { + FOREACH_ARGUMENT(u32, 0) { assert_se(u32 == 0); assert_se(i++ == 0); } assert_se(i == 1); i = 0; - VA_ARGS_FOREACH(u32, 1000) { + FOREACH_ARGUMENT(u32, 1000) { assert_se(u32 == 1000); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(u32) + FOREACH_ARGUMENT(u32) assert_se(false); i = 0; uint64_t u64, u64_1 = 0xffffffffffffffff, u64_2 = 50, u64_3 = 0xffff; - VA_ARGS_FOREACH(u64, 44, 0, u64_3, 100, u64_2, u64_1, 50000) { - switch(i++) { + FOREACH_ARGUMENT(u64, 44, 0, u64_3, 100, u64_2, u64_1, 50000) { + switch (i++) { case 0: assert_se(u64 == 44); break; case 1: assert_se(u64 == 0); break; case 2: assert_se(u64 == u64_3); break; @@ -380,18 +450,18 @@ TEST(FOREACH_VA_ARGS) { } assert_se(i == 7); i = 0; - VA_ARGS_FOREACH(u64, 0) { + FOREACH_ARGUMENT(u64, 0) { assert_se(u64 == 0); assert_se(i++ == 0); } assert_se(i == 1); i = 0; - VA_ARGS_FOREACH(u64, 0xff00ff00000000) { + FOREACH_ARGUMENT(u64, 0xff00ff00000000) { assert_se(u64 == 0xff00ff00000000); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(u64) + FOREACH_ARGUMENT(u64) assert_se(false); struct test { @@ -405,8 +475,8 @@ TEST(FOREACH_VA_ARGS) { s_2 = { .a = 100000, .b = 'z', }, s_3 = { .a = 0xff, .b = 'q', }, s_4 = { .a = 1, .b = 'x', }; - VA_ARGS_FOREACH(s, s_1, (struct test){ .a = 10, .b = 'd', }, s_2, (struct test){}, s_3, s_4) { - switch(i++) { + FOREACH_ARGUMENT(s, s_1, (struct test){ .a = 10, .b = 'd', }, s_2, (struct test){}, s_3, s_4) { + switch (i++) { case 0: assert_se(s.a == 0 ); assert_se(s.b == 'c'); break; case 1: assert_se(s.a == 10 ); assert_se(s.b == 'd'); break; case 2: assert_se(s.a == 100000); assert_se(s.b == 'z'); break; @@ -418,69 +488,69 @@ TEST(FOREACH_VA_ARGS) { } assert_se(i == 6); i = 0; - VA_ARGS_FOREACH(s, (struct test){ .a = 1, .b = 'A', }) { + FOREACH_ARGUMENT(s, (struct test){ .a = 1, .b = 'A', }) { assert_se(s.a == 1); assert_se(s.b == 'A'); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(s) + FOREACH_ARGUMENT(s) assert_se(false); i = 0; struct test *p, *p_1 = &s_1, *p_2 = &s_2, *p_3 = &s_3, *p_4 = &s_4; - VA_ARGS_FOREACH(p, p_1, NULL, p_2, p_3, NULL, p_4, NULL) { - switch(i++) { + FOREACH_ARGUMENT(p, p_1, NULL, p_2, p_3, NULL, p_4, NULL) { + switch (i++) { case 0: assert_se(p == p_1); break; - case 1: assert_se(p == NULL); break; + case 1: ASSERT_NULL(p); break; case 2: assert_se(p == p_2); break; case 3: assert_se(p == p_3); break; - case 4: assert_se(p == NULL); break; + case 4: ASSERT_NULL(p); break; case 5: assert_se(p == p_4); break; - case 6: assert_se(p == NULL); break; + case 6: ASSERT_NULL(p); break; default: assert_se(false); } } assert_se(i == 7); i = 0; - VA_ARGS_FOREACH(p, p_3) { + FOREACH_ARGUMENT(p, p_3) { assert_se(p == p_3); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(p) + FOREACH_ARGUMENT(p) assert_se(false); i = 0; void *v, *v_1 = p_1, *v_2 = p_2, *v_3 = p_3; uint32_t *u32p = &u32; - VA_ARGS_FOREACH(v, v_1, NULL, u32p, v_3, p_2, p_4, v_2, NULL) { - switch(i++) { + FOREACH_ARGUMENT(v, v_1, NULL, u32p, v_3, p_2, p_4, v_2, NULL) { + switch (i++) { case 0: assert_se(v == v_1); break; - case 1: assert_se(v == NULL); break; + case 1: ASSERT_NULL(v); break; case 2: assert_se(v == u32p); break; case 3: assert_se(v == v_3); break; case 4: assert_se(v == p_2); break; case 5: assert_se(v == p_4); break; case 6: assert_se(v == v_2); break; - case 7: assert_se(v == NULL); break; + case 7: ASSERT_NULL(v); break; default: assert_se(false); } } assert_se(i == 8); i = 0; - VA_ARGS_FOREACH(v, NULL) { - assert_se(v == NULL); + FOREACH_ARGUMENT(v, NULL) { + ASSERT_NULL(v); assert_se(i++ == 0); } assert_se(i == 1); i = 0; - VA_ARGS_FOREACH(v, v_1) { + FOREACH_ARGUMENT(v, v_1) { assert_se(v == v_1); assert_se(i++ == 0); } assert_se(i == 1); - VA_ARGS_FOREACH(v) + FOREACH_ARGUMENT(v) assert_se(false); } @@ -1037,4 +1107,80 @@ TEST(u64_multiply_safe) { assert_se(u64_multiply_safe(UINT64_MAX, UINT64_MAX) == 0); } +TEST(ASSERT) { + char *null = NULL; + + ASSERT_OK(0); + ASSERT_OK(255); + ASSERT_OK(printf("Hello world\n")); + ASSERT_SIGNAL(ASSERT_OK(-1), SIGABRT); + ASSERT_SIGNAL(ASSERT_OK(-ENOANO), SIGABRT); + + ASSERT_OK_ERRNO(0 >= 0); + ASSERT_OK_ERRNO(255 >= 0); + ASSERT_OK_ERRNO(printf("Hello world\n")); + ASSERT_SIGNAL(ASSERT_OK_ERRNO(-1), SIGABRT); + ASSERT_SIGNAL(ASSERT_OK_ERRNO(-ENOANO), SIGABRT); + + ASSERT_ERROR(-ENOENT, ENOENT); + ASSERT_ERROR(RET_NERRNO(mkdir("/i/will/fail/with/enoent", 666)), ENOENT); + ASSERT_SIGNAL(ASSERT_ERROR(0, ENOENT), SIGABRT); + ASSERT_SIGNAL(ASSERT_ERROR(RET_NERRNO(mkdir("/i/will/fail/with/enoent", 666)), ENOANO), SIGABRT); + + errno = ENOENT; + ASSERT_ERROR_ERRNO(-1, ENOENT); + errno = 0; + ASSERT_ERROR_ERRNO(mkdir("/i/will/fail/with/enoent", 666), ENOENT); + ASSERT_SIGNAL(ASSERT_ERROR_ERRNO(0, ENOENT), SIGABRT); + errno = 0; + ASSERT_SIGNAL(ASSERT_ERROR_ERRNO(mkdir("/i/will/fail/with/enoent", 666), ENOANO), SIGABRT); + + ASSERT_TRUE(true); + ASSERT_TRUE(255); + ASSERT_TRUE(getpid()); + ASSERT_SIGNAL(ASSERT_TRUE(1 == 0), SIGABRT); + + ASSERT_FALSE(false); + ASSERT_FALSE(1 == 0); + ASSERT_SIGNAL(ASSERT_FALSE(1 > 0), SIGABRT); + + ASSERT_NULL(NULL); + ASSERT_SIGNAL(ASSERT_NULL(signal_to_string(SIGINT)), SIGABRT); + + ASSERT_NOT_NULL(signal_to_string(SIGTERM)); + ASSERT_SIGNAL(ASSERT_NOT_NULL(NULL), SIGABRT); + + ASSERT_STREQ(NULL, null); + ASSERT_STREQ("foo", "foo"); + ASSERT_SIGNAL(ASSERT_STREQ(null, "bar"), SIGABRT); + ASSERT_SIGNAL(ASSERT_STREQ("foo", "bar"), SIGABRT); + + ASSERT_EQ(0, 0); + ASSERT_EQ(-1, -1); + ASSERT_SIGNAL(ASSERT_EQ(255, -1), SIGABRT); + + ASSERT_GE(0, 0); + ASSERT_GE(1, -1); + ASSERT_SIGNAL(ASSERT_GE(-1, 1), SIGABRT); + + ASSERT_LE(0, 0); + ASSERT_LE(-1, 1); + ASSERT_SIGNAL(ASSERT_LE(1, -1), SIGABRT); + + ASSERT_NE(0, (int64_t) UINT_MAX); + ASSERT_NE(-1, 1); + ASSERT_SIGNAL(ASSERT_NE(0, 0), SIGABRT); + ASSERT_SIGNAL(ASSERT_NE(-1, -1), SIGABRT); + + ASSERT_GT(1, 0); + ASSERT_GT(1, -1); + ASSERT_SIGNAL(ASSERT_GT(0, 0), SIGABRT); + ASSERT_SIGNAL(ASSERT_GT(-1, 1), SIGABRT); + + ASSERT_LT(0, 1); + ASSERT_LT(-1, 1); + ASSERT_SIGNAL(ASSERT_LT(0, 0), SIGABRT); + ASSERT_SIGNAL(ASSERT_LT(1, -1), SIGABRT); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-manager.c b/src/test/test-manager.c deleted file mode 100644 index 76e094b..0000000 --- a/src/test/test-manager.c +++ /dev/null @@ -1,19 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include "manager.h" -#include "tests.h" - -TEST(manager_taint_string) { - Manager m = {}; - - _cleanup_free_ char *a = manager_taint_string(&m); - assert_se(a); - log_debug("taint string: '%s'", a); - - if (cg_all_unified() == 0) - assert_se(strstr(a, "cgroupsv1")); - else - assert_se(!strstr(a, "cgroupsv1")); -} - -DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mempool.c b/src/test/test-mempool.c index d3bc173..1f071db 100644 --- a/src/test/test-mempool.c +++ b/src/test/test-mempool.c @@ -35,7 +35,7 @@ TEST(mempool_trim) { assert_se(!a[x] || a[x]->value == x); if (a[x]) - n_freed ++; + n_freed++; a[x] = mempool_free_tile(&test_mempool, a[x]); } @@ -46,7 +46,7 @@ TEST(mempool_trim) { for (size_t i = 2; i < NN; i += 3) { assert_se(!a[i] || a[i]->value == i); if (a[i]) - n_freed ++; + n_freed++; a[i] = mempool_free_tile(&test_mempool, a[i]); } @@ -66,7 +66,7 @@ TEST(mempool_trim) { for (size_t i = 0; i < NN; i += 1) { assert_se(!a[i] || a[i]->value == i); if (a[i]) - n_freed ++; + n_freed++; a[i] = mempool_free_tile(&test_mempool, a[i]); } @@ -77,7 +77,7 @@ TEST(mempool_trim) { for (size_t i = 0; i < NN; i += 1) { assert_se(!b[i] || b[i]->value == ~(uint64_t) i); if (b[i]) - n_freed ++; + n_freed++; b[i] = mempool_free_tile(&test_mempool, b[i]); } diff --git a/src/test/test-mempress.c b/src/test/test-mempress.c index 26ce4ce..44f1f4c 100644 --- a/src/test/test-mempress.c +++ b/src/test/test-mempress.c @@ -40,7 +40,7 @@ static void *fake_pressure_thread(void *p) { assert_se(cfd >= 0); char buf[STRLEN("hello")+1] = {}; assert_se(read(cfd, buf, sizeof(buf)-1) == sizeof(buf)-1); - assert_se(streq(buf, "hello")); + ASSERT_STREQ(buf, "hello"); assert_se(write(cfd, &(const char) { 'z' }, 1) == 1); return 0; @@ -220,7 +220,7 @@ TEST(real_pressure) { assert_se(sd_bus_message_read(reply, "o", &object) >= 0); - assert_se(bus_wait_for_jobs_one(w, object, /* quiet= */ false, /* extra_args= */ NULL) >= 0); + assert_se(bus_wait_for_jobs_one(w, object, /* flags= */ BUS_WAIT_JOBS_LOG_ERROR, /* extra_args= */ NULL) >= 0); assert_se(sd_event_default(&e) >= 0); @@ -233,7 +233,7 @@ TEST(real_pressure) { _exit(EXIT_SUCCESS); } - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); assert_se(sd_event_add_child(e, &cs, pid, WEXITED, real_pressure_child_callback, NULL) >= 0); assert_se(sd_event_source_set_child_process_own(cs, true) >= 0); diff --git a/src/test/test-memstream-util.c b/src/test/test-memstream-util.c index 254bdca..b8cc856 100644 --- a/src/test/test-memstream-util.c +++ b/src/test/test-memstream-util.c @@ -17,7 +17,7 @@ TEST(memstream_empty) { assert_se(memstream_init(&m)); assert_se(memstream_finalize(&m, &buf, &sz) >= 0); - assert_se(streq(buf, "")); + ASSERT_STREQ(buf, ""); assert_se(sz == 0); } @@ -32,7 +32,7 @@ TEST(memstream) { fputs("おはよう!", f); fputs(u8"😀😀😀", f); assert_se(memstream_finalize(&m, &buf, &sz) >= 0); - assert_se(streq(buf, u8"hogeおはよう!😀😀😀")); + ASSERT_STREQ(buf, u8"hogeおはよう!😀😀😀"); assert_se(sz == strlen(u8"hogeおはよう!😀😀😀")); buf = mfree(buf); @@ -40,7 +40,7 @@ TEST(memstream) { assert_se(f = memstream_init(&m)); fputs("second", f); assert_se(memstream_finalize(&m, &buf, &sz) >= 0); - assert_se(streq(buf, "second")); + ASSERT_STREQ(buf, "second"); assert_se(sz == strlen("second")); } diff --git a/src/test/test-mkdir.c b/src/test/test-mkdir.c index 4820b32..ef00729 100644 --- a/src/test/test-mkdir.c +++ b/src/test/test-mkdir.c @@ -96,7 +96,7 @@ TEST(mkdir_p_root) { assert_se(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp) >= 0); assert_se(p = path_join(tmp, "run/aaa/bbb")); - assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); + assert_se(mkdir_p_root(tmp, "/run/aaa/bbb", UID_INVALID, GID_INVALID, 0755) >= 0); assert_se(is_dir(p, false) > 0); assert_se(is_dir(p, true) > 0); @@ -109,18 +109,18 @@ TEST(mkdir_p_root) { p = mfree(p); assert_se(p = path_join(tmp, "var/run/hoge/foo/baz")); - assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755, NULL) >= 0); + assert_se(mkdir_p_root(tmp, "/var/run/hoge/foo/baz", UID_INVALID, GID_INVALID, 0755) >= 0); assert_se(is_dir(p, false) > 0); assert_se(is_dir(p, true) > 0); p = mfree(p); assert_se(p = path_join(tmp, "not-exists")); - assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOENT); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOENT); p = mfree(p); assert_se(p = path_join(tmp, "regular-file")); assert_se(touch(p) >= 0); - assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755, NULL) == -ENOTDIR); + assert_se(mkdir_p_root(p, "/aaa", UID_INVALID, GID_INVALID, 0755) == -ENOTDIR); /* FIXME: The tests below do not work. p = mfree(p); @@ -138,4 +138,31 @@ TEST(mkdir_p_root) { */ } +TEST(mkdir_p_root_full) { + _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; + _cleanup_free_ char *p = NULL; + struct stat st; + + ASSERT_OK(mkdtemp_malloc("/tmp/test-mkdir-XXXXXX", &tmp)); + + ASSERT_NOT_NULL(p = path_join(tmp, "foo")); + ASSERT_OK(mkdir_p_root_full(tmp, "/foo", UID_INVALID, GID_INVALID, 0755, 2 * USEC_PER_SEC, NULL)); + ASSERT_GT(is_dir(p, false), 0); + ASSERT_GT(is_dir(p, true), 0); + ASSERT_OK_ERRNO(stat(p, &st)); + ASSERT_EQ(st.st_mtim.tv_sec, 2); + ASSERT_EQ(st.st_atim.tv_sec, 2); + + p = mfree(p); + ASSERT_NOT_NULL(p = path_join(tmp, "dir-not-exists/foo")); + ASSERT_OK(mkdir_p_root_full(NULL, p, UID_INVALID, GID_INVALID, 0755, 90 * USEC_PER_HOUR, NULL)); + ASSERT_GT(is_dir(p, false), 0); + ASSERT_GT(is_dir(p, true), 0); + p = mfree(p); + ASSERT_NOT_NULL(p = path_join(tmp, "dir-not-exists")); + ASSERT_OK_ERRNO(stat(p, &st)); + ASSERT_EQ(st.st_mtim.tv_sec, 90 * 60 * 60); + ASSERT_EQ(st.st_atim.tv_sec, 90 * 60 * 60); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-mount-util.c b/src/test/test-mount-util.c index c3d0acb..4f6da39 100644 --- a/src/test/test-mount-util.c +++ b/src/test/test-mount-util.c @@ -31,56 +31,56 @@ TEST(mount_option_mangle) { assert_se(mount_option_mangle(NULL, MS_RDONLY|MS_NOSUID, &f, &opts) == 0); assert_se(f == (MS_RDONLY|MS_NOSUID)); - assert_se(opts == NULL); + ASSERT_NULL(opts); assert_se(mount_option_mangle("", MS_RDONLY|MS_NOSUID, &f, &opts) == 0); assert_se(f == (MS_RDONLY|MS_NOSUID)); - assert_se(opts == NULL); + ASSERT_NULL(opts); assert_se(mount_option_mangle("ro,nosuid,nodev,noexec", 0, &f, &opts) == 0); assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); - assert_se(opts == NULL); + ASSERT_NULL(opts); assert_se(mount_option_mangle("ro,nosuid,nodev,noexec,mode=0755", 0, &f, &opts) == 0); assert_se(f == (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC)); - assert_se(streq(opts, "mode=0755")); + ASSERT_STREQ(opts, "mode=0755"); opts = mfree(opts); assert_se(mount_option_mangle("rw,nosuid,foo,hogehoge,nodev,mode=0755", 0, &f, &opts) == 0); assert_se(f == (MS_NOSUID|MS_NODEV)); - assert_se(streq(opts, "foo,hogehoge,mode=0755")); + ASSERT_STREQ(opts, "foo,hogehoge,mode=0755"); opts = mfree(opts); assert_se(mount_option_mangle("rw,nosuid,nodev,noexec,relatime,net_cls,net_prio", MS_RDONLY, &f, &opts) == 0); assert_se(f == (MS_NOSUID|MS_NODEV|MS_NOEXEC|MS_RELATIME)); - assert_se(streq(opts, "net_cls,net_prio")); + ASSERT_STREQ(opts, "net_cls,net_prio"); opts = mfree(opts); assert_se(mount_option_mangle("rw,nosuid,nodev,relatime,size=1630748k,mode=0700,uid=1000,gid=1000", MS_RDONLY, &f, &opts) == 0); assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); - assert_se(streq(opts, "size=1630748k,mode=0700,uid=1000,gid=1000")); + ASSERT_STREQ(opts, "size=1630748k,mode=0700,uid=1000,gid=1000"); opts = mfree(opts); assert_se(mount_option_mangle("size=1630748k,rw,gid=1000,,,nodev,relatime,,mode=0700,nosuid,uid=1000", MS_RDONLY, &f, &opts) == 0); assert_se(f == (MS_NOSUID|MS_NODEV|MS_RELATIME)); - assert_se(streq(opts, "size=1630748k,gid=1000,mode=0700,uid=1000")); + ASSERT_STREQ(opts, "size=1630748k,gid=1000,mode=0700,uid=1000"); opts = mfree(opts); assert_se(mount_option_mangle("rw,exec,size=8143984k,nr_inodes=2035996,mode=0755", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, &f, &opts) == 0); assert_se(f == (MS_NOSUID|MS_NODEV)); - assert_se(streq(opts, "size=8143984k,nr_inodes=2035996,mode=0755")); + ASSERT_STREQ(opts, "size=8143984k,nr_inodes=2035996,mode=0755"); opts = mfree(opts); assert_se(mount_option_mangle("rw,relatime,fmask=0022,,,dmask=0022", MS_RDONLY, &f, &opts) == 0); assert_se(f == MS_RELATIME); - assert_se(streq(opts, "fmask=0022,dmask=0022")); + ASSERT_STREQ(opts, "fmask=0022,dmask=0022"); opts = mfree(opts); assert_se(mount_option_mangle("rw,relatime,fmask=0022,dmask=0022,\"hogehoge", MS_RDONLY, &f, &opts) < 0); assert_se(mount_option_mangle("mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"", 0, &f, &opts) == 0); assert_se(f == 0); - assert_se(streq(opts, "mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\"")); + ASSERT_STREQ(opts, "mode=01777,size=10%,nr_inodes=400k,uid=496107520,gid=496107520,context=\"system_u:object_r:svirt_sandbox_file_t:s0:c0,c1\""); opts = mfree(opts); } @@ -91,7 +91,7 @@ static void test_mount_flags_to_string_one(unsigned long flags, const char *expe r = mount_flags_to_string(flags, &x); log_info("flags: %#lX → %d/\"%s\"", flags, r, strnull(x)); assert_se(r >= 0); - assert_se(streq(x, expected)); + ASSERT_STREQ(x, expected); } TEST(mount_flags_to_string) { @@ -213,6 +213,25 @@ TEST(bind_remount_one) { _exit(EXIT_SUCCESS); } + assert_se(wait_for_terminate_and_check("test-remount-one-with-mountinfo", pid, WAIT_LOG) == EXIT_SUCCESS); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + /* child */ + + assert_se(detach_mount_namespace() >= 0); + + assert_se(bind_remount_one("/run", MS_RDONLY, MS_RDONLY) >= 0); + assert_se(bind_remount_one("/run", MS_NOEXEC, MS_RDONLY|MS_NOEXEC) >= 0); + assert_se(bind_remount_one("/proc/idontexist", MS_RDONLY, MS_RDONLY) == -ENOENT); + assert_se(bind_remount_one("/proc/self", MS_RDONLY, MS_RDONLY) == -EINVAL); + assert_se(bind_remount_one("/", MS_RDONLY, MS_RDONLY) >= 0); + + _exit(EXIT_SUCCESS); + } + assert_se(wait_for_terminate_and_check("test-remount-one", pid, WAIT_LOG) == EXIT_SUCCESS); } @@ -276,7 +295,17 @@ TEST(make_mount_switch_root) { assert_se(s); assert_se(touch(s) >= 0); - for (int force_ms_move = 0; force_ms_move < 2; force_ms_move++) { + struct { + const char *path; + bool force_ms_move; + } table[] = { + { t, false }, + { t, true }, + { "/", false }, + { "/", true }, + }; + + FOREACH_ELEMENT(i, table) { r = safe_fork("(switch-root)", FORK_RESET_SIGNALS | FORK_CLOSE_ALL_FDS | @@ -290,12 +319,14 @@ TEST(make_mount_switch_root) { assert_se(r >= 0); if (r == 0) { - assert_se(make_mount_point(t) >= 0); - assert_se(mount_switch_root_full(t, /* mount_propagation_flag= */ 0, force_ms_move) >= 0); + assert_se(make_mount_point(i->path) >= 0); + assert_se(mount_switch_root_full(i->path, /* mount_propagation_flag= */ 0, i->force_ms_move) >= 0); - assert_se(access(ASSERT_PTR(strrchr(s, '/')), F_OK) >= 0); /* absolute */ - assert_se(access(ASSERT_PTR(strrchr(s, '/')) + 1, F_OK) >= 0); /* relative */ - assert_se(access(s, F_OK) < 0 && errno == ENOENT); /* doesn't exist in our new environment */ + if (!path_equal(i->path, "/")) { + assert_se(access(ASSERT_PTR(strrchr(s, '/')), F_OK) >= 0); /* absolute */ + assert_se(access(ASSERT_PTR(strrchr(s, '/')) + 1, F_OK) >= 0); /* relative */ + assert_se(access(s, F_OK) < 0 && errno == ENOENT); /* doesn't exist in our new environment */ + } _exit(EXIT_SUCCESS); } @@ -327,7 +358,7 @@ TEST(umount_recursive) { int r; - FOREACH_ARRAY(t, test_table, ELEMENTSOF(test_table)) { + FOREACH_ELEMENT(t, test_table) { r = safe_fork("(umount-rec)", FORK_RESET_SIGNALS | @@ -496,11 +527,11 @@ TEST(bind_mount_submounts) { free(x); assert_se(x = path_join(b, "x")); - assert_se(path_is_mount_point(x, NULL, 0) > 0); + assert_se(path_is_mount_point(x) > 0); free(x); assert_se(x = path_join(b, "y")); - assert_se(path_is_mount_point(x, NULL, 0) > 0); + assert_se(path_is_mount_point(x) > 0); assert_se(umount_recursive(a, 0) >= 0); assert_se(umount_recursive(b, 0) >= 0); diff --git a/src/test/test-mountpoint-util.c b/src/test/test-mountpoint-util.c index ff447c6..85c0859 100644 --- a/src/test/test-mountpoint-util.c +++ b/src/test/test-mountpoint-util.c @@ -33,7 +33,7 @@ static void test_mount_propagation_flag_one(const char *name, int ret, unsigned if (isempty(name)) assert_se(isempty(c)); else - assert_se(streq(c, name)); + ASSERT_STREQ(c, name); } } @@ -69,7 +69,7 @@ TEST(mnt_id) { assert_se(sscanf(line, "%i %*s %*s %*s %ms", &mnt_id, &path) == 2); #if HAS_FEATURE_MEMORY_SANITIZER /* We don't know the length of the string, so we need to unpoison it one char at a time */ - for (const char *c = path; ;c++) { + for (const char *c = path; ; c++) { msan_unpoison(c, 1); if (!*c) break; @@ -101,7 +101,7 @@ TEST(mnt_id) { * See #11505. */ assert_se(q = hashmap_get(h, INT_TO_PTR(mnt_id2))); - assert_se((r = path_is_mount_point(p, NULL, 0)) >= 0); + assert_se((r = path_is_mount_point_full(p, NULL, 0)) >= 0); if (r == 0) { /* If the path is not a mount point anymore, then it must be a sub directory of * the path corresponds to mnt_id2. */ @@ -123,25 +123,20 @@ TEST(path_is_mount_point) { _cleanup_free_ char *dir1 = NULL, *dir1file = NULL, *dirlink1 = NULL, *dirlink1file = NULL; _cleanup_free_ char *dir2 = NULL, *dir2file = NULL; - assert_se(path_is_mount_point("/", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("/", NULL, 0) > 0); - assert_se(path_is_mount_point("//", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("//", NULL, 0) > 0); + assert_se(path_is_mount_point_full("/", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point_full("/", NULL, 0) > 0); + assert_se(path_is_mount_point_full("//", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point_full("//", NULL, 0) > 0); - assert_se(path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("/proc", NULL, 0) > 0); - assert_se(path_is_mount_point("/proc/", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("/proc/", NULL, 0) > 0); + assert_se(path_is_mount_point_full("/proc", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point_full("/proc", NULL, 0) > 0); + assert_se(path_is_mount_point_full("/proc/", NULL, AT_SYMLINK_FOLLOW) > 0); + assert_se(path_is_mount_point_full("/proc/", NULL, 0) > 0); - assert_se(path_is_mount_point("/proc/1", NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point("/proc/1", NULL, 0) == 0); - assert_se(path_is_mount_point("/proc/1/", NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point("/proc/1/", NULL, 0) == 0); - - assert_se(path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("/sys", NULL, 0) > 0); - assert_se(path_is_mount_point("/sys/", NULL, AT_SYMLINK_FOLLOW) > 0); - assert_se(path_is_mount_point("/sys/", NULL, 0) > 0); + assert_se(path_is_mount_point_full("/proc/1", NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full("/proc/1", NULL, 0) == 0); + assert_se(path_is_mount_point_full("/proc/1/", NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full("/proc/1/", NULL, 0) == 0); /* we'll create a hierarchy of different kinds of dir/file/link * layouts: @@ -157,7 +152,7 @@ TEST(path_is_mount_point) { */ /* file mountpoints */ - assert_se(mkdtemp(tmp_dir) != NULL); + ASSERT_NOT_NULL(mkdtemp(tmp_dir)); file1 = path_join(tmp_dir, "file1"); assert_se(file1); file2 = path_join(tmp_dir, "file2"); @@ -175,10 +170,10 @@ TEST(path_is_mount_point) { assert_se(link1); assert_se(symlink("file2", link2) == 0); - assert_se(path_is_mount_point(file1, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(file1, NULL, 0) == 0); - assert_se(path_is_mount_point(link1, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(link1, NULL, 0) == 0); + assert_se(path_is_mount_point_full(file1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(file1, NULL, 0) == 0); + assert_se(path_is_mount_point_full(link1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(link1, NULL, 0) == 0); /* directory mountpoints */ dir1 = path_join(tmp_dir, "dir1"); @@ -194,10 +189,10 @@ TEST(path_is_mount_point) { assert_se(dir2); assert_se(mkdir(dir2, 0755) == 0); - assert_se(path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(dir1, NULL, 0) == 0); - assert_se(path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(dirlink1, NULL, 0) == 0); + assert_se(path_is_mount_point_full(dir1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(dir1, NULL, 0) == 0); + assert_se(path_is_mount_point_full(dirlink1, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(dirlink1, NULL, 0) == 0); /* file in subdirectory mountpoints */ dir1file = path_join(dir1, "file"); @@ -206,10 +201,10 @@ TEST(path_is_mount_point) { assert_se(fd > 0); close(fd); - assert_se(path_is_mount_point(dir1file, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(dir1file, NULL, 0) == 0); - assert_se(path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW) == 0); - assert_se(path_is_mount_point(dirlink1file, NULL, 0) == 0); + assert_se(path_is_mount_point_full(dir1file, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(dir1file, NULL, 0) == 0); + assert_se(path_is_mount_point_full(dirlink1file, NULL, AT_SYMLINK_FOLLOW) == 0); + assert_se(path_is_mount_point_full(dirlink1file, NULL, 0) == 0); /* these tests will only work as root */ if (mount(file1, file2, NULL, MS_BIND, NULL) >= 0) { @@ -219,17 +214,17 @@ TEST(path_is_mount_point) { /* files */ /* capture results in vars, to avoid dangling mounts on failure */ log_info("%s: %s", __func__, file2); - rf = path_is_mount_point(file2, NULL, 0); - rt = path_is_mount_point(file2, NULL, AT_SYMLINK_FOLLOW); + rf = path_is_mount_point_full(file2, NULL, 0); + rt = path_is_mount_point_full(file2, NULL, AT_SYMLINK_FOLLOW); file2d = strjoina(file2, "/"); log_info("%s: %s", __func__, file2d); - rdf = path_is_mount_point(file2d, NULL, 0); - rdt = path_is_mount_point(file2d, NULL, AT_SYMLINK_FOLLOW); + rdf = path_is_mount_point_full(file2d, NULL, 0); + rdt = path_is_mount_point_full(file2d, NULL, AT_SYMLINK_FOLLOW); log_info("%s: %s", __func__, link2); - rlf = path_is_mount_point(link2, NULL, 0); - rlt = path_is_mount_point(link2, NULL, AT_SYMLINK_FOLLOW); + rlf = path_is_mount_point_full(link2, NULL, 0); + rlt = path_is_mount_point_full(link2, NULL, AT_SYMLINK_FOLLOW); assert_se(umount(file2) == 0); @@ -250,15 +245,15 @@ TEST(path_is_mount_point) { assert_se(mount(dir2, dir1, NULL, MS_BIND, NULL) >= 0); log_info("%s: %s", __func__, dir1); - rf = path_is_mount_point(dir1, NULL, 0); - rt = path_is_mount_point(dir1, NULL, AT_SYMLINK_FOLLOW); + rf = path_is_mount_point_full(dir1, NULL, 0); + rt = path_is_mount_point_full(dir1, NULL, AT_SYMLINK_FOLLOW); log_info("%s: %s", __func__, dirlink1); - rlf = path_is_mount_point(dirlink1, NULL, 0); - rlt = path_is_mount_point(dirlink1, NULL, AT_SYMLINK_FOLLOW); + rlf = path_is_mount_point_full(dirlink1, NULL, 0); + rlt = path_is_mount_point_full(dirlink1, NULL, AT_SYMLINK_FOLLOW); log_info("%s: %s", __func__, dirlink1file); /* its parent is a mount point, but not /file itself */ - rl1f = path_is_mount_point(dirlink1file, NULL, 0); - rl1t = path_is_mount_point(dirlink1file, NULL, AT_SYMLINK_FOLLOW); + rl1f = path_is_mount_point_full(dirlink1file, NULL, 0); + rl1t = path_is_mount_point_full(dirlink1file, NULL, AT_SYMLINK_FOLLOW); assert_se(umount(dir1) == 0); @@ -333,23 +328,23 @@ TEST(mount_option_supported) { r = mount_option_supported("tmpfs", "size", "64M"); log_info("tmpfs supports size=64M: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); - assert_se(r > 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + assert_se(r > 0 || r == -EAGAIN || ERRNO_IS_NEG_PRIVILEGE(r)); r = mount_option_supported("ext4", "discard", NULL); log_info("ext4 supports discard: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); - assert_se(r > 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + assert_se(r > 0 || r == -EAGAIN || ERRNO_IS_NEG_PRIVILEGE(r)); r = mount_option_supported("tmpfs", "idontexist", "64M"); log_info("tmpfs supports idontexist: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); - assert_se(r == 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + assert_se(IN_SET(r, 0, -EAGAIN) || ERRNO_IS_NEG_PRIVILEGE(r)); r = mount_option_supported("tmpfs", "ialsodontexist", NULL); log_info("tmpfs supports ialsodontexist: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); - assert_se(r == 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + assert_se(IN_SET(r, 0, -EAGAIN) || ERRNO_IS_NEG_PRIVILEGE(r)); r = mount_option_supported("proc", "hidepid", "1"); log_info("proc supports hidepid=1: %s (%i)", r < 0 ? "don't know" : yes_no(r), r); - assert_se(r >= 0 || r == -EAGAIN || (r < 0 && ERRNO_IS_PRIVILEGE(r))); + assert_se(r >= 0 || r == -EAGAIN || ERRNO_IS_NEG_PRIVILEGE(r)); } TEST(fstype_can_discard) { @@ -359,9 +354,9 @@ TEST(fstype_can_discard) { } TEST(fstype_can_norecovery) { - assert_se(fstype_can_norecovery("ext4")); - assert_se(!fstype_can_norecovery("vfat")); - assert_se(!fstype_can_norecovery("tmpfs")); + ASSERT_STREQ(fstype_norecovery_option("ext4"), "norecovery"); + ASSERT_NULL(fstype_norecovery_option("vfat")); + ASSERT_NULL(fstype_norecovery_option("tmpfs")); } TEST(fstype_can_umask) { diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index 65d0825..2a684ce 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#include #include #include @@ -84,6 +85,7 @@ TEST(tmpdir) { static void test_shareable_ns(unsigned long nsflag) { _cleanup_close_pair_ int s[2] = EBADF_PAIR; + bool permission_denied = false; pid_t pid1, pid2, pid3; int r, n = 0; siginfo_t si; @@ -100,8 +102,8 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid1 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - _exit(r); + assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } pid2 = fork(); @@ -109,8 +111,8 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid2 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - exit(r); + assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } pid3 = fork(); @@ -118,24 +120,38 @@ static void test_shareable_ns(unsigned long nsflag) { if (pid3 == 0) { r = setup_shareable_ns(s, nsflag); - assert_se(r >= 0); - exit(r); + assert_se(r >= 0 || ERRNO_IS_NEG_PRIVILEGE(r)); + _exit(r >= 0 ? r : EX_NOPERM); } r = wait_for_terminate(pid1, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; r = wait_for_terminate(pid2, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; r = wait_for_terminate(pid3, &si); assert_se(r >= 0); assert_se(si.si_code == CLD_EXITED); - n += si.si_status; + if (si.si_status == EX_NOPERM) + permission_denied = true; + else + n += si.si_status; + + /* LSMs can cause setup_shareable_ns() to fail with permission denied, do not fail the test in that + * case (e.g.: LXC with AppArmor on kernel < v6.2). */ + if (permission_denied) + return (void) log_tests_skipped("insufficient privileges"); assert_se(n == 1); } diff --git a/src/test/test-net-naming-scheme.c b/src/test/test-net-naming-scheme.c index f7ec5a6..a339020 100644 --- a/src/test/test-net-naming-scheme.c +++ b/src/test/test-net-naming-scheme.c @@ -25,7 +25,7 @@ TEST(naming_scheme_conversions) { log_info("latest → %s", n->name); assert_se(n = naming_scheme_from_name("v238")); - assert_se(streq(n->name, "v238")); + ASSERT_STREQ(n->name, "v238"); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c index 6543c61..8c1b0d4 100644 --- a/src/test/test-netlink-manual.c +++ b/src/test/test-netlink-manual.c @@ -1,5 +1,7 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include #include @@ -13,25 +15,29 @@ #include "tests.h" static int load_module(const char *mod_name) { - _cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL; - _cleanup_(kmod_module_unref_listp) struct kmod_list *list = NULL; + _cleanup_(sym_kmod_unrefp) struct kmod_ctx *ctx = NULL; + _cleanup_(sym_kmod_module_unref_listp) struct kmod_list *list = NULL; struct kmod_list *l; int r; - ctx = kmod_new(NULL, NULL); + r = dlopen_libkmod(); + if (r < 0) + return log_error_errno(r, "Failed to load libkmod: %m"); + + ctx = sym_kmod_new(NULL, NULL); if (!ctx) return log_oom(); - r = kmod_module_new_from_lookup(ctx, mod_name, &list); + r = sym_kmod_module_new_from_lookup(ctx, mod_name, &list); if (r < 0) return r; - kmod_list_foreach(l, list) { - _cleanup_(kmod_module_unrefp) struct kmod_module *mod = NULL; + sym_kmod_list_foreach(l, list) { + _cleanup_(sym_kmod_module_unrefp) struct kmod_module *mod = NULL; - mod = kmod_module_get_module(l); + mod = sym_kmod_module_get_module(l); - r = kmod_module_probe_insert_module(mod, 0, NULL, NULL, NULL, NULL); + r = sym_kmod_module_probe_insert_module(mod, 0, NULL, NULL, NULL, NULL); if (r > 0) r = -EINVAL; } @@ -78,7 +84,7 @@ static int test_tunnel_configure(sd_netlink *rtnl) { assert_se(sd_netlink_call(rtnl, m, -1, 0) == 1); - assert_se((m = sd_netlink_message_unref(m)) == NULL); + ASSERT_NULL((m = sd_netlink_message_unref(m))); /* sit */ assert_se(sd_rtnl_message_new_link(rtnl, &n, RTM_NEWLINK, 0) >= 0); @@ -104,7 +110,7 @@ static int test_tunnel_configure(sd_netlink *rtnl) { assert_se(sd_netlink_call(rtnl, n, -1, 0) == 1); - assert_se((n = sd_netlink_message_unref(n)) == NULL); + ASSERT_NULL((n = sd_netlink_message_unref(n))); return EXIT_SUCCESS; } @@ -120,7 +126,7 @@ int main(int argc, char *argv[]) { r = test_tunnel_configure(rtnl); - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + ASSERT_NULL((rtnl = sd_netlink_unref(rtnl))); return r; } diff --git a/src/test/test-nss-hosts.c b/src/test/test-nss-hosts.c index 72a9c64..2f1810d 100644 --- a/src/test/test-nss-hosts.c +++ b/src/test/test-nss-hosts.c @@ -140,7 +140,7 @@ static void test_gethostbyname4_r(void *handle, const char *module, const char * assert_se(status == NSS_STATUS_SUCCESS); assert_se(n == socket_ipv6_is_enabled() + 1); - } else if (streq(module, "resolve") && getenv_bool_secure("SYSTEMD_NSS_RESOLVE_SYNTHESIZE") != 0) { + } else if (streq(module, "resolve") && secure_getenv_bool("SYSTEMD_NSS_RESOLVE_SYNTHESIZE") != 0) { assert_se(status == NSS_STATUS_SUCCESS); if (socket_ipv6_is_enabled()) assert_se(n == 2); diff --git a/src/test/test-nulstr-util.c b/src/test/test-nulstr-util.c index 95c25f1..9b13d3c 100644 --- a/src/test/test-nulstr-util.c +++ b/src/test/test-nulstr-util.c @@ -13,10 +13,10 @@ TEST(strv_split_nulstr) { l = strv_split_nulstr(nulstr); assert_se(l); - assert_se(streq(l[0], "str0")); - assert_se(streq(l[1], "str1")); - assert_se(streq(l[2], "str2")); - assert_se(streq(l[3], "str3")); + ASSERT_STREQ(l[0], "str0"); + ASSERT_STREQ(l[1], "str1"); + ASSERT_STREQ(l[2], "str2"); + ASSERT_STREQ(l[3], "str3"); } #define strv_parse_nulstr_full_one(s, n, e0, e1) \ @@ -97,7 +97,7 @@ static void test_strv_make_nulstr_one(char **l) { assert_se(memcmp_nn(b, n, c, m) == 0); NULSTR_FOREACH(s, b) - assert_se(streq(s, l[i++])); + ASSERT_STREQ(s, l[i++]); assert_se(i == strv_length(l)); } diff --git a/src/test/test-open-file.c b/src/test/test-open-file.c index 4314d0d..22f58af 100644 --- a/src/test/test-open-file.c +++ b/src/test/test-open-file.c @@ -11,32 +11,32 @@ TEST(open_file_parse) { r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == OPENFILE_READ_ONLY); of = open_file_free(of); r = open_file_parse("/proc/1/ns/mnt", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "mnt")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "mnt"); assert_se(of->flags == 0); of = open_file_free(of); r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == 0); of = open_file_free(of); r = open_file_parse("/proc/1/ns/mnt::read-only", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "mnt")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "mnt"); assert_se(of->flags == OPENFILE_READ_ONLY); of = open_file_free(of); @@ -53,16 +53,16 @@ TEST(open_file_parse) { r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:append", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == OPENFILE_APPEND); of = open_file_free(of); r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:truncate", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == OPENFILE_TRUNCATE); of = open_file_free(of); @@ -89,16 +89,16 @@ TEST(open_file_parse) { r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:graceful", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == OPENFILE_GRACEFUL); of = open_file_free(of); r = open_file_parse("/proc/1/ns/mnt:host-mount-namespace:read-only,graceful", &of); assert_se(r >= 0); - assert_se(streq(of->path, "/proc/1/ns/mnt")); - assert_se(streq(of->fdname, "host-mount-namespace")); + ASSERT_STREQ(of->path, "/proc/1/ns/mnt"); + ASSERT_STREQ(of->fdname, "host-mount-namespace"); assert_se(of->flags == (OPENFILE_READ_ONLY | OPENFILE_GRACEFUL)); of = open_file_free(of); @@ -120,7 +120,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace:read-only"); s = mfree(s); of->flags = OPENFILE_APPEND; @@ -128,7 +128,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:append")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace:append"); s = mfree(s); of->flags = OPENFILE_TRUNCATE; @@ -136,7 +136,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:truncate")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace:truncate"); s = mfree(s); of->flags = OPENFILE_GRACEFUL; @@ -144,7 +144,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:graceful")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace:graceful"); s = mfree(s); of->flags = OPENFILE_READ_ONLY | OPENFILE_GRACEFUL; @@ -152,7 +152,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace:read-only,graceful")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace:read-only,graceful"); s = mfree(s); of->flags = 0; @@ -160,7 +160,7 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt:host-mount-namespace")); + ASSERT_STREQ(s, "/proc/1/ns/mnt:host-mount-namespace"); s = mfree(s); assert_se(free_and_strdup(&of->fdname, "mnt")); @@ -169,15 +169,15 @@ TEST(open_file_to_string) { r = open_file_to_string(of, &s); assert_se(r >= 0); - assert_se(streq(s, "/proc/1/ns/mnt::read-only")); + ASSERT_STREQ(s, "/proc/1/ns/mnt::read-only"); s = mfree(s); - assert_se(free_and_strdup(&of->path, "/path:with:colon") >= 0); - assert_se(free_and_strdup(&of->fdname, "path:with:colon") >= 0); + ASSERT_OK(free_and_strdup(&of->path, "/path:with:colon")); + ASSERT_OK(free_and_strdup(&of->fdname, "path:with:colon")); of->flags = 0; - assert_se(open_file_to_string(of, &s) >= 0); - assert_se(streq(s, "/path\\x3awith\\x3acolon")); + ASSERT_OK(open_file_to_string(of, &s)); + ASSERT_STREQ(s, "/path\\x3awith\\x3acolon"); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c index dfdd1ab..d0ea0f0 100644 --- a/src/test/test-openssl.c +++ b/src/test/test-openssl.c @@ -95,7 +95,7 @@ TEST(invalid) { DEFINE_HEX_PTR(key, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b7b"); assert_se(openssl_pkey_from_pem(key, key_len, &pkey) == -EIO); - assert_se(pkey == NULL); + ASSERT_NULL(pkey); } static const struct { @@ -136,7 +136,7 @@ static const struct { TEST(digest_size) { size_t size; - FOREACH_ARRAY(t, digest_size_table, ELEMENTSOF(digest_size_table)) { + FOREACH_ELEMENT(t, digest_size_table) { assert(openssl_digest_size(t->alg, &size) >= 0); assert_se(size == t->size); diff --git a/src/test/test-ordered-set.c b/src/test/test-ordered-set.c index c055411..bb1eefb 100644 --- a/src/test/test-ordered-set.c +++ b/src/test/test-ordered-set.c @@ -81,9 +81,9 @@ TEST(set_put) { * non-trivial hash ops. */ assert_se(t = ordered_set_get_strv(m)); - assert_se(streq(t[0], "1")); - assert_se(streq(t[1], "22")); - assert_se(streq(t[2], "333")); + ASSERT_STREQ(t[0], "1"); + ASSERT_STREQ(t[1], "22"); + ASSERT_STREQ(t[2], "333"); assert_se(!t[3]); ordered_set_print(stdout, "FOO=", m); diff --git a/src/test/test-os-util.c b/src/test/test-os-util.c index 84e55e1..55475a5 100644 --- a/src/test/test-os-util.c +++ b/src/test/test-os-util.c @@ -14,47 +14,49 @@ #include "tmpfile-util.h" TEST(path_is_os_tree) { - assert_se(path_is_os_tree("/") > 0); - assert_se(path_is_os_tree("/etc") == 0); + ASSERT_GT(path_is_os_tree("/"), 0); + ASSERT_EQ(path_is_os_tree("/etc"), 0); assert_se(path_is_os_tree("/idontexist") == -ENOENT); } TEST(parse_os_release) { - /* Let's assume that we're running in a valid system, so os-release is available */ _cleanup_free_ char *id = NULL, *id2 = NULL, *name = NULL, *foobar = NULL; - assert_se(parse_os_release(NULL, "ID", &id) == 0); - log_info("ID: %s", id); - assert_se(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1) == 0); - assert_se(parse_os_release(NULL, "ID", &id2) == 0); + if (access("/etc/os-release", F_OK) >= 0 || access("/usr/lib/os-release", F_OK) >= 0) { + ASSERT_EQ(parse_os_release(NULL, "ID", &id), 0); + log_info("ID: %s", id); + } + + ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1), 0); + ASSERT_EQ(parse_os_release(NULL, "ID", &id2), 0); log_info("ID: %s", strnull(id2)); _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX"; - assert_se(write_tmpfile(tmpfile, + ASSERT_EQ(write_tmpfile(tmpfile, "ID=the-id \n" - "NAME=the-name") == 0); + "NAME=the-name"), 0); - assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0); - assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0); + ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1), 0); + ASSERT_EQ(parse_os_release(NULL, "ID", &id, "NAME", &name), 0); log_info("ID: %s NAME: %s", id, name); - assert_se(streq(id, "the-id")); - assert_se(streq(name, "the-name")); + ASSERT_STREQ(id, "the-id"); + ASSERT_STREQ(name, "the-name"); _cleanup_(unlink_tempfilep) char tmpfile2[] = "/tmp/test-os-util.XXXXXX"; - assert_se(write_tmpfile(tmpfile2, + ASSERT_EQ(write_tmpfile(tmpfile2, "ID=\"ignored\" \n" "ID=\"the-id\" \n" - "NAME='the-name'") == 0); + "NAME='the-name'"), 0); - assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile2, 1) == 0); - assert_se(parse_os_release(NULL, "ID", &id, "NAME", &name) == 0); + ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile2, 1), 0); + ASSERT_EQ(parse_os_release(NULL, "ID", &id, "NAME", &name), 0); log_info("ID: %s NAME: %s", id, name); - assert_se(streq(id, "the-id")); - assert_se(streq(name, "the-name")); + ASSERT_STREQ(id, "the-id"); + ASSERT_STREQ(name, "the-name"); - assert_se(parse_os_release(NULL, "FOOBAR", &foobar) == 0); + ASSERT_EQ(parse_os_release(NULL, "FOOBAR", &foobar), 0); log_info("FOOBAR: %s", strnull(foobar)); - assert_se(foobar == NULL); + ASSERT_NULL(foobar); assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); } @@ -70,6 +72,7 @@ TEST(parse_extension_release) { assert_se(a = path_join(tempdir, "/usr/lib/extension-release.d/extension-release.test")); assert_se(mkdir_parents(a, 0777) >= 0); + ASSERT_GE(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) @@ -77,8 +80,8 @@ TEST(parse_extension_release) { 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_STREQ(id, "the-id"); + ASSERT_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); @@ -87,42 +90,42 @@ TEST(parse_extension_release) { 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); + ASSERT_EQ(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_STREQ(id, "the-id"); + ASSERT_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_NULL(foobar); assert_se(parse_extension_release(tempdir, IMAGE_SYSEXT, "test", false, "FOOBAR", &foobar) == 0); log_info("FOOBAR: %s", strnull(foobar)); - assert_se(foobar == NULL); + ASSERT_NULL(foobar); } TEST(load_os_release_pairs) { _cleanup_(unlink_tempfilep) char tmpfile[] = "/tmp/test-os-util.XXXXXX"; - assert_se(write_tmpfile(tmpfile, + ASSERT_EQ(write_tmpfile(tmpfile, "ID=\"ignored\" \n" "ID=\"the-id\" \n" - "NAME='the-name'") == 0); + "NAME='the-name'"), 0); - assert_se(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1) == 0); + ASSERT_EQ(setenv("SYSTEMD_OS_RELEASE", tmpfile, 1), 0); _cleanup_strv_free_ char **pairs = NULL; - assert_se(load_os_release_pairs(NULL, &pairs) == 0); + ASSERT_EQ(load_os_release_pairs(NULL, &pairs), 0); assert_se(strv_equal(pairs, STRV_MAKE("ID", "the-id", "NAME", "the-name"))); - assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); + ASSERT_EQ(unsetenv("SYSTEMD_OS_RELEASE"), 0); } TEST(os_release_support_ended) { int r; - assert_se(os_release_support_ended("1999-01-01", false, NULL) == true); - assert_se(os_release_support_ended("2037-12-31", false, NULL) == false); + ASSERT_TRUE(os_release_support_ended("1999-01-01", false, NULL)); + ASSERT_FALSE(os_release_support_ended("2037-12-31", false, NULL)); assert_se(os_release_support_ended("-1-1-1", true, NULL) == -EINVAL); r = os_release_support_ended(NULL, false, NULL); diff --git a/src/test/test-parse-argument.c b/src/test/test-parse-argument.c index cf3d542..c07b2d9 100644 --- a/src/test/test-parse-argument.c +++ b/src/test/test-parse-argument.c @@ -20,13 +20,13 @@ TEST(parse_path_argument) { _cleanup_free_ char *path = NULL; assert_se(parse_path_argument("help", false, &path) == 0); - assert_se(streq(basename(path), "help")); + ASSERT_STREQ(basename(path), "help"); assert_se(parse_path_argument("/", false, &path) == 0); - assert_se(streq(path, "/")); + ASSERT_STREQ(path, "/"); assert_se(parse_path_argument("/", true, &path) == 0); - assert_se(path == NULL); + ASSERT_NULL(path); } TEST(parse_signal_argument) { diff --git a/src/test/test-parse-helpers.c b/src/test/test-parse-helpers.c index 052e251..20d4c2f 100644 --- a/src/test/test-parse-helpers.c +++ b/src/test/test-parse-helpers.c @@ -37,6 +37,7 @@ static void test_invalid_item(const char *str) { TEST(valid_items) { test_valid_item("any", AF_UNSPEC, 0, 0, 0); + test_valid_item("0-65535", AF_UNSPEC, 0, 0, 0); test_valid_item("ipv4", AF_INET, 0, 0, 0); test_valid_item("ipv6", AF_INET6, 0, 0, 0); test_valid_item("ipv4:any", AF_INET, 0, 0, 0); @@ -45,6 +46,7 @@ TEST(valid_items) { test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0); test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0); test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0); + test_valid_item("0", AF_UNSPEC, 0, 1, 0); test_valid_item("6666", AF_UNSPEC, 0, 1, 6666); test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666); test_valid_item("65535", AF_UNSPEC, 0, 1, 65535); @@ -61,6 +63,7 @@ TEST(valid_items) { test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666); test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666); test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0); + test_valid_item("ipv6:tcp:0", AF_INET6, IPPROTO_TCP, 1, 0); } TEST(invalid_items) { @@ -77,9 +80,7 @@ TEST(invalid_items) { test_invalid_item("ipv6::"); test_invalid_item("ipv6:ipv6"); test_invalid_item("ipv6:icmp"); - test_invalid_item("ipv6:tcp:0"); test_invalid_item("65536"); - test_invalid_item("0-65535"); test_invalid_item("ipv6:tcp:6666-6665"); test_invalid_item("ipv6:tcp:6666-100000"); test_invalid_item("ipv6::6666"); @@ -92,4 +93,39 @@ TEST(invalid_items) { test_invalid_item("ipv6:tcp:6666\n zupa"); } +static int test_path_simplify_and_warn_one(const char *p, const char *q, PathSimplifyWarnFlags f) { + _cleanup_free_ char *s = ASSERT_PTR(strdup(p)); + int a, b; + + a = path_simplify_and_warn(s, f, /* unit= */ NULL, /* filename= */ NULL, /* line= */ 0, "Foobar="); + assert(streq_ptr(s, q)); + + free(s); + s = ASSERT_PTR(strdup(p)); + + b = path_simplify_and_warn(s, f|PATH_CHECK_FATAL, /* unit= */ NULL, /* filename= */ NULL, /* line= */ 0, "Foobar="); + assert(streq_ptr(s, q)); + + assert(a == b); + + return a; +} + +TEST(path_simplify_and_warn) { + + assert_se(test_path_simplify_and_warn_one("", "", 0) == -EINVAL); + assert_se(test_path_simplify_and_warn_one("/", "/", 0) == 0); + assert_se(test_path_simplify_and_warn_one("/foo/../bar", "/foo/../bar", 0) == -EINVAL); + assert_se(test_path_simplify_and_warn_one("/foo/./bar", "/foo/bar", 0) == 0); + assert_se(test_path_simplify_and_warn_one("/proc/self///fd", "/proc/self/fd", 0) == 0); + assert_se(test_path_simplify_and_warn_one("/proc/self///fd", "/proc/self/fd", PATH_CHECK_NON_API_VFS) == -EINVAL); + assert_se(test_path_simplify_and_warn_one("aaaa", "aaaa", 0) == 0); + assert_se(test_path_simplify_and_warn_one("aaaa", "aaaa", PATH_CHECK_ABSOLUTE) == -EINVAL); + assert_se(test_path_simplify_and_warn_one("aaaa", "aaaa", PATH_CHECK_RELATIVE) == 0); + assert_se(test_path_simplify_and_warn_one("/aaaa", "/aaaa", 0) == 0); + assert_se(test_path_simplify_and_warn_one("/aaaa", "/aaaa", PATH_CHECK_ABSOLUTE) == 0); + assert_se(test_path_simplify_and_warn_one("/aaaa", "/aaaa", PATH_CHECK_RELATIVE) == -EINVAL); + +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-path-lookup.c b/src/test/test-path-lookup.c index 431a859..cff2774 100644 --- a/src/test/test-path-lookup.c +++ b/src/test/test-path-lookup.c @@ -13,8 +13,8 @@ static void test_paths_one(RuntimeScope scope) { _cleanup_(rm_rf_physical_and_freep) char *tmp = NULL; - _cleanup_(lookup_paths_free) LookupPaths lp_without_env = {}; - _cleanup_(lookup_paths_free) LookupPaths lp_with_env = {}; + _cleanup_(lookup_paths_done) LookupPaths lp_without_env = {}; + _cleanup_(lookup_paths_done) LookupPaths lp_with_env = {}; char *systemd_unit_path; assert_se(mkdtemp_malloc("/tmp/test-path-lookup.XXXXXXX", &tmp) >= 0); @@ -28,7 +28,7 @@ static void test_paths_one(RuntimeScope scope) { assert_se(setenv("SYSTEMD_UNIT_PATH", systemd_unit_path, 1) == 0); assert_se(lookup_paths_init(&lp_with_env, scope, 0, NULL) == 0); assert_se(strv_length(lp_with_env.search_path) == 1); - assert_se(streq(lp_with_env.search_path[0], systemd_unit_path)); + ASSERT_STREQ(lp_with_env.search_path[0], systemd_unit_path); lookup_paths_log(&lp_with_env); assert_se(strv_equal(lp_with_env.search_path, STRV_MAKE(systemd_unit_path))); } @@ -40,7 +40,7 @@ TEST(paths) { } TEST(user_and_global_paths) { - _cleanup_(lookup_paths_free) LookupPaths lp_global = {}, lp_user = {}; + _cleanup_(lookup_paths_done) LookupPaths lp_global = {}, lp_user = {}; char **u, **g; unsigned k = 0; diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index f5a4256..e02bd8c 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -18,30 +18,32 @@ #include "tmpfile-util.h" TEST(print_paths) { - log_info("DEFAULT_PATH=%s", DEFAULT_PATH); - log_info("DEFAULT_USER_PATH=%s", DEFAULT_USER_PATH); + log_info("default system PATH: %s", default_PATH()); + log_info("default user PATH: %s", default_user_PATH()); } TEST(path) { - assert_se(path_is_absolute("/")); + assert_se( path_is_absolute("/")); assert_se(!path_is_absolute("./")); - assert_se(streq(basename("./aa/bb/../file.da."), "file.da.")); - assert_se(streq(basename("/aa///.file"), ".file")); - assert_se(streq(basename("/aa///file..."), "file...")); - assert_se(streq(basename("file.../"), "")); + ASSERT_STREQ(basename("./aa/bb/../file.da."), "file.da."); + ASSERT_STREQ(basename("/aa///.file"), ".file"); + ASSERT_STREQ(basename("/aa///file..."), "file..."); + ASSERT_STREQ(basename("file.../"), ""); - assert_se(PATH_IN_SET("/bin", "/", "/bin", "/foo")); - assert_se(PATH_IN_SET("/bin", "/bin")); - assert_se(PATH_IN_SET("/bin", "/foo/bar", "/bin")); - assert_se(PATH_IN_SET("/", "/", "/", "/foo/bar")); + assert_se( PATH_IN_SET("/bin", "/", "/bin", "/foo")); + assert_se( PATH_IN_SET("/bin", "/bin")); + assert_se( PATH_IN_SET("/bin", "/foo/bar", "/bin")); + assert_se( PATH_IN_SET("/", "/", "/", "/foo/bar")); assert_se(!PATH_IN_SET("/", "/abc", "/def")); - assert_se(path_equal_ptr(NULL, NULL)); - assert_se(path_equal_ptr("/a", "/a")); - 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(NULL, NULL)); + assert_se( path_equal("/a", "/a")); + assert_se(!path_equal("/a", "/b")); + assert_se(!path_equal("/a", NULL)); + assert_se(!path_equal(NULL, "/a")); + assert_se(!path_equal("a", NULL)); + assert_se(!path_equal(NULL, "a")); } TEST(is_path) { @@ -144,7 +146,7 @@ static void test_path_simplify_one(const char *in, const char *out, PathSimplify p = strdupa_safe(in); path_simplify_full(p, flags); log_debug("/* test_path_simplify(%s) → %s (expected: %s) */", in, p, out); - assert_se(streq(p, out)); + ASSERT_STREQ(p, out); } TEST(path_simplify) { @@ -377,12 +379,12 @@ TEST(find_executable_full) { assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); puts(p); - assert_se(streq(basename(p), "sh")); + ASSERT_STREQ(basename(p), "sh"); free(p); assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); puts(p); - assert_se(streq(basename(p), "sh")); + ASSERT_STREQ(basename(p), "sh"); free(p); _cleanup_free_ char *oldpath = NULL; @@ -394,12 +396,12 @@ TEST(find_executable_full) { assert_se(find_executable_full("sh", NULL, NULL, true, &p, NULL) == 0); puts(p); - assert_se(streq(basename(p), "sh")); + ASSERT_STREQ(basename(p), "sh"); free(p); assert_se(find_executable_full("sh", NULL, NULL, false, &p, NULL) == 0); puts(p); - assert_se(streq(basename(p), "sh")); + ASSERT_STREQ(basename(p), "sh"); free(p); if (oldpath) @@ -412,7 +414,7 @@ TEST(find_executable_full) { assert_se(find_executable_full(test_file_name, NULL, STRV_MAKE("/doesnotexist", "/tmp", "/bin"), false, &p, NULL) == 0); puts(p); - assert_se(streq(p, fn)); + ASSERT_STREQ(p, fn); free(p); (void) unlink(fn); @@ -440,12 +442,12 @@ TEST(find_executable) { free(p); assert_se(find_executable("/bin/touch", &p) == 0); - assert_se(streq(p, "/bin/touch")); + ASSERT_STREQ(p, "/bin/touch"); free(p); assert_se(find_executable("touch", &p) == 0); assert_se(path_is_absolute(p)); - assert_se(streq(basename(p), "touch")); + ASSERT_STREQ(basename(p), "touch"); free(p); assert_se(find_executable("xxxx-xxxx", &p) == -ENOENT); @@ -466,7 +468,7 @@ static void test_find_executable_exec_one(const char *path) { assert_se(fd > STDERR_FILENO); assert_se(path_is_absolute(t)); if (path_is_absolute(path)) - assert_se(streq(t, path)); + ASSERT_STREQ(t, path); pid = fork(); assert_se(pid >= 0); @@ -504,26 +506,26 @@ TEST(prefixes) { i = 0; PATH_FOREACH_PREFIX_MORE(s, "/a/b/c/d") { log_error("---%s---", s); - assert_se(streq(s, values[i++])); + ASSERT_STREQ(s, values[i++]); } - assert_se(values[i] == NULL); + ASSERT_NULL(values[i]); i = 1; PATH_FOREACH_PREFIX(s, "/a/b/c/d") { log_error("---%s---", s); - assert_se(streq(s, values[i++])); + ASSERT_STREQ(s, values[i++]); } - assert_se(values[i] == NULL); + ASSERT_NULL(values[i]); i = 0; PATH_FOREACH_PREFIX_MORE(s, "////a////b////c///d///////") - assert_se(streq(s, values[i++])); - assert_se(values[i] == NULL); + ASSERT_STREQ(s, values[i++]); + ASSERT_NULL(values[i]); i = 1; PATH_FOREACH_PREFIX(s, "////a////b////c///d///////") - assert_se(streq(s, values[i++])); - assert_se(values[i] == NULL); + ASSERT_STREQ(s, values[i++]); + ASSERT_NULL(values[i]); PATH_FOREACH_PREFIX(s, "////") assert_not_reached(); @@ -531,7 +533,7 @@ TEST(prefixes) { b = false; PATH_FOREACH_PREFIX_MORE(s, "////") { assert_se(!b); - assert_se(streq(s, "")); + ASSERT_STREQ(s, ""); b = true; } assert_se(b); @@ -542,7 +544,7 @@ TEST(prefixes) { b = false; PATH_FOREACH_PREFIX_MORE(s, "") { assert_se(!b); - assert_se(streq(s, "")); + ASSERT_STREQ(s, ""); b = true; } } @@ -552,7 +554,7 @@ TEST(path_join) { _cleanup_free_ char *z = NULL; \ z = path_join(__VA_ARGS__); \ log_debug("got \"%s\", expected \"%s\"", z, expected); \ - assert_se(streq(z, expected)); \ + ASSERT_STREQ(z, expected); \ } test_join("/root/a/b/c", "/root", "/a/b", "/c"); @@ -595,25 +597,25 @@ TEST(path_extend) { _cleanup_free_ char *p = NULL; assert_se(path_extend(&p, "foo", "bar", "baz") == p); - assert_se(streq(p, "foo/bar/baz")); + ASSERT_STREQ(p, "foo/bar/baz"); assert_se(path_extend(&p, "foo", "bar", "baz") == p); - assert_se(streq(p, "foo/bar/baz/foo/bar/baz")); + ASSERT_STREQ(p, "foo/bar/baz/foo/bar/baz"); p = mfree(p); assert_se(path_extend(&p, "foo") == p); - assert_se(streq(p, "foo")); + ASSERT_STREQ(p, "foo"); assert_se(path_extend(&p, "/foo") == p); - assert_se(streq(p, "foo/foo")); + ASSERT_STREQ(p, "foo/foo"); assert_se(path_extend(&p, "/waaaah/wahhh//") == p); - assert_se(streq(p, "foo/foo/waaaah/wahhh//")); /* path_extend() does not drop redundant slashes */ + ASSERT_STREQ(p, "foo/foo/waaaah/wahhh//"); /* path_extend() does not drop redundant slashes */ assert_se(path_extend(&p, "/aaa/bbb/") == p); - assert_se(streq(p, "foo/foo/waaaah/wahhh///aaa/bbb/")); /* but not add an extra slash */ + ASSERT_STREQ(p, "foo/foo/waaaah/wahhh///aaa/bbb/"); /* but not add an extra slash */ assert_se(free_and_strdup(&p, "/") >= 0); assert_se(path_extend(&p, "foo") == p); - assert_se(streq(p, "/foo")); + ASSERT_STREQ(p, "/foo"); } TEST(fsck_exists) { @@ -635,7 +637,7 @@ static void test_path_make_relative_one(const char *from, const char *to, const r = path_make_relative(from, to, &z); assert_se((r >= 0) == !!expected); - assert_se(streq_ptr(z, expected)); + ASSERT_STREQ(z, expected); } TEST(path_make_relative) { @@ -661,7 +663,7 @@ static void test_path_make_relative_parent_one(const char *from, const char *to, r = path_make_relative_parent(from, to, &z); assert_se((r >= 0) == !!expected); - assert_se(streq_ptr(z, expected)); + ASSERT_STREQ(z, expected); } TEST(path_make_relative_parent) { @@ -685,7 +687,7 @@ TEST(path_strv_resolve) { _cleanup_strv_free_ char **search_dirs = NULL; _cleanup_strv_free_ char **absolute_dirs = NULL; - assert_se(mkdtemp(tmp_dir) != NULL); + ASSERT_NOT_NULL(mkdtemp(tmp_dir)); search_dirs = strv_new("/dir1", "/dir2", "/dir3"); assert_se(search_dirs); @@ -700,9 +702,9 @@ TEST(path_strv_resolve) { assert_se(symlink("dir2", absolute_dirs[2]) == 0); path_strv_resolve(search_dirs, tmp_dir); - assert_se(streq(search_dirs[0], "/dir1")); - assert_se(streq(search_dirs[1], "/dir2")); - assert_se(streq(search_dirs[2], "/dir2")); + ASSERT_STREQ(search_dirs[0], "/dir1"); + ASSERT_STREQ(search_dirs[1], "/dir2"); + ASSERT_STREQ(search_dirs[2], "/dir2"); assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); } @@ -713,10 +715,10 @@ static void test_path_startswith_one(const char *path, const char *prefix, const log_debug("/* %s(%s, %s) */", __func__, path, prefix); p = path_startswith(path, prefix); - assert_se(streq_ptr(p, expected)); + ASSERT_STREQ(p, expected); if (p) { q = strjoina(skipped, p); - assert_se(streq(q, path)); + ASSERT_STREQ(q, path); assert_se(p == path + strlen(skipped)); } } @@ -759,11 +761,11 @@ static void test_prefix_root_one(const char *r, const char *p, const char *expec const char *t; assert_se(s = path_join(r, p)); - assert_se(path_equal_ptr(s, expected)); + assert_se(path_equal(s, expected)); t = prefix_roota(r, p); assert_se(t); - assert_se(path_equal_ptr(t, expected)); + assert_se(path_equal(t, expected)); } TEST(prefix_root) { @@ -791,17 +793,17 @@ TEST(file_in_same_dir) { assert_se(file_in_same_dir("/", "a", &t) == -EADDRNOTAVAIL); assert_se(file_in_same_dir("/", "/a", &t) >= 0); - assert_se(streq(t, "/a")); + ASSERT_STREQ(t, "/a"); free(t); assert_se(file_in_same_dir("", "a", &t) == -EINVAL); assert_se(file_in_same_dir("a/", "x", &t) >= 0); - assert_se(streq(t, "x")); + ASSERT_STREQ(t, "x"); free(t); assert_se(file_in_same_dir("bar/foo", "bar", &t) >= 0); - assert_se(streq(t, "bar/bar")); + ASSERT_STREQ(t, "bar/bar"); free(t); } @@ -984,23 +986,23 @@ TEST(path_find_last_component) { } TEST(last_path_component) { - assert_se(last_path_component(NULL) == NULL); - assert_se(streq(last_path_component("a/b/c"), "c")); - assert_se(streq(last_path_component("a/b/c/"), "c/")); - assert_se(streq(last_path_component("/"), "/")); - assert_se(streq(last_path_component("//"), "/")); - assert_se(streq(last_path_component("///"), "/")); - assert_se(streq(last_path_component("."), ".")); - assert_se(streq(last_path_component("./."), ".")); - assert_se(streq(last_path_component("././"), "./")); - assert_se(streq(last_path_component("././/"), ".//")); - assert_se(streq(last_path_component("/foo/a"), "a")); - assert_se(streq(last_path_component("/foo/a/"), "a/")); - assert_se(streq(last_path_component(""), "")); - assert_se(streq(last_path_component("a"), "a")); - assert_se(streq(last_path_component("a/"), "a/")); - assert_se(streq(last_path_component("/a"), "a")); - assert_se(streq(last_path_component("/a/"), "a/")); + ASSERT_NULL(last_path_component(NULL)); + ASSERT_STREQ(last_path_component("a/b/c"), "c"); + ASSERT_STREQ(last_path_component("a/b/c/"), "c/"); + ASSERT_STREQ(last_path_component("/"), "/"); + ASSERT_STREQ(last_path_component("//"), "/"); + ASSERT_STREQ(last_path_component("///"), "/"); + ASSERT_STREQ(last_path_component("."), "."); + ASSERT_STREQ(last_path_component("./."), "."); + ASSERT_STREQ(last_path_component("././"), "./"); + ASSERT_STREQ(last_path_component("././/"), ".//"); + ASSERT_STREQ(last_path_component("/foo/a"), "a"); + ASSERT_STREQ(last_path_component("/foo/a/"), "a/"); + ASSERT_STREQ(last_path_component(""), ""); + ASSERT_STREQ(last_path_component("a"), "a"); + ASSERT_STREQ(last_path_component("a/"), "a/"); + ASSERT_STREQ(last_path_component("/a"), "a"); + ASSERT_STREQ(last_path_component("/a/"), "a/"); } static void test_path_extract_filename_one(const char *input, const char *output, int ret) { @@ -1012,7 +1014,7 @@ static void test_path_extract_filename_one(const char *input, const char *output strnull(input), strnull(k), r < 0 ? STRERROR(r) : "-", strnull(output), ret < 0 ? STRERROR(ret) : "-"); - assert_se(streq_ptr(k, output)); + ASSERT_STREQ(k, output); assert_se(r == ret); } @@ -1056,7 +1058,7 @@ static void test_path_extract_directory_one(const char *input, const char *outpu strnull(input), strnull(k), r < 0 ? STRERROR(r) : "-", strnull(output), STRERROR(ret)); - assert_se(streq_ptr(k, output)); + ASSERT_STREQ(k, output); assert_se(r == ret); /* Extra safety check: let's make sure that if we split out the filename too (and it works) the @@ -1190,17 +1192,17 @@ TEST(hidden_or_backup_file) { } TEST(skip_dev_prefix) { - assert_se(streq(skip_dev_prefix("/"), "/")); - assert_se(streq(skip_dev_prefix("/dev"), "")); - assert_se(streq(skip_dev_prefix("/dev/"), "")); - assert_se(streq(skip_dev_prefix("/dev/foo"), "foo")); - assert_se(streq(skip_dev_prefix("/dev/foo/bar"), "foo/bar")); - assert_se(streq(skip_dev_prefix("//dev"), "")); - assert_se(streq(skip_dev_prefix("//dev//"), "")); - assert_se(streq(skip_dev_prefix("/dev///foo"), "foo")); - assert_se(streq(skip_dev_prefix("///dev///foo///bar"), "foo///bar")); - assert_se(streq(skip_dev_prefix("//foo"), "//foo")); - assert_se(streq(skip_dev_prefix("foo"), "foo")); + ASSERT_STREQ(skip_dev_prefix("/"), "/"); + ASSERT_STREQ(skip_dev_prefix("/dev"), ""); + ASSERT_STREQ(skip_dev_prefix("/dev/"), ""); + ASSERT_STREQ(skip_dev_prefix("/dev/foo"), "foo"); + ASSERT_STREQ(skip_dev_prefix("/dev/foo/bar"), "foo/bar"); + ASSERT_STREQ(skip_dev_prefix("//dev"), ""); + ASSERT_STREQ(skip_dev_prefix("//dev//"), ""); + ASSERT_STREQ(skip_dev_prefix("/dev///foo"), "foo"); + ASSERT_STREQ(skip_dev_prefix("///dev///foo///bar"), "foo///bar"); + ASSERT_STREQ(skip_dev_prefix("//foo"), "//foo"); + ASSERT_STREQ(skip_dev_prefix("foo"), "foo"); } TEST(empty_or_root) { @@ -1217,43 +1219,43 @@ TEST(empty_or_root) { } TEST(path_startswith_set) { - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/bar", "/zzz"), "")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/", "/zzz"), "bar")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo", "/zzz"), "bar")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/", "/zzz"), "foo/bar")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "", "/zzz"), NULL)); - - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/bar", "/zzz"), NULL)); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/", "/zzz"), "bar2")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo", "/zzz"), "bar2")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/", "/zzz"), "foo/bar2")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "", "/zzz"), NULL)); - - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/bar", "/zzz"), NULL)); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/", "/zzz"), NULL)); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo", "/zzz"), NULL)); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/", "/zzz"), "foo2/bar")); - assert_se(streq_ptr(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL)); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/bar", "/zzz"), ""); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo/", "/zzz"), "bar"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/foo", "/zzz"), "bar"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "/", "/zzz"), "foo/bar"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar", "/foo/quux", "", "/zzz"), NULL); + + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/bar", "/zzz"), NULL); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo/", "/zzz"), "bar2"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/foo", "/zzz"), "bar2"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "/", "/zzz"), "foo/bar2"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo/bar2", "/foo/quux", "", "/zzz"), NULL); + + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/bar", "/zzz"), NULL); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo/", "/zzz"), NULL); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/foo", "/zzz"), NULL); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "/", "/zzz"), "foo2/bar"); + ASSERT_STREQ(PATH_STARTSWITH_SET("/foo2/bar", "/foo/quux", "", "/zzz"), NULL); } TEST(path_startswith_strv) { - assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), "")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); - - assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL)); - assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar2")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar2")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar2")); - assert_se(streq_ptr(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); - - assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL)); - assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), NULL)); - assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), NULL)); - assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo2/bar")); - assert_se(streq_ptr(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL)); + ASSERT_STREQ(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), ""); + ASSERT_STREQ(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar"); + ASSERT_STREQ(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar"); + ASSERT_STREQ(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar"); + ASSERT_STREQ(path_startswith_strv("/foo/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL); + + ASSERT_STREQ(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL); + ASSERT_STREQ(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), "bar2"); + ASSERT_STREQ(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/foo", "/zzz")), "bar2"); + ASSERT_STREQ(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo/bar2"); + ASSERT_STREQ(path_startswith_strv("/foo/bar2", STRV_MAKE("/foo/quux", "", "/zzz")), NULL); + + ASSERT_STREQ(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/bar", "/zzz")), NULL); + ASSERT_STREQ(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo/", "/zzz")), NULL); + ASSERT_STREQ(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/foo", "/zzz")), NULL); + ASSERT_STREQ(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "/", "/zzz")), "foo2/bar"); + ASSERT_STREQ(path_startswith_strv("/foo2/bar", STRV_MAKE("/foo/quux", "", "/zzz")), NULL); } static void test_path_glob_can_match_one(const char *pattern, const char *prefix, const char *expected) { @@ -1262,7 +1264,7 @@ static void test_path_glob_can_match_one(const char *pattern, const char *prefix log_debug("%s(%s, %s, %s)", __func__, pattern, prefix, strnull(expected)); assert_se(path_glob_can_match(pattern, prefix, &result) == !!expected); - assert_se(streq_ptr(result, expected)); + ASSERT_STREQ(result, expected); } TEST(path_glob_can_match) { @@ -1305,4 +1307,28 @@ TEST(print_MAX) { assert_cc(FILENAME_MAX == PATH_MAX); } +TEST(path_implies_directory) { + assert_se(!path_implies_directory(NULL)); + assert_se(!path_implies_directory("")); + assert_se(path_implies_directory("/")); + assert_se(path_implies_directory("////")); + assert_se(path_implies_directory("////.///")); + assert_se(path_implies_directory("////./")); + assert_se(path_implies_directory("////.")); + assert_se(path_implies_directory(".")); + assert_se(path_implies_directory("./")); + assert_se(path_implies_directory("/.")); + assert_se(path_implies_directory("..")); + assert_se(path_implies_directory("../")); + assert_se(path_implies_directory("/..")); + assert_se(!path_implies_directory("a")); + assert_se(!path_implies_directory("ab")); + assert_se(path_implies_directory("ab/")); + assert_se(!path_implies_directory("ab/a")); + assert_se(path_implies_directory("ab/a/")); + assert_se(path_implies_directory("ab/a/..")); + assert_se(path_implies_directory("ab/a/.")); + assert_se(path_implies_directory("ab/a//")); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-path.c b/src/test/test-path.c index 22ed88f..e49653a 100644 --- a/src/test/test-path.c +++ b/src/test/test-path.c @@ -100,7 +100,8 @@ static int _check_states(unsigned line, service_state_to_string(service->state), service_result_to_string(service->result)); - if (service->state == SERVICE_FAILED && service->main_exec_status.status == EXIT_CGROUP) { + if (service->state == SERVICE_FAILED && + (service->main_exec_status.status == EXIT_CGROUP || service->result == SERVICE_FAILURE_RESOURCES)) { const char *ci = ci_environment(); /* On a general purpose system we may fail to start the service for reasons which are diff --git a/src/test/test-pidref.c b/src/test/test-pidref.c new file mode 100644 index 0000000..2c4d894 --- /dev/null +++ b/src/test/test-pidref.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "pidref.h" +#include "process-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "tests.h" + +TEST(pidref_is_set) { + assert_se(!pidref_is_set(NULL)); + assert_se(!pidref_is_set(&PIDREF_NULL)); + assert_se(pidref_is_set(&PIDREF_MAKE_FROM_PID(1))); +} + +TEST(pidref_equal) { + assert_se(pidref_equal(NULL, NULL)); + assert_se(pidref_equal(NULL, &PIDREF_NULL)); + assert_se(pidref_equal(&PIDREF_NULL, NULL)); + assert_se(pidref_equal(&PIDREF_NULL, &PIDREF_NULL)); + + assert_se(!pidref_equal(NULL, &PIDREF_MAKE_FROM_PID(1))); + assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), NULL)); + assert_se(!pidref_equal(&PIDREF_NULL, &PIDREF_MAKE_FROM_PID(1))); + assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_NULL)); + assert_se(pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(1))); + assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(2))); +} + +TEST(pidref_set_pid) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + r = pidref_set_pid(&pidref, 1); + if (r == -ESRCH) + return (void) log_tests_skipped_errno(r, "PID1 does not exist"); + assert_se(r >= 0); + + assert_se(pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(1))); + assert_se(!pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(2))); +} + +TEST(pidref_set_self) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_set_self(&pidref) >= 0); + assert_se(pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(getpid_cached()))); + assert_se(!pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(getpid_cached()+1))); +} + +TEST(pidref_set_pidstr) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + char buf[DECIMAL_STR_MAX(pid_t)]; + + xsprintf(buf, PID_FMT, getpid_cached()); + assert_se(pidref_set_pidstr(&pidref, buf) >= 0); + assert_se(pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(getpid_cached()))); + assert_se(!pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(getpid_cached()+1))); +} + +TEST(pidref_set_pidfd) { + _cleanup_(pidref_done) PidRef a = PIDREF_NULL, b = PIDREF_NULL, c = PIDREF_NULL, d = PIDREF_NULL; + + assert_se(pidref_set_self(&a) >= 0); + if (a.fd < 0) + return (void) log_tests_skipped("PIDFD not supported"); + + assert_se(pidref_set_pidfd(&b, a.fd) >= 0); + assert_se(pidref_equal(&a, &b)); + assert_se(pidref_set_pidfd_take(&c, b.fd) >= 0); + b.fd = -EBADF; + assert_se(pidref_equal(&a, &c)); + assert_se(pidref_set_pidfd_consume(&d, TAKE_FD(c.fd)) >= 0); + assert_se(pidref_equal(&a, &d)); +} + +TEST(pidref_is_self) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_set_self(&pidref) >= 0); + assert_se(pidref_is_self(&pidref)); + + assert_se(!pidref_is_self(NULL)); + assert_se(!pidref_is_self(&PIDREF_NULL)); + assert_se(pidref_is_self(&PIDREF_MAKE_FROM_PID(getpid_cached()))); + assert_se(!pidref_is_self(&PIDREF_MAKE_FROM_PID(getpid_cached()+1))); +} + +TEST(pidref_copy) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int r; + + assert_se(pidref_copy(NULL, &pidref) >= 0); + assert_se(!pidref_is_set(&pidref)); + + assert_se(pidref_copy(&PIDREF_NULL, &pidref) >= 0); + assert_se(!pidref_is_set(&pidref)); + + assert_se(pidref_copy(&PIDREF_MAKE_FROM_PID(getpid_cached()), &pidref) >= 0); + assert_se(pidref_is_self(&pidref)); + pidref_done(&pidref); + + r = pidref_copy(&PIDREF_MAKE_FROM_PID(1), &pidref); + if (r == -ESRCH) + return (void) log_tests_skipped_errno(r, "PID1 does not exist"); + assert_se(r >= 0); + assert_se(pidref_equal(&pidref, &PIDREF_MAKE_FROM_PID(1))); +} + +TEST(pidref_dup) { + _cleanup_(pidref_freep) PidRef *pidref = NULL; + int r; + + assert_se(pidref_dup(NULL, &pidref) >= 0); + assert_se(pidref); + assert_se(!pidref_is_set(pidref)); + pidref = pidref_free(pidref); + + assert_se(pidref_dup(&PIDREF_NULL, &pidref) >= 0); + assert_se(pidref); + assert_se(!pidref_is_set(pidref)); + pidref = pidref_free(pidref); + + assert_se(pidref_dup(&PIDREF_MAKE_FROM_PID(getpid_cached()), &pidref) >= 0); + assert_se(pidref_is_self(pidref)); + pidref = pidref_free(pidref); + + r = pidref_dup(&PIDREF_MAKE_FROM_PID(1), &pidref); + if (r == -ESRCH) + return (void) log_tests_skipped_errno(r, "PID1 does not exist"); + assert_se(r >= 0); + assert_se(pidref_equal(pidref, &PIDREF_MAKE_FROM_PID(1))); +} + +TEST(pidref_new_from_pid) { + _cleanup_(pidref_freep) PidRef *pidref = NULL; + int r; + + assert_se(pidref_new_from_pid(-1, &pidref) == -ESRCH); + assert_se(!pidref); + + assert_se(pidref_new_from_pid(0, &pidref) >= 0); + assert_se(pidref_is_self(pidref)); + pidref = pidref_free(pidref); + + assert_se(pidref_new_from_pid(getpid_cached(), &pidref) >= 0); + assert_se(pidref_is_self(pidref)); + pidref = pidref_free(pidref); + + r = pidref_new_from_pid(1, &pidref); + if (r == -ESRCH) + return (void) log_tests_skipped_errno(r, "PID1 does not exist"); + assert_se(r >= 0); + assert_se(pidref_equal(pidref, &PIDREF_MAKE_FROM_PID(1))); +} + +TEST(pidref_kill) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + r = pidref_safe_fork("(test-pidref-kill)", FORK_DEATHSIG_SIGKILL, &pidref); + assert_se(r >= 0); + if (r == 0) + freeze(); + + assert_se(pidref_kill(&pidref, SIGKILL) >= 0); + assert_se(pidref_wait_for_terminate(&pidref, &si) >= 0); + assert_se(si.si_signo == SIGCHLD); +} + +TEST(pidref_kill_and_sigcont) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + r = pidref_safe_fork("(test-pidref-kill-and-sigcont)", FORK_DEATHSIG_SIGTERM, &pidref); + assert_se(r >= 0); + if (r == 0) + freeze(); + + assert_se(pidref_kill_and_sigcont(&pidref, SIGTERM) >= 0); + assert_se(pidref_wait_for_terminate(&pidref, &si) >= 0); + assert_se(si.si_signo == SIGCHLD); +} + +TEST(pidref_sigqueue) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + siginfo_t si; + int r; + + r = pidref_safe_fork("(test-pidref-sigqueue)", FORK_DEATHSIG_SIGTERM, &pidref); + assert_se(r >= 0); + if (r == 0) + freeze(); + + assert_se(pidref_sigqueue(&pidref, SIGTERM, 42) >= 0); + assert_se(pidref_wait_for_terminate(&pidref, &si) >= 0); + assert_se(si.si_signo == SIGCHLD); +} + +TEST(pidref_done_sigkill_wait) { + _cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL; + int r; + + r = pidref_safe_fork("(test-pidref-done-sigkill-wait)", FORK_DEATHSIG_SIGKILL, &pidref); + assert_se(r >= 0); + if (r == 0) + freeze(); +} + +TEST(pidref_verify) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_verify(NULL) == -ESRCH); + assert_se(pidref_verify(&PIDREF_NULL) == -ESRCH); + + assert_se(pidref_verify(&PIDREF_MAKE_FROM_PID(1)) == 1); + assert_se(pidref_verify(&PIDREF_MAKE_FROM_PID(getpid_cached())) == 0); + + assert_se(pidref_set_self(&pidref) >= 0); + assert_se(pidref_verify(&pidref) == (pidref.fd >= 0)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-pretty-print.c b/src/test/test-pretty-print.c index 52b2bc8..9ab52c6 100644 --- a/src/test/test-pretty-print.c +++ b/src/test/test-pretty-print.c @@ -22,7 +22,7 @@ static void test_draw_cylon_one(unsigned pos) { memset(buf, 0xff, sizeof(buf)); draw_cylon(buf, sizeof(buf), CYLON_WIDTH, pos); - assert_se(strlen(buf) < sizeof(buf)); + ASSERT_LE(strlen(buf), sizeof(buf)); } TEST(draw_cylon) { diff --git a/src/test/test-prioq.c b/src/test/test-prioq.c index 540863c..92c22d3 100644 --- a/src/test/test-prioq.c +++ b/src/test/test-prioq.c @@ -54,7 +54,7 @@ static int test_compare(const struct test *x, const struct test *y) { } static void test_hash(const struct test *x, struct siphash *state) { - siphash24_compress(&x->value, sizeof(x->value), state); + siphash24_compress_typesafe(x->value, state); } DEFINE_PRIVATE_HASH_OPS(test_hash_ops, struct test, test_hash, test_compare); @@ -70,10 +70,10 @@ TEST(struct) { assert_se(q = prioq_new((compare_func_t) test_compare)); assert_se(s = set_new(&test_hash_ops)); - assert_se(prioq_peek(q) == NULL); - assert_se(prioq_peek_by_index(q, 0) == NULL); - assert_se(prioq_peek_by_index(q, 1) == NULL); - assert_se(prioq_peek_by_index(q, UINT_MAX) == NULL); + ASSERT_NULL(prioq_peek(q)); + ASSERT_NULL(prioq_peek_by_index(q, 0)); + ASSERT_NULL(prioq_peek_by_index(q, 1)); + ASSERT_NULL(prioq_peek_by_index(q, UINT_MAX)); for (i = 0; i < SET_SIZE; i++) { assert_se(t = new0(struct test, 1)); @@ -87,7 +87,7 @@ TEST(struct) { for (i = 0; i < SET_SIZE; i++) assert_se(prioq_peek_by_index(q, i)); - assert_se(prioq_peek_by_index(q, SET_SIZE) == NULL); + ASSERT_NULL(prioq_peek_by_index(q, SET_SIZE)); unsigned count = 0; PRIOQ_FOREACH_ITEM(q, t) { diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 8b5bbb0..420f312 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -37,7 +37,7 @@ TEST(proc_cmdline_override) { /* First test if the overrides for /proc/cmdline still work */ 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\"")); + ASSERT_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"))); @@ -57,7 +57,7 @@ TEST(proc_cmdline_override) { 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")); + ASSERT_STREQ(line, "hoge"); line = mfree(line); assert_se(proc_cmdline_strv(&args) >= 0); assert_se(strv_equal(args, STRV_MAKE("hoge"))); @@ -83,13 +83,13 @@ static int parse_item_given(const char *key, const char *value, void *data) { log_info("%s: option <%s> = <%s>", __func__, key, strna(value)); if (proc_cmdline_key_streq(key, "foo_bar")) - assert_se(streq(value, "quux")); + ASSERT_STREQ(value, "quux"); else if (proc_cmdline_key_streq(key, "wuff-piep")) - assert_se(streq(value, "tuet ")); + ASSERT_STREQ(value, "tuet "); else if (proc_cmdline_key_streq(key, "space")) - assert_se(streq(value, "x y z")); + ASSERT_STREQ(value, "x y z"); else if (proc_cmdline_key_streq(key, "miepf")) - assert_se(streq(value, "uuu")); + ASSERT_STREQ(value, "uuu"); else if (in_initrd() && *strip && proc_cmdline_key_streq(key, "zumm")) assert_se(!value); else if (in_initrd() && !*strip && proc_cmdline_key_streq(key, "rd.zumm")) @@ -231,13 +231,13 @@ TEST(proc_cmdline_get_key_many) { "doubleticks", &value6, "zummm", &value7) == 5); - assert_se(streq_ptr(value1, "quux")); + ASSERT_STREQ(value1, "quux"); assert_se(!value2); - assert_se(streq_ptr(value3, "tuet")); + ASSERT_STREQ(value3, "tuet"); assert_se(!value4); - assert_se(streq_ptr(value5, "one two")); - assert_se(streq_ptr(value6, " aaa aaa ")); - assert_se(streq_ptr(value7, "\n")); + ASSERT_STREQ(value5, "one two"); + ASSERT_STREQ(value6, " aaa aaa "); + ASSERT_STREQ(value7, "\n"); } TEST(proc_cmdline_key_streq) { diff --git a/src/test/test-process-util.c b/src/test/test-process-util.c index 957e214..c96bd43 100644 --- a/src/test/test-process-util.c +++ b/src/test/test-process-util.c @@ -176,7 +176,7 @@ static void test_pid_get_comm_escape_one(const char *input, const char *output) log_debug("got: <%s>", n); - assert_se(streq_ptr(n, output)); + ASSERT_STREQ(n, output); } TEST(pid_get_comm_escape) { @@ -236,14 +236,14 @@ TEST(personality) { assert_se(personality_to_string(PER_LINUX)); assert_se(!personality_to_string(PERSONALITY_INVALID)); - assert_se(streq(personality_to_string(PER_LINUX), architecture_to_string(native_architecture()))); + ASSERT_STREQ(personality_to_string(PER_LINUX), architecture_to_string(native_architecture())); assert_se(personality_from_string(personality_to_string(PER_LINUX)) == PER_LINUX); assert_se(personality_from_string(architecture_to_string(native_architecture())) == PER_LINUX); #ifdef __x86_64__ - assert_se(streq_ptr(personality_to_string(PER_LINUX), "x86-64")); - assert_se(streq_ptr(personality_to_string(PER_LINUX32), "x86")); + ASSERT_STREQ(personality_to_string(PER_LINUX), "x86-64"); + ASSERT_STREQ(personality_to_string(PER_LINUX32), "x86"); assert_se(personality_from_string("x86-64") == PER_LINUX); assert_se(personality_from_string("x86") == PER_LINUX32); @@ -328,49 +328,49 @@ TEST(pid_get_cmdline_harder) { assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "[testa]")); + ASSERT_STREQ(line, "[testa]"); line = mfree(line); assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK | PROCESS_CMDLINE_QUOTE, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "\"[testa]\"")); /* quoting is enabled here */ + ASSERT_STREQ(line, "\"[testa]\""); /* quoting is enabled here */ line = mfree(line); assert_se(pid_get_cmdline(0, 0, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "")); + ASSERT_STREQ(line, ""); line = mfree(line); assert_se(pid_get_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "…")); + ASSERT_STREQ(line, "…"); line = mfree(line); assert_se(pid_get_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[…")); + ASSERT_STREQ(line, "[…"); line = mfree(line); assert_se(pid_get_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[t…")); + ASSERT_STREQ(line, "[t…"); line = mfree(line); assert_se(pid_get_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[te…")); + ASSERT_STREQ(line, "[te…"); line = mfree(line); assert_se(pid_get_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[tes…")); + ASSERT_STREQ(line, "[tes…"); line = mfree(line); assert_se(pid_get_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[test…")); + ASSERT_STREQ(line, "[test…"); line = mfree(line); assert_se(pid_get_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[testa]")); + ASSERT_STREQ(line, "[testa]"); line = mfree(line); assert_se(pid_get_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "[testa]")); + ASSERT_STREQ(line, "[testa]"); line = mfree(line); assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); @@ -383,11 +383,11 @@ TEST(pid_get_cmdline_harder) { assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar")); + ASSERT_STREQ(line, "foo bar"); line = mfree(line); assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); - assert_se(streq(line, "foo bar")); + ASSERT_STREQ(line, "foo bar"); line = mfree(line); assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); @@ -397,87 +397,87 @@ TEST(pid_get_cmdline_harder) { assert_se(write(fd, "quux", 4) == 4); assert_se(pid_get_cmdline(0, SIZE_MAX, 0, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline(0, 1, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "…")); + ASSERT_STREQ(line, "…"); line = mfree(line); assert_se(pid_get_cmdline(0, 2, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "f…")); + ASSERT_STREQ(line, "f…"); line = mfree(line); assert_se(pid_get_cmdline(0, 3, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "fo…")); + ASSERT_STREQ(line, "fo…"); line = mfree(line); assert_se(pid_get_cmdline(0, 4, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo…")); + ASSERT_STREQ(line, "foo…"); line = mfree(line); assert_se(pid_get_cmdline(0, 5, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo …")); + ASSERT_STREQ(line, "foo …"); line = mfree(line); assert_se(pid_get_cmdline(0, 6, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo b…")); + ASSERT_STREQ(line, "foo b…"); line = mfree(line); assert_se(pid_get_cmdline(0, 7, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo ba…")); + ASSERT_STREQ(line, "foo ba…"); line = mfree(line); assert_se(pid_get_cmdline(0, 8, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar…")); + ASSERT_STREQ(line, "foo bar…"); line = mfree(line); assert_se(pid_get_cmdline(0, 9, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar …")); + ASSERT_STREQ(line, "foo bar …"); line = mfree(line); assert_se(pid_get_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar q…")); + ASSERT_STREQ(line, "foo bar q…"); line = mfree(line); assert_se(pid_get_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar qu…")); + ASSERT_STREQ(line, "foo bar qu…"); line = mfree(line); assert_se(pid_get_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline(0, 13, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline(0, 14, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline(0, 1000, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "foo bar quux")); + ASSERT_STREQ(line, "foo bar quux"); line = mfree(line); assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); @@ -491,22 +491,22 @@ TEST(pid_get_cmdline_harder) { assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "[aaaa bbbb cccc]")); + ASSERT_STREQ(line, "[aaaa bbbb cccc]"); line = mfree(line); assert_se(pid_get_cmdline(0, 10, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "[aaaa bbb…")); + ASSERT_STREQ(line, "[aaaa bbb…"); line = mfree(line); assert_se(pid_get_cmdline(0, 11, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "[aaaa bbbb…")); + ASSERT_STREQ(line, "[aaaa bbbb…"); line = mfree(line); assert_se(pid_get_cmdline(0, 12, PROCESS_CMDLINE_COMM_FALLBACK, &line) >= 0); log_debug("'%s'", line); - assert_se(streq(line, "[aaaa bbbb …")); + ASSERT_STREQ(line, "[aaaa bbbb …"); line = mfree(line); assert_se(pid_get_cmdline_strv(0, PROCESS_CMDLINE_COMM_FALLBACK, &args) >= 0); @@ -527,13 +527,13 @@ TEST(pid_get_cmdline_harder) { assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); log_debug("got: ==%s==", line); log_debug("exp: ==%s==", EXPECT1); - assert_se(streq(line, EXPECT1)); + ASSERT_STREQ(line, EXPECT1); line = mfree(line); assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); log_debug("got: ==%s==", line); log_debug("exp: ==%s==", EXPECT1p); - assert_se(streq(line, EXPECT1p)); + ASSERT_STREQ(line, EXPECT1p); line = mfree(line); assert_se(pid_get_cmdline_strv(0, 0, &args) >= 0); @@ -552,13 +552,13 @@ TEST(pid_get_cmdline_harder) { assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE, &line) >= 0); log_debug("got: ==%s==", line); log_debug("exp: ==%s==", EXPECT2); - assert_se(streq(line, EXPECT2)); + ASSERT_STREQ(line, EXPECT2); line = mfree(line); assert_se(pid_get_cmdline(0, SIZE_MAX, PROCESS_CMDLINE_QUOTE_POSIX, &line) >= 0); log_debug("got: ==%s==", line); log_debug("exp: ==%s==", EXPECT2p); - assert_se(streq(line, EXPECT2p)); + ASSERT_STREQ(line, EXPECT2p); line = mfree(line); assert_se(pid_get_cmdline_strv(0, 0, &args) >= 0); @@ -651,7 +651,7 @@ TEST(safe_fork) { TEST(pid_to_ptr) { assert_se(PTR_TO_PID(NULL) == 0); - assert_se(PID_TO_PTR(0) == NULL); + ASSERT_NULL(PID_TO_PTR(0)); assert_se(PTR_TO_PID(PID_TO_PTR(1)) == 1); assert_se(PTR_TO_PID(PID_TO_PTR(2)) == 2); @@ -946,6 +946,27 @@ TEST(is_reaper_process) { } } +TEST(pid_get_start_time) { + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + + assert_se(pidref_set_self(&pidref) >= 0); + + uint64_t start_time; + assert_se(pidref_get_start_time(&pidref, &start_time) >= 0); + log_info("our starttime: %" PRIu64, start_time); + + _cleanup_(pidref_done_sigkill_wait) PidRef child = PIDREF_NULL; + + assert_se(pidref_safe_fork("(stub)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS, &child) >= 0); + + uint64_t start_time2; + assert_se(pidref_get_start_time(&child, &start_time2) >= 0); + + log_info("child starttime: %" PRIu64, start_time2); + + assert_se(start_time2 >= start_time); +} + static int intro(void) { log_show_color(true); return EXIT_SUCCESS; diff --git a/src/test/test-progress-bar.c b/src/test/test-progress-bar.c new file mode 100644 index 0000000..b47adf0 --- /dev/null +++ b/src/test/test-progress-bar.c @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "pretty-print.h" +#include "random-util.h" +#include "tests.h" + +#define PROGRESS_PREFIX "test: " + +TEST(progress_bar) { + + draw_progress_bar(PROGRESS_PREFIX, 0); + + bool paused = false; + + for (double d = 0; d <= 100; d += 0.5) { + usleep_safe(random_u64_range(20 * USEC_PER_MSEC)); + draw_progress_bar(PROGRESS_PREFIX, d); + + if (!paused && d >= 50) { + clear_progress_bar(PROGRESS_PREFIX); + fputs("Sleeping for 1s...", stdout); + fflush(stdout); + usleep_safe(USEC_PER_SEC); + paused = true; + } + } + + draw_progress_bar(PROGRESS_PREFIX, 100); + usleep_safe(300 * MSEC_PER_SEC); + clear_progress_bar(PROGRESS_PREFIX); + fputs("Done.\n", stdout); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-recovery-key.c b/src/test/test-recovery-key.c new file mode 100644 index 0000000..8d5ed71 --- /dev/null +++ b/src/test/test-recovery-key.c @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "memory-util.h" +#include "random-util.h" +#include "recovery-key.h" +#include "tests.h" + +TEST(make_recovery_key) { + _cleanup_(erase_and_freep) char *recovery_key = NULL; + size_t length; + const size_t num_test = 10; + char *generated_keys[num_test]; + int r; + + /* Check for successful recovery-key creation */ + r = make_recovery_key(&recovery_key); + assert_se(r == 0); + ASSERT_NOT_NULL(recovery_key); + + /* Check that length of formatted key is 72 with 64 modhex characters */ + length = strlen(recovery_key); + assert_se(length == RECOVERY_KEY_MODHEX_FORMATTED_LENGTH - 1); + /* Check modhex characters in formatted key with dashes */ + for (size_t i = 0; i < length; i++) { + assert_se((recovery_key[i] >= 'a' && recovery_key[i] <= 'v') || recovery_key[i] == '-'); + if (i % 9 == 8) + /* confirm '-' is after every 8 characters */ + assert_se(recovery_key[i] == '-'); + } + /* Repeat tests to determine randomness of generated keys */ + for (size_t test = 0; test < num_test; ++test) { + r = make_recovery_key(&generated_keys[test]); + assert_se(r == 0); + length = strlen(generated_keys[test]); + assert_se(length == RECOVERY_KEY_MODHEX_FORMATTED_LENGTH - 1); + for (size_t i = 0; i < length; i++) { + assert_se((generated_keys[test][i] >= 'a' && generated_keys[test][i] <= 'v') + || generated_keys[test][i] == '-'); + if (i % 9 == 8) + assert_se(generated_keys[test][i] == '-'); + } + /* Check for uniqueness of each generated recovery key */ + for (size_t prev = 0; prev < test; ++prev) + assert_se(!streq(generated_keys[test], generated_keys[prev])); + } + for (size_t i = 0; i < num_test; i++) + free(generated_keys[i]); +} + +TEST(decode_modhex_char) { + + assert_se(decode_modhex_char('c') == 0); + assert_se(decode_modhex_char('C') == 0); + assert_se(decode_modhex_char('b') == 1); + assert_se(decode_modhex_char('B') == 1); + assert_se(decode_modhex_char('d') == 2); + assert_se(decode_modhex_char('D') == 2); + assert_se(decode_modhex_char('e') == 3); + assert_se(decode_modhex_char('E') == 3); + assert_se(decode_modhex_char('f') == 4); + assert_se(decode_modhex_char('F') == 4); + assert_se(decode_modhex_char('g') == 5); + assert_se(decode_modhex_char('G') == 5); + assert_se(decode_modhex_char('h') == 6); + assert_se(decode_modhex_char('H') == 6); + assert_se(decode_modhex_char('i') == 7); + assert_se(decode_modhex_char('I') == 7); + assert_se(decode_modhex_char('j') == 8); + assert_se(decode_modhex_char('J') == 8); + assert_se(decode_modhex_char('k') == 9); + assert_se(decode_modhex_char('K') == 9); + assert_se(decode_modhex_char('l') == 10); + assert_se(decode_modhex_char('L') == 10); + assert_se(decode_modhex_char('n') == 11); + assert_se(decode_modhex_char('N') == 11); + assert_se(decode_modhex_char('r') == 12); + assert_se(decode_modhex_char('R') == 12); + assert_se(decode_modhex_char('t') == 13); + assert_se(decode_modhex_char('T') == 13); + assert_se(decode_modhex_char('u') == 14); + assert_se(decode_modhex_char('U') == 14); + assert_se(decode_modhex_char('v') == 15); + assert_se(decode_modhex_char('V') == 15); + assert_se(decode_modhex_char('a') == -EINVAL); + assert_se(decode_modhex_char('A') == -EINVAL); + assert_se(decode_modhex_char('x') == -EINVAL); + assert_se(decode_modhex_char('.') == -EINVAL); + assert_se(decode_modhex_char('/') == -EINVAL); + assert_se(decode_modhex_char('\0') == -EINVAL); +} + +TEST(normalize_recovery_key) { + _cleanup_(erase_and_freep) char *normalized_key1 = NULL; + _cleanup_(erase_and_freep) char *normalized_key2 = NULL; + _cleanup_(erase_and_freep) char *normalized_key3 = NULL; + int r; + + /* Case 1: Normalization without dashes */ + r = normalize_recovery_key("cdefghijcdefghijcdefghijcdefghijcdefghijcdefghijcdefghijcdefghij", + &normalized_key1); + assert(r == 0); + assert(streq(normalized_key1, "cdefghij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghij")); + + /* Case 2: Normalization with dashes */ + r = normalize_recovery_key("cdefVhij-cDefghij-cdefkhij-cdufghij-cdefgdij-cidefIhj-cdefNijR-cdVfguij", + &normalized_key2); + assert_se(r == 0); + ASSERT_STREQ(normalized_key2, "cdefvhij-cdefghij-cdefkhij-cdufghij-cdefgdij-cidefihj-cdefnijr-cdvfguij"); + + /* Case 3: Invalid password length */ + r = normalize_recovery_key("1234-5678-90AB-CDEF-1234-5678-90AB-CDEF", &normalized_key1); + assert(r == -EINVAL); + + /* Case 4: Invalid password format(missing dash) */ + r = normalize_recovery_key("cdefghij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghijcdefghij", + &normalized_key1); + assert_se(r == -EINVAL); + + /* Case 5: Normalization of Upper cases password without dashes */ + r = normalize_recovery_key("BFGHICEHHIUVLKJIHFHEDlntruvcdefjiTUVKLNIJVTUTKJIHDFBCBGHIJHHFDBC", + &normalized_key3); + assert(r == 0); + ASSERT_STREQ(normalized_key3, "bfghiceh-hiuvlkji-hfhedlnt-ruvcdefj-ituvklni-jvtutkji-hdfbcbgh-ijhhfdbc"); + + /* Case 6: Minimum password length */ + r = normalize_recovery_key("", &normalized_key1); + assert_se(r == -EINVAL); + + /* Case 7: Invalid characters and numbers in password */ + r = normalize_recovery_key("cde123hi-cdefgzij-cdefghij-cdefghij-cdefghij-cdefghij-cdefghijcdefghij", + &normalized_key1); + assert_se(r == -EINVAL); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-replace-var.c b/src/test/test-replace-var.c index f861b27..56634a8 100644 --- a/src/test/test-replace-var.c +++ b/src/test/test-replace-var.c @@ -16,7 +16,7 @@ TEST(replace_var) { assert_se(r = replace_var("@@@foobar@xyz@HALLO@foobar@test@@testtest@TEST@...@@@", lookup, NULL)); puts(r); - assert_se(streq(r, "@@@foobar@xyz<<>>foobar@test@@testtest<<>>...@@@")); + ASSERT_STREQ(r, "@@@foobar@xyz<<>>foobar@test@@testtest<<>>...@@@"); free(r); } @@ -25,7 +25,7 @@ TEST(strreplace) { assert_se(r = strreplace("XYZFFFFXYZFFFFXYZ", "XYZ", "ABC")); puts(r); - assert_se(streq(r, "ABCFFFFABCFFFFABC")); + ASSERT_STREQ(r, "ABCFFFFABCFFFFABC"); free(r); } diff --git a/src/test/test-rlimit-util.c b/src/test/test-rlimit-util.c index 86d0c04..fc9e89c 100644 --- a/src/test/test-rlimit-util.c +++ b/src/test/test-rlimit-util.c @@ -1,15 +1,20 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include +#if HAVE_VALGRIND_VALGRIND_H +#include +#endif #include "alloc-util.h" #include "capability-util.h" #include "macro.h" #include "missing_resource.h" +#include "process-util.h" #include "rlimit-util.h" #include "string-util.h" #include "tests.h" #include "time-util.h" +#include "user-util.h" static void test_rlimit_parse_format_one(int resource, const char *string, rlim_t soft, rlim_t hard, int ret, const char *formatted) { _cleanup_free_ char *f = NULL; @@ -29,7 +34,7 @@ static void test_rlimit_parse_format_one(int resource, const char *string, rlim_ assert_se(rl.rlim_max == hard); assert_se(rlimit_format(&rl, &f) >= 0); - assert_se(streq(formatted, f)); + ASSERT_STREQ(formatted, f); assert_se(rlimit_parse(resource, formatted, &rl2) >= 0); assert_se(memcmp(&rl, &rl2, sizeof(struct rlimit)) == 0); @@ -113,8 +118,8 @@ TEST(setrlimit) { new.rlim_max = old.rlim_max; assert_se(setrlimit(RLIMIT_NOFILE, &new) >= 0); - assert_se(streq_ptr(rlimit_to_string(RLIMIT_NOFILE), "NOFILE")); - assert_se(rlimit_to_string(-1) == NULL); + ASSERT_STREQ(rlimit_to_string(RLIMIT_NOFILE), "NOFILE"); + ASSERT_NULL(rlimit_to_string(-1)); assert_se(getrlimit(RLIMIT_NOFILE, &old) == 0); assert_se(setrlimit_closest(RLIMIT_NOFILE, &old) == 0); @@ -136,4 +141,45 @@ TEST(setrlimit) { assert_se(old.rlim_max == new.rlim_max); } +TEST(pid_getrlimit) { + int r; + + /* We fork off a child and read the parent's resource limit from there (i.e. our own), and compare + * with what getrlimit() gives us */ + + for (int resource = 0; resource < _RLIMIT_MAX; resource++) { + struct rlimit direct; + + assert_se(getrlimit(resource, &direct) >= 0); + + /* We fork off a child so that getrlimit() doesn't work anymore */ + r = safe_fork("(getrlimit)", FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_LOG|FORK_WAIT, /* ret_pid= */ NULL); + assert_se(r >= 0); + + if (r == 0) { + struct rlimit indirect; + /* child */ + + /* Drop privs, so that prlimit() doesn't work anymore */ + (void) setresgid(GID_NOBODY, GID_NOBODY, GID_NOBODY); + (void) setresuid(UID_NOBODY, UID_NOBODY, UID_NOBODY); + + assert_se(pid_getrlimit(getppid(), resource, &indirect) >= 0); + +#if HAVE_VALGRIND_VALGRIND_H + /* Valgrind fakes some changes in RLIMIT_NOFILE getrlimit() returns, work around that */ + if (RUNNING_ON_VALGRIND && resource == RLIMIT_NOFILE) { + log_info("Skipping pid_getrlimit() check for RLIMIT_NOFILE, running in valgrind"); + _exit(EXIT_SUCCESS); + } +#endif + + assert_se(direct.rlim_cur == indirect.rlim_cur); + assert_se(direct.rlim_max == indirect.rlim_max); + + _exit(EXIT_SUCCESS); + } + } +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sd-hwdb.c b/src/test/test-sd-hwdb.c index ecb6118..307609c 100644 --- a/src/test/test-sd-hwdb.c +++ b/src/test/test-sd-hwdb.c @@ -18,8 +18,8 @@ TEST(failed_enumerate) { assert_se(sd_hwdb_seek(hwdb, "no-such-modalias-should-exist") == 0); assert_se(sd_hwdb_enumerate(hwdb, &key, &value) == 0); - assert_se(sd_hwdb_enumerate(hwdb, &key, NULL) == -EINVAL); - assert_se(sd_hwdb_enumerate(hwdb, NULL, &value) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, &key, NULL) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_enumerate(hwdb, NULL, &value) == -EINVAL); } #define DELL_MODALIAS \ @@ -58,8 +58,8 @@ TEST(sd_hwdb_new_from_path) { _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; int r; - assert_se(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL); - assert_se(sd_hwdb_new_from_path("", &hwdb) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path(NULL, &hwdb) == -EINVAL); + ASSERT_RETURN_EXPECTED_SE(sd_hwdb_new_from_path("", &hwdb) == -EINVAL); assert_se(sd_hwdb_new_from_path("/path/that/should/not/exist", &hwdb) < 0); NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { diff --git a/src/test/test-seccomp.c b/src/test/test-seccomp.c index 279a155..74d950a 100644 --- a/src/test/test-seccomp.c +++ b/src/test/test-seccomp.c @@ -52,40 +52,40 @@ TEST(parse_syscall_and_errno) { int e; assert_se(parse_syscall_and_errno("uname:EILSEQ", &n, &e) >= 0); - assert_se(streq(n, "uname")); + ASSERT_STREQ(n, "uname"); assert_se(e == errno_from_name("EILSEQ") && e >= 0); n = mfree(n); assert_se(parse_syscall_and_errno("uname:EINVAL", &n, &e) >= 0); - assert_se(streq(n, "uname")); + ASSERT_STREQ(n, "uname"); assert_se(e == errno_from_name("EINVAL") && e >= 0); n = mfree(n); assert_se(parse_syscall_and_errno("@sync:4095", &n, &e) >= 0); - assert_se(streq(n, "@sync")); + ASSERT_STREQ(n, "@sync"); assert_se(e == 4095); n = mfree(n); /* If errno is omitted, then e is set to -1 */ assert_se(parse_syscall_and_errno("mount", &n, &e) >= 0); - assert_se(streq(n, "mount")); + ASSERT_STREQ(n, "mount"); assert_se(e == -1); n = mfree(n); /* parse_syscall_and_errno() does not check the syscall name is valid or not. */ assert_se(parse_syscall_and_errno("hoge:255", &n, &e) >= 0); - assert_se(streq(n, "hoge")); + ASSERT_STREQ(n, "hoge"); assert_se(e == 255); n = mfree(n); /* 0 is also a valid errno. */ assert_se(parse_syscall_and_errno("hoge:0", &n, &e) >= 0); - assert_se(streq(n, "hoge")); + ASSERT_STREQ(n, "hoge"); assert_se(e == 0); n = mfree(n); assert_se(parse_syscall_and_errno("hoge:kill", &n, &e) >= 0); - assert_se(streq(n, "hoge")); + ASSERT_STREQ(n, "hoge"); assert_se(e == SECCOMP_ERROR_NUMBER_KILL); n = mfree(n); @@ -151,7 +151,7 @@ TEST(architecture_table) { assert_se(seccomp_arch_from_string(n, &c) >= 0); n2 = seccomp_arch_to_string(c); log_info("seccomp-arch: %s → 0x%"PRIx32" → %s", n, c, n2); - assert_se(streq_ptr(n, n2)); + ASSERT_STREQ(n, n2); } } @@ -231,11 +231,11 @@ TEST(filter_sets) { TEST(filter_sets_ordered) { /* Ensure "@default" always remains at the beginning of the list */ assert_se(SYSCALL_FILTER_SET_DEFAULT == 0); - assert_se(streq(syscall_filter_sets[0].name, "@default")); + ASSERT_STREQ(syscall_filter_sets[0].name, "@default"); /* Ensure "@known" always remains at the end of the list */ assert_se(SYSCALL_FILTER_SET_KNOWN == _SYSCALL_FILTER_SET_MAX - 1); - assert_se(streq(syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].name, "@known")); + ASSERT_STREQ(syscall_filter_sets[SYSCALL_FILTER_SET_KNOWN].name, "@known"); for (size_t i = 0; i < _SYSCALL_FILTER_SET_MAX; i++) { const char *p = NULL; @@ -294,7 +294,7 @@ TEST(restrict_namespace) { s = mfree(s); assert_se(namespace_flags_to_string(NAMESPACE_FLAGS_ALL, &s) == 0); - assert_se(streq(s, "cgroup ipc net mnt pid user uts")); + ASSERT_STREQ(s, "cgroup ipc net mnt pid user uts"); assert_se(namespace_flags_from_string(s, &ul) == 0 && ul == NAMESPACE_FLAGS_ALL); s = mfree(s); @@ -624,17 +624,17 @@ TEST(memory_deny_write_execute_mmap) { if (pid == 0) { void *p; - p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); assert_se(p != MAP_FAILED); assert_se(munmap(p, page_size()) >= 0); - p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); assert_se(p != MAP_FAILED); assert_se(munmap(p, page_size()) >= 0); assert_se(seccomp_memory_deny_write_execute() >= 0); - p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + p = mmap(NULL, page_size(), PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); #if defined(__x86_64__) || defined(__i386__) || defined(__powerpc64__) || defined(__arm__) || defined(__aarch64__) || defined(__loongarch_lp64) assert_se(p == MAP_FAILED); assert_se(errno == EPERM); @@ -644,7 +644,7 @@ TEST(memory_deny_write_execute_mmap) { if (p != MAP_FAILED) assert_se(munmap(p, page_size()) == 0); - p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1,0); + p = mmap(NULL, page_size(), PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); assert_se(p != MAP_FAILED); assert_se(munmap(p, page_size()) >= 0); @@ -937,7 +937,7 @@ TEST(native_syscalls_filtered) { } TEST(lock_personality) { - unsigned long current; + unsigned long current_opinionated; pid_t pid; if (!is_seccomp_available()) { @@ -949,24 +949,21 @@ TEST(lock_personality) { return; } - assert_se(opinionated_personality(¤t) >= 0); - /* On ppc64le sanitizers disable ASLR (i.e. by setting ADDR_NO_RANDOMIZE), - * which opinionated_personality() doesn't return. Let's tweak the current - * personality ourselves in such cases. - * See: https://github.com/llvm/llvm-project/commit/78f7a6eaa601bfdd6ae70ffd3da2254c21ff77f9 - */ - if (FLAGS_SET(safe_personality(PERSONALITY_INVALID), ADDR_NO_RANDOMIZE)) - current |= ADDR_NO_RANDOMIZE; + assert_se(opinionated_personality(¤t_opinionated) >= 0); - log_info("current personality=0x%lX", current); + log_info("current personality=0x%lX", (unsigned long) safe_personality(PERSONALITY_INVALID)); + log_info("current opinionated personality=0x%lX", current_opinionated); pid = fork(); assert_se(pid >= 0); if (pid == 0) { - assert_se(seccomp_lock_personality(current) >= 0); + unsigned long current; - assert_se((unsigned long) safe_personality(current) == current); + assert_se(seccomp_lock_personality(current_opinionated) >= 0); + + current = safe_personality(current_opinionated); + assert_se((current & OPINIONATED_PERSONALITY_MASK) == current_opinionated); /* Note, we also test that safe_personality() works correctly, by checking whether errno is properly * set, in addition to the return value */ @@ -981,14 +978,15 @@ TEST(lock_personality) { assert_se(safe_personality(PER_LINUX_32BIT) == -EPERM); assert_se(safe_personality(PER_SVR4) == -EPERM); assert_se(safe_personality(PER_BSD) == -EPERM); - assert_se(safe_personality(current == PER_LINUX ? PER_LINUX32 : PER_LINUX) == -EPERM); + assert_se(safe_personality(current_opinionated == PER_LINUX ? PER_LINUX32 : PER_LINUX) == -EPERM); assert_se(safe_personality(PER_LINUX32_3GB) == -EPERM); assert_se(safe_personality(PER_UW7) == -EPERM); assert_se(safe_personality(0x42) == -EPERM); assert_se(safe_personality(PERSONALITY_INVALID) == -EPERM); /* maybe remove this later */ - assert_se((unsigned long) personality(current) == current); + current = safe_personality(current_opinionated); + assert_se((current & OPINIONATED_PERSONALITY_MASK) == current_opinionated); _exit(EXIT_SUCCESS); } diff --git a/src/test/test-secure-bits.c b/src/test/test-secure-bits.c index 27e6a20..3d353ae 100644 --- a/src/test/test-secure-bits.c +++ b/src/test/test-secure-bits.c @@ -31,7 +31,7 @@ TEST(secure_bits_basic) { 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)); + ASSERT_STREQ(*bit, s); } /* Ditto, but with all bits at once */ @@ -42,7 +42,7 @@ TEST(secure_bits_basic) { 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)); + ASSERT_STREQ(joined, str); str = mfree(str); @@ -61,11 +61,11 @@ TEST(secure_bits_basic) { /* Bits to string with check */ assert_se(secure_bits_to_string_alloc_with_check(INT_MAX, &str) == -EINVAL); - assert_se(str == NULL); + ASSERT_NULL(str); 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")); + ASSERT_STREQ(str, "keep-caps keep-caps-locked"); } TEST(secure_bits_mix) { @@ -90,7 +90,7 @@ TEST(secure_bits_mix) { 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)); + ASSERT_STREQ(s->expected, str); } } diff --git a/src/test/test-serialize.c b/src/test/test-serialize.c index 8f74472..40abf6c 100644 --- a/src/test/test-serialize.c +++ b/src/test/test-serialize.c @@ -32,13 +32,13 @@ TEST(serialize_item) { _cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL, *line4 = NULL; assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); - assert_se(streq(line1, "a=bbb")); + ASSERT_STREQ(line1, "a=bbb"); assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); - assert_se(streq(line2, "a=bbb")); + ASSERT_STREQ(line2, "a=bbb"); assert_se(read_line(f, LONG_LINE_MAX, &line3) > 0); - assert_se(streq(line3, "c=yes")); + ASSERT_STREQ(line3, "c=yes"); assert_se(read_line(f, LONG_LINE_MAX, &line4) == 0); - assert_se(streq(line4, "")); + ASSERT_STREQ(line4, ""); } TEST(serialize_item_escaped) { @@ -59,11 +59,11 @@ TEST(serialize_item_escaped) { _cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL; assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); - assert_se(streq(line1, "a=bbb")); + ASSERT_STREQ(line1, "a=bbb"); assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0); - assert_se(streq(line2, "a=bbb")); + ASSERT_STREQ(line2, "a=bbb"); assert_se(read_line(f, LONG_LINE_MAX, &line3) == 0); - assert_se(streq(line3, "")); + ASSERT_STREQ(line3, ""); } TEST(serialize_usec) { @@ -83,7 +83,7 @@ TEST(serialize_usec) { usec_t x; assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0); - assert_se(streq(line1, "usec2=0")); + ASSERT_STREQ(line1, "usec2=0"); assert_se(deserialize_usec(line1 + 6, &x) == 0); assert_se(x == 0); @@ -204,7 +204,7 @@ TEST(serialize_item_hexmem) { _cleanup_free_ char *line = NULL; assert_se(read_line(f, LONG_LINE_MAX, &line) > 0); - assert_se(streq(line, "a=ffffff")); + ASSERT_STREQ(line, "a=ffffff"); } @@ -222,7 +222,7 @@ TEST(serialize_item_base64mem) { _cleanup_free_ char *line = NULL; assert_se(read_line(f, LONG_LINE_MAX, &line) > 0); - assert_se(streq(line, "a=////")); + ASSERT_STREQ(line, "a=////"); } TEST(serialize_string_set) { diff --git a/src/test/test-set.c b/src/test/test-set.c index 0d5a6a1..23bac02 100644 --- a/src/test/test-set.c +++ b/src/test/test-set.c @@ -141,7 +141,7 @@ TEST(set_ensure_allocated) { assert_se(set_ensure_allocated(&m, &string_hash_ops) == 1); assert_se(set_ensure_allocated(&m, &string_hash_ops) == 0); assert_se(set_ensure_allocated(&m, NULL) == 0); - assert_se(set_size(m) == 0); + assert_se(set_isempty(m)); } TEST(set_copy) { @@ -231,28 +231,28 @@ TEST(set_strjoin) { /* Single entry */ assert_se(set_put_strdup(&m, "aaa") == 1); assert_se(set_strjoin(m, NULL, false, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, "", false, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, " ", false, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, "xxx", false, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, NULL, true, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, "", true, &joined) >= 0); - assert_se(streq(joined, "aaa")); + ASSERT_STREQ(joined, "aaa"); joined = mfree(joined); assert_se(set_strjoin(m, " ", true, &joined) >= 0); - assert_se(streq(joined, " aaa ")); + ASSERT_STREQ(joined, " aaa "); joined = mfree(joined); assert_se(set_strjoin(m, "xxx", true, &joined) >= 0); - assert_se(streq(joined, "xxxaaaxxx")); + ASSERT_STREQ(joined, "xxxaaaxxx"); /* Two entries */ assert_se(set_put_strdup(&m, "bbb") == 1); diff --git a/src/test/test-sha256.c b/src/test/test-sha256.c index f168e4c..1acc893 100644 --- a/src/test/test-sha256.c +++ b/src/test/test-sha256.c @@ -31,7 +31,7 @@ static void test_sha256_one(const char *key, const char *expect) { sha256_finish_ctx(&ctx, result + j); hex_result = hexmem(result + j, SHA256_DIGEST_SIZE); - assert_se(streq_ptr(hex_result, expect)); + ASSERT_STREQ(hex_result, expect); } } } diff --git a/src/test/test-sleep-config.c b/src/test/test-sleep-config.c index 112fec6..dc1a8fb 100644 --- a/src/test/test-sleep-config.c +++ b/src/test/test-sleep-config.c @@ -52,10 +52,10 @@ TEST(sleep_supported) { log_info("Standby configured: %s", yes_no(sleep_state_supported(standby) > 0)); log_info("Suspend configured: %s", yes_no(sleep_state_supported(mem) > 0)); log_info("Hibernate configured: %s", yes_no(sleep_state_supported(disk) > 0)); - log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported(suspend) > 0)); - log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported(reboot) > 0)); - log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported(platform) > 0)); - log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported(shutdown) > 0)); + log_info("Hibernate+Suspend (Hybrid-Sleep) configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", suspend) > 0)); + log_info("Hibernate+Reboot configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", reboot) > 0)); + log_info("Hibernate+Platform configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", platform) > 0)); + log_info("Hibernate+Shutdown configured: %s", yes_no(sleep_mode_supported("/sys/power/disk", shutdown) > 0)); log_info("Freeze configured: %s", yes_no(sleep_state_supported(freeze) > 0)); log_info("/= high-level sleep verbs =/"); diff --git a/src/test/test-socket-bind.c b/src/test/test-socket-bind.c index 84a8978..13ffa92 100644 --- a/src/test/test-socket-bind.c +++ b/src/test/test-socket-bind.c @@ -66,7 +66,7 @@ static int test_socket_bind( fputc('\n', stderr); exec_start = strjoin("-timeout --preserve-status -sSIGTERM 1s ", netcat_path, " -l ", port, " -vv"); - assert_se(exec_start != NULL); + ASSERT_NOT_NULL(exec_start); r = config_parse_exec(u->id, "filename", 1, "Service", 1, "ExecStart", SERVICE_EXEC_START, exec_start, SERVICE(u)->exec_command, u); @@ -83,7 +83,7 @@ static int test_socket_bind( while (!IN_SET(SERVICE(u)->state, SERVICE_DEAD, SERVICE_FAILED)) { r = sd_event_run(m->event, UINT64_MAX); if (r < 0) - return log_error_errno(errno, "Event run failed %m"); + return log_error_errno(r, "Event run failed %m"); } cld_code = SERVICE(u)->exec_command[SERVICE_EXEC_START]->exec_status.code; diff --git a/src/test/test-socket-netlink.c b/src/test/test-socket-netlink.c index 6dbd50f..1849585 100644 --- a/src/test/test-socket-netlink.c +++ b/src/test/test-socket-netlink.c @@ -29,7 +29,7 @@ static void test_socket_address_parse_one(const char *in, int ret, int family, c assert_se(r == ret); if (r >= 0) { assert_se(a.sockaddr.sa.sa_family == family); - assert_se(streq(out, expected ?: in)); + ASSERT_STREQ(out, expected ?: in); } } @@ -199,7 +199,7 @@ TEST(socket_address_get_path) { assert_se(!socket_address_get_path(&a)); assert_se(socket_address_parse(&a, "/foo/bar") >= 0); - assert_se(streq(socket_address_get_path(&a), "/foo/bar")); + ASSERT_STREQ(socket_address_get_path(&a), "/foo/bar"); assert_se(socket_address_parse(&a, "vsock:2:1234") >= 0); assert_se(!socket_address_get_path(&a)); @@ -235,7 +235,7 @@ static void test_in_addr_ifindex_to_string_one(int f, const char *a, int ifindex assert_se(in_addr_from_string(f, a, &ua) >= 0); assert_se(in_addr_ifindex_to_string(f, &ua, ifindex, &r) >= 0); printf("test_in_addr_ifindex_to_string_one: %s == %s\n", b, r); - assert_se(streq(b, r)); + ASSERT_STREQ(b, r); assert_se(in_addr_ifindex_from_string_auto(b, &ff, &uuaa, &ifindex2) >= 0); assert_se(ff == f); @@ -248,9 +248,9 @@ TEST(in_addr_ifindex_to_string) { test_in_addr_ifindex_to_string_one(AF_INET, "10.11.12.13", 9, "10.11.12.13"); test_in_addr_ifindex_to_string_one(AF_INET6, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 10, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); test_in_addr_ifindex_to_string_one(AF_INET6, "::1", 11, "::1"); - test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 12, "fe80::%12"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", LOOPBACK_IFINDEX, "fe80::%1"); test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::", 0, "fe80::"); - test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::14", 12, "fe80::14%12"); + test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::14", 0, "fe80::14"); test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::15", -7, "fe80::15"); test_in_addr_ifindex_to_string_one(AF_INET6, "fe80::16", LOOPBACK_IFINDEX, "fe80::16%1"); } @@ -265,9 +265,9 @@ TEST(in_addr_ifindex_from_string_auto) { assert_se(family == AF_INET6); assert_se(ifindex == 0); - assert_se(in_addr_ifindex_from_string_auto("fe80::18%19", &family, &ua, &ifindex) >= 0); + assert_se(in_addr_ifindex_from_string_auto("fe80::18%1", &family, &ua, &ifindex) >= 0); assert_se(family == AF_INET6); - assert_se(ifindex == 19); + assert_se(ifindex == 1); assert_se(in_addr_ifindex_from_string_auto("fe80::18%lo", &family, &ua, &ifindex) >= 0); assert_se(family == AF_INET6); @@ -282,14 +282,14 @@ static void test_in_addr_ifindex_name_from_string_auto_one(const char *a, const _cleanup_free_ char *server_name = NULL; assert_se(in_addr_ifindex_name_from_string_auto(a, &family, &ua, &ifindex, &server_name) >= 0); - assert_se(streq_ptr(server_name, expected)); + ASSERT_STREQ(server_name, expected); } TEST(in_addr_ifindex_name_from_string_auto) { test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1", NULL); test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1#test.com", "test.com"); - test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19", NULL); - test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19#another.test.com", "another.test.com"); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%1", NULL); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%1#another.test.com", "another.test.com"); } static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, int family, uint16_t port, int ifindex, @@ -307,9 +307,9 @@ static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, assert_se(family == f); assert_se(port == p); assert_se(ifindex == i); - assert_se(streq_ptr(server_name, name)); + ASSERT_STREQ(server_name, name); assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, i, name, &x) >= 0); - assert_se(streq(str_repr ?: str, x)); + ASSERT_STREQ(str_repr ?: str, x); } if (port > 0) @@ -319,9 +319,9 @@ static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, NULL, &i, &name) == 0); assert_se(family == f); assert_se(ifindex == i); - assert_se(streq_ptr(server_name, name)); + ASSERT_STREQ(server_name, name); assert_se(in_addr_port_ifindex_name_to_string(f, &a, 0, i, name, &x) >= 0); - assert_se(streq(str_repr ?: str, x)); + ASSERT_STREQ(str_repr ?: str, x); } if (ifindex > 0) @@ -331,9 +331,9 @@ static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, assert_se(in_addr_port_ifindex_name_from_string_auto(str, &f, &a, &p, NULL, &name) == 0); assert_se(family == f); assert_se(port == p); - assert_se(streq_ptr(server_name, name)); + ASSERT_STREQ(server_name, name); assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, 0, name, &x) >= 0); - assert_se(streq(str_repr ?: str, x)); + ASSERT_STREQ(str_repr ?: str, x); } if (server_name) @@ -345,7 +345,7 @@ static void test_in_addr_port_ifindex_name_from_string_auto_one(const char *str, assert_se(port == p); assert_se(ifindex == i); assert_se(in_addr_port_ifindex_name_to_string(f, &a, p, i, NULL, &x) >= 0); - assert_se(streq(str_repr ?: str, x)); + ASSERT_STREQ(str_repr ?: str, x); } } @@ -356,17 +356,29 @@ TEST(in_addr_port_ifindex_name_from_string_auto) { test_in_addr_port_ifindex_name_from_string_auto_one("192.168.0.1:53#example.com", AF_INET, 53, 0, "example.com", NULL); test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18", AF_INET6, 0, 0, NULL, NULL); test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18#hoge.com", AF_INET6, 0, 0, "hoge.com", NULL); - test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%19", AF_INET6, 0, 19, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%1", AF_INET6, 0, 1, NULL, NULL); test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%lo", AF_INET6, 0, 1, NULL, "fe80::18%1"); test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53", AF_INET6, 53, 0, NULL, NULL); - test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19", AF_INET6, 53, 19, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%1", AF_INET6, 53, 1, NULL, NULL); test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo", AF_INET6, 53, 1, NULL, "[fe80::18]:53%1"); - test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%19#hoge.com", AF_INET6, 0, 19, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("fe80::18%1#hoge.com", AF_INET6, 0, 1, "hoge.com", NULL); test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53#hoge.com", AF_INET6, 53, 0, "hoge.com", NULL); - test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19", AF_INET6, 53, 19, NULL, NULL); - test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%19#hoge.com", AF_INET6, 53, 19, "hoge.com", NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%1", AF_INET6, 53, 1, NULL, NULL); + test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%1#hoge.com", AF_INET6, 53, 1, "hoge.com", NULL); test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo", AF_INET6, 53, 1, NULL, "[fe80::18]:53%1"); test_in_addr_port_ifindex_name_from_string_auto_one("[fe80::18]:53%lo#hoge.com", AF_INET6, 53, 1, "hoge.com", "[fe80::18]:53%1#hoge.com"); } +TEST(netns_get_nsid) { + uint32_t u; + int r; + + r = netns_get_nsid(-EBADF, &u); + assert_se(r == -ENODATA || r >= 0); + if (r == -ENODATA) + log_info("Our network namespace has no NSID assigned."); + else + log_info("Our NSID is %" PRIu32, u); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-socket-util.c b/src/test/test-socket-util.c index e9c776a..e34aa10 100644 --- a/src/test/test-socket-util.c +++ b/src/test/test-socket-util.c @@ -69,7 +69,7 @@ static void test_socket_print_unix_one(const char *in, size_t len_in, const char assert_se(socket_address_print(&a, &out) >= 0); assert_se(c = cescape(in)); log_info("\"%s\" → \"%s\" (expect \"%s\")", in, out, expected); - assert_se(streq(out, expected)); + ASSERT_STREQ(out, expected); } TEST(socket_print_unix) { @@ -168,7 +168,7 @@ TEST(getpeercred_getpeergroups) { uid_t test_uid; gid_t test_gid; struct ucred ucred; - int pair[2]; + int pair[2] = EBADF_PAIR; if (geteuid() == 0) { test_uid = 1; @@ -176,10 +176,7 @@ TEST(getpeercred_getpeergroups) { test_gids = (gid_t*) gids; n_test_gids = ELEMENTSOF(gids); - assert_se(setgroups(n_test_gids, test_gids) >= 0); - assert_se(setresgid(test_gid, test_gid, test_gid) >= 0); - assert_se(setresuid(test_uid, test_uid, test_uid) >= 0); - + assert_se(fully_set_uid_gid(test_uid, test_gid, test_gids, n_test_gids) >= 0); } else { long ngroups_max; @@ -223,7 +220,7 @@ TEST(getpeercred_getpeergroups) { TEST(passfd_read) { static const char file_contents[] = "test contents for passfd"; - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); @@ -249,7 +246,7 @@ TEST(passfd_read) { /* Parent */ char buf[64]; struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); - _cleanup_close_ int fd; + _cleanup_close_ int fd = -EBADF; pair[1] = safe_close(pair[1]); @@ -259,11 +256,11 @@ TEST(passfd_read) { r = read(fd, buf, sizeof(buf)-1); assert_se(r >= 0); buf[r] = 0; - assert_se(streq(buf, file_contents)); + ASSERT_STREQ(buf, file_contents); } TEST(passfd_contents_read) { - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; static const char file_contents[] = "test contents in the file"; static const char wire_contents[] = "test contents on the wire"; int r; @@ -293,7 +290,7 @@ TEST(passfd_contents_read) { /* Parent */ char buf[64]; struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1); - _cleanup_close_ int fd; + _cleanup_close_ int fd = -EBADF; ssize_t k; pair[1] = safe_close(pair[1]); @@ -301,17 +298,17 @@ TEST(passfd_contents_read) { k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); assert_se(k > 0); buf[k] = 0; - assert_se(streq(buf, wire_contents)); + ASSERT_STREQ(buf, wire_contents); assert_se(fd >= 0); r = read(fd, buf, sizeof(buf)-1); assert_se(r >= 0); buf[r] = 0; - assert_se(streq(buf, file_contents)); + ASSERT_STREQ(buf, file_contents); } TEST(pass_many_fds_contents_read) { - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; static const char file_contents[][STRLEN("test contents in the fileX") + 1] = { "test contents in the file0", "test contents in the file1", @@ -361,7 +358,7 @@ TEST(pass_many_fds_contents_read) { k = receive_many_fds_iov(pair[0], &iov, 1, &fds, &n_fds, MSG_DONTWAIT); assert_se(k > 0); buf[k] = 0; - assert_se(streq(buf, wire_contents)); + ASSERT_STREQ(buf, wire_contents); assert_se(n_fds == 3); @@ -370,13 +367,13 @@ TEST(pass_many_fds_contents_read) { r = read(fds[i], buf, sizeof(buf)-1); assert_se(r >= 0); buf[r] = 0; - assert_se(streq(buf, file_contents[i])); + ASSERT_STREQ(buf, file_contents[i]); safe_close(fds[i]); } } TEST(receive_nopassfd) { - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; static const char wire_contents[] = "no fd passed here"; int r; @@ -406,14 +403,14 @@ TEST(receive_nopassfd) { k = receive_one_fd_iov(pair[0], &iov, 1, MSG_DONTWAIT, &fd); assert_se(k > 0); buf[k] = 0; - assert_se(streq(buf, wire_contents)); + ASSERT_STREQ(buf, wire_contents); /* no fd passed here, confirm it was reset */ assert_se(fd == -EBADF); } TEST(send_nodata_nofd) { - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); @@ -446,7 +443,7 @@ TEST(send_nodata_nofd) { } TEST(send_emptydata) { - _cleanup_close_pair_ int pair[2]; + _cleanup_close_pair_ int pair[2] = EBADF_PAIR; int r; assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0); diff --git a/src/test/test-specifier.c b/src/test/test-specifier.c index d6a8b79..7d25969 100644 --- a/src/test/test-specifier.c +++ b/src/test/test-specifier.c @@ -16,7 +16,7 @@ static void test_specifier_escape_one(const char *a, const char *b) { _cleanup_free_ char *x = NULL; x = specifier_escape(a); - assert_se(streq_ptr(x, b)); + ASSERT_STREQ(x, b); } TEST(specifier_escape) { @@ -73,7 +73,7 @@ TEST(specifier_printf) { assert_se(w); puts(w); - assert_se(streq(w, "xxx a=AAAA b=BBBB e= yyy")); + ASSERT_STREQ(w, "xxx a=AAAA b=BBBB e= yyy"); free(w); r = specifier_printf("boot=%b, host=%H, pretty=%q, version=%v, arch=%a, empty=%e", SIZE_MAX, table, NULL, NULL, &w); @@ -107,7 +107,7 @@ TEST(specifier_real_path) { /* /dev/initctl should normally be a symlink to /run/initctl */ if (inode_same("/dev/initctl", "/run/initctl", 0) > 0) - assert_se(streq(w, "p=/dev/initctl y=/run/initctl Y=/run w=/dev/tty W=/dev")); + ASSERT_STREQ(w, "p=/dev/initctl y=/run/initctl Y=/run w=/dev/tty W=/dev"); } TEST(specifier_real_path_missing_file) { @@ -138,6 +138,8 @@ TEST(specifiers) { xsprintf(spec, "%%%c", s->specifier); r = specifier_printf(spec, SIZE_MAX, specifier_table, NULL, NULL, &resolved); + if (s->specifier == 'A' && r == -EUNATCH) /* os-release might be missing in build chroots */ + continue; if (s->specifier == 'm' && IN_SET(r, -EUNATCH, -ENOMEDIUM, -ENOPKG)) /* machine-id might be missing in build chroots */ continue; assert_se(r >= 0); @@ -176,11 +178,11 @@ TEST(specifiers_missing_data_ok) { assert_se(setenv("SYSTEMD_OS_RELEASE", "/dev/null", 1) == 0); assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) >= 0); - assert_se(streq(resolved, "-----")); + ASSERT_STREQ(resolved, "-----"); assert_se(setenv("SYSTEMD_OS_RELEASE", "/nosuchfileordirectory", 1) == 0); assert_se(specifier_printf("%A-%B-%M-%o-%w-%W", SIZE_MAX, specifier_table, NULL, NULL, &resolved) == -EUNATCH); - assert_se(streq(resolved, "-----")); + ASSERT_STREQ(resolved, "-----"); assert_se(unsetenv("SYSTEMD_OS_RELEASE") == 0); } diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 8d7fd5b..6f274c9 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "alloc-util.h" @@ -73,11 +74,11 @@ TEST(is_symlink) { TEST(path_is_fs_type) { /* run might not be a mount point in build chroots */ - if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) { + if (path_is_mount_point_full("/run", NULL, AT_SYMLINK_FOLLOW) > 0) { assert_se(path_is_fs_type("/run", TMPFS_MAGIC) > 0); assert_se(path_is_fs_type("/run", BTRFS_SUPER_MAGIC) == 0); } - if (path_is_mount_point("/proc", NULL, AT_SYMLINK_FOLLOW) > 0) { + if (path_is_mount_point_full("/proc", NULL, AT_SYMLINK_FOLLOW) > 0) { assert_se(path_is_fs_type("/proc", PROC_SUPER_MAGIC) > 0); assert_se(path_is_fs_type("/proc", BTRFS_SUPER_MAGIC) == 0); } @@ -95,7 +96,7 @@ TEST(path_is_temporary_fs) { } /* run might not be a mount point in build chroots */ - if (path_is_mount_point("/run", NULL, AT_SYMLINK_FOLLOW) > 0) + if (path_is_mount_point_full("/run", NULL, AT_SYMLINK_FOLLOW) > 0) assert_se(path_is_temporary_fs("/run") > 0); assert_se(path_is_temporary_fs("/proc") == 0); assert_se(path_is_temporary_fs("/i-dont-exist") == -ENOENT); @@ -111,7 +112,7 @@ TEST(path_is_read_only_fs) { s, r, r < 0 ? errno_to_name(r) : yes_no(r)); } - if (path_is_mount_point("/sys", NULL, AT_SYMLINK_FOLLOW) > 0) + if (path_is_mount_point_full("/sys", NULL, AT_SYMLINK_FOLLOW) > 0) assert_se(IN_SET(path_is_read_only_fs("/sys"), 0, 1)); assert_se(path_is_read_only_fs("/proc") == 0); @@ -180,6 +181,36 @@ TEST(dir_is_empty) { assert_se(dir_is_empty_at(AT_FDCWD, empty_dir, /* ignore_hidden_or_backup= */ false) > 0); } +TEST(inode_type_from_string) { + static const mode_t types[] = { + S_IFREG, + S_IFDIR, + S_IFLNK, + S_IFCHR, + S_IFBLK, + S_IFIFO, + S_IFSOCK, + }; + + FOREACH_ELEMENT(m, types) + assert_se(inode_type_from_string(inode_type_to_string(*m)) == *m); +} + +TEST(anonymous_inode) { + _cleanup_close_ int fd = -EBADF; + + fd = eventfd(0, EFD_CLOEXEC); + assert_se(fd >= 0); + + /* Verify that we handle anonymous inodes correctly, i.e. those which have no file type */ + + struct stat st; + ASSERT_OK_ERRNO(fstat(fd, &st)); + assert_se((st.st_mode & S_IFMT) == 0); + + assert_se(!inode_type_to_string(st.st_mode)); +} + TEST(fd_verify_linked) { _cleanup_(rm_rf_physical_and_freep) char *t = NULL; _cleanup_close_ int tfd = -EBADF, fd = -EBADF; diff --git a/src/test/test-strbuf.c b/src/test/test-strbuf.c index 39a7142..c0a4d11 100644 --- a/src/test/test-strbuf.c +++ b/src/test/test-strbuf.c @@ -32,12 +32,12 @@ TEST(strbuf) { l = strv_parse_nulstr(sb->buf, sb->len); assert_se(l); - assert_se(streq(l[0], "")); /* root */ - assert_se(streq(l[1], "waldo")); - assert_se(streq(l[2], "foo")); - assert_se(streq(l[3], "bar")); - assert_se(streq(l[4], "waldorf")); - assert_se(l[5] == NULL); + ASSERT_STREQ(l[0], ""); /* root */ + ASSERT_STREQ(l[1], "waldo"); + ASSERT_STREQ(l[2], "foo"); + ASSERT_STREQ(l[3], "bar"); + ASSERT_STREQ(l[4], "waldorf"); + ASSERT_NULL(l[5]); assert_se(sb->nodes_count == 5); /* root + 4 non-duplicates */ assert_se(sb->dedup_count == 4); @@ -57,17 +57,17 @@ TEST(strbuf) { assert_se(g == 15); assert_se(h == 0); - assert_se(streq(sb->buf + a, "waldo")); - assert_se(streq(sb->buf + b, "foo")); - assert_se(streq(sb->buf + c, "bar")); - assert_se(streq(sb->buf + d, "waldo")); - assert_se(streq(sb->buf + e, "aldo")); - assert_se(streq(sb->buf + f, "do")); - assert_se(streq(sb->buf + g, "waldorf")); - assert_se(streq(sb->buf + h, "")); + ASSERT_STREQ(sb->buf + a, "waldo"); + ASSERT_STREQ(sb->buf + b, "foo"); + ASSERT_STREQ(sb->buf + c, "bar"); + ASSERT_STREQ(sb->buf + d, "waldo"); + ASSERT_STREQ(sb->buf + e, "aldo"); + ASSERT_STREQ(sb->buf + f, "do"); + ASSERT_STREQ(sb->buf + g, "waldorf"); + ASSERT_STREQ(sb->buf + h, ""); strbuf_complete(sb); - assert_se(sb->root == NULL); + ASSERT_NULL(sb->root); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-string-util.c b/src/test/test-string-util.c index a8fd45d..4bf7548 100644 --- a/src/test/test-string-util.c +++ b/src/test/test-string-util.c @@ -13,13 +13,13 @@ TEST(string_erase) { char *x; x = strdupa_safe(""); - assert_se(streq(string_erase(x), "")); + ASSERT_STREQ(string_erase(x), ""); x = strdupa_safe("1"); - assert_se(streq(string_erase(x), "")); + ASSERT_STREQ(string_erase(x), ""); x = strdupa_safe("123456789"); - assert_se(streq(string_erase(x), "")); + ASSERT_STREQ(string_erase(x), ""); assert_se(x[1] == '\0'); assert_se(x[2] == '\0'); @@ -37,7 +37,7 @@ static void test_free_and_strndup_one(char **t, const char *src, size_t l, const __func__, strnull(*t), strnull(src), l, strnull(expected), yes_no(change)); int r = free_and_strndup(t, src, l); - assert_se(streq_ptr(*t, expected)); + ASSERT_STREQ(*t, expected); assert_se(r == change); /* check that change occurs only when necessary */ } @@ -88,6 +88,35 @@ TEST(free_and_strndup) { } } +TEST(strdup_to_full) { + _cleanup_free_ char *dst; + + assert_se(strdup_to_full(NULL, NULL) == 0); + assert_se(strdup_to_full(&dst, NULL) == 0); + + assert_se(strdup_to_full(NULL, "") == 1); + assert_se(strdup_to_full(&dst, "") == 1); + ASSERT_STREQ(dst, ""); + dst = mfree(dst); + + assert_se(strdup_to_full(NULL, "x") == 1); + assert_se(strdup_to_full(&dst, "x") == 1); + ASSERT_STREQ(dst, "x"); +} + +TEST(strdup_to) { + _cleanup_free_ char *dst; + + assert_se(strdup_to(&dst, NULL) == 0); + + assert_se(strdup_to(&dst, "") == 0); + ASSERT_STREQ(dst, ""); + dst = mfree(dst); + + assert_se(strdup_to(&dst, "x") == 0); + ASSERT_STREQ(dst, "x"); +} + TEST(ascii_strcasecmp_n) { assert_se(ascii_strcasecmp_n("", "", 0) == 0); assert_se(ascii_strcasecmp_n("", "", 1) == 0); @@ -134,82 +163,82 @@ TEST(ascii_strcasecmp_nn) { TEST(cellescape) { char buf[40]; - assert_se(streq(cellescape(buf, 1, ""), "")); - assert_se(streq(cellescape(buf, 1, "1"), "")); - assert_se(streq(cellescape(buf, 1, "12"), "")); - - assert_se(streq(cellescape(buf, 2, ""), "")); - assert_se(streq(cellescape(buf, 2, "1"), "1")); - assert_se(streq(cellescape(buf, 2, "12"), ".")); - assert_se(streq(cellescape(buf, 2, "123"), ".")); - - assert_se(streq(cellescape(buf, 3, ""), "")); - assert_se(streq(cellescape(buf, 3, "1"), "1")); - assert_se(streq(cellescape(buf, 3, "12"), "12")); - assert_se(streq(cellescape(buf, 3, "123"), "..")); - assert_se(streq(cellescape(buf, 3, "1234"), "..")); - - assert_se(streq(cellescape(buf, 4, ""), "")); - assert_se(streq(cellescape(buf, 4, "1"), "1")); - assert_se(streq(cellescape(buf, 4, "12"), "12")); - assert_se(streq(cellescape(buf, 4, "123"), "123")); - assert_se(streq(cellescape(buf, 4, "1234"), is_locale_utf8() ? "…" : "...")); - assert_se(streq(cellescape(buf, 4, "12345"), is_locale_utf8() ? "…" : "...")); - - assert_se(streq(cellescape(buf, 5, ""), "")); - assert_se(streq(cellescape(buf, 5, "1"), "1")); - assert_se(streq(cellescape(buf, 5, "12"), "12")); - assert_se(streq(cellescape(buf, 5, "123"), "123")); - assert_se(streq(cellescape(buf, 5, "1234"), "1234")); - assert_se(streq(cellescape(buf, 5, "12345"), is_locale_utf8() ? "1…" : "1...")); - assert_se(streq(cellescape(buf, 5, "123456"), is_locale_utf8() ? "1…" : "1...")); - - assert_se(streq(cellescape(buf, 1, "\020"), "")); - assert_se(streq(cellescape(buf, 2, "\020"), ".")); - assert_se(streq(cellescape(buf, 3, "\020"), "..")); - assert_se(streq(cellescape(buf, 4, "\020"), is_locale_utf8() ? "…" : "...")); - assert_se(streq(cellescape(buf, 5, "\020"), "\\020")); - - assert_se(streq(cellescape(buf, 5, "1234\020"), is_locale_utf8() ? "1…" : "1...")); - assert_se(streq(cellescape(buf, 6, "1234\020"), is_locale_utf8() ? "12…" : "12...")); - assert_se(streq(cellescape(buf, 7, "1234\020"), is_locale_utf8() ? "123…" : "123...")); - assert_se(streq(cellescape(buf, 8, "1234\020"), is_locale_utf8() ? "1234…" : "1234...")); - assert_se(streq(cellescape(buf, 9, "1234\020"), "1234\\020")); - - assert_se(streq(cellescape(buf, 1, "\t\n"), "")); - assert_se(streq(cellescape(buf, 2, "\t\n"), ".")); - assert_se(streq(cellescape(buf, 3, "\t\n"), "..")); - assert_se(streq(cellescape(buf, 4, "\t\n"), is_locale_utf8() ? "…" : "...")); - assert_se(streq(cellescape(buf, 5, "\t\n"), "\\t\\n")); - - assert_se(streq(cellescape(buf, 5, "1234\t\n"), is_locale_utf8() ? "1…" : "1...")); - assert_se(streq(cellescape(buf, 6, "1234\t\n"), is_locale_utf8() ? "12…" : "12...")); - assert_se(streq(cellescape(buf, 7, "1234\t\n"), is_locale_utf8() ? "123…" : "123...")); - assert_se(streq(cellescape(buf, 8, "1234\t\n"), is_locale_utf8() ? "1234…" : "1234...")); - assert_se(streq(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n")); - - assert_se(streq(cellescape(buf, 4, "x\t\020\n"), is_locale_utf8() ? "…" : "...")); - assert_se(streq(cellescape(buf, 5, "x\t\020\n"), is_locale_utf8() ? "x…" : "x...")); - assert_se(streq(cellescape(buf, 6, "x\t\020\n"), is_locale_utf8() ? "x…" : "x...")); - assert_se(streq(cellescape(buf, 7, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); - assert_se(streq(cellescape(buf, 8, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); - assert_se(streq(cellescape(buf, 9, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t...")); - assert_se(streq(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n")); - - assert_se(streq(cellescape(buf, 6, "1\011"), "1\\t")); - assert_se(streq(cellescape(buf, 6, "1\020"), "1\\020")); - assert_se(streq(cellescape(buf, 6, "1\020x"), is_locale_utf8() ? "1…" : "1...")); - - assert_se(streq(cellescape(buf, 40, "1\020"), "1\\020")); - assert_se(streq(cellescape(buf, 40, "1\020x"), "1\\020x")); - - assert_se(streq(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'")); - assert_se(streq(cellescape(buf, 6, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); - assert_se(streq(cellescape(buf, 7, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a...")); - assert_se(streq(cellescape(buf, 8, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b...")); - - assert_se(streq(cellescape(buf, sizeof buf, "1\020"), "1\\020")); - assert_se(streq(cellescape(buf, sizeof buf, "1\020x"), "1\\020x")); + ASSERT_STREQ(cellescape(buf, 1, ""), ""); + ASSERT_STREQ(cellescape(buf, 1, "1"), ""); + ASSERT_STREQ(cellescape(buf, 1, "12"), ""); + + ASSERT_STREQ(cellescape(buf, 2, ""), ""); + ASSERT_STREQ(cellescape(buf, 2, "1"), "1"); + ASSERT_STREQ(cellescape(buf, 2, "12"), "."); + ASSERT_STREQ(cellescape(buf, 2, "123"), "."); + + ASSERT_STREQ(cellescape(buf, 3, ""), ""); + ASSERT_STREQ(cellescape(buf, 3, "1"), "1"); + ASSERT_STREQ(cellescape(buf, 3, "12"), "12"); + ASSERT_STREQ(cellescape(buf, 3, "123"), ".."); + ASSERT_STREQ(cellescape(buf, 3, "1234"), ".."); + + ASSERT_STREQ(cellescape(buf, 4, ""), ""); + ASSERT_STREQ(cellescape(buf, 4, "1"), "1"); + ASSERT_STREQ(cellescape(buf, 4, "12"), "12"); + ASSERT_STREQ(cellescape(buf, 4, "123"), "123"); + ASSERT_STREQ(cellescape(buf, 4, "1234"), is_locale_utf8() ? "…" : "..."); + ASSERT_STREQ(cellescape(buf, 4, "12345"), is_locale_utf8() ? "…" : "..."); + + ASSERT_STREQ(cellescape(buf, 5, ""), ""); + ASSERT_STREQ(cellescape(buf, 5, "1"), "1"); + ASSERT_STREQ(cellescape(buf, 5, "12"), "12"); + ASSERT_STREQ(cellescape(buf, 5, "123"), "123"); + ASSERT_STREQ(cellescape(buf, 5, "1234"), "1234"); + ASSERT_STREQ(cellescape(buf, 5, "12345"), is_locale_utf8() ? "1…" : "1..."); + ASSERT_STREQ(cellescape(buf, 5, "123456"), is_locale_utf8() ? "1…" : "1..."); + + ASSERT_STREQ(cellescape(buf, 1, "\020"), ""); + ASSERT_STREQ(cellescape(buf, 2, "\020"), "."); + ASSERT_STREQ(cellescape(buf, 3, "\020"), ".."); + ASSERT_STREQ(cellescape(buf, 4, "\020"), is_locale_utf8() ? "…" : "..."); + ASSERT_STREQ(cellescape(buf, 5, "\020"), "\\020"); + + ASSERT_STREQ(cellescape(buf, 5, "1234\020"), is_locale_utf8() ? "1…" : "1..."); + ASSERT_STREQ(cellescape(buf, 6, "1234\020"), is_locale_utf8() ? "12…" : "12..."); + ASSERT_STREQ(cellescape(buf, 7, "1234\020"), is_locale_utf8() ? "123…" : "123..."); + ASSERT_STREQ(cellescape(buf, 8, "1234\020"), is_locale_utf8() ? "1234…" : "1234..."); + ASSERT_STREQ(cellescape(buf, 9, "1234\020"), "1234\\020"); + + ASSERT_STREQ(cellescape(buf, 1, "\t\n"), ""); + ASSERT_STREQ(cellescape(buf, 2, "\t\n"), "."); + ASSERT_STREQ(cellescape(buf, 3, "\t\n"), ".."); + ASSERT_STREQ(cellescape(buf, 4, "\t\n"), is_locale_utf8() ? "…" : "..."); + ASSERT_STREQ(cellescape(buf, 5, "\t\n"), "\\t\\n"); + + ASSERT_STREQ(cellescape(buf, 5, "1234\t\n"), is_locale_utf8() ? "1…" : "1..."); + ASSERT_STREQ(cellescape(buf, 6, "1234\t\n"), is_locale_utf8() ? "12…" : "12..."); + ASSERT_STREQ(cellescape(buf, 7, "1234\t\n"), is_locale_utf8() ? "123…" : "123..."); + ASSERT_STREQ(cellescape(buf, 8, "1234\t\n"), is_locale_utf8() ? "1234…" : "1234..."); + ASSERT_STREQ(cellescape(buf, 9, "1234\t\n"), "1234\\t\\n"); + + ASSERT_STREQ(cellescape(buf, 4, "x\t\020\n"), is_locale_utf8() ? "…" : "..."); + ASSERT_STREQ(cellescape(buf, 5, "x\t\020\n"), is_locale_utf8() ? "x…" : "x..."); + ASSERT_STREQ(cellescape(buf, 6, "x\t\020\n"), is_locale_utf8() ? "x…" : "x..."); + ASSERT_STREQ(cellescape(buf, 7, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t..."); + ASSERT_STREQ(cellescape(buf, 8, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t..."); + ASSERT_STREQ(cellescape(buf, 9, "x\t\020\n"), is_locale_utf8() ? "x\\t…" : "x\\t..."); + ASSERT_STREQ(cellescape(buf, 10, "x\t\020\n"), "x\\t\\020\\n"); + + ASSERT_STREQ(cellescape(buf, 6, "1\011"), "1\\t"); + ASSERT_STREQ(cellescape(buf, 6, "1\020"), "1\\020"); + ASSERT_STREQ(cellescape(buf, 6, "1\020x"), is_locale_utf8() ? "1…" : "1..."); + + ASSERT_STREQ(cellescape(buf, 40, "1\020"), "1\\020"); + ASSERT_STREQ(cellescape(buf, 40, "1\020x"), "1\\020x"); + + ASSERT_STREQ(cellescape(buf, 40, "\a\b\f\n\r\t\v\\\"'"), "\\a\\b\\f\\n\\r\\t\\v\\\\\\\"\\'"); + ASSERT_STREQ(cellescape(buf, 6, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."); + ASSERT_STREQ(cellescape(buf, 7, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a…" : "\\a..."); + ASSERT_STREQ(cellescape(buf, 8, "\a\b\f\n\r\t\v\\\"'"), is_locale_utf8() ? "\\a\\b…" : "\\a\\b..."); + + ASSERT_STREQ(cellescape(buf, sizeof buf, "1\020"), "1\\020"); + ASSERT_STREQ(cellescape(buf, sizeof buf, "1\020x"), "1\\020x"); } TEST(streq_ptr) { @@ -221,41 +250,41 @@ TEST(strstrip) { char *ret, input[] = " hello, waldo. "; ret = strstrip(input); - assert_se(streq(ret, "hello, waldo.")); + ASSERT_STREQ(ret, "hello, waldo."); } TEST(strextend) { _cleanup_free_ char *str = NULL; assert_se(strextend(&str, NULL)); - assert_se(streq_ptr(str, "")); + ASSERT_STREQ(str, ""); assert_se(strextend(&str, "", "0", "", "", "123")); - assert_se(streq_ptr(str, "0123")); + ASSERT_STREQ(str, "0123"); assert_se(strextend(&str, "456", "78", "9")); - assert_se(streq_ptr(str, "0123456789")); + ASSERT_STREQ(str, "0123456789"); } TEST(strextend_with_separator) { _cleanup_free_ char *str = NULL; assert_se(strextend_with_separator(&str, NULL, NULL)); - assert_se(streq_ptr(str, "")); + ASSERT_STREQ(str, ""); str = mfree(str); assert_se(strextend_with_separator(&str, "...", NULL)); - assert_se(streq_ptr(str, "")); + ASSERT_STREQ(str, ""); assert_se(strextend_with_separator(&str, "...", NULL)); - assert_se(streq_ptr(str, "")); + ASSERT_STREQ(str, ""); str = mfree(str); assert_se(strextend_with_separator(&str, "xyz", "a", "bb", "ccc")); - assert_se(streq_ptr(str, "axyzbbxyzccc")); + ASSERT_STREQ(str, "axyzbbxyzccc"); str = mfree(str); assert_se(strextend_with_separator(&str, ",", "start", "", "1", "234")); - assert_se(streq_ptr(str, "start,,1,234")); + ASSERT_STREQ(str, "start,,1,234"); assert_se(strextend_with_separator(&str, ";", "more", "5", "678")); - assert_se(streq_ptr(str, "start,,1,234;more;5;678")); + ASSERT_STREQ(str, "start,,1,234;more;5;678"); } TEST(strrep) { @@ -266,15 +295,15 @@ TEST(strrep) { three = strrep("waldo", 3); zero = strrep("waldo", 0); - assert_se(streq(one, "waldo")); - assert_se(streq(three, "waldowaldowaldo")); - assert_se(streq(zero, "")); + ASSERT_STREQ(one, "waldo"); + ASSERT_STREQ(three, "waldowaldowaldo"); + ASSERT_STREQ(zero, ""); onea = strrepa("waldo", 1); threea = strrepa("waldo", 3); - assert_se(streq(onea, "waldo")); - assert_se(streq(threea, "waldowaldowaldo")); + ASSERT_STREQ(onea, "waldo"); + ASSERT_STREQ(threea, "waldowaldowaldo"); } TEST(string_has_cc) { @@ -293,14 +322,19 @@ TEST(string_has_cc) { TEST(ascii_strlower) { char a[] = "AabBcC Jk Ii Od LKJJJ kkd LK"; - assert_se(streq(ascii_strlower(a), "aabbcc jk ii od lkjjj kkd lk")); + ASSERT_STREQ(ascii_strlower(a), "aabbcc jk ii od lkjjj kkd lk"); } TEST(strshorten) { char s[] = "foobar"; + assert_se(strlen(strshorten(s, SIZE_MAX)) == 6); + assert_se(strlen(strshorten(s, SIZE_MAX-1)) == 6); + assert_se(strlen(strshorten(s, SIZE_MAX-2)) == 6); assert_se(strlen(strshorten(s, 6)) == 6); + assert_se(strlen(strshorten(s, 7)) == 6); assert_se(strlen(strshorten(s, 12)) == 6); + assert_se(strlen(strshorten(s, 5)) == 5); assert_se(strlen(strshorten(s, 2)) == 2); assert_se(strlen(strshorten(s, 0)) == 0); } @@ -309,63 +343,63 @@ TEST(strjoina) { char *actual; actual = strjoina("", "foo", "bar"); - assert_se(streq(actual, "foobar")); + ASSERT_STREQ(actual, "foobar"); actual = strjoina("foo", "bar", "baz"); - assert_se(streq(actual, "foobarbaz")); + ASSERT_STREQ(actual, "foobarbaz"); actual = strjoina("foo", "", "bar", "baz"); - assert_se(streq(actual, "foobarbaz")); + ASSERT_STREQ(actual, "foobarbaz"); actual = strjoina("foo"); - assert_se(streq(actual, "foo")); + ASSERT_STREQ(actual, "foo"); actual = strjoina(NULL); - assert_se(streq(actual, "")); + ASSERT_STREQ(actual, ""); actual = strjoina(NULL, "foo"); - assert_se(streq(actual, "")); + ASSERT_STREQ(actual, ""); actual = strjoina("foo", NULL, "bar"); - assert_se(streq(actual, "foo")); + ASSERT_STREQ(actual, "foo"); actual = strjoina("/sys/fs/cgroup/", "dn", "/a/b/c", "/cgroup.procs"); - assert_se(streq(actual, "/sys/fs/cgroup/dn/a/b/c/cgroup.procs")); + ASSERT_STREQ(actual, "/sys/fs/cgroup/dn/a/b/c/cgroup.procs"); actual = strjoina("/sys/fs/cgroup/", "dn", NULL, NULL); - assert_se(streq(actual, "/sys/fs/cgroup/dn")); + ASSERT_STREQ(actual, "/sys/fs/cgroup/dn"); } TEST(strjoin) { char *actual; actual = strjoin("", "foo", "bar"); - assert_se(streq(actual, "foobar")); - mfree(actual); + ASSERT_STREQ(actual, "foobar"); + free(actual); actual = strjoin("foo", "bar", "baz"); - assert_se(streq(actual, "foobarbaz")); - mfree(actual); + ASSERT_STREQ(actual, "foobarbaz"); + free(actual); actual = strjoin("foo", "", "bar", "baz"); - assert_se(streq(actual, "foobarbaz")); - mfree(actual); + ASSERT_STREQ(actual, "foobarbaz"); + free(actual); actual = strjoin("foo", NULL); - assert_se(streq(actual, "foo")); - mfree(actual); + ASSERT_STREQ(actual, "foo"); + free(actual); actual = strjoin(NULL, NULL); - assert_se(streq(actual, "")); - mfree(actual); + ASSERT_STREQ(actual, ""); + free(actual); actual = strjoin(NULL, "foo"); - assert_se(streq(actual, "")); - mfree(actual); + ASSERT_STREQ(actual, ""); + free(actual); actual = strjoin("foo", NULL, "bar"); - assert_se(streq(actual, "foo")); - mfree(actual); + ASSERT_STREQ(actual, "foo"); + free(actual); } TEST(strcmp_ptr) { @@ -402,7 +436,7 @@ TEST(foreach_word) { } assert_se(r > 0); - assert_se(streq(expected[i++], word)); + ASSERT_STREQ(expected[i++], word); } } @@ -423,10 +457,10 @@ static void check(const char *test, char** expected, bool trailing) { break; } - assert_se(streq(word, expected[i++])); + ASSERT_STREQ(word, expected[i++]); printf("<%s>\n", word); } - assert_se(expected[i] == NULL); + ASSERT_NULL(expected[i]); } TEST(foreach_word_quoted) { @@ -478,7 +512,7 @@ TEST(delete_chars) { char *s, input[] = " hello, waldo. abc"; s = delete_chars(input, WHITESPACE); - assert_se(streq(s, "hello,waldo.abc")); + ASSERT_STREQ(s, "hello,waldo.abc"); assert_se(s == input); } @@ -489,19 +523,19 @@ TEST(delete_trailing_chars) { input3[] = "abcdef"; s = delete_trailing_chars(input1, WHITESPACE); - assert_se(streq(s, " \n \r k")); + ASSERT_STREQ(s, " \n \r k"); assert_se(s == input1); s = delete_trailing_chars(input2, "kt"); - assert_se(streq(s, "kkkkthiskkkiskkkaktes")); + ASSERT_STREQ(s, "kkkkthiskkkiskkkaktes"); assert_se(s == input2); s = delete_trailing_chars(input3, WHITESPACE); - assert_se(streq(s, "abcdef")); + ASSERT_STREQ(s, "abcdef"); assert_se(s == input3); s = delete_trailing_chars(input3, "fe"); - assert_se(streq(s, "abcd")); + ASSERT_STREQ(s, "abcd"); assert_se(s == input3); } @@ -511,11 +545,11 @@ TEST(delete_trailing_slashes) { s3[] = "foobar", s4[] = ""; - assert_se(streq(delete_trailing_chars(s1, "_"), "foobar//")); - assert_se(streq(delete_trailing_chars(s1, "/"), "foobar")); - assert_se(streq(delete_trailing_chars(s2, "/"), "foobar")); - assert_se(streq(delete_trailing_chars(s3, "/"), "foobar")); - assert_se(streq(delete_trailing_chars(s4, "/"), "")); + ASSERT_STREQ(delete_trailing_chars(s1, "_"), "foobar//"); + ASSERT_STREQ(delete_trailing_chars(s1, "/"), "foobar"); + ASSERT_STREQ(delete_trailing_chars(s2, "/"), "foobar"); + ASSERT_STREQ(delete_trailing_chars(s3, "/"), "foobar"); + ASSERT_STREQ(delete_trailing_chars(s4, "/"), ""); } TEST(skip_leading_chars) { @@ -523,11 +557,11 @@ TEST(skip_leading_chars) { input2[] = "kkkkthiskkkiskkkaktestkkk", input3[] = "abcdef"; - assert_se(streq(skip_leading_chars(input1, WHITESPACE), "k \n \r ")); - assert_se(streq(skip_leading_chars(input2, "k"), "thiskkkiskkkaktestkkk")); - assert_se(streq(skip_leading_chars(input2, "tk"), "hiskkkiskkkaktestkkk")); - assert_se(streq(skip_leading_chars(input3, WHITESPACE), "abcdef")); - assert_se(streq(skip_leading_chars(input3, "bcaef"), "def")); + ASSERT_STREQ(skip_leading_chars(input1, WHITESPACE), "k \n \r "); + ASSERT_STREQ(skip_leading_chars(input2, "k"), "thiskkkiskkkaktestkkk"); + ASSERT_STREQ(skip_leading_chars(input2, "tk"), "hiskkkiskkkaktestkkk"); + ASSERT_STREQ(skip_leading_chars(input3, WHITESPACE), "abcdef"); + ASSERT_STREQ(skip_leading_chars(input3, "bcaef"), "def"); } TEST(in_charset) { @@ -542,19 +576,19 @@ TEST(split_pair) { assert_se(split_pair("foo=bar", "", &a, &b) == -EINVAL); assert_se(split_pair("", "=", &a, &b) == -EINVAL); assert_se(split_pair("foo=bar", "=", &a, &b) >= 0); - assert_se(streq(a, "foo")); - assert_se(streq(b, "bar")); + ASSERT_STREQ(a, "foo"); + ASSERT_STREQ(b, "bar"); free(a); free(b); assert_se(split_pair("==", "==", &a, &b) >= 0); - assert_se(streq(a, "")); - assert_se(streq(b, "")); + ASSERT_STREQ(a, ""); + ASSERT_STREQ(b, ""); free(a); free(b); assert_se(split_pair("===", "==", &a, &b) >= 0); - assert_se(streq(a, "")); - assert_se(streq(b, "=")); + ASSERT_STREQ(a, ""); + ASSERT_STREQ(b, "="); } TEST(first_word) { @@ -578,33 +612,33 @@ TEST(strlen_ptr) { } TEST(memory_startswith) { - assert_se(streq(memory_startswith("", 0, ""), "")); - assert_se(streq(memory_startswith("", 1, ""), "")); - assert_se(streq(memory_startswith("x", 2, ""), "x")); + ASSERT_STREQ(memory_startswith("", 0, ""), ""); + ASSERT_STREQ(memory_startswith("", 1, ""), ""); + ASSERT_STREQ(memory_startswith("x", 2, ""), "x"); assert_se(!memory_startswith("", 1, "x")); assert_se(!memory_startswith("", 1, "xxxxxxxx")); - assert_se(streq(memory_startswith("xxx", 4, "x"), "xx")); - assert_se(streq(memory_startswith("xxx", 4, "xx"), "x")); - assert_se(streq(memory_startswith("xxx", 4, "xxx"), "")); + ASSERT_STREQ(memory_startswith("xxx", 4, "x"), "xx"); + ASSERT_STREQ(memory_startswith("xxx", 4, "xx"), "x"); + ASSERT_STREQ(memory_startswith("xxx", 4, "xxx"), ""); assert_se(!memory_startswith("xxx", 4, "xxxx")); } TEST(memory_startswith_no_case) { - assert_se(streq(memory_startswith_no_case("", 0, ""), "")); - assert_se(streq(memory_startswith_no_case("", 1, ""), "")); - assert_se(streq(memory_startswith_no_case("x", 2, ""), "x")); - assert_se(streq(memory_startswith_no_case("X", 2, ""), "X")); + ASSERT_STREQ(memory_startswith_no_case("", 0, ""), ""); + ASSERT_STREQ(memory_startswith_no_case("", 1, ""), ""); + ASSERT_STREQ(memory_startswith_no_case("x", 2, ""), "x"); + ASSERT_STREQ(memory_startswith_no_case("X", 2, ""), "X"); assert_se(!memory_startswith_no_case("", 1, "X")); assert_se(!memory_startswith_no_case("", 1, "xxxxXXXX")); - assert_se(streq(memory_startswith_no_case("xxx", 4, "X"), "xx")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "x"), "XX")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "X"), "XX")); - assert_se(streq(memory_startswith_no_case("xxx", 4, "XX"), "x")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "xx"), "X")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "XX"), "X")); - assert_se(streq(memory_startswith_no_case("xxx", 4, "XXX"), "")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "xxx"), "")); - assert_se(streq(memory_startswith_no_case("XXX", 4, "XXX"), "")); + ASSERT_STREQ(memory_startswith_no_case("xxx", 4, "X"), "xx"); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "x"), "XX"); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "X"), "XX"); + ASSERT_STREQ(memory_startswith_no_case("xxx", 4, "XX"), "x"); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "xx"), "X"); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "XX"), "X"); + ASSERT_STREQ(memory_startswith_no_case("xxx", 4, "XXX"), ""); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "xxx"), ""); + ASSERT_STREQ(memory_startswith_no_case("XXX", 4, "XXX"), ""); assert_se(memory_startswith_no_case((char[2]){'x', 'x'}, 2, "xx")); assert_se(memory_startswith_no_case((char[2]){'x', 'X'}, 2, "xX")); @@ -617,7 +651,7 @@ static void test_string_truncate_lines_one(const char *input, size_t n_lines, co int k; assert_se((k = string_truncate_lines(input, n_lines, &b)) >= 0); - assert_se(streq(b, output)); + ASSERT_STREQ(b, output); assert_se(!!k == truncation); } @@ -688,7 +722,7 @@ static void test_string_extract_lines_one(const char *input, size_t i, const cha int k; assert_se((k = string_extract_line(input, i, &b)) >= 0); - assert_se(streq(b ?: input, output)); + ASSERT_STREQ(b ?: input, output); assert_se(!!k == more); } @@ -765,25 +799,25 @@ TEST(string_contains_word_strv) { assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("a", "b"), NULL)); assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("a", "b"), &w)); - assert_se(streq(w, "a")); + ASSERT_STREQ(w, "a"); assert_se(!string_contains_word_strv("a b cc", NULL, STRV_MAKE("d"), &w)); - assert_se(w == NULL); + ASSERT_NULL(w); assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("b", "a"), &w)); - assert_se(streq(w, "a")); + ASSERT_STREQ(w, "a"); assert_se(string_contains_word_strv("b a b cc", NULL, STRV_MAKE("b", "a", "b"), &w)); - assert_se(streq(w, "b")); + ASSERT_STREQ(w, "b"); assert_se(string_contains_word_strv("a b cc", NULL, STRV_MAKE("b", ""), &w)); - assert_se(streq(w, "b")); + ASSERT_STREQ(w, "b"); assert_se(!string_contains_word_strv("a b cc", NULL, STRV_MAKE(""), &w)); - assert_se(w == NULL); + ASSERT_NULL(w); assert_se(string_contains_word_strv("a b cc", " ", STRV_MAKE(""), &w)); - assert_se(streq(w, "")); + ASSERT_STREQ(w, ""); } TEST(string_contains_word) { @@ -1105,38 +1139,38 @@ TEST(strextendf) { _cleanup_free_ char *p = NULL; assert_se(strextendf(&p, "<%i>", 77) >= 0); - assert_se(streq(p, "<77>")); + ASSERT_STREQ(p, "<77>"); assert_se(strextendf(&p, "<%i>", 99) >= 0); - assert_se(streq(p, "<77><99>")); + ASSERT_STREQ(p, "<77><99>"); assert_se(strextendf(&p, "<%80i>", 88) >= 0); - assert_se(streq(p, "<77><99>< 88>")); + ASSERT_STREQ(p, "<77><99>< 88>"); assert_se(strextendf(&p, "<%08x>", 0x1234u) >= 0); - assert_se(streq(p, "<77><99>< 88><00001234>")); + ASSERT_STREQ(p, "<77><99>< 88><00001234>"); p = mfree(p); assert_se(strextendf_with_separator(&p, ",", "<%i>", 77) >= 0); - assert_se(streq(p, "<77>")); + ASSERT_STREQ(p, "<77>"); assert_se(strextendf_with_separator(&p, ",", "<%i>", 99) >= 0); - assert_se(streq(p, "<77>,<99>")); + ASSERT_STREQ(p, "<77>,<99>"); assert_se(strextendf_with_separator(&p, ",", "<%80i>", 88) >= 0); - assert_se(streq(p, "<77>,<99>,< 88>")); + ASSERT_STREQ(p, "<77>,<99>,< 88>"); assert_se(strextendf_with_separator(&p, ",", "<%08x>", 0x1234u) >= 0); - assert_se(streq(p, "<77>,<99>,< 88>,<00001234>")); + ASSERT_STREQ(p, "<77>,<99>,< 88>,<00001234>"); } TEST(string_replace_char) { - assert_se(streq(string_replace_char(strdupa_safe(""), 'a', 'b'), "")); - assert_se(streq(string_replace_char(strdupa_safe("abc"), 'a', 'b'), "bbc")); - assert_se(streq(string_replace_char(strdupa_safe("hoge"), 'a', 'b'), "hoge")); - assert_se(streq(string_replace_char(strdupa_safe("aaaa"), 'a', 'b'), "bbbb")); - assert_se(streq(string_replace_char(strdupa_safe("aaaa"), 'a', '\t'), "\t\t\t\t")); + ASSERT_STREQ(string_replace_char(strdupa_safe(""), 'a', 'b'), ""); + ASSERT_STREQ(string_replace_char(strdupa_safe("abc"), 'a', 'b'), "bbc"); + ASSERT_STREQ(string_replace_char(strdupa_safe("hoge"), 'a', 'b'), "hoge"); + ASSERT_STREQ(string_replace_char(strdupa_safe("aaaa"), 'a', 'b'), "bbbb"); + ASSERT_STREQ(string_replace_char(strdupa_safe("aaaa"), 'a', '\t'), "\t\t\t\t"); } TEST(strspn_from_end) { @@ -1180,7 +1214,7 @@ TEST(streq_skip_trailing_chars) { do { \ _cleanup_free_ char *b = NULL; \ assert_se(make_cstring((x), ELEMENTSOF(x), (mode), &b) == (ret)); \ - assert_se(streq_ptr(b, (expect))); \ + ASSERT_STREQ(b, (expect)); \ } while(false) TEST(make_cstring) { @@ -1261,7 +1295,7 @@ TEST(strstrafter) { assert_se(!strstrafter(NULL, NULL)); assert_se(!strstrafter("", NULL)); assert_se(!strstrafter(NULL, "")); - assert_se(streq_ptr(strstrafter("", ""), "")); + ASSERT_STREQ(strstrafter("", ""), ""); assert_se(strstrafter(buffer, "a") == buffer + 1); assert_se(strstrafter(buffer, "") == buffer); @@ -1285,18 +1319,18 @@ TEST(version_is_valid) { TEST(strextendn) { _cleanup_free_ char *x = NULL; - assert_se(streq_ptr(strextendn(&x, NULL, 0), "")); + ASSERT_STREQ(strextendn(&x, NULL, 0), ""); x = mfree(x); - assert_se(streq_ptr(strextendn(&x, "", 0), "")); + ASSERT_STREQ(strextendn(&x, "", 0), ""); x = mfree(x); - assert_se(streq_ptr(strextendn(&x, "xxx", 3), "xxx")); - assert_se(streq_ptr(strextendn(&x, "xxx", 3), "xxxxxx")); - assert_se(streq_ptr(strextendn(&x, "...", 1), "xxxxxx.")); - assert_se(streq_ptr(strextendn(&x, "...", 2), "xxxxxx...")); - assert_se(streq_ptr(strextendn(&x, "...", 3), "xxxxxx......")); - assert_se(streq_ptr(strextendn(&x, "...", 4), "xxxxxx.........")); + ASSERT_STREQ(strextendn(&x, "xxx", 3), "xxx"); + ASSERT_STREQ(strextendn(&x, "xxx", 3), "xxxxxx"); + ASSERT_STREQ(strextendn(&x, "...", 1), "xxxxxx."); + ASSERT_STREQ(strextendn(&x, "...", 2), "xxxxxx..."); + ASSERT_STREQ(strextendn(&x, "...", 3), "xxxxxx......"); + ASSERT_STREQ(strextendn(&x, "...", 4), "xxxxxx........."); x = mfree(x); } @@ -1324,4 +1358,27 @@ TEST(strlevenshtein) { assert_se(strlevenshtein("sunday", "saturday") == 3); } +TEST(strrstr) { + assert_se(!strrstr(NULL, NULL)); + assert_se(!strrstr("foo", NULL)); + assert_se(!strrstr(NULL, "foo")); + + const char *p = "foo"; + assert_se(strrstr(p, "foo") == p); + assert_se(strrstr(p, "fo") == p); + assert_se(strrstr(p, "f") == p); + assert_se(strrstr(p, "oo") == p + 1); + assert_se(strrstr(p, "o") == p + 2); + assert_se(strrstr(p, "") == p + strlen(p)); + assert_se(!strrstr(p, "bar")); + + p = "xoxoxox"; + assert_se(strrstr(p, "") == p + strlen(p)); + assert_se(strrstr(p, "x") == p + 6); + assert_se(strrstr(p, "ox") == p + 5); + assert_se(strrstr(p, "xo") == p + 4); + assert_se(strrstr(p, "xox") == p + 4); + assert_se(!strrstr(p, "xx")); +} + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-strip-tab-ansi.c b/src/test/test-strip-tab-ansi.c index 6f73d26..3ad0fcd 100644 --- a/src/test/test-strip-tab-ansi.c +++ b/src/test/test-strip-tab-ansi.c @@ -15,38 +15,38 @@ TEST(strip_tab_ansi) { assert_se(p = strdup("\tFoobar\tbar\twaldo\t")); assert_se(strip_tab_ansi(&p, NULL, NULL)); fprintf(stdout, "<%s>\n", p); - assert_se(streq(p, " Foobar bar waldo ")); + ASSERT_STREQ(p, " Foobar bar waldo "); free(p); assert_se(p = strdup(ANSI_HIGHLIGHT "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); assert_se(strip_tab_ansi(&p, NULL, NULL)); fprintf(stdout, "<%s>\n", p); - assert_se(streq(p, "Hello world!")); + ASSERT_STREQ(p, "Hello world!"); free(p); assert_se(p = strdup("\x1B[\x1B[\t\x1B[" ANSI_HIGHLIGHT "\x1B[" "Hello" ANSI_NORMAL ANSI_HIGHLIGHT_RED " world!" ANSI_NORMAL)); assert_se(strip_tab_ansi(&p, NULL, NULL)); - assert_se(streq(p, "\x1B[\x1B[ \x1B[\x1B[Hello world!")); + ASSERT_STREQ(p, "\x1B[\x1B[ \x1B[\x1B[Hello world!"); free(p); assert_se(p = strdup("\x1B[waldo")); assert_se(strip_tab_ansi(&p, NULL, NULL)); - assert_se(streq(p, "\x1B[waldo")); + ASSERT_STREQ(p, "\x1B[waldo"); free(p); assert_se(p = strdup("\r\rwaldo")); assert_se(strip_tab_ansi(&p, NULL, NULL)); - assert_se(streq(p, "\r\rwaldo")); + ASSERT_STREQ(p, "\r\rwaldo"); free(p); assert_se(p = strdup("waldo\r\r")); assert_se(strip_tab_ansi(&p, NULL, NULL)); - assert_se(streq(p, "waldo")); + ASSERT_STREQ(p, "waldo"); free(p); assert_se(p = strdup("waldo\r\r\n\r\n")); assert_se(strip_tab_ansi(&p, NULL, NULL)); - assert_se(streq(p, "waldo\n\n")); + ASSERT_STREQ(p, "waldo\n\n"); free(p); assert_se(terminal_urlify_path("/etc/fstab", "i am a fabulous link", &urlified) >= 0); @@ -55,7 +55,7 @@ TEST(strip_tab_ansi) { printf("<%s>\n", p); assert_se(strip_tab_ansi(&p, NULL, NULL)); printf("<%s>\n", p); - assert_se(streq(p, "something i am a fabulous link something-else")); + ASSERT_STREQ(p, "something i am a fabulous link something-else"); p = mfree(p); /* Truncate the formatted string in the middle of an ANSI sequence (in which case we shouldn't touch the @@ -65,7 +65,7 @@ TEST(strip_tab_ansi) { *z = 0; assert_se(qq = strdup(q)); assert_se(strip_tab_ansi(&q, NULL, NULL)); - assert_se(streq(q, qq)); + ASSERT_STREQ(q, qq); } } diff --git a/src/test/test-strv.c b/src/test/test-strv.c index cfd662b..28b8b22 100644 --- a/src/test/test-strv.c +++ b/src/test/test-strv.c @@ -34,9 +34,9 @@ TEST(startswith_set) { assert_se(STARTSWITH_SET("abc", "ax", "abx", "abc")); assert_se(!STARTSWITH_SET("abc", "ax", "abx", "abcx")); - assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "foo", "zzz"), "bar")); - assert_se(streq_ptr(STARTSWITH_SET("foobar", "hhh", "kkk", "", "zzz"), "foobar")); - assert_se(streq_ptr(STARTSWITH_SET("", "hhh", "kkk", "zzz", ""), "")); + ASSERT_STREQ(STARTSWITH_SET("foobar", "hhh", "kkk", "foo", "zzz"), "bar"); + ASSERT_STREQ(STARTSWITH_SET("foobar", "hhh", "kkk", "", "zzz"), "foobar"); + ASSERT_STREQ(STARTSWITH_SET("", "hhh", "kkk", "zzz", ""), ""); } static const char* const input_table_multiple[] = { @@ -125,78 +125,78 @@ TEST(strv_find_startswith) { TEST(strv_join) { _cleanup_free_ char *p = strv_join((char **)input_table_multiple, ", "); assert_se(p); - assert_se(streq(p, "one, two, three")); + ASSERT_STREQ(p, "one, two, three"); _cleanup_free_ char *q = strv_join((char **)input_table_multiple, ";"); assert_se(q); - assert_se(streq(q, "one;two;three")); + ASSERT_STREQ(q, "one;two;three"); _cleanup_free_ char *r = strv_join((char **)input_table_multiple, NULL); assert_se(r); - assert_se(streq(r, "one two three")); + ASSERT_STREQ(r, "one two three"); _cleanup_free_ char *s = strv_join(STRV_MAKE("1", "2", "3,3"), ","); assert_se(s); - assert_se(streq(s, "1,2,3,3")); + ASSERT_STREQ(s, "1,2,3,3"); _cleanup_free_ char *t = strv_join((char **)input_table_one, ", "); assert_se(t); - assert_se(streq(t, "one")); + ASSERT_STREQ(t, "one"); _cleanup_free_ char *u = strv_join((char **)input_table_none, ", "); assert_se(u); - assert_se(streq(u, "")); + ASSERT_STREQ(u, ""); _cleanup_free_ char *v = strv_join((char **)input_table_two_empties, ", "); assert_se(v); - assert_se(streq(v, ", ")); + ASSERT_STREQ(v, ", "); _cleanup_free_ char *w = strv_join((char **)input_table_one_empty, ", "); assert_se(w); - assert_se(streq(w, "")); + ASSERT_STREQ(w, ""); } TEST(strv_join_full) { _cleanup_free_ char *p = strv_join_full((char **)input_table_multiple, ", ", "foo", false); assert_se(p); - assert_se(streq(p, "fooone, footwo, foothree")); + ASSERT_STREQ(p, "fooone, footwo, foothree"); _cleanup_free_ char *q = strv_join_full((char **)input_table_multiple, ";", "foo", false); assert_se(q); - assert_se(streq(q, "fooone;footwo;foothree")); + ASSERT_STREQ(q, "fooone;footwo;foothree"); _cleanup_free_ char *r = strv_join_full(STRV_MAKE("a", "a;b", "a:c"), ";", NULL, true); assert_se(r); - assert_se(streq(r, "a;a\\;b;a:c")); + ASSERT_STREQ(r, "a;a\\;b;a:c"); _cleanup_free_ char *s = strv_join_full(STRV_MAKE("a", "a;b", "a;;c", ";", ";x"), ";", NULL, true); assert_se(s); - assert_se(streq(s, "a;a\\;b;a\\;\\;c;\\;;\\;x")); + ASSERT_STREQ(s, "a;a\\;b;a\\;\\;c;\\;;\\;x"); _cleanup_free_ char *t = strv_join_full(STRV_MAKE("a", "a;b", "a:c", ";"), ";", "=", true); assert_se(t); - assert_se(streq(t, "=a;=a\\;b;=a:c;=\\;")); + ASSERT_STREQ(t, "=a;=a\\;b;=a:c;=\\;"); t = mfree(t); _cleanup_free_ char *u = strv_join_full((char **)input_table_multiple, NULL, "foo", false); assert_se(u); - assert_se(streq(u, "fooone footwo foothree")); + ASSERT_STREQ(u, "fooone footwo foothree"); _cleanup_free_ char *v = strv_join_full((char **)input_table_one, ", ", "foo", false); assert_se(v); - assert_se(streq(v, "fooone")); + ASSERT_STREQ(v, "fooone"); _cleanup_free_ char *w = strv_join_full((char **)input_table_none, ", ", "foo", false); assert_se(w); - assert_se(streq(w, "")); + ASSERT_STREQ(w, ""); _cleanup_free_ char *x = strv_join_full((char **)input_table_two_empties, ", ", "foo", false); assert_se(x); - assert_se(streq(x, "foo, foo")); + ASSERT_STREQ(x, "foo, foo"); _cleanup_free_ char *y = strv_join_full((char **)input_table_one_empty, ", ", "foo", false); assert_se(y); - assert_se(streq(y, "foo")); + ASSERT_STREQ(y, "foo"); } static void test_strv_unquote_one(const char *quoted, char **list) { @@ -215,9 +215,9 @@ static void test_strv_unquote_one(const char *quoted, char **list) { puts(j); STRV_FOREACH(t, s) - assert_se(streq(list[i++], *t)); + ASSERT_STREQ(list[i++], *t); - assert_se(list[i] == NULL); + ASSERT_NULL(list[i]); } TEST(strv_unquote) { @@ -245,7 +245,7 @@ static void test_invalid_unquote_one(const char *quoted) { log_info("/* %s */", __func__); r = strv_split_full(&s, quoted, WHITESPACE, EXTRACT_UNQUOTE); - assert_se(s == NULL); + ASSERT_NULL(s); assert_se(r == -EINVAL); } @@ -402,12 +402,12 @@ TEST(strv_split_full) { r = strv_split_full(&l, str, ":", EXTRACT_DONT_COALESCE_SEPARATORS); assert_se(r == (int) strv_length(l)); - assert_se(streq_ptr(l[0], "")); - assert_se(streq_ptr(l[1], "foo:bar")); - assert_se(streq_ptr(l[2], "")); - assert_se(streq_ptr(l[3], "waldo")); - assert_se(streq_ptr(l[4], "")); - assert_se(streq_ptr(l[5], NULL)); + ASSERT_STREQ(l[0], ""); + ASSERT_STREQ(l[1], "foo:bar"); + ASSERT_STREQ(l[2], ""); + ASSERT_STREQ(l[3], "waldo"); + ASSERT_STREQ(l[4], ""); + ASSERT_STREQ(l[5], NULL); } TEST(strv_split_and_extend_full) { @@ -420,14 +420,14 @@ TEST(strv_split_and_extend_full) { assert_se(r == (int) strv_length(l)); r = strv_split_and_extend_full(&l, str1, ":", false, EXTRACT_DONT_COALESCE_SEPARATORS); assert_se(r == (int) strv_length(l)); - assert_se(streq_ptr(l[0], "")); - assert_se(streq_ptr(l[1], "foo:bar")); - assert_se(streq_ptr(l[2], "")); + ASSERT_STREQ(l[0], ""); + ASSERT_STREQ(l[1], "foo:bar"); + ASSERT_STREQ(l[2], ""); r = strv_split_and_extend_full(&l, str2, ":", false, 0); assert_se(r == (int) strv_length(l)); - assert_se(streq_ptr(l[3], "waldo")); - assert_se(streq_ptr(l[4], "baz")); - assert_se(streq_ptr(l[5], NULL)); + ASSERT_STREQ(l[3], "waldo"); + ASSERT_STREQ(l[4], "baz"); + ASSERT_STREQ(l[5], NULL); } TEST(strv_split_colon_pairs) { @@ -439,19 +439,19 @@ TEST(strv_split_colon_pairs) { r = strv_split_colon_pairs(&l, str); assert_se(r == (int) strv_length(l)); assert_se(r == 12); - assert_se(streq_ptr(l[0], "one")); - assert_se(streq_ptr(l[1], "two")); - assert_se(streq_ptr(l[2], "three")); - assert_se(streq_ptr(l[3], "")); - assert_se(streq_ptr(l[4], "four")); - assert_se(streq_ptr(l[5], "five")); - assert_se(streq_ptr(l[6], "six")); - assert_se(streq_ptr(l[7], "")); - assert_se(streq_ptr(l[8], "seven")); - assert_se(streq_ptr(l[9], "eight:nine")); - assert_se(streq_ptr(l[10], "ten:eleven\\")); - assert_se(streq_ptr(l[11], "")); - assert_se(streq_ptr(l[12], NULL)); + ASSERT_STREQ(l[0], "one"); + ASSERT_STREQ(l[1], "two"); + ASSERT_STREQ(l[2], "three"); + ASSERT_STREQ(l[3], ""); + ASSERT_STREQ(l[4], "four"); + ASSERT_STREQ(l[5], "five"); + ASSERT_STREQ(l[6], "six"); + ASSERT_STREQ(l[7], ""); + ASSERT_STREQ(l[8], "seven"); + ASSERT_STREQ(l[9], "eight:nine"); + ASSERT_STREQ(l[10], "ten:eleven\\"); + ASSERT_STREQ(l[11], ""); + ASSERT_STREQ(l[12], NULL); r = strv_split_colon_pairs(&l, str_inval); assert_se(r == -EINVAL); @@ -466,7 +466,7 @@ TEST(strv_split_newlines) { assert_se(l); STRV_FOREACH(s, l) - assert_se(streq(*s, input_table_multiple[i++])); + ASSERT_STREQ(*s, input_table_multiple[i++]); } TEST(strv_split_newlines_full) { @@ -520,11 +520,27 @@ TEST(strv_sort) { strv_sort((char **)input_table); - assert_se(streq(input_table[0], "CAPITAL LETTERS FIRST")); - assert_se(streq(input_table[1], "apple")); - assert_se(streq(input_table[2], "banana")); - assert_se(streq(input_table[3], "citrus")); - assert_se(streq(input_table[4], "durian")); + ASSERT_STREQ(input_table[0], "CAPITAL LETTERS FIRST"); + ASSERT_STREQ(input_table[1], "apple"); + ASSERT_STREQ(input_table[2], "banana"); + ASSERT_STREQ(input_table[3], "citrus"); + ASSERT_STREQ(input_table[4], "durian"); +} + +TEST(strv_extend_strv_biconcat) { + _cleanup_strv_free_ char **a = NULL, **b = NULL; + + a = strv_new("without", "suffix"); + b = strv_new("with", "suffix"); + assert_se(a); + assert_se(b); + + assert_se(strv_extend_strv_biconcat(&a, "prefix_", (const char* const*) b, "_suffix") >= 0); + + ASSERT_STREQ(a[0], "without"); + ASSERT_STREQ(a[1], "suffix"); + ASSERT_STREQ(a[2], "prefix_with_suffix"); + ASSERT_STREQ(a[3], "prefix_suffix_suffix"); } TEST(strv_extend_strv_concat) { @@ -535,12 +551,12 @@ TEST(strv_extend_strv_concat) { assert_se(a); assert_se(b); - assert_se(strv_extend_strv_concat(&a, b, "_suffix") >= 0); + assert_se(strv_extend_strv_concat(&a, (const char* const*) b, "_suffix") >= 0); - assert_se(streq(a[0], "without")); - assert_se(streq(a[1], "suffix")); - assert_se(streq(a[2], "with_suffix")); - assert_se(streq(a[3], "suffix_suffix")); + ASSERT_STREQ(a[0], "without"); + ASSERT_STREQ(a[1], "suffix"); + ASSERT_STREQ(a[2], "with_suffix"); + ASSERT_STREQ(a[3], "suffix_suffix"); } TEST(strv_extend_strv) { @@ -553,19 +569,19 @@ TEST(strv_extend_strv) { assert_se(strv_extend_strv(&a, b, true) == 3); - assert_se(streq(a[0], "abc")); - assert_se(streq(a[1], "def")); - assert_se(streq(a[2], "ghi")); - assert_se(streq(a[3], "jkl")); - assert_se(streq(a[4], "mno")); - assert_se(streq(a[5], "pqr")); + ASSERT_STREQ(a[0], "abc"); + ASSERT_STREQ(a[1], "def"); + ASSERT_STREQ(a[2], "ghi"); + ASSERT_STREQ(a[3], "jkl"); + ASSERT_STREQ(a[4], "mno"); + ASSERT_STREQ(a[5], "pqr"); assert_se(strv_length(a) == 6); assert_se(strv_extend_strv(&n, b, false) >= 0); - assert_se(streq(n[0], "jkl")); - assert_se(streq(n[1], "mno")); - assert_se(streq(n[2], "abc")); - assert_se(streq(n[3], "pqr")); + ASSERT_STREQ(n[0], "jkl"); + ASSERT_STREQ(n[1], "mno"); + ASSERT_STREQ(n[2], "abc"); + ASSERT_STREQ(n[3], "pqr"); assert_se(strv_length(n) == 4); } @@ -581,11 +597,11 @@ TEST(strv_extend_with_size) { assert_se(strv_extend_with_size(&a, &n, "test3") >= 0); assert_se(n == 4); - assert_se(streq(a[0], "test")); - assert_se(streq(a[1], "test1")); - assert_se(streq(a[2], "test2")); - assert_se(streq(a[3], "test3")); - assert_se(a[4] == NULL); + ASSERT_STREQ(a[0], "test"); + ASSERT_STREQ(a[1], "test1"); + ASSERT_STREQ(a[2], "test2"); + ASSERT_STREQ(a[3], "test3"); + ASSERT_NULL(a[4]); } TEST(strv_extend) { @@ -596,10 +612,10 @@ TEST(strv_extend) { assert_se(strv_extend(&a, "test2") >= 0); assert_se(strv_extend(&b, "test3") >= 0); - assert_se(streq(a[0], "test")); - assert_se(streq(a[1], "test1")); - assert_se(streq(a[2], "test2")); - assert_se(streq(b[0], "test3")); + ASSERT_STREQ(a[0], "test"); + ASSERT_STREQ(a[1], "test1"); + ASSERT_STREQ(a[2], "test2"); + ASSERT_STREQ(b[0], "test3"); } TEST(strv_extendf) { @@ -610,10 +626,10 @@ TEST(strv_extendf) { assert_se(strv_extendf(&a, "test2 %s %d %s", "foo", 128, "bar") >= 0); assert_se(strv_extendf(&b, "test3 %s %s %d", "bar", "foo", 128) >= 0); - assert_se(streq(a[0], "test")); - assert_se(streq(a[1], "test1")); - assert_se(streq(a[2], "test2 foo 128 bar")); - assert_se(streq(b[0], "test3 bar foo 128")); + ASSERT_STREQ(a[0], "test"); + ASSERT_STREQ(a[1], "test1"); + ASSERT_STREQ(a[2], "test2 foo 128 bar"); + ASSERT_STREQ(b[0], "test3 bar foo 128"); } TEST(strv_foreach) { @@ -624,7 +640,7 @@ TEST(strv_foreach) { assert_se(a); STRV_FOREACH(check, a) - assert_se(streq(*check, input_table_multiple[i++])); + ASSERT_STREQ(*check, input_table_multiple[i++]); } TEST(strv_foreach_backwards) { @@ -636,7 +652,7 @@ TEST(strv_foreach_backwards) { assert_se(a); STRV_FOREACH_BACKWARDS(check, a) - assert_se(streq_ptr(*check, input_table_multiple[i--])); + ASSERT_STREQ(*check, input_table_multiple[i--]); STRV_FOREACH_BACKWARDS(check, (char**) NULL) assert_not_reached(); @@ -657,7 +673,7 @@ TEST(strv_foreach_pair) { "pair_two", "pair_two", "pair_three", "pair_three"); STRV_FOREACH_PAIR(x, y, a) - assert_se(streq(*x, *y)); + ASSERT_STREQ(*x, *y); } static void test_strv_from_stdarg_alloca_one(char **l, const char *first, ...) { @@ -669,7 +685,7 @@ static void test_strv_from_stdarg_alloca_one(char **l, const char *first, ...) { j = strv_from_stdarg_alloca(first); for (i = 0;; i++) { - assert_se(streq_ptr(l[i], j[i])); + ASSERT_STREQ(l[i], j[i]); if (!l[i]) break; @@ -686,29 +702,29 @@ TEST(strv_insert) { _cleanup_strv_free_ char **a = NULL; assert_se(strv_insert(&a, 0, strdup("first")) == 0); - assert_se(streq(a[0], "first")); + ASSERT_STREQ(a[0], "first"); assert_se(!a[1]); assert_se(strv_insert(&a, 0, NULL) == 0); - assert_se(streq(a[0], "first")); + ASSERT_STREQ(a[0], "first"); assert_se(!a[1]); assert_se(strv_insert(&a, 1, strdup("two")) == 0); - assert_se(streq(a[0], "first")); - assert_se(streq(a[1], "two")); + ASSERT_STREQ(a[0], "first"); + ASSERT_STREQ(a[1], "two"); assert_se(!a[2]); assert_se(strv_insert(&a, 4, strdup("tri")) == 0); - assert_se(streq(a[0], "first")); - assert_se(streq(a[1], "two")); - assert_se(streq(a[2], "tri")); + ASSERT_STREQ(a[0], "first"); + ASSERT_STREQ(a[1], "two"); + ASSERT_STREQ(a[2], "tri"); assert_se(!a[3]); assert_se(strv_insert(&a, 1, strdup("duo")) == 0); - assert_se(streq(a[0], "first")); - assert_se(streq(a[1], "duo")); - assert_se(streq(a[2], "two")); - assert_se(streq(a[3], "tri")); + ASSERT_STREQ(a[0], "first"); + ASSERT_STREQ(a[1], "duo"); + ASSERT_STREQ(a[2], "two"); + ASSERT_STREQ(a[3], "tri"); assert_se(!a[4]); } @@ -718,18 +734,18 @@ TEST(strv_push_prepend) { assert_se(a = strv_new("foo", "bar", "three")); assert_se(strv_push_prepend(&a, strdup("first")) >= 0); - assert_se(streq(a[0], "first")); - assert_se(streq(a[1], "foo")); - assert_se(streq(a[2], "bar")); - assert_se(streq(a[3], "three")); + ASSERT_STREQ(a[0], "first"); + ASSERT_STREQ(a[1], "foo"); + ASSERT_STREQ(a[2], "bar"); + ASSERT_STREQ(a[3], "three"); assert_se(!a[4]); assert_se(strv_consume_prepend(&a, strdup("first2")) >= 0); - assert_se(streq(a[0], "first2")); - assert_se(streq(a[1], "first")); - assert_se(streq(a[2], "foo")); - assert_se(streq(a[3], "bar")); - assert_se(streq(a[4], "three")); + ASSERT_STREQ(a[0], "first2"); + ASSERT_STREQ(a[1], "first"); + ASSERT_STREQ(a[2], "foo"); + ASSERT_STREQ(a[3], "bar"); + ASSERT_STREQ(a[4], "three"); assert_se(!a[5]); } @@ -749,10 +765,10 @@ TEST(strv_push_with_size) { assert_se(strv_push_with_size(&a, &n, j) >= 0); assert_se(n == 3); - assert_se(streq_ptr(a[0], "foo")); - assert_se(streq_ptr(a[1], "a")); - assert_se(streq_ptr(a[2], "b")); - assert_se(streq_ptr(a[3], NULL)); + ASSERT_STREQ(a[0], "foo"); + ASSERT_STREQ(a[1], "a"); + ASSERT_STREQ(a[2], "b"); + ASSERT_STREQ(a[3], NULL); assert_se(n = strv_length(a)); } @@ -768,10 +784,10 @@ TEST(strv_push) { assert_se(j = strdup("b")); assert_se(strv_push_pair(&a, i, j) >= 0); - assert_se(streq_ptr(a[0], "foo")); - assert_se(streq_ptr(a[1], "a")); - assert_se(streq_ptr(a[2], "b")); - assert_se(streq_ptr(a[3], NULL)); + ASSERT_STREQ(a[0], "foo"); + ASSERT_STREQ(a[1], "a"); + ASSERT_STREQ(a[2], "b"); + ASSERT_STREQ(a[3], NULL); } TEST(strv_compare) { @@ -833,23 +849,23 @@ TEST(strv_reverse) { b = strv_new("foo"); assert_se(b); strv_reverse(b); - assert_se(streq_ptr(b[0], "foo")); - assert_se(streq_ptr(b[1], NULL)); + ASSERT_STREQ(b[0], "foo"); + ASSERT_STREQ(b[1], NULL); c = strv_new("foo", "bar"); assert_se(c); strv_reverse(c); - assert_se(streq_ptr(c[0], "bar")); - assert_se(streq_ptr(c[1], "foo")); - assert_se(streq_ptr(c[2], NULL)); + ASSERT_STREQ(c[0], "bar"); + ASSERT_STREQ(c[1], "foo"); + ASSERT_STREQ(c[2], NULL); d = strv_new("foo", "bar", "waldo"); assert_se(d); strv_reverse(d); - assert_se(streq_ptr(d[0], "waldo")); - assert_se(streq_ptr(d[1], "bar")); - assert_se(streq_ptr(d[2], "foo")); - assert_se(streq_ptr(d[3], NULL)); + ASSERT_STREQ(d[0], "waldo"); + ASSERT_STREQ(d[1], "bar"); + ASSERT_STREQ(d[2], "foo"); + ASSERT_STREQ(d[3], NULL); } TEST(strv_shell_escape) { @@ -858,10 +874,10 @@ TEST(strv_shell_escape) { v = strv_new("foo:bar", "bar,baz", "wal\\do"); assert_se(v); assert_se(strv_shell_escape(v, ",:")); - assert_se(streq_ptr(v[0], "foo\\:bar")); - assert_se(streq_ptr(v[1], "bar\\,baz")); - assert_se(streq_ptr(v[2], "wal\\\\do")); - assert_se(streq_ptr(v[3], NULL)); + ASSERT_STREQ(v[0], "foo\\:bar"); + ASSERT_STREQ(v[1], "bar\\,baz"); + ASSERT_STREQ(v[2], "wal\\\\do"); + ASSERT_STREQ(v[3], NULL); } static void test_strv_skip_one(char **a, size_t n, char **b) { @@ -895,22 +911,22 @@ TEST(strv_extend_n) { assert_se(strv_extend_n(&v, "waldo", 3) >= 0); assert_se(strv_extend_n(&v, "piep", 2) >= 0); - assert_se(streq(v[0], "foo")); - assert_se(streq(v[1], "bar")); - assert_se(streq(v[2], "waldo")); - assert_se(streq(v[3], "waldo")); - assert_se(streq(v[4], "waldo")); - assert_se(streq(v[5], "piep")); - assert_se(streq(v[6], "piep")); - assert_se(v[7] == NULL); + ASSERT_STREQ(v[0], "foo"); + ASSERT_STREQ(v[1], "bar"); + ASSERT_STREQ(v[2], "waldo"); + ASSERT_STREQ(v[3], "waldo"); + ASSERT_STREQ(v[4], "waldo"); + ASSERT_STREQ(v[5], "piep"); + ASSERT_STREQ(v[6], "piep"); + ASSERT_NULL(v[7]); v = strv_free(v); assert_se(strv_extend_n(&v, "foo", 1) >= 0); assert_se(strv_extend_n(&v, "bar", 0) >= 0); - assert_se(streq(v[0], "foo")); - assert_se(v[1] == NULL); + ASSERT_STREQ(v[0], "foo"); + ASSERT_NULL(v[1]); } TEST(foreach_string) { @@ -923,11 +939,11 @@ TEST(foreach_string) { unsigned i = 0; FOREACH_STRING(x, "foo", "bar", "waldo") - assert_se(streq_ptr(t[i++], x)); + ASSERT_STREQ(t[i++], x); assert_se(i == 3); FOREACH_STRING(x, "zzz") - assert_se(streq(x, "zzz")); + ASSERT_STREQ(x, "zzz"); } TEST(strv_fnmatch) { @@ -950,8 +966,8 @@ TEST(strv_extend_join) { assert_se(strv_extend_assignment(&v, "MISSING", NULL) >= 0); assert_se(strv_length(v) == 2); - assert_se(streq(v[0], "MESSAGE=ABC")); - assert_se(streq(v[1], "ABC=QER")); + ASSERT_STREQ(v[0], "MESSAGE=ABC"); + ASSERT_STREQ(v[1], "ABC=QER"); } TEST(strv_copy_n) { @@ -997,13 +1013,46 @@ TEST(strv_copy_n) { 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")); + ASSERT_NULL(strv_find_first_field(NULL, NULL)); + ASSERT_NULL(strv_find_first_field(NULL, haystack)); + ASSERT_NULL(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), NULL)); + ASSERT_NULL(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "b"), haystack)); + ASSERT_STREQ(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "a", "c"), haystack), "b"); + ASSERT_STREQ(strv_find_first_field(STRV_MAKE("k", "l", "m", "d", "c", "a"), haystack), "d"); + ASSERT_STREQ(strv_find_first_field(STRV_MAKE("i", "k", "l", "m", "d", "c", "a", "b"), haystack), "j"); +} + +TEST(endswith_strv) { + ASSERT_STREQ(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "ldo", "zzz")), "ldo"); + ASSERT_STREQ(endswith_strv("waldo", STRV_MAKE("xxx", "yyy", "zzz")), NULL); + ASSERT_STREQ(endswith_strv("waldo", STRV_MAKE("waldo")), "waldo"); + ASSERT_STREQ(endswith_strv("waldo", STRV_MAKE("w", "o", "ldo")), "o"); + ASSERT_STREQ(endswith_strv("waldo", STRV_MAKE("knurz", "", "waldo")), ""); +} + +TEST(strv_extend_many) { + _cleanup_strv_free_ char **l = NULL; + + assert_se(strv_extend_many(&l, NULL) >= 0); + assert_se(strv_isempty(l)); + + assert_se(strv_extend_many(&l, NULL, NULL, NULL) >= 0); + assert_se(strv_isempty(l)); + + assert_se(strv_extend_many(&l, "foo") >= 0); + assert_se(strv_equal(l, STRV_MAKE("foo"))); + + assert_se(strv_extend_many(&l, NULL, "bar", NULL) >= 0); + assert_se(strv_equal(l, STRV_MAKE("foo", "bar"))); + + assert_se(strv_extend_many(&l, "waldo", "quux") >= 0); + assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux"))); + + assert_se(strv_extend_many(&l, "1", "2", "3", "4") >= 0); + assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4"))); + + assert_se(strv_extend_many(&l, "yes", NULL, "no") >= 0); + assert_se(strv_equal(l, STRV_MAKE("foo", "bar", "waldo", "quux", "1", "2", "3", "4", "yes", "no"))); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-strxcpyx.c b/src/test/test-strxcpyx.c index b679522..00f595c 100644 --- a/src/test/test-strxcpyx.c +++ b/src/test/test-strxcpyx.c @@ -24,27 +24,27 @@ TEST(strpcpy) { space_left = strpcpy_full(&s, space_left, "r", &truncated); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "12345hey hey heywaldobar")); + ASSERT_STREQ(target, "12345hey hey heywaldobar"); space_left = strpcpy_full(&s, space_left, "", &truncated); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "12345hey hey heywaldobar")); + ASSERT_STREQ(target, "12345hey hey heywaldobar"); space_left = strpcpy_full(&s, space_left, "f", &truncated); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "12345hey hey heywaldobar")); + ASSERT_STREQ(target, "12345hey hey heywaldobar"); space_left = strpcpy_full(&s, space_left, "", &truncated); assert_se(!truncated); assert_se(space_left == 0); - assert_se(streq(target, "12345hey hey heywaldobar")); + ASSERT_STREQ(target, "12345hey hey heywaldobar"); space_left = strpcpy_full(&s, space_left, "foo", &truncated); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "12345hey hey heywaldobar")); + ASSERT_STREQ(target, "12345hey hey heywaldobar"); } TEST(strpcpyf) { @@ -59,38 +59,38 @@ TEST(strpcpyf) { space_left = strpcpyf_full(&s, space_left, &truncated, "foo%s", "bar"); assert_se(!truncated); assert_se(space_left == 3); - assert_se(streq(target, "space left: 25. foobar")); + ASSERT_STREQ(target, "space left: 25. foobar"); space_left = strpcpyf_full(&s, space_left, &truncated, "%i", 42); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "space left: 25. foobar42")); + ASSERT_STREQ(target, "space left: 25. foobar42"); space_left = strpcpyf_full(&s, space_left, &truncated, "%s", ""); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "space left: 25. foobar42")); + ASSERT_STREQ(target, "space left: 25. foobar42"); space_left = strpcpyf_full(&s, space_left, &truncated, "%c", 'x'); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "space left: 25. foobar42")); + ASSERT_STREQ(target, "space left: 25. foobar42"); space_left = strpcpyf_full(&s, space_left, &truncated, "%s", ""); assert_se(!truncated); assert_se(space_left == 0); - assert_se(streq(target, "space left: 25. foobar42")); + ASSERT_STREQ(target, "space left: 25. foobar42"); space_left = strpcpyf_full(&s, space_left, &truncated, "abc%s", "hoge"); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "space left: 25. foobar42")); + ASSERT_STREQ(target, "space left: 25. foobar42"); /* test overflow */ s = target; space_left = strpcpyf_full(&s, 12, &truncated, "00 left: %i. ", 999); assert_se(truncated); - assert_se(streq(target, "00 left: 99")); + ASSERT_STREQ(target, "00 left: 99"); assert_se(space_left == 0); assert_se(target[12] == '2'); } @@ -107,22 +107,22 @@ TEST(strpcpyl) { space_left = strpcpyl_full(&s, space_left, &truncated, "Banana", NULL); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "waldo test waldo. Banana")); + ASSERT_STREQ(target, "waldo test waldo. Banana"); space_left = strpcpyl_full(&s, space_left, &truncated, "", "", "", NULL); assert_se(!truncated); assert_se(space_left == 1); - assert_se(streq(target, "waldo test waldo. Banana")); + ASSERT_STREQ(target, "waldo test waldo. Banana"); space_left = strpcpyl_full(&s, space_left, &truncated, "", "x", "", NULL); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "waldo test waldo. Banana")); + ASSERT_STREQ(target, "waldo test waldo. Banana"); space_left = strpcpyl_full(&s, space_left, &truncated, "hoge", NULL); assert_se(truncated); assert_se(space_left == 0); - assert_se(streq(target, "waldo test waldo. Banana")); + ASSERT_STREQ(target, "waldo test waldo. Banana"); } TEST(strscpy) { @@ -134,7 +134,7 @@ TEST(strscpy) { space_left = strscpy_full(target, space_left, "12345", &truncated); assert_se(!truncated); - assert_se(streq(target, "12345")); + ASSERT_STREQ(target, "12345"); assert_se(space_left == 20); } @@ -147,7 +147,7 @@ TEST(strscpyl) { space_left = strscpyl_full(target, space_left, &truncated, "12345", "waldo", "waldo", NULL); assert_se(!truncated); - assert_se(streq(target, "12345waldowaldo")); + ASSERT_STREQ(target, "12345waldowaldo"); assert_se(space_left == 10); } @@ -169,7 +169,7 @@ TEST(sd_event_code_migration) { for (i = 0; i < 100; i++) l = strpcpyf(&p, l, "%u ", i); - assert_se(streq(b, c)); + ASSERT_STREQ(b, c); } DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-sysctl-util.c b/src/test/test-sysctl-util.c index 81207f5..e940996 100644 --- a/src/test/test-sysctl-util.c +++ b/src/test/test-sysctl-util.c @@ -34,7 +34,7 @@ TEST(sysctl_normalize) { assert_se(sysctl_normalize(t) == t); log_info("\"%s\" → \"%s\", expected \"%s\"", *s, t, *expected); - assert_se(streq(t, *expected)); + ASSERT_STREQ(t, *expected); } } @@ -66,7 +66,7 @@ TEST(sysctl_read) { assert_se(sysctl_read("kernel/hostname", &s) >= 0); assert_se(uname(&u) >= 0); - assert_se(streq_ptr(s, u.nodename)); + ASSERT_STREQ(s, u.nodename); r = sysctl_write("kernel/hostname", s); assert_se(r >= 0 || ERRNO_IS_PRIVILEGE(r) || r == -EROFS); diff --git a/src/test/test-taint.c b/src/test/test-taint.c new file mode 100644 index 0000000..d9aa79d --- /dev/null +++ b/src/test/test-taint.c @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "taint.h" +#include "tests.h" + +TEST(taint_string) { + _cleanup_free_ char *a = taint_string(); + assert_se(a); + log_debug("taint string: '%s'", a); + + assert_se(!!strstr(a, "cgroupsv1") == (cg_all_unified() == 0)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-terminal-util.c b/src/test/test-terminal-util.c index a2b7101..dbd7654 100644 --- a/src/test/test-terminal-util.c +++ b/src/test/test-terminal-util.c @@ -155,7 +155,7 @@ TEST(get_ctty) { } /* In almost all cases STDIN will match our controlling TTY. Let's verify that and then compare paths */ - assert_se(fstat(STDIN_FILENO, &st) >= 0); + ASSERT_OK_ERRNO(fstat(STDIN_FILENO, &st)); if (S_ISCHR(st.st_mode) && st.st_rdev == devnr) { _cleanup_free_ char *stdin_name = NULL; @@ -165,4 +165,15 @@ TEST(get_ctty) { log_notice("Not invoked with stdin == ctty, cutting get_ctty() test short"); } +TEST(get_default_background_color) { + double red, green, blue; + int r; + + r = get_default_background_color(&red, &green, &blue); + if (r < 0) + log_notice_errno(r, "Can't get terminal default background color: %m"); + else + log_notice("R=%g G=%g B=%g", red, green, blue); +} + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/test/test-time-util.c b/src/test/test-time-util.c index 53bc779..9943923 100644 --- a/src/test/test-time-util.c +++ b/src/test/test-time-util.c @@ -413,7 +413,7 @@ static void test_format_timestamp_impl(usec_t x) { override ? ", ignoring." : ""); if (!override) { assert_se(x / USEC_PER_SEC == y / USEC_PER_SEC); - assert_se(streq(xx, yy)); + ASSERT_STREQ(xx, yy); } } @@ -482,69 +482,69 @@ TEST(format_timestamp_relative_full) { x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 1*USEC_PER_MONTH); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 year 1 month ago")); + ASSERT_STREQ(buf, "1 year 1 month ago"); x = now(CLOCK_MONOTONIC) + (1*USEC_PER_YEAR + 1.5*USEC_PER_MONTH); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_MONOTONIC, false)); log_debug("%s", buf); - assert_se(streq(buf, "1 year 1 month left")); + ASSERT_STREQ(buf, "1 year 1 month left"); x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 2*USEC_PER_MONTH); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 year 2 months ago")); + ASSERT_STREQ(buf, "1 year 2 months ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 1*USEC_PER_MONTH); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 years 1 month ago")); + ASSERT_STREQ(buf, "2 years 1 month ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 2*USEC_PER_MONTH); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 years 2 months ago")); + ASSERT_STREQ(buf, "2 years 2 months ago"); /* Months and days */ x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 1*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 month 1 day ago")); + ASSERT_STREQ(buf, "1 month 1 day ago"); x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 2*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 month 2 days ago")); + ASSERT_STREQ(buf, "1 month 2 days ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 1*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 months 1 day ago")); + ASSERT_STREQ(buf, "2 months 1 day ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 2*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 months 2 days ago")); + ASSERT_STREQ(buf, "2 months 2 days ago"); /* Weeks and days */ x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 1*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 week 1 day ago")); + ASSERT_STREQ(buf, "1 week 1 day ago"); x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 2*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "1 week 2 days ago")); + ASSERT_STREQ(buf, "1 week 2 days ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 1*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 weeks 1 day ago")); + ASSERT_STREQ(buf, "2 weeks 1 day ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 2*USEC_PER_DAY); assert_se(format_timestamp_relative_full(buf, sizeof(buf), x, CLOCK_REALTIME, true)); log_debug("%s", buf); - assert_se(streq(buf, "2 weeks 2 days ago")); + ASSERT_STREQ(buf, "2 weeks 2 days ago"); } TEST(format_timestamp_relative) { @@ -559,64 +559,64 @@ TEST(format_timestamp_relative) { x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 1*USEC_PER_MONTH); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 year 1 month ago")); + ASSERT_STREQ(buf, "1 year 1 month ago"); x = now(CLOCK_REALTIME) - (1*USEC_PER_YEAR + 2*USEC_PER_MONTH); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 year 2 months ago")); + ASSERT_STREQ(buf, "1 year 2 months ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 1*USEC_PER_MONTH); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 years 1 month ago")); + ASSERT_STREQ(buf, "2 years 1 month ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_YEAR + 2*USEC_PER_MONTH); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 years 2 months ago")); + ASSERT_STREQ(buf, "2 years 2 months ago"); /* Months and days */ x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 1*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 month 1 day ago")); + ASSERT_STREQ(buf, "1 month 1 day ago"); x = now(CLOCK_REALTIME) - (1*USEC_PER_MONTH + 2*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 month 2 days ago")); + ASSERT_STREQ(buf, "1 month 2 days ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 1*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 months 1 day ago")); + ASSERT_STREQ(buf, "2 months 1 day ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_MONTH + 2*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 months 2 days ago")); + ASSERT_STREQ(buf, "2 months 2 days ago"); /* Weeks and days */ x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 1*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 week 1 day ago")); + ASSERT_STREQ(buf, "1 week 1 day ago"); x = now(CLOCK_REALTIME) - (1*USEC_PER_WEEK + 2*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "1 week 2 days ago")); + ASSERT_STREQ(buf, "1 week 2 days ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 1*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 weeks 1 day ago")); + ASSERT_STREQ(buf, "2 weeks 1 day ago"); x = now(CLOCK_REALTIME) - (2*USEC_PER_WEEK + 2*USEC_PER_DAY); assert_se(format_timestamp_relative(buf, sizeof(buf), x)); log_debug("%s", buf); - assert_se(streq(buf, "2 weeks 2 days ago")); + ASSERT_STREQ(buf, "2 weeks 2 days ago"); } static void test_format_timestamp_one(usec_t val, TimestampStyle style, const char *result) { @@ -624,7 +624,7 @@ static void test_format_timestamp_one(usec_t val, TimestampStyle style, const ch const char *t; t = format_timestamp_style(buf, sizeof(buf), val, style); - assert_se(streq_ptr(t, result)); + ASSERT_STREQ(t, result); } TEST(format_timestamp_range) { @@ -1062,15 +1062,15 @@ TEST(in_utc_timezone) { assert_se(setenv("TZ", ":UTC", 1) >= 0); assert_se(in_utc_timezone()); - assert_se(streq(tzname[0], "UTC")); - assert_se(streq(tzname[1], "UTC")); + ASSERT_STREQ(tzname[0], "UTC"); + ASSERT_STREQ(tzname[1], "UTC"); assert_se(timezone == 0); assert_se(daylight == 0); assert_se(setenv("TZ", ":Europe/Berlin", 1) >= 0); assert_se(!in_utc_timezone()); - assert_se(streq(tzname[0], "CET")); - assert_se(streq(tzname[1], "CEST")); + ASSERT_STREQ(tzname[0], "CET"); + ASSERT_STREQ(tzname[1], "CEST"); assert_se(set_unset_env("TZ", tz, true) == 0); tzset(); @@ -1089,9 +1089,9 @@ TEST(map_clock_usec) { assert_se(nowr < USEC_INFINITY - USEC_PER_DAY*7); /* overflow check */ x = nowr + USEC_PER_DAY*7; /* 1 week from now */ y = map_clock_usec(x, CLOCK_REALTIME, CLOCK_MONOTONIC); - assert_se(y > 0 && y < USEC_INFINITY); + assert_se(timestamp_is_set(y)); z = map_clock_usec(y, CLOCK_MONOTONIC, CLOCK_REALTIME); - assert_se(z > 0 && z < USEC_INFINITY); + assert_se(timestamp_is_set(z)); assert_se((z > x ? z - x : x - z) < USEC_PER_HOUR); assert_se(nowr > USEC_PER_DAY * 7); /* underflow check */ @@ -1100,7 +1100,7 @@ TEST(map_clock_usec) { if (y != 0) { /* might underflow if machine is not up long enough for the monotonic clock to be beyond 1w */ assert_se(y < USEC_INFINITY); z = map_clock_usec(y, CLOCK_MONOTONIC, CLOCK_REALTIME); - assert_se(z > 0 && z < USEC_INFINITY); + assert_se(timestamp_is_set(z)); assert_se((z > x ? z - x : x - z) < USEC_PER_HOUR); } } @@ -1114,14 +1114,14 @@ static void test_timezone_offset_change_one(const char *utc, const char *pretty) s = FORMAT_TIMESTAMP_STYLE(x, TIMESTAMP_UTC); assert_se(parse_timestamp(s, &y) >= 0); log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, utc, x, s, y); - assert_se(streq(s, utc)); + ASSERT_STREQ(s, utc); assert_se(x == y); assert_se(parse_timestamp(pretty, &y) >= 0); s = FORMAT_TIMESTAMP_STYLE(y, TIMESTAMP_PRETTY); assert_se(parse_timestamp(s, &z) >= 0); log_debug("%s -> " USEC_FMT " -> %s -> " USEC_FMT, pretty, y, s, z); - assert_se(streq(s, pretty)); + ASSERT_STREQ(s, pretty); assert_se(x == y); assert_se(x == z); } diff --git a/src/test/test-tmpfile-util.c b/src/test/test-tmpfile-util.c index 4859f62..bef503b 100644 --- a/src/test/test-tmpfile-util.c +++ b/src/test/test-tmpfile-util.c @@ -105,7 +105,7 @@ static void test_tempfn_xxxxxx_one(const char *p, const char *extra, const char const char *suffix; assert_se(suffix = startswith(s, expect)); - assert_se(streq(suffix, "XXXXXX")); + ASSERT_STREQ(suffix, "XXXXXX"); } assert_se(ret == r); } @@ -283,7 +283,7 @@ TEST(link_tmpfile) { assert_se(link_tmpfile(fd, tmp, d, /* flags= */ 0) >= 0); assert_se(read_one_line_file(d, &line) >= 0); - assert_se(streq(line, "foobar")); + ASSERT_STREQ(line, "foobar"); fd = safe_close(fd); tmp = mfree(tmp); @@ -298,7 +298,7 @@ TEST(link_tmpfile) { line = mfree(line); assert_se(read_one_line_file(d, &line) >= 0); - assert_se(streq(line, "waumiau")); + ASSERT_STREQ(line, "waumiau"); assert_se(unlink(d) >= 0); } diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c index e3c7da8..3b5a375 100644 --- a/src/test/test-tpm2.c +++ b/src/test/test-tpm2.c @@ -74,7 +74,7 @@ TEST(tpm2_util_pbkdf2_hmac_sha256) { }; uint8_t res[SHA256_DIGEST_SIZE]; - for(size_t i = 0; i < sizeof(test_vectors)/sizeof(test_vectors[0]); i++) { + for (size_t i = 0; i < sizeof(test_vectors)/sizeof(test_vectors[0]); i++) { int rc = tpm2_util_pbkdf2_hmac_sha256( test_vectors[i].pass, @@ -199,7 +199,7 @@ static void _test_tpms_sw( tpm2_tpms_pcr_selection_from_mask(mask, hash, &s); _cleanup_free_ char *tpms_str = tpm2_tpms_pcr_selection_to_string(&s); - assert_se(streq(tpms_str, expected_str)); + ASSERT_STREQ(tpms_str, expected_str); assert_se(tpm2_tpms_pcr_selection_weight(&s) == expected_weight); assert_se(tpm2_tpms_pcr_selection_is_empty(&s) == (expected_weight == 0)); @@ -242,7 +242,7 @@ static void _test_tpml_sw( assert_se(l.count == expected_count); _cleanup_free_ char *tpml_str = tpm2_tpml_pcr_selection_to_string(&l); - assert_se(streq(tpml_str, expected_str)); + ASSERT_STREQ(tpml_str, expected_str); assert_se(tpm2_tpml_pcr_selection_weight(&l) == expected_weight); assert_se(tpm2_tpml_pcr_selection_is_empty(&l) == (expected_weight == 0)); @@ -678,7 +678,7 @@ TEST(parse_pcr_argument) { assert_se(tpm2_parse_pcr_argument("1,2=123456abc", &v, &n_v) < 0); assert_se(tpm2_parse_pcr_argument("1,2:invalid", &v, &n_v) < 0); assert_se(tpm2_parse_pcr_argument("1:sha1=invalid", &v, &n_v) < 0); - assert_se(v == NULL); + ASSERT_NULL(v); assert_se(n_v == 0); check_parse_pcr_argument_to_mask("", 0x0); @@ -1139,42 +1139,38 @@ static void calculate_seal_and_unseal( assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0); size_t secret_size = strlen(secret_string) + 1; - _cleanup_free_ void *blob = NULL; - size_t blob_size = 0; - _cleanup_free_ void *serialized_parent = NULL; - size_t serialized_parent_size; + _cleanup_(iovec_done) struct iovec blob = {}, serialized_parent = {}; assert_se(tpm2_calculate_seal( parent_index, parent_public, /* attributes= */ NULL, - secret_string, secret_size, + &IOVEC_MAKE(secret_string, secret_size), /* policy= */ NULL, /* pin= */ NULL, - /* ret_secret= */ NULL, /* ret_secret_size= */ 0, - &blob, &blob_size, - &serialized_parent, &serialized_parent_size) >= 0); + /* ret_secret= */ NULL, + &blob, + &serialized_parent) >= 0); - _cleanup_free_ void *unsealed_secret = NULL; - size_t unsealed_secret_size; + _cleanup_(iovec_done) struct iovec unsealed_secret = {}; assert_se(tpm2_unseal( c, /* hash_pcr_mask= */ 0, /* pcr_bank= */ 0, - /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey= */ NULL, /* pubkey_pcr_mask= */ 0, /* signature= */ NULL, /* pin= */ NULL, /* pcrlock_policy= */ NULL, /* primary_alg= */ 0, - blob, blob_size, - /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0, - serialized_parent, serialized_parent_size, - &unsealed_secret, &unsealed_secret_size) >= 0); + &blob, + /* known_policy_hash= */ NULL, + &serialized_parent, + &unsealed_secret) >= 0); - assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0); + assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret.iov_base, unsealed_secret.iov_len) == 0); - char unsealed_string[unsealed_secret_size]; - assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1); + char unsealed_string[unsealed_secret.iov_len]; + assert_se(snprintf(unsealed_string, unsealed_secret.iov_len, "%s", (char*) unsealed_secret.iov_base) == (int) unsealed_secret.iov_len - 1); log_debug("Unsealed secret is: %s", unsealed_string); } @@ -1226,34 +1222,33 @@ static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); - _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL; - size_t secret_size, blob_size, srk_size, unsealed_secret_size; + _cleanup_(iovec_done) struct iovec secret = {}, blob = {}, srk = {}, unsealed_secret = {}; assert_se(tpm2_seal( c, handle, &policy, /* pin= */ NULL, - &secret, &secret_size, - &blob, &blob_size, + &secret, + &blob, /* ret_primary_alg= */ NULL, - &srk, &srk_size) >= 0); + &srk) >= 0); assert_se(tpm2_unseal( c, /* hash_pcr_mask= */ 0, /* pcr_bank= */ 0, - /* pubkey= */ NULL, /* pubkey_size= */ 0, + /* pubkey= */ NULL, /* pubkey_pcr_mask= */ 0, /* signature= */ NULL, /* pin= */ NULL, /* pcrlock_policy= */ NULL, /* primary_alg= */ 0, - blob, blob_size, - /* policy_hash= */ NULL, /* policy_hash_size= */ 0, - srk, srk_size, - &unsealed_secret, &unsealed_secret_size) >= 0); + &blob, + /* policy_hash= */ NULL, + &srk, + &unsealed_secret) >= 0); - assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0); + assert_se(iovec_memcmp(&secret, &unsealed_secret) == 0); } static void check_seal_unseal(Tpm2Context *c) { @@ -1271,7 +1266,7 @@ static void check_seal_unseal(Tpm2Context *c) { check_seal_unseal_for_handle(c, 0); check_seal_unseal_for_handle(c, TPM2_SRK_HANDLE); - FOREACH_ARRAY(template, test_templates, ELEMENTSOF(test_templates)) { + FOREACH_ELEMENT(template, test_templates) { TPM2B_PUBLIC public = { .publicArea = **template, .size = sizeof(**template), diff --git a/src/test/test-udev-util.c b/src/test/test-udev-util.c index cb80c69..07f4e01 100644 --- a/src/test/test-udev-util.c +++ b/src/test/test-udev-util.c @@ -16,7 +16,7 @@ static void test_udev_replace_whitespace_one_len(const char *str, size_t len, co assert_se(result); r = udev_replace_whitespace(str, result, len); assert_se((size_t) r == strlen(expected)); - assert_se(streq(result, expected)); + ASSERT_STREQ(result, expected); } static void test_udev_replace_whitespace_one(const char *str, const char *expected) { diff --git a/src/test/test-uid-alloc-range.c b/src/test/test-uid-alloc-range.c deleted file mode 100644 index cd06463..0000000 --- a/src/test/test-uid-alloc-range.c +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: LGPL-2.1-or-later */ - -#include -#include - -#include "fd-util.h" -#include "fileio.h" -#include "format-util.h" -#include "fs-util.h" -#include "tests.h" -#include "tmpfile-util.h" -#include "uid-alloc-range.h" - -static void test_read_login_defs_one(const char *path) { - log_info("/* %s(\"%s\") */", __func__, path ?: ""); - - _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-user-record.XXXXXX"; - _cleanup_fclose_ FILE *f = NULL; - if (!path) { - assert_se(fmkostemp_safe(name, "r+", &f) == 0); - fprintf(f, - "SYS_UID_MIN "UID_FMT"\n" - "SYS_UID_MAX "UID_FMT"\n" - "SYS_GID_MIN "GID_FMT"\n" - "SYS_GID_MAX "GID_FMT"\n", - (uid_t) (SYSTEM_ALLOC_UID_MIN + 5), - (uid_t) (SYSTEM_UID_MAX + 5), - (gid_t) (SYSTEM_ALLOC_GID_MIN + 5), - (gid_t) (SYSTEM_GID_MAX + 5)); - assert_se(fflush_and_check(f) >= 0); - } - - UGIDAllocationRange defs; - assert_se(read_login_defs(&defs, path ?: name, NULL) >= 0); - - log_info("system_alloc_uid_min="UID_FMT, defs.system_alloc_uid_min); - log_info("system_uid_max="UID_FMT, defs.system_uid_max); - log_info("system_alloc_gid_min="GID_FMT, defs.system_alloc_gid_min); - log_info("system_gid_max="GID_FMT, defs.system_gid_max); - - if (!path) { - uid_t offset = ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES ? 5 : 0; - assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN + offset); - assert_se(defs.system_uid_max == SYSTEM_UID_MAX + offset); - assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN + offset); - assert_se(defs.system_gid_max == SYSTEM_GID_MAX + offset); - } else if (streq(path, "/dev/null")) { - assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN); - assert_se(defs.system_uid_max == SYSTEM_UID_MAX); - assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN); - assert_se(defs.system_gid_max == SYSTEM_GID_MAX); - } -} - -TEST(read_login_defs) { - test_read_login_defs_one("/dev/null"); - test_read_login_defs_one("/etc/login.defs"); - test_read_login_defs_one(NULL); -} - -TEST(acquire_ugid_allocation_range) { - const UGIDAllocationRange *defs; - assert_se(defs = acquire_ugid_allocation_range()); - - log_info("system_alloc_uid_min="UID_FMT, defs->system_alloc_uid_min); - log_info("system_uid_max="UID_FMT, defs->system_uid_max); - log_info("system_alloc_gid_min="GID_FMT, defs->system_alloc_gid_min); - log_info("system_gid_max="GID_FMT, defs->system_gid_max); -} - -TEST(uid_is_system) { - uid_t uid = 0; - log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); - - uid = 999; - log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); - - uid = getuid(); - log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); -} - -TEST(gid_is_system) { - gid_t gid = 0; - log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); - - gid = 999; - log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); - - gid = getgid(); - log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); -} - -DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-uid-classification.c b/src/test/test-uid-classification.c new file mode 100644 index 0000000..9c7500a --- /dev/null +++ b/src/test/test-uid-classification.c @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include + +#include "fd-util.h" +#include "fileio.h" +#include "format-util.h" +#include "fs-util.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "uid-classification.h" + +static void test_read_login_defs_one(const char *path) { + log_info("/* %s(\"%s\") */", __func__, path ?: ""); + + _cleanup_(unlink_tempfilep) char name[] = "/tmp/test-user-record.XXXXXX"; + _cleanup_fclose_ FILE *f = NULL; + if (!path) { + assert_se(fmkostemp_safe(name, "r+", &f) == 0); + fprintf(f, + "SYS_UID_MIN "UID_FMT"\n" + "SYS_UID_MAX "UID_FMT"\n" + "SYS_GID_MIN "GID_FMT"\n" + "SYS_GID_MAX "GID_FMT"\n", + (uid_t) (SYSTEM_ALLOC_UID_MIN + 5), + (uid_t) (SYSTEM_UID_MAX + 5), + (gid_t) (SYSTEM_ALLOC_GID_MIN + 5), + (gid_t) (SYSTEM_GID_MAX + 5)); + assert_se(fflush_and_check(f) >= 0); + } + + UGIDAllocationRange defs; + assert_se(read_login_defs(&defs, path ?: name, NULL) >= 0); + + log_info("system_alloc_uid_min="UID_FMT, defs.system_alloc_uid_min); + log_info("system_uid_max="UID_FMT, defs.system_uid_max); + log_info("system_alloc_gid_min="GID_FMT, defs.system_alloc_gid_min); + log_info("system_gid_max="GID_FMT, defs.system_gid_max); + + if (!path) { + uid_t offset = ENABLE_COMPAT_MUTABLE_UID_BOUNDARIES ? 5 : 0; + assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN + offset); + assert_se(defs.system_uid_max == SYSTEM_UID_MAX + offset); + assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN + offset); + assert_se(defs.system_gid_max == SYSTEM_GID_MAX + offset); + } else if (streq(path, "/dev/null")) { + assert_se(defs.system_alloc_uid_min == SYSTEM_ALLOC_UID_MIN); + assert_se(defs.system_uid_max == SYSTEM_UID_MAX); + assert_se(defs.system_alloc_gid_min == SYSTEM_ALLOC_GID_MIN); + assert_se(defs.system_gid_max == SYSTEM_GID_MAX); + } +} + +TEST(read_login_defs) { + test_read_login_defs_one("/dev/null"); + test_read_login_defs_one("/etc/login.defs"); + test_read_login_defs_one(NULL); +} + +TEST(acquire_ugid_allocation_range) { + const UGIDAllocationRange *defs; + assert_se(defs = acquire_ugid_allocation_range()); + + log_info("system_alloc_uid_min="UID_FMT, defs->system_alloc_uid_min); + log_info("system_uid_max="UID_FMT, defs->system_uid_max); + log_info("system_alloc_gid_min="GID_FMT, defs->system_alloc_gid_min); + log_info("system_gid_max="GID_FMT, defs->system_gid_max); +} + +TEST(uid_is_system) { + uid_t uid = 0; + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); + + uid = 999; + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); + + uid = getuid(); + log_info("uid_is_system("UID_FMT") = %s", uid, yes_no(uid_is_system(uid))); +} + +TEST(gid_is_system) { + gid_t gid = 0; + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); + + gid = 999; + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); + + gid = getgid(); + log_info("gid_is_system("GID_FMT") = %s", gid, yes_no(gid_is_system(gid))); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-uid-range.c b/src/test/test-uid-range.c index 186f6ee..6041d08 100644 --- a/src/test/test-uid-range.c +++ b/src/test/test-uid-range.c @@ -14,16 +14,24 @@ #include "virt.h" TEST(uid_range) { - _cleanup_(uid_range_freep) UidRange *p = NULL; + _cleanup_(uid_range_freep) UIDRange *p = NULL; uid_t search; assert_se(uid_range_covers(p, 0, 0)); assert_se(!uid_range_covers(p, 0, 1)); assert_se(!uid_range_covers(p, 100, UINT32_MAX)); + assert_se(!uid_range_covers(p, UINT32_MAX, 1)); + assert_se(!uid_range_covers(p, UINT32_MAX - 10, 11)); + + assert_se(uid_range_entries(p) == 0); + assert_se(uid_range_size(p) == 0); + assert_se(uid_range_is_empty(p)); assert_se(uid_range_add_str(&p, "500-999") >= 0); assert_se(p); - assert_se(p->n_entries == 1); + assert_se(uid_range_entries(p) == 1); + assert_se(uid_range_size(p) == 500); + assert_se(!uid_range_is_empty(p)); assert_se(p->entries[0].start == 500); assert_se(p->entries[0].nr == 500); @@ -54,19 +62,23 @@ TEST(uid_range) { assert_se(uid_range_next_lower(p, &search) == -EBUSY); assert_se(uid_range_add_str(&p, "1000") >= 0); - assert_se(p->n_entries == 1); + assert_se(uid_range_entries(p) == 1); assert_se(p->entries[0].start == 500); assert_se(p->entries[0].nr == 501); assert_se(uid_range_add_str(&p, "30-40") >= 0); - assert_se(p->n_entries == 2); + assert_se(uid_range_entries(p) == 2); + assert_se(uid_range_size(p) == 500 + 1 + 11); + assert_se(!uid_range_is_empty(p)); assert_se(p->entries[0].start == 30); assert_se(p->entries[0].nr == 11); assert_se(p->entries[1].start == 500); assert_se(p->entries[1].nr == 501); assert_se(uid_range_add_str(&p, "60-70") >= 0); - assert_se(p->n_entries == 3); + assert_se(uid_range_entries(p) == 3); + assert_se(uid_range_size(p) == 500 + 1 + 11 + 11); + assert_se(!uid_range_is_empty(p)); assert_se(p->entries[0].start == 30); assert_se(p->entries[0].nr == 11); assert_se(p->entries[1].start == 60); @@ -75,31 +87,44 @@ TEST(uid_range) { assert_se(p->entries[2].nr == 501); assert_se(uid_range_add_str(&p, "20-2000") >= 0); - assert_se(p->n_entries == 1); + assert_se(uid_range_entries(p) == 1); + assert_se(uid_range_size(p) == 1981); assert_se(p->entries[0].start == 20); assert_se(p->entries[0].nr == 1981); assert_se(uid_range_add_str(&p, "2002") >= 0); - assert_se(p->n_entries == 2); + assert_se(uid_range_entries(p) == 2); + assert_se(uid_range_size(p) == 1982); assert_se(p->entries[0].start == 20); assert_se(p->entries[0].nr == 1981); assert_se(p->entries[1].start == 2002); assert_se(p->entries[1].nr == 1); + _cleanup_(uid_range_freep) UIDRange *q = NULL; + assert_se(!uid_range_equal(p, q)); + assert_se(uid_range_add_str(&q, "20-2000") >= 0); + assert_se(!uid_range_equal(p, q)); + assert_se(uid_range_add_str(&q, "2002") >= 0); + assert_se(uid_range_equal(p, q)); + assert_se(uid_range_add_str(&p, "2001") >= 0); - assert_se(p->n_entries == 1); + assert_se(uid_range_entries(p) == 1); + assert_se(uid_range_size(p) == 1983); assert_se(p->entries[0].start == 20); assert_se(p->entries[0].nr == 1983); + + assert_se(uid_range_add_str(&q, "2001") >= 0); + assert_se(uid_range_equal(p, q)); } TEST(load_userns) { - _cleanup_(uid_range_freep) UidRange *p = NULL; + _cleanup_(uid_range_freep) UIDRange *p = NULL; _cleanup_(unlink_and_freep) char *fn = NULL; _cleanup_fclose_ FILE *f = NULL; int r; - r = uid_range_load_userns(&p, NULL); - if (r < 0 && ERRNO_IS_NOT_SUPPORTED(r)) + r = uid_range_load_userns(NULL, UID_RANGE_USERNS_INSIDE, &p); + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return; assert_se(r >= 0); @@ -121,7 +146,7 @@ TEST(load_userns) { p = uid_range_free(p); - assert_se(uid_range_load_userns(&p, fn) >= 0); + assert_se(uid_range_load_userns(fn, UID_RANGE_USERNS_INSIDE, &p) >= 0); assert_se(uid_range_contains(p, 0)); assert_se(uid_range_contains(p, 19)); @@ -134,7 +159,7 @@ TEST(load_userns) { } TEST(uid_range_coalesce) { - _cleanup_(uid_range_freep) UidRange *p = NULL; + _cleanup_(uid_range_freep) UIDRange *p = NULL; for (size_t i = 0; i < 10; i++) { assert_se(uid_range_add_internal(&p, i * 10, 10, /* coalesce = */ false) >= 0); diff --git a/src/test/test-unit-file.c b/src/test/test-unit-file.c index 9f8787b..63e5000 100644 --- a/src/test/test-unit-file.c +++ b/src/test/test-unit-file.c @@ -26,7 +26,7 @@ TEST(unit_validate_alias_symlink_and_warn) { } TEST(unit_file_build_name_map) { - _cleanup_(lookup_paths_free) LookupPaths lp = {}; + _cleanup_(lookup_paths_done) LookupPaths lp = {}; _cleanup_hashmap_free_ Hashmap *unit_ids = NULL; _cleanup_hashmap_free_ Hashmap *unit_names = NULL; const char *k, *dst; @@ -88,18 +88,18 @@ TEST(unit_file_build_name_map) { TEST(runlevel_to_target) { in_initrd_force(false); - assert_se(streq_ptr(runlevel_to_target(NULL), NULL)); - assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL)); - assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL)); - assert_se(streq_ptr(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET)); - assert_se(streq_ptr(runlevel_to_target("rd.rescue"), NULL)); + ASSERT_STREQ(runlevel_to_target(NULL), NULL); + ASSERT_STREQ(runlevel_to_target("unknown-runlevel"), NULL); + ASSERT_STREQ(runlevel_to_target("rd.unknown-runlevel"), NULL); + ASSERT_STREQ(runlevel_to_target("3"), SPECIAL_MULTI_USER_TARGET); + ASSERT_STREQ(runlevel_to_target("rd.rescue"), NULL); in_initrd_force(true); - assert_se(streq_ptr(runlevel_to_target(NULL), NULL)); - assert_se(streq_ptr(runlevel_to_target("unknown-runlevel"), NULL)); - assert_se(streq_ptr(runlevel_to_target("rd.unknown-runlevel"), NULL)); - assert_se(streq_ptr(runlevel_to_target("3"), NULL)); - assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET)); + ASSERT_STREQ(runlevel_to_target(NULL), NULL); + ASSERT_STREQ(runlevel_to_target("unknown-runlevel"), NULL); + ASSERT_STREQ(runlevel_to_target("rd.unknown-runlevel"), NULL); + ASSERT_STREQ(runlevel_to_target("3"), NULL); + ASSERT_STREQ(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET); } static int intro(void) { diff --git a/src/test/test-unit-name.c b/src/test/test-unit-name.c index 8e9332c..b482104 100644 --- a/src/test/test-unit-name.c +++ b/src/test/test-unit-name.c @@ -92,7 +92,7 @@ static void test_unit_name_replace_instance_one(const char *pattern, const char _cleanup_free_ char *t = NULL; assert_se(unit_name_replace_instance(pattern, repl, &t) == ret); puts(strna(t)); - assert_se(streq_ptr(t, expected)); + ASSERT_STREQ(t, expected); } TEST(unit_name_replace_instance) { @@ -112,7 +112,7 @@ static void test_unit_name_from_path_one(const char *path, const char *suffix, c assert_se(unit_name_from_path(path, suffix, &t) == ret); puts(strna(t)); - assert_se(streq_ptr(t, expected)); + ASSERT_STREQ(t, expected); if (t) { _cleanup_free_ char *k = NULL; @@ -159,7 +159,7 @@ static void test_unit_name_from_path_instance_one(const char *pattern, const cha assert_se(unit_name_from_path_instance(pattern, path, suffix, &t) == ret); puts(strna(t)); - assert_se(streq_ptr(t, expected)); + ASSERT_STREQ(t, expected); if (t) { _cleanup_free_ char *k = NULL, *v = NULL; @@ -185,7 +185,7 @@ static void test_unit_name_to_path_one(const char *unit, const char *path, int r _cleanup_free_ char *p = NULL; assert_se(unit_name_to_path(unit, &p) == ret); - assert_se(streq_ptr(path, p)); + ASSERT_STREQ(path, p); } TEST(unit_name_to_path) { @@ -208,7 +208,7 @@ static void test_unit_name_mangle_one(bool allow_globs, const char *pattern, con assert_se(r == ret); puts(strna(t)); - assert_se(streq_ptr(t, expect)); + ASSERT_STREQ(t, expect); if (t) { _cleanup_free_ char *k = NULL; @@ -217,7 +217,7 @@ static void test_unit_name_mangle_one(bool allow_globs, const char *pattern, con (allow_globs && string_is_glob(t))); assert_se(unit_name_mangle(t, (allow_globs * UNIT_NAME_MANGLE_GLOB) | UNIT_NAME_MANGLE_WARN, &k) == 0); - assert_se(streq_ptr(t, k)); + ASSERT_STREQ(t, k); } } @@ -246,7 +246,7 @@ static void test_unit_name_mangle_with_suffix_one(const char *arg, int expected, log_debug("%s: %s -> %d, %s", __func__, arg, r, strnull(s)); assert_se(r == expected); - assert_se(streq_ptr(s, expected_name)); + ASSERT_STREQ(s, expected_name); } TEST(unit_name_mangle_with_suffix) { @@ -499,11 +499,11 @@ TEST(unit_name_change_suffix) { char *t; assert_se(unit_name_change_suffix("foo.mount", ".service", &t) == 0); - assert_se(streq(t, "foo.service")); + ASSERT_STREQ(t, "foo.service"); free(t); assert_se(unit_name_change_suffix("foo@stuff.service", ".socket", &t) == 0); - assert_se(streq(t, "foo@stuff.socket")); + ASSERT_STREQ(t, "foo@stuff.socket"); free(t); } @@ -511,15 +511,15 @@ TEST(unit_name_build) { char *t; assert_se(unit_name_build("foo", "bar", ".service", &t) == 0); - assert_se(streq(t, "foo@bar.service")); + ASSERT_STREQ(t, "foo@bar.service"); free(t); assert_se(unit_name_build("fo0-stUff_b", "bar", ".mount", &t) == 0); - assert_se(streq(t, "fo0-stUff_b@bar.mount")); + ASSERT_STREQ(t, "fo0-stUff_b@bar.mount"); free(t); assert_se(unit_name_build("foo", NULL, ".service", &t) == 0); - assert_se(streq(t, "foo.service")); + ASSERT_STREQ(t, "foo.service"); free(t); } @@ -563,7 +563,7 @@ TEST(build_subslice) { free(b); assert_se(slice_build_subslice(a, "foobar", &b) >= 0); free(a); - assert_se(streq(b, "foo-bar-barfoo-foobar.slice")); + ASSERT_STREQ(b, "foo-bar-barfoo-foobar.slice"); free(b); assert_se(slice_build_subslice("foo.service", "bar", &a) < 0); @@ -574,7 +574,7 @@ static void test_build_parent_slice_one(const char *name, const char *expect, in _cleanup_free_ char *s = NULL; assert_se(slice_build_parent_slice(name, &s) == ret); - assert_se(streq_ptr(s, expect)); + ASSERT_STREQ(s, expect); } TEST(build_parent_slice) { @@ -602,17 +602,17 @@ TEST(unit_name_to_instance) { r = unit_name_to_instance("foo@bar.service", &instance); assert_se(r == UNIT_NAME_INSTANCE); - assert_se(streq(instance, "bar")); + ASSERT_STREQ(instance, "bar"); free(instance); r = unit_name_to_instance("foo@.service", &instance); assert_se(r == UNIT_NAME_TEMPLATE); - assert_se(streq(instance, "")); + ASSERT_STREQ(instance, ""); free(instance); r = unit_name_to_instance("fo0-stUff_b@b.service", &instance); assert_se(r == UNIT_NAME_INSTANCE); - assert_se(streq(instance, "b")); + ASSERT_STREQ(instance, "b"); free(instance); r = unit_name_to_instance("foo.service", &instance); @@ -633,7 +633,7 @@ TEST(unit_name_escape) { r = unit_name_escape("ab+-c.a/bc@foo.service"); assert_se(r); - assert_se(streq(r, "ab\\x2b\\x2dc.a-bc\\x40foo.service")); + ASSERT_STREQ(r, "ab\\x2b\\x2dc.a-bc\\x40foo.service"); } static void test_u_n_t_one(const char *name, const char *expected, int ret) { @@ -641,7 +641,7 @@ static void test_u_n_t_one(const char *name, const char *expected, int ret) { assert_se(unit_name_template(name, &f) == ret); printf("got: %s, expected: %s\n", strna(f), strna(expected)); - assert_se(streq_ptr(f, expected)); + ASSERT_STREQ(f, expected); } TEST(unit_name_template) { @@ -653,7 +653,7 @@ static void test_unit_name_path_unescape_one(const char *name, const char *path, _cleanup_free_ char *p = NULL; assert_se(unit_name_path_unescape(name, &p) == ret); - assert_se(streq_ptr(path, p)); + ASSERT_STREQ(path, p); } TEST(unit_name_path_unescape) { @@ -675,7 +675,7 @@ static void test_unit_name_to_prefix_one(const char *input, int ret, const char _cleanup_free_ char *k = NULL; assert_se(unit_name_to_prefix(input, &k) == ret); - assert_se(streq_ptr(k, output)); + ASSERT_STREQ(k, output); } TEST(unit_name_to_prefix) { @@ -695,7 +695,7 @@ static void test_unit_name_from_dbus_path_one(const char *input, int ret, const _cleanup_free_ char *k = NULL; assert_se(unit_name_from_dbus_path(input, &k) == ret); - assert_se(streq_ptr(k, output)); + ASSERT_STREQ(k, output); } TEST(unit_name_from_dbus_path) { diff --git a/src/test/test-user-util.c b/src/test/test-user-util.c index db76cde..e21d8da 100644 --- a/src/test/test-user-util.c +++ b/src/test/test-user-util.c @@ -21,7 +21,7 @@ static void test_uid_to_name_one(uid_t uid, const char *name) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } - assert_se(streq_ptr(t, name)); + ASSERT_STREQ(t, name); } TEST(uid_to_name) { @@ -41,7 +41,7 @@ static void test_gid_to_name_one(gid_t gid, const char *name) { log_info("(skipping detailed tests because nobody is not synthesized)"); return; } - assert_se(streq_ptr(t, name)); + ASSERT_STREQ(t, name); } TEST(gid_to_name) { @@ -137,8 +137,8 @@ TEST(parse_uid) { } TEST(uid_ptr) { - assert_se(UID_TO_PTR(0) != NULL); - assert_se(UID_TO_PTR(1000) != NULL); + ASSERT_NOT_NULL(UID_TO_PTR(0)); + ASSERT_NOT_NULL(UID_TO_PTR(1000)); assert_se(PTR_TO_UID(UID_TO_PTR(0)) == 0); assert_se(PTR_TO_UID(UID_TO_PTR(1000)) == 1000); @@ -338,7 +338,7 @@ static void test_get_user_creds_one(const char *id, const char *name, uid_t uid, return; } assert_se(r == 0); - assert_se(streq_ptr(id, name)); + ASSERT_STREQ(id, name); assert_se(ruid == uid); assert_se(rgid == gid); assert_se(path_equal(rhome, home)); @@ -364,7 +364,7 @@ static void test_get_group_creds_one(const char *id, const char *name, gid_t gid return; } assert_se(r == 0); - assert_se(streq_ptr(id, name)); + ASSERT_STREQ(id, name); assert_se(rgid == gid); } @@ -466,7 +466,7 @@ static void test_mangle_gecos_one(const char *input, const char *expected) { _cleanup_free_ char *p = NULL; assert_se(p = mangle_gecos(input)); - assert_se(streq(p, expected)); + ASSERT_STREQ(p, expected); assert_se(valid_gecos(p)); } diff --git a/src/test/test-utf8.c b/src/test/test-utf8.c index a0d7dc1..d60cf00 100644 --- a/src/test/test-utf8.c +++ b/src/test/test-utf8.c @@ -62,7 +62,7 @@ static void test_utf8_to_ascii_one(const char *s, int r_expected, const char *ex r = utf8_to_ascii(s, '*', &ans); log_debug("\"%s\" → %d/\"%s\" (expected %d/\"%s\")", s, r, strnull(ans), r_expected, strnull(expected)); assert_se(r == r_expected); - assert_se(streq_ptr(ans, expected)); + ASSERT_STREQ(ans, expected); } TEST(utf8_to_ascii) { @@ -223,7 +223,7 @@ TEST(utf8_to_utf16) { b = utf16_to_utf8(a, SIZE_MAX); assert_se(b); - assert_se(streq(p, b)); + ASSERT_STREQ(p, b); } } diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index cbdb9c6..86dbcc9 100644 --- a/src/test/test-varlink-idl.c +++ b/src/test/test-varlink-idl.c @@ -8,11 +8,17 @@ #include "varlink.h" #include "varlink-idl.h" #include "varlink-io.systemd.h" +#include "varlink-io.systemd.BootControl.h" +#include "varlink-io.systemd.Credentials.h" #include "varlink-io.systemd.Journal.h" #include "varlink-io.systemd.ManagedOOM.h" +#include "varlink-io.systemd.MountFileSystem.h" +#include "varlink-io.systemd.NamespaceResource.h" +#include "varlink-io.systemd.Network.h" #include "varlink-io.systemd.PCRExtend.h" -#include "varlink-io.systemd.Resolve.Monitor.h" +#include "varlink-io.systemd.PCRLock.h" #include "varlink-io.systemd.Resolve.h" +#include "varlink-io.systemd.Resolve.Monitor.h" #include "varlink-io.systemd.UserDatabase.h" #include "varlink-io.systemd.oom.h" #include "varlink-io.systemd.service.h" @@ -117,7 +123,7 @@ static void test_parse_format_one(const VarlinkInterface *iface) { assert_se(varlink_idl_parse(text, NULL, NULL, &parsed) >= 0); assert_se(varlink_idl_consistent(parsed, LOG_ERR) >= 0); assert_se(varlink_idl_format(parsed, &text2) >= 0); - assert_se(streq(text, text2)); + ASSERT_STREQ(text, text2); } TEST(parse_format) { @@ -125,6 +131,8 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_UserDatabase); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_NamespaceResource); + print_separator(); test_parse_format_one(&vl_interface_io_systemd_Journal); print_separator(); test_parse_format_one(&vl_interface_io_systemd_Resolve); @@ -133,16 +141,26 @@ TEST(parse_format) { print_separator(); test_parse_format_one(&vl_interface_io_systemd_ManagedOOM); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_MountFileSystem); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Network); + print_separator(); test_parse_format_one(&vl_interface_io_systemd_oom); print_separator(); test_parse_format_one(&vl_interface_io_systemd); print_separator(); test_parse_format_one(&vl_interface_io_systemd_PCRExtend); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_PCRLock); + print_separator(); test_parse_format_one(&vl_interface_io_systemd_service); print_separator(); test_parse_format_one(&vl_interface_io_systemd_sysext); print_separator(); + test_parse_format_one(&vl_interface_io_systemd_Credentials); + print_separator(); + test_parse_format_one(&vl_interface_io_systemd_BootControl); + print_separator(); test_parse_format_one(&vl_interface_xyz_test); } @@ -338,7 +356,7 @@ TEST(validate_method_call) { JsonVariant *reply = NULL; const char *error_id = NULL; - assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, JSON_BUILD_OBJECT( JSON_BUILD_PAIR_UNSIGNED("foo", 8), JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0); @@ -355,7 +373,7 @@ TEST(validate_method_call) { json_variant_dump(expected_reply, JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO, NULL, NULL); assert_se(json_variant_equal(reply, expected_reply)); - assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, JSON_BUILD_OBJECT( JSON_BUILD_PAIR_UNSIGNED("foo", 9), JSON_BUILD_PAIR_UNSIGNED("bar", 8), @@ -364,18 +382,18 @@ TEST(validate_method_call) { assert_se(!error_id); assert_se(json_variant_equal(reply, expected_reply)); - assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, JSON_BUILD_OBJECT( JSON_BUILD_PAIR_UNSIGNED("foo", 8), JSON_BUILD_PAIR_UNSIGNED("bar", 9), JSON_BUILD_PAIR_STRING("zzz", "pfft"))) >= 0); - assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER)); + ASSERT_STREQ(error_id, VARLINK_ERROR_INVALID_PARAMETER); - assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, NULL, + assert_se(varlink_callb(v, "xyz.TestMethod", &reply, &error_id, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_STRING("foo", "wuff"), + JSON_BUILD_PAIR_BOOLEAN("foo", true), JSON_BUILD_PAIR_UNSIGNED("bar", 9))) >= 0); - assert_se(streq_ptr(error_id, VARLINK_ERROR_INVALID_PARAMETER)); + ASSERT_STREQ(error_id, VARLINK_ERROR_INVALID_PARAMETER); assert_se(varlink_send(v, "xyz.Done", NULL) >= 0); assert_se(varlink_flush(v) >= 0); diff --git a/src/test/test-varlink.c b/src/test/test-varlink.c index 2617ed0..8ad5757 100644 --- a/src/test/test-varlink.c +++ b/src/test/test-varlink.c @@ -107,7 +107,7 @@ static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFl if (!a) return varlink_error(link, "io.test.BadParameters", NULL); - assert_se(streq_ptr(json_variant_string(a), "whoop")); + ASSERT_STREQ(json_variant_string(a), "whoop"); int xx = varlink_peek_fd(link, 0), yy = varlink_peek_fd(link, 1), @@ -123,8 +123,8 @@ static int method_passfd(Varlink *link, JsonVariant *parameters, VarlinkMethodFl 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); + _cleanup_close_ int vv = acquire_data_fd("miau"); + _cleanup_close_ int ww = acquire_data_fd("wuff"); assert_se(vv >= 0); assert_se(ww >= 0); @@ -183,7 +183,7 @@ static int overload_reply(Varlink *link, JsonVariant *parameters, const char *er * be talking to an overloaded server */ log_debug("Over reply triggered with error: %s", strna(error_id)); - assert_se(streq(error_id, VARLINK_ERROR_DISCONNECTED)); + ASSERT_STREQ(error_id, VARLINK_ERROR_DISCONNECTED); sd_event_exit(varlink_get_event(link), 0); return 0; @@ -238,10 +238,10 @@ static void flood_test(const char *address) { static void *thread(void *arg) { _cleanup_(varlink_flush_close_unrefp) Varlink *c = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *i = NULL, *j = NULL; - JsonVariant *o = NULL, *k = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *i = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *wrong = NULL; + JsonVariant *o = NULL, *k = NULL, *j = NULL; const char *error_id; - VarlinkReplyFlags flags = 0; const char *e; int x = 0; @@ -253,10 +253,16 @@ static void *thread(void *arg) { 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_collect(c, "io.test.DoSomethingMore", i, &j, &error_id, &flags) >= 0); + /* Test that client is able to perform two sequential varlink_collect calls if first resulted in an error */ + assert_se(json_build(&wrong, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("a", JSON_BUILD_INTEGER(88)), + JSON_BUILD_PAIR("c", JSON_BUILD_INTEGER(99)))) >= 0); + assert_se(varlink_collect(c, "io.test.DoSomethingMore", wrong, &j, &error_id) >= 0); + assert_se(strcmp_ptr(error_id, "org.varlink.service.InvalidParameter") == 0); + + + assert_se(varlink_collect(c, "io.test.DoSomethingMore", i, &j, &error_id) >= 0); assert_se(!error_id); - assert_se(!flags); assert_se(json_variant_is_array(j) && !json_variant_is_blank_array(j)); JSON_VARIANT_ARRAY_FOREACH(k, j) { @@ -265,13 +271,13 @@ static void *thread(void *arg) { } assert_se(x == 6); - assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e, NULL) >= 0); + assert_se(varlink_call(c, "io.test.DoSomething", i, &o, &e) >= 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); + int fd1 = acquire_data_fd("foo"); + int fd2 = acquire_data_fd("bar"); + int fd3 = acquire_data_fd("quux"); assert_se(fd1 >= 0); assert_se(fd2 >= 0); @@ -281,7 +287,7 @@ static void *thread(void *arg) { 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); + assert_se(varlink_callb(c, "io.test.PassFD", &o, &e, 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); @@ -292,9 +298,9 @@ static void *thread(void *arg) { test_fd(fd4, "miau", 4); test_fd(fd5, "wuff", 4); - assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0); - assert_se(streq_ptr(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist")); - assert_se(streq(e, VARLINK_ERROR_METHOD_NOT_FOUND)); + assert_se(varlink_callb(c, "io.test.IDontExist", &o, &e, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("x", JSON_BUILD_REAL(5.5)))) >= 0); + ASSERT_STREQ(json_variant_string(json_variant_by_key(o, "method")), "io.test.IDontExist"); + ASSERT_STREQ(e, VARLINK_ERROR_METHOD_NOT_FOUND); flood_test(arg); diff --git a/src/test/test-vpick.c b/src/test/test-vpick.c new file mode 100644 index 0000000..88646ec --- /dev/null +++ b/src/test/test-vpick.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "tests.h" +#include "tmpfile-util.h" +#include "vpick.h" + +TEST(path_pick) { + _cleanup_(rm_rf_physical_and_freep) char *p = NULL; + _cleanup_close_ int dfd = -EBADF, sub_dfd = -EBADF; + + dfd = mkdtemp_open(NULL, O_DIRECTORY|O_CLOEXEC, &p); + assert(dfd >= 0); + + sub_dfd = open_mkdir_at(dfd, "foo.v", O_CLOEXEC, 0777); + assert(sub_dfd >= 0); + + assert_se(write_string_file_at(sub_dfd, "foo_5.5.raw", "5.5", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_55.raw", "55", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_5.raw", "5", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_5_ia64.raw", "5", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_7.raw", "7", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_7_x86-64.raw", "7 64bit", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_55_x86-64.raw", "55 64bit", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_55_x86.raw", "55 32bit", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "foo_99_x86.raw", "99 32bit", WRITE_STRING_FILE_CREATE) >= 0); + + /* Let's add an entry for sparc (which is a valid arch, but almost certainly not what we test + * on). This entry should hence always be ignored */ + if (native_architecture() != ARCHITECTURE_SPARC) + assert_se(write_string_file_at(sub_dfd, "foo_100_sparc.raw", "100 sparc", WRITE_STRING_FILE_CREATE) >= 0); + + assert_se(write_string_file_at(sub_dfd, "quux_1_s390.raw", "waldo1", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "quux_2_s390+4-6.raw", "waldo2", WRITE_STRING_FILE_CREATE) >= 0); + assert_se(write_string_file_at(sub_dfd, "quux_3_s390+0-10.raw", "waldo3", WRITE_STRING_FILE_CREATE) >= 0); + + _cleanup_free_ char *pp = NULL; + pp = path_join(p, "foo.v"); + assert_se(pp); + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + + PickFilter filter = { + .architecture = _ARCHITECTURE_INVALID, + .suffix = STRV_MAKE(".raw"), + }; + + if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) { + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "99"); + assert_se(result.architecture == ARCHITECTURE_X86); + assert_se(endswith(result.path, "/foo_99_x86.raw")); + + pick_result_done(&result); + } + + filter.architecture = ARCHITECTURE_X86_64; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "55"); + assert_se(result.architecture == ARCHITECTURE_X86_64); + assert_se(endswith(result.path, "/foo_55_x86-64.raw")); + pick_result_done(&result); + + filter.architecture = ARCHITECTURE_IA64; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "5"); + assert_se(result.architecture == ARCHITECTURE_IA64); + assert_se(endswith(result.path, "/foo_5_ia64.raw")); + pick_result_done(&result); + + filter.architecture = _ARCHITECTURE_INVALID; + filter.version = "5"; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "5"); + if (native_architecture() != ARCHITECTURE_IA64) { + assert_se(result.architecture == _ARCHITECTURE_INVALID); + assert_se(endswith(result.path, "/foo_5.raw")); + } + pick_result_done(&result); + + filter.architecture = ARCHITECTURE_IA64; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "5"); + assert_se(result.architecture == ARCHITECTURE_IA64); + assert_se(endswith(result.path, "/foo_5_ia64.raw")); + pick_result_done(&result); + + filter.architecture = ARCHITECTURE_CRIS; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == 0); + assert_se(result.st.st_mode == MODE_INVALID); + assert_se(!result.version); + assert_se(result.architecture < 0); + assert_se(!result.path); + + assert_se(unlinkat(sub_dfd, "foo_99_x86.raw", 0) >= 0); + + filter.architecture = _ARCHITECTURE_INVALID; + filter.version = NULL; + if (IN_SET(native_architecture(), ARCHITECTURE_X86_64, ARCHITECTURE_X86)) { + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "55"); + + if (native_architecture() == ARCHITECTURE_X86_64) { + assert_se(result.architecture == ARCHITECTURE_X86_64); + assert_se(endswith(result.path, "/foo_55_x86-64.raw")); + } else { + assert_se(result.architecture == ARCHITECTURE_X86); + assert_se(endswith(result.path, "/foo_55_x86.raw")); + } + pick_result_done(&result); + } + + /* Test explicit patterns in last component of path not being .v */ + free(pp); + pp = path_join(p, "foo.v/foo___.raw"); + assert_se(pp); + + if (IN_SET(native_architecture(), ARCHITECTURE_X86, ARCHITECTURE_X86_64)) { + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "55"); + assert_se(result.architecture == native_architecture()); + assert_se(endswith(result.path, ".raw")); + assert_se(strrstr(result.path, "/foo_55_x86")); + pick_result_done(&result); + } + + /* Specify an explicit path */ + free(pp); + pp = path_join(p, "foo.v/foo_5.raw"); + assert_se(pp); + + filter.type_mask = UINT32_C(1) << DT_DIR; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) == -ENOTDIR); + + filter.type_mask = UINT32_C(1) << DT_REG; + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + assert_se(!result.version); + assert_se(result.architecture == _ARCHITECTURE_INVALID); + assert_se(path_equal(result.path, pp)); + pick_result_done(&result); + + free(pp); + pp = path_join(p, "foo.v"); + assert_se(pp); + + filter.architecture = ARCHITECTURE_S390; + filter.basename = "quux"; + + assert_se(path_pick(NULL, AT_FDCWD, pp, &filter, PICK_ARCHITECTURE|PICK_TRIES, &result) > 0); + assert_se(S_ISREG(result.st.st_mode)); + ASSERT_STREQ(result.version, "2"); + assert_se(result.tries_left == 4); + assert_se(result.tries_done == 6); + assert_se(endswith(result.path, "quux_2_s390+4-6.raw")); + assert_se(result.architecture == ARCHITECTURE_S390); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/src/test/test-watch-pid.c b/src/test/test-watch-pid.c index b0c2c06..423a9be 100644 --- a/src/test/test-watch-pid.c +++ b/src/test/test-watch-pid.c @@ -18,7 +18,7 @@ int main(int argc, char *argv[]) { if (getuid() != 0) return log_tests_skipped("not root"); r = enter_cgroup_subroot(NULL); - if (r == -ENOMEDIUM) + if (r < 0) return log_tests_skipped("cgroupfs not available"); _cleanup_free_ char *unit_dir = NULL; @@ -54,7 +54,7 @@ int main(int argc, char *argv[]) { assert_se(pid >= 0); assert_se(hashmap_isempty(m->watch_pids)); - assert_se(manager_get_unit_by_pid(m, pid) == NULL); + ASSERT_NULL(manager_get_unit_by_pid(m, pid)); assert_se(unit_watch_pid(a, pid, false) >= 0); assert_se(manager_get_unit_by_pid(m, pid) == a); @@ -93,10 +93,10 @@ int main(int argc, char *argv[]) { assert_se(manager_get_unit_by_pid(m, pid) == c); unit_unwatch_pid(c, pid); - assert_se(manager_get_unit_by_pid(m, pid) == NULL); + ASSERT_NULL(manager_get_unit_by_pid(m, pid)); unit_unwatch_pid(c, pid); - assert_se(manager_get_unit_by_pid(m, pid) == NULL); + ASSERT_NULL(manager_get_unit_by_pid(m, pid)); return 0; } diff --git a/src/test/test-xattr-util.c b/src/test/test-xattr-util.c index 85901c9..c754ac5 100644 --- a/src/test/test-xattr-util.c +++ b/src/test/test-xattr-util.c @@ -45,13 +45,13 @@ TEST(getxattr_at_malloc) { fd = open("/", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY); assert_se(fd >= 0); r = getxattr_at_malloc(fd, "usr", "user.idontexist", 0, &value); - assert_se(r < 0 && ERRNO_IS_XATTR_ABSENT(r)); + assert_se(ERRNO_IS_NEG_XATTR_ABSENT(r)); safe_close(fd); fd = open(x, O_PATH|O_CLOEXEC); assert_se(fd >= 0); assert_se(getxattr_at_malloc(fd, NULL, "user.foo", 0, &value) == 3); - assert_se(streq(value, "bar")); + ASSERT_STREQ(value, "bar"); } TEST(getcrtime) { @@ -83,7 +83,7 @@ static void verify_xattr(int dfd, const char *expected) { _cleanup_free_ char *value = NULL; assert_se(getxattr_at_malloc(dfd, "test", "user.foo", 0, &value) == (int) strlen(expected)); - assert_se(streq(value, expected)); + ASSERT_STREQ(value, expected); } TEST(xsetxattr) { @@ -99,7 +99,7 @@ TEST(xsetxattr) { /* by full path */ r = xsetxattr(AT_FDCWD, x, "user.foo", "fullpath", SIZE_MAX, 0); - if (r < 0 && ERRNO_IS_NOT_SUPPORTED(r)) + if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) return (void) log_tests_skipped_errno(r, "no xattrs supported on /var/tmp"); assert_se(r >= 0); verify_xattr(dfd, "fullpath"); diff --git a/src/test/test-xml.c b/src/test/test-xml.c index a8cb635..6367f27 100644 --- a/src/test/test-xml.c +++ b/src/test/test-xml.c @@ -29,7 +29,7 @@ static void test_one(const char *data, ...) { break; nn = va_arg(ap, const char *); - assert_se(streq_ptr(nn, name)); + ASSERT_STREQ(nn, name); } va_end(ap); diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c index 418faa5..46ec6b3 100644 --- a/src/timedate/timedatectl.c +++ b/src/timedate/timedatectl.c @@ -888,6 +888,7 @@ static int help(void) { " -p --property=NAME Show only properties by this name\n" " -a --all Show all properties, including empty ones\n" " --value When showing properties, only print the value\n" + " -P NAME Equivalent to --value --property=NAME\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -902,7 +903,6 @@ static int verb_help(int argc, char **argv, void *userdata) { } static int parse_argv(int argc, char *argv[]) { - enum { ARG_VERSION = 0x100, ARG_NO_PAGER, @@ -922,8 +922,8 @@ static int parse_argv(int argc, char *argv[]) { { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, { "monitor", no_argument, NULL, ARG_MONITOR }, { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, { "value", no_argument, NULL, ARG_VALUE }, + { "all", no_argument, NULL, 'a' }, {} }; @@ -932,8 +932,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "hH:M:p:a", options, NULL)) >= 0) - + while ((c = getopt_long(argc, argv, "hH:M:p:P:a", options, NULL)) >= 0) switch (c) { case 'h': @@ -968,26 +967,27 @@ static int parse_argv(int argc, char *argv[]) { arg_monitor = true; break; - case 'p': { + case 'p': + case 'P': r = strv_extend(&arg_property, optarg); if (r < 0) return log_oom(); - /* If the user asked for a particular - * property, show it to them, even if it is - * empty. */ + /* If the user asked for a particular property, show it to them, even if empty. */ SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - break; - } - case 'a': - SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); - break; + if (c == 'p') + break; + _fallthrough_; case ARG_VALUE: SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_ONLY_VALUE, true); break; + case 'a': + SET_FLAG(arg_print_flags, BUS_PRINT_PROPERTY_SHOW_EMPTY, true); + break; + case '?': return -EINVAL; diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c index c7be30f..e3b4367 100644 --- a/src/timedate/timedated.c +++ b/src/timedate/timedated.c @@ -22,6 +22,7 @@ #include "clock-util.h" #include "conf-files.h" #include "constants.h" +#include "daemon-util.h" #include "fd-util.h" #include "fileio-label.h" #include "fileio.h" @@ -45,6 +46,7 @@ #define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" #define UNIT_LIST_DIRS (const char* const*) CONF_PATHS_STRV("systemd/ntp-units.d") +#define SET_NTP_IN_FLIGHT_MAX 16 typedef struct UnitStatusInfo { char *name; @@ -61,6 +63,7 @@ typedef struct Context { bool local_rtc; Hashmap *polkit_registry; sd_bus_message *cache; + Set *set_ntp_calls; sd_bus_slot *slot_job_removed; @@ -119,8 +122,9 @@ static void context_clear(Context *c) { assert(c); free(c->zone); - bus_verify_polkit_async_registry_free(c->polkit_registry); + hashmap_free(c->polkit_registry); sd_bus_message_unref(c->cache); + set_free(c->set_ntp_calls); sd_bus_slot_unref(c->slot_job_removed); @@ -461,11 +465,19 @@ static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *er n += !!u->path; if (n == 0) { + sd_bus_message *cm; + c->slot_job_removed = sd_bus_slot_unref(c->slot_job_removed); (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); + while ((cm = set_steal_first(c->set_ntp_calls))) { + r = sd_bus_reply_method_return(cm, NULL); + if (r < 0) + log_debug_errno(r, "Failed to reply to SetNTP method call, ignoring: %m"); + sd_bus_message_unref(cm); + } } return 0; @@ -584,6 +596,8 @@ static int property_get_rtc_time( log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp."); else if (r == -ENOENT) log_debug("/dev/rtc not found."); + else if (r == -ENODATA) + log_debug("/dev/rtc has no valid time, power loss probably occurred?"); else if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m"); else @@ -665,13 +679,12 @@ static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error * if (streq_ptr(z, c->zone)) return sd_bus_reply_method_return(m, NULL); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_TIME, "org.freedesktop.timedate1.set-timezone", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -740,13 +753,12 @@ static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error if (lrtc == c->local_rtc && !fix_system) return sd_bus_reply_method_return(m, NULL); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_TIME, "org.freedesktop.timedate1.set-local-rtc", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -860,13 +872,12 @@ static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *erro } else timespec_store(&ts, (usec_t) utc); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_TIME, "org.freedesktop.timedate1.set-time", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -924,13 +935,12 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error if (context_ntp_service_exists(c) <= 0) return sd_bus_error_set(error, BUS_ERROR_NO_NTP_SUPPORT, "NTP not supported"); - r = bus_verify_polkit_async( + r = bus_verify_polkit_async_full( m, - CAP_SYS_TIME, "org.freedesktop.timedate1.set-ntp", - NULL, - interactive, - UID_INVALID, + /* details= */ NULL, + /* good_user= */ UID_INVALID, + interactive ? POLKIT_ALLOW_INTERACTIVE : 0, &c->polkit_registry, error); if (r < 0) @@ -942,6 +952,9 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error LIST_FOREACH(units, u, c->units) u->path = mfree(u->path); + if (set_size(c->set_ntp_calls) >= SET_NTP_IN_FLIGHT_MAX) + return sd_bus_error_set_errnof(error, EAGAIN, "Too many calls in flight."); + if (!c->slot_job_removed) { r = bus_match_signal_async( bus, @@ -996,11 +1009,12 @@ static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error c->slot_job_removed = TAKE_PTR(slot); if (selected) - log_info("Set NTP to enabled (%s).", selected->name); + log_info("Set NTP to be enabled (%s).", selected->name); else - log_info("Set NTP to disabled."); + log_info("Set NTP to be disabled."); - return sd_bus_reply_method_return(m, NULL); + /* Asynchronous reply to m in match_job_removed() */ + return set_ensure_consume(&c->set_ntp_calls, &bus_message_hash_ops, sd_bus_message_ref(m)); } static int method_list_timezones(sd_bus_message *m, void *userdata, sd_bus_error *error) { @@ -1122,21 +1136,15 @@ static int run(int argc, char *argv[]) { umask(0022); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - r = sd_event_default(&event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); (void) sd_event_set_watchdog(event, true); - r = sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to install SIGINT handler: %m"); - - r = sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + r = sd_event_set_signal_exit(event, true); if (r < 0) - return log_error_errno(r, "Failed to install SIGTERM handler: %m"); + return log_error_errno(r, "Failed to install SIGINT/SIGTERM handlers: %m"); r = connect_bus(&context, event, &bus); if (r < 0) @@ -1152,6 +1160,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = sd_notify(false, NOTIFY_READY); + if (r < 0) + log_warning_errno(r, "Failed to send readiness notification, ignoring: %m"); + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); diff --git a/src/timesync/meson.build b/src/timesync/meson.build index 6844480..d843ed7 100644 --- a/src/timesync/meson.build +++ b/src/timesync/meson.build @@ -21,8 +21,7 @@ if get_option('link-timesyncd-shared') timesyncd_link_with = [libshared] else timesyncd_link_with = [libsystemd_static, - libshared_static, - libbasic_gcrypt] + libshared_static] endif libtimesyncd_core = static_library( diff --git a/src/timesync/timesyncd-bus.c b/src/timesync/timesyncd-bus.c index 7237080..d1d2a14 100644 --- a/src/timesync/timesyncd-bus.c +++ b/src/timesync/timesyncd-bus.c @@ -67,10 +67,12 @@ static int method_set_runtime_servers(sd_bus_message *message, void *userdata, s return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid NTP server name or address, refusing: %s", *name); } - r = bus_verify_polkit_async(message, CAP_NET_ADMIN, - "org.freedesktop.timesync1.set-runtime-servers", - NULL, true, UID_INVALID, - &m->polkit_registry, error); + r = bus_verify_polkit_async( + message, + "org.freedesktop.timesync1.set-runtime-servers", + /* details= */ NULL, + &m->polkit_registry, + error); if (r < 0) return r; if (r == 0) diff --git a/src/timesync/timesyncd-conf.c b/src/timesync/timesyncd-conf.c index 9c0b6f7..4b1d4dd 100644 --- a/src/timesync/timesyncd-conf.c +++ b/src/timesync/timesyncd-conf.c @@ -102,9 +102,12 @@ int manager_parse_config_file(Manager *m) { assert(m); - r = config_parse_config_file("timesyncd.conf", "Time\0", - config_item_perf_lookup, timesyncd_gperf_lookup, - CONFIG_PARSE_WARN, m); + r = config_parse_standard_file_with_dropins( + "systemd/timesyncd.conf", + "Time\0", + config_item_perf_lookup, timesyncd_gperf_lookup, + CONFIG_PARSE_WARN, + /* userdata= */ m); if (r < 0) return r; diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c index 1998ba9..8e0eda0 100644 --- a/src/timesync/timesyncd-manager.c +++ b/src/timesync/timesyncd-manager.c @@ -956,7 +956,7 @@ Manager* manager_free(Manager *m) { sd_bus_flush_close_unref(m->bus); - bus_verify_polkit_async_registry_free(m->polkit_registry); + hashmap_free(m->polkit_registry); return mfree(m); } diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h index f444787..027ec52 100644 --- a/src/timesync/timesyncd-manager.h +++ b/src/timesync/timesyncd-manager.h @@ -45,10 +45,9 @@ struct Manager { LIST_HEAD(ServerName, runtime_servers); LIST_HEAD(ServerName, fallback_servers); - bool have_fallbacks:1; - RateLimit ratelimit; bool exhausted_servers; + bool have_fallbacks; /* network */ sd_event_source *network_event_source; diff --git a/src/timesync/timesyncd-server.h b/src/timesync/timesyncd-server.h index e22917a..a4b5637 100644 --- a/src/timesync/timesyncd-server.h +++ b/src/timesync/timesyncd-server.h @@ -30,11 +30,11 @@ struct ServerAddress { struct ServerName { Manager *manager; + bool marked; + ServerType type; char *string; - bool marked:1; - LIST_HEAD(ServerAddress, addresses); LIST_FIELDS(ServerName, names); }; diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c index 1d8ebec..5c308a0 100644 --- a/src/timesync/timesyncd.c +++ b/src/timesync/timesyncd.c @@ -174,7 +174,7 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to drop privileges: %m"); } - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGRTMIN+18) >= 0); r = manager_new(&m); if (r < 0) diff --git a/src/timesync/wait-sync.c b/src/timesync/wait-sync.c index 832e117..b6c167b 100644 --- a/src/timesync/wait-sync.c +++ b/src/timesync/wait-sync.c @@ -184,7 +184,7 @@ static int run(int argc, char * argv[]) { }; int r; - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT) >= 0); r = sd_event_default(&event); if (r < 0) diff --git a/src/tmpfiles/meson.build b/src/tmpfiles/meson.build index 8a24a21..2e91850 100644 --- a/src/tmpfiles/meson.build +++ b/src/tmpfiles/meson.build @@ -21,7 +21,6 @@ executables += [ 'c_args' : '-DSTANDALONE', 'link_with' : [ libbasic, - libbasic_gcrypt, libshared_static, libsystemd_static, ], diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 4919cb7..807925f 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -45,12 +45,10 @@ #include "log.h" #include "macro.h" #include "main-func.h" -#include "missing_stat.h" #include "missing_syscall.h" #include "mkdir-label.h" #include "mount-util.h" #include "mountpoint-util.h" -#include "nulstr-util.h" #include "offline-passwd.h" #include "pager.h" #include "parse-argument.h" @@ -64,10 +62,12 @@ #include "set.h" #include "sort-util.h" #include "specifier.h" +#include "stat-util.h" #include "stdio-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "sysctl-util.h" #include "terminal-util.h" #include "umask-util.h" #include "user-util.h" @@ -82,6 +82,7 @@ typedef enum OperationMask { OPERATION_CREATE = 1 << 0, OPERATION_REMOVE = 1 << 1, OPERATION_CLEAN = 1 << 2, + OPERATION_PURGE = 1 << 3, } OperationMask; typedef enum ItemType { @@ -197,6 +198,7 @@ typedef enum { } CreationMode; static CatFlags arg_cat_flags = CAT_CONFIG_OFF; +static bool arg_dry_run = false; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static OperationMask arg_operation = 0; static bool arg_boot = false; @@ -213,12 +215,15 @@ static ImagePolicy *arg_image_policy = NULL; #define MAX_DEPTH 256 typedef struct Context { - OrderedHashmap *items, *globs; + OrderedHashmap *items; + OrderedHashmap *globs; Set *unix_sockets; + Hashmap *uid_cache; + Hashmap *gid_cache; } Context; -STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, freep); -STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, freep); +STATIC_DESTRUCTOR_REGISTER(arg_include_prefixes, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_exclude_prefixes, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); @@ -238,6 +243,9 @@ static void context_done(Context *c) { ordered_hashmap_free(c->globs); set_free(c->unix_sockets); + + hashmap_free(c->uid_cache); + hashmap_free(c->gid_cache); } /* Different kinds of errors that mean that information is not available in the environment. */ @@ -249,7 +257,13 @@ static bool ERRNO_IS_NOINFO(int r) { ENXIO); /* env var is unset */ } -static int specifier_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) { +static int specifier_directory( + char specifier, + const void *data, + const char *root, + const void *userdata, + char **ret) { + struct table_entry { uint64_t type; const char *suffix; @@ -328,6 +342,12 @@ static int log_unresolvable_specifier(const char *filename, unsigned line) { return 0; } +#define log_action(would, doing, fmt, ...) \ + log_full(arg_dry_run ? LOG_INFO : LOG_DEBUG, \ + fmt, \ + arg_dry_run ? (would) : (doing), \ + __VA_ARGS__) + static int user_config_paths(char*** ret) { _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; _cleanup_free_ char *persistent_config = NULL, *runtime_config = NULL, *data_home = NULL; @@ -350,23 +370,19 @@ static int user_config_paths(char*** ret) { if (r < 0 && !ERRNO_IS_NOINFO(r)) return r; - r = strv_extend_strv_concat(&res, config_dirs, "/user-tmpfiles.d"); - if (r < 0) - return r; - - r = strv_extend(&res, persistent_config); + r = strv_extend_strv_concat(&res, (const char* const*) config_dirs, "/user-tmpfiles.d"); if (r < 0) return r; - r = strv_extend(&res, runtime_config); + r = strv_extend_many( + &res, + persistent_config, + runtime_config, + data_home); if (r < 0) return r; - r = strv_extend(&res, data_home); - if (r < 0) - return r; - - r = strv_extend_strv_concat(&res, data_dirs, "/user-tmpfiles.d"); + r = strv_extend_strv_concat(&res, (const char* const*) data_dirs, "/user-tmpfiles.d"); if (r < 0) return r; @@ -378,23 +394,41 @@ static int user_config_paths(char*** ret) { return 0; } +static bool needs_purge(ItemType t) { + return IN_SET(t, + COPY_FILES, + TRUNCATE_FILE, + CREATE_FILE, + WRITE_FILE, + EMPTY_DIRECTORY, + CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA, + CREATE_CHAR_DEVICE, + CREATE_BLOCK_DEVICE, + CREATE_SYMLINK, + CREATE_FIFO, + CREATE_DIRECTORY, + TRUNCATE_DIRECTORY); +} + static bool needs_glob(ItemType t) { return IN_SET(t, WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH, EMPTY_DIRECTORY, - ADJUST_MODE, - RELABEL_PATH, - RECURSIVE_RELABEL_PATH, SET_XATTR, RECURSIVE_SET_XATTR, SET_ACL, RECURSIVE_SET_ACL, SET_ATTRIBUTE, - RECURSIVE_SET_ATTRIBUTE); + RECURSIVE_SET_ATTRIBUTE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH, + RELABEL_PATH, + RECURSIVE_RELABEL_PATH, + ADJUST_MODE); } static bool takes_ownership(ItemType t) { @@ -402,7 +436,6 @@ static bool takes_ownership(ItemType t) { CREATE_FILE, TRUNCATE_FILE, CREATE_DIRECTORY, - EMPTY_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, @@ -413,6 +446,7 @@ static bool takes_ownership(ItemType t) { CREATE_BLOCK_DEVICE, COPY_FILES, WRITE_FILE, + EMPTY_DIRECTORY, IGNORE_PATH, IGNORE_DIRECTORY_PATH, REMOVE_PATH, @@ -422,17 +456,10 @@ static bool takes_ownership(ItemType t) { static struct Item* find_glob(OrderedHashmap *h, const char *match) { ItemArray *j; - ORDERED_HASHMAP_FOREACH(j, h) { - size_t n; - - for (n = 0; n < j->n_items; n++) { - Item *item = j->items + n; - + ORDERED_HASHMAP_FOREACH(j, h) + FOREACH_ARRAY(item, j->items, j->n_items) if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) return item; - } - } - return NULL; } @@ -537,16 +564,59 @@ static DIR* opendir_nomod(const char *path) { return xopendirat_nomod(AT_FDCWD, path); } -static nsec_t load_statx_timestamp_nsec(const struct statx_timestamp *ts) { - assert(ts); +static int opendir_and_stat( + const char *path, + DIR **ret, + struct statx *ret_sx, + bool *ret_mountpoint) { + + _cleanup_closedir_ DIR *d = NULL; + STRUCT_NEW_STATX_DEFINE(st1); + int r; + + assert(path); + assert(ret); + assert(ret_sx); + assert(ret_mountpoint); + + /* Do opendir() and statx() on the directory. + * Return 1 if successful, 0 if file doesn't exist or is not a directory, + * negative errno otherwise. + */ + + d = opendir_nomod(path); + if (!d) { + bool ignore = IN_SET(errno, ENOENT, ENOTDIR); + r = log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, + errno, "Failed to open directory %s: %m", path); + if (!ignore) + return r; + + *ret = NULL; + *ret_sx = (struct statx) {}; + *ret_mountpoint = NULL; + return 0; + } - if (ts->tv_sec < 0) - return NSEC_INFINITY; + r = statx_fallback(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &st1.sx); + if (r < 0) + return log_error_errno(r, "statx(%s) failed: %m", path); + + if (FLAGS_SET(st1.sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) + *ret_mountpoint = FLAGS_SET(st1.sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); + else { + STRUCT_NEW_STATX_DEFINE(st2); + + r = statx_fallback(dirfd(d), "..", 0, STATX_INO, &st2.sx); + if (r < 0) + return log_error_errno(r, "statx(%s/..) failed: %m", path); - if ((nsec_t) ts->tv_sec >= (UINT64_MAX - ts->tv_nsec) / NSEC_PER_SEC) - return NSEC_INFINITY; + *ret_mountpoint = !statx_mount_same(&st1.nsx, &st2.nsx); + } - return ts->tv_sec * NSEC_PER_SEC + ts->tv_nsec; + *ret = TAKE_PTR(d); + *ret_sx = st1.sx; + return 1; } static bool needs_cleanup( @@ -655,8 +725,8 @@ static int dir_cleanup( continue; if (r < 0) { /* FUSE, NFS mounts, SELinux might return EACCES */ - r = log_full_errno(r == -EACCES ? LOG_DEBUG : LOG_ERR, r, - "statx(%s/%s) failed: %m", p, de->d_name); + log_full_errno(r == -EACCES ? LOG_DEBUG : LOG_ERR, r, + "statx(%s/%s) failed: %m", p, de->d_name); continue; } @@ -675,9 +745,9 @@ static int dir_cleanup( continue; } - /* Try to detect bind mounts of the same filesystem instance; they do not differ in device - * major/minors. This type of query is not supported on all kernels or filesystem types - * though. */ + /* Try to detect bind mounts of the same filesystem instance; they do not differ in + * device major/minors. This type of query is not supported on all kernels or + * filesystem types though. */ if (S_ISDIR(sx.stx_mode)) { int q; @@ -691,10 +761,10 @@ static int dir_cleanup( } } - atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? load_statx_timestamp_nsec(&sx.stx_atime) : 0; - mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? load_statx_timestamp_nsec(&sx.stx_mtime) : 0; - ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? load_statx_timestamp_nsec(&sx.stx_ctime) : 0; - btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? load_statx_timestamp_nsec(&sx.stx_btime) : 0; + atime_nsec = FLAGS_SET(sx.stx_mask, STATX_ATIME) ? statx_timestamp_load_nsec(&sx.stx_atime) : 0; + mtime_nsec = FLAGS_SET(sx.stx_mask, STATX_MTIME) ? statx_timestamp_load_nsec(&sx.stx_mtime) : 0; + ctime_nsec = FLAGS_SET(sx.stx_mask, STATX_CTIME) ? statx_timestamp_load_nsec(&sx.stx_ctime) : 0; + btime_nsec = FLAGS_SET(sx.stx_mask, STATX_BTIME) ? statx_timestamp_load_nsec(&sx.stx_btime) : 0; sub_path = path_join(p, de->d_name); if (!sub_path) { @@ -736,7 +806,8 @@ static int dir_cleanup( continue; } - if (flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) { + if (!arg_dry_run && + flock(dirfd(sub_dir), LOCK_EX|LOCK_NB) < 0) { log_debug_errno(errno, "Couldn't acquire shared BSD lock on directory \"%s\", skipping: %m", sub_path); continue; } @@ -769,13 +840,16 @@ static int dir_cleanup( cutoff_nsec, sub_path, age_by_dir, true)) continue; - log_debug("Removing directory \"%s\".", sub_path); - if (unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0) - if (!IN_SET(errno, ENOENT, ENOTEMPTY)) - r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path); + log_action("Would remove", "Removing", "%s directory \"%s\"", sub_path); + if (!arg_dry_run && + unlinkat(dirfd(d), de->d_name, AT_REMOVEDIR) < 0 && + !IN_SET(errno, ENOENT, ENOTEMPTY)) + r = log_warning_errno(errno, "Failed to remove directory \"%s\", ignoring: %m", sub_path); } else { - _cleanup_close_ int fd = -EBADF; + _cleanup_close_ int fd = -EBADF; /* This file descriptor is defined here so that the + * lock that is taken below is only dropped _after_ + * the unlink operation has finished. */ /* 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. */ @@ -807,7 +881,7 @@ static int dir_cleanup( continue; } - /* Keep files on this level around if this is requested */ + /* Keep files on this level if this was requested */ if (keep_this_level) { log_debug("Keeping \"%s\".", sub_path); continue; @@ -817,78 +891,77 @@ static int dir_cleanup( cutoff_nsec, sub_path, age_by_file, false)) continue; - fd = xopenat_full(dirfd(d), - de->d_name, - O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK, - /* xopen_flags = */ 0, - /* mode = */ 0); - if (fd < 0 && !IN_SET(fd, -ENOENT, -ELOOP)) - 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", sub_path); - continue; + if (!arg_dry_run) { + fd = xopenat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME|O_NONBLOCK|O_NOCTTY); + if (fd < 0 && !IN_SET(fd, -ENOENT, -ELOOP)) + log_warning_errno(fd, "Opening file \"%s\" failed, proceeding without lock: %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", sub_path); + continue; + } } - log_debug("Removing \"%s\".", sub_path); - if (unlinkat(dirfd(d), de->d_name, 0) < 0) - if (errno != ENOENT) - r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path); + log_action("Would remove", "Removing", "%s \"%s\"", sub_path); + if (!arg_dry_run && + unlinkat(dirfd(d), de->d_name, 0) < 0 && + errno != ENOENT) + r = log_warning_errno(errno, "Failed to remove \"%s\", ignoring: %m", sub_path); deleted = true; } } finish: - if (deleted) { + if (deleted && (self_atime_nsec < NSEC_INFINITY || self_mtime_nsec < NSEC_INFINITY)) { struct timespec ts[2]; - log_debug("Restoring access and modification time on \"%s\": %s, %s", - p, - FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), - FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US)); + log_action("Would restore", "Restoring", + "%s access and modification time on \"%s\": %s, %s", + p, + FORMAT_TIMESTAMP_STYLE(self_atime_nsec / NSEC_PER_USEC, TIMESTAMP_US), + FORMAT_TIMESTAMP_STYLE(self_mtime_nsec / NSEC_PER_USEC, TIMESTAMP_US)); timespec_store_nsec(ts + 0, self_atime_nsec); timespec_store_nsec(ts + 1, self_mtime_nsec); /* Restore original directory timestamps */ - if (futimens(dirfd(d), ts) < 0) + if (!arg_dry_run && + futimens(dirfd(d), ts) < 0) log_warning_errno(errno, "Failed to revert timestamps of '%s', ignoring: %m", p); } return r; } -static bool dangerous_hardlinks(void) { - _cleanup_free_ char *value = NULL; +static bool hardlinks_protected(void) { static int cached = -1; int r; - /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off, as that's - * what the upstream default is. */ + /* Check whether the fs.protected_hardlinks sysctl is on. If we can't determine it we assume its off, + * as that's what the kernel default is. + * Note that we ship 50-default.conf where it is enabled, but better be safe than sorry. */ if (cached >= 0) return cached; - r = read_one_line_file("/proc/sys/fs/protected_hardlinks", &value); - if (r < 0) { - log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl: %m"); - return true; - } + _cleanup_free_ char *value = NULL; - r = parse_boolean(value); + r = sysctl_read("fs/protected_hardlinks", &value); if (r < 0) { - log_debug_errno(r, "Failed to parse fs.protected_hardlinks sysctl: %m"); - return true; + log_debug_errno(r, "Failed to read fs.protected_hardlinks sysctl, assuming disabled: %m"); + return false; } - cached = r == 0; - return cached; + cached = parse_boolean(value); + if (cached < 0) + log_debug_errno(cached, "Failed to parse fs.protected_hardlinks sysctl, assuming disabled: %m"); + return cached > 0; } static bool hardlink_vulnerable(const struct stat *st) { assert(st); - return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && dangerous_hardlinks(); + return !S_ISDIR(st->st_mode) && st->st_nlink > 1 && !hardlinks_protected(); } static mode_t process_mask_perms(mode_t mode, mode_t current) { @@ -965,18 +1038,23 @@ static int fd_set_perms( if (((m ^ st->st_mode) & 07777) == 0) log_debug("\"%s\" matches temporary mode %o already.", path, m); else { - log_debug("Temporarily changing \"%s\" to mode %o.", path, m); - r = fchmod_opath(fd, m); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); + log_action("Would temporarily change", "Temporarily changing", + "%s \"%s\" to mode %o", path, m); + if (!arg_dry_run) { + r = fchmod_opath(fd, m); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); + } } } } if (do_chown) { - log_debug("Changing \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); + log_action("Would change", "Changing", + "%s \"%s\" to owner "UID_FMT":"GID_FMT, path, new_uid, new_gid); - if (fchownat(fd, "", + if (!arg_dry_run && + fchownat(fd, "", new_uid != st->st_uid ? new_uid : UID_INVALID, new_gid != st->st_gid ? new_gid : GID_INVALID, AT_EMPTY_PATH) < 0) @@ -989,10 +1067,12 @@ static int fd_set_perms( if (S_ISLNK(st->st_mode)) log_debug("Skipping mode fix for symlink %s.", path); else { - log_debug("Changing \"%s\" to mode %o.", path, new_mode); - r = fchmod_opath(fd, new_mode); - if (r < 0) - return log_error_errno(r, "fchmod() of %s failed: %m", path); + log_action("Would change", "Changing", "%s \"%s\" to mode %o", path, new_mode); + if (!arg_dry_run) { + r = fchmod_opath(fd, new_mode); + if (r < 0) + return log_error_errno(r, "fchmod() of %s failed: %m", path); + } } } @@ -1122,8 +1202,11 @@ static int fd_set_xattrs( assert(path); STRV_FOREACH_PAIR(name, value, i->xattrs) { - log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); - if (setxattr(FORMAT_PROC_FD_PATH(fd), *name, *value, strlen(*value), 0) < 0) + log_action("Would set", "Setting", + "%s extended attribute '%s=%s' on %s", *name, *value, path); + + if (!arg_dry_run && + setxattr(FORMAT_PROC_FD_PATH(fd), *name, *value, strlen(*value), 0) < 0) return log_error_errno(errno, "Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); } @@ -1327,12 +1410,13 @@ static int path_set_acl( return r; t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); - log_debug("Setting %s ACL %s on %s.", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); + log_action("Would set", "Setting", + "%s %s ACL %s on %s", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); - r = acl_set_file(path, type, dup); - if (r < 0) { + if (!arg_dry_run && + acl_set_file(path, type, dup) < 0) { if (ERRNO_IS_NOT_SUPPORTED(errno)) /* No error if filesystem doesn't support ACLs. Return negative. */ return -errno; @@ -1440,7 +1524,6 @@ static int path_set_acls( } static int parse_attribute_from_arg(Item *item) { - static const struct { char character; unsigned value; @@ -1501,8 +1584,7 @@ static int parse_attribute_from_arg(Item *item) { if (i >= ELEMENTSOF(attributes)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "Unknown file attribute '%c' on '%s'.", - *p, item->path); + "Unknown file attribute '%c' on '%s'.", *p, item->path); v = attributes[i].value; @@ -1531,7 +1613,6 @@ static int fd_set_attribute( const struct stat *st, CreationMode creation) { - _cleanup_close_ int procfs_fd = -EBADF; struct stat stbuf; unsigned f; int r; @@ -1563,20 +1644,29 @@ static int fd_set_attribute( if (!S_ISDIR(st->st_mode)) f &= ~FS_DIRSYNC_FL; - procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME); - if (procfs_fd < 0) - return log_error_errno(procfs_fd, "Failed to re-open '%s': %m", path); - - unsigned previous, current; - r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); - if (r == -ENOANO) - log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, " - "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", - path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask)); - else if (r < 0) - log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, - "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", - path, item->attribute_value, item->attribute_mask); + log_action("Would try to set", "Trying to set", + "%s file attributes 0x%08x on %s", + f & item->attribute_mask, + path); + + if (!arg_dry_run) { + _cleanup_close_ int procfs_fd = -EBADF; + + procfs_fd = fd_reopen(fd, O_RDONLY|O_CLOEXEC|O_NOATIME); + if (procfs_fd < 0) + return log_error_errno(procfs_fd, "Failed to reopen '%s': %m", path); + + unsigned previous, current; + r = chattr_full(procfs_fd, NULL, f, item->attribute_mask, &previous, ¤t, CHATTR_FALLBACK_BITWISE); + if (r == -ENOANO) + log_warning("Cannot set file attributes for '%s', maybe due to incompatibility in specified attributes, " + "previous=0x%08x, current=0x%08x, expected=0x%08x, ignoring.", + path, previous, current, (previous & ~item->attribute_mask) | (f & item->attribute_mask)); + else if (r < 0) + log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING, r, + "Cannot set file attributes for '%s', value=0x%08x, mask=0x%08x, ignoring: %m", + path, item->attribute_value, item->attribute_mask); + } return 0; } @@ -1614,11 +1704,13 @@ static int write_argument_data(Item *i, int fd, const char *path) { assert(item_binary_argument(i)); - log_debug("Writing to \"%s\".", path); + log_action("Would write", "Writing", "%s to \"%s\"", path); - r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i)); - if (r < 0) - return log_error_errno(r, "Failed to write file \"%s\": %m", path); + if (!arg_dry_run) { + r = loop_write(fd, item_binary_argument(i), item_binary_argument_size(i)); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); + } return 0; } @@ -1645,10 +1737,9 @@ static int write_one_file(Context *c, Item *i, const char *path, CreationMode cr if (dir_fd < 0) return dir_fd; - /* Follows symlinks */ - fd = openat(dir_fd, bn, - O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY|(i->append_or_force ? O_APPEND : 0), - i->mode); + /* Follow symlinks. Open with O_PATH in dry-run mode to make sure we don't use the path inadvertently. */ + int flags = O_NONBLOCK | O_CLOEXEC | O_WRONLY | O_NOCTTY | i->append_or_force * O_APPEND | arg_dry_run * O_PATH; + fd = openat(dir_fd, bn, flags, i->mode); if (fd < 0) { if (errno == ENOENT) { log_debug_errno(errno, "Not writing missing file \"%s\": %m", path); @@ -1694,6 +1785,14 @@ static int create_file( if (r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for writing, is a directory.", path); + if (arg_dry_run) { + log_info("Would create file %s", path); + return 0; + + /* The opening of the directory below would fail if it doesn't exist, + * so log and exit before even trying to do that. */ + } + /* Validate the path and keep the fd on the directory for opening the file so we're sure that it * can't be changed behind our back. */ dir_fd = path_open_parent_safe(path, i->allow_failure); @@ -1717,7 +1816,7 @@ static int create_file( * fd_set_perms() report the error if the perms need to be modified. */ fd = openat(dir_fd, bn, O_NOFOLLOW|O_CLOEXEC|O_PATH, i->mode); if (fd < 0) - return log_error_errno(errno, "Failed to re-open file %s: %m", path); + return log_error_errno(errno, "Failed to reopen file %s: %m", path); if (fstat(fd, &stbuf) < 0) return log_error_errno(errno, "stat(%s) failed: %m", path); @@ -1773,6 +1872,11 @@ static int truncate_file( if (dir_fd < 0) return dir_fd; + if (arg_dry_run) { + log_info("Would truncate %s", path); + return 0; + } + creation = CREATION_EXISTING; fd = RET_NERRNO(openat(dir_fd, bn, O_NOFOLLOW|O_NONBLOCK|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode)); if (fd == -ENOENT) { @@ -1800,7 +1904,7 @@ static int truncate_file( "Cannot create file %s on a read-only file system.", path); - return log_error_errno(errno, "Failed to re-open file %s: %m", path); + return log_error_errno(errno, "Failed to reopen file %s: %m", path); } erofs = true; @@ -1840,7 +1944,9 @@ static int copy_files(Context *c, Item *i) { struct stat st, a; int r; - log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); + log_action("Would copy", "Copying", "%s tree \"%s\" to \"%s\"", i->argument, i->path); + if (arg_dry_run) + return 0; r = path_extract_filename(i->path, &bn); if (r < 0) @@ -1919,18 +2025,27 @@ static int create_directory_or_subvolume( * heavy-weight). Thus, chroot() environments and suchlike will get a full brtfs * subvolume set up below their tree only if they specifically set up a btrfs * subvolume for the root dir too. */ - subvol = false; else { - WITH_UMASK((~mode) & 0777) - r = btrfs_subvol_make(pfd, bn); + log_action("Would create", "Creating", "%s btrfs subvolume %s", path); + if (!arg_dry_run) + WITH_UMASK((~mode) & 0777) + r = btrfs_subvol_make(pfd, bn); + else + r = 0; } } else r = 0; - if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) - WITH_UMASK(0000) - r = mkdirat_label(pfd, bn, mode); + if (!subvol || ERRNO_IS_NEG_NOT_SUPPORTED(r)) { + log_action("Would create", "Creating", "%s directory \"%s\"", path); + if (!arg_dry_run) + WITH_UMASK(0000) + r = mkdirat_label(pfd, bn, mode); + } + + if (arg_dry_run) + return 0; creation = r >= 0 ? CREATION_NORMAL : CREATION_EXISTING; @@ -1979,6 +2094,11 @@ static int create_directory( assert(i); assert(IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY)); + if (arg_dry_run) { + log_info("Would create directory %s", path); + return 0; + } + fd = create_directory_or_subvolume(path, i->mode, /* subvol= */ false, i->allow_failure, &st, &creation); if (fd == -EEXIST) return 0; @@ -2002,6 +2122,11 @@ static int create_subvolume( assert(i); assert(IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)); + if (arg_dry_run) { + log_info("Would create subvolume %s", path); + return 0; + } + fd = create_directory_or_subvolume(path, i->mode, /* subvol = */ true, i->allow_failure, &st, &creation); if (fd == -EEXIST) return 0; @@ -2088,7 +2213,13 @@ static int create_device( if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating device node, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating device node, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create device node %s", i->path); + return 0; + } /* Validate the path and use the returned directory fd for copying the target so we're sure that the * path can't be changed behind our back. */ @@ -2155,7 +2286,8 @@ static int create_device( return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); if (((st.st_mode ^ file_type) & S_IFMT) != 0) - return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Device node we just created is not a device node, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "Device node we just created is not a device node, refusing."); creation = CREATION_FORCE; } else { @@ -2193,7 +2325,13 @@ static int create_fifo(Context *c, Item *i) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create fifo %s", i->path); + return 0; + } pfd = path_open_parent_safe(i->path, i->allow_failure); if (pfd < 0) @@ -2250,7 +2388,8 @@ static int create_fifo(Context *c, Item *i) { return log_error_errno(errno, "Failed to fstat(%s): %m", i->path); if (!S_ISFIFO(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(EBADF), "FIFO inode we just created is not a FIFO, refusing."); + return log_error_errno(SYNTHETIC_ERRNO(EBADF), + "FIFO inode we just created is not a FIFO, refusing."); creation = CREATION_FORCE; } else { @@ -2279,7 +2418,13 @@ static int create_symlink(Context *c, Item *i) { if (r < 0) return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path); if (r == O_DIRECTORY) - return log_error_errno(SYNTHETIC_ERRNO(EISDIR), "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + return log_error_errno(SYNTHETIC_ERRNO(EISDIR), + "Cannot open path '%s' for creating FIFO, is a directory.", i->path); + + if (arg_dry_run) { + log_info("Would create symlink %s -> %s", i->path, i->argument); + return 0; + } pfd = path_open_parent_safe(i->path, i->allow_failure); if (pfd < 0) @@ -2366,12 +2511,13 @@ static int item_do( fdaction_t action) { struct stat st; - int r = 0, q; + int r; assert(c); assert(i); - assert(path); assert(fd >= 0); + assert(path); + assert(action); if (fstat(fd, &st) < 0) { r = log_error_errno(errno, "fstat() on file failed: %m"); @@ -2388,36 +2534,35 @@ static int item_do( * reading the directory content. */ d = opendir(FORMAT_PROC_FD_PATH(fd)); if (!d) { - log_error_errno(errno, "Failed to opendir() '%s': %m", FORMAT_PROC_FD_PATH(fd)); - if (r == 0) - r = -errno; + RET_GATHER(r, log_error_errno(errno, "Failed to opendir() '%s': %m", FORMAT_PROC_FD_PATH(fd))); goto finish; } - FOREACH_DIRENT_ALL(de, d, q = -errno; goto finish) { - int de_fd; + FOREACH_DIRENT_ALL(de, d, RET_GATHER(r, -errno); goto finish) { + _cleanup_close_ int de_fd = -EBADF; + _cleanup_free_ char *de_path = NULL; if (dot_or_dot_dot(de->d_name)) continue; de_fd = openat(fd, de->d_name, O_NOFOLLOW|O_CLOEXEC|O_PATH); - if (de_fd < 0) - q = log_error_errno(errno, "Failed to open() file '%s': %m", de->d_name); - else { - _cleanup_free_ char *de_path = NULL; - - de_path = path_join(path, de->d_name); - if (!de_path) - q = log_oom(); - else - /* Pass ownership of dirent fd over */ - q = item_do(c, i, de_fd, de_path, CREATION_EXISTING, action); + if (de_fd < 0) { + if (errno != ENOENT) + RET_GATHER(r, log_error_errno(errno, "Failed to open file '%s': %m", de->d_name)); + continue; + } + + de_path = path_join(path, de->d_name); + if (!de_path) { + r = log_oom(); + goto finish; } - if (q < 0 && r == 0) - r = q; + /* Pass ownership of dirent fd over */ + RET_GATHER(r, item_do(c, i, TAKE_FD(de_fd), de_path, CREATION_EXISTING, action)); } } + finish: safe_close(fd); return r; @@ -2427,21 +2572,22 @@ static int glob_item(Context *c, Item *i, action_t action) { _cleanup_globfree_ glob_t g = { .gl_opendir = (void *(*)(const char *)) opendir_nomod, }; - int r = 0, k; + int r; assert(c); assert(i); + assert(action); - k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); - if (k < 0 && k != -ENOENT) - return log_error_errno(k, "glob(%s) failed: %m", i->path); + r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to glob '%s': %m", i->path); - STRV_FOREACH(fn, g.gl_pathv) { + r = 0; + STRV_FOREACH(fn, g.gl_pathv) /* We pass CREATION_EXISTING here, since if we are globbing for it, it always has to exist */ - k = action(c, i, *fn, CREATION_EXISTING); - if (k < 0 && r == 0) - r = k; - } + RET_GATHER(r, action(c, i, *fn, CREATION_EXISTING)); return r; } @@ -2454,34 +2600,33 @@ static int glob_item_recursively( _cleanup_globfree_ glob_t g = { .gl_opendir = (void *(*)(const char *)) opendir_nomod, }; - int r = 0, k; + int r; - k = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); - if (k < 0 && k != -ENOENT) - return log_error_errno(k, "glob(%s) failed: %m", i->path); + assert(c); + assert(i); + assert(action); + r = safe_glob(i->path, GLOB_NOSORT|GLOB_BRACE, &g); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to glob '%s': %m", i->path); + + r = 0; STRV_FOREACH(fn, g.gl_pathv) { _cleanup_close_ int fd = -EBADF; - /* Make sure we won't trigger/follow file object (such as - * device nodes, automounts, ...) pointed out by 'fn' with - * O_PATH. Note, when O_PATH is used, flags other than + /* Make sure we won't trigger/follow file object (such as device nodes, automounts, ...) + * pointed out by 'fn' with O_PATH. Note, when O_PATH is used, flags other than * O_CLOEXEC, O_DIRECTORY, and O_NOFOLLOW are ignored. */ fd = open(*fn, O_CLOEXEC|O_NOFOLLOW|O_PATH); if (fd < 0) { - log_error_errno(errno, "Opening '%s' failed: %m", *fn); - if (r == 0) - r = -errno; + RET_GATHER(r, log_error_errno(errno, "Failed to open '%s': %m", *fn)); continue; } - k = item_do(c, i, fd, *fn, CREATION_EXISTING, action); - if (k < 0 && r == 0) - r = k; - - /* we passed fd ownership to the previous call */ - fd = -EBADF; + RET_GATHER(r, item_do(c, i, TAKE_FD(fd), *fn, CREATION_EXISTING, action)); } return r; @@ -2503,65 +2648,71 @@ static int rm_if_wrong_type_safe( assert(!follow_links || parent_st); assert((flags & ~AT_SYMLINK_NOFOLLOW) == 0); + if (mode == 0) + return 0; + if (!filename_is_valid(name)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "\"%s\" is not a valid filename.", name); r = fstatat_harder(parent_fd, name, &st, flags, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); if (r < 0) { (void) fd_get_path(parent_fd, &parent_name); - return log_full_errno(r == -ENOENT? LOG_DEBUG : LOG_ERR, r, - "Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name)); + return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_ERR, r, + "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name); } /* Fail before removing anything if this is an unsafe transition. */ if (follow_links && unsafe_transition(parent_st, &st)) { (void) fd_get_path(parent_fd, &parent_name); return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), - "Unsafe transition from \"%s\" to \"%s\".", parent_name, name); + "Unsafe transition from \"%s\" to \"%s\".", parent_name ?: "...", name); } if ((st.st_mode & S_IFMT) == mode) return 0; (void) fd_get_path(parent_fd, &parent_name); - log_notice("Wrong file type 0o%o; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, strna(parent_name), name); + log_notice("Wrong file type 0o%o; rm -rf \"%s/%s\"", st.st_mode & S_IFMT, parent_name ?: "...", name); /* If the target of the symlink was the wrong type, the link needs to be removed instead of the * target, so make sure it is identified as a link and not a directory. */ if (follow_links) { r = fstatat_harder(parent_fd, name, &st, AT_SYMLINK_NOFOLLOW, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); if (r < 0) - return log_error_errno(r, "Failed to stat \"%s\" at \"%s\": %m", name, strna(parent_name)); + return log_error_errno(r, "Failed to stat \"%s/%s\": %m", parent_name ?: "...", name); } /* Do not remove mount points. */ r = fd_is_mount_point(parent_fd, name, follow_links ? AT_SYMLINK_FOLLOW : 0); if (r < 0) - (void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; Continuing", - strna(parent_name), name); + (void) log_warning_errno(r, "Failed to check if \"%s/%s\" is a mount point: %m; continuing.", + parent_name ?: "...", name); else if (r > 0) return log_error_errno(SYNTHETIC_ERRNO(EBUSY), - "Not removing \"%s/%s\" because it is a mount point.", strna(parent_name), name); + "Not removing \"%s/%s\" because it is a mount point.", parent_name ?: "...", name); - if ((st.st_mode & S_IFMT) == S_IFDIR) { - _cleanup_close_ int child_fd = -EBADF; + log_action("Would remove", "Removing", "%s %s/%s", parent_name ?: "...", name); + if (!arg_dry_run) { + if ((st.st_mode & S_IFMT) == S_IFDIR) { + _cleanup_close_ int child_fd = -EBADF; - child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY); - if (child_fd < 0) - return log_error_errno(errno, "Failed to open \"%s\" at \"%s\": %m", name, strna(parent_name)); + child_fd = openat(parent_fd, name, O_NOCTTY | O_CLOEXEC | O_DIRECTORY); + if (child_fd < 0) + return log_error_errno(errno, "Failed to open \"%s/%s\": %m", parent_name ?: "...", name); - r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st); - if (r < 0) - return log_error_errno(r, "Failed to remove contents of \"%s\" at \"%s\": %m", name, strna(parent_name)); + r = rm_rf_children(TAKE_FD(child_fd), REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL, &st); + if (r < 0) + return log_error_errno(r, "Failed to remove contents of \"%s/%s\": %m", parent_name ?: "...", name); - r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); - } else - r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); - if (r < 0) - return log_error_errno(r, "Failed to remove \"%s\" at \"%s\": %m", name, strna(parent_name)); + r = unlinkat_harder(parent_fd, name, AT_REMOVEDIR, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); + } else + r = unlinkat_harder(parent_fd, name, 0, REMOVE_CHMOD | REMOVE_CHMOD_RESTORE); + if (r < 0) + return log_error_errno(r, "Failed to remove \"%s/%s\": %m", parent_name ?: "...", name); + } - /* This is covered by the log_notice "Wrong file type..." It is logged earlier because it gives - * context to other error messages that might follow. */ + /* This is covered by the log_notice "Wrong file type...". + * It is logged earlier because it gives context to other error messages that might follow. */ return -ENOENT; } @@ -2579,7 +2730,7 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { if (!is_path(path)) /* rm_if_wrong_type_safe already logs errors. */ - return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW) : 0; + return rm_if_wrong_type_safe(child_mode, AT_FDCWD, NULL, path, AT_SYMLINK_NOFOLLOW); if (child_mode != 0 && endswith(path, "/")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2609,20 +2760,22 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { /* Is this the last component? If so, then check the type */ if (*e == 0) - return child_mode != 0 ? rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW) : 0; + return rm_if_wrong_type_safe(child_mode, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW); r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, 0); /* Remove dangling symlinks. */ if (r == -ENOENT) r = rm_if_wrong_type_safe(S_IFDIR, parent_fd, &parent_st, t, AT_SYMLINK_NOFOLLOW); if (r == -ENOENT) { - WITH_UMASK(0000) - r = mkdirat_label(parent_fd, t, 0755); - if (r < 0) { - _cleanup_free_ char *parent_name = NULL; - - (void) fd_get_path(parent_fd, &parent_name); - return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, strnull(parent_name)); + if (!arg_dry_run) { + WITH_UMASK(0000) + r = mkdirat_label(parent_fd, t, 0755); + if (r < 0) { + _cleanup_free_ char *parent_name = NULL; + + (void) fd_get_path(parent_fd, &parent_name); + return log_error_errno(r, "Failed to mkdir \"%s\" at \"%s\": %m", t, strnull(parent_name)); + } } } else if (r < 0) /* rm_if_wrong_type_safe already logs errors. */ @@ -2649,13 +2802,15 @@ static int mkdir_parents_rm_if_wrong_type(mode_t child_mode, const char *path) { static int mkdir_parents_item(Item *i, mode_t child_mode) { int r; + if (i->try_replace) { r = mkdir_parents_rm_if_wrong_type(child_mode, i->path); if (r < 0 && r != -ENOENT) return r; } else WITH_UMASK(0000) - (void) mkdir_parents_label(i->path, 0755); + if (!arg_dry_run) + (void) mkdir_parents_label(i->path, 0755); return 0; } @@ -2830,44 +2985,100 @@ static int create_item(Context *c, Item *i) { return 0; } -static int remove_item_instance( +static int remove_recursive( Context *c, Item *i, const char *instance, - CreationMode creation) { + bool remove_instance) { + _cleanup_closedir_ DIR *d = NULL; + STRUCT_STATX_DEFINE(sx); + bool mountpoint; int r; + r = opendir_and_stat(instance, &d, &sx, &mountpoint); + if (r < 0) + return r; + if (r == 0) { + if (remove_instance) { + log_action("Would remove", "Removing", "%s file \"%s\".", instance); + if (!arg_dry_run && + remove(instance) < 0 && + errno != ENOENT) + return log_error_errno(errno, "rm %s: %m", instance); + } + return 0; + } + + r = dir_cleanup(c, i, instance, d, + /* self_atime_nsec= */ NSEC_INFINITY, + /* self_mtime_nsec= */ NSEC_INFINITY, + /* cutoff_nsec= */ NSEC_INFINITY, + sx.stx_dev_major, sx.stx_dev_minor, + mountpoint, + MAX_DEPTH, + /* keep_this_level= */ false, + /* age_by_file= */ 0, + /* age_by_dir= */ 0); + if (r < 0) + return r; + + if (remove_instance) { + log_debug("Removing directory \"%s\".", instance); + r = RET_NERRNO(rmdir(instance)); + if (r < 0 && !IN_SET(r, -ENOENT, -ENOTEMPTY)) + return log_error_errno(r, "Failed to remove %s: %m", instance); + } + return 0; +} + +static int purge_item_instance(Context *c, Item *i, const char *instance, CreationMode creation) { + return remove_recursive(c, i, instance, /* remove_instance= */ true); +} + +static int purge_item(Context *c, Item *i) { + assert(i); + + if (!needs_purge(i->type)) + return 0; + + log_debug("Running purge action for entry %c %s", (char) i->type, i->path); + + if (needs_glob(i->type)) + return glob_item(c, i, purge_item_instance); + + return purge_item_instance(c, i, i->path, CREATION_EXISTING); +} + +static int remove_item_instance( + Context *c, + Item *i, + const char *instance, + CreationMode creation) { + assert(c); assert(i); switch (i->type) { case REMOVE_PATH: - if (remove(instance) < 0 && errno != ENOENT) - return log_error_errno(errno, "rm(%s): %m", instance); + log_action("Would remove", "Removing", "%s \"%s\".", instance); + if (!arg_dry_run && + remove(instance) < 0 && + errno != ENOENT) + return log_error_errno(errno, "rm %s: %m", instance); - break; + return 0; case RECURSIVE_REMOVE_PATH: - /* FIXME: we probably should use dir_cleanup() here instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", instance); - r = rm_rf(instance, REMOVE_ROOT|REMOVE_SUBVOLUME|REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", instance); - - break; + return remove_recursive(c, i, instance, /* remove_instance= */ true); default: assert_not_reached(); } - - return 0; } static int remove_item(Context *c, Item *i) { - int r; - assert(c); assert(i); @@ -2876,13 +3087,7 @@ static int remove_item(Context *c, Item *i) { switch (i->type) { case TRUNCATE_DIRECTORY: - /* FIXME: we probably should use dir_cleanup() here instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", i->path); - r = rm_rf(i->path, REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", i->path); - - return 0; + return remove_recursive(c, i, i->path, /* remove_instance= */ false); case REMOVE_PATH: case RECURSIVE_REMOVE_PATH: @@ -2916,49 +3121,25 @@ static int clean_item_instance( const char* instance, CreationMode creation) { - _cleanup_closedir_ DIR *d = NULL; - STRUCT_STATX_DEFINE(sx); - int mountpoint, r; - usec_t cutoff, n; - assert(i); if (!i->age_set) return 0; - n = now(CLOCK_REALTIME); + usec_t n = now(CLOCK_REALTIME); if (n < i->age) return 0; - cutoff = n - i->age; - - d = opendir_nomod(instance); - if (!d) { - if (IN_SET(errno, ENOENT, ENOTDIR)) { - log_debug_errno(errno, "Directory \"%s\": %m", instance); - return 0; - } - - return log_error_errno(errno, "Failed to open directory %s: %m", instance); - } + usec_t cutoff = n - i->age; - r = statx_fallback(dirfd(d), "", AT_EMPTY_PATH, STATX_MODE|STATX_INO|STATX_ATIME|STATX_MTIME, &sx); - if (r < 0) - return log_error_errno(r, "statx(%s) failed: %m", instance); - - if (FLAGS_SET(sx.stx_attributes_mask, STATX_ATTR_MOUNT_ROOT)) - mountpoint = FLAGS_SET(sx.stx_attributes, STATX_ATTR_MOUNT_ROOT); - else { - struct stat ps; - - if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) - return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); + _cleanup_closedir_ DIR *d = NULL; + STRUCT_STATX_DEFINE(sx); + bool mountpoint; + int r; - mountpoint = - sx.stx_dev_major != major(ps.st_dev) || - sx.stx_dev_minor != minor(ps.st_dev) || - sx.stx_ino != ps.st_ino; - } + r = opendir_and_stat(instance, &d, &sx, &mountpoint); + if (r <= 0) + return r; if (DEBUG_LOGGING) { _cleanup_free_ char *ab_f = NULL, *ab_d = NULL; @@ -2979,10 +3160,11 @@ static int clean_item_instance( } return dir_cleanup(c, i, instance, d, - load_statx_timestamp_nsec(&sx.stx_atime), - load_statx_timestamp_nsec(&sx.stx_mtime), + statx_timestamp_load_nsec(&sx.stx_atime), + statx_timestamp_load_nsec(&sx.stx_mtime), cutoff * NSEC_PER_USEC, - sx.stx_dev_major, sx.stx_dev_minor, mountpoint, + sx.stx_dev_major, sx.stx_dev_minor, + mountpoint, MAX_DEPTH, i->keep_first_level, i->age_by_file, i->age_by_dir); } @@ -2996,16 +3178,16 @@ static int clean_item(Context *c, Item *i) { switch (i->type) { case CREATE_DIRECTORY: + case TRUNCATE_DIRECTORY: case CREATE_SUBVOLUME: case CREATE_SUBVOLUME_INHERIT_QUOTA: case CREATE_SUBVOLUME_NEW_QUOTA: - case TRUNCATE_DIRECTORY: - case IGNORE_PATH: case COPY_FILES: clean_item_instance(c, i, i->path, CREATION_EXISTING); return 0; case EMPTY_DIRECTORY: + case IGNORE_PATH: case IGNORE_DIRECTORY_PATH: return glob_item(c, i, clean_item_instance); @@ -3022,7 +3204,7 @@ static int process_item( OperationMask todo; _cleanup_free_ char *_path = NULL; const char *path; - int r, q, p; + int r; assert(c); assert(i); @@ -3058,12 +3240,11 @@ static int process_item( if (i->allow_failure) r = 0; - q = FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0; - p = FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0; + RET_GATHER(r, FLAGS_SET(operation, OPERATION_REMOVE) ? remove_item(c, i) : 0); + RET_GATHER(r, FLAGS_SET(operation, OPERATION_CLEAN) ? clean_item(c, i) : 0); + RET_GATHER(r, FLAGS_SET(operation, OPERATION_PURGE) ? purge_item(c, i) : 0); - return r < 0 ? r : - q < 0 ? q : - p; + return r; } static int process_item_array( @@ -3072,7 +3253,6 @@ static int process_item_array( OperationMask operation) { int r = 0; - size_t n; assert(c); assert(array); @@ -3082,25 +3262,15 @@ static int process_item_array( r = process_item_array(c, array->parent, operation & OPERATION_CREATE); /* Clean up all children first */ - if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN)) && !set_isempty(array->children)) { + if ((operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE)) && !set_isempty(array->children)) { ItemArray *cc; - SET_FOREACH(cc, array->children) { - int k; - - k = process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN)); - if (k < 0 && r == 0) - r = k; - } + SET_FOREACH(cc, array->children) + RET_GATHER(r, process_item_array(c, cc, operation & (OPERATION_REMOVE|OPERATION_CLEAN|OPERATION_PURGE))); } - for (n = 0; n < array->n_items; n++) { - int k; - - k = process_item(c, array->items + n, operation); - if (k < 0 && r == 0) - r = k; - } + FOREACH_ARRAY(item, array->items, array->n_items) + RET_GATHER(r, process_item(c, item, operation)); return r; } @@ -3125,13 +3295,11 @@ static void item_free_contents(Item *i) { } static ItemArray* item_array_free(ItemArray *a) { - size_t n; - if (!a) return NULL; - for (n = 0; n < a->n_items; n++) - item_free_contents(a->items + n); + FOREACH_ARRAY(item, a->items, a->n_items) + item_free_contents(item); set_free(a->children); free(a->items); @@ -3261,27 +3429,30 @@ static int patch_var_run(const char *fname, unsigned line, char **path) { assert(path); assert(*path); - /* Optionally rewrites lines referencing /var/run/, to use /run/ instead. Why bother? tmpfiles merges lines in - * some cases and detects conflicts in others. If files/directories are specified through two equivalent lines - * this is problematic as neither case will be detected. Ideally we'd detect these cases by resolving symlinks - * early, but that's precisely not what we can do here as this code very likely is running very early on, at a - * time where the paths in question are not available yet, or even more importantly, our own tmpfiles rules - * might create the paths that are intermediary to the listed paths. We can't really cover the generic case, - * but the least we can do is cover the specific case of /var/run vs. /run, as /var/run is a legacy name for - * /run only, and we explicitly document that and require that on systemd systems the former is a symlink to - * the latter. Moreover files below this path are by far the primary use case for tmpfiles.d/. */ + /* Optionally rewrites lines referencing /var/run/, to use /run/ instead. Why bother? tmpfiles merges + * lines in some cases and detects conflicts in others. If files/directories are specified through + * two equivalent lines this is problematic as neither case will be detected. Ideally we'd detect + * these cases by resolving symlinks early, but that's precisely not what we can do here as this code + * very likely is running very early on, at a time where the paths in question are not available yet, + * or even more importantly, our own tmpfiles rules might create the paths that are intermediary to + * the listed paths. We can't really cover the generic case, but the least we can do is cover the + * specific case of /var/run vs. /run, as /var/run is a legacy name for /run only, and we explicitly + * document that and require that on systemd systems the former is a symlink to the latter. Moreover + * files below this path are by far the primary use case for tmpfiles.d/. */ k = path_startswith(*path, "/var/run/"); - if (isempty(k)) /* Don't complain about other paths than /var/run, and not about /var/run itself either. */ + if (isempty(k)) /* Don't complain about paths other than under /var/run, + * and not about /var/run itself either. */ return 0; n = path_join("/run", k); if (!n) return log_oom(); - /* Also log about this briefly. We do so at LOG_NOTICE level, as we fixed up the situation automatically, hence - * there's no immediate need for action by the user. However, in the interest of making things less confusing - * to the user, let's still inform the user that these snippets should really be updated. */ + /* Also log about this briefly. We do so at LOG_NOTICE level, as we fixed up the situation + * automatically, hence there's no immediate need for action by the user. However, in the interest of + * making things less confusing to the user, let's still inform the user that these snippets should + * really be updated. */ log_syntax(NULL, LOG_NOTICE, fname, line, 0, "Line references path below legacy directory /var/run/, updating %s → %s; please update the tmpfiles.d/ drop-in file accordingly.", *path, n); @@ -3394,13 +3565,10 @@ static int parse_age_by_from_arg(const char *age_by_str, Item *item) { } static bool is_duplicated_item(ItemArray *existing, const Item *i) { - assert(existing); assert(i); - for (size_t n = 0; n < existing->n_items; n++) { - const Item *e = existing->items + n; - + FOREACH_ARRAY(e, existing->items, existing->n_items) { if (item_compatible(e, i)) continue; @@ -3414,14 +3582,13 @@ static bool is_duplicated_item(ItemArray *existing, const Item *i) { } static int parse_line( - Context *c, const char *fname, unsigned line, const char *buffer, bool *invalid_config, - Hashmap **uid_cache, - Hashmap **gid_cache) { + void *context) { + Context *c = ASSERT_PTR(context); _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; _cleanup_(item_free_contents) Item i = { /* The "age-by" argument considers all file timestamp types by default. */ @@ -3430,11 +3597,10 @@ static int parse_line( }; ItemArray *existing; OrderedHashmap *h; - int r, pos; bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false, from_cred = false, missing_user_or_group = false; + int r; - assert(c); assert(fname); assert(line >= 1); assert(buffer); @@ -3472,8 +3638,7 @@ static int parse_line( &mode, &user, &group, - &age, - NULL); + &age); if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) /* invalid quoting and such or an unknown specifier */ @@ -3492,10 +3657,11 @@ static int parse_line( if (isempty(action)) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Command too short '%s'.", action); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Command too short '%s'.", action); } - for (pos = 1; action[pos]; pos++) { + for (int pos = 1; action[pos]; pos++) if (action[pos] == '!' && !boot) boot = true; else if (action[pos] == '+' && !append_or_force) @@ -3510,12 +3676,13 @@ static int parse_line( from_cred = true; else { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Unknown modifiers in command '%s'", action); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Unknown modifiers in command '%s'.", action); } - } if (boot && !arg_boot) { - log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Ignoring entry %s \"%s\" because --boot is not specified.", action, path); + log_syntax(NULL, LOG_DEBUG, fname, line, 0, + "Ignoring entry %s \"%s\" because --boot is not specified.", action, path); return 0; } @@ -3530,7 +3697,8 @@ static int parse_line( if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to replace specifiers in '%s': %m", path); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Failed to replace specifiers in '%s': %m", path); } r = patch_var_run(fname, line, &i.path); @@ -3562,14 +3730,8 @@ static int parse_line( case RELABEL_PATH: case RECURSIVE_RELABEL_PATH: if (i.argument) - log_syntax(NULL, - LOG_WARNING, - fname, - line, - 0, - "%c lines don't take argument fields, ignoring.", - (char) i.type); - + log_syntax(NULL, LOG_WARNING, fname, line, 0, + "%c lines don't take argument fields, ignoring.", (char) i.type); break; case CREATE_FILE: @@ -3579,21 +3741,24 @@ static int parse_line( case CREATE_SYMLINK: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for symlink targets."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for symlink targets."); } break; case WRITE_FILE: if (!i.argument) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Write file requires argument."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Write file requires argument."); } break; case COPY_FILES: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for copy sources."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for copy sources."); } break; @@ -3601,18 +3766,21 @@ static int parse_line( case CREATE_BLOCK_DEVICE: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for device node creation."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for device node creation."); } if (!i.argument) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Device file requires argument."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Device file requires argument."); } r = parse_devnum(i.argument, &i.major_minor); if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Can't parse device file major/minor '%s'.", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Can't parse device file major/minor '%s'.", i.argument); } break; @@ -3621,7 +3789,8 @@ static int parse_line( case RECURSIVE_SET_XATTR: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for extended attributes."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for extended attributes."); } if (!i.argument) { *invalid_config = true; @@ -3637,7 +3806,8 @@ static int parse_line( case RECURSIVE_SET_ACL: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for ACLs."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for ACLs."); } if (!i.argument) { *invalid_config = true; @@ -3653,7 +3823,8 @@ static int parse_line( case RECURSIVE_SET_ATTRIBUTE: if (unbase64) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "base64 decoding not supported for file attributes."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "base64 decoding not supported for file attributes."); } if (!i.argument) { *invalid_config = true; @@ -3684,7 +3855,8 @@ static int parse_line( if (r < 0) { if (IN_SET(r, -EINVAL, -EBADSLT)) *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to substitute specifiers in argument: %m"); + return log_syntax(NULL, LOG_ERR, fname, line, r, + "Failed to substitute specifiers in argument: %m"); } } @@ -3704,7 +3876,8 @@ static int parse_line( return log_oom(); } else if (!path_is_absolute(i.argument)) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), "Source path '%s' is not absolute.", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG), + "Source path '%s' is not absolute.", i.argument); } @@ -3721,7 +3894,8 @@ static int parse_line( if (laccess(i.argument, F_OK) == -ENOENT) { /* Silently skip over lines where the source file is missing. */ - log_syntax(NULL, LOG_DEBUG, fname, line, 0, "Copy source path '%s' does not exist, skipping line.", i.argument); + log_syntax(NULL, LOG_DEBUG, fname, line, 0, + "Copy source path '%s' does not exist, skipping line.", i.argument); return 0; } @@ -3733,9 +3907,11 @@ static int parse_line( if (from_cred) { if (!i.argument) - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Reading from credential requested, but no credential name specified."); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Reading from credential requested, but no credential name specified."); if (!credential_name_valid(i.argument)) - return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), "Credential name not valid: %s", i.argument); + return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EINVAL), + "Credential name not valid: %s", i.argument); r = read_credential(i.argument, &i.binary_argument, &i.binary_argument_size); if (IN_SET(r, -ENXIO, -ENOENT)) { @@ -3753,7 +3929,8 @@ static int parse_line( _cleanup_free_ void *data = NULL; size_t data_size = 0; - r = unbase64mem(item_binary_argument(&i), item_binary_argument_size(&i), &data, &data_size); + r = unbase64mem_full(item_binary_argument(&i), item_binary_argument_size(&i), /* secure = */ false, + &data, &data_size); if (r < 0) return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to base64 decode specified argument '%s': %m", i.argument); @@ -3779,7 +3956,7 @@ static int parse_line( else u = user; - r = find_uid(u, &i.uid, uid_cache); + r = find_uid(u, &i.uid, &c->uid_cache); if (r == -ESRCH && arg_graceful) { log_syntax(NULL, LOG_DEBUG, fname, line, r, "%s: user '%s' not found, not adjusting ownership.", i.path, u); @@ -3800,7 +3977,7 @@ static int parse_line( else g = group; - r = find_gid(g, &i.gid, gid_cache); + r = find_gid(g, &i.gid, &c->gid_cache); if (r == -ESRCH && arg_graceful) { log_syntax(NULL, LOG_DEBUG, fname, line, r, "%s: group '%s' not found, not adjusting ownership.", i.path, g); @@ -3816,14 +3993,13 @@ static int parse_line( const char *mm; unsigned m; - for (mm = mode;; mm++) { + for (mm = mode;; mm++) if (*mm == '~') i.mask_perms = true; else if (*mm == ':') i.mode_only_create = true; else break; - } r = parse_mode(mm, &m); if (r < 0) { @@ -3940,16 +4116,16 @@ static int exclude_default_prefixes(void) { * likely over-mounted if the root directory is actually used, and it wouldbe less than ideal to have * all kinds of files created/adjusted underneath these mount points. */ - r = strv_extend_strv( + r = strv_extend_many( &arg_exclude_prefixes, - STRV_MAKE("/dev", - "/proc", - "/run", - "/sys"), - true); + "/dev", + "/proc", + "/run", + "/sys"); if (r < 0) return log_oom(); + strv_uniq(arg_exclude_prefixes); return 0; } @@ -3961,18 +4137,21 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n" - "\n%sCreates, deletes and cleans up volatile and temporary files and directories.%s\n\n" + printf("%1$s COMMAND [OPTIONS...] [CONFIGURATION FILE...]\n" + "\n%2$sCreate, delete, and clean up files and directories.%4$s\n" + "\n%3$sCommands:%4$s\n" + " --create Create files and directories\n" + " --clean Clean up files and directories\n" + " --remove Remove files and directories\n" " -h --help Show this help\n" - " --user Execute user configuration\n" " --version Show package version\n" + "\n%3$sOptions:%4$s\n" + " --user Execute user configuration\n" " --cat-config Show configuration files\n" " --tldr Show non-comment parts of configuration\n" - " --create Create marked files/directories\n" - " --clean Clean up marked directories\n" - " --remove Remove marked files/directories\n" " --boot Execute actions only safe at boot\n" " --graceful Quietly ignore unknown users or groups\n" + " --purge Delete all files owned by the configuration files\n" " --prefix=PATH Only apply rules with the specified prefix\n" " --exclude-prefix=PATH Ignore rules with the specified prefix\n" " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" @@ -3980,10 +4159,12 @@ static int help(void) { " --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" " --no-pager Do not pipe output into a pager\n" - "\nSee the %s for details.\n", + "\nSee the %5$s for details.\n", program_invocation_short_name, ansi_highlight(), + ansi_underline(), ansi_normal(), link); @@ -3991,7 +4172,6 @@ static int help(void) { } static int parse_argv(int argc, char *argv[]) { - enum { ARG_VERSION = 0x100, ARG_CAT_CONFIG, @@ -4000,6 +4180,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CREATE, ARG_CLEAN, ARG_REMOVE, + ARG_PURGE, ARG_BOOT, ARG_GRACEFUL, ARG_PREFIX, @@ -4008,6 +4189,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE, ARG_IMAGE_POLICY, ARG_REPLACE, + ARG_DRY_RUN, ARG_NO_PAGER, }; @@ -4020,6 +4202,7 @@ static int parse_argv(int argc, char *argv[]) { { "create", no_argument, NULL, ARG_CREATE }, { "clean", no_argument, NULL, ARG_CLEAN }, { "remove", no_argument, NULL, ARG_REMOVE }, + { "purge", no_argument, NULL, ARG_PURGE }, { "boot", no_argument, NULL, ARG_BOOT }, { "graceful", no_argument, NULL, ARG_GRACEFUL }, { "prefix", required_argument, NULL, ARG_PREFIX }, @@ -4028,6 +4211,7 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} }; @@ -4075,17 +4259,21 @@ static int parse_argv(int argc, char *argv[]) { arg_boot = true; break; + case ARG_PURGE: + arg_operation |= OPERATION_PURGE; + break; + case ARG_GRACEFUL: arg_graceful = true; break; case ARG_PREFIX: - if (strv_push(&arg_include_prefixes, optarg) < 0) + if (strv_extend(&arg_include_prefixes, optarg) < 0) return log_oom(); break; case ARG_EXCLUDE_PREFIX: - if (strv_push(&arg_exclude_prefixes, optarg) < 0) + if (strv_extend(&arg_exclude_prefixes, optarg) < 0) return log_oom(); break; @@ -4131,6 +4319,10 @@ static int parse_argv(int argc, char *argv[]) { arg_replace = optarg; break; + case ARG_DRY_RUN: + arg_dry_run = true; + break; + case ARG_NO_PAGER: arg_pager_flags |= PAGER_DISABLE; break; @@ -4144,7 +4336,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_operation == 0 && arg_cat_flags == CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "You need to specify at least one of --clean, --create, or --remove."); + "You need to specify at least one of --clean, --create, --remove, or --purge."); if (arg_replace && arg_cat_flags != CAT_CONFIG_OFF) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -4159,7 +4351,8 @@ static int parse_argv(int argc, char *argv[]) { "Combination of --user and --root= is not supported."); if (arg_image && arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Please specify either --root= or --image=, the combination of both is not supported."); return 1; } @@ -4171,77 +4364,28 @@ static int read_config_file( bool ignore_enoent, bool *invalid_config) { - _cleanup_hashmap_free_ Hashmap *uid_cache = NULL, *gid_cache = NULL; - _cleanup_fclose_ FILE *_f = NULL; - _cleanup_free_ char *pp = NULL; - unsigned v = 0; - FILE *f; ItemArray *ia; int r = 0; assert(c); assert(fn); - if (streq(fn, "-")) { - log_debug("Reading config from stdin%s", special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - fn = ""; - f = stdin; - } else { - r = search_and_fopen(fn, "re", arg_root, (const char**) config_dirs, &_f, &pp); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) { - log_debug_errno(r, "Failed to open \"%s\", ignoring: %m", fn); - return 0; - } - - return log_error_errno(r, "Failed to open '%s': %m", fn); - } - - log_debug("Reading config file \"%s\"%s", pp, special_glyph(SPECIAL_GLYPH_ELLIPSIS)); - fn = pp; - f = _f; - } - - for (;;) { - _cleanup_free_ char *line = NULL; - bool invalid_line = false; - int k; - - k = read_stripped_line(f, LONG_LINE_MAX, &line); - if (k < 0) - return log_error_errno(k, "Failed to read '%s': %m", fn); - if (k == 0) - break; - - v++; - - if (IN_SET(line[0], 0, '#')) - continue; - - k = parse_line(c, fn, v, line, &invalid_line, &uid_cache, &gid_cache); - if (k < 0) { - if (invalid_line) - /* Allow reporting with a special code if the caller requested this */ - *invalid_config = true; - else if (r == 0) - /* The first error becomes our return value */ - r = k; - } - } + r = conf_file_read(arg_root, (const char**) config_dirs, fn, + parse_line, c, ignore_enoent, invalid_config); + if (r <= 0) + return r; /* we have to determine age parameter for each entry of type X */ ORDERED_HASHMAP_FOREACH(ia, c->globs) - for (size_t ni = 0; ni < ia->n_items; ni++) { + FOREACH_ARRAY(i, ia->items, ia->n_items) { ItemArray *ja; - Item *i = ia->items + ni, *candidate_item = NULL; + Item *candidate_item = NULL; if (i->type != IGNORE_DIRECTORY_PATH) continue; ORDERED_HASHMAP_FOREACH(ja, c->items) - for (size_t nj = 0; nj < ja->n_items; nj++) { - Item *j = ja->items + nj; - + FOREACH_ARRAY(j, ja->items, ja->n_items) { if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, @@ -4266,12 +4410,6 @@ static int read_config_file( } } - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - return r; } @@ -4325,7 +4463,6 @@ static int read_config_files( } static int read_credential_lines(Context *c, bool *invalid_config) { - _cleanup_free_ char *j = NULL; const char *d; int r; @@ -4354,9 +4491,10 @@ static int link_parent(Context *c, ItemArray *a) { assert(c); assert(a); - /* Finds the closest "parent" item array for the specified item array. Then registers the specified item array - * as child of it, and fills the parent in, linking them both ways. This allows us to later create parents - * before their children, and clean up/remove children before their parents. */ + /* Finds the closest "parent" item array for the specified item array. Then registers the specified + * item array as child of it, and fills the parent in, linking them both ways. This allows us to + * later create parents before their children, and clean up/remove children before their parents. + */ if (a->n_items <= 0) return 0; @@ -4395,6 +4533,7 @@ static int run(int argc, char *argv[]) { bool invalid_config = false; ItemArray *a; enum { + PHASE_PURGE, PHASE_REMOVE_AND_CLEAN, PHASE_CREATE, _PHASE_MAX @@ -4429,7 +4568,7 @@ static int run(int argc, char *argv[]) { break; case RUNTIME_SCOPE_SYSTEM: - config_dirs = strv_split_nulstr(CONF_PATHS_NULSTR("tmpfiles.d")); + config_dirs = strv_new(CONF_PATHS("tmpfiles.d")); if (!config_dirs) return log_oom(); break; @@ -4476,7 +4615,8 @@ static int run(int argc, char *argv[]) { DISSECT_IMAGE_VALIDATE_OS | DISSECT_IMAGE_RELAX_VAR_CHECK | DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_GROWFS, + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, &mounted_dir, /* ret_dir_fd= */ NULL, &loop_device); @@ -4525,12 +4665,14 @@ static int run(int argc, char *argv[]) { return r; } - /* If multiple operations are requested, let's first run the remove/clean operations, and only then the create - * operations. i.e. that we first clean out the platform we then build on. */ + /* If multiple operations are requested, let's first run the remove/clean operations, and only then + * the create operations. i.e. that we first clean out the platform we then build on. */ for (phase = 0; phase < _PHASE_MAX; phase++) { OperationMask op; - if (phase == PHASE_REMOVE_AND_CLEAN) + if (phase == PHASE_PURGE) + op = arg_operation & OPERATION_PURGE; + else if (phase == PHASE_REMOVE_AND_CLEAN) op = arg_operation & (OPERATION_REMOVE|OPERATION_CLEAN); else if (phase == PHASE_CREATE) op = arg_operation & OPERATION_CREATE; diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build index c85721c..77fad97 100644 --- a/src/tpm2-setup/meson.build +++ b/src/tpm2-setup/meson.build @@ -13,4 +13,10 @@ executables += [ libopenssl, ], }, + + generator_template + { + 'name' : 'systemd-tpm2-generator', + 'sources' : files('tpm2-generator.c'), + }, + ] diff --git a/src/tpm2-setup/tpm2-generator.c b/src/tpm2-setup/tpm2-generator.c new file mode 100644 index 0000000..f1d903e --- /dev/null +++ b/src/tpm2-setup/tpm2-generator.c @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "generator.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "special.h" +#include "tpm2-util.h" + +/* A small generator that enqueues tpm2.target as synchronization point if the TPM2 device hasn't shown up + * yet, but the firmware reports it to exist. This is supposed to deal with systems where the TPM2 driver + * support is built as kmod and must be loaded before it's ready to be used. The tpm2.target is only enqueued + * if firmware says there is a TPM2 device, our userspace support for TPM2 is fully available but the TPM2 + * device hasn't shown up in /dev/ yet. */ + +static const char *arg_dest = NULL; +static int arg_tpm2_wait = -1; /* tri-state: negative → don't know */ + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (proc_cmdline_key_streq(key, "systemd.tpm2_wait")) { + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning_errno(r, "Failed to parse 'systemd.tpm2_wait=' kernel command line argument, ignoring: %s", value); + else + arg_tpm2_wait = r; + } + + return 0; +} + +static int generate_tpm_target_symlink(void) { + int r; + + if (arg_tpm2_wait == 0) { + log_debug("Not generating tpm2.target synchronization point, as this was explicitly turned off via kernel command line."); + return 0; + } + + if (arg_tpm2_wait < 0) { + Tpm2Support support = tpm2_support(); + + if (FLAGS_SET(support, TPM2_SUPPORT_DRIVER)) { + log_debug("Not generating tpm2.target synchronization point, as TPM2 device is already present."); + return 0; + } + + if (!FLAGS_SET(support, TPM2_SUPPORT_FIRMWARE)) { + log_debug("Not generating tpm2.target synchronization point, as firmware reports no TPM2 present."); + return 0; + } + + if (!FLAGS_SET(support, TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM|TPM2_SUPPORT_LIBRARIES)) { + log_debug("Not generating tpm2.target synchronization point, as userspace support for TPM2 is not complete."); + return 0; + } + } + + r = generator_add_symlink(arg_dest, SPECIAL_SYSINIT_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/" SPECIAL_TPM2_TARGET); + if (r < 0) + return log_error_errno(r, "Failed to hook in tpm2.target: %m"); + + return 0; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + assert_se(arg_dest = dest); + + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + return generate_tpm_target_symlink(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c index 3a30bfe..31b284b 100644 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ b/src/tty-ask-password-agent/tty-ask-password-agent.c @@ -157,7 +157,11 @@ static int agent_ask_password_tty( log_info("Starting password query on %s.", con); } - r = ask_password_tty(tty_fd, message, NULL, until, flags, flag_file, ret); + AskPasswordRequest req = { + .message = message, + }; + + r = ask_password_tty(tty_fd, &req, until, flags, flag_file, ret); if (arg_console) { tty_fd = safe_close(tty_fd); @@ -245,9 +249,13 @@ static int process_one_password_file(const char *filename) { SET_FLAG(flags, ASK_PASSWORD_ECHO, echo); SET_FLAG(flags, ASK_PASSWORD_SILENT, silent); - if (arg_plymouth) - r = ask_password_plymouth(message, not_after, flags, filename, &passwords); - else + if (arg_plymouth) { + AskPasswordRequest req = { + .message = message, + }; + + r = ask_password_plymouth(&req, not_after, flags, filename, &passwords); + } else r = agent_ask_password_tty(message, not_after, flags, filename, &passwords); if (r < 0) { /* If the query went away, that's OK */ @@ -348,7 +356,7 @@ static int process_and_watch_password_files(bool watch) { (void) mkdir_p_label("/run/systemd/ask-password", 0755); assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGTERM, -1) >= 0); + assert_se(sigset_add_many(&mask, SIGTERM) >= 0); assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); if (watch) { @@ -548,7 +556,7 @@ static int ask_on_this_console(const char *tty, pid_t *ret_pid, char **arguments assert_se(sigaction(SIGCHLD, &sigchld, NULL) >= 0); assert_se(sigaction(SIGHUP, &sighup, NULL) >= 0); - assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD) >= 0); r = safe_fork("(sd-passwd)", FORK_RESET_SIGNALS|FORK_LOG, ret_pid); if (r < 0) diff --git a/src/udev/ata_id/ata_id.c b/src/udev/ata_id/ata_id.c index 4dd7e54..6baf139 100644 --- a/src/udev/ata_id/ata_id.c +++ b/src/udev/ata_id/ata_id.c @@ -86,14 +86,14 @@ static int disk_scsi_inquiry_command( 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"); + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v3 failed."); } 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 log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed."); } return 0; @@ -160,7 +160,7 @@ static int disk_identify_command( } 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 log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed."); } return 0; @@ -232,7 +232,7 @@ static int disk_identify_packet_device_command( 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 log_debug_errno(SYNTHETIC_ERRNO(EIO), "ioctl v4 failed."); } return 0; @@ -413,10 +413,8 @@ static int run(int argc, char *argv[]) { uint16_t word; int r, peripheral_device_type = -1; - log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); - log_open(); + (void) udev_parse_config(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/udev/cdrom_id/cdrom_id.c b/src/udev/cdrom_id/cdrom_id.c index 9285dd8..195fadc 100644 --- a/src/udev/cdrom_id/cdrom_id.c +++ b/src/udev/cdrom_id/cdrom_id.c @@ -959,10 +959,8 @@ static int run(int argc, char *argv[]) { _cleanup_(context_clear) Context c = CONTEXT_EMPTY; int r; - log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); - log_open(); + (void) udev_parse_config(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/udev/dmi_memory_id/dmi_memory_id.c b/src/udev/dmi_memory_id/dmi_memory_id.c index 3f89cc7..9823df0 100644 --- a/src/udev/dmi_memory_id/dmi_memory_id.c +++ b/src/udev/dmi_memory_id/dmi_memory_id.c @@ -96,7 +96,7 @@ static const char *dmi_string(const struct dmi_header *dm, uint8_t s) { return "Not Specified"; bp += dm->length; - for (;s > 1 && !isempty(bp); s--) + for (; s > 1 && !isempty(bp); s--) bp += strlen(bp) + 1; if (isempty(bp)) @@ -685,10 +685,8 @@ static int run(int argc, char* const* argv) { size_t size; int r; - log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); - log_open(); + (void) udev_parse_config(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/udev/fido_id/fido_id.c b/src/udev/fido_id/fido_id.c index e01f37d..6c4b099 100644 --- a/src/udev/fido_id/fido_id.c +++ b/src/udev/fido_id/fido_id.c @@ -70,10 +70,8 @@ static int run(int argc, char **argv) { ssize_t desc_len; int r; - log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); - log_open(); + (void) udev_parse_config(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) diff --git a/src/udev/meson.build b/src/udev/meson.build index 824ec47..3535551 100644 --- a/src/udev/meson.build +++ b/src/udev/meson.build @@ -114,7 +114,7 @@ libudevd_core = static_library( include_directories : includes + include_directories('net'), link_with : udev_link_with, dependencies : [libblkid, - libkmod, + libkmod_cflags, userspace], build_by_default : false) @@ -205,6 +205,7 @@ executables += [ }, udev_test_template + { 'sources' : files('net/test-link-config-tables.c'), + 'include_directories' : includes + include_directories('.'), 'suite' : 'udev', }, udev_test_template + { @@ -240,6 +241,7 @@ executables += [ }, udev_fuzz_template + { 'sources' : files('net/fuzz-link-parser.c'), + 'include_directories' : includes + include_directories('.'), }, udev_fuzz_template + { 'sources' : files('fuzz-udev-rule-parse-value.c'), diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf index 240f16e..b77759d 100644 --- a/src/udev/net/link-config-gperf.gperf +++ b/src/udev/net/link-config-gperf.gperf @@ -38,6 +38,9 @@ Match.Credential, config_parse_net_condition, Match.Architecture, config_parse_net_condition, CONDITION_ARCHITECTURE, offsetof(LinkConfig, conditions) Match.Firmware, config_parse_net_condition, CONDITION_FIRMWARE, offsetof(LinkConfig, conditions) Link.Description, config_parse_string, 0, offsetof(LinkConfig, description) +Link.Property, config_parse_udev_property, 0, offsetof(LinkConfig, properties) +Link.ImportProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, import_properties) +Link.UnsetProperty, config_parse_udev_property_name, 0, offsetof(LinkConfig, unset_properties) Link.MACAddressPolicy, config_parse_mac_address_policy, 0, offsetof(LinkConfig, mac_address_policy) Link.MACAddress, config_parse_hw_addr, 0, offsetof(LinkConfig, hw_addr) Link.NamePolicy, config_parse_name_policy, 0, offsetof(LinkConfig, name_policy) @@ -105,6 +108,7 @@ Link.RxMaxCoalescedHighFrames, config_parse_coalesce_u32, Link.TxCoalesceHighSec, config_parse_coalesce_sec, 0, offsetof(LinkConfig, coalesce.tx_coalesce_usecs_high) Link.TxMaxCoalescedHighFrames, config_parse_coalesce_u32, 0, offsetof(LinkConfig, coalesce.tx_max_coalesced_frames_high) Link.CoalescePacketRateSampleIntervalSec, config_parse_coalesce_sec, 0, offsetof(LinkConfig, coalesce.rate_sample_interval) +Link.ReceivePacketSteeringCPUMask, config_parse_rps_cpu_mask, 0, offsetof(LinkConfig, rps_cpu_mask) Link.MDI, config_parse_mdi, 0, offsetof(LinkConfig, mdi) Link.SR-IOVVirtualFunctions, config_parse_sr_iov_num_vfs, 0, offsetof(LinkConfig, sr_iov_num_vfs) SR-IOV.VirtualFunction, config_parse_sr_iov_uint32, 0, offsetof(LinkConfig, sr_iov_by_section) diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 910ec27..647cdee 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -15,6 +15,8 @@ #include "creds-util.h" #include "device-private.h" #include "device-util.h" +#include "env-util.h" +#include "escape.h" #include "ethtool-util.h" #include "fd-util.h" #include "fileio.h" @@ -30,12 +32,20 @@ #include "path-util.h" #include "proc-cmdline.h" #include "random-util.h" +#include "specifier.h" #include "stat-util.h" #include "string-table.h" #include "string-util.h" #include "strv.h" +#include "udev-builtin.h" #include "utf8.h" +static const Specifier link_specifier_table[] = { + COMMON_SYSTEM_SPECIFIERS, + COMMON_TMP_SPECIFIERS, + {} +}; + struct LinkConfigContext { LIST_HEAD(LinkConfig, configs); int ethtool_fd; @@ -53,6 +63,9 @@ static LinkConfig* link_config_free(LinkConfig *config) { condition_free_list(config->conditions); free(config->description); + strv_free(config->properties); + strv_free(config->import_properties); + strv_free(config->unset_properties); free(config->name_policy); free(config->name); strv_free(config->alternative_names); @@ -60,6 +73,7 @@ static LinkConfig* link_config_free(LinkConfig *config) { free(config->alias); free(config->wol_password_file); erase_and_free(config->wol_password); + cpu_set_free(config->rps_cpu_mask); ordered_hashmap_free_with_destructor(config->sr_iov_by_section, sr_iov_free); @@ -363,18 +377,20 @@ Link *link_free(Link *link) { return NULL; sd_device_unref(link->device); + sd_device_unref(link->device_db_clone); free(link->kind); strv_free(link->altnames); return mfree(link); } -int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret) { +int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret) { _cleanup_(link_freep) Link *link = NULL; int r; assert(ctx); assert(rtnl); assert(device); + assert(device_db_clone); assert(ret); link = new(Link, 1); @@ -383,6 +399,7 @@ int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link *link = (Link) { .device = sd_device_ref(device), + .device_db_clone = sd_device_ref(device_db_clone), }; r = sd_device_get_sysname(device, &link->ifname); @@ -466,7 +483,7 @@ int link_get_config(LinkConfigContext *ctx, Link *link) { return -ENOENT; } -static int link_apply_ethtool_settings(Link *link, int *ethtool_fd) { +static int link_apply_ethtool_settings(Link *link, int *ethtool_fd, EventMode mode) { LinkConfig *config; const char *name; int r; @@ -475,6 +492,11 @@ static int link_apply_ethtool_settings(Link *link, int *ethtool_fd) { assert(link->config); assert(ethtool_fd); + if (mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of ethtool settings."); + return 0; + } + config = link->config; name = link->ifname; @@ -667,7 +689,7 @@ finalize: return 0; } -static int link_apply_rtnl_settings(Link *link, sd_netlink **rtnl) { +static int link_apply_rtnl_settings(Link *link, sd_netlink **rtnl, EventMode mode) { struct hw_addr_data hw_addr = {}; LinkConfig *config; int r; @@ -676,6 +698,11 @@ static int link_apply_rtnl_settings(Link *link, sd_netlink **rtnl) { assert(link->config); assert(rtnl); + if (mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of rtnl settings."); + return 0; + } + config = link->config; (void) link_generate_new_hw_addr(link, &hw_addr); @@ -725,7 +752,7 @@ static int link_generate_new_name(Link *link) { device = link->device; if (link->action != SD_DEVICE_ADD) { - log_link_debug(link, "Skipping to apply Name= and NamePolicy= on '%s' uevent.", + log_link_debug(link, "Not applying Name= and NamePolicy= on '%s' uevent.", device_action_to_string(link->action)); goto no_rename; } @@ -805,7 +832,7 @@ static int link_generate_alternative_names(Link *link) { assert(!link->altnames); if (link->action != SD_DEVICE_ADD) { - log_link_debug(link, "Skipping to apply AlternativeNames= and AlternativeNamesPolicy= on '%s' uevent.", + log_link_debug(link, "Not applying AlternativeNames= and AlternativeNamesPolicy= on '%s' uevent.", device_action_to_string(link->action)); return 0; } @@ -879,7 +906,7 @@ static int sr_iov_configure(Link *link, sd_netlink **rtnl, SRIOV *sr_iov) { return 0; } -static int link_apply_sr_iov_config(Link *link, sd_netlink **rtnl) { +static int link_apply_sr_iov_config(Link *link, sd_netlink **rtnl, EventMode mode) { SRIOV *sr_iov; uint32_t n; int r; @@ -888,6 +915,11 @@ static int link_apply_sr_iov_config(Link *link, sd_netlink **rtnl) { assert(link->config); assert(link->device); + if (mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of SR-IOV settings."); + return 0; + } + r = sr_iov_set_num_vfs(link->device, link->config->sr_iov_num_vfs, link->config->sr_iov_by_section); if (r < 0) log_link_warning_errno(link, r, "Failed to set the number of SR-IOV virtual functions, ignoring: %m"); @@ -921,26 +953,122 @@ static int link_apply_sr_iov_config(Link *link, sd_netlink **rtnl) { return 0; } -int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) { +static int link_apply_rps_cpu_mask(Link *link, EventMode mode) { + _cleanup_free_ char *mask_str = NULL; + LinkConfig *config; int r; - assert(ctx); - assert(rtnl); assert(link); + config = ASSERT_PTR(link->config); - if (!IN_SET(link->action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) { - log_link_debug(link, "Skipping to apply .link settings on '%s' uevent.", - device_action_to_string(link->action)); + if (mode != EVENT_UDEV_WORKER) { + log_link_debug(link, "Running in test mode, skipping application of RPS setting."); + return 0; + } - link->new_name = link->ifname; + /* Skip if the config is not specified. */ + if (!config->rps_cpu_mask) return 0; + + mask_str = cpu_set_to_mask_string(config->rps_cpu_mask); + if (!mask_str) + return log_oom(); + + log_link_debug(link, "Applying RPS CPU mask: %s", mask_str); + + /* Currently, this will set CPU mask to all rx queue of matched device. */ + FOREACH_DEVICE_SYSATTR(link->device, attr) { + const char *c; + + c = path_startswith(attr, "queues/"); + if (!c) + continue; + + c = startswith(c, "rx-"); + if (!c) + continue; + + c += strcspn(c, "/"); + + if (!path_equal(c, "/rps_cpus")) + continue; + + r = sd_device_set_sysattr_value(link->device, attr, mask_str); + if (r < 0) + log_link_warning_errno(link, r, "Failed to write %s sysfs attribute, ignoring: %m", attr); + } + + return 0; +} + +static int link_apply_udev_properties(Link *link, EventMode mode) { + LinkConfig *config; + sd_device *device; + + assert(link); + + config = ASSERT_PTR(link->config); + device = ASSERT_PTR(link->device); + + /* 1. apply ImportProperty=. */ + STRV_FOREACH(p, config->import_properties) + (void) udev_builtin_import_property(device, link->device_db_clone, mode, *p); + + /* 2. apply Property=. */ + STRV_FOREACH(p, config->properties) { + _cleanup_free_ char *key = NULL; + const char *eq; + + eq = strchr(*p, '='); + if (!eq) + continue; + + key = strndup(*p, eq - *p); + if (!key) + return log_oom(); + + (void) udev_builtin_add_property(device, mode, key, eq + 1); + } + + /* 3. apply UnsetProperty=. */ + STRV_FOREACH(p, config->unset_properties) + (void) udev_builtin_add_property(device, mode, *p, NULL); + + /* 4. set the default properties. */ + (void) udev_builtin_add_property(device, mode, "ID_NET_LINK_FILE", config->filename); + + _cleanup_free_ char *joined = NULL; + STRV_FOREACH(d, config->dropins) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(*d, ":"); + if (!escaped) + return log_oom(); + + if (!strextend_with_separator(&joined, ":", escaped)) + return log_oom(); } - r = link_apply_ethtool_settings(link, &ctx->ethtool_fd); + (void) udev_builtin_add_property(device, mode, "ID_NET_LINK_FILE_DROPINS", joined); + + if (link->new_name) + (void) udev_builtin_add_property(device, mode, "ID_NET_NAME", link->new_name); + + return 0; +} + +int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, EventMode mode) { + int r; + + assert(ctx); + assert(rtnl); + assert(link); + + r = link_apply_ethtool_settings(link, &ctx->ethtool_fd, mode); if (r < 0) return r; - r = link_apply_rtnl_settings(link, rtnl); + r = link_apply_rtnl_settings(link, rtnl, mode); if (r < 0) return r; @@ -952,11 +1080,151 @@ int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) { if (r < 0) return r; - r = link_apply_sr_iov_config(link, rtnl); + r = link_apply_sr_iov_config(link, rtnl, mode); if (r < 0) return r; - return 0; + r = link_apply_rps_cpu_mask(link, mode); + if (r < 0) + return r; + + return link_apply_udev_properties(link, mode); +} + +int config_parse_udev_property( + 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 ***properties = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *properties = strv_free(*properties); + return 0; + } + + for (const char *p = rvalue;; ) { + _cleanup_free_ char *word = NULL, *resolved = NULL, *key = NULL; + const char *eq; + + r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring assignment: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve specifiers in %s, ignoring assignment: %m", word); + continue; + } + + /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */ + if (!env_assignment_is_valid(resolved)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid udev property, ignoring assignment: %s", word); + continue; + } + + assert_se(eq = strchr(resolved, '=')); + key = strndup(resolved, eq - resolved); + if (!key) + return log_oom(); + + if (!device_property_can_set(key)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid udev property name '%s', ignoring assignment: %s", key, resolved); + continue; + } + + r = strv_env_replace_consume(properties, TAKE_PTR(resolved)); + if (r < 0) + return log_error_errno(r, "Failed to update properties: %m"); + } +} + +int config_parse_udev_property_name( + 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 ***properties = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *properties = strv_free(*properties); + return 0; + } + + for (const char *p = rvalue;; ) { + _cleanup_free_ char *word = NULL, *resolved = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax, ignoring assignment: %s", rvalue); + return 0; + } + if (r == 0) + return 0; + + r = specifier_printf(word, SIZE_MAX, link_specifier_table, NULL, NULL, &resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve specifiers in %s, ignoring assignment: %m", word); + continue; + } + + /* The restriction for udev property is not clear. Let's apply the one for environment variable here. */ + if (!env_name_is_valid(resolved)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid udev property name, ignoring assignment: %s", resolved); + continue; + } + + if (!device_property_can_set(resolved)) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Invalid udev property name, ignoring assignment: %s", resolved); + continue; + } + + r = strv_consume(properties, TAKE_PTR(resolved)); + if (r < 0) + return log_error_errno(r, "Failed to update properties: %m"); + } } int config_parse_ifalias( @@ -1110,6 +1378,63 @@ int config_parse_wol_password( return 0; } +int config_parse_rps_cpu_mask( + 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_(cpu_set_freep) CPUSet *allocated = NULL; + CPUSet *mask, **rps_cpu_mask = ASSERT_PTR(data); + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + *rps_cpu_mask = cpu_set_free(*rps_cpu_mask); + return 0; + } + + if (*rps_cpu_mask) + mask = *rps_cpu_mask; + else { + allocated = new0(CPUSet, 1); + if (!allocated) + return log_oom(); + + mask = allocated; + } + + if (streq(rvalue, "disable")) + cpu_set_reset(mask); + + else if (streq(rvalue, "all")) { + r = cpu_mask_add_all(mask); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to create CPU affinity mask representing \"all\" cpus, ignoring: %m"); + return 0; + } + } else { + r = parse_cpu_set_extend(rvalue, mask, /* warn= */ true, unit, filename, line, lvalue); + if (r < 0) + return 0; + } + + if (allocated) + *rps_cpu_mask = TAKE_PTR(allocated); + + return 0; +} + static const char* const mac_address_policy_table[_MAC_ADDRESS_POLICY_MAX] = { [MAC_ADDRESS_POLICY_PERSISTENT] = "persistent", [MAC_ADDRESS_POLICY_RANDOM] = "random", diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h index bab9d12..103343f 100644 --- a/src/udev/net/link-config.h +++ b/src/udev/net/link-config.h @@ -6,11 +6,13 @@ #include "condition.h" #include "conf-parser.h" +#include "cpu-set-util.h" #include "ethtool-util.h" #include "hashmap.h" #include "list.h" #include "net-condition.h" #include "netif-naming-scheme.h" +#include "udev-event.h" typedef struct LinkConfigContext LinkConfigContext; typedef struct LinkConfig LinkConfig; @@ -31,6 +33,7 @@ typedef struct Link { LinkConfig *config; sd_device *device; + sd_device *device_db_clone; sd_device_action_t action; char *kind; @@ -51,6 +54,9 @@ struct LinkConfig { LIST_HEAD(Condition, conditions); char *description; + char **properties; + char **import_properties; + char **unset_properties; struct hw_addr_data hw_addr; MACAddressPolicy mac_address_policy; NamePolicy *name_policy; @@ -80,6 +86,7 @@ struct LinkConfig { int autoneg_flow_control; netdev_coalesce_param coalesce; uint8_t mdi; + CPUSet *rps_cpu_mask; uint32_t sr_iov_num_vfs; OrderedHashmap *sr_iov_by_section; @@ -95,12 +102,12 @@ int link_load_one(LinkConfigContext *ctx, const char *filename); int link_config_load(LinkConfigContext *ctx); bool link_config_should_reload(LinkConfigContext *ctx); -int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link **ret); +int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, sd_device *device_db_clone, Link **ret); Link *link_free(Link *link); DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); int link_get_config(LinkConfigContext *ctx, Link *link); -int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link); +int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link, EventMode mode); const char *mac_address_policy_to_string(MACAddressPolicy p) _const_; MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_; @@ -108,6 +115,8 @@ MACAddressPolicy mac_address_policy_from_string(const char *p) _pure_; /* gperf lookup function */ const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN_TYPE length); +CONFIG_PARSER_PROTOTYPE(config_parse_udev_property); +CONFIG_PARSER_PROTOTYPE(config_parse_udev_property_name); CONFIG_PARSER_PROTOTYPE(config_parse_ifalias); CONFIG_PARSER_PROTOTYPE(config_parse_rx_tx_queues); CONFIG_PARSER_PROTOTYPE(config_parse_txqueuelen); @@ -115,3 +124,4 @@ CONFIG_PARSER_PROTOTYPE(config_parse_wol_password); CONFIG_PARSER_PROTOTYPE(config_parse_mac_address_policy); CONFIG_PARSER_PROTOTYPE(config_parse_name_policy); CONFIG_PARSER_PROTOTYPE(config_parse_alternative_names_policy); +CONFIG_PARSER_PROTOTYPE(config_parse_rps_cpu_mask); diff --git a/src/udev/scsi_id/scsi.h b/src/udev/scsi_id/scsi.h index ee3e401..ebb8ae9 100644 --- a/src/udev/scsi_id/scsi.h +++ b/src/udev/scsi_id/scsi.h @@ -1,17 +1,7 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once -/* - * scsi.h - * - * General scsi and linux scsi specific defines and structs. - * - * Copyright (C) IBM Corp. 2003 - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation version 2 of the License. - */ +/* Copyright (C) IBM Corp. 2003 */ #include diff --git a/src/udev/scsi_id/scsi_id.c b/src/udev/scsi_id/scsi_id.c index 6308c52..b63a46a 100644 --- a/src/udev/scsi_id/scsi_id.c +++ b/src/udev/scsi_id/scsi_id.c @@ -84,6 +84,13 @@ static void set_type(unsigned type_num, char *to, size_t len) { case 0xf: type = "optical"; break; + case 0x14: + /* + * Use "zbc" here to be brief and consistent with "lsscsi" command. + * Other tools, e.g., "sg3_utils" would say "host managed zoned block". + */ + type = "zbc"; + break; default: type = "generic"; break; @@ -144,7 +151,7 @@ static int get_file_options(const char *vendor, const char *model, if (*buf == '#') continue; - r = extract_many_words(&buf, "=\",\n", 0, &key, &value, NULL); + r = extract_many_words(&buf, "=\",\n", 0, &key, &value); if (r < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer); @@ -152,7 +159,7 @@ static int get_file_options(const char *vendor, const char *model, vendor_in = TAKE_PTR(value); key = mfree(key); - r = extract_many_words(&buf, "=\",\n", 0, &key, &value, NULL); + r = extract_many_words(&buf, "=\",\n", 0, &key, &value); if (r < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer); @@ -160,7 +167,7 @@ static int get_file_options(const char *vendor, const char *model, model_in = TAKE_PTR(value); key = mfree(key); - r = extract_many_words(&buf, "=\",\n", 0, &key, &value, NULL); + r = extract_many_words(&buf, "=\",\n", 0, &key, &value); if (r < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Error parsing config file line %d '%s'", lineno, buffer); } @@ -473,10 +480,8 @@ int main(int argc, char **argv) { char maj_min_dev[MAX_PATH_LEN]; int newargc; - log_set_target(LOG_TARGET_AUTO); - udev_parse_config(); - log_parse_environment(); - log_open(); + (void) udev_parse_config(); + log_setup(); /* * Get config file options. diff --git a/src/udev/test-udev-format.c b/src/udev/test-udev-format.c index d8e3808..7eacb6b 100644 --- a/src/udev/test-udev-format.c +++ b/src/udev/test-udev-format.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "mountpoint-util.h" #include "string-util.h" #include "tests.h" #include "udev-format.h" @@ -34,4 +35,11 @@ TEST(udev_resolve_subsys_kernel) { test_udev_resolve_subsys_kernel_one("[net/lo]/address", true, 0, "00:00:00:00:00:00"); } -DEFINE_TEST_MAIN(LOG_DEBUG); +static int intro(void) { + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + + return EXIT_SUCCESS; +} + +DEFINE_TEST_MAIN_WITH_INTRO(LOG_DEBUG, intro); diff --git a/src/udev/test-udev-rule-runner.c b/src/udev/test-udev-rule-runner.c index 72296b3..10da645 100644 --- a/src/udev/test-udev-rule-runner.c +++ b/src/udev/test-udev-rule-runner.c @@ -12,6 +12,7 @@ #include #include "device-private.h" +#include "device-util.h" #include "fs-util.h" #include "log.h" #include "main-func.h" @@ -141,16 +142,15 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_debug_errno(r, "Failed to open device '%s'", devpath); - assert_se(event = udev_event_new(dev, 0, NULL, log_get_max_level())); + assert_se(event = udev_event_new(dev, NULL, EVENT_TEST_RULE_RUNNER)); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD) >= 0); /* do what devtmpfs usually provides us */ if (sd_device_get_devname(dev, &devname) >= 0) { - const char *subsystem; mode_t mode = 0600; - if (sd_device_get_subsystem(dev, &subsystem) >= 0 && streq(subsystem, "block")) + if (device_in_subsystem(dev, "block")) mode |= S_IFBLK; else mode |= S_IFCHR; @@ -169,8 +169,8 @@ static int run(int argc, char *argv[]) { } } - udev_event_execute_rules(event, -1, 3 * USEC_PER_SEC, SIGKILL, NULL, rules); - udev_event_execute_run(event, 3 * USEC_PER_SEC, SIGKILL); + udev_event_execute_rules(event, rules); + udev_event_execute_run(event); return 0; } diff --git a/src/udev/test-udev-spawn.c b/src/udev/test-udev-spawn.c index 4f43fac..7cbccf3 100644 --- a/src/udev/test-udev-spawn.c +++ b/src/udev/test-udev-spawn.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "mountpoint-util.h" #include "path-util.h" #include "signal-util.h" #include "strv.h" @@ -16,8 +17,8 @@ static void test_event_spawn_core(bool with_pidfd, const char *cmd, char *result assert_se(setenv("SYSTEMD_PIDFD", yes_no(with_pidfd), 1) >= 0); assert_se(sd_device_new_from_syspath(&dev, "/sys/class/net/lo") >= 0); - assert_se(event = udev_event_new(dev, 0, NULL, LOG_DEBUG)); - assert_se(udev_event_spawn(event, 5 * USEC_PER_SEC, SIGKILL, false, cmd, result_buf, buf_size, NULL) == 0); + assert_se(event = udev_event_new(dev, NULL, EVENT_TEST_SPAWN)); + assert_se(udev_event_spawn(event, false, cmd, result_buf, buf_size, NULL) == 0); assert_se(unsetenv("SYSTEMD_PIDFD") >= 0); } @@ -80,6 +81,9 @@ static void test2(void) { int main(int argc, char *argv[]) { _cleanup_free_ char *self = NULL; + if (path_is_mount_point("/sys") <= 0) + return log_tests_skipped("/sys is not mounted"); + if (argc > 1) { if (streq(argv[1], "test1")) test1(); @@ -93,7 +97,7 @@ int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); test_event_spawn_cat(true, SIZE_MAX); test_event_spawn_cat(false, SIZE_MAX); diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 11419a3..4d13035 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -34,95 +34,95 @@ #include "strxcpyx.h" #include "udev-builtin.h" -static void print_property(sd_device *dev, bool test, const char *name, const char *value) { +static void print_property(sd_device *dev, EventMode mode, const char *name, const char *value) { char s[256]; s[0] = '\0'; if (streq(name, "TYPE")) { - udev_builtin_add_property(dev, test, "ID_FS_TYPE", value); + udev_builtin_add_property(dev, mode, "ID_FS_TYPE", value); } else if (streq(name, "USAGE")) { - udev_builtin_add_property(dev, test, "ID_FS_USAGE", value); + udev_builtin_add_property(dev, mode, "ID_FS_USAGE", value); } else if (streq(name, "VERSION")) { - udev_builtin_add_property(dev, test, "ID_FS_VERSION", value); + udev_builtin_add_property(dev, mode, "ID_FS_VERSION", value); } else if (streq(name, "UUID")) { blkid_safe_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_UUID", s); + udev_builtin_add_property(dev, mode, "ID_FS_UUID", s); blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_UUID_ENC", s); + udev_builtin_add_property(dev, mode, "ID_FS_UUID_ENC", s); } else if (streq(name, "UUID_SUB")) { blkid_safe_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB", s); + udev_builtin_add_property(dev, mode, "ID_FS_UUID_SUB", s); blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_UUID_SUB_ENC", s); + udev_builtin_add_property(dev, mode, "ID_FS_UUID_SUB_ENC", s); } else if (streq(name, "LABEL")) { blkid_safe_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_LABEL", s); + udev_builtin_add_property(dev, mode, "ID_FS_LABEL", s); blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_LABEL_ENC", s); + udev_builtin_add_property(dev, mode, "ID_FS_LABEL_ENC", s); } else if (STR_IN_SET(name, "FSSIZE", "FSLASTBLOCK", "FSBLOCKSIZE")) { strscpyl(s, sizeof(s), "ID_FS_", name + 2, NULL); - udev_builtin_add_property(dev, test, s, value); + udev_builtin_add_property(dev, mode, s, value); } else if (streq(name, "PTTYPE")) { - udev_builtin_add_property(dev, test, "ID_PART_TABLE_TYPE", value); + udev_builtin_add_property(dev, mode, "ID_PART_TABLE_TYPE", value); } else if (streq(name, "PTUUID")) { - udev_builtin_add_property(dev, test, "ID_PART_TABLE_UUID", value); + udev_builtin_add_property(dev, mode, "ID_PART_TABLE_UUID", value); } else if (streq(name, "PART_ENTRY_NAME")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_PART_ENTRY_NAME", s); + udev_builtin_add_property(dev, mode, "ID_PART_ENTRY_NAME", s); } else if (streq(name, "PART_ENTRY_TYPE")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_PART_ENTRY_TYPE", s); + udev_builtin_add_property(dev, mode, "ID_PART_ENTRY_TYPE", s); } else if (startswith(name, "PART_ENTRY_")) { strscpyl(s, sizeof(s), "ID_", name, NULL); - udev_builtin_add_property(dev, test, s, value); + udev_builtin_add_property(dev, mode, s, value); } else if (streq(name, "SYSTEM_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_SYSTEM_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_SYSTEM_ID", s); } else if (streq(name, "PUBLISHER_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_PUBLISHER_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_PUBLISHER_ID", s); } else if (streq(name, "APPLICATION_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_APPLICATION_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_APPLICATION_ID", s); } else if (streq(name, "BOOT_SYSTEM_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_BOOT_SYSTEM_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_BOOT_SYSTEM_ID", s); } else if (streq(name, "VOLUME_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_VOLUME_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_VOLUME_ID", s); } else if (streq(name, "LOGICAL_VOLUME_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_LOGICAL_VOLUME_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_LOGICAL_VOLUME_ID", s); } else if (streq(name, "VOLUME_SET_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_VOLUME_SET_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_VOLUME_SET_ID", s); } else if (streq(name, "DATA_PREPARER_ID")) { blkid_encode_string(value, s, sizeof(s)); - udev_builtin_add_property(dev, test, "ID_FS_DATA_PREPARER_ID", s); + udev_builtin_add_property(dev, mode, "ID_FS_DATA_PREPARER_ID", s); } } -static int find_gpt_root(sd_device *dev, blkid_probe pr, bool test) { +static int find_gpt_root(sd_device *dev, blkid_probe pr, EventMode mode) { #if defined(SD_GPT_ROOT_NATIVE) && ENABLE_EFI @@ -201,7 +201,7 @@ static int find_gpt_root(sd_device *dev, blkid_probe pr, bool test) { /* We found the ESP/XBOOTLDR on this disk, and also found a root partition, nice! Let's export its * UUID */ if (found_esp_or_xbootldr && !sd_id128_is_null(root_id)) - udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT_UUID", SD_ID128_TO_UUID_STRING(root_id)); + udev_builtin_add_property(dev, mode, "ID_PART_GPT_AUTO_ROOT_UUID", SD_ID128_TO_UUID_STRING(root_id)); #endif return 0; @@ -315,7 +315,7 @@ notloop: return 0; } -static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *devnode, *root_partition = NULL, *data, *name; _cleanup_(blkid_free_probep) blkid_probe pr = NULL; @@ -368,7 +368,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { if (r < 0) return log_device_error_errno(dev, r, "Failed to parse '%s' as an integer: %m", optarg); if (offset < 0) - return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid offset %"PRIi64": %m", offset); + return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid offset %"PRIi64".", offset); break; case 'R': noraid = true; @@ -422,7 +422,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { if (blkid_probe_get_value(pr, i, &name, &data, NULL) < 0) continue; - print_property(dev, test, name, data); + print_property(dev, event->event_mode, name, data); /* Is this a disk with GPT partition table? */ if (streq(name, "PTTYPE") && streq(data, "gpt")) @@ -431,11 +431,11 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { /* Is this a partition that matches the root partition * property inherited from the parent? */ if (root_partition && streq(name, "PART_ENTRY_UUID") && streq(data, root_partition)) - udev_builtin_add_property(dev, test, "ID_PART_GPT_AUTO_ROOT", "1"); + udev_builtin_add_property(dev, event->event_mode, "ID_PART_GPT_AUTO_ROOT", "1"); } if (is_gpt) - find_gpt_root(dev, pr, test); + find_gpt_root(dev, pr, event->event_mode); r = read_loopback_backing_inode( dev, @@ -446,8 +446,8 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { if (r < 0) log_device_debug_errno(dev, r, "Failed to read loopback backing inode, ignoring: %m"); else if (r > 0) { - udev_builtin_add_propertyf(dev, test, "ID_LOOP_BACKING_DEVICE", DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(backing_devno)); - udev_builtin_add_propertyf(dev, test, "ID_LOOP_BACKING_INODE", "%" PRIu64, (uint64_t) backing_inode); + udev_builtin_add_propertyf(dev, event->event_mode, "ID_LOOP_BACKING_DEVICE", DEVNUM_FORMAT_STR, DEVNUM_FORMAT_VAL(backing_devno)); + udev_builtin_add_propertyf(dev, event->event_mode, "ID_LOOP_BACKING_INODE", "%" PRIu64, (uint64_t) backing_inode); if (backing_fname) { /* In the worst case blkid_encode_string() will blow up to 4x the string @@ -458,8 +458,8 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { assert(strlen(backing_fname) < ELEMENTSOF(encoded) / 4); blkid_encode_string(backing_fname, encoded, ELEMENTSOF(encoded)); - udev_builtin_add_property(dev, test, "ID_LOOP_BACKING_FILENAME", backing_fname); - udev_builtin_add_property(dev, test, "ID_LOOP_BACKING_FILENAME_ENC", encoded); + udev_builtin_add_property(dev, event->event_mode, "ID_LOOP_BACKING_FILENAME", backing_fname); + udev_builtin_add_property(dev, event->event_mode, "ID_LOOP_BACKING_FILENAME_ENC", encoded); } } diff --git a/src/udev/udev-builtin-btrfs.c b/src/udev/udev-builtin-btrfs.c index 9b12aeb..fe030d0 100644 --- a/src/udev/udev-builtin-btrfs.c +++ b/src/udev/udev-builtin-btrfs.c @@ -12,7 +12,7 @@ #include "strxcpyx.h" #include "udev-builtin.h" -static int builtin_btrfs(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_btrfs(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); struct btrfs_ioctl_vol_args args = {}; _cleanup_close_ int fd = -EBADF; @@ -27,7 +27,7 @@ static int builtin_btrfs(UdevEvent *event, int argc, char *argv[], bool test) { /* Driver not installed? Then we aren't ready. This is useful in initrds that lack * btrfs.ko. After the host transition (where btrfs.ko will hopefully become * available) the device can be retriggered and will then be considered ready. */ - udev_builtin_add_property(dev, test, "ID_BTRFS_READY", "0"); + udev_builtin_add_property(dev, event->event_mode, "ID_BTRFS_READY", "0"); return 0; } @@ -39,7 +39,7 @@ static int builtin_btrfs(UdevEvent *event, int argc, char *argv[], bool test) { if (r < 0) return log_device_debug_errno(dev, errno, "Failed to call BTRFS_IOC_DEVICES_READY: %m"); - udev_builtin_add_property(dev, test, "ID_BTRFS_READY", one_zero(r == 0)); + udev_builtin_add_property(dev, event->event_mode, "ID_BTRFS_READY", one_zero(r == 0)); return 0; } diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index 19e07e7..e33a7fe 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -17,9 +17,13 @@ static sd_hwdb *hwdb; -int udev_builtin_hwdb_lookup(sd_device *dev, - const char *prefix, const char *modalias, - const char *filter, bool test) { +int udev_builtin_hwdb_lookup( + sd_device *dev, + const char *prefix, + const char *modalias, + const char *filter, + EventMode mode) { + _cleanup_free_ char *lookup = NULL; const char *key, *value; int n = 0, r; @@ -38,7 +42,7 @@ int udev_builtin_hwdb_lookup(sd_device *dev, if (filter && fnmatch(filter, key, FNM_NOESCAPE) != 0) continue; - r = udev_builtin_add_property(dev, test, key, value); + r = udev_builtin_add_property(dev, mode, key, value); if (r < 0) return r; n++; @@ -64,9 +68,14 @@ static const char *modalias_usb(sd_device *dev, char *s, size_t size) { return s; } -static int udev_builtin_hwdb_search(sd_device *dev, sd_device *srcdev, - const char *subsystem, const char *prefix, - const char *filter, bool test) { +static int udev_builtin_hwdb_search( + sd_device *dev, + sd_device *srcdev, + const char *subsystem, + const char *prefix, + const char *filter, + EventMode mode) { + char s[LINE_MAX]; bool last = false; int r = 0; @@ -77,20 +86,15 @@ static int udev_builtin_hwdb_search(sd_device *dev, sd_device *srcdev, srcdev = dev; for (sd_device *d = srcdev; d; ) { - const char *dsubsys, *devtype, *modalias = NULL; - - if (sd_device_get_subsystem(d, &dsubsys) < 0) - goto next; + const char *modalias = NULL; /* look only at devices of a specific subsystem */ - if (subsystem && !streq(dsubsys, subsystem)) + if (subsystem && !device_in_subsystem(d, subsystem)) goto next; (void) sd_device_get_property_value(d, "MODALIAS", &modalias); - if (streq(dsubsys, "usb") && - sd_device_get_devtype(d, &devtype) >= 0 && - streq(devtype, "usb_device")) { + if (device_in_subsystem(d, "usb") && device_is_devtype(d, "usb_device")) { /* if the usb_device does not have a modalias, compose one */ if (!modalias) modalias = modalias_usb(d, s, sizeof(s)); @@ -104,7 +108,7 @@ static int udev_builtin_hwdb_search(sd_device *dev, sd_device *srcdev, log_device_debug(dev, "hwdb modalias key: \"%s\"", modalias); - r = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, test); + r = udev_builtin_hwdb_lookup(dev, prefix, modalias, filter, mode); if (r > 0) break; @@ -118,7 +122,7 @@ next: return r; } -static int builtin_hwdb(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_hwdb(UdevEvent *event, int argc, char *argv[]) { static const struct option options[] = { { "filter", required_argument, NULL, 'f' }, { "device", required_argument, NULL, 'd' }, @@ -165,7 +169,7 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[], bool test) { /* query a specific key given as argument */ if (argv[optind]) { - r = udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, test); + r = udev_builtin_hwdb_lookup(dev, prefix, argv[optind], filter, event->event_mode); if (r < 0) return log_device_debug_errno(dev, r, "Failed to look up hwdb: %m"); if (r == 0) @@ -180,7 +184,7 @@ static int builtin_hwdb(UdevEvent *event, int argc, char *argv[], bool test) { return log_device_debug_errno(dev, r, "Failed to create sd_device object '%s': %m", device); } - r = udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, test); + r = udev_builtin_hwdb_search(dev, srcdev, subsystem, prefix, filter, event->event_mode); if (r < 0) return log_device_debug_errno(dev, r, "Failed to look up hwdb: %m"); if (r == 0) diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c index 295e8d2..6f75d9d 100644 --- a/src/udev/udev-builtin-input_id.c +++ b/src/udev/udev-builtin-input_id.c @@ -44,7 +44,7 @@ static int abs_size_mm(const struct input_absinfo *absinfo) { return (absinfo->maximum - absinfo->minimum) / absinfo->resolution; } -static void extract_info(sd_device *dev, bool test) { +static void extract_info(sd_device *dev, EventMode mode) { char width[DECIMAL_STR_MAX(int)], height[DECIMAL_STR_MAX(int)]; struct input_absinfo xabsinfo = {}, yabsinfo = {}; _cleanup_close_ int fd = -EBADF; @@ -63,8 +63,8 @@ static void extract_info(sd_device *dev, bool test) { xsprintf(width, "%d", abs_size_mm(&xabsinfo)); xsprintf(height, "%d", abs_size_mm(&yabsinfo)); - udev_builtin_add_property(dev, test, "ID_INPUT_WIDTH_MM", width); - udev_builtin_add_property(dev, test, "ID_INPUT_HEIGHT_MM", height); + udev_builtin_add_property(dev, mode, "ID_INPUT_WIDTH_MM", width); + udev_builtin_add_property(dev, mode, "ID_INPUT_HEIGHT_MM", height); } /* @@ -73,9 +73,13 @@ static void extract_info(sd_device *dev, bool test) { * @param attr sysfs attribute name (e. g. "capabilities/key") * @param bitmask: Output array which has a sizeof of bitmask_size */ -static void get_cap_mask(sd_device *pdev, const char* attr, - unsigned long *bitmask, size_t bitmask_size, - bool test) { +static void get_cap_mask( + sd_device *pdev, + const char* attr, + unsigned long *bitmask, + size_t bitmask_size, + EventMode mode) { + const char *v; char text[4096]; unsigned i; @@ -110,7 +114,7 @@ static void get_cap_mask(sd_device *pdev, const char* attr, else log_device_debug(pdev, "Ignoring %s block %lX which is larger than maximum size", attr, val); - if (test && DEBUG_LOGGING) { + if (mode == EVENT_UDEVADM_TEST_BUILTIN && DEBUG_LOGGING) { log_device_debug(pdev, "%s decoded bit map:", attr); val = bitmask_size / sizeof (unsigned long); @@ -144,14 +148,16 @@ static struct input_id get_input_id(sd_device *dev) { } /* pointer devices */ -static bool test_pointers(sd_device *dev, - const struct input_id *id, - const unsigned long* bitmask_ev, - const unsigned long* bitmask_abs, - const unsigned long* bitmask_key, - const unsigned long* bitmask_rel, - const unsigned long* bitmask_props, - bool test) { +static bool test_pointers( + sd_device *dev, + const struct input_id *id, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_abs, + const unsigned long* bitmask_key, + const unsigned long* bitmask_rel, + const unsigned long* bitmask_props, + EventMode mode) { + bool has_abs_coordinates = false; bool has_rel_coordinates = false; bool has_mt_coordinates = false; @@ -186,7 +192,7 @@ static bool test_pointers(sd_device *dev, is_accelerometer = true; if (is_accelerometer) { - udev_builtin_add_property(dev, test, "ID_INPUT_ACCELEROMETER", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_ACCELEROMETER", "1"); return true; } @@ -309,28 +315,29 @@ static bool test_pointers(sd_device *dev, } if (is_pointing_stick) - udev_builtin_add_property(dev, test, "ID_INPUT_POINTINGSTICK", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_POINTINGSTICK", "1"); if (is_mouse || is_abs_mouse) - udev_builtin_add_property(dev, test, "ID_INPUT_MOUSE", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_MOUSE", "1"); if (is_touchpad) - udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHPAD", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_TOUCHPAD", "1"); if (is_touchscreen) - udev_builtin_add_property(dev, test, "ID_INPUT_TOUCHSCREEN", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_TOUCHSCREEN", "1"); if (is_joystick) - udev_builtin_add_property(dev, test, "ID_INPUT_JOYSTICK", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_JOYSTICK", "1"); if (is_tablet) - udev_builtin_add_property(dev, test, "ID_INPUT_TABLET", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_TABLET", "1"); if (is_tablet_pad) - udev_builtin_add_property(dev, test, "ID_INPUT_TABLET_PAD", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_TABLET_PAD", "1"); return is_tablet || is_mouse || is_abs_mouse || is_touchpad || is_touchscreen || is_joystick || is_pointing_stick; } /* key like devices */ -static bool test_key(sd_device *dev, - const unsigned long* bitmask_ev, - const unsigned long* bitmask_key, - bool test) { +static bool test_key( + sd_device *dev, + const unsigned long* bitmask_ev, + const unsigned long* bitmask_key, + EventMode mode) { bool found = false; @@ -357,19 +364,19 @@ static bool test_key(sd_device *dev, } if (found) - udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_KEY", "1"); /* the first 32 bits are ESC, numbers, and Q to D; if we have all of * those, consider it a full keyboard; do not test KEY_RESERVED, though */ if (FLAGS_SET(bitmask_key[0], 0xFFFFFFFE)) { - udev_builtin_add_property(dev, test, "ID_INPUT_KEYBOARD", "1"); + udev_builtin_add_property(dev, mode, "ID_INPUT_KEYBOARD", "1"); return true; } return found; } -static int builtin_input_id(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_input_id(UdevEvent *event, int argc, char *argv[]) { sd_device *pdev, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); unsigned long bitmask_ev[NBITS(EV_MAX)]; unsigned long bitmask_abs[NBITS(ABS_MAX)]; @@ -400,28 +407,28 @@ static int builtin_input_id(UdevEvent *event, int argc, char *argv[], bool test) /* Use this as a flag that input devices were detected, so that this * program doesn't need to be called more than once per device */ - udev_builtin_add_property(dev, test, "ID_INPUT", "1"); - get_cap_mask(pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), test); - get_cap_mask(pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), test); - get_cap_mask(pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), test); - get_cap_mask(pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), test); - get_cap_mask(pdev, "properties", bitmask_props, sizeof(bitmask_props), test); + udev_builtin_add_property(dev, event->event_mode, "ID_INPUT", "1"); + get_cap_mask(pdev, "capabilities/ev", bitmask_ev, sizeof(bitmask_ev), event->event_mode); + get_cap_mask(pdev, "capabilities/abs", bitmask_abs, sizeof(bitmask_abs), event->event_mode); + get_cap_mask(pdev, "capabilities/rel", bitmask_rel, sizeof(bitmask_rel), event->event_mode); + get_cap_mask(pdev, "capabilities/key", bitmask_key, sizeof(bitmask_key), event->event_mode); + get_cap_mask(pdev, "properties", bitmask_props, sizeof(bitmask_props), event->event_mode); is_pointer = test_pointers(dev, &id, bitmask_ev, bitmask_abs, bitmask_key, bitmask_rel, - bitmask_props, test); - is_key = test_key(dev, bitmask_ev, bitmask_key, test); + bitmask_props, event->event_mode); + is_key = test_key(dev, bitmask_ev, bitmask_key, event->event_mode); /* Some evdev nodes have only a scrollwheel */ if (!is_pointer && !is_key && test_bit(EV_REL, bitmask_ev) && (test_bit(REL_WHEEL, bitmask_rel) || test_bit(REL_HWHEEL, bitmask_rel))) - udev_builtin_add_property(dev, test, "ID_INPUT_KEY", "1"); + udev_builtin_add_property(dev, event->event_mode, "ID_INPUT_KEY", "1"); if (test_bit(EV_SW, bitmask_ev)) - udev_builtin_add_property(dev, test, "ID_INPUT_SWITCH", "1"); + udev_builtin_add_property(dev, event->event_mode, "ID_INPUT_SWITCH", "1"); } if (sd_device_get_sysname(dev, &sysname) >= 0 && startswith(sysname, "event")) - extract_info(dev, test); + extract_info(dev, event->event_mode); return 0; } diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c index 3903bc4..e1e6de0 100644 --- a/src/udev/udev-builtin-keyboard.c +++ b/src/udev/udev-builtin-keyboard.c @@ -159,7 +159,7 @@ static int set_trackpoint_sensitivity(sd_device *dev, const char *value) { return 0; } -static int builtin_keyboard(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_keyboard(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); unsigned release[1024]; unsigned release_count = 0; @@ -167,6 +167,11 @@ static int builtin_keyboard(UdevEvent *event, int argc, char *argv[], bool test) const char *node; int has_abs = -1, r; + if (event->event_mode != EVENT_UDEV_WORKER) { + log_device_debug(dev, "Running in test mode, skipping execution of 'keyboard' builtin command."); + return 0; + } + r = sd_device_get_devname(dev, &node); if (r < 0) return log_device_error_errno(dev, r, "Failed to get device name: %m"); diff --git a/src/udev/udev-builtin-kmod.c b/src/udev/udev-builtin-kmod.c index 3ab5c48..f4aa480 100644 --- a/src/udev/udev-builtin-kmod.c +++ b/src/udev/udev-builtin-kmod.c @@ -18,14 +18,15 @@ static struct kmod_ctx *ctx = NULL; -_printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *file, int line, const char *fn, const char *format, va_list args) { - log_internalv(priority, 0, file, line, fn, format, args); -} - -static int builtin_kmod(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_kmod(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; + if (event->event_mode != EVENT_UDEV_WORKER) { + log_device_debug(dev, "Running in test mode, skipping execution of 'kmod' builtin command."); + return 0; + } + if (!ctx) return 0; @@ -39,7 +40,7 @@ static int builtin_kmod(UdevEvent *event, int argc, char *argv[], bool test) { r = sd_device_get_property_value(dev, "MODALIAS", &modalias); if (r < 0) - return log_device_warning_errno(dev, r, "Failed to read property \"MODALIAS\"."); + return log_device_warning_errno(dev, r, "Failed to read property \"MODALIAS\": %m"); (void) module_load_and_warn(ctx, modalias, /* verbose = */ false); } else @@ -51,23 +52,28 @@ static int builtin_kmod(UdevEvent *event, int argc, char *argv[], bool test) { /* called at udev startup and reload */ static int builtin_kmod_init(void) { + int r; + if (ctx) return 0; - ctx = kmod_new(NULL, NULL); - if (!ctx) - return -ENOMEM; - log_debug("Loading kernel module index."); - kmod_set_log_fn(ctx, udev_kmod_log, NULL); - kmod_load_resources(ctx); + + r = module_setup_context(&ctx); + if (r < 0) + return log_error_errno(r, "Failed to initialize libkmod context: %m"); + return 0; } /* called on udev shutdown and reload request */ static void builtin_kmod_exit(void) { log_debug("Unload kernel module index."); - ctx = kmod_unref(ctx); + + if (!ctx) + return; + + ctx = sym_kmod_unref(ctx); } /* called every couple of seconds during event activity; 'true' if config has changed */ @@ -75,7 +81,7 @@ static bool builtin_kmod_should_reload(void) { if (!ctx) return false; - if (kmod_validate_resources(ctx) != KMOD_RESOURCES_OK) { + if (sym_kmod_validate_resources(ctx) != KMOD_RESOURCES_OK) { log_debug("Kernel module index needs reloading."); return true; } diff --git a/src/udev/udev-builtin-net_driver.c b/src/udev/udev-builtin-net_driver.c index f1642a4..90a9e8d 100644 --- a/src/udev/udev-builtin-net_driver.c +++ b/src/udev/udev-builtin-net_driver.c @@ -9,7 +9,7 @@ #include "string-util.h" #include "udev-builtin.h" -static int builtin_net_driver_set_driver(UdevEvent *event, int argc, char **argv, bool test) { +static int builtin_net_driver_set_driver(UdevEvent *event, int argc, char **argv) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); _cleanup_close_ int ethtool_fd = -EBADF; _cleanup_free_ char *driver = NULL; @@ -32,7 +32,7 @@ static int builtin_net_driver_set_driver(UdevEvent *event, int argc, char **argv if (r < 0) return log_device_warning_errno(dev, r, "Failed to get driver for '%s': %m", sysname); - return udev_builtin_add_property(event->dev, test, "ID_NET_DRIVER", driver); + return udev_builtin_add_property(event->dev, event->event_mode, "ID_NET_DRIVER", driver); } const UdevBuiltin udev_builtin_net_driver = { diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c index 91b4008..384a1f3 100644 --- a/src/udev/udev-builtin-net_id.c +++ b/src/udev/udev-builtin-net_id.c @@ -12,9 +12,10 @@ * When the code here is changed, man/systemd.net-naming-scheme.xml must be updated too. */ +/* Make sure the net/if.h header is included before any linux/ one */ +#include #include #include -#include #include #include #include @@ -49,12 +50,7 @@ static sd_device *device_skip_virtio(sd_device *dev) { * safely ignore any virtio buses. see * http://lists.linuxfoundation.org/pipermail/virtualization/2015-August/030331.html */ while (dev) { - const char *subsystem; - - if (sd_device_get_subsystem(dev, &subsystem) < 0) - break; - - if (!streq(subsystem, "virtio")) + if (!device_in_subsystem(dev, "virtio")) break; if (sd_device_get_parent(dev, &dev) < 0) @@ -86,22 +82,15 @@ static int get_matching_parent( return -ENODEV; } - if (!strv_isempty(parent_subsystems)) { - const char *subsystem; - - /* check if our direct parent is in an expected subsystem. */ - r = sd_device_get_subsystem(parent, &subsystem); - if (r < 0) - return r; - - if (!strv_contains(parent_subsystems, subsystem)) - return -ENODEV; - } - - if (ret) - *ret = parent; + /* check if our direct parent is in an expected subsystem. */ + STRV_FOREACH(s, parent_subsystems) + if (device_in_subsystem(parent, *s)) { + if (ret) + *ret = parent; + return 0; + } - return 0; + return -ENODEV; } static int get_first_syspath_component(sd_device *dev, const char *prefix, char **ret) { @@ -188,7 +177,7 @@ static int get_dev_port(sd_device *dev, bool fallback_to_dev_id, unsigned *ret) /* Get kernel provided port index for the case when multiple ports on a single PCI function. */ - r = device_get_sysattr_unsigned(dev, "dev_port", &v); + r = device_get_sysattr_unsigned_filtered(dev, "dev_port", &v); if (r < 0) return r; if (r > 0) { @@ -204,7 +193,7 @@ static int get_dev_port(sd_device *dev, bool fallback_to_dev_id, unsigned *ret) if (fallback_to_dev_id) { unsigned iftype; - r = device_get_sysattr_unsigned(dev, "type", &iftype); + r = device_get_sysattr_unsigned_filtered(dev, "type", &iftype); if (r < 0) return r; @@ -212,7 +201,7 @@ static int get_dev_port(sd_device *dev, bool fallback_to_dev_id, unsigned *ret) } if (fallback_to_dev_id) - return device_get_sysattr_unsigned(dev, "dev_id", ret); + return device_get_sysattr_unsigned_filtered(dev, "dev_id", ret); /* Otherwise, return the original index 0. */ *ret = 0; @@ -229,7 +218,7 @@ static int get_port_specifier(sd_device *dev, bool fallback_to_dev_id, char **re assert(ret); /* First, try to use the kernel provided front panel port name for multiple port PCI device. */ - r = sd_device_get_sysattr_value(dev, "phys_port_name", &phys_port_name); + r = device_get_sysattr_value_filtered(dev, "phys_port_name", &phys_port_name); if (r >= 0 && !isempty(phys_port_name)) { if (naming_scheme_has(NAMING_SR_IOV_R)) { int vf_id = -1; @@ -292,10 +281,10 @@ static int pci_get_onboard_index(sd_device *dev, unsigned *ret) { assert(ret); /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */ - r = device_get_sysattr_unsigned(dev, "acpi_index", &idx); + r = device_get_sysattr_unsigned_filtered(dev, "acpi_index", &idx); if (r < 0) /* SMBIOS type 41 — Onboard Devices Extended Information */ - r = device_get_sysattr_unsigned(dev, "index", &idx); + r = device_get_sysattr_unsigned_filtered(dev, "index", &idx); if (r < 0) return log_device_debug_errno(dev, r, "Could not obtain onboard index: %m"); @@ -310,7 +299,7 @@ static int pci_get_onboard_index(sd_device *dev, unsigned *ret) { return 0; } -static int names_pci_onboard(sd_device *dev, sd_device *pci_dev, const char *prefix, const char *suffix, bool test) { +static int names_pci_onboard(sd_device *dev, sd_device *pci_dev, const char *prefix, const char *suffix, EventMode mode) { _cleanup_free_ char *port = NULL; unsigned idx = 0; /* avoid false maybe-uninitialized warning */ int r; @@ -330,7 +319,7 @@ static int names_pci_onboard(sd_device *dev, sd_device *pci_dev, const char *pre char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%so%u%s%s", prefix, idx, strempty(port), strempty(suffix))) - udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_ONBOARD", str); log_device_debug(dev, "Onboard index identifier: index=%u port=%s %s %s", idx, strna(port), @@ -339,7 +328,7 @@ static int names_pci_onboard(sd_device *dev, sd_device *pci_dev, const char *pre return 0; } -static int names_pci_onboard_label(sd_device *dev, sd_device *pci_dev, const char *prefix, bool test) { +static int names_pci_onboard_label(sd_device *dev, sd_device *pci_dev, const char *prefix, EventMode mode) { const char *label; int r; @@ -347,7 +336,7 @@ static int names_pci_onboard_label(sd_device *dev, sd_device *pci_dev, const cha assert(prefix); /* retrieve on-board label from firmware */ - r = sd_device_get_sysattr_value(pci_dev, "label", &label); + r = device_get_sysattr_value_filtered(pci_dev, "label", &label); if (r < 0) return log_device_debug_errno(pci_dev, r, "Failed to get PCI onboard label: %m"); @@ -355,7 +344,7 @@ static int names_pci_onboard_label(sd_device *dev, sd_device *pci_dev, const cha if (snprintf_ok(str, sizeof str, "%s%s", naming_scheme_has(NAMING_LABEL_NOPREFIX) ? "" : prefix, label)) - udev_builtin_add_property(dev, test, "ID_NET_LABEL_ONBOARD", str); + udev_builtin_add_property(dev, mode, "ID_NET_LABEL_ONBOARD", str); log_device_debug(dev, "Onboard label from PCI device: %s", label); return 0; @@ -392,7 +381,7 @@ static int is_pci_multifunction(sd_device *dev) { static bool is_pci_ari_enabled(sd_device *dev) { assert(dev); - return device_get_sysattr_bool(dev, "ari_enabled") > 0; + return device_get_sysattr_bool_filtered(dev, "ari_enabled") > 0; } static bool is_pci_bridge(sd_device *dev) { @@ -400,7 +389,7 @@ static bool is_pci_bridge(sd_device *dev) { assert(dev); - if (sd_device_get_sysattr_value(dev, "modalias", &v) < 0) + if (device_get_sysattr_value_filtered(dev, "modalias", &v) < 0) return false; if (!startswith(v, "pci:")) @@ -442,7 +431,7 @@ static int parse_hotplug_slot_from_function_id(sd_device *dev, int slots_dirfd, return 0; } - if (sd_device_get_sysattr_value(dev, "function_id", &attr) < 0) { + if (device_get_sysattr_value_filtered(dev, "function_id", &attr) < 0) { *ret = 0; return 0; } @@ -505,7 +494,7 @@ static int pci_get_hotplug_slot_from_address( if (!path) return log_oom_debug(); - if (sd_device_get_sysattr_value(pci, path, &address) < 0) + if (device_get_sysattr_value_filtered(pci, path, &address) < 0) continue; /* match slot address with device by stripping the function */ @@ -625,7 +614,7 @@ static int get_pci_slot_specifiers( return 0; } -static int names_pci_slot(sd_device *dev, sd_device *pci_dev, const char *prefix, const char *suffix, bool test) { +static int names_pci_slot(sd_device *dev, sd_device *pci_dev, const char *prefix, const char *suffix, EventMode mode) { _cleanup_free_ char *domain = NULL, *bus_and_slot = NULL, *func = NULL, *port = NULL; uint32_t hotplug_slot = 0; /* avoid false maybe-uninitialized warning */ char str[ALTIFNAMSIZ]; @@ -646,7 +635,7 @@ static int names_pci_slot(sd_device *dev, sd_device *pci_dev, const char *prefix /* compose a name based on the raw kernel's PCI bus, slot numbers */ if (snprintf_ok(str, sizeof str, "%s%s%s%s%s%s", prefix, strempty(domain), bus_and_slot, strempty(func), strempty(port), strempty(suffix))) - udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_PATH", str); log_device_debug(dev, "PCI path identifier: domain=%s bus_and_slot=%s func=%s port=%s %s %s", strna(domain), bus_and_slot, strna(func), strna(port), @@ -662,7 +651,7 @@ static int names_pci_slot(sd_device *dev, sd_device *pci_dev, const char *prefix if (snprintf_ok(str, sizeof str, "%s%ss%"PRIu32"%s%s%s", prefix, strempty(domain), hotplug_slot, strempty(func), strempty(port), strempty(suffix))) - udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_SLOT", str); log_device_debug(dev, "Slot identifier: domain=%s slot=%"PRIu32" func=%s port=%s %s %s", strna(domain), hotplug_slot, strna(func), strna(port), @@ -671,7 +660,7 @@ static int names_pci_slot(sd_device *dev, sd_device *pci_dev, const char *prefix return 0; } -static int names_vio(sd_device *dev, const char *prefix, bool test) { +static int names_vio(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_free_ char *s = NULL; unsigned slotid; int r; @@ -710,13 +699,13 @@ static int names_vio(sd_device *dev, const char *prefix, bool test) { char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%sv%u", prefix, slotid)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_SLOT", str); log_device_debug(dev, "Vio slot identifier: slotid=%u %s %s", slotid, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; } -static int names_platform(sd_device *dev, const char *prefix, bool test) { +static int names_platform(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_free_ char *p = NULL; const char *validchars; char *vendor, *model_str, *instance_str; @@ -743,15 +732,15 @@ static int names_platform(sd_device *dev, const char *prefix, bool test) { * The Vendor (3 or 4 char), followed by hexadecimal model number : instance id. */ if (r == 10 && p[7] == ':') { /* 3 char vendor string */ - vendor = strndupa(p, 3); - model_str = strndupa(p + 3, 4); - instance_str = strndupa(p + 8, 2); + vendor = strndupa_safe(p, 3); + model_str = strndupa_safe(p + 3, 4); + instance_str = strndupa_safe(p + 8, 2); validchars = UPPERCASE_LETTERS; } else if (r == 11 && p[8] == ':') { /* 4 char vendor string */ - vendor = strndupa(p, 4); - model_str = strndupa(p + 4, 4); - instance_str = strndupa(p + 9, 2); + vendor = strndupa_safe(p, 4); + model_str = strndupa_safe(p + 4, 4); + instance_str = strndupa_safe(p + 9, 2); validchars = UPPERCASE_LETTERS DIGITS; } else return -EOPNOTSUPP; @@ -772,13 +761,13 @@ static int names_platform(sd_device *dev, const char *prefix, bool test) { char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%sa%s%xi%u", prefix, vendor, model, instance)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_PATH", str); log_device_debug(dev, "Platform identifier: vendor=%s model=%x instance=%u %s %s", vendor, model, instance, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; } -static int names_devicetree(sd_device *dev, const char *prefix, bool test) { +static int names_devicetree(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_(sd_device_unrefp) sd_device *aliases_dev = NULL, *ofnode_dev = NULL, *devicetree_dev = NULL; const char *ofnode_path, *ofnode_syspath, *devicetree_syspath; sd_device *parent; @@ -825,7 +814,7 @@ static int names_devicetree(sd_device *dev, const char *prefix, bool test) { ofnode_path = path_startswith(ofnode_syspath, devicetree_syspath); if (!ofnode_path) return log_device_debug_errno(ofnode_dev, SYNTHETIC_ERRNO(EINVAL), - "The device '%s' is not a child device of '%s': %m", + "The device '%s' is not a child device of '%s'.", ofnode_syspath, devicetree_syspath); /* Get back our leading / to match the contents of the aliases */ @@ -845,7 +834,7 @@ static int names_devicetree(sd_device *dev, const char *prefix, bool test) { if (!alias_index) continue; - if (sd_device_get_sysattr_value(aliases_dev, alias, &alias_path) < 0) + if (device_get_sysattr_value_filtered(aliases_dev, alias, &alias_path) < 0) continue; if (!path_equal(ofnode_path, alias_path)) @@ -864,13 +853,13 @@ static int names_devicetree(sd_device *dev, const char *prefix, bool test) { } /* ...but make sure we don't have an alias conflict */ - if (i == 0 && sd_device_get_sysattr_value(aliases_dev, conflict, NULL) >= 0) + if (i == 0 && device_get_sysattr_value_filtered(aliases_dev, conflict, NULL) >= 0) return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST), - "Ethernet alias conflict: ethernet and ethernet0 both exist"); + "Ethernet alias conflict: ethernet and ethernet0 both exist."); char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%sd%u", prefix, i)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_ONBOARD", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_ONBOARD", str); log_device_debug(dev, "devicetree identifier: alias_index=%u %s \"%s\"", i, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; @@ -879,7 +868,7 @@ static int names_devicetree(sd_device *dev, const char *prefix, bool test) { return -ENOENT; } -static int names_pci(sd_device *dev, const char *prefix, bool test) { +static int names_pci(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_(sd_device_unrefp) sd_device *physfn_pcidev = NULL; _cleanup_free_ char *virtfn_suffix = NULL; sd_device *parent; @@ -896,10 +885,10 @@ static int names_pci(sd_device *dev, const char *prefix, bool test) { get_virtfn_info(parent, &physfn_pcidev, &virtfn_suffix) >= 0) parent = physfn_pcidev; else - (void) names_pci_onboard_label(dev, parent, prefix, test); + (void) names_pci_onboard_label(dev, parent, prefix, mode); - (void) names_pci_onboard(dev, parent, prefix, virtfn_suffix, test); - (void) names_pci_slot(dev, parent, prefix, virtfn_suffix, test); + (void) names_pci_onboard(dev, parent, prefix, virtfn_suffix, mode); + (void) names_pci_slot(dev, parent, prefix, virtfn_suffix, mode); return 0; } @@ -962,7 +951,7 @@ static int get_usb_specifier(sd_device *dev, char **ret) { return 0; } -static int names_usb(sd_device *dev, const char *prefix, bool test) { +static int names_usb(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_free_ char *suffix = NULL; sd_device *usbdev, *pcidev; int r; @@ -983,7 +972,7 @@ static int names_usb(sd_device *dev, const char *prefix, bool test) { /* If the USB bus is on PCI bus, then suffix the USB specifier to the name based on the PCI bus. */ r = sd_device_get_parent_with_subsystem_devtype(usbdev, "pci", NULL, &pcidev); if (r >= 0) - return names_pci_slot(dev, pcidev, prefix, suffix, test); + return names_pci_slot(dev, pcidev, prefix, suffix, mode); if (r != -ENOENT || !naming_scheme_has(NAMING_USB_HOST)) return log_device_debug_errno(usbdev, r, "Failed to get parent PCI bus: %m"); @@ -991,7 +980,7 @@ static int names_usb(sd_device *dev, const char *prefix, bool test) { /* Otherwise, e.g. on-chip asics that have USB ports, use the USB specifier as is. */ char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%s%s", prefix, suffix)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_PATH", str); return 0; } @@ -1026,7 +1015,7 @@ static int get_bcma_specifier(sd_device *dev, char **ret) { return 0; } -static int names_bcma(sd_device *dev, const char *prefix, bool test) { +static int names_bcma(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_free_ char *suffix = NULL; sd_device *bcmadev, *pcidev; int r; @@ -1046,10 +1035,10 @@ static int names_bcma(sd_device *dev, const char *prefix, bool test) { if (r < 0) return r; - return names_pci_slot(dev, pcidev, prefix, suffix, test); + return names_pci_slot(dev, pcidev, prefix, suffix, mode); } -static int names_ccw(sd_device *dev, const char *prefix, bool test) { +static int names_ccw(sd_device *dev, const char *prefix, EventMode mode) { sd_device *cdev; const char *bus_id; size_t bus_id_start, bus_id_len; @@ -1091,14 +1080,14 @@ static int names_ccw(sd_device *dev, const char *prefix, bool test) { /* Use the CCW bus-ID as network device name */ char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%sc%s", prefix, bus_id)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_PATH", str); log_device_debug(dev, "CCW identifier: ccw_busid=%s %s \"%s\"", bus_id, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; } /* IEEE Organizationally Unique Identifier vendor string */ -static int ieee_oui(sd_device *dev, const struct hw_addr_data *hw_addr, bool test) { +static int ieee_oui(sd_device *dev, const struct hw_addr_data *hw_addr, EventMode mode) { char str[32]; assert(dev); @@ -1121,10 +1110,10 @@ static int ieee_oui(sd_device *dev, const struct hw_addr_data *hw_addr, bool tes hw_addr->bytes[4], hw_addr->bytes[5]); - return udev_builtin_hwdb_lookup(dev, NULL, str, NULL, test); + return udev_builtin_hwdb_lookup(dev, NULL, str, NULL, mode); } -static int names_mac(sd_device *dev, const char *prefix, bool test) { +static int names_mac(sd_device *dev, const char *prefix, EventMode mode) { unsigned iftype, assign_type; struct hw_addr_data hw_addr; const char *s; @@ -1133,7 +1122,7 @@ static int names_mac(sd_device *dev, const char *prefix, bool test) { assert(dev); assert(prefix); - r = device_get_sysattr_unsigned(dev, "type", &iftype); + r = device_get_sysattr_unsigned_filtered(dev, "type", &iftype); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read 'type' attribute: %m"); @@ -1145,7 +1134,7 @@ static int names_mac(sd_device *dev, const char *prefix, bool test) { "Not generating MAC name for infiniband device."); /* check for NET_ADDR_PERM, skip random MAC addresses */ - r = device_get_sysattr_unsigned(dev, "addr_assign_type", &assign_type); + r = device_get_sysattr_unsigned_filtered(dev, "addr_assign_type", &assign_type); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read/parse addr_assign_type: %m"); @@ -1153,7 +1142,7 @@ static int names_mac(sd_device *dev, const char *prefix, bool test) { return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "addr_assign_type=%u, MAC address is not permanent.", assign_type); - r = sd_device_get_sysattr_value(dev, "address", &s); + r = device_get_sysattr_value_filtered(dev, "address", &s); if (r < 0) return log_device_debug_errno(dev, r, "Failed to read 'address' attribute: %m"); @@ -1168,16 +1157,16 @@ static int names_mac(sd_device *dev, const char *prefix, bool test) { char str[ALTIFNAMSIZ]; xsprintf(str, "%sx%s", prefix, HW_ADDR_TO_STR_FULL(&hw_addr, HW_ADDR_TO_STRING_NO_COLON)); - udev_builtin_add_property(dev, test, "ID_NET_NAME_MAC", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_MAC", str); log_device_debug(dev, "MAC address identifier: hw_addr=%s %s %s", HW_ADDR_TO_STR(&hw_addr), special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); - (void) ieee_oui(dev, &hw_addr, test); + (void) ieee_oui(dev, &hw_addr, mode); return 0; } -static int names_netdevsim(sd_device *dev, const char *prefix, bool test) { +static int names_netdevsim(sd_device *dev, const char *prefix, EventMode mode) { sd_device *netdevsimdev; const char *sysnum, *phys_port_name; unsigned addr; @@ -1203,7 +1192,7 @@ static int names_netdevsim(sd_device *dev, const char *prefix, bool test) { if (r < 0) return log_device_debug_errno(netdevsimdev, r, "Failed to parse device sysnum: %m"); - r = sd_device_get_sysattr_value(dev, "phys_port_name", &phys_port_name); + r = device_get_sysattr_value_filtered(dev, "phys_port_name", &phys_port_name); if (r < 0) return log_device_debug_errno(dev, r, "Failed to get 'phys_port_name' attribute: %m"); if (isempty(phys_port_name)) @@ -1212,13 +1201,13 @@ static int names_netdevsim(sd_device *dev, const char *prefix, bool test) { char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%si%un%s", prefix, addr, phys_port_name)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_PATH", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_PATH", str); log_device_debug(dev, "Netdevsim identifier: address=%u, port_name=%s %s %s", addr, phys_port_name, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; } -static int names_xen(sd_device *dev, const char *prefix, bool test) { +static int names_xen(sd_device *dev, const char *prefix, EventMode mode) { _cleanup_free_ char *vif = NULL; const char *p; unsigned id; @@ -1243,7 +1232,7 @@ static int names_xen(sd_device *dev, const char *prefix, bool test) { p = startswith(vif, "vif-"); if (!p) - return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid vif name: %s: %m", vif); + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EINVAL), "Invalid vif name: %s.", vif); r = safe_atou_full(p, SAFE_ATO_REFUSE_PLUS_MINUS | SAFE_ATO_REFUSE_LEADING_ZERO | SAFE_ATO_REFUSE_LEADING_WHITESPACE | 10, &id); @@ -1252,7 +1241,7 @@ static int names_xen(sd_device *dev, const char *prefix, bool test) { char str[ALTIFNAMSIZ]; if (snprintf_ok(str, sizeof str, "%sX%u", prefix, id)) - udev_builtin_add_property(dev, test, "ID_NET_NAME_SLOT", str); + udev_builtin_add_property(dev, mode, "ID_NET_NAME_SLOT", str); log_device_debug(dev, "Xen identifier: id=%u %s %s", id, special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), str + strlen(prefix)); return 0; @@ -1265,22 +1254,16 @@ static int get_ifname_prefix(sd_device *dev, const char **ret) { assert(dev); assert(ret); - r = device_get_sysattr_unsigned(dev, "type", &iftype); + r = device_get_sysattr_unsigned_filtered(dev, "type", &iftype); if (r < 0) return r; /* handle only ARPHRD_ETHER, ARPHRD_SLIP and ARPHRD_INFINIBAND devices */ switch (iftype) { case ARPHRD_ETHER: { - const char *s = NULL; - - r = sd_device_get_devtype(dev, &s); - if (r < 0 && r != -ENOENT) - return r; - - if (streq_ptr(s, "wlan")) + if (device_is_devtype(dev, "wlan")) *ret = "wl"; - else if (streq_ptr(s, "wwan")) + else if (device_is_devtype(dev, "wwan")) *ret = "ww"; else *ret = "en"; @@ -1311,14 +1294,14 @@ static int device_is_stacked(sd_device *dev) { if (r < 0) return r; - r = device_get_sysattr_int(dev, "iflink", &iflink); + r = device_get_sysattr_int_filtered(dev, "iflink", &iflink); if (r < 0) return r; return ifindex != iflink; } -static int builtin_net_id(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_net_id(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *prefix; int r; @@ -1336,18 +1319,18 @@ static int builtin_net_id(UdevEvent *event, int argc, char *argv[], bool test) { return 0; } - udev_builtin_add_property(dev, test, "ID_NET_NAMING_SCHEME", naming_scheme()->name); - - (void) names_mac(dev, prefix, test); - (void) names_devicetree(dev, prefix, test); - (void) names_ccw(dev, prefix, test); - (void) names_vio(dev, prefix, test); - (void) names_platform(dev, prefix, test); - (void) names_netdevsim(dev, prefix, test); - (void) names_xen(dev, prefix, test); - (void) names_pci(dev, prefix, test); - (void) names_usb(dev, prefix, test); - (void) names_bcma(dev, prefix, test); + udev_builtin_add_property(dev, event->event_mode, "ID_NET_NAMING_SCHEME", naming_scheme()->name); + + (void) names_mac(dev, prefix, event->event_mode); + (void) names_devicetree(dev, prefix, event->event_mode); + (void) names_ccw(dev, prefix, event->event_mode); + (void) names_vio(dev, prefix, event->event_mode); + (void) names_platform(dev, prefix, event->event_mode); + (void) names_netdevsim(dev, prefix, event->event_mode); + (void) names_xen(dev, prefix, event->event_mode); + (void) names_pci(dev, prefix, event->event_mode); + (void) names_usb(dev, prefix, event->event_mode); + (void) names_bcma(dev, prefix, event->event_mode); return 0; } diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c index a308a21..8cfcaa9 100644 --- a/src/udev/udev-builtin-net_setup_link.c +++ b/src/udev/udev-builtin-net_setup_link.c @@ -1,8 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "alloc-util.h" +#include "device-private.h" #include "device-util.h" -#include "escape.h" #include "errno-util.h" #include "link-config.h" #include "log.h" @@ -12,16 +12,36 @@ static LinkConfigContext *ctx = NULL; -static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) { +static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); _cleanup_(link_freep) Link *link = NULL; - _cleanup_free_ char *joined = NULL; int r; if (argc > 1) return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); - r = link_new(ctx, &event->rtnl, dev, &link); + sd_device_action_t action; + r = sd_device_get_action(dev, &action); + if (r < 0) + return log_device_error_errno(dev, r, "Failed to get action: %m"); + + if (!IN_SET(action, SD_DEVICE_ADD, SD_DEVICE_BIND, SD_DEVICE_MOVE)) { + log_device_debug(dev, "Not applying .link settings on '%s' uevent.", + device_action_to_string(action)); + + /* Import previously assigned .link file name. */ + (void) udev_builtin_import_property(dev, event->dev_db_clone, event->event_mode, "ID_NET_LINK_FILE"); + (void) udev_builtin_import_property(dev, event->dev_db_clone, event->event_mode, "ID_NET_LINK_FILE_DROPINS"); + + /* Set ID_NET_NAME= with the current interface name. */ + const char *value; + if (sd_device_get_sysname(dev, &value) >= 0) + (void) udev_builtin_add_property(dev, event->event_mode, "ID_NET_NAME", value); + + return 0; + } + + r = link_new(ctx, &event->rtnl, dev, event->dev_db_clone, &link); if (r == -ENODEV) { log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring."); return 0; @@ -39,31 +59,14 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool return log_device_error_errno(dev, r, "Failed to get link config: %m"); } - r = link_apply_config(ctx, &event->rtnl, link); + r = link_apply_config(ctx, &event->rtnl, link, event->event_mode); if (r == -ENODEV) log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring."); else if (r < 0) log_device_warning_errno(dev, r, "Could not apply link configuration, ignoring: %m"); - udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE", link->config->filename); - if (link->new_name) - udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name); - event->altnames = TAKE_PTR(link->altnames); - STRV_FOREACH(d, link->config->dropins) { - _cleanup_free_ char *escaped = NULL; - - escaped = xescape(*d, ":"); - if (!escaped) - return log_oom(); - - if (!strextend_with_separator(&joined, ":", escaped)) - return log_oom(); - } - - udev_builtin_add_property(dev, test, "ID_NET_LINK_FILE_DROPINS", joined); - return 0; } diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c index 467c9a6..a23b32d 100644 --- a/src/udev/udev-builtin-path_id.c +++ b/src/udev/udev-builtin-path_id.c @@ -95,12 +95,7 @@ static sd_device *skip_subsystem(sd_device *dev, const char *subsys) { */ for (parent = dev; ; ) { - const char *subsystem; - - if (sd_device_get_subsystem(parent, &subsystem) < 0) - break; - - if (!streq(subsystem, subsys)) + if (!device_in_subsystem(parent, subsys)) break; dev = parent; @@ -191,7 +186,7 @@ static sd_device *handle_scsi_sas(sd_device *parent, char **path) { return NULL; /* Check if we are simple disk */ - if (strncmp(phy_count, "1", 2) != 0) + if (!streq(phy_count, "1")) return handle_scsi_sas_wide_port(parent, path); /* Get connected phy */ @@ -417,10 +412,9 @@ static sd_device *handle_scsi_hyperv(sd_device *parent, char **path, size_t guid } static sd_device *handle_scsi(sd_device *parent, char **path, char **compat_path, bool *supported_parent) { - const char *devtype, *id, *name; + const char *id, *name; - if (sd_device_get_devtype(parent, &devtype) < 0 || - !streq(devtype, "scsi_device")) + if (!device_is_devtype(parent, "scsi_device")) return parent; /* firewire */ @@ -532,12 +526,10 @@ static int get_usb_revision(sd_device *dev) { } static sd_device *handle_usb(sd_device *parent, char **path) { - const char *devtype, *str, *port; + const char *str, *port; int r; - if (sd_device_get_devtype(parent, &devtype) < 0) - return parent; - if (!STR_IN_SET(devtype, "usb_interface", "usb_device")) + if (!device_is_devtype(parent, "usb_interface") && !device_is_devtype(parent, "usb_device")) return parent; if (sd_device_get_sysname(parent, &str) < 0) @@ -632,7 +624,7 @@ static int find_real_nvme_parent(sd_device *dev, sd_device **ret) { return -ENXIO; end += strspn(end, DIGITS); - sysname = strndupa(sysname, end - sysname); + sysname = strndupa_safe(sysname, end - sysname); r = sd_device_new_from_subsystem_sysname(&nvme, "nvme", sysname); if (r < 0) @@ -650,9 +642,8 @@ static int find_real_nvme_parent(sd_device *dev, sd_device **ret) { return 0; } -static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) { +static void add_id_with_usb_revision(sd_device *dev, EventMode mode, char *path) { char *p; - int r; assert(dev); assert(path); @@ -668,18 +659,15 @@ static void add_id_with_usb_revision(sd_device *dev, bool test, char *path) { if (p[1] != '-') return; - r = udev_builtin_add_property(dev, test, "ID_PATH_WITH_USB_REVISION", path); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to add ID_PATH_WITH_USB_REVISION property, ignoring: %m"); + (void) udev_builtin_add_property(dev, mode, "ID_PATH_WITH_USB_REVISION", path); /* Drop the USB revision specifier for backward compatibility. */ memmove(p - 1, p + 1, strlen(p + 1) + 1); } -static void add_id_tag(sd_device *dev, bool test, const char *path) { +static void add_id_tag(sd_device *dev, EventMode mode, const char *path) { char tag[UDEV_NAME_SIZE]; size_t i = 0; - int r; /* compose valid udev tag name */ for (const char *p = path; *p; p++) { @@ -705,115 +693,111 @@ static void add_id_tag(sd_device *dev, bool test, const char *path) { i--; tag[i] = '\0'; - r = udev_builtin_add_property(dev, test, "ID_PATH_TAG", tag); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to add ID_PATH_TAG property, ignoring: %m"); + (void) udev_builtin_add_property(dev, mode, "ID_PATH_TAG", tag); } -static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_path_id(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); _cleanup_(sd_device_unrefp) sd_device *dev_other_branch = NULL; _cleanup_free_ char *path = NULL, *compat_path = NULL; bool supported_transport = false, supported_parent = false; - const char *subsystem; int r; /* walk up the chain of devices and compose path */ for (sd_device *parent = dev; parent; ) { - const char *subsys, *sysname; + const char *sysname; - if (sd_device_get_subsystem(parent, &subsys) < 0 || - sd_device_get_sysname(parent, &sysname) < 0) { + if (sd_device_get_sysname(parent, &sysname) < 0) { ; - } else if (streq(subsys, "scsi_tape")) { + } else if (device_in_subsystem(parent, "scsi_tape")) { handle_scsi_tape(parent, &path); - } else if (streq(subsys, "scsi")) { + } else if (device_in_subsystem(parent, "scsi")) { parent = handle_scsi(parent, &path, &compat_path, &supported_parent); supported_transport = true; - } else if (streq(subsys, "cciss")) { + } else if (device_in_subsystem(parent, "cciss")) { parent = handle_cciss(parent, &path); supported_transport = true; - } else if (streq(subsys, "usb")) { + } else if (device_in_subsystem(parent, "usb")) { parent = handle_usb(parent, &path); supported_transport = true; - } else if (streq(subsys, "bcma")) { + } else if (device_in_subsystem(parent, "bcma")) { parent = handle_bcma(parent, &path); supported_transport = true; - } else if (streq(subsys, "serio")) { + } else if (device_in_subsystem(parent, "serio")) { const char *sysnum; if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { path_prepend(&path, "serio-%s", sysnum); parent = skip_subsystem(parent, "serio"); } - } else if (streq(subsys, "pci")) { + } else if (device_in_subsystem(parent, "pci")) { path_prepend(&path, "pci-%s", sysname); if (compat_path) path_prepend(&compat_path, "pci-%s", sysname); parent = skip_subsystem(parent, "pci"); supported_parent = true; - } else if (streq(subsys, "platform")) { + } else if (device_in_subsystem(parent, "platform")) { path_prepend(&path, "platform-%s", sysname); if (compat_path) path_prepend(&compat_path, "platform-%s", sysname); parent = skip_subsystem(parent, "platform"); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "amba")) { + } else if (device_in_subsystem(parent, "amba")) { path_prepend(&path, "amba-%s", sysname); if (compat_path) path_prepend(&compat_path, "amba-%s", sysname); parent = skip_subsystem(parent, "amba"); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "acpi")) { + } else if (device_in_subsystem(parent, "acpi")) { path_prepend(&path, "acpi-%s", sysname); if (compat_path) path_prepend(&compat_path, "acpi-%s", sysname); parent = skip_subsystem(parent, "acpi"); supported_parent = true; - } else if (streq(subsys, "xen")) { + } else if (device_in_subsystem(parent, "xen")) { path_prepend(&path, "xen-%s", sysname); if (compat_path) path_prepend(&compat_path, "xen-%s", sysname); parent = skip_subsystem(parent, "xen"); supported_parent = true; - } else if (streq(subsys, "virtio")) { + } else if (device_in_subsystem(parent, "virtio")) { parent = skip_subsystem(parent, "virtio"); supported_transport = true; - } else if (streq(subsys, "scm")) { + } else if (device_in_subsystem(parent, "scm")) { path_prepend(&path, "scm-%s", sysname); if (compat_path) path_prepend(&compat_path, "scm-%s", sysname); parent = skip_subsystem(parent, "scm"); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "ccw")) { + } else if (device_in_subsystem(parent, "ccw")) { path_prepend(&path, "ccw-%s", sysname); if (compat_path) path_prepend(&compat_path, "ccw-%s", sysname); parent = skip_subsystem(parent, "ccw"); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "ccwgroup")) { + } else if (device_in_subsystem(parent, "ccwgroup")) { path_prepend(&path, "ccwgroup-%s", sysname); if (compat_path) path_prepend(&compat_path, "ccwgroup-%s", sysname); parent = skip_subsystem(parent, "ccwgroup"); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "ap")) { + } else if (device_in_subsystem(parent, "ap")) { parent = handle_ap(parent, &path); supported_transport = true; supported_parent = true; - } else if (streq(subsys, "iucv")) { + } else if (device_in_subsystem(parent, "iucv")) { path_prepend(&path, "iucv-%s", sysname); if (compat_path) path_prepend(&compat_path, "iucv-%s", sysname); parent = skip_subsystem(parent, "iucv"); supported_transport = true; supported_parent = true; - } else if (STR_IN_SET(subsys, "nvme", "nvme-subsystem")) { + } else if (device_in_subsystem(parent, "nvme") || device_in_subsystem(parent, "nvme-subsystem")) { const char *nsid; if (sd_device_get_sysattr_value(dev, "nsid", &nsid) >= 0) { @@ -821,7 +805,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) if (compat_path) path_prepend(&compat_path, "nvme-%s", nsid); - if (streq(subsys, "nvme-subsystem")) { + if (device_in_subsystem(parent, "nvme-subsystem")) { r = find_real_nvme_parent(dev, &dev_other_branch); if (r < 0) return r; @@ -833,7 +817,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) supported_parent = true; supported_transport = true; } - } else if (streq(subsys, "spi")) { + } else if (device_in_subsystem(parent, "spi")) { const char *sysnum; if (sd_device_get_sysnum(parent, &sysnum) >= 0 && sysnum) { @@ -864,18 +848,14 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) * devices do not expose their buses and do not provide a unique * and predictable name that way. */ - if (sd_device_get_subsystem(dev, &subsystem) >= 0 && - streq(subsystem, "block") && - !supported_transport) + if (device_in_subsystem(dev, "block") && !supported_transport) return -ENOENT; - add_id_with_usb_revision(dev, test, path); + add_id_with_usb_revision(dev, event->event_mode, path); - r = udev_builtin_add_property(dev, test, "ID_PATH", path); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to add ID_PATH property, ignoring: %m"); + (void) udev_builtin_add_property(dev, event->event_mode, "ID_PATH", path); - add_id_tag(dev, test, path); + add_id_tag(dev, event->event_mode, path); /* * Compatible link generation for ATA devices @@ -883,7 +863,7 @@ static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) * ID_PATH_ATA_COMPAT */ if (compat_path) - udev_builtin_add_property(dev, test, "ID_PATH_ATA_COMPAT", compat_path); + (void) udev_builtin_add_property(dev, event->event_mode, "ID_PATH_ATA_COMPAT", compat_path); return 0; } diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c index da42ef5..805d048 100644 --- a/src/udev/udev-builtin-uaccess.c +++ b/src/udev/udev-builtin-uaccess.c @@ -17,13 +17,18 @@ #include "log.h" #include "udev-builtin.h" -static int builtin_uaccess(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_uaccess(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); const char *path = NULL, *seat; bool changed_acl = false; uid_t uid; int r; + if (event->event_mode != EVENT_UDEV_WORKER) { + log_device_debug(dev, "Running in test mode, skipping execution of 'uaccess' builtin command."); + return 0; + } + umask(0022); /* don't muck around with ACLs when the system is not running systemd */ diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c index 8e83c9c..2413f9c 100644 --- a/src/udev/udev-builtin-usb_id.c +++ b/src/udev/udev-builtin-usb_id.c @@ -224,7 +224,7 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { * 6.) If the device supplies a serial number, this number * is concatenated with the identification with an underscore '_'. */ -static int builtin_usb_id(UdevEvent *event, int argc, char *argv[], bool test) { +static int builtin_usb_id(UdevEvent *event, int argc, char *argv[]) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); char vendor_str[64] = ""; char vendor_str_enc[256]; @@ -248,7 +248,7 @@ static int builtin_usb_id(UdevEvent *event, int argc, char *argv[], bool test) { size_t l; char *s; - const char *syspath, *sysname, *devtype, *interface_syspath; + const char *syspath, *sysname, *interface_syspath; int r; r = sd_device_get_syspath(dev, &syspath); @@ -260,7 +260,7 @@ static int builtin_usb_id(UdevEvent *event, int argc, char *argv[], bool test) { return r; /* shortcut, if we are called directly for a "usb_device" type */ - if (sd_device_get_devtype(dev, &devtype) >= 0 && streq(devtype, "usb_device")) { + if (device_is_devtype(dev, "usb_device")) { dev_if_packed_info(dev, packed_if_str, sizeof(packed_if_str)); dev_usb = dev; goto fallback; @@ -429,55 +429,55 @@ fallback: if (sd_device_get_property_value(dev, "ID_BUS", NULL) >= 0) log_device_debug(dev, "ID_BUS property is already set, setting only properties prefixed with \"ID_USB_\"."); else { - udev_builtin_add_property(dev, test, "ID_BUS", "usb"); + udev_builtin_add_property(dev, event->event_mode, "ID_BUS", "usb"); - udev_builtin_add_property(dev, test, "ID_MODEL", model_str); - udev_builtin_add_property(dev, test, "ID_MODEL_ENC", model_str_enc); - udev_builtin_add_property(dev, test, "ID_MODEL_ID", product_id); + udev_builtin_add_property(dev, event->event_mode, "ID_MODEL", model_str); + udev_builtin_add_property(dev, event->event_mode, "ID_MODEL_ENC", model_str_enc); + udev_builtin_add_property(dev, event->event_mode, "ID_MODEL_ID", product_id); - udev_builtin_add_property(dev, test, "ID_SERIAL", serial); + udev_builtin_add_property(dev, event->event_mode, "ID_SERIAL", serial); if (!isempty(serial_str)) - udev_builtin_add_property(dev, test, "ID_SERIAL_SHORT", serial_str); + udev_builtin_add_property(dev, event->event_mode, "ID_SERIAL_SHORT", serial_str); - udev_builtin_add_property(dev, test, "ID_VENDOR", vendor_str); - udev_builtin_add_property(dev, test, "ID_VENDOR_ENC", vendor_str_enc); - udev_builtin_add_property(dev, test, "ID_VENDOR_ID", vendor_id); + udev_builtin_add_property(dev, event->event_mode, "ID_VENDOR", vendor_str); + udev_builtin_add_property(dev, event->event_mode, "ID_VENDOR_ENC", vendor_str_enc); + udev_builtin_add_property(dev, event->event_mode, "ID_VENDOR_ID", vendor_id); - udev_builtin_add_property(dev, test, "ID_REVISION", revision_str); + udev_builtin_add_property(dev, event->event_mode, "ID_REVISION", revision_str); if (!isempty(type_str)) - udev_builtin_add_property(dev, test, "ID_TYPE", type_str); + udev_builtin_add_property(dev, event->event_mode, "ID_TYPE", type_str); if (!isempty(instance_str)) - udev_builtin_add_property(dev, test, "ID_INSTANCE", instance_str); + udev_builtin_add_property(dev, event->event_mode, "ID_INSTANCE", instance_str); } /* Also export the same values in the above by prefixing ID_USB_. */ - udev_builtin_add_property(dev, test, "ID_USB_MODEL", model_str); - udev_builtin_add_property(dev, test, "ID_USB_MODEL_ENC", model_str_enc); - udev_builtin_add_property(dev, test, "ID_USB_MODEL_ID", product_id); - udev_builtin_add_property(dev, test, "ID_USB_SERIAL", serial); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_MODEL", model_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_MODEL_ENC", model_str_enc); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_MODEL_ID", product_id); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_SERIAL", serial); if (!isempty(serial_str)) - udev_builtin_add_property(dev, test, "ID_USB_SERIAL_SHORT", serial_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_SERIAL_SHORT", serial_str); - udev_builtin_add_property(dev, test, "ID_USB_VENDOR", vendor_str); - udev_builtin_add_property(dev, test, "ID_USB_VENDOR_ENC", vendor_str_enc); - udev_builtin_add_property(dev, test, "ID_USB_VENDOR_ID", vendor_id); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_VENDOR", vendor_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_VENDOR_ENC", vendor_str_enc); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_VENDOR_ID", vendor_id); - udev_builtin_add_property(dev, test, "ID_USB_REVISION", revision_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_REVISION", revision_str); if (!isempty(type_str)) - udev_builtin_add_property(dev, test, "ID_USB_TYPE", type_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_TYPE", type_str); if (!isempty(instance_str)) - udev_builtin_add_property(dev, test, "ID_USB_INSTANCE", instance_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_INSTANCE", instance_str); if (!isempty(packed_if_str)) - udev_builtin_add_property(dev, test, "ID_USB_INTERFACES", packed_if_str); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_INTERFACES", packed_if_str); if (ifnum) - udev_builtin_add_property(dev, test, "ID_USB_INTERFACE_NUM", ifnum); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_INTERFACE_NUM", ifnum); if (driver) - udev_builtin_add_property(dev, test, "ID_USB_DRIVER", driver); + udev_builtin_add_property(dev, event->event_mode, "ID_USB_DRIVER", driver); return 0; } diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c index bcc2018..1a1cb37 100644 --- a/src/udev/udev-builtin.c +++ b/src/udev/udev-builtin.c @@ -99,7 +99,7 @@ UdevBuiltinCommand udev_builtin_lookup(const char *command) { return _UDEV_BUILTIN_INVALID; } -int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test) { +int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command) { _cleanup_strv_free_ char **argv = NULL; int r; @@ -117,10 +117,10 @@ int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *comma /* we need '0' here to reset the internal state */ optind = 0; - return builtins[cmd]->cmd(event, strv_length(argv), argv, test); + return builtins[cmd]->cmd(event, strv_length(argv), argv); } -int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val) { +int udev_builtin_add_property(sd_device *dev, EventMode mode, const char *key, const char *val) { int r; assert(dev); @@ -131,13 +131,13 @@ int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const return log_device_debug_errno(dev, r, "Failed to add property '%s%s%s'", key, val ? "=" : "", strempty(val)); - if (test) + if (mode == EVENT_UDEVADM_TEST_BUILTIN) printf("%s=%s\n", key, strempty(val)); return 0; } -int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) { +int udev_builtin_add_propertyf(sd_device *dev, EventMode mode, const char *key, const char *valf, ...) { _cleanup_free_ char *val = NULL; va_list ap; int r; @@ -152,5 +152,28 @@ int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const if (r < 0) return log_oom_debug(); - return udev_builtin_add_property(dev, test, key, val); + return udev_builtin_add_property(dev, mode, key, val); +} + +int udev_builtin_import_property(sd_device *dev, sd_device *src, EventMode mode, const char *key) { + const char *val; + int r; + + assert(dev); + assert(key); + + if (!src) + return 0; + + r = sd_device_get_property_value(src, key, &val); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_device_debug_errno(src, r, "Failed to get property \"%s\", ignoring: %m", key); + + r = udev_builtin_add_property(dev, mode, key, val); + if (r < 0) + return r; + + return 1; } diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h index fcd41d6..0d82beb 100644 --- a/src/udev/udev-builtin.h +++ b/src/udev/udev-builtin.h @@ -34,7 +34,7 @@ typedef enum UdevBuiltinCommand { typedef struct UdevBuiltin { const char *name; - int (*cmd)(UdevEvent *event, int argc, char *argv[], bool test); + int (*cmd)(UdevEvent *event, int argc, char *argv[]); const char *help; int (*init)(void); void (*exit)(void); @@ -79,10 +79,11 @@ void udev_builtin_exit(void); UdevBuiltinCommand udev_builtin_lookup(const char *command); const char *udev_builtin_name(UdevBuiltinCommand cmd); bool udev_builtin_run_once(UdevBuiltinCommand cmd); -int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test); +int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command); void udev_builtin_list(void); bool udev_builtin_should_reload(void); -int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val); -int udev_builtin_add_propertyf(sd_device *dev, bool test, const char *key, const char *valf, ...) _printf_(4, 5); +int udev_builtin_add_property(sd_device *dev, EventMode mode, const char *key, const char *val); +int udev_builtin_add_propertyf(sd_device *dev, EventMode mode, const char *key, const char *valf, ...) _printf_(4, 5); +int udev_builtin_import_property(sd_device *dev, sd_device *src, EventMode mode, const char *key); int udev_builtin_hwdb_lookup(sd_device *dev, const char *prefix, const char *modalias, - const char *filter, bool test); + const char *filter, EventMode mode); diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c index ed22c8b..607071a 100644 --- a/src/udev/udev-event.c +++ b/src/udev/udev-event.c @@ -14,10 +14,10 @@ #include "udev-node.h" #include "udev-trace.h" #include "udev-util.h" -#include "udev-watch.h" #include "user-util.h" -UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rtnl, int log_level) { +UdevEvent *udev_event_new(sd_device *dev, UdevWorker *worker, EventMode mode) { + int log_level = worker ? worker->log_level : log_get_max_level(); UdevEvent *event; assert(dev); @@ -27,15 +27,16 @@ UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rt return NULL; *event = (UdevEvent) { + .worker = worker, + .rtnl = worker ? sd_netlink_ref(worker->rtnl) : NULL, .dev = sd_device_ref(dev), .birth_usec = now(CLOCK_MONOTONIC), - .exec_delay_usec = exec_delay_usec, - .rtnl = sd_netlink_ref(rtnl), .uid = UID_INVALID, .gid = GID_INVALID, .mode = MODE_INVALID, .log_level_was_debug = log_level == LOG_DEBUG, .default_log_level = log_level, + .event_mode = mode, }; return event; @@ -110,6 +111,9 @@ static int rename_netif(UdevEvent *event) { assert(event); + if (!EVENT_MODE_DESTRUCTIVE(event)) + return 0; + if (!event->name) return 0; /* No new name is requested. */ @@ -171,6 +175,12 @@ static int rename_netif(UdevEvent *event) { goto revert; } + r = device_add_property(event->dev_db_clone, "ID_PROCESSING", "1"); + if (r < 0) { + log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_PROCESSING' property: %m"); + goto revert; + } + r = device_update_db(event->dev_db_clone); if (r < 0) { log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); @@ -197,6 +207,7 @@ static int rename_netif(UdevEvent *event) { revert: /* Restore 'dev_db_clone' */ (void) device_add_property(event->dev_db_clone, "ID_RENAMING", NULL); + (void) device_add_property(event->dev_db_clone, "ID_PROCESSING", NULL); (void) device_update_db(event->dev_db_clone); /* Restore 'dev' */ @@ -215,6 +226,9 @@ static int assign_altnames(UdevEvent *event) { int ifindex, r; const char *s; + if (!EVENT_MODE_DESTRUCTIVE(event)) + return 0; + if (strv_isempty(event->altnames)) return 0; @@ -243,6 +257,9 @@ static int update_devnode(UdevEvent *event) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; + if (!EVENT_MODE_DESTRUCTIVE(event)) + return 0; + r = sd_device_get_devnum(dev, NULL); if (r == -ENOENT) return 0; @@ -276,14 +293,7 @@ static int update_devnode(UdevEvent *event) { return udev_node_update(dev, event->dev_db_clone); } -static int event_execute_rules_on_remove( - UdevEvent *event, - int inotify_fd, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list, - UdevRules *rules) { - +static int event_execute_rules_on_remove(UdevEvent *event, UdevRules *rules) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; @@ -291,22 +301,22 @@ static int event_execute_rules_on_remove( if (r < 0) log_device_debug_errno(dev, r, "Failed to read database under /run/udev/data/: %m"); - r = device_tag_index(dev, NULL, false); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to remove corresponding tag files under /run/udev/tag/, ignoring: %m"); - - r = device_delete_db(dev); - if (r < 0) - log_device_debug_errno(dev, r, "Failed to delete database under /run/udev/data/, ignoring: %m"); + if (EVENT_MODE_DESTRUCTIVE(event)) { + r = device_tag_index(dev, NULL, false); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to remove corresponding tag files under /run/udev/tag/, ignoring: %m"); - r = udev_watch_end(inotify_fd, dev); - if (r < 0) - log_device_warning_errno(dev, r, "Failed to remove inotify watch, ignoring: %m"); + r = device_delete_db(dev); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to delete database under /run/udev/data/, ignoring: %m"); + } - r = udev_rules_apply_to_event(rules, event, timeout_usec, timeout_signal, properties_list); + r = udev_rules_apply_to_event(rules, event); - if (sd_device_get_devnum(dev, NULL) >= 0) - (void) udev_node_remove(dev); + if (EVENT_MODE_DESTRUCTIVE(event)) { + if (sd_device_get_devnum(dev, NULL) >= 0) + (void) udev_node_remove(dev); + } return r; } @@ -328,19 +338,45 @@ static int copy_all_tags(sd_device *d, sd_device *s) { return 0; } -int udev_event_execute_rules( - UdevEvent *event, - int inotify_fd, /* This may be negative */ - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list, - UdevRules *rules) { +static int update_clone(UdevEvent *event) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev_db_clone); + int r; + + if (!EVENT_MODE_DESTRUCTIVE(event)) + return 0; + + /* Drop previously added property for safety to make IMPORT{db}="ID_RENAMING" not work. This is + * mostly for 'move' uevent, but let's do unconditionally. Why? If a network interface is renamed in + * initrd, then udevd may lose the 'move' uevent during switching root. Usually, we do not set the + * persistent flag for network interfaces, but user may set it. Just for safety. */ + r = device_add_property(dev, "ID_RENAMING", NULL); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to remove 'ID_RENAMING' property: %m"); + + /* If the database file already exists, append ID_PROCESSING property to the existing database, + * to indicate that the device is being processed by udevd. */ + if (device_has_db(dev) > 0) { + r = device_add_property(dev, "ID_PROCESSING", "1"); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to add 'ID_PROCESSING' property: %m"); + + r = device_update_db(dev); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to update database under /run/udev/data/: %m"); + } + + return 0; +} + +int udev_event_execute_rules(UdevEvent *event, UdevRules *rules) { sd_device_action_t action; sd_device *dev; int r; - dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + assert(event); + assert(IN_SET(event->event_mode, EVENT_UDEV_WORKER, EVENT_UDEVADM_TEST, EVENT_TEST_RULE_RUNNER)); + dev = ASSERT_PTR(event->dev); assert(rules); r = sd_device_get_action(dev, &action); @@ -348,12 +384,7 @@ int udev_event_execute_rules( return log_device_error_errno(dev, r, "Failed to get ACTION: %m"); if (action == SD_DEVICE_REMOVE) - return event_execute_rules_on_remove(event, inotify_fd, timeout_usec, timeout_signal, properties_list, rules); - - /* Disable watch during event processing. */ - r = udev_watch_end(inotify_fd, dev); - if (r < 0) - log_device_warning_errno(dev, r, "Failed to remove inotify watch, ignoring: %m"); + return event_execute_rules_on_remove(event, rules); r = device_clone_with_db(dev, &event->dev_db_clone); if (r < 0) @@ -363,17 +394,13 @@ int udev_event_execute_rules( if (r < 0) log_device_warning_errno(dev, r, "Failed to copy all tags from old database entry, ignoring: %m"); - /* Drop previously added property for safety to make IMPORT{db}="ID_RENAMING" not work. This is - * mostly for 'move' uevent, but let's do unconditionally. Why? If a network interface is renamed in - * initrd, then udevd may lose the 'move' uevent during switching root. Usually, we do not set the - * persistent flag for network interfaces, but user may set it. Just for safety. */ - r = device_add_property(event->dev_db_clone, "ID_RENAMING", NULL); + r = update_clone(event); if (r < 0) - return log_device_debug_errno(dev, r, "Failed to remove 'ID_RENAMING' property: %m"); + return r; DEVICE_TRACE_POINT(rules_start, dev); - r = udev_rules_apply_to_event(rules, event, timeout_usec, timeout_signal, properties_list); + r = udev_rules_apply_to_event(rules, event); if (r < 0) return log_device_debug_errno(dev, r, "Failed to apply udev rules: %m"); @@ -396,14 +423,27 @@ int udev_event_execute_rules( if (r < 0) return log_device_debug_errno(dev, r, "Failed to set initialization timestamp: %m"); - /* (re)write database file */ - r = device_tag_index(dev, event->dev_db_clone, true); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m"); + if (EVENT_MODE_DESTRUCTIVE(event)) { + /* (re)write database file */ + r = device_tag_index(dev, event->dev_db_clone, true); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to update tags under /run/udev/tag/: %m"); + } - r = device_update_db(dev); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to update database under /run/udev/data/: %m"); + /* If the database file for the device will be created below, add ID_PROCESSING=1 to indicate that + * the device is still being processed by udevd, as commands specified in RUN are invoked after + * the database is created. See issue #30056. */ + if (device_should_have_db(dev) && !ordered_hashmap_isempty(event->run_list)) { + r = device_add_property(dev, "ID_PROCESSING", "1"); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to add 'ID_PROCESSING' property: %m"); + } + + if (EVENT_MODE_DESTRUCTIVE(event)) { + r = device_update_db(dev); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to update database under /run/udev/data/: %m"); + } device_set_is_initialized(dev); diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h index 6b94fd0..3dc8936 100644 --- a/src/udev/udev-event.h +++ b/src/udev/udev-event.h @@ -15,9 +15,21 @@ #include "macro.h" #include "time-util.h" #include "udev-rules.h" +#include "udev-worker.h" #include "user-util.h" +typedef enum EventMode { + EVENT_UDEV_WORKER, + EVENT_UDEVADM_TEST, + EVENT_UDEVADM_TEST_BUILTIN, + EVENT_TEST_RULE_RUNNER, + EVENT_TEST_SPAWN, +} EventMode; + typedef struct UdevEvent { + UdevWorker *worker; + sd_netlink *rtnl; + sd_device *dev; sd_device *dev_parent; sd_device *dev_db_clone; @@ -29,9 +41,7 @@ typedef struct UdevEvent { gid_t gid; OrderedHashmap *seclabel_list; OrderedHashmap *run_list; - usec_t exec_delay_usec; usec_t birth_usec; - sd_netlink *rtnl; unsigned builtin_run; unsigned builtin_ret; UdevRuleEscapeType esc:8; @@ -45,16 +55,16 @@ typedef struct UdevEvent { bool run_final; bool log_level_was_debug; int default_log_level; + EventMode event_mode; } UdevEvent; -UdevEvent *udev_event_new(sd_device *dev, usec_t exec_delay_usec, sd_netlink *rtnl, int log_level); +UdevEvent *udev_event_new(sd_device *dev, UdevWorker *worker, EventMode mode); UdevEvent *udev_event_free(UdevEvent *event); DEFINE_TRIVIAL_CLEANUP_FUNC(UdevEvent*, udev_event_free); -int udev_event_execute_rules( - UdevEvent *event, - int inotify_fd, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list, - UdevRules *rules); +int udev_event_execute_rules(UdevEvent *event, UdevRules *rules); + +static inline bool EVENT_MODE_DESTRUCTIVE(UdevEvent *event) { + assert(event); + return IN_SET(event->event_mode, EVENT_UDEV_WORKER, EVENT_TEST_RULE_RUNNER); +} diff --git a/src/udev/udev-format.c b/src/udev/udev-format.c index 05ed9fd..bd74ab0 100644 --- a/src/udev/udev-format.c +++ b/src/udev/udev-format.c @@ -156,7 +156,6 @@ static ssize_t udev_event_subst_format( const char *attr, char *dest, size_t l, - Hashmap *global_props, bool *ret_truncated) { sd_device *parent, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); @@ -349,7 +348,7 @@ static ssize_t udev_event_subst_format( case FORMAT_SUBST_ENV: if (isempty(attr)) return -EINVAL; - r = device_get_property_value_with_fallback(dev, attr, global_props, &val); + r = device_get_property_value_with_fallback(dev, attr, event->worker ? event->worker->properties : NULL, &val); if (r == -ENOENT) goto null_terminate; if (r < 0) @@ -379,7 +378,6 @@ size_t udev_event_apply_format( char *dest, size_t size, bool replace_whitespace, - Hashmap *global_props, bool *ret_truncated) { bool truncated = false; @@ -412,7 +410,7 @@ size_t udev_event_apply_format( continue; } - subst_len = udev_event_subst_format(event, type, attr, dest, size, global_props, &t); + subst_len = udev_event_subst_format(event, type, attr, dest, size, &t); if (subst_len < 0) { log_device_warning_errno(event->dev, subst_len, "Failed to substitute variable '$%s' or apply format '%%%c', ignoring: %m", diff --git a/src/udev/udev-format.h b/src/udev/udev-format.h index 92fef9b..9914dc0 100644 --- a/src/udev/udev-format.h +++ b/src/udev/udev-format.h @@ -14,7 +14,6 @@ size_t udev_event_apply_format( char *dest, size_t size, bool replace_whitespace, - Hashmap *global_props, bool *ret_truncated); int udev_check_format(const char *value, size_t *offset, const char **hint); diff --git a/src/udev/udev-manager.c b/src/udev/udev-manager.c index 8077e51..bb94478 100644 --- a/src/udev/udev-manager.c +++ b/src/udev/udev-manager.c @@ -274,15 +274,9 @@ static void manager_reload(Manager *manager, bool force) { return; /* If we eat this up, then tell our service manager to just continue */ - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Skipping configuration reloading, nothing changed.\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + (void) notify_reloading_full("Skipping configuration reloading, nothing changed."); } else { - (void) sd_notifyf(/* unset= */ false, - "RELOADING=1\n" - "STATUS=Flushing configuration...\n" - "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + (void) notify_reloading(); manager_kill_workers(manager, false); @@ -332,12 +326,38 @@ static int on_event_timeout_warning(sd_event_source *s, uint64_t usec, void *use return 1; } +static usec_t extra_timeout_usec(void) { + static usec_t saved = 10 * USEC_PER_SEC; + static bool parsed = false; + usec_t timeout; + const char *e; + int r; + + if (parsed) + return saved; + + parsed = true; + + e = getenv("SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC"); + if (!e) + return saved; + + r = parse_sec(e, &timeout); + if (r < 0) + log_debug_errno(r, "Failed to parse $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s, ignoring: %m", e); + + if (timeout > 5 * USEC_PER_HOUR) /* Add an arbitrary upper bound */ + log_debug("Parsed $SYSTEMD_UDEV_EXTRA_TIMEOUT_SEC=%s is too large, ignoring.", e); + else + saved = timeout; + + return saved; +} + static void worker_attach_event(Worker *worker, Event *event) { - Manager *manager; - sd_event *e; + Manager *manager = ASSERT_PTR(ASSERT_PTR(worker)->manager); + sd_event *e = ASSERT_PTR(manager->event); - assert(worker); - assert(worker->manager); assert(event); assert(!event->worker); assert(!worker->event); @@ -347,15 +367,16 @@ static void worker_attach_event(Worker *worker, Event *event) { event->state = EVENT_RUNNING; event->worker = worker; - manager = worker->manager; - e = manager->event; - (void) sd_event_add_time_relative(e, &event->timeout_warning_event, CLOCK_MONOTONIC, udev_warn_timeout(manager->timeout_usec), USEC_PER_SEC, on_event_timeout_warning, event); + /* Manager.timeout_usec is also used as the timeout for running programs specified in + * IMPORT{program}=, PROGRAM=, or RUN=. Here, let's add an extra time before the manager + * kills a worker, to make it possible that the worker detects timed out of spawned programs, + * kills them, and finalizes the event. */ (void) sd_event_add_time_relative(e, &event->timeout_event, CLOCK_MONOTONIC, - manager->timeout_usec, USEC_PER_SEC, + usec_add(manager->timeout_usec, extra_timeout_usec()), USEC_PER_SEC, on_event_timeout, event); } @@ -864,7 +885,7 @@ static int on_ctrl_msg(UdevCtrl *uctrl, UdevCtrlMessageType type, const UdevCtrl switch (type) { case UDEV_CTRL_SET_LOG_LEVEL: - if ((value->intval & LOG_PRIMASK) != value->intval) { + if (LOG_PRI(value->intval) != value->intval) { log_debug("Received invalid udev control message (SET_LOG_LEVEL, %i), ignoring.", value->intval); break; } @@ -1194,13 +1215,33 @@ Manager* manager_new(void) { .worker_watch = EBADF_PAIR, .log_level = LOG_INFO, .resolve_name_timing = RESOLVE_NAME_EARLY, - .timeout_usec = 180 * USEC_PER_SEC, + .timeout_usec = DEFAULT_WORKER_TIMEOUT_USEC, .timeout_signal = SIGKILL, }; return manager; } +void manager_adjust_arguments(Manager *manager) { + assert(manager); + + if (manager->timeout_usec < MIN_WORKER_TIMEOUT_USEC) { + log_debug("Timeout (%s) for processing event is too small, using the default: %s", + FORMAT_TIMESPAN(manager->timeout_usec, 1), + FORMAT_TIMESPAN(DEFAULT_WORKER_TIMEOUT_USEC, 1)); + + manager->timeout_usec = DEFAULT_WORKER_TIMEOUT_USEC; + } + + if (manager->exec_delay_usec >= manager->timeout_usec) { + log_debug("Delay (%s) for executing RUN= commands is too large compared with the timeout (%s) for event execution, ignoring the delay.", + FORMAT_TIMESPAN(manager->exec_delay_usec, 1), + FORMAT_TIMESPAN(manager->timeout_usec, 1)); + + manager->exec_delay_usec = 0; + } +} + int manager_init(Manager *manager, int fd_ctrl, int fd_uevent) { _cleanup_free_ char *cgroup = NULL; int r; @@ -1260,22 +1301,22 @@ int manager_main(Manager *manager) { udev_watch_restore(manager->inotify_fd); - /* block and listen to all signals on signalfd */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGHUP, SIGCHLD, SIGRTMIN+18, -1) >= 0); + /* block SIGCHLD for listening child events. */ + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); r = sd_event_default(&manager->event); if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - r = sd_event_add_signal(manager->event, NULL, SIGINT, on_sigterm, manager); + r = sd_event_add_signal(manager->event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_sigterm, manager); if (r < 0) return log_error_errno(r, "Failed to create SIGINT event source: %m"); - r = sd_event_add_signal(manager->event, NULL, SIGTERM, on_sigterm, manager); + r = sd_event_add_signal(manager->event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_sigterm, manager); if (r < 0) return log_error_errno(r, "Failed to create SIGTERM event source: %m"); - r = sd_event_add_signal(manager->event, NULL, SIGHUP, on_sighup, manager); + r = sd_event_add_signal(manager->event, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, on_sighup, manager); if (r < 0) return log_error_errno(r, "Failed to create SIGHUP event source: %m"); @@ -1325,7 +1366,8 @@ int manager_main(Manager *manager) { log_full_errno(ERRNO_IS_NOT_SUPPORTED(r) || ERRNO_IS_PRIVILEGE(r) || (r == -EHOSTDOWN) ? LOG_DEBUG : LOG_WARNING, r, "Failed to allocate memory pressure watch, ignoring: %m"); - r = sd_event_add_signal(manager->event, &manager->memory_pressure_event_source, SIGRTMIN+18, sigrtmin18_handler, NULL); + r = sd_event_add_signal(manager->event, &manager->memory_pressure_event_source, + (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); if (r < 0) return log_error_errno(r, "Failed to allocate SIGRTMIN+18 event source, ignoring: %m"); diff --git a/src/udev/udev-manager.h b/src/udev/udev-manager.h index afbc67f..864fc02 100644 --- a/src/udev/udev-manager.h +++ b/src/udev/udev-manager.h @@ -56,6 +56,7 @@ Manager* manager_new(void); Manager* manager_free(Manager *manager); DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); +void manager_adjust_arguments(Manager *manager); int manager_init(Manager *manager, int fd_ctrl, int fd_uevent); int manager_main(Manager *manager); diff --git a/src/udev/udev-node.c b/src/udev/udev-node.c index e12c26c..633fb2a 100644 --- a/src/udev/udev-node.c +++ b/src/udev/udev-node.c @@ -510,22 +510,17 @@ static int link_update(sd_device *dev, const char *slink, bool add) { } static int device_get_devpath_by_devnum(sd_device *dev, char **ret) { - const char *subsystem; dev_t devnum; int r; assert(dev); assert(ret); - r = sd_device_get_subsystem(dev, &subsystem); - if (r < 0) - return r; - r = sd_device_get_devnum(dev, &devnum); if (r < 0) return r; - return device_path_make_major_minor(streq(subsystem, "block") ? S_IFBLK : S_IFCHR, devnum, ret); + return device_path_make_major_minor(device_in_subsystem(dev, "block") ? S_IFBLK : S_IFCHR, devnum, ret); } int udev_node_update(sd_device *dev, sd_device *dev_old) { diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c index 5f12002..581bbaf 100644 --- a/src/udev/udev-rules.c +++ b/src/udev/udev-rules.c @@ -691,9 +691,7 @@ static int parse_token(UdevRuleLine *rule_line, const char *key, char *attr, Ude } if (!is_match) { - if (STR_IN_SET(attr, - "ACTION", "DEVLINKS", "DEVNAME", "DEVPATH", "DEVTYPE", "DRIVER", - "IFINDEX", "MAJOR", "MINOR", "SEQNUM", "SUBSYSTEM", "TAGS")) + if (!device_property_can_set(attr)) return log_line_error_errno(rule_line, SYNTHETIC_ERRNO(EINVAL), "Invalid ENV attribute. '%s' cannot be set.", attr); @@ -1538,7 +1536,7 @@ int udev_rules_parse_file(UdevRules *rules, const char *filename, bool extra_che 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); + return log_warning_errno(r, "Failed to save stat for %s, ignoring: %m", filename); (void) fd_warn_permissions(filename, fileno(f)); @@ -1761,7 +1759,7 @@ static bool token_match_attr(UdevRuleToken *token, sd_device *dev, UdevEvent *ev switch (token->attr_subst_type) { case SUBST_TYPE_FORMAT: - (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, NULL, &truncated); + (void) udev_event_apply_format(event, name, nbuf, sizeof(nbuf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "sysfs attribute name", name, token->type == TK_M_ATTR ? "ATTR" : "ATTRS", /* is_match = */ true); @@ -1928,10 +1926,7 @@ static size_t udev_replace_ifname(char *str) { static int udev_rule_apply_token_to_event( UdevRuleToken *token, sd_device *dev, - UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list) { + UdevEvent *event) { int r; @@ -1983,7 +1978,7 @@ static int udev_rule_apply_token_to_event( case TK_M_ENV: { const char *val = NULL; - (void) device_get_property_value_with_fallback(dev, token->data, properties_list, &val); + (void) device_get_property_value_with_fallback(dev, token->data, event->worker ? event->worker->properties : NULL, &val); return token_match_string(token, val); } @@ -2038,7 +2033,7 @@ static int udev_rule_apply_token_to_event( char buf[UDEV_PATH_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ true); return false; @@ -2056,7 +2051,7 @@ static int udev_rule_apply_token_to_event( struct stat statbuf; bool match, truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "file name", token->value, "TEST", /* is_match = */ true); return false; @@ -2099,15 +2094,15 @@ static int udev_rule_apply_token_to_event( size_t count; event->program_result = mfree(event->program_result); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "command", token->value, "PROGRAM", /* is_match = */ true); return false; } - log_event_debug(dev, token, "Running PROGRAM '%s'", buf); + log_event_debug(dev, token, "Running PROGRAM=\"%s\"", buf); - r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof(result), NULL); + r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof(result), NULL); if (r != 0) { if (r < 0) log_event_warning_errno(dev, token, r, "Failed to execute \"%s\": %m", buf); @@ -2131,7 +2126,7 @@ static int udev_rule_apply_token_to_event( char buf[UDEV_PATH_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "file name to be imported", token->value, "IMPORT", /* is_match = */ true); return false; @@ -2182,7 +2177,7 @@ static int udev_rule_apply_token_to_event( char buf[UDEV_LINE_SIZE], result[UDEV_LINE_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "command", token->value, "IMPORT", /* is_match = */ true); return false; @@ -2190,7 +2185,7 @@ static int udev_rule_apply_token_to_event( log_event_debug(dev, token, "Importing properties from results of '%s'", buf); - r = udev_event_spawn(event, timeout_usec, timeout_signal, true, buf, result, sizeof result, &truncated); + r = udev_event_spawn(event, /* accept_failure = */ true, buf, result, sizeof result, &truncated); if (r != 0) { if (r < 0) log_event_warning_errno(dev, token, r, "Failed to execute '%s', ignoring: %m", buf); @@ -2261,7 +2256,7 @@ static int udev_rule_apply_token_to_event( event->builtin_run |= mask; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "builtin command", token->value, "IMPORT", /* is_match = */ true); return false; @@ -2269,7 +2264,7 @@ static int udev_rule_apply_token_to_event( log_event_debug(dev, token, "Importing properties from results of builtin command '%s'", buf); - r = udev_builtin_run(event, cmd, buf, false); + r = udev_builtin_run(event, cmd, buf); if (r < 0) { /* remember failure */ log_event_debug_errno(dev, token, r, "Failed to run builtin '%s': %m", buf); @@ -2317,7 +2312,7 @@ static int udev_rule_apply_token_to_event( char buf[UDEV_PATH_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "property name", token->value, "IMPORT", /* is_match = */ true); return false; @@ -2378,7 +2373,7 @@ static int udev_rule_apply_token_to_event( if (token->op == OP_ASSIGN_FINAL) event->owner_final = true; - (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, owner, sizeof(owner), false, &truncated); if (truncated) { log_event_truncated(dev, token, "user name", token->value, "OWNER", /* is_match = */ false); break; @@ -2401,7 +2396,7 @@ static int udev_rule_apply_token_to_event( if (token->op == OP_ASSIGN_FINAL) event->group_final = true; - (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, group, sizeof(group), false, &truncated); if (truncated) { log_event_truncated(dev, token, "group name", token->value, "GROUP", /* is_match = */ false); break; @@ -2423,7 +2418,7 @@ static int udev_rule_apply_token_to_event( if (token->op == OP_ASSIGN_FINAL) event->mode_final = true; - (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, mode_str, sizeof(mode_str), false, &truncated); if (truncated) { log_event_truncated(dev, token, "mode", token->value, "MODE", /* is_match = */ false); break; @@ -2475,7 +2470,7 @@ static int udev_rule_apply_token_to_event( if (!name) return log_oom(); - (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, label_str, sizeof(label_str), false, &truncated); if (truncated) { log_event_truncated(dev, token, "security label", token->value, "SECLABEL", /* is_match = */ false); break; @@ -2519,7 +2514,7 @@ static int udev_rule_apply_token_to_event( } if (token->op == OP_ADD && - device_get_property_value_with_fallback(dev, name, properties_list, &val) >= 0) { + device_get_property_value_with_fallback(dev, name, event->worker ? event->worker->properties : NULL, &val) >= 0) { l = strpcpyl_full(&p, l, &truncated, val, " ", NULL); if (truncated) { log_event_warning(dev, token, @@ -2529,7 +2524,7 @@ static int udev_rule_apply_token_to_event( } } - (void) udev_event_apply_format(event, token->value, p, l, false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, p, l, false, &truncated); if (truncated) { _cleanup_free_ char *key_with_name = strjoin("ENV{", name, "}"); log_event_truncated(dev, token, "property value", token->value, @@ -2554,7 +2549,7 @@ static int udev_rule_apply_token_to_event( char buf[UDEV_PATH_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "tag name", token->value, "TAG", /* is_match = */ false); break; @@ -2591,7 +2586,7 @@ static int udev_rule_apply_token_to_event( break; } - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "network interface name", token->value, "NAME", /* is_match = */ false); break; @@ -2629,7 +2624,7 @@ static int udev_rule_apply_token_to_event( device_cleanup_devlinks(dev); (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), - /* replace_whitespace = */ event->esc != ESCAPE_NONE, properties_list, &truncated); + /* replace_whitespace = */ event->esc != ESCAPE_NONE, &truncated); if (truncated) { log_event_truncated(dev, token, "symbolic link path", token->value, "SYMLINK", /* is_match = */ false); break; @@ -2701,33 +2696,37 @@ static int udev_rule_apply_token_to_event( log_event_error_errno(dev, token, r, "Could not find file matches '%s', ignoring: %m", buf); break; } - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); if (truncated) { log_event_truncated(dev, token, "attribute value", token->value, "ATTR", /* is_match = */ false); break; } - log_event_debug(dev, token, "ATTR '%s' writing '%s'", buf, value); - r = write_string_file(buf, value, - WRITE_STRING_FILE_VERIFY_ON_FAILURE | - WRITE_STRING_FILE_DISABLE_BUFFER | - WRITE_STRING_FILE_AVOID_NEWLINE | - WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE); - if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write ATTR{%s}, ignoring: %m", buf); + if (EVENT_MODE_DESTRUCTIVE(event)) { + log_event_debug(dev, token, "Writing ATTR{'%s'}=\"%s\".", buf, value); + r = write_string_file(buf, value, + WRITE_STRING_FILE_VERIFY_ON_FAILURE | + WRITE_STRING_FILE_DISABLE_BUFFER | + WRITE_STRING_FILE_AVOID_NEWLINE | + WRITE_STRING_FILE_VERIFY_IGNORE_NEWLINE); + if (r < 0) + log_event_error_errno(dev, token, r, "Failed to write ATTR{%s}=\"%s\", ignoring: %m", buf, value); + } else + log_event_debug(dev, token, "Running in test mode, skipping writing ATTR{%s}=\"%s\".", buf, value); + break; } case TK_A_SYSCTL: { char buf[UDEV_PATH_SIZE], value[UDEV_NAME_SIZE]; bool truncated; - (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->data, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "sysctl entry name", token->data, "SYSCTL", /* is_match = */ false); break; } - (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, value, sizeof(value), false, &truncated); if (truncated) { _cleanup_free_ char *key_with_name = strjoin("SYSCTL{", buf, "}"); log_event_truncated(dev, token, "sysctl value", token->value, @@ -2736,10 +2735,15 @@ static int udev_rule_apply_token_to_event( } sysctl_normalize(buf); - log_event_debug(dev, token, "SYSCTL '%s' writing '%s'", buf, value); - r = sysctl_write(buf, value); - if (r < 0) - log_event_error_errno(dev, token, r, "Failed to write SYSCTL{%s}='%s', ignoring: %m", buf, value); + + if (EVENT_MODE_DESTRUCTIVE(event)) { + log_event_debug(dev, token, "Writing SYSCTL{%s}=\"%s\".", buf, value); + r = sysctl_write(buf, value); + if (r < 0) + log_event_error_errno(dev, token, r, "Failed to write SYSCTL{%s}=\"%s\", ignoring: %m", buf, value); + } else + log_event_debug(dev, token, "Running in test mode, skipping writing SYSCTL{%s}=\"%s\".", buf, value); + break; } case TK_A_RUN_BUILTIN: @@ -2756,7 +2760,7 @@ static int udev_rule_apply_token_to_event( if (IN_SET(token->op, OP_ASSIGN, OP_ASSIGN_FINAL)) ordered_hashmap_clear_free_key(event->run_list); - (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, properties_list, &truncated); + (void) udev_event_apply_format(event, token->value, buf, sizeof(buf), false, &truncated); if (truncated) { log_event_truncated(dev, token, "command", token->value, token->type == TK_A_RUN_BUILTIN ? "RUN{builtin}" : "RUN{program}", @@ -2793,11 +2797,7 @@ static bool token_is_for_parents(UdevRuleToken *token) { return token->type >= TK_M_PARENTS_KERNEL && token->type <= TK_M_PARENTS_TAG; } -static int udev_rule_apply_parent_token_to_event( - UdevRuleToken *head_token, - UdevEvent *event, - int timeout_signal) { - +static int udev_rule_apply_parent_token_to_event(UdevRuleToken *head_token, UdevEvent *event) { int r; assert(head_token); @@ -2810,7 +2810,7 @@ static int udev_rule_apply_parent_token_to_event( if (!token_is_for_parents(token)) return true; /* All parent tokens match. */ - r = udev_rule_apply_token_to_event(token, event->dev_parent, event, 0, timeout_signal, NULL); + r = udev_rule_apply_token_to_event(token, event->dev_parent, event); if (r < 0) return r; if (r == 0) @@ -2830,9 +2830,6 @@ static int udev_rule_apply_parent_token_to_event( static int udev_rule_apply_line_to_event( UdevRuleLine *line, UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list, UdevRuleLine **next_line) { UdevRuleLineType mask = LINE_HAS_GOTO | LINE_UPDATE_SOMETHING; @@ -2868,7 +2865,7 @@ static int udev_rule_apply_line_to_event( if (parents_done) continue; - r = udev_rule_apply_parent_token_to_event(token, event, timeout_signal); + r = udev_rule_apply_parent_token_to_event(token, event); if (r <= 0) return r; @@ -2876,7 +2873,7 @@ static int udev_rule_apply_line_to_event( continue; } - r = udev_rule_apply_token_to_event(token, event->dev, event, timeout_usec, timeout_signal, properties_list); + r = udev_rule_apply_token_to_event(token, event->dev, event); if (r <= 0) return r; } @@ -2887,13 +2884,7 @@ static int udev_rule_apply_line_to_event( return 0; } -int udev_rules_apply_to_event( - UdevRules *rules, - UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list) { - +int udev_rules_apply_to_event(UdevRules *rules, UdevEvent *event) { int r; assert(rules); @@ -2901,7 +2892,7 @@ int udev_rules_apply_to_event( LIST_FOREACH(rule_files, file, rules->rule_files) LIST_FOREACH_WITH_NEXT(rule_lines, line, next_line, file->rule_lines) { - r = udev_rule_apply_line_to_event(line, event, timeout_usec, timeout_signal, properties_list, &next_line); + r = udev_rule_apply_line_to_event(line, event, &next_line); if (r < 0) return r; } diff --git a/src/udev/udev-rules.h b/src/udev/udev-rules.h index 4352312..9d19c80 100644 --- a/src/udev/udev-rules.h +++ b/src/udev/udev-rules.h @@ -39,10 +39,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(UdevRules*, udev_rules_free); #define udev_rules_free_and_replace(a, b) free_and_replace_full(a, b, udev_rules_free) bool udev_rules_should_reload(UdevRules *rules); -int udev_rules_apply_to_event(UdevRules *rules, UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, - Hashmap *properties_list); +int udev_rules_apply_to_event(UdevRules *rules, UdevEvent *event); int udev_rules_apply_static_dev_perms(UdevRules *rules); ResolveNameTiming resolve_name_timing_from_string(const char *s) _pure_; diff --git a/src/udev/udev-spawn.c b/src/udev/udev-spawn.c index 67a3005..3f867a8 100644 --- a/src/udev/udev-spawn.c +++ b/src/udev/udev-spawn.c @@ -23,6 +23,7 @@ typedef struct Spawn { usec_t timeout_usec; int timeout_signal; usec_t event_birth_usec; + usec_t cmd_birth_usec; bool accept_failure; int fd_stdout; int fd_stderr; @@ -105,19 +106,18 @@ static int on_spawn_timeout(sd_event_source *s, uint64_t usec, void *userdata) { DEVICE_TRACE_POINT(spawn_timeout, spawn->device, spawn->cmd); - kill_and_sigcont(spawn->pid, spawn->timeout_signal); - - log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing", + log_device_error(spawn->device, "Spawned process '%s' ["PID_FMT"] timed out after %s, killing.", spawn->cmd, spawn->pid, FORMAT_TIMESPAN(spawn->timeout_usec, USEC_PER_SEC)); + kill_and_sigcont(spawn->pid, spawn->timeout_signal); return 1; } static int on_spawn_timeout_warning(sd_event_source *s, uint64_t usec, void *userdata) { Spawn *spawn = ASSERT_PTR(userdata); - log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete", + log_device_warning(spawn->device, "Spawned process '%s' ["PID_FMT"] is taking longer than %s to complete.", spawn->cmd, spawn->pid, FORMAT_TIMESPAN(spawn->timeout_warn_usec, USEC_PER_SEC)); @@ -164,31 +164,20 @@ static int spawn_wait(Spawn *spawn) { if (r < 0) return log_device_debug_errno(spawn->device, r, "Failed to allocate sd-event object: %m"); - if (spawn->timeout_usec > 0) { - usec_t usec, age_usec; - - usec = now(CLOCK_MONOTONIC); - age_usec = usec - spawn->event_birth_usec; - if (age_usec < spawn->timeout_usec) { - if (spawn->timeout_warn_usec > 0 && - spawn->timeout_warn_usec < spawn->timeout_usec && - spawn->timeout_warn_usec > age_usec) { - spawn->timeout_warn_usec -= age_usec; - - r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, - usec + spawn->timeout_warn_usec, USEC_PER_SEC, - on_spawn_timeout_warning, spawn); - if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m"); - } - - spawn->timeout_usec -= age_usec; - + if (spawn->timeout_usec != USEC_INFINITY) { + if (spawn->timeout_warn_usec < spawn->timeout_usec) { r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, - usec + spawn->timeout_usec, USEC_PER_SEC, on_spawn_timeout, spawn); + usec_add(spawn->cmd_birth_usec, spawn->timeout_warn_usec), USEC_PER_SEC, + on_spawn_timeout_warning, spawn); if (r < 0) - return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m"); + return log_device_debug_errno(spawn->device, r, "Failed to create timeout warning event source: %m"); } + + r = sd_event_add_time(e, NULL, CLOCK_MONOTONIC, + usec_add(spawn->cmd_birth_usec, spawn->timeout_usec), USEC_PER_SEC, + on_spawn_timeout, spawn); + if (r < 0) + return log_device_debug_errno(spawn->device, r, "Failed to create timeout event source: %m"); } if (spawn->fd_stdout >= 0) { @@ -222,8 +211,6 @@ static int spawn_wait(Spawn *spawn) { int udev_event_spawn( UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, bool accept_failure, const char *cmd, char *result, @@ -238,9 +225,30 @@ int udev_event_spawn( int r; assert(event); + assert(IN_SET(event->event_mode, EVENT_UDEV_WORKER, EVENT_UDEVADM_TEST, EVENT_TEST_RULE_RUNNER, EVENT_TEST_SPAWN)); assert(event->dev); + assert(cmd); assert(result || result_size == 0); + if (event->event_mode == EVENT_UDEVADM_TEST && + !STARTSWITH_SET(cmd, "ata_id", "cdrom_id", "dmi_memory_id", "fido_id", "mtd_probe", "scsi_id")) { + log_device_debug(event->dev, "Running in test mode, skipping execution of '%s'.", cmd); + result[0] = '\0'; + if (ret_truncated) + *ret_truncated = false; + return 0; + } + + int timeout_signal = event->worker ? event->worker->timeout_signal : SIGKILL; + usec_t timeout_usec = event->worker ? event->worker->timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC; + usec_t now_usec = now(CLOCK_MONOTONIC); + usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec); + usec_t cmd_timeout_usec = usec_sub_unsigned(timeout_usec, age_usec); + if (cmd_timeout_usec <= 0) + return log_device_warning_errno(event->dev, SYNTHETIC_ERRNO(ETIME), + "The event already takes longer (%s) than the timeout (%s), skipping execution of '%s'.", + FORMAT_TIMESPAN(age_usec, 1), FORMAT_TIMESPAN(timeout_usec, 1), cmd); + /* pipes from child to parent */ if (result || log_get_max_level() >= LOG_INFO) if (pipe2(outpipe, O_NONBLOCK|O_CLOEXEC) != 0) @@ -300,10 +308,11 @@ int udev_event_spawn( .cmd = cmd, .pid = pid, .accept_failure = accept_failure, - .timeout_warn_usec = udev_warn_timeout(timeout_usec), - .timeout_usec = timeout_usec, + .timeout_warn_usec = udev_warn_timeout(cmd_timeout_usec), + .timeout_usec = cmd_timeout_usec, .timeout_signal = timeout_signal, .event_birth_usec = event->birth_usec, + .cmd_birth_usec = now_usec, .fd_stdout = outpipe[READ_END], .fd_stderr = errpipe[READ_END], .result = result, @@ -323,29 +332,42 @@ int udev_event_spawn( return r; /* 0 for success, and positive if the program failed */ } -void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal) { +void udev_event_execute_run(UdevEvent *event) { const char *command; void *val; int r; + assert(event); + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); if (builtin_cmd != _UDEV_BUILTIN_INVALID) { log_device_debug(event->dev, "Running built-in command \"%s\"", command); - r = udev_builtin_run(event, builtin_cmd, command, false); + r = udev_builtin_run(event, builtin_cmd, command); if (r < 0) log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command); } else { - if (event->exec_delay_usec > 0) { + if (event->worker && event->worker->exec_delay_usec > 0) { + usec_t timeout_usec = event->worker ? event->worker->timeout_usec : DEFAULT_WORKER_TIMEOUT_USEC; + usec_t now_usec = now(CLOCK_MONOTONIC); + usec_t age_usec = usec_sub_unsigned(now_usec, event->birth_usec); + + if (event->worker->exec_delay_usec >= usec_sub_unsigned(timeout_usec, age_usec)) { + log_device_warning(event->dev, + "Cannot delaying execution of \"%s\" for %s, skipping.", + command, FORMAT_TIMESPAN(event->worker->exec_delay_usec, USEC_PER_SEC)); + continue; + } + log_device_debug(event->dev, "Delaying execution of \"%s\" for %s.", - command, FORMAT_TIMESPAN(event->exec_delay_usec, USEC_PER_SEC)); - (void) usleep_safe(event->exec_delay_usec); + command, FORMAT_TIMESPAN(event->worker->exec_delay_usec, USEC_PER_SEC)); + (void) usleep_safe(event->worker->exec_delay_usec); } log_device_debug(event->dev, "Running command \"%s\"", command); - r = udev_event_spawn(event, timeout_usec, timeout_signal, false, command, NULL, 0, NULL); + r = udev_event_spawn(event, /* accept_failure = */ false, command, NULL, 0, NULL); if (r < 0) log_device_warning_errno(event->dev, r, "Failed to execute '%s', ignoring: %m", command); else if (r > 0) /* returned value is positive when program fails */ diff --git a/src/udev/udev-spawn.h b/src/udev/udev-spawn.h index 5efea2e..6b22b68 100644 --- a/src/udev/udev-spawn.h +++ b/src/udev/udev-spawn.h @@ -14,15 +14,16 @@ typedef struct UdevEvent UdevEvent; int udev_event_spawn( UdevEvent *event, - usec_t timeout_usec, - int timeout_signal, bool accept_failure, const char *cmd, char *result, size_t ressize, bool *ret_truncated); -void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_signal); +void udev_event_execute_run(UdevEvent *event); static inline usec_t udev_warn_timeout(usec_t timeout_usec) { + if (timeout_usec == USEC_INFINITY) + return USEC_INFINITY; + return DIV_ROUND_UP(timeout_usec, 3); } diff --git a/src/udev/udev-watch.c b/src/udev/udev-watch.c index 58c8279..258eb26 100644 --- a/src/udev/udev-watch.c +++ b/src/udev/udev-watch.c @@ -232,12 +232,9 @@ int udev_watch_end(int inotify_fd, sd_device *dev) { _cleanup_close_ int dirfd = -EBADF; int wd, r; + assert(inotify_fd >= 0); assert(dev); - /* This may be called by 'udevadm test'. In that case, inotify_fd is not initialized. */ - if (inotify_fd < 0) - return 0; - if (sd_device_get_devname(dev, NULL) < 0) return 0; diff --git a/src/udev/udev-worker.c b/src/udev/udev-worker.c index 53722b2..97c5679 100644 --- a/src/udev/udev-worker.c +++ b/src/udev/udev-worker.c @@ -138,11 +138,7 @@ static int worker_mark_block_device_read_only(sd_device *dev) { if (!device_for_action(dev, SD_DEVICE_ADD)) return 0; - r = sd_device_get_subsystem(dev, &val); - if (r < 0) - return log_device_debug_errno(dev, r, "Failed to get subsystem: %m"); - - if (!streq(val, "block")) + if (!device_in_subsystem(dev, "block")) return 0; r = sd_device_get_sysname(dev, &val); @@ -176,7 +172,7 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) { log_device_uevent(dev, "Processing device"); - udev_event = udev_event_new(dev, worker->exec_delay_usec, worker->rtnl, worker->log_level); + udev_event = udev_event_new(dev, worker, EVENT_UDEV_WORKER); if (!udev_event) return -ENOMEM; @@ -194,18 +190,17 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) { if (worker->blockdev_read_only) (void) worker_mark_block_device_read_only(dev); + /* Disable watch during event processing. */ + r = udev_watch_end(worker->inotify_fd, dev); + if (r < 0) + log_device_warning_errno(dev, r, "Failed to remove inotify watch, ignoring: %m"); + /* apply rules, create node, symlinks */ - r = udev_event_execute_rules( - udev_event, - worker->inotify_fd, - worker->timeout_usec, - worker->timeout_signal, - worker->properties, - worker->rules); + r = udev_event_execute_rules(udev_event, worker->rules); if (r < 0) return r; - udev_event_execute_run(udev_event, worker->timeout_usec, worker->timeout_signal); + udev_event_execute_run(udev_event); if (!worker->rtnl) /* in case rtnl was initialized */ @@ -217,6 +212,15 @@ static int worker_process_device(UdevWorker *worker, sd_device *dev) { log_device_warning_errno(dev, r, "Failed to add inotify watch, ignoring: %m"); } + /* Finalize database. */ + r = device_add_property(dev, "ID_PROCESSING", NULL); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to remove 'ID_PROCESSING' property: %m"); + + r = device_update_db(dev); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to update database under /run/udev/data/: %m"); + log_device_uevent(dev, "Device processed"); return 0; } @@ -245,6 +249,7 @@ void udev_broadcast_result(sd_device_monitor *monitor, sd_device *dev, EventResu break; } case EVENT_RESULT_EXIT_STATUS_BASE ... EVENT_RESULT_EXIT_STATUS_MAX: + assert(result != EVENT_RESULT_EXIT_STATUS_BASE); (void) device_add_propertyf(dev, "UDEV_WORKER_EXIT_STATUS", "%i", result - EVENT_RESULT_EXIT_STATUS_BASE); break; @@ -318,8 +323,6 @@ int udev_worker_main(UdevWorker *worker, sd_device *dev) { DEVICE_TRACE_POINT(worker_spawned, dev, getpid_cached()); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, -1) >= 0); - /* Reset OOM score, we only protect the main daemon. */ r = set_oom_score_adjust(0); if (r < 0) @@ -329,7 +332,7 @@ int udev_worker_main(UdevWorker *worker, sd_device *dev) { if (r < 0) return log_error_errno(r, "Failed to allocate event loop: %m"); - r = sd_event_add_signal(worker->event, NULL, SIGTERM, NULL, NULL); + r = sd_event_add_signal(worker->event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to set SIGTERM event: %m"); diff --git a/src/udev/udev-worker.h b/src/udev/udev-worker.h index 05c319e..e9aefc5 100644 --- a/src/udev/udev-worker.h +++ b/src/udev/udev-worker.h @@ -11,6 +11,9 @@ #include "hashmap.h" #include "time-util.h" +#define DEFAULT_WORKER_TIMEOUT_USEC (3 * USEC_PER_MINUTE) +#define MIN_WORKER_TIMEOUT_USEC (1 * USEC_PER_MSEC) + typedef struct UdevRules UdevRules; typedef struct UdevWorker { diff --git a/src/udev/udevadm-control.c b/src/udev/udevadm-control.c index 64615f5..29dc883 100644 --- a/src/udev/udevadm-control.c +++ b/src/udev/udevadm-control.c @@ -1,15 +1,4 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/* - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 of the License, or - * (at your option) any later version. - * - * This program 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. - */ #include #include @@ -19,6 +8,7 @@ #include #include +#include "creds-util.h" #include "parse-util.h" #include "process-util.h" #include "static-destruct.h" @@ -37,9 +27,21 @@ static bool arg_exit = false; static int arg_max_children = -1; static int arg_log_level = -1; static int arg_start_exec_queue = -1; +static bool arg_load_credentials = false; STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep); +static bool arg_has_control_commands(void) { + return + arg_exit || + arg_log_level >= 0 || + arg_start_exec_queue >= 0 || + arg_reload || + !strv_isempty(arg_env) || + arg_max_children >= 0 || + arg_ping; +} + static int help(void) { printf("%s control OPTION\n\n" "Control the udev daemon.\n\n" @@ -53,7 +55,8 @@ static int help(void) { " -p --property=KEY=VALUE Set a global property for all events\n" " -m --children-max=N Maximum number of children\n" " --ping Wait for udev to respond to a ping message\n" - " -t --timeout=SECONDS Maximum time to block for a reply\n", + " -t --timeout=SECONDS Maximum time to block for a reply\n" + " --load-credentials Load udev rules from credentials\n", program_invocation_short_name); return 0; @@ -62,23 +65,25 @@ static int help(void) { static int parse_argv(int argc, char *argv[]) { enum { ARG_PING = 0x100, + ARG_LOAD_CREDENTIALS, }; static const struct option options[] = { - { "exit", no_argument, NULL, 'e' }, - { "log-level", required_argument, NULL, 'l' }, - { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */ - { "stop-exec-queue", no_argument, NULL, 's' }, - { "start-exec-queue", no_argument, NULL, 'S' }, - { "reload", no_argument, NULL, 'R' }, - { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ - { "property", required_argument, NULL, 'p' }, - { "env", required_argument, NULL, 'p' }, /* alias for -p */ - { "children-max", required_argument, NULL, 'm' }, - { "ping", no_argument, NULL, ARG_PING }, - { "timeout", required_argument, NULL, 't' }, - { "version", no_argument, NULL, 'V' }, - { "help", no_argument, NULL, 'h' }, + { "exit", no_argument, NULL, 'e' }, + { "log-level", required_argument, NULL, 'l' }, + { "log-priority", required_argument, NULL, 'l' }, /* for backward compatibility */ + { "stop-exec-queue", no_argument, NULL, 's' }, + { "start-exec-queue", no_argument, NULL, 'S' }, + { "reload", no_argument, NULL, 'R' }, + { "reload-rules", no_argument, NULL, 'R' }, /* alias for -R */ + { "property", required_argument, NULL, 'p' }, + { "env", required_argument, NULL, 'p' }, /* alias for -p */ + { "children-max", required_argument, NULL, 'm' }, + { "ping", no_argument, NULL, ARG_PING }, + { "timeout", required_argument, NULL, 't' }, + { "load-credentials", no_argument, NULL, ARG_LOAD_CREDENTIALS }, + { "version", no_argument, NULL, 'V' }, + { "help", no_argument, NULL, 'h' }, {} }; @@ -87,10 +92,6 @@ static int parse_argv(int argc, char *argv[]) { assert(argc >= 0); assert(argv); - if (argc <= 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "This command expects one or more options."); - while ((c = getopt_long(argc, argv, "el:sSRp:m:t:Vh", options, NULL)) >= 0) switch (c) { @@ -145,6 +146,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse timeout value '%s': %m", optarg); break; + case ARG_LOAD_CREDENTIALS: + arg_load_credentials = true; + break; + case 'V': return print_version(); @@ -158,6 +163,10 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (!arg_has_control_commands() && !arg_load_credentials) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "No control command option is specified."); + if (optind < argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Extraneous argument: %s", argv[optind]); @@ -165,19 +174,10 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -int control_main(int argc, char *argv[], void *userdata) { +static int send_control_commands(void) { _cleanup_(udev_ctrl_unrefp) UdevCtrl *uctrl = NULL; int r; - if (running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - return 0; - } - - r = parse_argv(argc, argv); - if (r <= 0) - return r; - r = udev_ctrl_new(&uctrl); if (r < 0) return log_error_errno(r, "Failed to initialize udev control: %m"); @@ -237,3 +237,34 @@ int control_main(int argc, char *argv[], void *userdata) { return 0; } + +int control_main(int argc, char *argv[], void *userdata) { + int r; + + if (running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (arg_load_credentials) { + static const PickUpCredential table[] = { + { "udev.conf.", "/run/udev/udev.conf.d/", ".conf" }, + { "udev.rules.", "/run/udev/rules.d/", ".rules" }, + }; + r = pick_up_credentials(table, ELEMENTSOF(table)); + if (r < 0) + return r; + } + + if (arg_has_control_commands()) { + r = send_control_commands(); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/udev/udevadm-hwdb.c b/src/udev/udevadm-hwdb.c index 2f5429f..f306a4f 100644 --- a/src/udev/udevadm-hwdb.c +++ b/src/udev/udevadm-hwdb.c @@ -89,7 +89,7 @@ int hwdb_main(int argc, char *argv[], void *userdata) { log_notice("udevadm hwdb is deprecated. Use systemd-hwdb instead."); - if (arg_update) { + if (arg_update && !hwdb_bypass()) { r = hwdb_update(arg_root, arg_hwdb_bin_dir, arg_strict, true); if (r < 0) return r; diff --git a/src/udev/udevadm-info.c b/src/udev/udevadm-info.c index 4cd9ad4..7c3c0cd 100644 --- a/src/udev/udevadm-info.c +++ b/src/udev/udevadm-info.c @@ -759,7 +759,7 @@ static int parse_key_value_argument(const char *s, char **key, char **value) { assert(key); assert(value); - r = extract_many_words(&s, "=", EXTRACT_DONT_COALESCE_SEPARATORS, &k, &v, NULL); + r = extract_many_words(&s, "=", EXTRACT_DONT_COALESCE_SEPARATORS, &k, &v); if (r < 0) return log_error_errno(r, "Failed to parse key/value pair %s: %m", s); if (r < 2) diff --git a/src/udev/udevadm-lock.c b/src/udev/udevadm-lock.c index bc2d5e7..e6384e3 100644 --- a/src/udev/udevadm-lock.c +++ b/src/udev/udevadm-lock.c @@ -126,7 +126,7 @@ static int parse_argv(int argc, char *argv[]) { if (arg_print) { if (optind != argc) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected"); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No arguments expected."); } else { if (optind + 1 > argc) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too few arguments, command to execute."); @@ -193,7 +193,7 @@ static int lock_device( /* Extra safety: check that the device still refers to what we think it refers to */ if (!S_ISBLK(st.st_mode) || st.st_rdev != devno) - return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Path '%s' no longer refers to specified block device %u:%u: %m", path, major(devno), minor(devno)); + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Path '%s' no longer refers to specified block device %u:%u.", path, major(devno), minor(devno)); r = lock_generic(fd, LOCK_BSD, LOCK_EX|LOCK_NB); if (r < 0) { diff --git a/src/udev/udevadm-monitor.c b/src/udev/udevadm-monitor.c index 27c4853..b1124df 100644 --- a/src/udev/udevadm-monitor.c +++ b/src/udev/udevadm-monitor.c @@ -208,7 +208,7 @@ int monitor_main(int argc, char *argv[], void *userdata) { goto finalize; } - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT) >= 0); (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c index 088b4da..322f627 100644 --- a/src/udev/udevadm-test-builtin.c +++ b/src/udev/udevadm-test-builtin.c @@ -6,6 +6,8 @@ #include #include +#include "device-private.h" +#include "device-util.h" #include "log.h" #include "udev-builtin.h" #include "udevadm.h" @@ -77,8 +79,7 @@ int builtin_main(int argc, char *argv[], void *userdata) { UdevBuiltinCommand cmd; int r; - log_set_max_level(LOG_DEBUG); - log_parse_environment(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -98,13 +99,22 @@ int builtin_main(int argc, char *argv[], void *userdata) { goto finish; } - event = udev_event_new(dev, 0, NULL, LOG_DEBUG); + event = udev_event_new(dev, NULL, EVENT_UDEVADM_TEST_BUILTIN); if (!event) { r = log_oom(); goto finish; } - r = udev_builtin_run(event, cmd, arg_command, true); + if (arg_action != SD_DEVICE_REMOVE) { + /* For net_setup_link */ + r = device_clone_with_db(dev, &event->dev_db_clone); + if (r < 0) { + log_device_error_errno(dev, r, "Failed to clone device: %m"); + goto finish; + } + } + + r = udev_builtin_run(event, cmd, arg_command); if (r < 0) { log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command); goto finish; diff --git a/src/udev/udevadm-test.c b/src/udev/udevadm-test.c index e1afd7d..c8c23e8 100644 --- a/src/udev/udevadm-test.c +++ b/src/udev/udevadm-test.c @@ -16,14 +16,18 @@ #include "device-private.h" #include "device-util.h" +#include "format-util.h" #include "path-util.h" #include "string-util.h" +#include "strv.h" #include "strxcpyx.h" +#include "terminal-util.h" #include "udev-builtin.h" #include "udev-event.h" #include "udev-format.h" #include "udevadm-util.h" #include "udevadm.h" +#include "user-util.h" static sd_device_action_t arg_action = SD_DEVICE_ADD; static ResolveNameTiming arg_resolve_name_timing = RESOLVE_NAME_EARLY; @@ -89,13 +93,10 @@ int test_main(int argc, char *argv[], void *userdata) { _cleanup_(udev_rules_freep) UdevRules *rules = NULL; _cleanup_(udev_event_freep) UdevEvent *event = NULL; _cleanup_(sd_device_unrefp) sd_device *dev = NULL; - const char *cmd; sigset_t mask, sigmask_orig; - void *val; int r; - log_set_max_level(LOG_DEBUG); - log_parse_environment(); + log_setup(); r = parse_argv(argc, argv); if (r <= 0) @@ -125,24 +126,89 @@ int test_main(int argc, char *argv[], void *userdata) { /* don't read info from the db */ device_seal(dev); - event = udev_event_new(dev, 0, NULL, LOG_DEBUG); + event = udev_event_new(dev, NULL, EVENT_UDEVADM_TEST); assert_se(sigfillset(&mask) >= 0); assert_se(sigprocmask(SIG_SETMASK, &mask, &sigmask_orig) >= 0); - udev_event_execute_rules(event, -1, 60 * USEC_PER_SEC, SIGKILL, NULL, rules); + udev_event_execute_rules(event, rules); + printf("%sProperties:%s\n", ansi_highlight(), ansi_normal()); FOREACH_DEVICE_PROPERTY(dev, key, value) - printf("%s=%s\n", key, value); + printf(" %s=%s\n", key, value); - ORDERED_HASHMAP_FOREACH_KEY(val, cmd, event->run_list) { - char program[UDEV_PATH_SIZE]; - bool truncated; + if (sd_device_get_tag_first(dev)) { + printf("%sTags:%s\n", ansi_highlight(), ansi_normal()); + FOREACH_DEVICE_TAG(dev, tag) + printf(" %s\n", tag); + } + + if (sd_device_get_devnum(dev, NULL) >= 0) { + + if (sd_device_get_devlink_first(dev)) { + int prio; + device_get_devlink_priority(dev, &prio); + printf("%sDevice node symlinks:%s (priority=%i)\n", ansi_highlight(), ansi_normal(), prio); + FOREACH_DEVICE_DEVLINK(dev, devlink) + printf(" %s\n", devlink); + } + + printf("%sInotify watch:%s\n %s\n", ansi_highlight(), ansi_normal(), enabled_disabled(event->inotify_watch)); + + uid_t uid = event->uid; + if (!uid_is_valid(uid)) + (void) device_get_devnode_uid(dev, &uid); + if (uid_is_valid(uid)) { + _cleanup_free_ char *user = uid_to_name(uid); + printf("%sDevice node owner:%s\n %s (uid="UID_FMT")\n", ansi_highlight(), ansi_normal(), strna(user), uid); + } + + gid_t gid = event->gid; + if (!gid_is_valid(uid)) + (void) device_get_devnode_gid(dev, &gid); + if (gid_is_valid(gid)) { + _cleanup_free_ char *group = gid_to_name(gid); + printf("%sDevice node group:%s\n %s (gid="GID_FMT")\n", ansi_highlight(), ansi_normal(), strna(group), gid); + } - (void) udev_event_apply_format(event, cmd, program, sizeof(program), false, NULL, &truncated); - if (truncated) - log_warning("The command '%s' is truncated while substituting into '%s'.", program, cmd); - printf("run: '%s'\n", program); + mode_t mode = event->mode; + if (mode == MODE_INVALID) + (void) device_get_devnode_mode(dev, &mode); + if (mode != MODE_INVALID) + printf("%sDevice node permission:%s\n %04o\n", ansi_highlight(), ansi_normal(), mode); + + if (!ordered_hashmap_isempty(event->seclabel_list)) { + const char *name, *label; + printf("%sDevice node security label:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(label, name, event->seclabel_list) + printf(" %s : %s\n", name, label); + } + } + + if (sd_device_get_ifindex(dev, NULL) >= 0) { + if (!isempty(event->name)) + printf("%sNetwork interface name:%s\n %s\n", ansi_highlight(), ansi_normal(), event->name); + + if (!strv_isempty(event->altnames)) { + bool space = true; + printf("%sAlternative interface names:%s", ansi_highlight(), ansi_normal()); + fputstrv(stdout, event->altnames, "\n ", &space); + puts(""); + } + } + + if (!ordered_hashmap_isempty(event->run_list)) { + void *val; + const char *command; + printf("%sQueued commands:%s\n", ansi_highlight(), ansi_normal()); + ORDERED_HASHMAP_FOREACH_KEY(val, command, event->run_list) { + UdevBuiltinCommand builtin_cmd = PTR_TO_UDEV_BUILTIN_CMD(val); + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) + printf(" RUN{builtin} : %s\n", command); + else + printf(" RUN{program} : %s\n", command); + } } r = 0; diff --git a/src/udev/udevadm-wait.c b/src/udev/udevadm-wait.c index e6620c2..6ffc86b 100644 --- a/src/udev/udevadm-wait.c +++ b/src/udev/udevadm-wait.c @@ -67,7 +67,7 @@ static int check_device(const char *path) { return r; if (arg_wait_until == WAIT_UNTIL_INITIALIZED) - return sd_device_get_is_initialized(dev); + return device_is_processed(dev); return true; } diff --git a/src/udev/udevadm.c b/src/udev/udevadm.c index 687b927..899d4e6 100644 --- a/src/udev/udevadm.c +++ b/src/udev/udevadm.c @@ -123,7 +123,7 @@ static int run(int argc, char *argv[]) { if (invoked_as(argv, "udevd")) return run_udevd(argc, argv); - udev_parse_config(); + (void) udev_parse_config(); log_setup(); r = parse_argv(argc, argv); diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 2ed4282..5018541 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -10,6 +10,7 @@ #include "sd-daemon.h" +#include "conf-parser.h" #include "env-file.h" #include "errno-util.h" #include "fd-util.h" @@ -32,16 +33,15 @@ static int arg_daemonize = false; static int listen_fds(int *ret_ctrl, int *ret_netlink) { int ctrl_fd = -EBADF, netlink_fd = -EBADF; - int fd, n; assert(ret_ctrl); assert(ret_netlink); - n = sd_listen_fds(true); + int n = sd_listen_fds(true); if (n < 0) return n; - for (fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) { + for (int fd = SD_LISTEN_FDS_START; fd < n + SD_LISTEN_FDS_START; fd++) { if (sd_is_socket(fd, AF_UNIX, SOCK_SEQPACKET, -1) > 0) { if (ctrl_fd >= 0) return -EINVAL; @@ -65,70 +65,29 @@ static int listen_fds(int *ret_ctrl, int *ret_netlink) { return 0; } +static DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_name_timing, resolve_name_timing, ResolveNameTiming, "Failed to parse resolve name timing"); + static int manager_parse_udev_config(Manager *manager) { - _cleanup_free_ char *log_val = NULL, *children_max = NULL, *exec_delay = NULL, - *event_timeout = NULL, *resolve_names = NULL, *timeout_signal = NULL; - int r; + int r, log_val = -1; assert(manager); - r = parse_env_file(NULL, "/etc/udev/udev.conf", - "udev_log", &log_val, - "children_max", &children_max, - "exec_delay", &exec_delay, - "event_timeout", &event_timeout, - "resolve_names", &resolve_names, - "timeout_signal", &timeout_signal); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; + const ConfigTableItem config_table[] = { + { NULL, "udev_log", config_parse_log_level, 0, &log_val }, + { NULL, "children_max", config_parse_unsigned, 0, &manager->children_max }, + { NULL, "exec_delay", config_parse_sec, 0, &manager->exec_delay_usec }, + { NULL, "event_timeout", config_parse_sec, 0, &manager->timeout_usec }, + { NULL, "resolve_names", config_parse_resolve_name_timing, 0, &manager->resolve_name_timing }, + { NULL, "timeout_signal", config_parse_signal, 0, &manager->timeout_signal }, + {} + }; - r = udev_set_max_log_level(log_val); + r = udev_parse_config_full(config_table); if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to set udev log level '%s', ignoring: %m", log_val); - - if (children_max) { - r = safe_atou(children_max, &manager->children_max); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to parse children_max=%s, ignoring: %m", children_max); - } - - if (exec_delay) { - r = parse_sec(exec_delay, &manager->exec_delay_usec); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to parse exec_delay=%s, ignoring: %m", exec_delay); - } - - if (event_timeout) { - r = parse_sec(event_timeout, &manager->timeout_usec); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to parse event_timeout=%s, ignoring: %m", event_timeout); - } - - if (resolve_names) { - ResolveNameTiming t; - - t = resolve_name_timing_from_string(resolve_names); - if (t < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to parse resolve_names=%s, ignoring.", resolve_names); - else - manager->resolve_name_timing = t; - } + return r; - if (timeout_signal) { - r = signal_from_string(timeout_signal); - if (r < 0) - log_syntax(NULL, LOG_WARNING, "/etc/udev/udev.conf", 0, r, - "Failed to parse timeout_signal=%s, ignoring: %m", timeout_signal); - else - manager->timeout_signal = r; - } + if (log_val >= 0) + log_set_max_level(log_val); return 0; } @@ -330,8 +289,7 @@ int run_udevd(int argc, char *argv[]) { int fd_ctrl = -EBADF, fd_uevent = -EBADF; int r; - log_set_target(LOG_TARGET_AUTO); - log_open(); + log_setup(); manager = manager_new(); if (!manager) @@ -339,9 +297,6 @@ int run_udevd(int argc, char *argv[]) { manager_parse_udev_config(manager); - log_parse_environment(); - log_open(); /* Done again to update after reading configuration. */ - r = parse_argv(argc, argv, manager); if (r <= 0) return r; @@ -355,6 +310,8 @@ int run_udevd(int argc, char *argv[]) { log_set_max_level(LOG_DEBUG); } + manager_adjust_arguments(manager); + r = must_be_root(); if (r < 0) return r; diff --git a/src/udev/v4l_id/v4l_id.c b/src/udev/v4l_id/v4l_id.c index 30527e9..5c54065 100644 --- a/src/udev/v4l_id/v4l_id.c +++ b/src/udev/v4l_id/v4l_id.c @@ -1,16 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ /* * Copyright (c) 2009 Filippo Argiolas - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * This program 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: */ #include diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py index 5866447..0e3f932 100755 --- a/src/ukify/test/test_ukify.py +++ b/src/ukify/test/test_ukify.py @@ -120,7 +120,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -143,7 +143,7 @@ def test_apply_config(tmp_path): assert ns.sign_kernel is False assert ns._groups == ['NAME'] - assert ns.pcr_private_keys == [pathlib.Path('some/path7')] + assert ns.pcr_private_keys == ['some/path7'] assert ns.pcr_public_keys == [pathlib.Path('some/path8')] assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] @@ -189,7 +189,7 @@ def test_parse_args_many_deprecated(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -235,7 +235,7 @@ def test_parse_args_many(): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] + assert opts.pcr_private_keys == ['PKEY1'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] assert opts.pcr_banks == ['SHA1', 'SHA256'] assert opts.signing_engine == 'ENGINE' @@ -342,8 +342,7 @@ def test_config_priority(tmp_path): assert opts.pcrpkey == pathlib.Path('PATH') assert opts.uname == '1.2.3' assert opts.stub == pathlib.Path('STUBPATH') - assert opts.pcr_private_keys == [pathlib.Path('PKEY1'), - pathlib.Path('some/path7')] + assert opts.pcr_private_keys == ['PKEY1', 'some/path7'] assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), pathlib.Path('some/path8')] assert opts.pcr_banks == ['SHA1', 'SHA256'] @@ -522,14 +521,12 @@ baz,3 assert found is True - def unbase64(filename): tmp = tempfile.NamedTemporaryFile() base64.decode(filename.open('rb'), tmp) tmp.flush() return tmp - def test_uname_scraping(kernel_initrd): if kernel_initrd is None: pytest.skip('linux+initrd not found') @@ -539,7 +536,8 @@ def test_uname_scraping(kernel_initrd): assert re.match(r'\d+\.\d+\.\d+', uname) @pytest.mark.skipif(not slow_tests, reason='slow') -def test_efi_signing_sbsign(kernel_initrd, tmp_path): +@pytest.mark.parametrize("days", [365*10, None]) +def test_efi_signing_sbsign(days, kernel_initrd, tmp_path): if kernel_initrd is None: pytest.skip('linux+initrd not found') if not shutil.which('sbsign'): @@ -550,7 +548,7 @@ def test_efi_signing_sbsign(kernel_initrd, tmp_path): key = unbase64(ourdir / 'example.signing.key.base64') output = f'{tmp_path}/signed.efi' - opts = ukify.parse_args([ + args = [ 'build', *kernel_initrd, f'--output={output}', @@ -558,7 +556,11 @@ def test_efi_signing_sbsign(kernel_initrd, tmp_path): '--cmdline=ARG1 ARG2 ARG3', f'--secureboot-certificate={cert.name}', f'--secureboot-private-key={key.name}', - ]) + ] + if days is not None: + args += [f'--secureboot-certificate-validity={days}'] + + opts = ukify.parse_args(args) try: ukify.check_inputs(opts) @@ -701,8 +703,9 @@ def test_pcr_signing(kernel_initrd, tmp_path): f'--pcr-private-key={priv.name}', ] + arg_tools - # If the public key is not explicitly specified, it is derived automatically. Let's make sure everything - # works as expected both when the public keys is specified explicitly and when it is derived from the + # If the public key is not explicitly specified, it is derived + # automatically. Let's make sure everything works as expected both when the + # public keys is specified explicitly and when it is derived from the # private key. for extra in ([f'--pcrpkey={pub.name}', f'--pcr-public-key={pub.name}'], []): opts = ukify.parse_args(args + extra) diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index b0d0961..f1db9ba 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -51,7 +51,7 @@ from typing import (Any, import pefile # type: ignore -__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})' +__version__ = '{{PROJECT_VERSION_FULL}} ({{VERSION_TAG}})' EFI_ARCH_MAP = { # host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported] @@ -68,7 +68,7 @@ EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), []) # Default configuration directories and file name. # When the user does not specify one, the directories are searched in this order and the first file found is used. -DEFAULT_CONFIG_DIRS = ['/run/systemd', '/etc/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] +DEFAULT_CONFIG_DIRS = ['/etc/systemd', '/run/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] DEFAULT_CONFIG_FILE = 'ukify.conf' class Style: @@ -236,7 +236,7 @@ class Uname: @classmethod def scrape_x86(cls, filename, opts=None): # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136 - # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header + # and https://docs.kernel.org/arch/x86/boot.html#the-real-mode-kernel-header with open(filename, 'rb') as f: f.seek(0x202) magic = f.read(4) @@ -303,6 +303,7 @@ class Uname: DEFAULT_SECTIONS_TO_SHOW = { '.linux' : 'binary', '.initrd' : 'binary', + '.ucode' : 'binary', '.splash' : 'binary', '.dtb' : 'binary', '.cmdline' : 'text', @@ -449,7 +450,7 @@ def check_cert_and_keys_nonexistent(opts): *((priv_key, pub_key) for priv_key, pub_key, _ in key_path_groups(opts))) for path in paths: - if path and path.exists(): + if path and pathlib.Path(path).exists(): raise ValueError(f'{path} is present') @@ -539,7 +540,11 @@ def call_systemd_measure(uki, linux, opts): for priv_key, pub_key, group in key_path_groups(opts): extra = [f'--private-key={priv_key}'] - if pub_key: + if opts.signing_engine is not None: + assert pub_key + extra += [f'--private-key-source=engine:{opts.signing_engine}'] + extra += [f'--certificate={pub_key}'] + elif pub_key: extra += [f'--public-key={pub_key}'] extra += [f'--phase={phase_path}' for phase_path in group or ()] @@ -728,11 +733,13 @@ def sbsign_sign(sbsign_tool, input_f, output_f, opts=None): sbsign_tool, '--key', opts.sb_key, '--cert', opts.sb_cert, - input_f, - '--output', output_f, ] if opts.signing_engine is not None: sign_invocation += ['--engine', opts.signing_engine] + sign_invocation += [ + input_f, + '--output', output_f, + ] signer_sign(sign_invocation) def find_pesign(opts=None): @@ -818,9 +825,23 @@ def make_uki(opts): if pcrpkey is None: if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1: pcrpkey = opts.pcr_public_keys[0] + # If we are getting a certificate when using an engine, we need to convert it to public key format + if opts.signing_engine is not None and pathlib.Path(pcrpkey).exists(): + from cryptography.hazmat.primitives import serialization + from cryptography.x509 import load_pem_x509_certificate + + try: + cert = load_pem_x509_certificate(pathlib.Path(pcrpkey).read_bytes()) + except ValueError: + raise ValueError(f'{pcrpkey} must be an X.509 certificate when signing with an engine') + else: + pcrpkey = cert.public_key().public_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PublicFormat.SubjectPublicKeyInfo, + ) elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: - import cryptography.hazmat.primitives.serialization as serialization - privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None) + from cryptography.hazmat.primitives import serialization + privkey = serialization.load_pem_private_key(pathlib.Path(opts.pcr_private_keys[0]).read_bytes(), password=None) pcrpkey = privkey.public_key().public_bytes( encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo, @@ -835,6 +856,7 @@ def make_uki(opts): ('.splash', opts.splash, True ), ('.pcrpkey', pcrpkey, True ), ('.initrd', initrd, True ), + ('.ucode', opts.microcode, True ), # linux shall be last to leave breathing room for decompression. # We'll add it later. @@ -897,7 +919,6 @@ uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/l print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}") - @contextlib.contextmanager def temporary_umask(mask: int): # Drop bits from umask @@ -1008,7 +1029,7 @@ def generate_keys(opts): print(f'Writing private key for PCR signing to {priv_key}') with temporary_umask(0o077): - priv_key.write_bytes(priv_key_pem) + pathlib.Path(priv_key).write_bytes(priv_key_pem) if pub_key: print(f'Writing public key for PCR signing to {pub_key}') pub_key.write_bytes(pub_key_pem) @@ -1260,6 +1281,14 @@ CONFIG_ITEMS = [ config_push = ConfigItem.config_list_prepend, ), + ConfigItem( + '--microcode', + metavar = 'UCODE', + type = pathlib.Path, + help = 'microcode file [.ucode section]', + config_key = 'UKI/Microcode', + ), + ConfigItem( ('--config', '-c'), metavar = 'PATH', @@ -1411,10 +1440,8 @@ CONFIG_ITEMS = [ ConfigItem( '--pcr-private-key', dest = 'pcr_private_keys', - metavar = 'PATH', - type = pathlib.Path, action = 'append', - help = 'private part of the keypair for signing PCR signatures', + help = 'private part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPrivateKey', config_push = ConfigItem.config_set_group, ), @@ -1424,7 +1451,7 @@ CONFIG_ITEMS = [ metavar = 'PATH', type = pathlib.Path, action = 'append', - help = 'public part of the keypair for signing PCR signatures', + help = 'public part of the keypair or engine-specific designation for signing PCR signatures', config_key = 'PCRSignature:/PCRPublicKey', config_push = ConfigItem.config_set_group, ), @@ -1619,7 +1646,7 @@ def finalize_options(opts): opts.verb = 'build' # Check that --pcr-public-key=, --pcr-private-key=, and --phases= - # have either the same number of arguments are are not specified at all. + # have either the same number of arguments or are not specified at all. n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys) n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups) diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c index 4ee935e..c376676 100644 --- a/src/update-utmp/update-utmp.c +++ b/src/update-utmp/update-utmp.c @@ -82,7 +82,7 @@ static int get_current_runlevel(Context *c) { assert(c); for (unsigned n_attempts = 0;;) { - FOREACH_ARRAY(e, table, ELEMENTSOF(table)) { + FOREACH_ELEMENT(e, table) { _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_free_ char *state = NULL, *path = NULL; diff --git a/src/userdb/20-systemd-userdb.conf.in b/src/userdb/20-systemd-userdb.conf.in new file mode 100644 index 0000000..031fc3a --- /dev/null +++ b/src/userdb/20-systemd-userdb.conf.in @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# Make sure SSH authorized keys recorded in user records can be consumed by SSH +# +AuthorizedKeysCommand {{BINDIR}}/userdbctl ssh-authorized-keys %u +AuthorizedKeysCommandUser root diff --git a/src/userdb/meson.build b/src/userdb/meson.build index 2d701c8..413f2be 100644 --- a/src/userdb/meson.build +++ b/src/userdb/meson.build @@ -23,3 +23,20 @@ executables += [ 'dependencies' : threads, }, ] + +if conf.get('ENABLE_SSH_USERDB_CONFIG') == 1 + custom_target( + '20-systemd-userdb.conf', + input : '20-systemd-userdb.conf.in', + output : '20-systemd-userdb.conf', + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : true, + install_dir : sshdconfdir.startswith('/usr/') ? sshdconfdir : libexecdir / 'sshd_config.d') + + if not sshdconfdir.startswith('/usr/') + install_emptydir(sshdconfdir) + + meson.add_install_script(sh, '-c', + ln_s.format(libexecdir / 'sshd_config.d' / '20-systemd-userdb.conf', sshdconfdir / '20-systemd-userdb.conf')) + endif +endif diff --git a/src/userdb/userdbctl.c b/src/userdb/userdbctl.c index 238a71d..1718419 100644 --- a/src/userdb/userdbctl.c +++ b/src/userdb/userdbctl.c @@ -169,24 +169,24 @@ static const struct { }, }; -static int table_add_uid_boundaries(Table *table, const UidRange *p) { +static int table_add_uid_boundaries(Table *table, const UIDRange *p) { int r; assert(table); - for (size_t i = 0; i < ELEMENTSOF(uid_range_table); i++) { + FOREACH_ELEMENT(i, uid_range_table) { _cleanup_free_ char *name = NULL, *comment = NULL; - if (!uid_range_covers(p, uid_range_table[i].first, uid_range_table[i].last - uid_range_table[i].first + 1)) + if (!uid_range_covers(p, i->first, i->last - i->first + 1)) continue; name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), - " begin ", uid_range_table[i].name, " users ", + " begin ", i->name, " users ", special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); if (!name) return log_oom(); - comment = strjoin("First ", uid_range_table[i].name, " user"); + comment = strjoin("First ", i->name, " user"); if (!comment) return log_oom(); @@ -195,9 +195,9 @@ static int table_add_uid_boundaries(Table *table, const UidRange *p) { TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_TOP), TABLE_STRING, name, TABLE_SET_COLOR, ansi_grey(), - TABLE_STRING, user_disposition_to_string(uid_range_table[i].disposition), + TABLE_STRING, user_disposition_to_string(i->disposition), TABLE_SET_COLOR, ansi_grey(), - TABLE_UID, uid_range_table[i].first, + TABLE_UID, i->first, TABLE_SET_COLOR, ansi_grey(), TABLE_EMPTY, TABLE_STRING, comment, @@ -210,13 +210,13 @@ static int table_add_uid_boundaries(Table *table, const UidRange *p) { free(name); name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP), - " end ", uid_range_table[i].name, " users ", + " end ", i->name, " users ", special_glyph(SPECIAL_GLYPH_ARROW_UP)); if (!name) return log_oom(); free(comment); - comment = strjoin("Last ", uid_range_table[i].name, " user"); + comment = strjoin("Last ", i->name, " user"); if (!comment) return log_oom(); @@ -225,9 +225,9 @@ static int table_add_uid_boundaries(Table *table, const UidRange *p) { TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), TABLE_STRING, name, TABLE_SET_COLOR, ansi_grey(), - TABLE_STRING, user_disposition_to_string(uid_range_table[i].disposition), + TABLE_STRING, user_disposition_to_string(i->disposition), TABLE_SET_COLOR, ansi_grey(), - TABLE_UID, uid_range_table[i].last, + TABLE_UID, i->last, TABLE_SET_COLOR, ansi_grey(), TABLE_EMPTY, TABLE_STRING, comment, @@ -301,7 +301,7 @@ static int add_unavailable_uid(Table *table, uid_t start, uid_t end) { static int table_add_uid_map( Table *table, - const UidRange *p, + const UIDRange *p, int (*add_unavailable)(Table *t, uid_t start, uid_t end)) { uid_t focus = 0; @@ -313,9 +313,7 @@ static int table_add_uid_map( if (!p) return 0; - for (size_t i = 0; p && i < p->n_entries; i++) { - UidRangeEntry *x = p->entries + i; - + FOREACH_ARRAY(x, p->entries, p->n_entries) { if (focus < x->start) { r = add_unavailable(table, focus, x->start-1); if (r < 0) @@ -428,10 +426,10 @@ static int display_user(int argc, char *argv[], void *userdata) { } if (table) { - _cleanup_(uid_range_freep) UidRange *uid_range = NULL; + _cleanup_(uid_range_freep) UIDRange *uid_range = NULL; int boundary_lines, uid_map_lines; - r = uid_range_load_userns(&uid_range, "/proc/self/uid_map"); + r = uid_range_load_userns(/* path = */ NULL, UID_RANGE_USERNS_INSIDE, &uid_range); if (r < 0) log_debug_errno(r, "Failed to load /proc/self/uid_map, ignoring: %m"); @@ -443,7 +441,7 @@ static int display_user(int argc, char *argv[], void *userdata) { if (uid_map_lines < 0) return uid_map_lines; - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return table_log_print_error(r); @@ -529,25 +527,24 @@ static int show_group(GroupRecord *gr, Table *table) { return 0; } -static int table_add_gid_boundaries(Table *table, const UidRange *p) { +static int table_add_gid_boundaries(Table *table, const UIDRange *p) { int r; assert(table); - for (size_t i = 0; i < ELEMENTSOF(uid_range_table); i++) { + FOREACH_ELEMENT(i, uid_range_table) { _cleanup_free_ char *name = NULL, *comment = NULL; - if (!uid_range_covers(p, uid_range_table[i].first, - uid_range_table[i].last - uid_range_table[i].first + 1)) + if (!uid_range_covers(p, i->first, i->last - i->first + 1)) continue; name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_DOWN), - " begin ", uid_range_table[i].name, " groups ", + " begin ", i->name, " groups ", special_glyph(SPECIAL_GLYPH_ARROW_DOWN)); if (!name) return log_oom(); - comment = strjoin("First ", uid_range_table[i].name, " group"); + comment = strjoin("First ", i->name, " group"); if (!comment) return log_oom(); @@ -556,9 +553,9 @@ static int table_add_gid_boundaries(Table *table, const UidRange *p) { TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_TOP), TABLE_STRING, name, TABLE_SET_COLOR, ansi_grey(), - TABLE_STRING, user_disposition_to_string(uid_range_table[i].disposition), + TABLE_STRING, user_disposition_to_string(i->disposition), TABLE_SET_COLOR, ansi_grey(), - TABLE_GID, uid_range_table[i].first, + TABLE_GID, i->first, TABLE_SET_COLOR, ansi_grey(), TABLE_STRING, comment, TABLE_SET_COLOR, ansi_grey(), @@ -568,13 +565,13 @@ static int table_add_gid_boundaries(Table *table, const UidRange *p) { free(name); name = strjoin(special_glyph(SPECIAL_GLYPH_ARROW_UP), - " end ", uid_range_table[i].name, " groups ", + " end ", i->name, " groups ", special_glyph(SPECIAL_GLYPH_ARROW_UP)); if (!name) return log_oom(); free(comment); - comment = strjoin("Last ", uid_range_table[i].name, " group"); + comment = strjoin("Last ", i->name, " group"); if (!comment) return log_oom(); @@ -583,9 +580,9 @@ static int table_add_gid_boundaries(Table *table, const UidRange *p) { TABLE_STRING, special_glyph(SPECIAL_GLYPH_TREE_RIGHT), TABLE_STRING, name, TABLE_SET_COLOR, ansi_grey(), - TABLE_STRING, user_disposition_to_string(uid_range_table[i].disposition), + TABLE_STRING, user_disposition_to_string(i->disposition), TABLE_SET_COLOR, ansi_grey(), - TABLE_GID, uid_range_table[i].last, + TABLE_GID, i->last, TABLE_SET_COLOR, ansi_grey(), TABLE_STRING, comment, TABLE_SET_COLOR, ansi_grey(), @@ -732,10 +729,10 @@ static int display_group(int argc, char *argv[], void *userdata) { } if (table) { - _cleanup_(uid_range_freep) UidRange *gid_range = NULL; + _cleanup_(uid_range_freep) UIDRange *gid_range = NULL; int boundary_lines, gid_map_lines; - r = uid_range_load_userns(&gid_range, "/proc/self/gid_map"); + r = uid_range_load_userns(/* path = */ NULL, GID_RANGE_USERNS_INSIDE, &gid_range); if (r < 0) log_debug_errno(r, "Failed to load /proc/self/gid_map, ignoring: %m"); @@ -747,7 +744,7 @@ static int display_group(int argc, char *argv[], void *userdata) { if (gid_map_lines < 0) return gid_map_lines; - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return table_log_print_error(r); @@ -895,17 +892,17 @@ static int display_memberships(int argc, char *argv[], void *userdata) { } if (table) { - if (table_get_rows(table) > 1) { + if (!table_isempty(table)) { r = table_print_with_pager(table, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return table_log_print_error(r); } if (arg_legend) { - if (table_get_rows(table) > 1) - printf("\n%zu memberships listed.\n", table_get_rows(table) - 1); - else + if (table_isempty(table)) printf("No memberships.\n"); + else + printf("\n%zu memberships listed.\n", table_get_rows(table) - 1); } } @@ -960,17 +957,17 @@ static int display_services(int argc, char *argv[], void *userdata) { return table_log_add_error(r); } - if (table_get_rows(t) > 1) { + if (!table_isempty(t)) { r = table_print_with_pager(t, arg_json_format_flags, arg_pager_flags, arg_legend); if (r < 0) return table_log_print_error(r); } 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 + if (table_isempty(t)) printf("No services.\n"); + else + printf("\n%zu services listed.\n", table_get_rows(t) - 1); } return 0; diff --git a/src/userdb/userdbd-manager.c b/src/userdb/userdbd-manager.c index 359c827..5925602 100644 --- a/src/userdb/userdbd-manager.c +++ b/src/userdb/userdbd-manager.c @@ -4,6 +4,7 @@ #include "sd-daemon.h" +#include "build-path.h" #include "common-signal.h" #include "env-util.h" #include "fd-util.h" @@ -14,6 +15,7 @@ #include "signal-util.h" #include "socket-util.h" #include "stdio-util.h" +#include "strv.h" #include "umask-util.h" #include "userdbd-manager.h" @@ -185,17 +187,19 @@ static int start_one_worker(Manager *m) { _exit(EXIT_FAILURE); } - if (setenv("USERDB_FIXED_WORKER", one_zero(fixed), 1) < 0) { log_error_errno(errno, "Failed to set $USERDB_FIXED_WORKER: %m"); _exit(EXIT_FAILURE); } - /* execl("/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */ - /* execl("/usr/bin/valgrind", "valgrind", "/home/lennart/projects/systemd/build/systemd-userwork", "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /\* With some extra space rename_process() can make use of *\/ */ + r = setenv_systemd_log_level(); + if (r < 0) { + log_error_errno(r, "Failed to set $SYSTEMD_LOG_LEVEL: %m"); + _exit(EXIT_FAILURE); + } - execl(SYSTEMD_USERWORK_PATH, "systemd-userwork", "xxxxxxxxxxxxxxxx", NULL); /* With some extra space rename_process() can make use of */ - log_error_errno(errno, "Failed start worker process: %m"); + r = invoke_callout_binary(SYSTEMD_USERWORK_PATH, STRV_MAKE(SYSTEMD_USERWORK_PATH, "xxxxxxxxxxxxxxxx")); /* With some extra space rename_process() can make use of */ + log_error_errno(r, "Failed start worker process: %m"); _exit(EXIT_FAILURE); } @@ -265,57 +269,85 @@ static int start_workers(Manager *m, bool explicit_request) { return 0; } -int manager_startup(Manager *m) { - int n, r; +static int manager_make_listen_socket(Manager *m) { + static const union sockaddr_union sockaddr = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/userdb/io.systemd.Multiplexer", + }; + int r; assert(m); - assert(m->listen_fd < 0); - n = sd_listen_fds(false); + if (m->listen_fd >= 0) + return 0; + + r = mkdir_p("/run/systemd/userdb", 0755); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/userdb: %m"); + + m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); + if (m->listen_fd < 0) + return log_error_errno(errno, "Failed to bind on socket: %m"); + + (void) sockaddr_un_unlink(&sockaddr.un); + + WITH_UMASK(0000) + if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0) + return log_error_errno(errno, "Failed to bind socket: %m"); + + FOREACH_STRING(alias, + "/run/systemd/userdb/io.systemd.NameServiceSwitch", + "/run/systemd/userdb/io.systemd.DropIn") { + + r = symlink_idempotent("io.systemd.Multiplexer", alias, /* make_relative= */ false); + if (r < 0) + return log_error_errno(r, "Failed to symlink '%s': %m", alias); + } + + if (listen(m->listen_fd, SOMAXCONN_DELUXE) < 0) + return log_error_errno(errno, "Failed to listen on socket: %m"); + + return 1; +} + +static int manager_scan_listen_fds(Manager *m) { + int n; + + assert(m); + + n = sd_listen_fds(/* unset_environment= */ true); if (n < 0) return log_error_errno(n, "Failed to determine number of passed file descriptors: %m"); if (n > 1) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected one listening fd, got %i.", n); if (n == 1) m->listen_fd = SD_LISTEN_FDS_START; - else { - static const union sockaddr_union sockaddr = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/userdb/io.systemd.Multiplexer", - }; - - r = mkdir_p("/run/systemd/userdb", 0755); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd/userdb: %m"); - - m->listen_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); - if (m->listen_fd < 0) - return log_error_errno(errno, "Failed to bind on socket: %m"); - (void) sockaddr_un_unlink(&sockaddr.un); + return 0; +} - WITH_UMASK(0000) - if (bind(m->listen_fd, &sockaddr.sa, SOCKADDR_UN_LEN(sockaddr.un)) < 0) - return log_error_errno(errno, "Failed to bind socket: %m"); +int manager_startup(Manager *m) { + int r; - r = symlink_idempotent("io.systemd.Multiplexer", - "/run/systemd/userdb/io.systemd.NameServiceSwitch", false); - if (r < 0) - return log_error_errno(r, "Failed to bind io.systemd.Multiplexer: %m"); + assert(m); + assert(m->listen_fd < 0); - r = symlink_idempotent("io.systemd.Multiplexer", - "/run/systemd/userdb/io.systemd.DropIn", false); - if (r < 0) - return log_error_errno(r, "Failed to bind io.systemd.Multiplexer: %m"); + r = manager_scan_listen_fds(m); + if (r < 0) + return r; - if (listen(m->listen_fd, SOMAXCONN_DELUXE) < 0) - return log_error_errno(errno, "Failed to listen on socket: %m"); - } + r = manager_make_listen_socket(m); + if (r < 0) + return r; /* Let's make sure every accept() call on this socket times out after 25s. This allows workers to be * GC'ed on idle */ if (setsockopt(m->listen_fd, SOL_SOCKET, SO_RCVTIMEO, TIMEVAL_STORE(LISTEN_TIMEOUT_USEC), sizeof(struct timeval)) < 0) return log_error_errno(errno, "Failed to se SO_RCVTIMEO: %m"); - return start_workers(m, /* explicit_request= */ false); + r = start_workers(m, /* explicit_request= */ false); + if (r < 0) + return r; + + return 0; } diff --git a/src/userdb/userdbd.c b/src/userdb/userdbd.c index 89ac9c7..cbe4c8e 100644 --- a/src/userdb/userdbd.c +++ b/src/userdb/userdbd.c @@ -37,7 +37,7 @@ static int run(int argc, char *argv[]) { if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.NameServiceSwitch:io.systemd.Multiplexer:io.systemd.DropIn", 1) < 0) return log_error_errno(errno, "Failed to set $SYSTEMD_BYPASS_USERDB: %m"); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); r = manager_new(&m); if (r < 0) diff --git a/src/userdb/userwork.c b/src/userdb/userwork.c index b49dbbd..729a9a1 100644 --- a/src/userdb/userwork.c +++ b/src/userdb/userwork.c @@ -353,9 +353,9 @@ static int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, Va static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { static const JsonDispatch dispatch_table[] = { - { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 }, + { "userName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, user_name), 0 }, { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), 0 }, - { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service), 0 }, {} }; @@ -425,16 +425,16 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name)))); } -static int process_connection(VarlinkServer *server, int fd) { +static int process_connection(VarlinkServer *server, int _fd) { + _cleanup_close_ int fd = TAKE_FD(_fd); /* always take possession */ _cleanup_(varlink_close_unrefp) Varlink *vl = NULL; int r; r = varlink_server_add_connection(server, fd, &vl); - if (r < 0) { - fd = safe_close(fd); + if (r < 0) return log_error_errno(r, "Failed to add connection: %m"); - } + TAKE_FD(fd); vl = varlink_ref(vl); for (;;) { @@ -461,6 +461,7 @@ static int process_connection(VarlinkServer *server, int fd) { static int run(int argc, char *argv[]) { usec_t start_time, listen_idle_usec, last_busy_usec = USEC_INFINITY; _cleanup_(varlink_server_unrefp) VarlinkServer *server = NULL; + _cleanup_(pidref_done) PidRef parent = PIDREF_NULL; unsigned n_iterations = 0; int m, listen_fd, r; @@ -505,6 +506,12 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to disable userdb NSS compatibility: %m"); + r = pidref_set_parent(&parent); + if (r < 0) + return log_error_errno(r, "Failed to acquire pidfd of parent process: %m"); + if (parent.pid == 1) /* We got reparented away from userdbd? */ + return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent already died, exiting."); + start_time = now(CLOCK_MONOTONIC); for (;;) { @@ -554,14 +561,11 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to test for POLLIN on listening socket: %m"); if (FLAGS_SET(r, POLLIN)) { - pid_t parent; - - parent = getppid(); - if (parent <= 1) - return log_error_errno(SYNTHETIC_ERRNO(ESRCH), "Parent already died?"); - - if (kill(parent, SIGUSR2) < 0) - return log_error_errno(errno, "Failed to kill our own parent: %m"); + r = pidref_kill(&parent, SIGUSR2); + if (r == -ESRCH) + return log_error_errno(r, "Parent already died?"); + if (r < 0) + return log_error_errno(r, "Failed to send SIGUSR2 signal to parent: %m"); } } diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c index 64105c7..9da484d 100644 --- a/src/varlinkctl/varlinkctl.c +++ b/src/varlinkctl/varlinkctl.c @@ -19,6 +19,7 @@ static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static VarlinkMethodFlags arg_method_flags = 0; +static bool arg_collect = false; static int help(void) { _cleanup_free_ char *link = NULL; @@ -47,6 +48,7 @@ static int help(void) { " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " --more Request multiple responses\n" + " --collect Collect multiple responses in a JSON array\n" " --oneway Do not request response\n" " --json=MODE Output as JSON\n" " -j Same as --json=pretty on tty, --json=short otherwise\n" @@ -73,6 +75,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_MORE, ARG_ONEWAY, ARG_JSON, + ARG_COLLECT, }; static const struct option options[] = { @@ -82,6 +85,7 @@ static int parse_argv(int argc, char *argv[]) { { "more", no_argument, NULL, ARG_MORE }, { "oneway", no_argument, NULL, ARG_ONEWAY }, { "json", required_argument, NULL, ARG_JSON }, + { "collect", no_argument, NULL, ARG_COLLECT }, {}, }; @@ -112,6 +116,10 @@ static int parse_argv(int argc, char *argv[]) { arg_method_flags = (arg_method_flags & ~VARLINK_METHOD_MORE) | VARLINK_METHOD_ONEWAY; break; + case ARG_COLLECT: + arg_collect = true; + break; + case ARG_JSON: r = parse_json_argument(optarg, &arg_json_format_flags); if (r <= 0) @@ -210,12 +218,9 @@ static int verb_info(int argc, char *argv[], void *userdata) { return r; JsonVariant *reply = NULL; - const char *error = NULL; - r = varlink_call(vl, "org.varlink.service.GetInfo", NULL, &reply, &error, NULL); + r = varlink_call_and_log(vl, "org.varlink.service.GetInfo", /* parameters= */ NULL, &reply); if (r < 0) - return log_error_errno(r, "Failed to issue GetInfo() call: %m"); - if (error) - return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInfo() failed: %s", error); + return r; pager_open(arg_pager_flags); @@ -296,12 +301,13 @@ static int verb_introspect(int argc, char *argv[], void *userdata) { return r; JsonVariant *reply = NULL; - const char *error = NULL; - r = varlink_callb(vl, "org.varlink.service.GetInterfaceDescription", &reply, &error, NULL, JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface))); + r = varlink_callb_and_log( + vl, + "org.varlink.service.GetInterfaceDescription", + &reply, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR_STRING("interface", interface))); if (r < 0) - return log_error_errno(r, "Failed to issue GetInterfaceDescription() call: %m"); - if (error) - return log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call GetInterfaceDescription() failed: %s", error); + return r; pager_open(arg_pager_flags); @@ -344,7 +350,7 @@ static int reply_callback( VarlinkReplyFlags flags, void *userdata) { - int r; + int *ret = ASSERT_PTR(userdata), r; assert(link); @@ -352,7 +358,7 @@ static int reply_callback( /* Propagate the error we received via sd_notify() */ (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error); - r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); + r = *ret = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call failed: %s", error); } else r = 0; @@ -373,7 +379,13 @@ static int verb_call(int argc, char *argv[], void *userdata) { method = argv[2]; parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL; - arg_json_format_flags &= ~JSON_FORMAT_OFF; + /* No JSON mode explicitly configured? Then default to the same as -j */ + if (FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) + arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO; + + /* For pipeable text tools it's kinda customary to finish output off in a newline character, and not + * leave incomplete lines hanging around. */ + arg_json_format_flags |= JSON_FORMAT_NEWLINE; if (parameter) { /* is correct, as dispatch_verb() shifts arguments by one for the verb. */ @@ -390,7 +402,26 @@ static int verb_call(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (arg_method_flags & VARLINK_METHOD_ONEWAY) { + if (arg_collect) { + JsonVariant *reply = NULL; + const char *error = NULL; + + r = varlink_collect(vl, method, jp, &reply, &error); + if (r < 0) + return log_error_errno(r, "Failed to issue %s() call: %m", method); + if (error) { + /* Propagate the error we received via sd_notify() */ + (void) sd_notifyf(/* unset_environment= */ false, "VARLINKERROR=%s", error); + + r = log_error_errno(SYNTHETIC_ERRNO(EBADE), "Method call %s() failed: %s", method, error); + } else + r = 0; + + pager_open(arg_pager_flags); + json_variant_dump(reply, arg_json_format_flags, stdout, NULL); + return r; + + } else if (arg_method_flags & VARLINK_METHOD_ONEWAY) { r = varlink_send(vl, method, jp); if (r < 0) return log_error_errno(r, "Failed to issue %s() call: %m", method); @@ -401,7 +432,8 @@ static int verb_call(int argc, char *argv[], void *userdata) { } else if (arg_method_flags & VARLINK_METHOD_MORE) { - varlink_set_userdata(vl, (void*) method); + int ret = 0; + varlink_set_userdata(vl, &ret); r = varlink_bind_reply(vl, reply_callback); if (r < 0) @@ -428,11 +460,13 @@ static int verb_call(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to wait for varlink connection events: %m"); } + + return ret; } else { JsonVariant *reply = NULL; const char *error = NULL; - r = varlink_call(vl, method, jp, &reply, &error, NULL); + r = varlink_call(vl, method, jp, &reply, &error); if (r < 0) return log_error_errno(r, "Failed to issue %s() call: %m", method); diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c index 4d82c65..ba742dd 100644 --- a/src/vconsole/vconsole-setup.c +++ b/src/vconsole/vconsole-setup.c @@ -217,6 +217,8 @@ static int verify_vc_allocation_byfd(int fd) { static int verify_vc_kbmode(int fd) { int curr_mode; + assert(fd >= 0); + /* * Make sure we only adjust consoles in K_XLATE or K_UNICODE mode. * Otherwise we would (likely) interfere with X11's processing of the @@ -231,6 +233,20 @@ static int verify_vc_kbmode(int fd) { return IN_SET(curr_mode, K_XLATE, K_UNICODE) ? 0 : -EBUSY; } +static int verify_vc_display_mode(int fd) { + int mode; + + assert(fd >= 0); + + /* Similarly the vc is likely busy if it is in KD_GRAPHICS mode. If it's not the case and it's been + * left in graphics mode, the kernel will refuse to operate on the font settings anyway. */ + + if (ioctl(fd, KDGETMODE, &mode) < 0) + return -errno; + + return mode != KD_TEXT ? -EBUSY : 0; +} + static int toggle_utf8_vc(const char *name, int fd, bool utf8) { int r; struct termios tc = {}; @@ -374,13 +390,11 @@ static int font_load_and_wait(const char *vc, Context *c) { } /* - * A newly allocated VT uses the font from the source VT. Here - * we update all possibly already allocated VTs with the configured - * font. It also allows to restart systemd-vconsole-setup.service, - * to apply a new font to all VTs. + * A newly allocated VT uses the font from the source VT. Here we update all possibly already allocated VTs + * with the configured font. It also allows systemd-vconsole-setup.service to be restarted to apply a new + * font to all VTs. * - * We also setup per-console utf8 related stuff: kbdmode, term - * processing, stty iutf8. + * We also setup per-console utf8 related stuff: kbdmode, term processing, stty iutf8. */ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { struct console_font_op cfo = { @@ -470,24 +484,14 @@ static void setup_remaining_vcs(int src_fd, unsigned src_idx, bool utf8) { if (cfo.op != KD_FONT_OP_SET) continue; - r = ioctl(fd_d, KDFONTOP, &cfo); + r = verify_vc_display_mode(fd_d); if (r < 0) { - int last_errno, mode; - - /* The fonts couldn't have been copied. It might be due to the - * terminal being in graphical mode. In this case the kernel - * returns -EINVAL which is too generic for distinguishing this - * specific case. So we need to retrieve the terminal mode and if - * the graphical mode is in used, let's assume that something else - * is using the terminal and the failure was expected as we - * shouldn't have tried to copy the fonts. */ - - last_errno = errno; - if (ioctl(fd_d, KDGETMODE, &mode) >= 0 && mode != KD_TEXT) - log_debug("KD_FONT_OP_SET skipped: tty%u is not in text mode", i); - else - log_warning_errno(last_errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i); + log_debug_errno(r, "KD_FONT_OP_SET skipped: tty%u is not in text mode", i); + continue; + } + if (ioctl(fd_d, KDFONTOP, &cfo) < 0) { + log_warning_errno(errno, "KD_FONT_OP_SET failed, fonts will not be copied to tty%u: %m", i); continue; } @@ -515,14 +519,21 @@ static int find_source_vc(char **ret_path, unsigned *ret_idx) { assert(ret_path); assert(ret_idx); + /* This function returns an fd when it finds a candidate. When it fails, it returns the first error + * that occurred when the VC was being opened or -EBUSY when it finds some VCs but all are busy + * otherwise -ENOENT when there is no allocated VC. */ + for (unsigned i = 1; i <= 63; i++) { _cleanup_close_ int fd = -EBADF; _cleanup_free_ char *path = NULL; + /* We save the first error but we give less importance for the case where we previously fail + * due to the VCs being not allocated. Similarly errors on opening a device has a higher + * priority than errors due to devices either not allocated or busy. */ + r = verify_vc_allocation(i); if (r < 0) { - log_debug_errno(r, "VC %u existence check failed, skipping: %m", i); - RET_GATHER(err, r); + RET_GATHER(err, log_debug_errno(r, "VC %u existence check failed, skipping: %m", i)); continue; } @@ -532,23 +543,36 @@ static int find_source_vc(char **ret_path, unsigned *ret_idx) { fd = open_terminal(path, O_RDWR|O_CLOEXEC|O_NOCTTY); if (fd < 0) { log_debug_errno(fd, "Failed to open terminal %s, ignoring: %m", path); - RET_GATHER(err, r); + if (IN_SET(err, 0, -EBUSY, -ENOENT)) + err = fd; continue; } + r = verify_vc_kbmode(fd); if (r < 0) { log_debug_errno(r, "Failed to check VC %s keyboard mode: %m", path); - RET_GATHER(err, r); + if (IN_SET(err, 0, -ENOENT)) + err = r; continue; } + r = verify_vc_display_mode(fd); + if (r < 0) { + log_debug_errno(r, "Failed to check VC %s display mode: %m", path); + if (IN_SET(err, 0, -ENOENT)) + err = r; + continue; + } + + log_debug("Selecting %s as source console", path); + /* all checks passed, return this one as a source console */ *ret_idx = i; *ret_path = TAKE_PTR(path); return TAKE_FD(fd); } - return log_error_errno(err, "No usable source console found: %m"); + return err; } static int verify_source_vc(char **ret_path, const char *src_vc) { @@ -572,6 +596,13 @@ static int verify_source_vc(char **ret_path, const char *src_vc) { if (r < 0) return log_error_errno(r, "Virtual console %s is not in K_XLATE or K_UNICODE: %m", src_vc); + /* setfont(8) silently ignores when the font can't be applied due to the vc being in + * KD_GRAPHICS. Hence we continue to accept this case however we now let the user know that the vc + * will be initialized only partially.*/ + r = verify_vc_display_mode(fd); + if (r < 0) + log_notice_errno(r, "Virtual console %s is not in KD_TEXT, font settings likely won't be applied.", src_vc); + path = strdup(src_vc); if (!path) return log_oom(); @@ -592,30 +623,37 @@ static int run(int argc, char **argv) { umask(0022); - if (argv[1]) + if (argv[1]) { fd = verify_source_vc(&vc, argv[1]); - else + if (fd < 0) + return fd; + } else { fd = find_source_vc(&vc, &idx); - if (fd < 0) - return fd; + if (fd < 0 && fd != -EBUSY) + return log_error_errno(fd, "No usable source console found: %m"); + } utf8 = is_locale_utf8(); + (void) toggle_utf8_sysfs(utf8); + + if (fd < 0) { + /* We found only busy VCs, which might happen during the boot process when the boot splash is + * displayed on the only allocated VC. In this case we don't interfere and avoid initializing + * the VC partially as some operations are likely to fail. */ + log_notice("All allocated VCs are currently busy, skipping initialization of font and keyboard settings."); + return EXIT_SUCCESS; + } context_load_config(&c); /* Take lock around the remaining operation to avoid being interrupted by a tty reset operation * performed for services with TTYVHangup=yes. */ lock_fd = lock_dev_console(); - if (lock_fd < 0) { - log_full_errno(lock_fd == -ENOENT ? LOG_DEBUG : LOG_ERR, - lock_fd, - "Failed to lock /dev/console%s: %m", - lock_fd == -ENOENT ? ", ignoring" : ""); - if (lock_fd != -ENOENT) - return lock_fd; - } + if (ERRNO_IS_NEG_DEVICE_ABSENT(lock_fd)) + log_debug_errno(lock_fd, "Device /dev/console does not exist, proceeding without lock: %m"); + else if (lock_fd < 0) + log_warning_errno(lock_fd, "Failed to lock /dev/console, proceeding without lock: %m"); - (void) toggle_utf8_sysfs(utf8); (void) toggle_utf8_vc(vc, fd, utf8); r = font_load_and_wait(vc, &c); diff --git a/src/veritysetup/veritysetup-generator.c b/src/veritysetup/veritysetup-generator.c index d55d4aa..95ce82b 100644 --- a/src/veritysetup/veritysetup-generator.c +++ b/src/veritysetup/veritysetup-generator.c @@ -252,7 +252,7 @@ static int determine_device( if (*data_what && *hash_what) return 0; - r = unhexmem(hash, strlen(hash), &m, &l); + r = unhexmem(hash, &m, &l); if (r < 0) return log_error_errno(r, "Failed to parse hash: %s", hash); if (l < sizeof(sd_id128_t)) { diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index d73c2d3..d133572 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -205,7 +205,7 @@ static int parse_options(const char *options) { size_t l; void *m; - r = unhexmem(val, strlen(val), &m, &l); + r = unhexmem(val, &m, &l); if (r < 0) return log_error_errno(r, "Failed to parse salt '%s': %m", word); @@ -312,7 +312,7 @@ static int run(int argc, char *argv[]) { if (!filename_is_valid(volume)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); - r = unhexmem(root_hash, SIZE_MAX, &m, &l); + r = unhexmem(root_hash, &m, &l); if (r < 0) return log_error_errno(r, "Failed to parse root hash: %m"); @@ -378,7 +378,7 @@ static int run(int argc, char *argv[]) { char *value; if ((value = startswith(arg_root_hash_signature, "base64:"))) { - r = unbase64mem(value, strlen(value), (void *)&hash_sig, &hash_sig_size); + r = unbase64mem(value, (void*) &hash_sig, &hash_sig_size); if (r < 0) return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); } else { diff --git a/src/version/version.h.in b/src/version/version.h.in index 083779a..01ff697 100644 --- a/src/version/version.h.in +++ b/src/version/version.h.in @@ -7,4 +7,4 @@ * - where a simplified machine-parsable form is more useful, for example * pkgconfig files and version information written to binary files. */ -#define GIT_VERSION "@VCS_TAG@" +#define GIT_VERSION VERSION_TAG "@VCS_TAG@" diff --git a/src/vmspawn/meson.build b/src/vmspawn/meson.build index 800d7c3..3cd9a3b 100644 --- a/src/vmspawn/meson.build +++ b/src/vmspawn/meson.build @@ -3,6 +3,9 @@ libvmspawn_core_sources = files( 'vmspawn-settings.c', 'vmspawn-util.c', + 'vmspawn-scope.c', + 'vmspawn-mount.c', + 'vmspawn-register.c', ) libvmspawn_core = static_library( 'vmspawn-core', @@ -16,6 +19,10 @@ vmspawn_libs = [ libshared, ] +vmspawn_test_template = test_template + { + 'link_with' : [vmspawn_libs], +} + executables += [ executable_template + { 'name' : 'systemd-vmspawn', @@ -23,5 +30,10 @@ executables += [ 'conditions': ['ENABLE_VMSPAWN'], 'sources' : files('vmspawn.c'), 'link_with' : vmspawn_libs, - } + 'dependencies' : [libblkid] + }, + vmspawn_test_template + { + 'conditions': ['ENABLE_VMSPAWN'], + 'sources' : files('test-vmspawn-util.c'), + }, ] diff --git a/src/vmspawn/test-vmspawn-util.c b/src/vmspawn/test-vmspawn-util.c new file mode 100644 index 0000000..67e5c4c --- /dev/null +++ b/src/vmspawn/test-vmspawn-util.c @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "alloc-util.h" +#include "string-util.h" +#include "vmspawn-util.h" +#include "tests.h" + +#define _ESCAPE_QEMU_VALUE_CHECK(str, correct, varname) \ + do { \ + _cleanup_free_ char* varname = NULL; \ + varname = escape_qemu_value(str); \ + assert(varname); \ + assert_se(streq(varname, correct)); \ + } while (0) + +#define ESCAPE_QEMU_VALUE_CHECK(str, correct) \ + _ESCAPE_QEMU_VALUE_CHECK(str, correct, conf##__COUNTER__) + +TEST(escape_qemu_value) { + ESCAPE_QEMU_VALUE_CHECK("abcde", "abcde"); + ESCAPE_QEMU_VALUE_CHECK("a,bcde", "a,,bcde"); + ESCAPE_QEMU_VALUE_CHECK(",,,", ",,,,,,"); + ESCAPE_QEMU_VALUE_CHECK("", ""); +} + +DEFINE_TEST_MAIN(LOG_INFO); diff --git a/src/vmspawn/vmspawn-mount.c b/src/vmspawn/vmspawn-mount.c new file mode 100644 index 0000000..ee63bda --- /dev/null +++ b/src/vmspawn/vmspawn-mount.c @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "alloc-util.h" +#include "extract-word.h" +#include "macro.h" +#include "parse-argument.h" +#include "path-util.h" +#include "string-util.h" +#include "vmspawn-mount.h" + +static void runtime_mount_done(RuntimeMount *mount) { + assert(mount); + + mount->source = mfree(mount->source); + mount->target = mfree(mount->target); +} + +void runtime_mount_context_done(RuntimeMountContext *ctx) { + assert(ctx); + + FOREACH_ARRAY(mount, ctx->mounts, ctx->n_mounts) + runtime_mount_done(mount); + + free(ctx->mounts); +} + +int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only) { + _cleanup_(runtime_mount_done) RuntimeMount mount = { .read_only = read_only }; + _cleanup_free_ char *source_rel = NULL; + int r; + + assert(ctx); + + r = extract_first_word(&s, &source_rel, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (isempty(source_rel)) + return -EINVAL; + + r = path_make_absolute_cwd(source_rel, &mount.source); + if (r < 0) + return r; + + /* virtiofsd only supports directories */ + r = is_dir(mount.source, /* follow= */ true); + if (r < 0) + return r; + if (!r) + return -ENOTDIR; + + mount.target = s ? strdup(s) : TAKE_PTR(source_rel); + if (!mount.target) + return -ENOMEM; + + if (!path_is_absolute(mount.target)) + return -EINVAL; + + if (!GREEDY_REALLOC(ctx->mounts, ctx->n_mounts + 1)) + return log_oom(); + + ctx->mounts[ctx->n_mounts++] = TAKE_STRUCT(mount); + + return 0; +} diff --git a/src/vmspawn/vmspawn-mount.h b/src/vmspawn/vmspawn-mount.h new file mode 100644 index 0000000..2ea24fd --- /dev/null +++ b/src/vmspawn/vmspawn-mount.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include + +typedef struct RuntimeMount { + bool read_only; + char *source; + char *target; +} RuntimeMount; + +typedef struct RuntimeMountContext { + RuntimeMount *mounts; + size_t n_mounts; +} RuntimeMountContext; + +void runtime_mount_context_done(RuntimeMountContext *ctx); +int runtime_mount_parse(RuntimeMountContext *ctx, const char *s, bool read_only); diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c new file mode 100644 index 0000000..42650b8 --- /dev/null +++ b/src/vmspawn/vmspawn-register.c @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-bus.h" +#include "sd-id128.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "json.h" +#include "macro.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "varlink.h" +#include "vmspawn-register.h" + +int register_machine( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *directory, + unsigned cid, + const char *address, + const char *key_path) { + + _cleanup_(varlink_unrefp) Varlink *vl = NULL; + int r; + + assert(machine_name); + assert(service); + + /* First try to use varlink, as it provides more features (such as SSH support). */ + r = varlink_connect_address(&vl, "/run/systemd/machine/io.systemd.Machine"); + if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + assert(bus); + + /* In case we are running with an older machined, fallback to the existing D-Bus method. */ + r = bus_call_method( + bus, + bus_machine_mgr, + "RegisterMachine", + &error, + NULL, + "sayssus", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + "vm", + (uint32_t) getpid_cached(), + strempty(directory)); + if (r < 0) + return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r)); + + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to connect to machined on /run/systemd/machine/io.systemd.Machine: %m"); + + return varlink_callb_and_log(vl, + "io.systemd.Machine.Register", + NULL, + JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_STRING("name", machine_name), + JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", JSON_BUILD_ID128(uuid)), + JSON_BUILD_PAIR_STRING("service", service), + JSON_BUILD_PAIR_STRING("class", "vm"), + JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", JSON_BUILD_UNSIGNED(cid)), + JSON_BUILD_PAIR_CONDITION(directory, "rootDirectory", JSON_BUILD_STRING(directory)), + JSON_BUILD_PAIR_CONDITION(address, "sshAddress", JSON_BUILD_STRING(address)), + JSON_BUILD_PAIR_CONDITION(key_path, "sshPrivateKeyPath", JSON_BUILD_STRING(key_path)))); +} + +int unregister_machine(sd_bus *bus, const char *machine_name) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + + r = bus_call_method(bus, bus_machine_mgr, "UnregisterMachine", &error, NULL, "s", machine_name); + if (r < 0) + log_debug("Failed to unregister machine: %s", bus_error_message(&error, r)); + + return 0; +} diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h new file mode 100644 index 0000000..69f5671 --- /dev/null +++ b/src/vmspawn/vmspawn-register.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "sd-bus.h" +#include "sd-id128.h" + +int register_machine( + sd_bus *bus, + const char *machine_name, + sd_id128_t uuid, + const char *service, + const char *directory, + unsigned cid, + const char *address, + const char *key_path); +int unregister_machine(sd_bus *bus, const char *machine_name); diff --git a/src/vmspawn/vmspawn-scope.c b/src/vmspawn/vmspawn-scope.c new file mode 100644 index 0000000..58f6781 --- /dev/null +++ b/src/vmspawn/vmspawn-scope.c @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-bus.h" + +#include "bus-error.h" +#include "bus-locator.h" +#include "bus-unit-util.h" +#include "bus-util.h" +#include "bus-wait-for-jobs.h" +#include "escape.h" +#include "macro.h" +#include "process-util.h" +#include "random-util.h" +#include "socket-util.h" +#include "strv.h" +#include "unit-def.h" +#include "unit-name.h" +#include "vmspawn-scope.h" + +int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_free_ char *scope = NULL, *description = NULL; + const char *object; + int r; + + assert(bus); + assert(machine_name); + + /* Creates a transient scope unit which tracks the lifetime of the current process */ + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + if (asprintf(&scope, "machine-%"PRIu64"-%s.scope", random_u64(), machine_name) < 0) + return log_oom(); + + description = strjoin("Virtual Machine ", machine_name); + if (!description) + return log_oom(); + + 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_append(m, "ss", /* name */ scope, /* mode */ "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 = sd_bus_message_append(m, "(sv)(sv)(sv)", + "Description", "s", description, + "AddRef", "b", 1, + "CollectMode", "s", "inactive-or-failed"); + if (r < 0) + return bus_log_create_error(r); + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + r = pidref_set_self(&pidref); + if (r < 0) + return log_error_errno(r, "Failed to allocate PID reference: %m"); + + r = bus_append_scope_pidref(m, &pidref, allow_pidfd); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* No auxiliary units */ + r = sd_bus_message_append( + m, + "a(sa(sv))", + 0); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + /* If this failed with a property we couldn't write, this is quite likely because the server + * doesn't support PIDFDs yet, let's try without. */ + if (allow_pidfd && + sd_bus_error_has_names(&error, SD_BUS_ERROR_UNKNOWN_PROPERTY, SD_BUS_ERROR_PROPERTY_READ_ONLY)) + return start_transient_scope(bus, machine_name, false, ret_scope); + + return log_error_errno(r, "Failed to start transient scope unit: %s", bus_error_message(&error, r)); + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL); + if (r < 0) + return r; + + if (ret_scope) + *ret_scope = TAKE_PTR(scope); + + return 0; +} + +static int message_add_commands(sd_bus_message *m, const char *exec_type, char ***commands, size_t n_commands) { + int r; + + assert(m); + assert(exec_type); + assert(commands || n_commands == 0); + + /* A small helper for adding an ExecStart / ExecStopPost / etc.. property to an sd_bus_message */ + + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", exec_type); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return bus_log_create_error(r); + + FOREACH_ARRAY(cmd, commands, n_commands) { + char **cmdline = *cmd; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", cmdline[0]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, cmdline); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "b", 0); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 0; +} + +void socket_service_pair_done(SocketServicePair *p) { + assert(p); + + p->exec_start_pre = strv_free(p->exec_start_pre); + p->exec_start = strv_free(p->exec_start); + p->exec_stop_post = strv_free(p->exec_stop_post); + p->unit_name_prefix = mfree(p->unit_name_prefix); + p->runtime_directory = mfree(p->runtime_directory); + p->listen_address = mfree(p->listen_address); + p->socket_type = 0; +} + +int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _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_free_ char *service_desc = NULL, *service_name = NULL, *socket_name = NULL; + const char *object, *socket_type_str; + int r; + + /* Starts a socket/service unit pair bound to the given scope. */ + + assert(bus); + assert(scope); + assert(p); + assert(p->unit_name_prefix); + assert(p->exec_start); + assert(p->listen_address); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + socket_name = strjoin(p->unit_name_prefix, ".socket"); + if (!socket_name) + return log_oom(); + + service_name = strjoin(p->unit_name_prefix, ".service"); + if (!service_name) + return log_oom(); + + service_desc = quote_command_line(p->exec_start, SHELL_ESCAPE_EMPTY); + if (!service_desc) + return log_oom(); + + socket_type_str = socket_address_type_to_string(p->socket_type); + if (!socket_type_str) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Invalid socket type: %d", p->socket_type); + + 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_append(m, "ssa(sv)", + /* ss - name, mode */ + socket_name, "fail", + /* a(sv) - Properties */ + 5, + "Description", "s", p->listen_address, + "AddRef", "b", 1, + "BindsTo", "as", 1, scope, + "Listen", "a(ss)", 1, socket_type_str, p->listen_address, + "CollectMode", "s", "inactive-or-failed"); + if (r < 0) + return bus_log_create_error(r); + + /* aux */ + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", service_name); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)", + "Description", "s", service_desc, + "AddRef", "b", 1, + "BindsTo", "as", 1, scope, + "CollectMode", "s", "inactive-or-failed"); + if (r < 0) + return bus_log_create_error(r); + + if (p->runtime_directory) { + r = sd_bus_message_append(m, "(sv)", "RuntimeDirectory", "as", 1, p->runtime_directory); + if (r < 0) + return bus_log_create_error(r); + } + + if (p->exec_start_pre) { + r = message_add_commands(m, "ExecStartPre", &p->exec_start_pre, 1); + if (r < 0) + return r; + } + + r = message_add_commands(m, "ExecStart", &p->exec_start, 1); + if (r < 0) + return r; + + if (p->exec_stop_post) { + r = message_add_commands(m, "ExecStopPost", &p->exec_stop_post, 1); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start %s as transient unit: %s", p->exec_start[0], bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + return bus_wait_for_jobs_one(w, object, /* quiet */ false, NULL); +} diff --git a/src/vmspawn/vmspawn-scope.h b/src/vmspawn/vmspawn-scope.h new file mode 100644 index 0000000..74c7511 --- /dev/null +++ b/src/vmspawn/vmspawn-scope.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "sd-bus.h" + +#include "macro.h" + +typedef struct SocketServicePair { + char **exec_start_pre; + char **exec_start; + char **exec_stop_post; + char *unit_name_prefix; + char *runtime_directory; + char *listen_address; + int socket_type; +} SocketServicePair; + +void socket_service_pair_done(SocketServicePair *p); + +int start_transient_scope(sd_bus *bus, const char *machine_name, bool allow_pidfd, char **ret_scope); +int start_socket_service_pair(sd_bus *bus, const char *scope, SocketServicePair *p); diff --git a/src/vmspawn/vmspawn-settings.c b/src/vmspawn/vmspawn-settings.c index cb1a463..780df55 100644 --- a/src/vmspawn/vmspawn-settings.c +++ b/src/vmspawn/vmspawn-settings.c @@ -1,3 +1,13 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "string-table.h" #include "vmspawn-settings.h" + +static const char *const console_mode_table[_CONSOLE_MODE_MAX] = { + [CONSOLE_INTERACTIVE] = "interactive", + [CONSOLE_READ_ONLY] = "read-only", + [CONSOLE_NATIVE] = "native", + [CONSOLE_GUI] = "gui", +}; + +DEFINE_STRING_TABLE_LOOKUP(console_mode, ConsoleMode); diff --git a/src/vmspawn/vmspawn-settings.h b/src/vmspawn/vmspawn-settings.h index 268a874..5446c20 100644 --- a/src/vmspawn/vmspawn-settings.h +++ b/src/vmspawn/vmspawn-settings.h @@ -1,11 +1,28 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include #include +#include "macro.h" + +typedef enum ConsoleMode { + CONSOLE_INTERACTIVE, /* ptyfwd */ + CONSOLE_READ_ONLY, /* ptyfwd, but in read-only mode */ + CONSOLE_NATIVE, /* qemu's native TTY handling */ + CONSOLE_GUI, /* qemu's graphical UI */ + _CONSOLE_MODE_MAX, + _CONSOLE_MODE_INVALID = -EINVAL, +} ConsoleMode; + typedef enum SettingsMask { SETTING_START_MODE = UINT64_C(1) << 0, + SETTING_MACHINE_ID = UINT64_C(1) << 6, + SETTING_BIND_MOUNTS = UINT64_C(1) << 11, SETTING_DIRECTORY = UINT64_C(1) << 26, SETTING_CREDENTIALS = UINT64_C(1) << 30, _SETTING_FORCE_ENUM_WIDTH = UINT64_MAX } SettingsMask; + +const char *console_mode_to_string(ConsoleMode m) _const_; +ConsoleMode console_mode_from_string(const char *s) _pure_; diff --git a/src/vmspawn/vmspawn-util.c b/src/vmspawn/vmspawn-util.c index b5b5eaf..472dd92 100644 --- a/src/vmspawn/vmspawn-util.c +++ b/src/vmspawn/vmspawn-util.c @@ -7,6 +7,7 @@ #include "architecture.h" #include "conf-files.h" #include "errno-util.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "json.h" @@ -20,19 +21,53 @@ #include "siphash24.h" #include "socket-util.h" #include "sort-util.h" +#include "string-table.h" #include "string-util.h" #include "strv.h" #include "vmspawn-util.h" +static const char* const architecture_to_qemu_table[_ARCHITECTURE_MAX] = { + [ARCHITECTURE_ARM64] = "aarch64", /* differs from our name */ + [ARCHITECTURE_ARM] = "arm", + [ARCHITECTURE_ALPHA] = "alpha", + [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ + [ARCHITECTURE_X86] = "i386", /* differs from our name */ + [ARCHITECTURE_LOONGARCH64] = "loongarch64", + [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ + [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ + [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ + [ARCHITECTURE_PPC] = "ppc", + [ARCHITECTURE_RISCV32] = "riscv32", + [ARCHITECTURE_RISCV64] = "riscv64", + [ARCHITECTURE_S390X] = "s390x", +}; + +static int native_arch_as_qemu(const char **ret) { + const char *s = architecture_to_qemu_table[native_architecture()]; + if (!s) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture())); + + if (ret) + *ret = s; + + return 0; +} + OvmfConfig* ovmf_config_free(OvmfConfig *config) { if (!config) return NULL; free(config->path); + free(config->format); free(config->vars); + free(config->vars_format); return mfree(config); } +DEFINE_STRING_TABLE_LOOKUP(network_stack, NetworkStack); + int qemu_check_kvm_support(void) { if (access("/dev/kvm", F_OK) >= 0) return true; @@ -40,7 +75,7 @@ int qemu_check_kvm_support(void) { log_debug_errno(errno, "/dev/kvm not found. Not using KVM acceleration."); return false; } - if (errno == EPERM) { + if (ERRNO_IS_PRIVILEGE(errno)) { log_debug_errno(errno, "Permission denied to access /dev/kvm. Not using KVM acceleration."); return false; } @@ -62,11 +97,11 @@ int qemu_check_vsock_support(void) { fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); if (fd >= 0) return true; - if (errno == ENODEV) { + if (ERRNO_IS_DEVICE_ABSENT(errno)) { log_debug_errno(errno, "/dev/vhost-vsock device doesn't exist. Not adding a vsock device to the virtual machine."); return false; } - if (errno == EPERM) { + if (ERRNO_IS_PRIVILEGE(errno)) { log_debug_errno(errno, "Permission denied to access /dev/vhost-vsock. Not adding a vsock device to the virtual machine."); return false; } @@ -78,16 +113,28 @@ int qemu_check_vsock_support(void) { typedef struct FirmwareData { char **features; char *firmware; + char *firmware_format; char *vars; + char *vars_format; + char **architectures; } FirmwareData; +static bool firmware_data_supports_sb(const FirmwareData *fwd) { + assert(fwd); + + return strv_contains(fwd->features, "secure-boot"); +} + static FirmwareData* firmware_data_free(FirmwareData *fwd) { if (!fwd) return NULL; - fwd->features = strv_free(fwd->features); - fwd->firmware = mfree(fwd->firmware); - fwd->vars = mfree(fwd->vars); + strv_free(fwd->features); + free(fwd->firmware); + free(fwd->firmware_format); + free(fwd->vars); + free(fwd->vars_format); + strv_free(fwd->architectures); return mfree(fwd); } @@ -95,22 +142,22 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(FirmwareData*, firmware_data_free); static int firmware_executable(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch table[] = { - { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY }, - { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, firmware_format), JSON_MANDATORY }, {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); } static int firmware_nvram_template(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { static const JsonDispatch table[] = { - { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY }, - { "format", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "filename", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars), JSON_MANDATORY }, + { "format", JSON_VARIANT_STRING, json_dispatch_string, offsetof(FirmwareData, vars_format), JSON_MANDATORY }, {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); } static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { @@ -121,15 +168,170 @@ static int firmware_mapping(const char *name, JsonVariant *v, JsonDispatchFlags {} }; - return json_dispatch(v, table, 0, userdata); + return json_dispatch(v, table, flags, userdata); +} + +static int target_architecture(const char *name, JsonVariant *v, JsonDispatchFlags flags, void *userdata) { + int r; + JsonVariant *e; + char ***supported_architectures = ASSERT_PTR(userdata); + + static const JsonDispatch table[] = { + { "architecture", JSON_VARIANT_STRING, json_dispatch_string, 0, JSON_MANDATORY }, + { "machines", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + {} + }; + + JSON_VARIANT_ARRAY_FOREACH(e, v) { + _cleanup_free_ char *arch = NULL; + + r = json_dispatch(e, table, flags, &arch); + if (r < 0) + return r; + + r = strv_consume(supported_architectures, TAKE_PTR(arch)); + if (r < 0) + return r; + } + + return 0; +} + +static int get_firmware_search_dirs(char ***ret) { + int r; + + assert(ret); + + /* Search in: + * - $XDG_CONFIG_HOME/qemu/firmware + * - /etc/qemu/firmware + * - /usr/share/qemu/firmware + * + * Prioritising entries in "more specific" directories */ + + _cleanup_free_ char *user_firmware_dir = NULL; + r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware"); + if (r < 0) + return r; + + _cleanup_strv_free_ char **l = NULL; + l = strv_new(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware"); + if (!l) + return log_oom_debug(); + + *ret = TAKE_PTR(l); + return 0; +} + +int list_ovmf_config(char ***ret) { + _cleanup_strv_free_ char **search_dirs = NULL; + int r; + + assert(ret); + + r = get_firmware_search_dirs(&search_dirs); + if (r < 0) + return r; + + r = conf_files_list_strv( + ret, + ".json", + /* root= */ NULL, + CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR, + (const char *const*) search_dirs); + if (r < 0) + return log_debug_errno(r, "Failed to list firmware files: %m"); + + return 0; +} + +static int load_firmware_data(const char *path, FirmwareData **ret) { + int r; + + assert(path); + assert(ret); + + _cleanup_(json_variant_unrefp) JsonVariant *json = NULL; + r = json_parse_file( + /* f= */ NULL, + path, + /* flags= */ 0, + &json, + /* ret_line= */ NULL, + /* ret_column= */ NULL); + if (r < 0) + return r; + + static const JsonDispatch table[] = { + { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, + { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY }, + { "targets", JSON_VARIANT_ARRAY, target_architecture, offsetof(FirmwareData, architectures), JSON_MANDATORY }, + { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY }, + { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, + {} + }; + + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + fwd = new0(FirmwareData, 1); + if (!fwd) + return -ENOMEM; + + r = json_dispatch(json, table, JSON_ALLOW_EXTENSIONS, fwd); + if (r < 0) + return r; + + *ret = TAKE_PTR(fwd); + return 0; +} + +static int ovmf_config_make(FirmwareData *fwd, OvmfConfig **ret) { + assert(fwd); + assert(ret); + + _cleanup_free_ OvmfConfig *config = NULL; + config = new(OvmfConfig, 1); + if (!config) + return -ENOMEM; + + *config = (OvmfConfig) { + .path = TAKE_PTR(fwd->firmware), + .format = TAKE_PTR(fwd->firmware_format), + .vars = TAKE_PTR(fwd->vars), + .vars_format = TAKE_PTR(fwd->vars_format), + .supports_sb = firmware_data_supports_sb(fwd), + }; + + *ret = TAKE_PTR(config); + return 0; +} + +int load_ovmf_config(const char *path, OvmfConfig **ret) { + _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; + int r; + + assert(path); + assert(ret); + + r = load_firmware_data(path, &fwd); + if (r < 0) + return r; + + return ovmf_config_make(fwd, ret); } int find_ovmf_config(int search_sb, OvmfConfig **ret) { _cleanup_(ovmf_config_freep) OvmfConfig *config = NULL; - _cleanup_free_ char *user_firmware_dir = NULL; _cleanup_strv_free_ char **conf_files = NULL; + const char* native_arch_qemu; int r; + assert(ret); + + r = native_arch_as_qemu(&native_arch_qemu); + if (r < 0) + return r; + /* Search in: * - $XDG_CONFIG_HOME/qemu/firmware * - /etc/qemu/firmware @@ -138,74 +340,40 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { * Prioritising entries in "more specific" directories */ - r = xdg_user_config_dir(&user_firmware_dir, "/qemu/firmware"); + r = list_ovmf_config(&conf_files); if (r < 0) return r; - r = conf_files_list_strv(&conf_files, ".json", NULL, CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR, - STRV_MAKE_CONST(user_firmware_dir, "/etc/qemu/firmware", "/usr/share/qemu/firmware")); - if (r < 0) - return log_debug_errno(r, "Failed to list config files: %m"); - STRV_FOREACH(file, conf_files) { _cleanup_(firmware_data_freep) FirmwareData *fwd = NULL; - _cleanup_(json_variant_unrefp) JsonVariant *config_json = NULL; - _cleanup_free_ char *contents = NULL; - size_t contents_sz = 0; - r = read_full_file(*file, &contents, &contents_sz); - if (r == -ENOMEM) - return r; + r = load_firmware_data(*file, &fwd); if (r < 0) { - log_debug_errno(r, "Failed to read contents of %s - ignoring: %m", *file); + log_debug_errno(r, "Failed to load JSON file '%s', skipping: %m", *file); continue; } - r = json_parse(contents, 0, &config_json, NULL, NULL); - if (r == -ENOMEM) - return r; - if (r < 0) { - log_debug_errno(r, "Failed to parse the JSON in %s - ignoring: %m", *file); + if (strv_contains(fwd->features, "enrolled-keys")) { + log_debug("Skipping %s, firmware has enrolled keys which has been known to cause issues.", *file); continue; } - static const JsonDispatch table[] = { - { "description", JSON_VARIANT_STRING, NULL, 0, JSON_MANDATORY }, - { "interface-types", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - { "mapping", JSON_VARIANT_OBJECT, firmware_mapping, 0, JSON_MANDATORY }, - { "targets", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - { "features", JSON_VARIANT_ARRAY, json_dispatch_strv, offsetof(FirmwareData, features), JSON_MANDATORY }, - { "tags", JSON_VARIANT_ARRAY, NULL, 0, JSON_MANDATORY }, - {} - }; - - fwd = new0(FirmwareData, 1); - if (!fwd) - return -ENOMEM; - - r = json_dispatch(config_json, table, 0, fwd); - if (r == -ENOMEM) - return r; - if (r < 0) { - log_debug_errno(r, "Failed to extract the required fields from the JSON in %s - ignoring: %m", *file); + if (!strv_contains(fwd->architectures, native_arch_qemu)) { + log_debug("Skipping %s, firmware doesn't support the native architecture.", *file); continue; } - int sb_present = !!strv_find(fwd->features, "secure-boot"); - /* exclude firmware which doesn't match our Secure Boot requirements */ - if (search_sb >= 0 && search_sb != sb_present) { - log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration", *file); + if (search_sb >= 0 && !!search_sb != firmware_data_supports_sb(fwd)) { + log_debug("Skipping %s, firmware doesn't fit required Secure Boot configuration.", *file); continue; } - config = new0(OvmfConfig, 1); - if (!config) - return -ENOMEM; + r = ovmf_config_make(fwd, &config); + if (r < 0) + return r; - config->path = TAKE_PTR(fwd->firmware); - config->vars = TAKE_PTR(fwd->vars); - config->supports_sb = sb_present; + log_debug("Selected firmware definition %s.", *file); break; } @@ -219,6 +387,7 @@ int find_ovmf_config(int search_sb, OvmfConfig **ret) { } int find_qemu_binary(char **ret_qemu_binary) { + const char *native_arch_qemu; int r; /* @@ -228,24 +397,6 @@ int find_qemu_binary(char **ret_qemu_binary) { * If the native architecture is not supported by qemu -EOPNOTSUPP will be returned; */ - static const char *architecture_to_qemu_table[_ARCHITECTURE_MAX] = { - [ARCHITECTURE_ARM64] = "aarch64", /* differs from our name */ - [ARCHITECTURE_ARM] = "arm", - [ARCHITECTURE_ALPHA] = "alpha", - [ARCHITECTURE_X86_64] = "x86_64", /* differs from our name */ - [ARCHITECTURE_X86] = "i386", /* differs from our name */ - [ARCHITECTURE_LOONGARCH64] = "loongarch64", - [ARCHITECTURE_MIPS64_LE] = "mips", /* differs from our name */ - [ARCHITECTURE_MIPS_LE] = "mips", /* differs from our name */ - [ARCHITECTURE_PARISC] = "hppa", /* differs from our name */ - [ARCHITECTURE_PPC64_LE] = "ppc", /* differs from our name */ - [ARCHITECTURE_PPC64] = "ppc", /* differs from our name */ - [ARCHITECTURE_PPC] = "ppc", - [ARCHITECTURE_RISCV32] = "riscv32", - [ARCHITECTURE_RISCV64] = "riscv64", - [ARCHITECTURE_S390X] = "s390x", - }; - FOREACH_STRING(s, "qemu", "qemu-kvm") { r = find_executable(s, ret_qemu_binary); if (r == 0) @@ -255,19 +406,19 @@ int find_qemu_binary(char **ret_qemu_binary) { return r; } - const char *arch_qemu = architecture_to_qemu_table[native_architecture()]; - if (!arch_qemu) - return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Architecture %s not supported by qemu", architecture_to_string(native_architecture())); + r = native_arch_as_qemu(&native_arch_qemu); + if (r < 0) + return r; _cleanup_free_ char *qemu_arch_specific = NULL; - qemu_arch_specific = strjoin("qemu-system-", arch_qemu); + qemu_arch_specific = strjoin("qemu-system-", native_arch_qemu); if (!qemu_arch_specific) return -ENOMEM; return find_executable(qemu_arch_specific, ret_qemu_binary); } -int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock) { +int vsock_fix_child_cid(int vhost_device_fd, unsigned *machine_cid, const char *machine) { /* this is an arbitrary value picked from /dev/urandom */ static const uint8_t sip_key[HASH_KEY_SIZE] = { 0x03, 0xad, 0xf0, 0xa4, @@ -276,14 +427,13 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi 0xf5, 0x4c, 0x80, 0x52 }; struct siphash machine_hash_state, state; - _cleanup_close_ int vfd = -EBADF; int r; /* uint64_t is required here for the ioctl call, but valid CIDs are only 32 bits */ uint64_t cid = *ASSERT_PTR(machine_cid); assert(machine); - assert(ret_child_sock); + assert(vhost_device_fd >= 0); /* Fix the CID of the AF_VSOCK socket passed to qemu * @@ -296,16 +446,10 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi * If after another 64 attempts this hasn't worked then give up and return EADDRNOTAVAIL. */ - /* remove O_CLOEXEC before this fd is passed to QEMU */ - vfd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); - if (vfd < 0) - return log_debug_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); - if (cid != VMADDR_CID_ANY) { - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r < 0) return log_debug_errno(errno, "Failed to set CID for child vsock with user provided CID %" PRIu64 ": %m", cid); - *ret_child_sock = TAKE_FD(vfd); return 0; } @@ -317,10 +461,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi uint64_t hash = siphash24_finalize(&state); cid = 3 + (hash % (UINT_MAX - 4)); - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r >= 0) { *machine_cid = cid; - *ret_child_sock = TAKE_FD(vfd); return 0; } if (errno != EADDRINUSE) @@ -329,10 +472,9 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi for (unsigned i = 0; i < 64; i++) { cid = 3 + random_u64_range(UINT_MAX - 4); - r = ioctl(vfd, VHOST_VSOCK_SET_GUEST_CID, &cid); + r = ioctl(vhost_device_fd, VHOST_VSOCK_SET_GUEST_CID, &cid); if (r >= 0) { *machine_cid = cid; - *ret_child_sock = TAKE_FD(vfd); return 0; } @@ -342,3 +484,36 @@ int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_chi return log_debug_errno(SYNTHETIC_ERRNO(EADDRNOTAVAIL), "Failed to assign a CID to the guest vsock"); } + +char* escape_qemu_value(const char *s) { + const char *f; + char *e, *t; + size_t n; + + assert(s); + + /* QEMU requires that commas in arguments to be escaped by doubling up the commas. See + * https://www.qemu.org/docs/master/system/qemu-manpage.html#options for more information. + * + * This function performs this escaping, returning an allocated string with the escaped value, or + * NULL if allocation failed. */ + + n = strlen(s); + + if (n > (SIZE_MAX - 1) / 2) + return NULL; + + e = new(char, n*2 + 1); + if (!e) + return NULL; + + for (f = s, t = e; f < s + n; f++) { + *t++ = *f; + if (*f == ',') + *t++ = ','; + } + + *t = 0; + + return e; +} diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h index 53ad7dd..fed0996 100644 --- a/src/vmspawn/vmspawn-util.h +++ b/src/vmspawn/vmspawn-util.h @@ -5,22 +5,87 @@ #include "macro.h" #if defined(__x86_64__) || defined(__i386__) || defined(__arm__) || defined(__aarch64__) -#define ARCHITECTURE_SUPPORTS_SMBIOS 1 +# define ARCHITECTURE_SUPPORTS_SMBIOS 1 #else -#define ARCHITECTURE_SUPPORTS_SMBIOS 0 +# define ARCHITECTURE_SUPPORTS_SMBIOS 0 +#endif + +#if defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) +# define ARCHITECTURE_SUPPORTS_TPM 1 +#else +# define ARCHITECTURE_SUPPORTS_TPM 0 +#endif + +#if defined(__x86_64__) || defined(__i386__) +# define ARCHITECTURE_SUPPORTS_SMM 1 +#else +# define ARCHITECTURE_SUPPORTS_SMM 0 +#endif + +#if defined(__arm__) || defined(__aarch64__) +# define DEFAULT_SERIAL_TTY "ttyAMA0" +#elif defined(__s390__) || defined(__s390x__) +# define DEFAULT_SERIAL_TTY "ttysclp0" +#elif defined(__powerpc__) || defined(__powerpc64__) +# define DEFAULT_SERIAL_TTY "hvc0" +#else +# define DEFAULT_SERIAL_TTY "ttyS0" +#endif + +#if defined(__x86_64__) || defined(__i386__) +# define QEMU_MACHINE_TYPE "q35" +#elif defined(__arm__) || defined(__aarch64__) +# define QEMU_MACHINE_TYPE "virt" +#elif defined(__s390__) || defined(__s390x__) +# define QEMU_MACHINE_TYPE "s390-ccw-virtio" +#elif defined(__powerpc__) || defined(__powerpc64__) +# define QEMU_MACHINE_TYPE "pseries" +#else +# error "No qemu machine defined for this architecture" #endif typedef struct OvmfConfig { char *path; + char *format; char *vars; + char *vars_format; bool supports_sb; } OvmfConfig; +static inline const char *ovmf_config_format(const OvmfConfig *c) { + return ASSERT_PTR(c)->format ?: "raw"; +} + +static inline const char *ovmf_config_vars_format(const OvmfConfig *c) { + return ASSERT_PTR(c)->vars_format ?: "raw"; +} + OvmfConfig* ovmf_config_free(OvmfConfig *ovmf_config); DEFINE_TRIVIAL_CLEANUP_FUNC(OvmfConfig*, ovmf_config_free); +typedef enum NetworkStack { + NETWORK_STACK_TAP, + NETWORK_STACK_USER, + NETWORK_STACK_NONE, + _NETWORK_STACK_MAX, + _NETWORK_STACK_INVALID = -EINVAL, +} NetworkStack; + +static const char* const network_stack_table[_NETWORK_STACK_MAX] = { + [NETWORK_STACK_TAP] = "tap", + [NETWORK_STACK_USER] = "user", + [NETWORK_STACK_NONE] = "none", +}; + +const char* network_stack_to_string(NetworkStack type) _const_; +NetworkStack network_stack_from_string(const char *s) _pure_; + int qemu_check_kvm_support(void); int qemu_check_vsock_support(void); -int find_ovmf_config(int search_sb, OvmfConfig **ret_ovmf_config); +int list_ovmf_config(char ***ret); +int load_ovmf_config(const char *path, OvmfConfig **ret); +int find_ovmf_config(int search_sb, OvmfConfig **ret); int find_qemu_binary(char **ret_qemu_binary); -int vsock_fix_child_cid(unsigned *machine_cid, const char *machine, int *ret_child_sock); +int vsock_fix_child_cid(int vsock_fd, unsigned *machine_cid, const char *machine); + +char* escape_qemu_value(const char *s); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index ebae681..326722d 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -1,59 +1,136 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include +#include #include #include +#include #include -#include +#include +#include #include +#include "sd-daemon.h" +#include "sd-event.h" +#include "sd-id128.h" + #include "alloc-util.h" #include "architecture.h" +#include "bootspec.h" #include "build.h" +#include "bus-internal.h" +#include "bus-locator.h" +#include "bus-wait-for-jobs.h" +#include "chase.h" #include "common-signal.h" #include "copy.h" #include "creds-util.h" +#include "dirent-util.h" +#include "discover-image.h" +#include "dissect-image.h" #include "escape.h" +#include "ether-addr-util.h" +#include "event-util.h" +#include "extract-word.h" +#include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" +#include "gpt.h" #include "hexdecoct.h" #include "hostname-util.h" +#include "io-util.h" +#include "kernel-image.h" #include "log.h" #include "machine-credential.h" +#include "macro.h" #include "main-func.h" +#include "mkdir.h" +#include "netif-util.h" #include "pager.h" #include "parse-argument.h" #include "parse-util.h" +#include "path-lookup.h" #include "path-util.h" +#include "pidref.h" #include "pretty-print.h" #include "process-util.h" -#include "sd-event.h" +#include "ptyfwd.h" +#include "random-util.h" +#include "rm-rf.h" #include "signal-util.h" #include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" #include "strv.h" +#include "time-util.h" #include "tmpfile-util.h" +#include "unit-name.h" +#include "vmspawn-mount.h" +#include "vmspawn-register.h" +#include "vmspawn-scope.h" #include "vmspawn-settings.h" #include "vmspawn-util.h" +#define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93) + +typedef struct SSHInfo { + unsigned cid; + char *private_key_path; + unsigned port; +} SSHInfo; + +static bool arg_quiet = false; static PagerFlags arg_pager_flags = 0; +static char *arg_directory = NULL; static char *arg_image = NULL; static char *arg_machine = NULL; -static char *arg_qemu_smp = NULL; -static uint64_t arg_qemu_mem = 2ULL * 1024ULL * 1024ULL * 1024ULL; -static int arg_qemu_kvm = -1; -static int arg_qemu_vsock = -1; -static uint64_t arg_vsock_cid = UINT64_MAX; -static bool arg_qemu_gui = false; +static char *arg_cpus = NULL; +static uint64_t arg_ram = UINT64_C(2) * U64_GB; +static int arg_kvm = -1; +static int arg_vsock = -1; +static unsigned arg_vsock_cid = VMADDR_CID_ANY; +static int arg_tpm = -1; +static char *arg_linux = NULL; +static char **arg_initrds = NULL; +static ConsoleMode arg_console_mode = CONSOLE_INTERACTIVE; +static NetworkStack arg_network_stack = NETWORK_STACK_NONE; static int arg_secure_boot = -1; -static MachineCredential *arg_credentials = NULL; -static size_t arg_n_credentials = 0; +static MachineCredentialContext arg_credentials = {}; +static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; +static RuntimeMountContext arg_runtime_mounts = {}; static SettingsMask arg_settings_mask = 0; -static char **arg_parameters = NULL; - +static char *arg_firmware = NULL; +static char *arg_runtime_directory = NULL; +static char *arg_forward_journal = NULL; +static bool arg_runtime_directory_created = false; +static bool arg_privileged = false; +static bool arg_register = false; +static sd_id128_t arg_uuid = {}; +static char **arg_kernel_cmdline_extra = NULL; +static char **arg_extra_drives = NULL; +static char *arg_background = NULL; +static bool arg_pass_ssh_key = true; +static char *arg_ssh_key_type = NULL; +static bool arg_discard_disk = true; +struct ether_addr arg_network_provided_mac = {}; + +STATIC_DESTRUCTOR_REGISTER(arg_directory, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_machine, freep); -STATIC_DESTRUCTOR_REGISTER(arg_qemu_smp, freep); -STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_cpus, freep); +STATIC_DESTRUCTOR_REGISTER(arg_runtime_directory, freep); +STATIC_DESTRUCTOR_REGISTER(arg_credentials, machine_credential_context_done); +STATIC_DESTRUCTOR_REGISTER(arg_firmware, freep); +STATIC_DESTRUCTOR_REGISTER(arg_linux, freep); +STATIC_DESTRUCTOR_REGISTER(arg_initrds, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_runtime_mounts, runtime_mount_context_done); +STATIC_DESTRUCTOR_REGISTER(arg_forward_journal, freep); +STATIC_DESTRUCTOR_REGISTER(arg_kernel_cmdline_extra, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_extra_drives, strv_freep); +STATIC_DESTRUCTOR_REGISTER(arg_background, freep); +STATIC_DESTRUCTOR_REGISTER(arg_ssh_key_type, freep); static int help(void) { _cleanup_free_ char *link = NULL; @@ -67,29 +144,56 @@ static int help(void) { printf("%1$s [OPTIONS...] [ARGUMENTS...]\n\n" "%5$sSpawn a command or OS in a virtual machine.%6$s\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " --no-pager Do not pipe output into a pager\n\n" - "%3$sImage:%4$s\n" - " -i --image=PATH Root file system disk image (or device node) for\n" - " the virtual machine\n\n" - "%3$sHost Configuration:%4$s\n" - " --qemu-smp=SMP Configure guest's SMP settings\n" - " --qemu-mem=MEM Configure guest's RAM size\n" - " --qemu-kvm=BOOL Configure whether to use KVM or not\n" - " --qemu-vsock=BOOL Configure whether to use qemu with a vsock or not\n" - " --vsock-cid= Specify the CID to use for the qemu guest's vsock\n" - " --qemu-gui Start QEMU in graphical mode\n" - " --secure-boot=BOOL Configure whether to search for firmware which\n" - " supports Secure Boot\n\n" - "%3$sSystem Identity:%4$s\n" - " -M --machine=NAME Set the machine name for the container\n" - "%3$sCredentials:%4$s\n" + " -h --help Show this help\n" + " --version Print version string\n" + " -q --quiet Do not show status information\n" + " --no-pager Do not pipe output into a pager\n" + "\n%3$sImage:%4$s\n" + " -D --directory=PATH Root directory for the VM\n" + " -i --image=FILE|DEVICE Root file system disk image or device for the VM\n" + "\n%3$sHost Configuration:%4$s\n" + " --cpus=CPUS Configure number of CPUs in guest\n" + " --ram=BYTES Configure guest's RAM size\n" + " --kvm=BOOL Enable use of KVM\n" + " --vsock=BOOL Override autodetection of VSOCK support\n" + " --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n" + " --tpm=BOOL Enable use of a virtual TPM\n" + " --linux=PATH Specify the linux kernel for direct kernel boot\n" + " --initrd=PATH Specify the initrd for direct kernel boot\n" + " -n --network-tap Create a TAP device for networking\n" + " --network-user-mode Use user mode networking\n" + " --secure-boot=BOOL Enable searching for firmware supporting SecureBoot\n" + " --firmware=PATH|list Select firmware definition file (or list available)\n" + " --discard-disk=BOOL Control processing of discard requests\n" + "\n%3$sSystem Identity:%4$s\n" + " -M --machine=NAME Set the machine name for the VM\n" + " --uuid=UUID Set a specific machine UUID for the VM\n" + "\n%3$sProperties:%4$s\n" + " --register=BOOLEAN Register VM with systemd-machined\n" + "\n%3$sUser Namespacing:%4$s\n" + " --private-users=UIDBASE[:NUIDS]\n" + " Configure the UID/GID range to map into the\n" + " virtiofsd namespace\n" + "\n%3$sMounts:%4$s\n" + " --bind=SOURCE[:TARGET]\n" + " Mount a file or directory from the host into the VM\n" + " --bind-ro=SOURCE[:TARGET]\n" + " Mount a file or directory, but read-only\n" + " --extra-drive=PATH Adds an additional disk to the virtual machine\n" + "\n%3$sIntegration:%4$s\n" + " --forward-journal=FILE|DIR\n" + " Forward the VM's journal to the host\n" + " --pass-ssh-key=BOOL Create an SSH key to access the VM\n" + " --ssh-key-type=TYPE Choose what type of SSH key to pass\n" + "\n%3$sInput/Output:%4$s\n" + " --console=MODE Console mode (interactive, native, gui)\n" + " --background=COLOR Set ANSI color for background\n" + "\n%3$sCredentials:%4$s\n" " --set-credential=ID:VALUE\n" - " Pass a credential with literal value to container.\n" + " Pass a credential with literal value to the VM\n" " --load-credential=ID:PATH\n" - " Load credential to pass to container from file or\n" - " AF_UNIX stream socket.\n" + " Load credential for the VM from file or AF_UNIX\n" + " stream socket.\n" "\nSee the %2$s for details.\n", program_invocation_short_name, link, @@ -101,36 +205,91 @@ static int help(void) { return 0; } +static int parse_environment(void) { + const char *e; + int r; + + e = getenv("SYSTEMD_VMSPAWN_NETWORK_MAC"); + if (e) { + r = parse_ether_addr(e, &arg_network_provided_mac); + if (r < 0) + return log_error_errno(r, "Failed to parse provided MAC address via environment variable"); + } + + return 0; +} + static int parse_argv(int argc, char *argv[]) { enum { ARG_VERSION = 0x100, ARG_NO_PAGER, - ARG_QEMU_SMP, - ARG_QEMU_MEM, - ARG_QEMU_KVM, - ARG_QEMU_VSOCK, + ARG_CPUS, + ARG_RAM, + ARG_KVM, + ARG_VSOCK, ARG_VSOCK_CID, + ARG_TPM, + ARG_LINUX, + ARG_INITRD, ARG_QEMU_GUI, + ARG_NETWORK_USER_MODE, + ARG_UUID, + ARG_REGISTER, + ARG_BIND, + ARG_BIND_RO, + ARG_EXTRA_DRIVE, ARG_SECURE_BOOT, + ARG_PRIVATE_USERS, + ARG_FORWARD_JOURNAL, + ARG_PASS_SSH_KEY, + ARG_SSH_KEY_TYPE, ARG_SET_CREDENTIAL, ARG_LOAD_CREDENTIAL, + ARG_FIRMWARE, + ARG_DISCARD_DISK, + ARG_CONSOLE, + ARG_BACKGROUND, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "image", required_argument, NULL, 'i' }, - { "machine", required_argument, NULL, 'M' }, - { "qemu-smp", required_argument, NULL, ARG_QEMU_SMP }, - { "qemu-mem", required_argument, NULL, ARG_QEMU_MEM }, - { "qemu-kvm", required_argument, NULL, ARG_QEMU_KVM }, - { "qemu-vsock", required_argument, NULL, ARG_QEMU_VSOCK }, - { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, - { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, - { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, - { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, - { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "quiet", no_argument, NULL, 'q' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "image", required_argument, NULL, 'i' }, + { "directory", required_argument, NULL, 'D' }, + { "machine", required_argument, NULL, 'M' }, + { "cpus", required_argument, NULL, ARG_CPUS }, + { "qemu-smp", required_argument, NULL, ARG_CPUS }, /* Compat alias */ + { "ram", required_argument, NULL, ARG_RAM }, + { "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */ + { "kvm", required_argument, NULL, ARG_KVM }, + { "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */ + { "vsock", required_argument, NULL, ARG_VSOCK }, + { "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */ + { "vsock-cid", required_argument, NULL, ARG_VSOCK_CID }, + { "tpm", required_argument, NULL, ARG_TPM }, + { "linux", required_argument, NULL, ARG_LINUX }, + { "initrd", required_argument, NULL, ARG_INITRD }, + { "console", required_argument, NULL, ARG_CONSOLE }, + { "qemu-gui", no_argument, NULL, ARG_QEMU_GUI }, /* compat option */ + { "network-tap", no_argument, NULL, 'n' }, + { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "register", required_argument, NULL, ARG_REGISTER }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, + { "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE }, + { "secure-boot", required_argument, NULL, ARG_SECURE_BOOT }, + { "private-users", required_argument, NULL, ARG_PRIVATE_USERS }, + { "forward-journal", required_argument, NULL, ARG_FORWARD_JOURNAL }, + { "pass-ssh-key", required_argument, NULL, ARG_PASS_SSH_KEY }, + { "ssh-key-type", required_argument, NULL, ARG_SSH_KEY_TYPE }, + { "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL }, + { "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL }, + { "firmware", required_argument, NULL, ARG_FIRMWARE }, + { "discard-disk", required_argument, NULL, ARG_DISCARD_DISK }, + { "background", required_argument, NULL, ARG_BACKGROUND }, {} }; @@ -140,7 +299,7 @@ static int parse_argv(int argc, char *argv[]) { assert(argv); optind = 0; - while ((c = getopt_long(argc, argv, "+hi:M", options, NULL)) >= 0) + while ((c = getopt_long(argc, argv, "+hD:i:M:nq", options, NULL)) >= 0) switch (c) { case 'h': return help(); @@ -148,6 +307,18 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); + case 'q': + arg_quiet = true; + break; + + case 'D': + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_directory); + if (r < 0) + return r; + + arg_settings_mask |= SETTING_DIRECTORY; + break; + case 'i': r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); if (r < 0) @@ -174,48 +345,127 @@ static int parse_argv(int argc, char *argv[]) { arg_pager_flags |= PAGER_DISABLE; break; - case ARG_QEMU_SMP: - r = free_and_strdup_warn(&arg_qemu_smp, optarg); + case ARG_CPUS: + r = free_and_strdup_warn(&arg_cpus, optarg); if (r < 0) return r; break; - case ARG_QEMU_MEM: - r = parse_size(optarg, 1024, &arg_qemu_mem); + case ARG_RAM: + r = parse_size(optarg, 1024, &arg_ram); if (r < 0) - return log_error_errno(r, "Failed to parse --qemu-mem=%s: %m", optarg); + return log_error_errno(r, "Failed to parse --ram=%s: %m", optarg); break; - case ARG_QEMU_KVM: - r = parse_tristate(optarg, &arg_qemu_kvm); + case ARG_KVM: + r = parse_tristate(optarg, &arg_kvm); if (r < 0) - return log_error_errno(r, "Failed to parse --qemu-kvm=%s: %m", optarg); + return log_error_errno(r, "Failed to parse --kvm=%s: %m", optarg); break; - case ARG_QEMU_VSOCK: - r = parse_tristate(optarg, &arg_qemu_vsock); + case ARG_VSOCK: + r = parse_tristate(optarg, &arg_vsock); if (r < 0) - return log_error_errno(r, "Failed to parse --qemu-vsock=%s: %m", optarg); + return log_error_errno(r, "Failed to parse --vsock=%s: %m", optarg); break; - case ARG_VSOCK_CID: { - unsigned cid; + case ARG_VSOCK_CID: if (isempty(optarg)) - cid = VMADDR_CID_ANY; + arg_vsock_cid = VMADDR_CID_ANY; else { - r = safe_atou_bounded(optarg, 3, UINT_MAX - 1, &cid); - if (r == -ERANGE) - return log_error_errno(r, "Invalid value for --vsock-cid=: %m"); + unsigned cid; + + r = vsock_parse_cid(optarg, &cid); if (r < 0) - return log_error_errno(r, "Failed to parse --vsock-cid=%s: %m", optarg); + return log_error_errno(r, "Failed to parse --vsock-cid: %s", optarg); + if (!VSOCK_CID_IS_REGULAR(cid)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified CID is not regular, refusing: %u", cid); + + arg_vsock_cid = cid; } - arg_vsock_cid = (uint64_t)cid; + break; + + case ARG_TPM: + r = parse_tristate(optarg, &arg_tpm); + if (r < 0) + return log_error_errno(r, "Failed to parse --tpm=%s: %m", optarg); + break; + + case ARG_LINUX: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_linux); + if (r < 0) + return r; + break; + + case ARG_INITRD: { + _cleanup_free_ char *initrd_path = NULL; + r = parse_path_argument(optarg, /* suppress_root= */ false, &initrd_path); + if (r < 0) + return r; + + r = strv_consume(&arg_initrds, TAKE_PTR(initrd_path)); + if (r < 0) + return log_oom(); + break; } + case ARG_CONSOLE: + arg_console_mode = console_mode_from_string(optarg); + if (arg_console_mode < 0) + return log_error_errno(arg_console_mode, "Failed to parse specified console mode: %s", optarg); + + break; + case ARG_QEMU_GUI: - arg_qemu_gui = true; + arg_console_mode = CONSOLE_GUI; + break; + + case 'n': + arg_network_stack = NETWORK_STACK_TAP; + break; + + case ARG_NETWORK_USER_MODE: + arg_network_stack = NETWORK_STACK_USER; + break; + + case ARG_UUID: + r = id128_from_string_nonzero(optarg, &arg_uuid); + if (r == -ENXIO) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Machine UUID may not be all zeroes."); + if (r < 0) + return log_error_errno(r, "Invalid UUID: %s", optarg); + + arg_settings_mask |= SETTING_MACHINE_ID; + break; + + case ARG_REGISTER: + r = parse_boolean_argument("--register=", optarg, &arg_register); + if (r < 0) + return r; + break; + + case ARG_BIND: + case ARG_BIND_RO: + r = runtime_mount_parse(&arg_runtime_mounts, optarg, c == ARG_BIND_RO); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); + + arg_settings_mask |= SETTING_BIND_MOUNTS; + break; + + case ARG_EXTRA_DRIVE: { + _cleanup_free_ char *drive_path = NULL; + + r = parse_path_argument(optarg, /* suppress_root= */ false, &drive_path); + if (r < 0) + return r; + + r = strv_consume(&arg_extra_drives, TAKE_PTR(drive_path)); + if (r < 0) + return log_oom(); break; + } case ARG_SECURE_BOOT: r = parse_tristate(optarg, &arg_secure_boot); @@ -223,8 +473,35 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(r, "Failed to parse --secure-boot=%s: %m", optarg); break; + case ARG_PRIVATE_USERS: + r = parse_userns_uid_range(optarg, &arg_uid_shift, &arg_uid_range); + if (r < 0) + return r; + break; + + case ARG_FORWARD_JOURNAL: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_forward_journal); + if (r < 0) + return r; + break; + + case ARG_PASS_SSH_KEY: + r = parse_boolean_argument("--pass-ssh-key=", optarg, &arg_pass_ssh_key); + if (r < 0) + return r; + break; + + case ARG_SSH_KEY_TYPE: + if (!string_is_safe(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid value for --arg-ssh-key-type=: %s", optarg); + + r = free_and_strdup_warn(&arg_ssh_key_type, optarg); + if (r < 0) + return r; + break; + case ARG_SET_CREDENTIAL: { - r = machine_credential_set(&arg_credentials, &arg_n_credentials, optarg); + r = machine_credential_set(&arg_credentials, optarg); if (r < 0) return r; arg_settings_mask |= SETTING_CREDENTIALS; @@ -232,7 +509,7 @@ static int parse_argv(int argc, char *argv[]) { } case ARG_LOAD_CREDENTIAL: { - r = machine_credential_load(&arg_credentials, &arg_n_credentials, optarg); + r = machine_credential_load(&arg_credentials, optarg); if (r < 0) return r; @@ -240,6 +517,43 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_FIRMWARE: + if (streq(optarg, "list")) { + _cleanup_strv_free_ char **l = NULL; + + r = list_ovmf_config(&l); + if (r < 0) + return log_error_errno(r, "Failed to list firmwares: %m"); + + bool nl = false; + fputstrv(stdout, l, "\n", &nl); + if (nl) + putchar('\n'); + + return 0; + } + + if (!isempty(optarg) && !path_is_absolute(optarg) && !startswith(optarg, "./")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Absolute path or path starting with './' required."); + + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_firmware); + if (r < 0) + return r; + + break; + + case ARG_DISCARD_DISK: + r = parse_boolean_argument("--discard-disk=", optarg, &arg_discard_disk); + if (r < 0) + return r; + break; + + case ARG_BACKGROUND: + r = free_and_strdup_warn(&arg_background, optarg); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -248,9 +562,8 @@ static int parse_argv(int argc, char *argv[]) { } if (argc > optind) { - strv_free(arg_parameters); - arg_parameters = strv_copy(argv + optind); - if (!arg_parameters) + arg_kernel_cmdline_extra = strv_copy(argv + optind); + if (!arg_kernel_cmdline_extra) return log_oom(); arg_settings_mask |= SETTING_START_MODE; @@ -274,11 +587,11 @@ static int open_vsock(void) { r = bind(vsock_fd, &bind_addr.sa, sizeof(bind_addr.vm)); if (r < 0) - return log_error_errno(errno, "Failed to bind to vsock to address %u:%u: %m", bind_addr.vm.svm_cid, bind_addr.vm.svm_port); + return log_error_errno(errno, "Failed to bind to VSOCK address %u:%u: %m", bind_addr.vm.svm_cid, bind_addr.vm.svm_port); r = listen(vsock_fd, SOMAXCONN_DELUXE); if (r < 0) - return log_error_errno(errno, "Failed to listen on vsock: %m"); + return log_error_errno(errno, "Failed to listen on VSOCK: %m"); return TAKE_FD(vsock_fd); } @@ -352,13 +665,13 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u assert(userdata); if (revents != EPOLLIN) { - log_warning("Got unexpected poll event for vsock fd."); + log_warning("Got unexpected poll event for VSOCK fd."); return 0; } conn_fd = accept4(fd, NULL, NULL, SOCK_CLOEXEC|SOCK_NONBLOCK); if (conn_fd < 0) { - log_warning_errno(errno, "Failed to accept connection from vsock fd (%m), ignoring..."); + log_warning_errno(errno, "Failed to accept connection from VSOCK fd (%m), ignoring..."); return 0; } @@ -377,25 +690,84 @@ static int vmspawn_dispatch_vsock_connections(sd_event_source *source, int fd, u return 0; } -static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **notify_event_source) { +static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_event_source **ret_notify_event_source) { int r; - r = sd_event_add_io(event, notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status); + assert(event); + assert(fd >= 0); + assert(exit_status); + assert(ret_notify_event_source); + + r = sd_event_add_io(event, ret_notify_event_source, fd, EPOLLIN, vmspawn_dispatch_vsock_connections, exit_status); if (r < 0) return log_error_errno(r, "Failed to allocate notify socket event source: %m"); - (void) sd_event_source_set_description(*notify_event_source, "vmspawn-notify-sock"); + (void) sd_event_source_set_description(*ret_notify_event_source, "vmspawn-notify-sock"); + + return 0; +} + +static int bus_open_in_machine(sd_bus **ret, unsigned cid, unsigned port, const char *private_key_path) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *ssh_escaped = NULL, *bus_address = NULL; + char port_str[DECIMAL_STR_MAX(unsigned)], cid_str[DECIMAL_STR_MAX(unsigned)]; + int r; + + assert(ret); + assert(private_key_path); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh"; + ssh_escaped = bus_address_escape(ssh); + if (!ssh_escaped) + return -ENOMEM; + + xsprintf(port_str, "%u", port); + xsprintf(cid_str, "%u", cid); + + bus_address = strjoin( + "unixexec:path=", ssh_escaped, + /* -x: Disable X11 forwarding + * -T: Disable PTY allocation */ + ",argv1=-xT", + ",argv2=-o,argv3=IdentitiesOnly yes", + ",argv4=-o,argv5=IdentityFile=", private_key_path, + ",argv6=-p,argv7=", port_str, + ",argv8=--", + ",argv9=root@vsock/", cid_str, + ",argv10=systemd-stdio-bridge" + ); + if (!bus_address) + return -ENOMEM; + + free_and_replace(bus->address, bus_address); + bus->bus_client = true; + bus->trusted = true; + bus->runtime_scope = RUNTIME_SCOPE_SYSTEM; + bus->is_local = false; + + r = sd_bus_start(bus); + if (r < 0) + return r; + *ret = TAKE_PTR(bus); return 0; } static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - pid_t pid; + PidRef *pidref = userdata; + int r; + + /* Backup method to shut down the VM when D-BUS access over SSH is not available */ - pid = PTR_TO_PID(userdata); - if (pid > 0) { - /* TODO: actually talk to qemu and ask the guest to shutdown here */ - if (kill(pid, SIGKILL) >= 0) { + if (pidref) { + r = pidref_kill(pidref, SIGKILL); + if (r < 0) + log_warning_errno(r, "Failed to kill qemu, terminating: %m"); + else { log_info("Trying to halt qemu. Send SIGTERM again to trigger vmspawn to immediately terminate."); sd_event_source_set_userdata(s, NULL); return 0; @@ -406,6 +778,61 @@ static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo return 0; } +static int forward_signal_to_vm_pid1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _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; + SSHInfo *ssh_info = ASSERT_PTR(userdata); + const char *vm_pid1; + int r; + + assert(s); + assert(si); + + r = bus_open_in_machine(&bus, ssh_info->cid, ssh_info->port, ssh_info->private_key_path); + if (r < 0) + return log_error_errno(r, "Failed to connect to VM to forward signal: %m"); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch job: %m"); + + r = bus_call_method( + bus, + bus_systemd_mgr, + "GetUnitByPID", + &error, + NULL, + ""); + if (r < 0) + return log_error_errno(r, "Failed to get init process of VM: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &vm_pid1); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, vm_pid1, /* quiet */ false, NULL); + if (r < 0) + return r; + + r = bus_call_method( + bus, + bus_systemd_mgr, + "KillUnit", + &error, + NULL, + "ssi", + vm_pid1, + "leader", + si->ssi_signo); + if (r < 0) + return log_error_errno(r, "Failed to forward signal to PID 1 of the VM: %s", bus_error_message(&error, r)); + log_info("Sent signal %"PRIu32" to the VM's PID 1.", si->ssi_signo); + + return 0; +} + static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) { sd_event_exit(sd_event_source_get_event(s), 0); return 0; @@ -426,7 +853,6 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { assert(addr_len >= sizeof addr.vm); assert(addr.vm.svm_family == AF_VSOCK); - log_info("Using vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port); r = strv_extendf(cmdline, "type=11,value=io.systemd.credential:vmm.notify_socket=vsock-stream:%u:%u", (unsigned) VMADDR_CID_HOST, addr.vm.svm_port); if (r < 0) return r; @@ -434,218 +860,1208 @@ static int cmdline_add_vsock(char ***cmdline, int vsock_fd) { return 0; } -static int run_virtual_machine(void) { - _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; - _cleanup_strv_free_ char **cmdline = NULL; - _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL; +static int start_tpm( + sd_bus *bus, + const char *scope, + const char *swtpm, + char **ret_state_tempdir) { + + _cleanup_(rm_rf_physical_and_freep) char *state_dir = NULL; + _cleanup_free_ char *scope_prefix = NULL; + _cleanup_(socket_service_pair_done) SocketServicePair ssp = { + .socket_type = SOCK_STREAM, + }; int r; - _cleanup_close_ int vsock_fd = -EBADF; - bool use_kvm = arg_qemu_kvm > 0; - if (arg_qemu_kvm < 0) { - r = qemu_check_kvm_support(); - if (r < 0) - return log_error_errno(r, "Failed to check for KVM support: %m"); - use_kvm = r; - } + assert(bus); + assert(scope); + assert(swtpm); + assert(ret_state_tempdir); - r = find_ovmf_config(arg_secure_boot, &ovmf_config); + r = unit_name_to_prefix(scope, &scope_prefix); if (r < 0) - return log_error_errno(r, "Failed to find OVMF config: %m"); + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); - /* only warn if the user hasn't disabled secureboot */ - if (!ovmf_config->supports_sb && arg_secure_boot) - log_warning("Couldn't find OVMF firmware blob with Secure Boot support, " - "falling back to OVMF firmware blobs without Secure Boot support."); + ssp.unit_name_prefix = strjoin(scope_prefix, "-tpm"); + if (!ssp.unit_name_prefix) + return log_oom(); - const char *accel = use_kvm ? "kvm" : "tcg"; - if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) - machine = strjoin("type=virt,accel=", accel); - else - machine = strjoin("type=q35,accel=", accel, ",smm=", on_off(ovmf_config->supports_sb)); - if (!machine) + state_dir = path_join(arg_runtime_directory, ssp.unit_name_prefix); + if (!state_dir) return log_oom(); - r = find_qemu_binary(&qemu_binary); - if (r == -EOPNOTSUPP) - return log_error_errno(r, "Native architecture is not supported by qemu."); + if (arg_runtime_directory_created) { + ssp.runtime_directory = path_join("systemd/vmspawn", ssp.unit_name_prefix); + if (!ssp.runtime_directory) + return log_oom(); + } + + ssp.listen_address = path_join(state_dir, "sock"); + if (!ssp.listen_address) + return log_oom(); + + _cleanup_free_ char *swtpm_setup = NULL; + r = find_executable("swtpm_setup", &swtpm_setup); if (r < 0) - return log_error_errno(r, "Failed to find QEMU binary: %m"); + return log_error_errno(r, "Failed to find swtpm_setup binary: %m"); - if (asprintf(&mem, "%.4fM", (double)arg_qemu_mem / (1024.0 * 1024.0)) < 0) + ssp.exec_start_pre = strv_new(swtpm_setup, "--tpm-state", state_dir, "--tpm2", "--pcr-banks", "sha256"); + if (!ssp.exec_start_pre) return log_oom(); - cmdline = strv_new( - qemu_binary, - "-machine", machine, - "-smp", arg_qemu_smp ?: "1", - "-m", mem, - "-object", "rng-random,filename=/dev/urandom,id=rng0", - "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", - "-nic", "user,model=virtio-net-pci" - ); - if (!cmdline) + ssp.exec_start = strv_new(swtpm, "socket", "--tpm2", "--tpmstate"); + if (!ssp.exec_start) return log_oom(); - bool use_vsock = arg_qemu_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; - if (arg_qemu_vsock < 0) { - r = qemu_check_vsock_support(); - if (r < 0) - return log_error_errno(r, "Failed to check for VSock support: %m"); + r = strv_extendf(&ssp.exec_start, "dir=%s", state_dir); + if (r < 0) + return log_oom(); - use_vsock = r; - } + r = strv_extend_many(&ssp.exec_start, "--ctrl", "type=unixio,fd=3"); + if (r < 0) + return log_oom(); - unsigned child_cid = VMADDR_CID_ANY; - _cleanup_close_ int child_vsock_fd = -EBADF; - if (use_vsock) { - if (arg_vsock_cid < UINT_MAX) - child_cid = (unsigned)arg_vsock_cid; + r = start_socket_service_pair(bus, scope, &ssp); + if (r < 0) + return r; - r = vsock_fix_child_cid(&child_cid, arg_machine, &child_vsock_fd); - if (r < 0) - return log_error_errno(r, "Failed to fix CID for the guest vsock socket: %m"); + *ret_state_tempdir = TAKE_PTR(state_dir); + return 0; +} - r = strv_extend(&cmdline, "-device"); - if (r < 0) - return log_oom(); +static int start_systemd_journal_remote(sd_bus *bus, const char *scope, unsigned port, const char *sd_journal_remote, char **ret_listen_address) { + _cleanup_free_ char *scope_prefix = NULL; + _cleanup_(socket_service_pair_done) SocketServicePair ssp = { + .socket_type = SOCK_STREAM, + }; + int r; - log_debug("vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd); - r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, child_vsock_fd); - if (r < 0) - return log_oom(); - } + assert(bus); + assert(scope); + assert(sd_journal_remote); - r = strv_extend_strv(&cmdline, STRV_MAKE("-cpu", "max"), /* filter_duplicates= */ false); + r = unit_name_to_prefix(scope, &scope_prefix); if (r < 0) + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); + + ssp.unit_name_prefix = strjoin(scope_prefix, "-forward-journal"); + if (!ssp.unit_name_prefix) return log_oom(); - if (arg_qemu_gui) { - r = strv_extend_strv(&cmdline, STRV_MAKE("-vga", "virtio"), /* filter_duplicates= */ false); - if (r < 0) - return log_oom(); - } else { - r = strv_extend_strv(&cmdline, STRV_MAKE( - "-nographic", - "-nodefaults", - "-chardev", "stdio,mux=on,id=console,signal=off", - "-serial", "chardev:console", - "-mon", "console" - ), /* filter_duplicates= */ false); - if (r < 0) - return log_oom(); - } + r = asprintf(&ssp.listen_address, "vsock:2:%u", port); + if (r < 0) + return log_oom(); - if (ARCHITECTURE_SUPPORTS_SMBIOS) { - ssize_t n; - FOREACH_ARRAY(cred, arg_credentials, arg_n_credentials) { - _cleanup_free_ char *cred_data_b64 = NULL; + ssp.exec_start = strv_new( + sd_journal_remote, + "--output", arg_forward_journal, + "--split-mode", endswith(arg_forward_journal, ".journal") ? "none" : "host"); + if (!ssp.exec_start) + return log_oom(); - n = base64mem(cred->data, cred->size, &cred_data_b64); - if (n < 0) - return log_oom(); + r = start_socket_service_pair(bus, scope, &ssp); + if (r < 0) + return r; - r = strv_extend(&cmdline, "-smbios"); - if (r < 0) - return log_oom(); + if (ret_listen_address) + *ret_listen_address = TAKE_PTR(ssp.listen_address); - r = strv_extendf(&cmdline, "type=11,value=io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64); - if (r < 0) - return log_oom(); - } - } + return 0; +} - r = strv_extend(&cmdline, "-drive"); +static int discover_root(char **ret) { + int r; + _cleanup_(dissected_image_unrefp) DissectedImage *image = NULL; + _cleanup_free_ char *root = NULL; + + assert(ret); + + r = dissect_image_file_and_warn( + arg_image, + /* verity= */ NULL, + /* mount_options= */ NULL, + /* image_policy= */ NULL, + /* flags= */ 0, + &image); if (r < 0) - return log_oom(); + return r; - r = strv_extendf(&cmdline, "if=pflash,format=raw,readonly=on,file=%s", ovmf_config->path); - if (r < 0) - return log_oom(); + if (image->partitions[PARTITION_ROOT].found) + root = strjoin("root=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_ROOT].uuid)); + else if (image->partitions[PARTITION_USR].found) + root = strjoin("mount.usr=PARTUUID=", SD_ID128_TO_UUID_STRING(image->partitions[PARTITION_USR].uuid)); + else + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Cannot perform a direct kernel boot without a root or usr partition, refusing"); - _cleanup_(unlink_and_freep) char *ovmf_vars_to = NULL; - if (ovmf_config->supports_sb) { - const char *ovmf_vars_from = ovmf_config->vars; - _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; + if (!root) + return log_oom(); - r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); - if (r < 0) - return r; + *ret = TAKE_PTR(root); + return 0; +} - source_fd = open(ovmf_vars_from, O_RDONLY|O_CLOEXEC); - if (source_fd < 0) - return log_error_errno(source_fd, "Failed to open OVMF vars file %s: %m", ovmf_vars_from); +static int find_virtiofsd(char **ret) { + int r; + _cleanup_free_ char *virtiofsd = NULL; - target_fd = open(ovmf_vars_to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); - if (target_fd < 0) - return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", ovmf_vars_to); + assert(ret); - r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); - if (r < 0) - return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); + r = find_executable("virtiofsd", &virtiofsd); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Error while searching for virtiofsd: %m"); + + if (!virtiofsd) { + FOREACH_STRING(file, "/usr/libexec/virtiofsd", "/usr/lib/virtiofsd") { + if (access(file, X_OK) >= 0) { + virtiofsd = strdup(file); + if (!virtiofsd) + return log_oom(); + break; + } + + if (!IN_SET(errno, ENOENT, EACCES)) + return log_error_errno(errno, "Error while searching for virtiofsd: %m"); + } + } + + if (!virtiofsd) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to find virtiofsd binary."); + + *ret = TAKE_PTR(virtiofsd); + return 0; +} + +static int start_virtiofsd(sd_bus *bus, const char *scope, const char *directory, bool uidmap, char **ret_state_tempdir, char **ret_sock_name) { + _cleanup_(rm_rf_physical_and_freep) char *state_dir = NULL; + _cleanup_free_ char *virtiofsd = NULL, *sock_name = NULL, *scope_prefix = NULL; + _cleanup_(socket_service_pair_done) SocketServicePair ssp = { + .socket_type = SOCK_STREAM, + }; + static unsigned virtiofsd_instance = 0; + int r; + + assert(bus); + assert(scope); + assert(directory); + assert(ret_state_tempdir); + assert(ret_sock_name); + + r = find_virtiofsd(&virtiofsd); + if (r < 0) + return r; + + r = unit_name_to_prefix(scope, &scope_prefix); + if (r < 0) + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); + + if (asprintf(&ssp.unit_name_prefix, "%s-virtiofsd-%u", scope_prefix, virtiofsd_instance++) < 0) + return log_oom(); + + state_dir = path_join(arg_runtime_directory, ssp.unit_name_prefix); + if (!state_dir) + return log_oom(); + + if (arg_runtime_directory_created) { + ssp.runtime_directory = strjoin("systemd/vmspawn/", ssp.unit_name_prefix); + if (!ssp.runtime_directory) + return log_oom(); + } + + if (asprintf(&sock_name, "sock-%"PRIx64, random_u64()) < 0) + return log_oom(); + + ssp.listen_address = path_join(state_dir, sock_name); + if (!ssp.listen_address) + return log_oom(); + + /* QEMU doesn't support submounts so don't announce them */ + ssp.exec_start = strv_new(virtiofsd, "--shared-dir", directory, "--xattr", "--fd", "3", "--no-announce-submounts"); + if (!ssp.exec_start) + return log_oom(); + + if (uidmap && arg_uid_shift != UID_INVALID) { + r = strv_extend(&ssp.exec_start, "--uid-map"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&ssp.exec_start, ":0:" UID_FMT ":" UID_FMT ":", arg_uid_shift, arg_uid_range); + if (r < 0) + return log_oom(); + + r = strv_extend(&ssp.exec_start, "--gid-map"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&ssp.exec_start, ":0:" GID_FMT ":" GID_FMT ":", arg_uid_shift, arg_uid_range); + if (r < 0) + return log_oom(); + } + + r = start_socket_service_pair(bus, scope, &ssp); + if (r < 0) + return r; + + *ret_state_tempdir = TAKE_PTR(state_dir); + *ret_sock_name = TAKE_PTR(sock_name); + + return 0; +} + +static int kernel_cmdline_maybe_append_root(void) { + int r; + bool cmdline_contains_root = strv_find_startswith(arg_kernel_cmdline_extra, "root=") + || strv_find_startswith(arg_kernel_cmdline_extra, "mount.usr="); + + if (!cmdline_contains_root) { + _cleanup_free_ char *root = NULL; + + r = discover_root(&root); + if (r < 0) + return r; + + log_debug("Determined root file system %s from dissected image", root); + + r = strv_consume(&arg_kernel_cmdline_extra, TAKE_PTR(root)); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int discover_boot_entry(const char *root, char **ret_linux, char ***ret_initrds) { + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL; + int r; + + assert(root); + assert(ret_linux); + assert(ret_initrds); + + esp_path = path_join(root, "efi"); + if (!esp_path) + return log_oom(); + + xbootldr_path = path_join(root, "boot"); + if (!xbootldr_path) + return log_oom(); + + r = boot_config_load(&config, esp_path, xbootldr_path); + if (r < 0) + return r; + + r = boot_config_select_special_entries(&config, /* skip_efivars= */ true); + if (r < 0) + return log_error_errno(r, "Failed to find special boot config entries: %m"); + + const BootEntry *boot_entry = boot_config_default_entry(&config); + + if (boot_entry && !IN_SET(boot_entry->type, BOOT_ENTRY_UNIFIED, BOOT_ENTRY_CONF)) + boot_entry = NULL; + + /* If we cannot determine a default entry search for UKIs (Type #2 EFI Unified Kernel Images) + * then .conf files (Type #1 Boot Loader Specification Entries). + * https://uapi-group.org/specifications/specs/boot_loader_specification */ + if (!boot_entry) + FOREACH_ARRAY(entry, config.entries, config.n_entries) + if (entry->type == BOOT_ENTRY_UNIFIED) { + boot_entry = entry; + break; + } + + if (!boot_entry) + FOREACH_ARRAY(entry, config.entries, config.n_entries) + if (entry->type == BOOT_ENTRY_CONF) { + boot_entry = entry; + break; + } + + if (!boot_entry) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Failed to discover any boot entries."); + + log_debug("Discovered boot entry %s (%s)", boot_entry->id, boot_entry_type_to_string(boot_entry->type)); + + _cleanup_free_ char *linux_kernel = NULL; + _cleanup_strv_free_ char **initrds = NULL; + if (boot_entry->type == BOOT_ENTRY_UNIFIED) { + linux_kernel = path_join(boot_entry->root, boot_entry->kernel); + if (!linux_kernel) + return log_oom(); + } else if (boot_entry->type == BOOT_ENTRY_CONF) { + linux_kernel = path_join(boot_entry->root, boot_entry->kernel); + if (!linux_kernel) + return log_oom(); + + STRV_FOREACH(initrd, boot_entry->initrd) { + _cleanup_free_ char *initrd_path = path_join(boot_entry->root, *initrd); + if (!initrd_path) + return log_oom(); + + r = strv_consume(&initrds, TAKE_PTR(initrd_path)); + if (r < 0) + return log_oom(); + } + } else + assert_not_reached(); + + *ret_linux = TAKE_PTR(linux_kernel); + *ret_initrds = TAKE_PTR(initrds); + + return 0; +} + +static int merge_initrds(char **ret) { + _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; + _cleanup_close_ int ofd = -EBADF; + int r; + + assert(ret); + + r = tempfn_random_child(NULL, "vmspawn-initrd-", &merged_initrd); + if (r < 0) + return log_error_errno(r, "Failed to create temporary file: %m"); + + ofd = open(merged_initrd, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (ofd < 0) + return log_error_errno(errno, "Failed to create regular file %s: %m", merged_initrd); + + STRV_FOREACH(i, arg_initrds) { + _cleanup_close_ int ifd = -EBADF; + off_t off, to_seek; + + off = lseek(ofd, 0, SEEK_CUR); + if (off < 0) + return log_error_errno(errno, "Failed to get file offset of %s: %m", merged_initrd); + + to_seek = (4 - (off % 4)) % 4; + + /* seek to assure 4 byte alignment for each initrd */ + if (to_seek != 0 && lseek(ofd, to_seek, SEEK_CUR) < 0) + return log_error_errno(errno, "Failed to seek %s: %m", merged_initrd); + + ifd = open(*i, O_RDONLY|O_CLOEXEC); + if (ifd < 0) + return log_error_errno(errno, "Failed to open %s: %m", *i); + + r = copy_bytes(ifd, ofd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", *i, merged_initrd); + } + + *ret = TAKE_PTR(merged_initrd); + return 0; +} + +static void set_window_title(PTYForward *f) { + _cleanup_free_ char *hn = NULL, *dot = NULL; + + assert(f); + + (void) gethostname_strict(&hn); + + if (emoji_enabled()) + dot = strjoin(special_glyph(SPECIAL_GLYPH_GREEN_CIRCLE), " "); + + if (hn) + (void) pty_forward_set_titlef(f, "%sVirtual Machine %s on %s", strempty(dot), arg_machine, hn); + else + (void) pty_forward_set_titlef(f, "%sVirtual Machine %s", strempty(dot), arg_machine); + + if (dot) + (void) pty_forward_set_title_prefix(f, dot); +} + +static int generate_ssh_keypair(const char *key_path, const char *key_type) { + _cleanup_free_ char *ssh_keygen = NULL; + _cleanup_strv_free_ char **cmdline = NULL; + int r; + + assert(key_path); + + r = find_executable("ssh-keygen", &ssh_keygen); + if (r < 0) + return log_error_errno(r, "Failed to find ssh-keygen: %m"); + + cmdline = strv_new(ssh_keygen, "-f", key_path, /* don't encrypt the key */ "-N", ""); + if (!cmdline) + return log_oom(); + + if (key_type) { + r = strv_extend_many(&cmdline, "-t", key_type); + if (r < 0) + return log_oom(); + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); + if (!joined) + return log_oom(); + + log_debug("Executing: %s", joined); + } + + r = safe_fork( + ssh_keygen, + FORK_WAIT|FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO, + NULL); + if (r < 0) + return r; + if (r == 0) { + execv(ssh_keygen, cmdline); + log_error_errno(errno, "Failed to execve %s: %m", ssh_keygen); + _exit(EXIT_FAILURE); + } + + return 0; +} + +static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { + SSHInfo ssh_info; /* Used when talking to pid1 via SSH, but must survive until the function ends. */ + _cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL, *kernel = NULL; + _cleanup_(rm_rf_physical_and_freep) char *ssh_private_key_path = NULL, *ssh_public_key_path = NULL; + _cleanup_close_ int notify_sock_fd = -EBADF; + _cleanup_strv_free_ char **cmdline = NULL; + _cleanup_free_ int *pass_fds = NULL; + size_t n_pass_fds = 0; + const char *accel, *shm; + int r; + + if (arg_privileged) + r = sd_bus_default_system(&bus); + else + r = sd_bus_default_user(&bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to systemd bus: %m"); + + r = start_transient_scope(bus, arg_machine, /* allow_pidfd= */ true, &trans_scope); + if (r < 0) + return r; + + bool use_kvm = arg_kvm > 0; + if (arg_kvm < 0) { + r = qemu_check_kvm_support(); + if (r < 0) + return log_error_errno(r, "Failed to check for KVM support: %m"); + use_kvm = r; + } + + if (arg_firmware) + r = load_ovmf_config(arg_firmware, &ovmf_config); + else + r = find_ovmf_config(arg_secure_boot, &ovmf_config); + if (r < 0) + return log_error_errno(r, "Failed to find OVMF config: %m"); + + /* only warn if the user hasn't disabled secureboot */ + if (!ovmf_config->supports_sb && arg_secure_boot) + log_warning("Couldn't find OVMF firmware blob with Secure Boot support, " + "falling back to OVMF firmware blobs without Secure Boot support."); + + shm = arg_directory || arg_runtime_mounts.n_mounts != 0 ? ",memory-backend=mem" : ""; + if (ARCHITECTURE_SUPPORTS_SMM) + machine = strjoin("type=" QEMU_MACHINE_TYPE ",smm=", on_off(ovmf_config->supports_sb), shm); + else + machine = strjoin("type=" QEMU_MACHINE_TYPE, shm); + if (!machine) + return log_oom(); + + if (arg_linux) { + kernel = strdup(arg_linux); + if (!kernel) + return log_oom(); + } else if (arg_directory) { + /* a kernel is required for directory type images so attempt to locate a UKI under /boot and /efi */ + r = discover_boot_entry(arg_directory, &kernel, &arg_initrds); + if (r < 0) + return log_error_errno(r, "Failed to locate UKI in directory type image, please specify one with --linux=."); + + log_debug("Discovered UKI image at %s", kernel); + } + + r = find_qemu_binary(&qemu_binary); + if (r == -EOPNOTSUPP) + return log_error_errno(r, "Native architecture is not supported by qemu."); + if (r < 0) + return log_error_errno(r, "Failed to find QEMU binary: %m"); + + if (asprintf(&mem, "%" PRIu64 "M", DIV_ROUND_UP(arg_ram, U64_MB)) < 0) + return log_oom(); + + cmdline = strv_new( + qemu_binary, + "-machine", machine, + "-smp", arg_cpus ?: "1", + "-m", mem, + "-object", "rng-random,filename=/dev/urandom,id=rng0", + "-device", "virtio-rng-pci,rng=rng0,id=rng-device0", + "-device", "virtio-balloon,free-page-reporting=on" + ); + if (!cmdline) + return log_oom(); + + if (!sd_id128_is_null(arg_uuid)) + if (strv_extend_many(&cmdline, "-uuid", SD_ID128_TO_UUID_STRING(arg_uuid)) < 0) + return log_oom(); + + /* Derive a vmgenid automatically from the invocation ID, in a deterministic way. */ + sd_id128_t vmgenid; + r = sd_id128_get_invocation_app_specific(SD_ID128_MAKE(bd,84,6d,e3,e4,7d,4b,6c,a6,85,4a,87,0f,3c,a3,a0), &vmgenid); + if (r < 0) { + log_debug_errno(r, "Failed to get invocation ID, making up randomized vmgenid: %m"); + + r = sd_id128_randomize(&vmgenid); + if (r < 0) + return log_error_errno(r, "Failed to make up randomized vmgenid: %m"); + } + + _cleanup_free_ char *vmgenid_device = NULL; + if (asprintf(&vmgenid_device, "vmgenid,guid=" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(vmgenid)) < 0) + return log_oom(); + + if (strv_extend_many(&cmdline, "-device", vmgenid_device) < 0) + return log_oom(); + + /* if we are going to be starting any units with state then create our runtime dir */ + if (arg_tpm != 0 || arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = runtime_directory(&arg_runtime_directory, arg_privileged ? RUNTIME_SCOPE_SYSTEM : RUNTIME_SCOPE_USER, "systemd/vmspawn"); + if (r < 0) + return log_error_errno(r, "Failed to lookup runtime directory: %m"); + if (r) { + /* r > 0 means we need to create our own runtime dir */ + r = mkdir_p(arg_runtime_directory, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create runtime directory: %m"); + arg_runtime_directory_created = true; + } + } + + if (arg_network_stack == NETWORK_STACK_TAP) { + _cleanup_free_ char *tap_name = NULL; + struct ether_addr mac_vm = {}; + + tap_name = strjoin("tp-", arg_machine); + if (!tap_name) + return log_oom(); + + (void) net_shorten_ifname(tap_name, /* check_naming_scheme= */ false); + + if (ether_addr_is_null(&arg_network_provided_mac)){ + r = net_generate_mac(arg_machine, &mac_vm, VM_TAP_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for VM side: %m"); + } else + mac_vm = arg_network_provided_mac; + + r = strv_extend(&cmdline, "-nic"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "tap,ifname=%s,script=no,model=virtio-net-pci,mac=%s", tap_name, ETHER_ADDR_TO_STR(&mac_vm)); + if (r < 0) + return log_oom(); + } else if (arg_network_stack == NETWORK_STACK_USER) + r = strv_extend_many(&cmdline, "-nic", "user,model=virtio-net-pci"); + else + r = strv_extend_many(&cmdline, "-nic", "none"); + if (r < 0) + return log_oom(); + + /* A shared memory backend might increase ram usage so only add one if actually necessary for virtiofsd. */ + if (arg_directory || arg_runtime_mounts.n_mounts != 0) { + r = strv_extend(&cmdline, "-object"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "memory-backend-memfd,id=mem,size=%s,share=on", mem); + if (r < 0) + return log_oom(); + } + + bool use_vsock = arg_vsock > 0 && ARCHITECTURE_SUPPORTS_SMBIOS; + if (arg_vsock < 0) { + r = qemu_check_vsock_support(); + if (r < 0) + return log_error_errno(r, "Failed to check for VSOCK support: %m"); + + use_vsock = r; + } + + if (!use_kvm && kvm_device_fd >= 0) { + log_warning("KVM is disabled but fd for /dev/kvm was passed, closing fd and ignoring"); + kvm_device_fd = safe_close(kvm_device_fd); + } + + if (use_kvm && kvm_device_fd >= 0) { + /* /dev/fdset/1 is magic string to tell qemu where to find the fd for /dev/kvm + * we use this so that we can take a fd to /dev/kvm and then give qemu that fd */ + accel = "kvm,device=/dev/fdset/1"; + + r = strv_extend(&cmdline, "--add-fd"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "fd=%d,set=1,opaque=/dev/kvm", kvm_device_fd); + if (r < 0) + return log_oom(); + + if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) + return log_oom(); + + pass_fds[n_pass_fds++] = kvm_device_fd; + } else if (use_kvm) + accel = "kvm"; + else + accel = "tcg"; + + r = strv_extend_many(&cmdline, "-accel", accel); + if (r < 0) + return log_oom(); + + _cleanup_close_ int child_vsock_fd = -EBADF; + unsigned child_cid = arg_vsock_cid; + if (use_vsock) { + int device_fd = vhost_device_fd; + + if (device_fd < 0) { + child_vsock_fd = open("/dev/vhost-vsock", O_RDWR|O_CLOEXEC); + if (child_vsock_fd < 0) + return log_error_errno(errno, "Failed to open /dev/vhost-vsock as read/write: %m"); + + device_fd = child_vsock_fd; + } + + r = vsock_fix_child_cid(device_fd, &child_cid, arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to fix CID for the guest VSOCK socket: %m"); + + r = strv_extend(&cmdline, "-device"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "vhost-vsock-pci,guest-cid=%u,vhostfd=%d", child_cid, device_fd); + if (r < 0) + return log_oom(); + + if (!GREEDY_REALLOC(pass_fds, n_pass_fds + 1)) + return log_oom(); + + pass_fds[n_pass_fds++] = device_fd; + } + + r = strv_extend_many(&cmdline, "-cpu", +#ifdef __x86_64__ + "max,hv_relaxed,hv-vapic,hv-time" +#else + "max" +#endif + ); + if (r < 0) + return log_oom(); + + _cleanup_close_ int master = -EBADF; + PTYForwardFlags ptyfwd_flags = 0; + switch (arg_console_mode) { + + case CONSOLE_READ_ONLY: + ptyfwd_flags |= PTY_FORWARD_READ_ONLY; + + _fallthrough_; + + case CONSOLE_INTERACTIVE: { + _cleanup_free_ char *pty_path = NULL; + + master = openpt_allocate(O_RDWR|O_NONBLOCK, &pty_path); + if (master < 0) + return log_error_errno(master, "Failed to setup pty: %m"); + + if (strv_extend_many( + &cmdline, + "-nographic", + "-nodefaults", + "-chardev") < 0) + return log_oom(); + + if (strv_extendf(&cmdline, + "serial,id=console,path=%s", pty_path) < 0) + return log_oom(); + + r = strv_extend_many( + &cmdline, + "-serial", "chardev:console"); + break; + } + + case CONSOLE_GUI: + r = strv_extend_many( + &cmdline, + "-vga", + "virtio"); + break; + + case CONSOLE_NATIVE: + r = strv_extend_many( + &cmdline, + "-nographic", + "-nodefaults", + "-chardev", "stdio,mux=on,id=console,signal=off", + "-serial", "chardev:console", + "-mon", "console"); + break; + + default: + assert_not_reached(); + } + if (r < 0) + return log_oom(); + + r = strv_extend(&cmdline, "-drive"); + if (r < 0) + return log_oom(); + + _cleanup_free_ char *escaped_ovmf_config_path = escape_qemu_value(ovmf_config->path); + if (!escaped_ovmf_config_path) + return log_oom(); + + r = strv_extendf(&cmdline, "if=pflash,format=%s,readonly=on,file=%s", ovmf_config_format(ovmf_config), escaped_ovmf_config_path); + if (r < 0) + return log_oom(); + + _cleanup_(unlink_and_freep) char *ovmf_vars_to = NULL; + if (ovmf_config->supports_sb) { + const char *ovmf_vars_from = ovmf_config->vars; + _cleanup_free_ char *escaped_ovmf_vars_to = NULL; + _cleanup_close_ int source_fd = -EBADF, target_fd = -EBADF; + + r = tempfn_random_child(NULL, "vmspawn-", &ovmf_vars_to); + if (r < 0) + return r; + + source_fd = open(ovmf_vars_from, O_RDONLY|O_CLOEXEC); + if (source_fd < 0) + return log_error_errno(source_fd, "Failed to open OVMF vars file %s: %m", ovmf_vars_from); + + target_fd = open(ovmf_vars_to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0600); + if (target_fd < 0) + return log_error_errno(errno, "Failed to create regular file for OVMF vars at %s: %m", ovmf_vars_to); + + r = copy_bytes(source_fd, target_fd, UINT64_MAX, COPY_REFLINK); + if (r < 0) + return log_error_errno(r, "Failed to copy bytes from %s to %s: %m", ovmf_vars_from, ovmf_vars_to); /* These aren't always available so don't raise an error if they fail */ (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0); (void) copy_access(source_fd, target_fd); (void) copy_times(source_fd, target_fd, 0); - r = strv_extend_strv(&cmdline, STRV_MAKE( - "-global", "ICH9-LPC.disable_s3=1", - "-global", "driver=cfi.pflash01,property=secure,value=on", - "-drive" - ), /* filter_duplicates= */ false); + r = strv_extend_many( + &cmdline, + "-global", "ICH9-LPC.disable_s3=1", + "-global", "driver=cfi.pflash01,property=secure,value=on", + "-drive"); if (r < 0) return log_oom(); - r = strv_extendf(&cmdline, "file=%s,if=pflash,format=raw", ovmf_vars_to); + escaped_ovmf_vars_to = escape_qemu_value(ovmf_vars_to); + if (!escaped_ovmf_vars_to) + return log_oom(); + + r = strv_extendf(&cmdline, "file=%s,if=pflash,format=%s", escaped_ovmf_vars_to, ovmf_config_format(ovmf_config)); if (r < 0) return log_oom(); } - r = strv_extend(&cmdline, "-drive"); - if (r < 0) - return log_oom(); + STRV_FOREACH(drive, arg_extra_drives) { + _cleanup_free_ char *escaped_drive = NULL; - r = strv_extendf(&cmdline, "if=none,id=mkosi,file=%s,format=raw", arg_image); - if (r < 0) - return log_oom(); + r = strv_extend(&cmdline, "-drive"); + if (r < 0) + return log_oom(); + + escaped_drive = escape_qemu_value(*drive); + if (!escaped_drive) + return log_oom(); + + r = strv_extendf(&cmdline, "format=raw,cache=unsafe,file=%s", escaped_drive); + if (r < 0) + return log_oom(); + } + + if (kernel) { + r = strv_extend_many(&cmdline, "-kernel", kernel); + if (r < 0) + return log_oom(); + + /* We can't rely on gpt-auto-generator when direct kernel booting so synthesize a root= + * kernel argument instead. */ + if (arg_image) { + r = kernel_cmdline_maybe_append_root(); + if (r < 0) + return r; + } + } + + if (arg_image) { + _cleanup_free_ char *escaped_image = NULL; + + assert(!arg_directory); + + r = strv_extend(&cmdline, "-drive"); + if (r < 0) + return log_oom(); + + escaped_image = escape_qemu_value(arg_image); + if (!escaped_image) + log_oom(); + + r = strv_extendf(&cmdline, "if=none,id=mkosi,file=%s,format=raw,discard=%s", escaped_image, on_off(arg_discard_disk)); + if (r < 0) + return log_oom(); + + r = strv_extend_many(&cmdline, + "-device", "virtio-scsi-pci,id=scsi", + "-device", "scsi-hd,drive=mkosi,bootindex=1"); + if (r < 0) + return log_oom(); + } + + if (arg_directory) { + _cleanup_free_ char *sock_path = NULL, *sock_name = NULL, *escaped_sock_path = NULL; + + r = start_virtiofsd(bus, trans_scope, arg_directory, /* uidmap= */ true, &sock_path, &sock_name); + if (r < 0) + return r; + + escaped_sock_path = escape_qemu_value(sock_path); + if (!escaped_sock_path) + log_oom(); + + r = strv_extend(&cmdline, "-chardev"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "socket,id=%1$s,path=%2$s/%1$s", sock_name, escaped_sock_path); + if (r < 0) + return log_oom(); + + r = strv_extend(&cmdline, "-device"); + if (r < 0) + return log_oom(); - r = strv_extend_strv(&cmdline, STRV_MAKE( - "-device", "virtio-scsi-pci,id=scsi", - "-device", "scsi-hd,drive=mkosi,bootindex=1" - ), /* filter_duplicates= */ false); + r = strv_extendf(&cmdline, "vhost-user-fs-pci,queue-size=1024,chardev=%s,tag=root", sock_name); + if (r < 0) + return log_oom(); + + r = strv_extend(&arg_kernel_cmdline_extra, "root=root rootfstype=virtiofs rw"); + if (r < 0) + return log_oom(); + } + + r = strv_prepend(&arg_kernel_cmdline_extra, "console=" DEFAULT_SERIAL_TTY); if (r < 0) return log_oom(); - if (!strv_isempty(arg_parameters)) { - if (ARCHITECTURE_SUPPORTS_SMBIOS) { - _cleanup_free_ char *kcl = strv_join(arg_parameters, " "); - if (!kcl) + FOREACH_ARRAY(mount, arg_runtime_mounts.mounts, arg_runtime_mounts.n_mounts) { + _cleanup_free_ char *sock_path = NULL, *sock_name = NULL, *clean_target = NULL, *escaped_sock_path = NULL; + r = start_virtiofsd(bus, trans_scope, mount->source, /* uidmap= */ false, &sock_path, &sock_name); + if (r < 0) + return r; + + escaped_sock_path = escape_qemu_value(sock_path); + if (!escaped_sock_path) + log_oom(); + + r = strv_extend(&cmdline, "-chardev"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "socket,id=%1$s,path=%2$s/%1$s", sock_name, escaped_sock_path); + if (r < 0) + return log_oom(); + + r = strv_extend(&cmdline, "-device"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "vhost-user-fs-pci,queue-size=1024,chardev=%1$s,tag=%1$s", sock_name); + if (r < 0) + return log_oom(); + + clean_target = xescape(mount->target, "\":"); + if (!clean_target) + return log_oom(); + + r = strv_extendf(&arg_kernel_cmdline_extra, "systemd.mount-extra=\"%s:%s:virtiofs:%s\"", + sock_name, clean_target, mount->read_only ? "ro" : "rw"); + if (r < 0) + return log_oom(); + } + + if (ARCHITECTURE_SUPPORTS_SMBIOS) { + _cleanup_free_ char *kcl = strv_join(arg_kernel_cmdline_extra, " "), *escaped_kcl = NULL; + if (!kcl) + return log_oom(); + + if (kernel) { + r = strv_extend_many(&cmdline, "-append", kcl); + if (r < 0) + return log_oom(); + } else { + if (ARCHITECTURE_SUPPORTS_SMBIOS) { + escaped_kcl = escape_qemu_value(kcl); + if (!escaped_kcl) + log_oom(); + + r = strv_extend(&cmdline, "-smbios"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", escaped_kcl); + if (r < 0) + return log_oom(); + + r = strv_extend(&cmdline, "-smbios"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "type=11,value=io.systemd.boot.kernel-cmdline-extra=%s", escaped_kcl); + if (r < 0) + return log_oom(); + } else + log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS, ignoring"); + } + } else + log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS"); + + /* disable TPM autodetection if the user's hardware doesn't support it */ + if (!ARCHITECTURE_SUPPORTS_TPM) { + if (arg_tpm < 0) { + arg_tpm = 0; + log_debug("TPM not support on %s, disabling tpm autodetection and continuing", architecture_to_string(native_architecture())); + } else if (arg_tpm > 0) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM not supported on %s, refusing", architecture_to_string(native_architecture())); + } + + _cleanup_free_ char *swtpm = NULL; + if (arg_tpm != 0) { + r = find_executable("swtpm", &swtpm); + if (r < 0) { + /* log if the user asked for swtpm and we cannot find it */ + if (arg_tpm > 0) + return log_error_errno(r, "Failed to find swtpm binary: %m"); + /* also log if we got an error other than ENOENT from find_executable */ + if (r != -ENOENT && arg_tpm < 0) + return log_error_errno(r, "Error detecting swtpm: %m"); + } + } + + _cleanup_free_ char *tpm_state_tempdir = NULL; + if (swtpm) { + r = start_tpm(bus, trans_scope, swtpm, &tpm_state_tempdir); + if (r < 0) { + /* only bail if the user asked for a tpm */ + if (arg_tpm > 0) + return log_error_errno(r, "Failed to start tpm: %m"); + log_debug_errno(r, "Failed to start tpm, ignoring: %m"); + } + } + + if (tpm_state_tempdir) { + _cleanup_free_ char *escaped_state_dir = NULL; + + escaped_state_dir = escape_qemu_value(tpm_state_tempdir); + if (!escaped_state_dir) + log_oom(); + + r = strv_extend(&cmdline, "-chardev"); + if (r < 0) + return log_oom(); + + r = strv_extendf(&cmdline, "socket,id=chrtpm,path=%s/sock", escaped_state_dir); + if (r < 0) + return log_oom(); + + r = strv_extend_many(&cmdline, "-tpmdev", "emulator,id=tpm0,chardev=chrtpm"); + if (r < 0) + return log_oom(); + + if (native_architecture() == ARCHITECTURE_X86_64) + r = strv_extend_many(&cmdline, "-device", "tpm-tis,tpmdev=tpm0"); + else if (IN_SET(native_architecture(), ARCHITECTURE_ARM64, ARCHITECTURE_ARM64_BE)) + r = strv_extend_many(&cmdline, "-device", "tpm-tis-device,tpmdev=tpm0"); + if (r < 0) + return log_oom(); + } + + char *initrd = NULL; + _cleanup_(rm_rf_physical_and_freep) char *merged_initrd = NULL; + size_t n_initrds = strv_length(arg_initrds); + + if (n_initrds == 1) + initrd = arg_initrds[0]; + else if (n_initrds > 1) { + r = merge_initrds(&merged_initrd); + if (r < 0) + return r; + + initrd = merged_initrd; + } + + if (initrd) { + r = strv_extend_many(&cmdline, "-initrd", initrd); + if (r < 0) + return log_oom(); + } + + if (arg_forward_journal) { + _cleanup_free_ char *sd_journal_remote = NULL, *listen_address = NULL, *cred = NULL; + + r = find_executable_full( + "systemd-journal-remote", + /* root = */ NULL, + STRV_MAKE(LIBEXECDIR), + /* use_path_envvar = */ true, /* systemd-journal-remote should be installed in + * LIBEXECDIR, but for supporting fancy setups. */ + &sd_journal_remote, + /* ret_fd = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to find systemd-journal-remote binary: %m"); + + r = start_systemd_journal_remote(bus, trans_scope, child_cid, sd_journal_remote, &listen_address); + if (r < 0) + return r; + + cred = strjoin("journal.forward_to_socket:", listen_address); + if (!cred) + return log_oom(); + + r = machine_credential_set(&arg_credentials, cred); + if (r < 0) + return r; + } + + if (arg_pass_ssh_key) { + _cleanup_free_ char *scope_prefix = NULL, *privkey_path = NULL, *pubkey_path = NULL; + const char *key_type = arg_ssh_key_type ?: "ed25519"; + + r = unit_name_to_prefix(trans_scope, &scope_prefix); + if (r < 0) + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); + + privkey_path = strjoin(arg_runtime_directory, "/", scope_prefix, "-", key_type); + if (!privkey_path) + return log_oom(); + + pubkey_path = strjoin(privkey_path, ".pub"); + if (!pubkey_path) + return log_oom(); + + r = generate_ssh_keypair(privkey_path, key_type); + if (r < 0) + return r; + + ssh_private_key_path = TAKE_PTR(privkey_path); + ssh_public_key_path = TAKE_PTR(pubkey_path); + } + + if (ssh_public_key_path && ssh_private_key_path) { + _cleanup_free_ char *scope_prefix = NULL, *cred_path = NULL; + + cred_path = strjoin("ssh.ephemeral-authorized_keys-all:", ssh_public_key_path); + if (!cred_path) + return log_oom(); + + r = machine_credential_load(&arg_credentials, cred_path); + if (r < 0) + return log_error_errno(r, "Failed to load credential %s: %m", cred_path); + + r = unit_name_to_prefix(trans_scope, &scope_prefix); + if (r < 0) + return log_error_errno(r, "Failed to strip .scope suffix from scope: %m"); + + /* on distros that provide their own sshd@.service file we need to provide a dropin which + * picks up our public key credential */ + r = machine_credential_set( + &arg_credentials, + "systemd.unit-dropin.sshd-vsock@.service:" + "[Service]\n" + "ExecStart=\n" + "ExecStart=sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n" + "ImportCredential=ssh.ephemeral-authorized_keys-all\n"); + if (r < 0) + return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m"); + } + + if (ARCHITECTURE_SUPPORTS_SMBIOS) + FOREACH_ARRAY(cred, arg_credentials.credentials, arg_credentials.n_credentials) { + _cleanup_free_ char *cred_data_b64 = NULL; + ssize_t n; + + n = base64mem(cred->data, cred->size, &cred_data_b64); + if (n < 0) return log_oom(); r = strv_extend(&cmdline, "-smbios"); if (r < 0) return log_oom(); - r = strv_extendf(&cmdline, "type=11,value=io.systemd.stub.kernel-cmdline-extra=%s", kcl); + r = strv_extendf(&cmdline, "type=11,value=io.systemd.credential.binary:%s=%s", cred->id, cred_data_b64); if (r < 0) return log_oom(); - } else - log_warning("Cannot append extra args to kernel cmdline, native architecture doesn't support SMBIOS"); - } + } if (use_vsock) { - vsock_fd = open_vsock(); - if (vsock_fd < 0) - return log_error_errno(vsock_fd, "Failed to open vsock: %m"); + notify_sock_fd = open_vsock(); + if (notify_sock_fd < 0) + return log_error_errno(notify_sock_fd, "Failed to open VSOCK: %m"); - r = cmdline_add_vsock(&cmdline, vsock_fd); + r = cmdline_add_vsock(&cmdline, notify_sock_fd); if (r == -ENOMEM) return log_oom(); if (r < 0) - return log_error_errno(r, "Failed to call getsockname on vsock: %m"); + return log_error_errno(r, "Failed to call getsockname on VSOCK: %m"); + } + + const char *e = secure_getenv("SYSTEMD_VMSPAWN_QEMU_EXTRA"); + if (e) { + _cleanup_strv_free_ char **extra = NULL; + + r = strv_split_full(&extra, e, /* separator= */ NULL, EXTRACT_CUNESCAPE|EXTRACT_UNQUOTE); + if (r < 0) + return log_error_errno(r, "Failed to split $SYSTEMD_VMSPAWN_QEMU_EXTRA environment variable: %m"); + + if (strv_extend_strv(&cmdline, extra, /* filter_duplicates= */ false) < 0) + return log_oom(); + } + + if (DEBUG_LOGGING) { + _cleanup_free_ char *joined = quote_command_line(cmdline, SHELL_ESCAPE_EMPTY); + if (!joined) + return log_oom(); + + log_debug("Executing: %s", joined); + } + + if (arg_register) { + char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)]; + + xsprintf(vm_address, "vsock/%u", child_cid); + r = register_machine( + bus, + arg_machine, + arg_uuid, + trans_scope, + arg_directory, + child_cid, + child_cid != VMADDR_CID_ANY ? vm_address : NULL, + ssh_private_key_path); + if (r < 0) + return r; } + assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGCHLD, SIGWINCH) >= 0); + _cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL; _cleanup_(sd_event_unrefp) sd_event *event = NULL; r = sd_event_new(&event); @@ -654,15 +2070,16 @@ static int run_virtual_machine(void) { (void) sd_event_set_watchdog(event, true); - pid_t child_pid; - r = safe_fork_full( + _cleanup_(pidref_done) PidRef child_pidref = PIDREF_NULL; + + r = pidref_safe_fork_full( qemu_binary, - NULL, - &child_vsock_fd, 1, /* pass the vsock fd to qemu */ - FORK_CLOEXEC_OFF, - &child_pid); + /* stdio_fds= */ NULL, + pass_fds, n_pass_fds, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_CLOEXEC_OFF|FORK_RLIMIT_NOFILE_SAFE, + &child_pidref); if (r < 0) - return log_error_errno(r, "Failed to fork off %s: %m", qemu_binary); + return r; if (r == 0) { /* set TERM and LANG if they are missing */ if (setenv("TERM", "vt220", 0) < 0) @@ -671,35 +2088,72 @@ static int run_virtual_machine(void) { if (setenv("LANG", "C.UTF-8", 0) < 0) return log_oom(); - execve(qemu_binary, cmdline, environ); + execv(qemu_binary, cmdline); log_error_errno(errno, "Failed to execve %s: %m", qemu_binary); _exit(EXIT_FAILURE); } + /* Close the vsock fd we passed to qemu in the parent. We don't need it anymore. */ + child_vsock_fd = safe_close(child_vsock_fd); int exit_status = INT_MAX; if (use_vsock) { - r = setup_notify_parent(event, vsock_fd, &exit_status, ¬ify_event_source); + r = setup_notify_parent(event, notify_sock_fd, &exit_status, ¬ify_event_source); if (r < 0) - return log_error_errno(r, "Failed to setup event loop to handle vsock notify events: %m"); + return log_error_errno(r, "Failed to setup event loop to handle VSOCK notify events: %m"); } - /* shutdown qemu when we are shutdown */ - (void) sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(child_pid)); - (void) sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(child_pid)); + /* If we have the vsock address and the SSH key, ask pid1 inside the guest to shutdown. */ + if (child_cid != VMADDR_CID_ANY && ssh_private_key_path) { + ssh_info = (SSHInfo) { + .cid = child_cid, + .private_key_path = ssh_private_key_path, + .port = 22, + }; + + (void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info); + (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info); + } else { + /* As a fallback in case SSH cannot be used, send a shutdown signal to the VMM instead. */ + (void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref); + (void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref); + } - (void) sd_event_add_signal(event, NULL, SIGRTMIN+18, sigrtmin18_handler, NULL); + (void) sd_event_add_signal(event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL); /* Exit when the child exits */ - (void) sd_event_add_child(event, NULL, child_pid, WEXITED, on_child_exit, NULL); + (void) event_add_child_pidref(event, NULL, &child_pidref, WEXITED, on_child_exit, NULL); + + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + if (master >= 0) { + r = pty_forward_new(event, master, ptyfwd_flags, &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + if (!arg_background && shall_tint_background()) { + _cleanup_free_ char *bg = NULL; + + r = terminal_tint_color(130 /* green */, &bg); + if (r < 0) + log_debug_errno(r, "Failed to determine terminal background color, not tinting."); + else + (void) pty_forward_set_background_color(forward, bg); + } else if (!isempty(arg_background)) + (void) pty_forward_set_background_color(forward, arg_background); + + set_window_title(forward); + } r = sd_event_loop(event); if (r < 0) return log_error_errno(r, "Failed to run event loop: %m"); + if (arg_register) + (void) unregister_machine(bus, arg_machine); + if (use_vsock) { if (exit_status == INT_MAX) { - log_debug("Couldn't retrieve inner EXIT_STATUS from vsock"); + log_debug("Couldn't retrieve inner EXIT_STATUS from VSOCK"); return EXIT_SUCCESS; } if (exit_status != 0) @@ -713,20 +2167,52 @@ static int run_virtual_machine(void) { static int determine_names(void) { int r; - if (!arg_image) - return log_error_errno(SYNTHETIC_ERRNO(-EINVAL), "Missing required argument -i/--image=, quitting"); + if (!arg_directory && !arg_image) { + if (arg_machine) { + _cleanup_(image_unrefp) Image *i = NULL; - if (!arg_machine) { - char *e; + r = image_find(IMAGE_MACHINE, arg_machine, NULL, &i); + if (r == -ENOENT) + return log_error_errno(r, "No image for machine '%s'.", arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); + + if (IN_SET(i->type, IMAGE_RAW, IMAGE_BLOCK)) + r = free_and_strdup(&arg_image, i->path); + else if (IN_SET(i->type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME)) + r = free_and_strdup(&arg_directory, i->path); + else + assert_not_reached(); + if (r < 0) + return log_oom(); + } else { + r = safe_getcwd(&arg_directory); + if (r < 0) + return log_error_errno(r, "Failed to determine current directory: %m"); + } + } - r = path_extract_filename(arg_image, &arg_machine); - if (r < 0) - return log_error_errno(r, "Failed to extract file name from '%s': %m", arg_image); + if (!arg_machine) { + if (arg_directory && path_equal(arg_directory, "/")) { + arg_machine = gethostname_malloc(); + if (!arg_machine) + return log_oom(); + } else if (arg_image) { + char *e; - /* Truncate suffix if there is one */ - e = endswith(arg_machine, ".raw"); - if (e) - *e = 0; + r = path_extract_filename(arg_image, &arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", arg_image); + + /* Truncate suffix if there is one */ + e = endswith(arg_machine, ".raw"); + if (e) + *e = 0; + } else { + r = path_extract_filename(arg_directory, &arg_machine); + if (r < 0) + return log_error_errno(r, "Failed to extract file name from '%s': %m", arg_directory); + } hostname_cleanup(arg_machine); if (!hostname_is_valid(arg_machine, 0)) @@ -736,31 +2222,79 @@ static int determine_names(void) { return 0; } +static int verify_arguments(void) { + if (arg_network_stack == NETWORK_STACK_TAP && !arg_privileged) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--network-tap requires root privileges, refusing."); + + if (!strv_isempty(arg_initrds) && !arg_linux) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); + + if (arg_register && !arg_privileged) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--register= requires root privileges, refusing."); + + return 0; +} + static int run(int argc, char *argv[]) { - int r, ret = EXIT_SUCCESS; + int r, kvm_device_fd = -EBADF, vhost_device_fd = -EBADF; + _cleanup_strv_free_ char **names = NULL; log_setup(); + arg_privileged = getuid() == 0; + + /* don't attempt to register as a machine when running as a user */ + arg_register = arg_privileged; + + r = parse_environment(); + if (r < 0) + return r; + r = parse_argv(argc, argv); if (r <= 0) - goto finish; + return r; r = determine_names(); if (r < 0) - goto finish; + return r; + + r = verify_arguments(); + if (r < 0) + return r; + + if (!arg_quiet && arg_console_mode != CONSOLE_GUI) { + _cleanup_free_ char *u = NULL; + const char *vm_path = arg_image ?: arg_directory; + (void) terminal_urlify_path(vm_path, vm_path, &u); - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, SIGRTMIN+18, -1) >= 0); + log_info("%s %sSpawning VM %s on %s.%s", + special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), arg_machine, u ?: vm_path, ansi_normal()); - r = run_virtual_machine(); - if (r > 0) - ret = r; -finish: - machine_credential_free_all(arg_credentials, arg_n_credentials); + if (arg_console_mode == CONSOLE_INTERACTIVE) + log_info("%s %sPress %sCtrl-]%s three times within 1s to kill VM.%s", + special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal()); + else if (arg_console_mode == CONSOLE_NATIVE) + log_info("%s %sPress %sCtrl-a x%s to kill VM.%s", + special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), ansi_grey(), ansi_highlight(), ansi_grey(), ansi_normal()); + } + r = sd_listen_fds_with_names(true, &names); if (r < 0) - return r; + return log_error_errno(r, "Failed to get passed file descriptors: %m"); + + for (int i = 0; i < r; i++) { + int fd = SD_LISTEN_FDS_START + i; + if (streq(names[i], "kvm")) + kvm_device_fd = fd; + else if (streq(names[i], "vhost-vsock")) + vhost_device_fd = fd; + else { + log_notice("Couldn't recognize passed fd %d (%s), closing fd and ignoring...", fd, names[i]); + safe_close(fd); + } + } - return ret; + return run_virtual_machine(kvm_device_fd, vhost_device_fd); } DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run); diff --git a/src/volatile-root/volatile-root.c b/src/volatile-root/volatile-root.c index 27be7bd..9ab9650 100644 --- a/src/volatile-root/volatile-root.c +++ b/src/volatile-root/volatile-root.c @@ -154,7 +154,7 @@ static int run(int argc, char *argv[]) { if (!IN_SET(m, VOLATILE_YES, VOLATILE_OVERLAY)) return 0; - r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW); + r = path_is_mount_point_full(path, /* root = */ NULL, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path); if (r == 0) diff --git a/src/vpick/meson.build b/src/vpick/meson.build new file mode 100644 index 0000000..a8c14cb --- /dev/null +++ b/src/vpick/meson.build @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + executable_template + { + 'name' : 'systemd-vpick', + 'public' : true, + 'sources' : files('vpick-tool.c'), + }, +] diff --git a/src/vpick/vpick-tool.c b/src/vpick/vpick-tool.c new file mode 100644 index 0000000..c5ae8c5 --- /dev/null +++ b/src/vpick/vpick-tool.c @@ -0,0 +1,348 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "architecture.h" +#include "build.h" +#include "format-table.h" +#include "fs-util.h" +#include "main-func.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "vpick.h" + +static char *arg_filter_basename = NULL; +static char *arg_filter_version = NULL; +static Architecture arg_filter_architecture = _ARCHITECTURE_INVALID; +static char *arg_filter_suffix = NULL; +static uint32_t arg_filter_type_mask = 0; +static enum { + PRINT_PATH, + PRINT_FILENAME, + PRINT_VERSION, + PRINT_TYPE, + PRINT_ARCHITECTURE, + PRINT_TRIES, + PRINT_ALL, + _PRINT_INVALID = -EINVAL, +} arg_print = _PRINT_INVALID; +static PickFlags arg_flags = PICK_ARCHITECTURE|PICK_TRIES; + +STATIC_DESTRUCTOR_REGISTER(arg_filter_basename, freep); +STATIC_DESTRUCTOR_REGISTER(arg_filter_version, freep); +STATIC_DESTRUCTOR_REGISTER(arg_filter_suffix, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-vpick", "1", &link); + if (r < 0) + return log_oom(); + + printf("%1$s [OPTIONS...] PATH...\n" + "\n%5$sPick entry from versioned directory.%6$s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + "\n%3$sLookup Keys:%4$s\n" + " -B --basename=BASENAME\n" + " Look for specified basename\n" + " -V VERSION Look for specified version\n" + " -A ARCH Look for specified architecture\n" + " -S --suffix=SUFFIX Look for specified suffix\n" + " -t --type=TYPE Look for specified inode type\n" + "\n%3$sOutput:%4$s\n" + " -p --print=filename Print selected filename rather than path\n" + " -p --print=version Print selected version rather than path\n" + " -p --print=type Print selected inode type rather than path\n" + " -p --print=arch Print selected architecture rather than path\n" + " -p --print=tries Print selected tries left/tries done rather than path\n" + " -p --print=all Print all of the above\n" + " --resolve=yes Canonicalize the result path\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, + ansi_underline(), ansi_normal(), + ansi_highlight(), ansi_normal()); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_RESOLVE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "basename", required_argument, NULL, 'B' }, + { "suffix", required_argument, NULL, 'S' }, + { "type", required_argument, NULL, 't' }, + { "print", required_argument, NULL, 'p' }, + { "resolve", required_argument, NULL, ARG_RESOLVE }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hB:V:A:S:t:p:", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case 'B': + if (!filename_part_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid basename string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_basename, optarg); + if (r < 0) + return r; + + break; + + case 'V': + if (!version_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid version string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_version, optarg); + if (r < 0) + return r; + + break; + + case 'A': + if (streq(optarg, "native")) + arg_filter_architecture = native_architecture(); + else if (streq(optarg, "secondary")) { +#ifdef ARCHITECTURE_SECONDARY + arg_filter_architecture = ARCHITECTURE_SECONDARY; +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Local architecture has no secondary architecture."); +#endif + } else if (streq(optarg, "uname")) + arg_filter_architecture = uname_architecture(); + else if (streq(optarg, "auto")) + arg_filter_architecture = _ARCHITECTURE_INVALID; + else { + arg_filter_architecture = architecture_from_string(optarg); + if (arg_filter_architecture < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown architecture: %s", optarg); + } + break; + + case 'S': + if (!filename_part_is_valid(optarg)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid suffix string: %s", optarg); + + r = free_and_strdup_warn(&arg_filter_suffix, optarg); + if (r < 0) + return r; + + break; + + case 't': + if (isempty(optarg)) + arg_filter_type_mask = 0; + else { + mode_t m; + + m = inode_type_from_string(optarg); + if (m == MODE_INVALID) + return log_error_errno(m, "Unknown inode type: %s", optarg); + + arg_filter_type_mask |= UINT32_C(1) << IFTODT(m); + } + + break; + + case 'p': + if (streq(optarg, "path")) + arg_print = PRINT_PATH; + else if (streq(optarg, "filename")) + arg_print = PRINT_FILENAME; + else if (streq(optarg, "version")) + arg_print = PRINT_VERSION; + else if (streq(optarg, "type")) + arg_print = PRINT_TYPE; + else if (STR_IN_SET(optarg, "arch", "architecture")) + arg_print = PRINT_ARCHITECTURE; + else if (streq(optarg, "tries")) + arg_print = PRINT_TRIES; + else if (streq(optarg, "all")) + arg_print = PRINT_ALL; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown --print= argument: %s", optarg); + + break; + + case ARG_RESOLVE: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --resolve= value: %m"); + + SET_FLAG(arg_flags, PICK_RESOLVE, r); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached(); + } + } + + if (arg_print < 0) + arg_print = PRINT_PATH; + + return 1; +} + +static int run(int argc, char *argv[]) { + int r; + + log_setup(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r; + + if (optind >= argc) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to resolve must be specified."); + + for (int i = optind; i < argc; i++) { + _cleanup_free_ char *p = NULL; + r = path_make_absolute_cwd(argv[i], &p); + if (r < 0) + return log_error_errno(r, "Failed to make path '%s' absolute: %m", argv[i]); + + _cleanup_(pick_result_done) PickResult result = PICK_RESULT_NULL; + r = path_pick(/* toplevel_path= */ NULL, + /* toplevel_fd= */ AT_FDCWD, + p, + &(PickFilter) { + .basename = arg_filter_basename, + .version = arg_filter_version, + .architecture = arg_filter_architecture, + .suffix = STRV_MAKE(arg_filter_suffix), + .type_mask = arg_filter_type_mask, + }, + arg_flags, + &result); + if (r < 0) + return log_error_errno(r, "Failed to pick version for '%s': %m", p); + if (r == 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "No matching version for '%s' found.", p); + + switch (arg_print) { + + case PRINT_PATH: + fputs(result.path, stdout); + if (result.st.st_mode != MODE_INVALID && S_ISDIR(result.st.st_mode) && !endswith(result.path, "/")) + fputc('/', stdout); + fputc('\n', stdout); + break; + + case PRINT_FILENAME: { + _cleanup_free_ char *fname = NULL; + + r = path_extract_filename(result.path, &fname); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from path '%s': %m", result.path); + + puts(fname); + break; + } + + case PRINT_VERSION: + if (!result.version) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No version information discovered."); + + puts(result.version); + break; + + case PRINT_TYPE: + if (result.st.st_mode == MODE_INVALID) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No inode type information discovered."); + + puts(inode_type_to_string(result.st.st_mode)); + break; + + case PRINT_ARCHITECTURE: + if (result.architecture < 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No architecture information discovered."); + + puts(architecture_to_string(result.architecture)); + break; + + case PRINT_TRIES: + if (result.tries_left == UINT_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No tries left/tries done information discovered."); + + printf("+%u-%u", result.tries_left, result.tries_done); + break; + + case PRINT_ALL: { + _cleanup_(table_unrefp) Table *t = NULL; + + t = table_new_vertical(); + if (!t) + return log_oom(); + + table_set_ersatz_string(t, TABLE_ERSATZ_NA); + + r = table_add_many( + t, + TABLE_FIELD, "Path", + TABLE_PATH, result.path, + TABLE_FIELD, "Version", + TABLE_STRING, result.version, + TABLE_FIELD, "Type", + TABLE_STRING, result.st.st_mode == MODE_INVALID ? NULL : inode_type_to_string(result.st.st_mode), + TABLE_FIELD, "Architecture", + TABLE_STRING, result.architecture < 0 ? NULL : architecture_to_string(result.architecture)); + if (r < 0) + return table_log_add_error(r); + + if (result.tries_left != UINT_MAX) { + r = table_add_many( + t, + TABLE_FIELD, "Tries left", + TABLE_UINT, result.tries_left, + TABLE_FIELD, "Tries done", + TABLE_UINT, result.tries_done); + if (r < 0) + return table_log_add_error(r); + } + + r = table_print(t, stdout); + if (r < 0) + return table_log_print_error(r); + + break; + } + + default: + assert_not_reached(); + } + } + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/src/xdg-autostart-generator/xdg-autostart-generator.c b/src/xdg-autostart-generator/xdg-autostart-generator.c index 616c017..71e1a66 100644 --- a/src/xdg-autostart-generator/xdg-autostart-generator.c +++ b/src/xdg-autostart-generator/xdg-autostart-generator.c @@ -37,7 +37,7 @@ static int enumerate_xdg_autostart(Hashmap *all_services) { r = xdg_user_dirs(&config_dirs, &data_dirs); if (r < 0) return r; - r = strv_extend_strv_concat(&autostart_dirs, config_dirs, "/autostart"); + r = strv_extend_strv_concat(&autostart_dirs, (const char* const*) config_dirs, "/autostart"); if (r < 0) return r; -- cgit v1.2.3