]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #14761 from keszybz/link-network-no-match
authorYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 4 Feb 2020 11:26:37 +0000 (20:26 +0900)
committerGitHub <noreply@github.com>
Tue, 4 Feb 2020 11:26:37 +0000 (20:26 +0900)
Refuse .network and .link files with no matches

355 files changed:
.lgtm.yml
.mkosi/mkosi.fedora
NEWS
TODO
docs/BOOT_LOADER_SPECIFICATION.md
docs/DISCOVERABLE_PARTITIONS.md
docs/HOME_DIRECTORY.md [new file with mode: 0644]
docs/PORTABILITY_AND_STABILITY.md
docs/ROOT_STORAGE_DAEMONS.md
docs/TRANSIENT-SETTINGS.md
docs/UIDS-GIDS.md
docs/USER_RECORD.md
docs/_includes/footer.html
factory/etc/pam.d/system-auth
fuzzbuzz.yaml
hwdb.d/60-sensor.hwdb
man/halt.xml
man/homectl.xml [new file with mode: 0644]
man/journalctl.xml
man/journald.conf.xml
man/pam_systemd_home.xml [new file with mode: 0644]
man/repart.d.xml [new file with mode: 0644]
man/rules/meson.build
man/sd-bus.xml
man/sd_bus_message_dump.xml [new file with mode: 0644]
man/sd_bus_message_read.xml
man/sd_bus_message_read_array.xml
man/sd_bus_message_read_basic.xml
man/sd_journal_open.xml
man/systemd-fstab-generator.xml
man/systemd-homed.service.xml [new file with mode: 0644]
man/systemd-id128.xml
man/systemd-journald.service.xml
man/systemd-makefs@.service.xml
man/systemd-repart.xml [new file with mode: 0644]
man/systemd-system.conf.xml
man/systemd.exec.xml
man/systemd.journal-fields.xml
man/systemd.link.xml
man/systemd.mount.xml
man/systemd.netdev.xml
man/systemd.network.xml
man/systemd.path.xml
man/systemd.resource-control.xml
man/systemd.timer.xml
man/systemd.unit.xml
man/sysusers.d.xml
man/tmpfiles.d.xml
man/udev.xml
meson.build
meson_options.txt
po/POTFILES.in
po/cs.po
po/fr.po
po/it.po
po/ja.po
po/pl.po
presets/90-systemd.preset
presets/user/90-systemd.preset
semaphoreci/semaphore-runner.sh
src/analyze/analyze-security.c
src/basic/efivars.c
src/basic/efivars.h
src/basic/escape.c
src/basic/escape.h
src/basic/extract-word.c
src/basic/format-util.h
src/basic/fs-util.c
src/basic/locale-util.c
src/basic/locale-util.h
src/basic/parse-util.c
src/basic/parse-util.h
src/basic/proc-cmdline.c
src/basic/process-util.h
src/basic/string-util.c
src/basic/strv.c
src/basic/strv.h
src/basic/syslog-util.c
src/basic/syslog-util.h
src/basic/user-util.c
src/basic/user-util.h
src/basic/virt.c
src/boot/efi/boot.c
src/boot/efi/stub.c
src/boot/efi/util.c
src/boot/efi/util.h
src/busctl/busctl.c
src/core/core-varlink.c
src/core/dbus-execute.c
src/core/dbus-manager.c
src/core/dbus-unit.c
src/core/dbus.c
src/core/dynamic-user.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/manager.c
src/core/mount.c
src/core/namespace.c
src/core/namespace.h
src/core/service.c
src/core/systemd.pc.in
src/core/unit.c
src/core/unit.h
src/dissect/dissect.c
src/fstab-generator/fstab-generator.c
src/fuzz/fuzz-bus-message.c
src/hibernate-resume/hibernate-resume-generator.c
src/home/home-util.c [new file with mode: 0644]
src/home/home-util.h [new file with mode: 0644]
src/home/homectl.c [new file with mode: 0644]
src/home/homed-bus.c [new file with mode: 0644]
src/home/homed-bus.h [new file with mode: 0644]
src/home/homed-home-bus.c [new file with mode: 0644]
src/home/homed-home-bus.h [new file with mode: 0644]
src/home/homed-home.c [new file with mode: 0644]
src/home/homed-home.h [new file with mode: 0644]
src/home/homed-manager-bus.c [new file with mode: 0644]
src/home/homed-manager-bus.h [new file with mode: 0644]
src/home/homed-manager.c [new file with mode: 0644]
src/home/homed-manager.h [new file with mode: 0644]
src/home/homed-operation.c [new file with mode: 0644]
src/home/homed-operation.h [new file with mode: 0644]
src/home/homed-varlink.c [new file with mode: 0644]
src/home/homed-varlink.h [new file with mode: 0644]
src/home/homed.c [new file with mode: 0644]
src/home/homework-cifs.c [new file with mode: 0644]
src/home/homework-cifs.h [new file with mode: 0644]
src/home/homework-directory.c [new file with mode: 0644]
src/home/homework-directory.h [new file with mode: 0644]
src/home/homework-fscrypt.c [new file with mode: 0644]
src/home/homework-fscrypt.h [new file with mode: 0644]
src/home/homework-luks.c [new file with mode: 0644]
src/home/homework-luks.h [new file with mode: 0644]
src/home/homework-mount.c [new file with mode: 0644]
src/home/homework-mount.h [new file with mode: 0644]
src/home/homework-pkcs11.c [new file with mode: 0644]
src/home/homework-pkcs11.h [new file with mode: 0644]
src/home/homework-quota.c [new file with mode: 0644]
src/home/homework-quota.h [new file with mode: 0644]
src/home/homework.c [new file with mode: 0644]
src/home/homework.h [new file with mode: 0644]
src/home/meson.build [new file with mode: 0644]
src/home/org.freedesktop.home1.conf [new file with mode: 0644]
src/home/org.freedesktop.home1.policy [new file with mode: 0644]
src/home/org.freedesktop.home1.service [new file with mode: 0644]
src/home/pam_systemd_home.c [new file with mode: 0644]
src/home/pam_systemd_home.sym [new file with mode: 0644]
src/home/pwquality-util.c [new file with mode: 0644]
src/home/pwquality-util.h [new file with mode: 0644]
src/home/user-record-sign.c [new file with mode: 0644]
src/home/user-record-sign.h [new file with mode: 0644]
src/home/user-record-util.c [new file with mode: 0644]
src/home/user-record-util.h [new file with mode: 0644]
src/hostname/hostnamed.c
src/id128/id128.c
src/import/curl-util.c
src/import/import-raw.c
src/import/importd.c
src/import/pull-raw.c
src/journal-remote/journal-upload.c
src/journal/journal-internal.h
src/journal/journalctl.c
src/journal/journald-context.c
src/journal/journald-kmsg.c
src/journal/journald-native.c
src/journal/journald-native.h
src/journal/journald-rate-limit.c
src/journal/journald-server.c
src/journal/journald-server.h
src/journal/journald-stream.c
src/journal/journald-stream.h
src/journal/journald-syslog.c
src/journal/journald-syslog.h
src/journal/journald.c
src/journal/sd-journal.c
src/libsystemd-network/sd-dhcp6-client.c
src/libsystemd/libsystemd.sym
src/libsystemd/sd-bus/bus-common-errors.c
src/libsystemd/sd-bus/bus-common-errors.h
src/libsystemd/sd-bus/bus-dump.c
src/libsystemd/sd-bus/bus-dump.h
src/libsystemd/sd-bus/bus-introspect.c
src/libsystemd/sd-bus/test-bus-chat.c
src/libsystemd/sd-bus/test-bus-gvariant.c
src/libsystemd/sd-bus/test-bus-marshal.c
src/libsystemd/sd-bus/test-bus-objects.c
src/libsystemd/sd-bus/test-bus-server.c
src/libsystemd/sd-hwdb/hwdb-util.c
src/libsystemd/sd-id128/id128-util.c
src/libsystemd/sd-id128/id128-util.h
src/libsystemd/sd-id128/sd-id128.c
src/locale/keymap-util.c
src/locale/localed.c
src/login/loginctl.c
src/login/logind-dbus.c
src/login/logind-seat-dbus.c
src/login/logind-session-dbus.c
src/login/logind-user-dbus.c
src/login/logind-user.c
src/login/logind.c
src/login/org.freedesktop.login1.policy
src/login/pam_systemd.c
src/machine/image-dbus.c
src/machine/machine-dbus.c
src/machine/machinectl.c
src/machine/machined-dbus.c
src/machine/machined.c
src/network/netdev/tunnel.c
src/network/networkctl.c
src/network/networkd-can.c
src/network/networkd-dhcp-common.c
src/network/networkd-link-bus.c
src/network/networkd-link.c
src/network/networkd-manager-bus.c
src/network/networkd-manager.c
src/network/networkd-network-gperf.gperf
src/network/networkd-network.h
src/network/networkd-routing-policy-rule.c
src/network/networkd-routing-policy-rule.h
src/nspawn/nspawn-mount.c
src/nspawn/nspawn.c
src/partition/makefs.c
src/partition/meson.build [new file with mode: 0644]
src/partition/repart.c [new file with mode: 0644]
src/portable/portabled-bus.c
src/portable/portabled-image-bus.c
src/portable/portabled.c
src/resolve/resolved-bus.c
src/resolve/resolved-dns-trust-anchor.c
src/resolve/resolved-dnssd-bus.c
src/resolve/resolved-link-bus.c
src/resolve/resolved-manager.c
src/shared/bootspec.c
src/shared/bootspec.h
src/shared/bus-polkit.c [new file with mode: 0644]
src/shared/bus-polkit.h [new file with mode: 0644]
src/shared/bus-unit-util.c
src/shared/bus-util.c
src/shared/bus-util.h
src/shared/conf-parser.c
src/shared/conf-parser.h
src/shared/dissect-image.c
src/shared/dissect-image.h
src/shared/efi-loader.c
src/shared/efi-loader.h
src/shared/ethtool-util.c
src/shared/ethtool-util.h
src/shared/format-table.c
src/shared/format-table.h
src/shared/generator.c
src/shared/gpt.c [new file with mode: 0644]
src/shared/gpt.h
src/shared/id128-print.c
src/shared/id128-print.h
src/shared/import-util.c
src/shared/import-util.h
src/shared/json.c
src/shared/logs-show.c
src/shared/logs-show.h
src/shared/meson.build
src/shared/user-record-nss.c
src/shared/userdb.c
src/shared/varlink.c
src/shared/varlink.h
src/sleep/sleep.c
src/systemctl/systemctl.c
src/systemd/_sd-common.h
src/systemd/sd-bus.h
src/systemd/sd-journal.h
src/sysusers/sysusers.c
src/test/generate-sym-test.py
src/test/test-conf-parser.c
src/test/test-escape.c
src/test/test-execute.c
src/test/test-fs-util.c
src/test/test-namespace.c
src/test/test-ns.c
src/test/test-process-util.c
src/test/test-user-util.c
src/timedate/timedated.c
src/timesync/timesyncd-manager.c
src/udev/net/link-config-gperf.gperf
src/udev/net/link-config.c
src/udev/net/link-config.h
src/udev/udevd.c
src/userdb/userdbd-manager.c
test/TEST-21-SYSUSERS/test-13.expected-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-13.expected-passwd [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-13.input [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.expected-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.expected-passwd [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.initial-group [new file with mode: 0644]
test/TEST-21-SYSUSERS/test-14.input [new file with mode: 0644]
test/TEST-21-SYSUSERS/test.sh
test/TEST-21-SYSUSERS/unhappy-3.expected-err [new file with mode: 0644]
test/TEST-21-SYSUSERS/unhappy-3.input [new file with mode: 0644]
test/TEST-44-LOG-NAMESPACE/Makefile [new symlink]
test/TEST-44-LOG-NAMESPACE/test.sh [new file with mode: 0755]
test/TEST-44-LOG-NAMESPACE/testsuite.sh [new file with mode: 0755]
test/TEST-45-REPART/Makefile [new symlink]
test/TEST-45-REPART/test.sh [new file with mode: 0755]
test/TEST-45-REPART/testsuite.sh [new file with mode: 0755]
test/TEST-46-HOMED/Makefile [new symlink]
test/TEST-46-HOMED/test.sh [new file with mode: 0755]
test/TEST-46-HOMED/testsuite.sh [new file with mode: 0755]
test/fuzz/fuzz-link-parser/directives.link
test/fuzz/fuzz-network-parser/directives.network
test/fuzz/fuzz-unit-file/directives.service
test/meson.build
test/test-execute/exec-standardinput-file-cat.service [new file with mode: 0644]
test/test-functions
test/test-network/conf/25-fibrule-uidrange.network [new file with mode: 0644]
test/test-network/systemd-networkd-tests.py
tmpfiles.d/systemd.conf.m4
tools/oss-fuzz.sh
travis-ci/managers/debian.sh
travis-ci/managers/fedora.sh
travis-ci/managers/fuzzbuzz.sh
travis-ci/managers/fuzzit.sh
units/initrd-cleanup.service [moved from units/initrd-cleanup.service.in with 88% similarity]
units/initrd-parse-etc.service [moved from units/initrd-parse-etc.service.in with 77% similarity]
units/initrd-switch-root.service [moved from units/initrd-switch-root.service.in with 89% similarity]
units/initrd-udevadm-cleanup-db.service [moved from units/initrd-udevadm-cleanup-db.service.in with 93% similarity]
units/meson.build
units/systemd-ask-password-console.service [moved from units/systemd-ask-password-console.service.in with 90% similarity]
units/systemd-ask-password-wall.service [moved from units/systemd-ask-password-wall.service.in with 68% similarity]
units/systemd-boot-system-token.service [moved from units/systemd-boot-system-token.service.in with 96% similarity]
units/systemd-firstboot.service [moved from units/systemd-firstboot.service.in with 87% similarity]
units/systemd-halt.service [moved from units/systemd-halt.service.in with 93% similarity]
units/systemd-homed.service.in [new file with mode: 0644]
units/systemd-hwdb-update.service.in
units/systemd-journal-catalog-update.service [moved from units/systemd-journal-catalog-update.service.in with 93% similarity]
units/systemd-journal-flush.service [moved from units/systemd-journal-flush.service.in with 87% similarity]
units/systemd-journald-varlink@.socket [new file with mode: 0644]
units/systemd-journald.service.in
units/systemd-journald@.service.in [new file with mode: 0644]
units/systemd-journald@.socket [new file with mode: 0644]
units/systemd-kexec.service [moved from units/systemd-kexec.service.in with 93% similarity]
units/systemd-machine-id-commit.service [moved from units/systemd-machine-id-commit.service.in with 92% similarity]
units/systemd-networkd.service.in
units/systemd-nspawn@.service.in
units/systemd-repart.service.in [new file with mode: 0644]
units/systemd-sysusers.service [moved from units/systemd-sysusers.service.in with 94% similarity]
units/systemd-tmpfiles-clean.service [moved from units/systemd-tmpfiles-clean.service.in with 92% similarity]
units/systemd-tmpfiles-setup-dev.service [moved from units/systemd-tmpfiles-setup-dev.service.in with 90% similarity]
units/systemd-tmpfiles-setup.service [moved from units/systemd-tmpfiles-setup.service.in with 89% similarity]
units/systemd-udev-settle.service [moved from units/systemd-udev-settle.service.in with 95% similarity]
units/systemd-udev-trigger.service [moved from units/systemd-udev-trigger.service.in with 82% similarity]
units/systemd-udevd.service.in
units/user/meson.build
units/user/systemd-tmpfiles-clean.service [moved from units/user/systemd-tmpfiles-clean.service.in with 91% similarity]
units/user/systemd-tmpfiles-setup.service [moved from units/user/systemd-tmpfiles-setup.service.in with 90% similarity]

index 5948d8c2bc0d7fc7f941cd62c843f4bd6b7ed887..1c7be044cb61ce789fb414aeab1e9e8bddf1ef0a 100644 (file)
--- a/.lgtm.yml
+++ b/.lgtm.yml
@@ -5,6 +5,10 @@ extraction:
         - python3-pip
         - python3-setuptools
         - python3-wheel
+        - libpwquality-dev
+        - libfdisk-dev
+        - libp11-kit-dev
+        - libssl-dev
     after_prepare:
       - pip3 install meson
       - export PATH="$HOME/.local/bin/:$PATH"
index 911908cb77161c55dba50f969aa19126eb70b26c..3717875366245f6ee91312c4ed36d52195c6344d 100644 (file)
@@ -8,7 +8,7 @@ Distribution=fedora
 Release=31
 
 [Output]
-Format=raw_btrfs
+Format=gpt_ext4
 Bootable=yes
 KernelCommandLine=printk.devkmsg=on
 
@@ -38,19 +38,22 @@ BuildPackages=
         libblkid-devel
         libcap-devel
         libcurl-devel
+        libfdisk-devel
         libgcrypt-devel
         libidn2-devel
         libmicrohttpd-devel
         libmount-devel
+        libpwquality-devel
         libseccomp-devel
         libselinux-devel
-        libtool
         libxkbcommon-devel
         libxslt
         lz4
         lz4-devel
         m4
         meson
+        openssl-devel
+        p11-kit-devel
         pam-devel
         pcre2-devel
         pkgconfig
@@ -58,10 +61,18 @@ BuildPackages=
         python3-lxml
         qrencode-devel
         tree
+        valgrind-devel
         xz-devel
 
 Packages=
+        coreutils
+        cryptsetup-libs
+        kmod-libs
+        e2fsprogs
         libidn2
+        libseccomp
+        procps-ng
+        util-linux
 
 BuildDirectory=mkosi.builddir
 Cache=mkosi.cache
diff --git a/NEWS b/NEWS
index 4fe5799c0cde7aa25fec8ca5d4db174352630a8d..74dc7e368e53ffcf631f0766cd79f361f52b81f4 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,272 @@
 systemd System and Service Manager
 
+CHANGES WITH 245 in spe:
+
+        * A new tool "systemd-repart" has been added, that operates as an
+          idempotent, robust, incremental, elastic and declarative
+          repartitioner. It takes inspiration from
+          systemd-tmpfiles/systemd-sysusers but applies the algorithmic
+          concepts to GPT partition tables. Specifically, a set of partitions
+          that must or may exist can be configured via drop-in files, and
+          during every boot the partition table on disk is compared with these
+          files, creating missing partitions or growing existing ones based on
+          configurable relative and absolute size constraints. The tool is
+          strictly incremental, i.e. does not delete, shrink or move
+          partitions, but only adds and grows them. The primary use-case is OS
+          images that shall ship in minimized form, with only a minimal boot
+          and root partition, that on first boot is grown to the size of the
+          underlying block device or augmented with additional partitions. For
+          example, the root partition could be extended to cover the whole
+          disk, or a swap or /home partitions could be added implicitly on
+          first boot. It also has uses on systems that use an A/B update scheme
+          to allow shipping minimal images with just the A set of partition,
+          and with the B set added on first boot. The tool is primarily
+          intended to be run in the initrd, shortly before transitioning into
+          the host OS, but also can be run after the transition took place. It
+          automatically discovers the disk backing the root file system, and
+          should hence not require any additional configuration besides the
+          partition definition drop-ins.
+
+        * A new component "userdb" has been added, along with a small daemon
+          "systemd-userdb.service" and a client tool "userdbctl". The framework
+          allows defining rich user and group records in a JSON format,
+          extending on the classic "struct passwd" and "struct group"
+          structures. Various components in systemd have been updated to
+          process records in this format, including systemd-logind and
+          pam-systemd. The user records are intended to be extensible, and
+          allow setting various resource management, security and runtime
+          parameters that shall be applied to processes and sessions of the
+          user as they log in. This facility is intended to allow associating
+          such metadata directly with user/group records so that they can be
+          produced, extended and consumed in unified form. We hope that
+          eventually frameworks such as sssd will generate records this way, so
+          that for the first time resource management and various other
+          per-user settings can be configured in LDAP directories and then
+          provided to systemd (specifically to systemd-logind and pam-system)
+          to enforce on log-in. For further details see:
+
+          https://systemd.io/USER_RECORD
+          https://systemd.io/GROUP_RECORD
+          https://systemd.io/USER_GROUP_API
+
+        * A small new service systemd-homed.service has been added, that may be
+          used to securely manage home directories, with built-in encryption
+          and unifying the user's own home directory data together with
+          complete user record data in a single place, thus making home
+          directories naturally migratable. Its primary back-end is based on
+          LUKS volumes, but it also supports fscrypt, plain directories and
+          more. It solves a couple of problems we saw with traditional ways to
+          manage home directories, in particular when it comes to
+          encryption. For further discussion of this, see the video of
+          Lennart's talk at AllSystemsGo! 2019:
+
+          https://media.ccc.de/v/ASG2019-164-reinventing-home-directories
+
+          For further details about the format and expectations on home
+          directories this new daemon makes, see:
+
+          https://systemd.io/HOME_DIRECTORY
+
+        * systemd-journald is now multi-instantiable. In addition to the main
+          instance systemd-journald.service there's now a template unit
+          systemd-journald@.service that can be instantiated multiple times,
+          each time defining a new named log 'namespace' (whose name is
+          specified via the instance part of the instance unit name). A new
+          unit file setting LogNamespace= has been added, taking such a
+          namespace name, that allows assigning services to such log
+          namespaces. As each log namespace is serviced by its own, independent
+          journal daemon this functionality may be use to improve performance
+          and increase isolation of applications, at the price of losing global
+          message ordering. Each daemon may have a separate set of
+          configuration files, with possibly different disk space settings and
+          such. journalctl has been updated to take a new option --namespace=
+          which allows viewing logs from a specific log namespace. The
+          sd-journal.h API gained sd_journal_open_namespace() for opening the
+          log stream of a specific log namespace. systemd-journald also gained
+          the ability to exit on idle, which is useful in the context of log
+          namespaces, as this means log daemons for log namespaces can be
+          activated automatically on demand and stop automatically when no
+          longer used, minimizing resource usage.
+
+        * When systemd-tmpfiles copies a file tree using the 'C' line type it
+          will now implicitly label every copied file matching the SELinux
+          database.
+
+        * When systemd/PID 1 detects it is used in the initrd it will now boot
+          into initrd.target rather than default.target by default. This should
+          make it simpler to build initrds with systemd as for many cases the
+          only difference between a host OS image and an initrd image now is
+          the /etc/initrd-release file that identifies the initrd as one.
+
+        * A new kernel command line option systemd.cpu_affinity= is now
+          understood. It's equivalent to the CPUAffinity= option in
+          /etc/systemd/system.conf and allows setting the CPU mask for PID 1
+          itself and the default for all forked off processes.
+
+        * When systemd/PID 1 is reloaded (with systemctl daemon-reload or an
+          equivalent tool) the SELinux database is now reloaded, ensuring that
+          sockets and other file system objects are generated taking the new
+          database into account.
+
+        * The sd-event.h API now has native support for the new Linux "pidfd"
+          concept. This permits watching processes using file descriptors
+          instead of PID numbers, which fixes a number of races and makes
+          process supervision more robust and more efficient. All of systemd's
+          components will now use pidfds if the kernel supports it for process
+          watching, with the exception of PID 1 itself, unfortunately. We hope
+          to move PID 1 to exclusively using pidfds too eventually, but this
+          requires some more kernel work first. (Background: PID 1 watches
+          processes using waitid() with the P_ALL flag, and that does not play
+          together nicely with pidfds yet.)
+
+        * Closely related to this, the sd-event.h API gained two new calls
+          sd_event_source_send_child_signal() (for sending a signal to a
+          watched process) and sd_event_source_get_child_process_own() (for
+          marking a process so that it is killed implicitly whenever the event
+          source watching it is freed).
+
+        * systemd-networkd gained support for configuring Token Buffer Filter
+          (TBF) parameters in its qdisc configuration support. Similar, support
+          for Stochastic Fairness Queuing (SFQ), Controlled-Delay Active
+          Queue Management (CoDel), Fair Queue (FQ) has been added.
+
+        * systemd-networkd gained support for Intermediate Functional Block
+          (IFB) network devices.
+
+        * systemd-networkd gained support for configuring multi-path IP routes,
+          using the new MultiPathRoute= setting in the [Route] section.
+
+        * systemd-networkd's DHCPv4 support has been updated to support a new
+          SendDecline= option. If enabled duplicate address detection is done
+          after a DHCP offer is received from a server. If a conflict is
+          detected the address is declined. The DHCPv4 support also gained
+          support for a new RouteMTUBytes= setting that allows to configure the
+          MTU size to be used for routes generated from DHCPv4 leases.
+
+        * The PrefixRoute= setting in systemd-networkd's [Address] section of
+          .network files has been deprecated, and replaced by AddPrefixRoute=,
+          with it's sense inverted.
+
+        * The Gateway= setting of [Route] sections of .network files gained
+          support for a special new value "dhcp". If set the configured static
+          route uses the gateway host configured via DHCP.
+
+        * A new User= setting has been implemented for the [RoutingPolicyRule]
+          section of .network files for configuring source routing based on UID
+          ranges.
+
+        * sd-bus gained a new API call sd_bus_message_sensitive() for marking a
+          D-Bus message object as "sensitive". Objects that are marked that way
+          are erased from memory when they are freed. This concept is intended
+          to be used for messages that contain security sensitive data that
+          should be erased after use. A new flag SD_BUS_VTABLE_SENSITIVE has
+          been introduced as well that allows marking method calls in sd-bus
+          vtables like this, so that this new message flag is implicitly set
+          for incoming and outgoing messages of specific methods.
+
+        * sd-bus gained a new API call sd_bus_message_dump() for dumping the
+          contents of a message (or parts thereof) onto standard output, for
+          debugging purposes.
+
+        * systemd-sysusers gained support for creating users with primary
+          groups named differently than the user itself.
+
+        * systemd-resolved's DNS-over-TLS support gained SNI validation.
+
+        * systemd-growfs (i.e. the x-systemd.growfs mount option in /etc/fstab)
+          gained support for growing XFS partitions. Previously it supported
+          only ext4 and btrfs partitions.
+
+        * The support for /etc/crypttab gained a new x-initrd.attach option. If
+          set the specified encrypted volume is unlocked in the initrd
+          already. This concept corresponds to the x-initrd.mount option in
+          /etc/fstab.
+
+        * systemd-cryptsetup gained native support for unlocking encrypted
+          volumes utilizing PKCS#11 smartcards, i.e. for example to bind
+          encryption of volumes to YubiKeys.This is exposed in the new
+          pkcs11-uri= option in /etc/crypttab.
+
+        * The /etc/fstab support in systemd now supports two new mount options
+          x-systemd.{required,wanted}-by=, for explicitly configuring the units
+          that the specified mount shall be pulled in by, in place of
+          the usual local-fs.target/remote-fs.target.
+
+        * The https://systemd.io/ web site has been relaunched, directly
+          populated with most of the documentation included in the systemd
+          repository. In particular, systemd acquired a new logo, thanks to
+          Tobias Bernard.
+
+        * systemd-udevd gained support for managing "alternative" network
+          interface names, as supported by new Linux kernels. For the first
+          time this permits assigning multiple (and longer!) names to a network
+          interface. systemd-udevd will now by default assign the names
+          generated via all supported naming schemes to each interface in
+          parallel. This may be further tweaked with .link drop-in files, and
+          the AlternativeName= and AlternativeNamesPolicy= settings. All other
+          components of systemd have been updated to support the new
+          alternative names too, wherever that is appropriate. For example,
+          systemd-nspawn will now generate alternative interface names for the
+          host-facing side of container veth links based on the full container
+          name without truncation.
+
+        * systemd-nspawn interface naming logic has been updated in another way
+          too: if the main interface name (i.e. as opposed to new-style
+          "alternative" names) is the truncated result of container name a
+          simple hashing scheme is used that ensures that multiple containers
+          whose name all begin the same are likely resulting in different
+          interface names. Since this changes the primary interface names
+          pointing to containers if truncation happens the old scheme may still
+          be requested by selecting a different naming scheme than the v245
+          one, via the net.naming-scheme= kernel command line option.
+
+        * PrivateUsers= in service files now works in services run by the
+          systemd --user per-user instance of the service manager.
+
+        * A new per-service sandboxing option ProtectClock= has been added that
+          locks down write access to the system clock. It takes away device
+          node access to /dev/rtc as well as the system calls that allow to set
+          the system clock. It also removes the CAP_SYS_TIME and CAP_WAKE_ALARM
+          capabilities. Note that this option does not affect access to
+          auxiliary services that allow changing the clock, for example access
+          to systemd-timedated.
+
+        * The systemd-id128 tool gained a new "show" verb for listing or
+          resolving a number of well-known UUIDs/128bit IDs, currently mostly
+          GPT partition table types.
+
+        * The Discoverable Partitions Specification has been updated to support
+          /var and /var/tmp partition discovery. Support for this has been
+          added to systemd-gpt-auto-generator. For details see:
+
+          https://systemd.io/DISCOVERABLE_PARTITIONS
+
+        * "systemctl list-unit-files" has been updated to show a new column
+          with the suggested enablement state based on the vendor preset files
+          for the respective units.
+
+        * "systemctl" gained a new option "--with-dependencies". If specified
+          commands such as "systemctl status" or "systemctl cat" will now show
+          all specified units along with all units they depend on.
+
+        * networkctl gained support for showing per-interface logs in its
+          "status" output.
+
+        * The [Match] section of .link and .network files now supports a new
+          option PermanentMACAddress= which may be used to check against the
+          permanent MAC address of a network device even if a randomized MAC
+          address is used.
+
+       * systemd-logind will now validate access to the operation for changing
+         virtual terminals via a PolicyKit action. By default only users with
+         at least one session on a local VT will get access to the method call.
+
+       * When systemd sets up PAM sessions that invoked service processes shall
+         run in, the pam_setcred() API is now invoked, thus permitting PAM
+         modules to set additional credentials for the processes.
+
+        …
+
 CHANGES WITH 244:
 
         * Support for the cpuset cgroups v2 controller has been added.
diff --git a/TODO b/TODO
index ce37869ce17de40805a1f8ea5838573fb158d284..a9902ddff2d5ad98b8858a7fdd3066811606df46 100644 (file)
--- a/TODO
+++ b/TODO
@@ -19,17 +19,106 @@ Janitorial Clean-ups:
 
 Features:
 
-* when dissecting images, warn about unrecognized partition flags
+* cryptsetup/homed: also support FIDO2 HMAC password logic for unlocking
+  devices. (see: https://github.com/mjec/fido2-hmac-secret)
+
+* systemd-gpt-auto should probably set x-systemd.growfs on the mounts it
+  creates
+
+* homed/userdb: distuingish passwords and recovery keys in the records, since
+  we probably want to use different PBKDF algorithms/settings for them:
+  passwords have low entropy but recovery keys should have good entropy key
+  hence we can make them quicker to work.
+
+* bootctl:
+  - teach it to prepare an ESP wholesale, i.e. with mkfs.vfat invocation
+  - teach it to copy in unified kernel images and maybe type #1 boot loader spec entries from host
+  - make it operate on loopback files, dissecting enough to find ESP to operate on
+
+* by default, in systemd --user service bump the OOMAdjust to 100, as privs
+  allow so that systemd survives
 
 * honour specifiers in unit files that resolve to some very basic
   /etc/os-release data, such as ID, VERSION_ID, BUILD_ID, VARIANT_ID.
 
+* cryptsetup: allow encoding key directly in /etc/crypttab, maybe with a
+  "base64:" prefix. Useful in particular for pkcs11 mode.
+
 * socket units: allow creating a udev monitor socket with ListenDevices= or so,
   with matches, then actviate app thorugh that passing socket oveer
 
+* unify on openssl:
+  - port sd_id128_get_machine_app_specific() over from khash
+  - port resolved over from libgcrypt (DNSSEC code)
+  - port journald + fsprg over from libgcrypt
+  - port importd over from libgcrypt
+  - when that's done: kill khash.c
+  - when that's done: kill gnutls support in resolved
+
 * kill zenata, all hail weblate?
 
-* move discoverable partitions spec into markdown and our tree
+* when we resize disks (homed?) always round up to 4K sectors, not 512K
+
+* add growvol and makevol options for /etc/crypttab, similar to
+  x-systemd.growfs and x-systemd-makefs.
+
+* hook up the TPM to /etc/crypttab, with a new option that is similar to the
+  new PKCS#11 option in crypttab, and allows unlocking a LUKS volume via a key
+  unsealed from the TPM. Optionally, if TPM is not available fall back to
+  TPM-less mode, and set up linear DM mapping instead (inspired by kpartx), so
+  that the device paths stay the same, regardless if crypto is used or not.
+
+* systemd-repart: by default generate minimized partition tables (i.e. tables
+  that only covere the space actually used, excluding any free space at the
+  end), in order to maximize dd'ability. Requires libfdisk work, see
+  https://github.com/karelzak/util-linux/issues/907
+
+* systemd-repart: optionally, allow specifiying a path to initialize new
+  partitions from, i.e. an fs image file or a source device node. This would
+  then turn systemd-repart into a simple installer: with a few .repart files
+  you could replicate the host system on another device. a full installer would
+  then be: "systemd-repart /dev/sda && bootctl install /dev/sda &&
+  systemd-firstboot --image= …"
+
+* systemd-repart: MBR partition table support. Care needs to be taken regarding
+  Type=, so that partition definitions can sanely apply to both the GPT and the
+  MBR case. Idea: accept syntax "Type=gpt:home mbr:0x83" for setting the types
+  for the two partition types explicitly. And provide an internal mapping so
+  that "Type=linux-generic" maps to the right types for both partition tables
+  automatically.
+
+* systemd-repart: allow sizing partitions as factor of available RAM, so that
+  we can reasonably size swap partitions for hibernation.
+
+* systemd-repart: allow running mkfs before making partitions pop up +
+  encryption via LUKS to allow booting into an empty root with only /usr mounted in
+
+* systemd-repart: allow managing the gpt read-only partition flag + auto-mount flag
+
+* systemd-repart: allow disabling growing of specific partitions, or making
+  them (think ESP: we don't ever want to grow it, since we cannot resize vfat)
+
+* systemd-repart: add specifier expansion, add especifier that refers to root
+  device node of current system, /usr device node, and matching verity, so that
+  an installer can be made a "copy" installer of the booted OS
+
+* systemd-repart: make it a static checker during early boot for existence and
+  absence of other partitions for trusted boot environments
+
+* systemd-repart: when no configuration is found, exit early do not check
+  partition table, so that it is safe to run in the initrd on any system
+
+* systemd-repart: allow config of partition uuid
+
+* userdb: allow username prefix searches in varlink API
+
+* userdb: allow existence checks
+
+* pid: activation by journal search expression
+
+* when switching root from initrd to host, set the machine_id env var so that
+  if the host has no machine ID set yet we continue to use the random one the
+  initrd had set.
 
 * sd-event: add native support for P_ALL waitid() watching, then move PID 1 to
   it fo reaping assigned but unknown children. This needs to some special care
@@ -81,10 +170,6 @@ Features:
   right) become genuine first class citizens, and we gain automatic, sane JSON
   output for them.
 
-* dissector: invoke fsck on the file systems we encounter, after all ext4 is
-  still pretty popular (and we mount the ESP too with it after all, which is
-  fat)
-
 * systemd-firstboot: teach it dissector magic, so that you can point it to some
   disk image and it will just set everything in it all behind the scenes.
 
@@ -107,6 +192,38 @@ Features:
   user@.service, which returns the XDG_RUNTIME_DIR value, and make this
   behaviour selectable via pam module option.
 
+* homed:
+  - when user tries to log into record signed by unrecognized key, automatically add key to our chain after polkit auth
+  - hook up machined/nspawn users with a varlink user query interface
+  - rollback when resize fails mid-operation
+  - GNOME's side for forget key on suspend (requires rework so that lock screen runs outside of uid)
+  - resize on login?
+  - fstrim on logout?
+  - shrink fs on logout?
+  - update LUKS password on login if we find there's a password that unlocks the JSON record but not the LUKS device.
+  - create on activate?
+  - properties: icon url?, preferred session type?, administrator bool (which translates to 'wheel' membership)?, address?, telephone?, vcard?, samba stuff?, parental controls?
+  - communicate clearly when usb stick is safe to remove. probably involves
+    beefing up logind to make pam session close hook synchronous and wait until
+    systemd --user is shut down.
+  - logind: maybe keep a "busy fd" as long as there's a non-released session around or the user@.service
+  - maybe make automatic, read-only, time-based reflink-copies of LUKS disk images (think: time machine)
+  - distuingish destroy / remove (i.e. currently we can unregister a user, unregister+remove their home directory, but not just remove their home directory)
+  - in systemd's PAMName= logic: query passwords with ssh-askpassword, so that we can make "loginctl set-linger" mode work
+  - fingerprint authentication, pattern authentication, …
+  - make sure "classic" user records can also be managed by homed
+  - description field for groups
+  - make size of $XDG_RUNTIME_DIR configurable in user record
+  - reuse pwquality magic in firstboot
+  - query password from kernel keyring first
+  - update even if record is "absent"
+  - add a "access mode" + "fstype" field to the "status" section of json identity records reflecting the actually used access mode and fstype, even on non-luks backends
+  - move acct mgmt stuff from pam_systemd_home to pam_systemd?
+  - when "homectl --pkcs11-token-uri=" is used, synthesize ssh-authorized-keys records for all keys we have private keys on the stick for
+  - make slice for users configurable (requires logind rework)
+  - logind: populate auto-login list bus property from PKCS#11 token
+  - when determining state of a LUKS home directory, check DM suspended sysfs file
+
 * introduce a new per-process uuid, similar to the boot id, the machine id, the
   invocation id, that is derived from process creds, specifically a hashed
   combination of AT_RANDOM + getpid() + the starttime from
@@ -177,13 +294,6 @@ Features:
 
 * introduce per-unit (i.e. per-slice, per-service) journal log size limits.
 
-* optionally, if a per-partition GPT flag is set for the root/home/… partitions
-  format the partition on next boot and unset the flag, in order to implement
-  factory reset. also, add a second flag that simply indicates whether such a
-  scheme is supported. then, add a tool (or maybe beef up systemd-dissect) to
-  show state of these flags, and optionally trigger such a factory reset on
-  next boot by setting the flag.
-
 * sd-boot: automatically load EFI modules from some drop-in dir, so that people
   can add in file system drivers and such
 
@@ -397,10 +507,6 @@ Features:
   yogas can be recognized as "convertible" too, even if they predate the DMI
   "convertible" form factor
 
-* Maybe add a small tool invoked early at boot, that adds in or resizes
-  partitions automatically, to be used when the media used is actually larger
-  than the image written onto it is.
-
 * Maybe add PrivatePIDs= as new unit setting, and do minimal PID namespacing
   after all. Be strict however, only support the equivalent of nspawn's
   --as-pid2 switch, and sanely proxy sd_notify() messages dropping stuff such
@@ -419,15 +525,6 @@ Features:
   "systemd-gdb" for attaching to the start-up of any system service in its
   natural habitat.
 
-* maybe add gpt-partition-based user management: each user gets his own
-  LUKS-encrypted GPT partition with a new GPT type. A small nss module
-  enumerates users via udev partition enumeration. UIDs are assigned in a fixed
-  way: the partition index is added as offset to some fixed base uid. User name
-  is stored in GPT partition name. A PAM module authenticates the user via the
-  LUKS partition password. Benefits: strong per-user security, compatibility
-  with stateless/read-only/verity-enabled root. (other idea: do this based on
-  loopback files in /home, without GPT involvement)
-
 * gpt-auto logic: related to the above, maybe support a "secondary" root
   partition, that is mounted to / and is writable, and where the actual root's
   /usr is mounted into.
@@ -797,11 +894,6 @@ Features:
   - journald: when we drop syslog messages because the syslog socket is
     full, make sure to write how many messages are lost as first thing
     to syslog when it works again.
-  - change systemd-journal-flush into a service that stays around during
-    boot, and causes the journal to be moved back to /run on shutdown,
-    so that we do not keep /var busy. This needs to happen synchronously,
-    hence doing this via signals is not going to work.
-  - optionally support running journald from the command line for testing purposes in external projects
   - journald: allow per-priority and per-service retention times when rotating/vacuuming
   - journald: make use of uid-range.h to managed uid ranges to split
     journals in.
index 3d40df760b03911a7262b2102f300e47e154a8b8..13ceaa8482030339589564e9f2ee1f5c5da7e883 100644 (file)
@@ -60,7 +60,7 @@ Everything described below is located on a placeholder file system `$BOOT`. The
   * Otherwise, if the OS is installed on a disk with MBR disk label, a new partition with MBR type id of 0xEA shall be created, of a suitable size (let's say 500MB), and it should be used as `$BOOT`.
 * On disks with GPT disk labels
   * If the OS is installed on a disk with GPT disk label, and a partition with the GPT type GUID of `bc13c2ff-59e6-4262-a352-b275fd6f7172` already exists, it should be used as `$BOOT`.
-  * Otherwise, if the OS is installed on a disk with GPT disk label, and an ESP partition (i.e. with the GPT type UID of `c12a7328-f81f-11d2-ba4b-00a0c93ec93b`) already exists and is large enough (let's say 250MB`) and otherwise qualifies, it should be used as `$BOOT`.
+  * Otherwise, if the OS is installed on a disk with GPT disk label, and an ESP partition (i.e. with the GPT type UID of `c12a7328-f81f-11d2-ba4b-00a0c93ec93b`) already exists and is large enough (let's say 250MB) and otherwise qualifies, it should be used as `$BOOT`.
   * Otherwise, if the OS is installed on a disk with GPT disk label, and if the ESP partition already exists but is too small, a new suitably sized (let's say 500MB) partition with GPT type GUID of `bc13c2ff-59e6-4262-a352-b275fd6f7172` shall be created and it should be used as `$BOOT`.
   * Otherwise, if the OS is installed on a disk with GPT disk label, and no ESP partition exists yet, a new suitably sized (let's say 500MB) ESP should be created and should be used as `$BOOT`.
 
@@ -91,6 +91,20 @@ from the user. Only entries matching the feature set of boot loader and system
 shall be considered and displayed. This allows image builders to put together
 images that transparently support multiple different architectures.
 
+Note that the `$BOOT` partition is not supposed to be exclusive territory of
+this specification. This specification only defines semantics of the `/loader/`
+directory inside the file system (see below), but it doesn't intend to define
+ownership of the whole file system exclusively. Boot loaders, firmware, and
+other software implementating this specification may choose to place other
+files and directories in the same file system. For example, boot loaders that
+implement this specification might install their own boot code into the `$BOOT`
+partition. On systems where `$BOOT` is the ESP this is a particularly common
+setup. Implementations of this specification must be able to operate correctly
+if files or directories other than `/loader/` are found in the top level
+directory. Implementations that add their own files or directories to the file
+systems should use well-named directories, to make name collisions between
+multiple users of the file system unlikely.
+
 ### Type #1 Boot Loader Specification Entries
 
 We define two directories below `$BOOT`:
index 8b1a7b46e3ef0ffa3b2cc723ae6fd89f5729308a..f1537b89399a5e180fa7f22da917d9f4676f1927 100644 (file)
@@ -64,6 +64,9 @@ Other GPT type IDs might be used on Linux, for example to mark software RAID or
 LVM partitions. The definitions of those GPT types is outside of the scope of
 this specification.
 
+[systemd-id128(1)](http://www.freedesktop.org/software/systemd/man/systemd-i128.html)
+may be used to list those UUIDs.
+
 ## Partition Names
 
 For partitions of the types listed above it is recommended to use
diff --git a/docs/HOME_DIRECTORY.md b/docs/HOME_DIRECTORY.md
new file mode 100644 (file)
index 0000000..73c2359
--- /dev/null
@@ -0,0 +1,173 @@
+---
+title: Home Directories
+category: Concepts
+layout: default
+---
+
+# Home Directories
+
+[`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html)
+manages home directories of regular ("human") users. Each directory it manages
+encapsulates both the data store and the user record of the user so that it
+comprehensively describes the user account, and is thus naturally portable
+between systems without any further, external metadata. This document describes
+the format used by these home directories, in context of the storage mechanism
+used.
+
+## General Structure
+
+Inside of the home directory a file `~/.identity` contains the JSON formatted
+user record of the user. It follows the format defined in [`JSON User
+Records`](https://systemd.io/USER_RECORD). It is recommended to bring the
+record into 'normalized' form (i.e. all objects should contain their fields
+sorted alphabetically by their key) before storing it there, though this is not
+required nor enforced. Since the user record is cryptographically signed the
+user cannot make modifications to the file on their own (at least not without
+corrupting it, or knowing the private key used for signing the record). Note
+that user records are stored here without their `binding`, `status` and
+`secret` sections, i.e. only with the sections included in the signature plus
+the signature section itself.
+
+## Storage Mechanism: Plain Directory/`btrfs` Subvolume
+
+If the plain directory or `btrfs` subvolume storage mechanism of
+`systemd-homed` is used (i.e. `--storage=directory` or `--storage=subvolume` on
+the
+[`homectl(1)`](https://www.freedesktop.org/software/systemd/man/homectl.html)
+command line) the home directory requires no special set-up besides including
+the user record in the `~/.identity` file.
+
+It is recommended to name home directories managed this way by
+`systemd-homed.service` by the user name, suffixed with `.homedir` (example:
+`lennart.homedir` for a user `lennart`) but this is not enforced. When the user
+is logged in the directory is generally mounted to `/home/$USER` (in our
+example: `/home/lennart`), thus dropping the suffix while the home directory is
+active. `systemd-homed` will automatically discover home directories named this
+way in `/home/*.homedir` and synthesize NSS user records for them as they show
+up.
+
+## Storage Mechanism: `fscrypt` Directories
+
+This storage mechanism is mostly identical to the plain directory storage
+mechanism, except that the home directory is encrypted using `fscrypt`. (Use
+`--storage=fscrypt` on the `homectl` command line.) Key management is
+implemented via extended attributes on the directory itself: for each password
+an extended attribute `trusted.fscrypt_slot0`, `trusted.fscrypt_slot1`,
+`trusted.fscrypt_slot2`, … is maintained. It's value contains a colon-separated
+pair of Base64 encoded data fields. The first field contains a salt value, the
+second field the encrypted volume key. The latter is encrypted using AES256 in
+counter mode, using a key derived from the password via PBKDF2-HMAC-SHA512
+together with the salt value. The construction is similar to what LUKS does for
+`dm-crypt` encrypted volumes. Note that extended attributes are not encrypted
+by `fscrypt` and hence are suitable for carry the key slots. Moreover, by using
+extended attributes the slots are directly attached to the directory and an
+independent sidecar key database is not required.
+
+## Storage Mechanism: `cifs` Home Directories
+
+In this storage mechanism the home directory is mounted from a CIFS server and
+service at login, configured inside the user record. (Use `--storage=cifs` on
+the `homectl` command line.) The local password of the user is used to log into
+the CIFS service. The directory share needs to contain the user record in
+`~/.identity` as well. Note that this means that the user record needs to be
+registered locally before it can be mounted for the first time, since CIFS
+domain and server information needs to be known *before* the mount. Note that
+for all other storage mechanisms it is entirely sufficient if the directories
+or storage artifacts are placed at the right locations — all information to
+activate them can be derived automatically from their mere availability.
+
+## Storage Mechanism: `luks` Home Directories
+
+This is the most advanced and most secure storage mechanism and consists of a
+Linux file system inside a LUKS2 volume inside a loopback file (or on removable
+media). (Use `--storage=luks` on the `homectl` command line.)  Specifically:
+
+* The image contains a GPT partition table. For now it should only contain a
+  single partition, and that partition must have the type UUID
+  `773f91ef-66d4-49b5-bd83-d683bf40ad16`. It's partition label must be the
+  user name.
+
+* This partition must contain a LUKS2 volume, whose label must be the user
+  name. The LUKS2 volume must contain a LUKS2 token field of type
+  `systemd-homed`. The JSON data of this token must have a `record` field,
+  containing a string with base64-encoded data. This data is the JSON user
+  record, in the same serialization as in `~/.identity`, though encrypted. The
+  JSON data of this token must also have an `iv` field, which contains a
+  base64-encoded binary initialization vector for the encryption. The
+  encryption used is the same as the LUKS2 volume itself uses, unlocked by the
+  same volume key, but based on its own IV.
+
+* Inside of this LUKS2 volume must be a Linux file system, one of `ext4`,
+  `btrfs` and `xfs`. The file system label must be the user name.
+
+* This file system should contain a single directory named after the user. This
+  directory will become the home directory of the user when activated. It
+  contains a second copy of the user record in the `~/.identity` file, like in
+  the other storage mechanisms.
+
+The image file should either reside in a directory `/home/` on the system,
+named after the user, suffixed with `.home`. When activated the container home
+directory is mounted to the same path, though with the `.home` suffix dropped —
+unless a different mount point is defined in the user record. (e.g.: the
+loopback file `/home/waldo.home` is mounted to `/home/waldo` while activated.)
+When the image is stored on removable media (such as a USB stick) the image
+file can be directly `dd`'ed onto it, the format is unchanged. The GPT envelope
+should ensure the image is properly recognizable as a home directory both when
+used in a loopback file and on a removable USB stick. (Note that when mounting
+a home directory from an USB stick it too defaults to a directory in `/home/`,
+named after the username, with no further suffix.)
+
+Rationale for the GPT partition table envelope: this way the image is nicely
+discoverable and recognizable already by partition managers as a home
+directory. Moreover, when copied onto a USB stick the GPT envelope makes sure
+the stick is properly recognizable as a portable home directory
+medium. (Moreover it allows to embed additional partitions later on, for
+example for allowing a multi-purpose USB stick that contains both a home
+directory and a generic storage volume.)
+
+Rationale for including the encrypted user record in the the LUKS2 header:
+Linux kernel file system implementations are generally not robust towards
+maliciously formatted file systems; there's a good chance that file system
+images can be used as attack vectors, exploiting the kernel. Thus it is
+necessary to validate the home directory image *before* mounting it and
+establishing a minimal level of trust. Since the user record data is
+cryptographically signed and user records not signed with a recognized private
+key are not accepted a minimal level of trust between the system and the home
+directory image is established.
+
+Rationale for storing the home directory one level below to root directory of
+the contained file system: this way special directories such as `lost+found/`
+do not show up in the user's home directory.
+
+## Algorithm
+
+Regardless of the storage mechanism used, an activated home directory
+necessarily involves a mount point to be established. In case of the
+directory-based storage mechanisms (`directory`, `subvolume` and `fscrypt`)
+this is a bind mount, in case of `cifs` this is a CIFS network mount, and in
+case of the LUKS2 backend a regular block device mount of the file system
+contained in the LUKS2 image. By requiring a mount for all cases (even for
+those that already are a directory) a clear logic is defined to distuingish
+active and inactive home directories, so that the directories become
+inaccessible under their regular path the instant they are
+deactivated. Moreover, the `nosuid`, `nodev` and `noexec` flags configured in
+the user record are applied when the bind mount is established.
+
+During activation, the user records retained on the host, the user record
+stored in the LUKS2 header (in case of the LUKS2 storage mechanism) and the
+user record stored inside the home directory in `~/.identity` are
+compared. Activation is only permitted if they match the same user and are
+signed by a recognized key. When the three instances differ in `lastChangeUSec`
+field, the newest record wins, and is propagated to the other two locations.
+
+During activation the file system checker (`fsck`) appropriate for the
+selected file system is automatically invoked, ensuring the file system is in a
+healthy state before it is mounted.
+
+If the UID assigned to a user does not match the owner of the home directory in
+the file system, the home directory is automatically and recursively `chown()`ed
+to the correct UID.
+
+Depending on the `discard` setting of the user record either the backing
+loopback file is `fallocate()`ed during activation, or the mounted file system
+is `FITRIM`ed after mounting, to ensure the setting is correctly enforced.
index e1aa656b0f5885d4100bfd33ced71ff2b3898822..ebea93b526d1f206e259ba10ae19fdf4822b2086 100644 (file)
@@ -4,7 +4,7 @@ category: Interfaces
 layout: default
 ---
 
-# Interface Stability Promise
+# Interface Portability and Stability Promise
 
 systemd provides various interfaces developers and programs might rely on. Starting with version 26 (the first version released with Fedora 15) we promise to keep a number of them stable and compatible for the future.
 
@@ -18,7 +18,7 @@ The stable interfaces are:
 
 * Some of the **"special" unit names** and their semantics. To be precise the ones that are necessary for normal services, and not those required only for early boot and late shutdown, with very few exceptions. To list them here: `basic.target`, `shutdown.target`, `sockets.target`, `network.target`, `getty.target`, `graphical.target`, `multi-user.target`, `rescue.target`, `emergency.target`, `poweroff.target`, `reboot.target`, `halt.target`, `runlevel[1-5].target`.
 
-* **The D-Bus interfaces of the main service daemon and other daemons**. We try to always preserve backwards compatiblity, and intentational breakage is never introduced. Nevertheless, when we find bugs that mean that the existing interface was not useful, or when the implementation did something different than stated by the documentation and the implemented behaviour is not useful, we will fix the implementation and thus introduce a change in behaviour. But the API (parameter counts and types) is never changed, and existing attributes and methods will not be removed.
+* **The D-Bus interfaces of the main service daemon and other daemons**. We try to always preserve backwards compatibility, and intentional breakage is never introduced. Nevertheless, when we find bugs that mean that the existing interface was not useful, or when the implementation did something different than stated by the documentation and the implemented behaviour is not useful, we will fix the implementation and thus introduce a change in behaviour. But the API (parameter counts and types) is never changed, and existing attributes and methods will not be removed.
 
 * For a more comprehensive and authoritative list, consult the chart below.
 
@@ -41,7 +41,7 @@ What does this mean for you? When developing with systemd, don't use any of the
 Note that this is a promise, not an eternal guarantee. These are our intentions, but if in the future there are very good reasons to change or get rid of an interface we have listed above as stable, then we might take the liberty to do so, despite this promise. However, if we do this, then we'll do our best to provide a smooth and reasonably long transition phase.
 
 
-# Interface Portability And Stability Chart
+## Interface Portability And Stability Chart
 
 systemd provides a number of APIs to applications. Below you'll find a table detailing which APIs are considered stable and how portable they are.
 
@@ -72,11 +72,9 @@ A number of systemd's APIs expose Linux or systemd-specific features that cannot
 Note that not all of these interfaces are our invention (but most), we just adopted them in systemd to make them more prominently implemented. For example, we adopted many Debian facilities in systemd to push it into the other distributions as well.
 
 
-
 ---
 
 
-
 And now, here's the list of (hopefully) all APIs that we have introduced with systemd:
 
 | API  | Type | Covered by Interface Stability Promise | Fully documented | Known External Consumers | Reimplementable Independently | Known Other Implementations | systemd Implementation portable to other OSes or non-systemd distributions |
@@ -136,9 +134,9 @@ This is not an attempt to comprehensively list all users of these APIs. We are j
 
 Of course, one last thing I can't make myself not ask you before we finish here, and before you start reimplementing these APIs in your distribution: are you sure it's time well spent if you work on reimplementing all this code instead of just spending it on adopting systemd on your distro as well?
 
-## Independent operation of systemd programs
+## Independent Operation of systemd Programs
 
-Some programs in the systemd suite are indended to operate independently of the
+Some programs in the systemd suite are intended to operate independently of the
 running init process (or even without an init process, for example when
 creating system installation chroots). They can be safely called on systems with
 a different init process or for example in package installation scriptlets.
index ff4c7718188285e3b0d608fa28624e629babcf87..779044b0d4e8358134a20c7ec99624e5c2bc8bf3 100644 (file)
@@ -87,11 +87,11 @@ systemd 38:
 Processes (run by the root user) whose first character of the zeroth command
 line argument is `@` are excluded from the killing spree, much the same way as
 kernel threads are excluded too. Thus, a daemon which wants to take advantage
-of this logic needs to place the following at the top of its main() function:
+of this logic needs to place the following at the top of its `main()` function:
 
 ```c
 ...
-[0][0] = '@';
+argv[0][0] = '@';
 ...
 ```
 
index 9f93e3b836d95855c487274dfc401eaf3055b8c0..271d8ab1e3d6d23670bac8a736f987e912b17c42 100644 (file)
@@ -192,6 +192,7 @@ All execution-related settings are available for transient units.
 ✓ PrivateUsers=
 ✓ ProtectSystem=
 ✓ ProtectHome=
+✓ ProtectClock=
 ✓ MountFlags=
 ✓ MountAPIVFS=
 ✓ Personality=
index ab26bc15f7b72fc6f6b26664cee24daacd0284c8..255cc713226968cbf0c23a3227c3c674cdc81a1e 100644 (file)
@@ -96,7 +96,15 @@ but downstreams are strongly advised against doing that.)
 
 `systemd` defines a number of special UID ranges:
 
-1. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
+1. 60001…60513 → UIDs for home directories managed by
+   [`systemd-homed.service(8)`](https://www.freedesktop.org/software/systemd/man/systemd-homed.service.html). UIDs
+   from this range are automatically assigned to any home directory discovered,
+   and persisted locally on first login. On different systems the same user
+   might get different UIDs assigned in case of conflict, though it is
+   attempted to make UID assignments stable, by deriving them from a hash of
+   the user name.
+
+2. 61184…65519 → UIDs for dynamic users are allocated from this range (see the
    `DynamicUser=` documentation in
    [`systemd.exec(5)`](https://www.freedesktop.org/software/systemd/man/systemd.exec.html)). This
    range has been chosen so that it is below the 16bit boundary (i.e. below
@@ -111,7 +119,7 @@ but downstreams are strongly advised against doing that.)
    user record resolving works correctly without those users being in
    `/etc/passwd`.
 
-2. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
+3. 524288…1879048191 → UID range for `systemd-nspawn`'s automatic allocation of
    per-container UID ranges. When the `--private-users=pick` switch is used (or
    `-U`) then it will automatically find a so far unused 16bit subrange of this
    range and assign it to the container. The range is picked so that the upper
@@ -232,7 +240,8 @@ the artifacts the container manager persistently leaves in the system.
 |                     5 | `tty` group           | `systemd`     | `/etc/passwd`                 |
 |                 6…999 | System users          | Distributions | `/etc/passwd`                 |
 |            1000…60000 | Regular users         | Distributions | `/etc/passwd` + LDAP/NIS/…    |
-|           60001…61183 | Unused                |               |                               |
+|           60001…60513 | Human Users (homed)   | `systemd`     | `nss-systemd`
+|           60514…61183 | Unused                |               |                               |
 |           61184…65519 | Dynamic service users | `systemd`     | `nss-systemd`                 |
 |           65520…65533 | Unused                |               |                               |
 |                 65534 | `nobody` user         | Linux         | `/etc/passwd` + `nss-systemd` |
index a12870dd42b105c882aa9cd34057e26b014ac5bc..08118952ee999660e312d357042be42446f2f9cd 100644 (file)
@@ -71,11 +71,11 @@ the following extensions are envisioned:
 4. Default parameters for backup applications and similar
 
 Similar to JSON User Records there are also [JSON Group
-Records](https://systemd.io/GROUP_RECORD.md) that encapsulate UNIX groups.
+Records](https://systemd.io/GROUP_RECORD) that encapsulate UNIX groups.
 
 JSON User Records may be transferred or written to disk in various protocols
 and formats. To inquire about such records defined on the local system use the
-[User/Group Lookup API via Varlink](https://systemd.io/USER_GROUP_API.md).
+[User/Group Lookup API via Varlink](https://systemd.io/USER_GROUP_API).
 
 ## Why JSON?
 
@@ -184,7 +184,7 @@ does not need to to be concerned with the `secret` section of user records, as
 the fields included therein are only useful when executing authentication
 operations natively against JSON user records.
 
-The `systemd-homed' manager uses all seven sections for various
+The `systemd-homed` manager uses all seven sections for various
 purposes. Inside the home directories (and if the LUKS2 backend is used, also
 in the LUKS2 header) a user record containing the `regular`, `privileged`,
 `perMachine` and `signature` sections is stored. `systemd-homed` also stores a
index d8c1535f28481c1a8f9add4dc76aabd4049958d9..95289732b7b350353887b77a4e977303a2aa029c 100644 (file)
@@ -1,5 +1,5 @@
 <footer class="site-footer">
-  <p>&copy; systemd, 2019</p>
+  <p>&copy; systemd, 2020</p>
 
   <p><a href="https://github.com/systemd/systemd">Website source</a></p>
 </footer>
index 747efc1fbdb69e32d50abc18bc138aab296ef570..522c51324af17a6bf3c13c5e7b9cb46671d17a28 100644 (file)
@@ -3,17 +3,21 @@
 # You really want to adjust this to your local distribution. If you use this
 # unmodified you are not building systems safely and securely.
 
-auth     sufficient pam_unix.so nullok try_first_pass
-auth     required   pam_deny.so
+auth      sufficient pam_unix.so
+-auth     sufficient pam_systemd_home.so
+auth      required   pam_deny.so
 
-account  required   pam_nologin.so
-account  sufficient pam_unix.so
-account  required   pam_permit.so
+account   required   pam_nologin.so
+-account  sufficient pam_systemd_home.so
+account   sufficient pam_unix.so
+account   required   pam_permit.so
 
-password sufficient pam_unix.so nullok sha512 shadow try_first_pass try_authtok
-password required   pam_deny.so
+-password sufficient pam_systemd_home.so
+password  sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
+password  required   pam_deny.so
 
--session optional   pam_keyinit.so revoke
--session optional   pam_loginuid.so
--session optional   pam_systemd.so
-session  sufficient pam_unix.so
+-session  optional   pam_keyinit.so revoke
+-session  optional   pam_loginuid.so
+-session  optional   pam_systemd_home.so
+-session  optional   pam_systemd.so
+session   required   pam_unix.so
index 18c70e3555323a9795f8d3f4589f551228a14cbd..2cd1763a6dda8fb61568d71a94d9e8776e58790d 100644 (file)
@@ -5,11 +5,8 @@ setup:
 - sudo apt-get update -y
 - sudo apt-get build-dep -y systemd
 - sudo apt-get install -y python3-pip
-# FIXME: temporarily pin the meson version as 0.53 doesn't work with older
-# python 3.5
-# # See: https://github.com/mesonbuild/meson/issues/6427
-#
-- pip3 install meson==0.52.1 ninja
+- sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
+- pip3 install meson ninja
 - export PATH="$HOME/.local/bin/:$PATH"
 - CC=$FUZZ_CC CXX=$FUZZ_CXX meson -Dfuzzbuzz=true -Dfuzzbuzz-engine-dir=$(dirname "$FUZZ_ENGINE") -Dfuzzbuzz-engine=$(cut -d. -f1 <(basename "$FUZZ_ENGINE")) -Db_lundef=false ./build
 - ninja -v -C ./build fuzzers
index be9c8d2c4bdd22b3c4630060ddf64ed43eba4222..931e760ea584172f5399bb8874433ccb264b8f43 100644 (file)
@@ -82,7 +82,7 @@ sensor:modalias:acpi:SMO8500:*:dmi:*Acer*:pnOneS1002*
 
 sensor:modalias:acpi:KIOX0009*:dmi:*:svnAcer:pnOneS1003:*
  ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
+
 sensor:modalias:acpi:BOSC0200*:dmi:*:svnAcer*:pnSwitchSW312-31:*
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
@@ -105,20 +105,14 @@ sensor:modalias:acpi:INVN6500*:dmi:*svnASUSTeK*:pnT300CHI*
  ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
 
 sensor:modalias:acpi:INVN6500*:dmi:*svnASUSTeK*:*pnT100TA*
- ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
-
 sensor:modalias:acpi:INVN6500*:dmi:*svnASUSTeK*:pnT200TA*
  ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
 
 sensor:modalias:acpi:INVN6500*:dmi:*svnASUSTeK*:*pnTP201SA*
- ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
-
 sensor:modalias:acpi:INVN6500*:dmi:*svnASUSTeK*:pn*E205SA*
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
 sensor:modalias:acpi:INVN6500*:dmi:*svn*ASUSTeK*:*pn*TP300LA*
- ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1
-
 sensor:modalias:acpi:INVN6500*:dmi:*svn*ASUSTeK*:*pn*TP300LD*
  ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1
 
@@ -131,6 +125,7 @@ sensor:modalias:acpi:KXJ2109*:dmi:*:svnASUSTeK*:pnME176C*
 sensor:modalias:acpi:SMO8500*:dmi:*svn*ASUSTeK*:*pn*TP300LJ*
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
+sensor:modalias:acpi:SMO8500*:dmi:*svn*ASUSTeK*:*pn*TP500LAB*
 sensor:modalias:acpi:SMO8500*:dmi:*svn*ASUSTeK*:*pn*TP500LB*
  ACCEL_MOUNT_MATRIX=0, 1, 0; 1, 0, 0; 0, 0, 1
 
@@ -171,7 +166,7 @@ sensor:modalias:acpi:BOSC0200*:dmi:bvnAmericanMegatrendsInc.:bvr5.11:*:svnDefaul
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
 # Chuwi Hi10 (CWI1515)
-sensor:modalias:acpi:BOSC0200*:dmi:bvnAmericanMegatrendsInc.:bvrP02A_C106.60E:*:svnDefaultstring:pnDefaultstring:* 
+sensor:modalias:acpi:BOSC0200*:dmi:bvnAmericanMegatrendsInc.:bvrP02A_C106.60E:*:svnDefaultstring:pnDefaultstring:*
  ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
 
 # Chuwi Hi10 Pro
index 75604b834221d64ff92146c7fd22ecf606feb7b1..f811b1034848c7c9dcf1c1ee919a55d574561328 100644 (file)
@@ -41,9 +41,8 @@
   <refsect1>
     <title>Description</title>
 
-    <para><command>halt</command>, <command>poweroff</command>,
-    <command>reboot</command> may be used to halt, power-off or reboot
-    the machine.</para>
+    <para><command>halt</command>, <command>poweroff</command>, <command>reboot</command> may be used to
+    halt, power-off, or reboot the machine. All three commands take the same options.</para>
 
   </refsect1>
 
   <refsect1>
     <title>Notes</title>
 
-    <para>These commands are implemented in a way that preserves compatibility with
-    the original SysV commands.
-    <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-    verbs <command>halt</command>, <command>poweroff</command>,
-    <command>reboot</command> provide the same functionality with some additional
-    features.</para>
+    <para>These commands are implemented in a way that preserves basic compatibility with the original SysV
+    commands.  <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    verbs <command>halt</command>, <command>poweroff</command>, <command>reboot</command> provide the same
+    functionality with some additional features.</para>
+
+    <para>Note that on many SysV systems <command>halt</command> used to be synonymous to
+    <command>poweroff</command>, i.e. both commands would equally result in powering the machine off. systemd
+    is more accurate here, and <command>halt</command> results in halting the machine only (leaving power
+    on), and <command>poweroff</command> is required to actually power it off.</para>
   </refsect1>
 
   <refsect1>
diff --git a/man/homectl.xml b/man/homectl.xml
new file mode 100644 (file)
index 0000000..ae502c8
--- /dev/null
@@ -0,0 +1,833 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="homectl" conditional='ENABLE_HOMED'
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>homectl</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>homectl</refentrytitle>
+    <manvolnum>1</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>homectl</refname>
+    <refpurpose>Create, remove, change or inspect home directories</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>homectl</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="req">COMMAND</arg>
+      <arg choice="opt" rep="repeat">NAME</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>homectl</command> may be used to create, remove, change or inspect a user's home
+    directory. It's primarily a command interfacing with
+    <citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    which manages home directories of users.</para>
+
+    <para>Home directories managed by <filename>systemd-homed.service</filename> are self-contained, and thus
+    include the user's full metadata record in the home's data storage itself, making them easy to migrate
+    between machines. In particular, a home directory describes a matching user record, and every user record
+    managed by <filename>systemd-homed.service</filename> also implies existence and encapsulation of a home
+    directory. The user account and home directory become the same concept.</para>
+
+    <para>The following backing storage mechanisms are supported:</para>
+
+    <itemizedlist>
+      <listitem><para>An individual LUKS2 encrypted loopback file for a user, stored in
+      <filename>/home/*.home</filename>. At login the file system contained in this files is mounted, after
+      the LUKS2 encrypted volume has been attached. The user's password is identical to the encryption
+      passphrase of the LUKS2 volume. Access to data without preceeding user authentication is thus not
+      possible, even for the system administrator. This storage mechanism provides the strongest data
+      security and is thus recommended.</para></listitem>
+
+      <listitem><para>Similar, but the LUKS2 encrypted file system is located on regular block device, such
+      as an USB storage stick. In this mode home directories and all data they include are nicely migratable
+      between machines, simply by plugging the USB stick into different systems at different
+      times.</para></listitem>
+
+      <listitem><para>An encrypted directory using <literal>fscrypt</literal> on file systems that support it
+      (at the moment this is primarily <literal>ext4</literal>), located in
+      <filename>/home/*.homedir</filename>. This mechanism also provides encryption, but substantially
+      weaker than LUKS2, as most file system metadata is unprotected. Moreover
+      it currently does not support changing user passwords once the home directory has been
+      created.</para></listitem>
+
+      <listitem><para>A <literal>btrfs</literal> subvolume for each user, also located in
+      <filename>/home/*.homedir</filename>. This provides no encryption, but good quota
+      support.</para></listitem>
+
+      <listitem><para>A regular directory for each user, also located in
+      <filename>/home/*.homedir</filename>. This provides no encryption, but is a suitable fallback
+      available on all machines, even where LUKS2, <literal>fscrypt</literal> or <literal>btrfs</literal>
+      support is not available.</para></listitem>
+
+      <listitem><para>An individual Windows file share (CIFS) for each user.</para></listitem>
+    </itemizedlist>
+
+    <para>Note that <filename>systemd-homed.service</filename> and <command>homectl</command> will not manage
+    "classic" UNIX user accounts as created with <citerefentry
+    project='man-pages'><refentrytitle>useradd</refentrytitle><manvolnum>8</manvolnum></citerefentry> or
+    similar tools. In particular, this functionality is not suitable for managing system users (i.e. users
+    with a UID below 1000) but is exclusive to regular ("human") users.</para>
+
+    <para>Note that users/home directories managed via <command>systemd-homed.service</command> do not show
+    up in <filename>/etc/passwd</filename> and similar files, they are synthesized via glibc NSS during
+    runtime. They are thus resolvable and may be enumerated via the <citerefentry
+    project='man-pages'><refentrytitle>getent</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    tool.</para>
+
+    <para>This tool interfaces directly with <filename>systemd-homed.service</filename>, and may execute
+    specific commands on the home directories it manages. Since every home directory managed that way also
+    defines a JSON user and group record these home directories may also be inspected and enumerated via
+    <citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+
+    <para>Home directories managed by <filename>systemd-homed.service</filename> are usually in one of two
+    states, or in a transition state between them: when <literal>active</literal> they are unlocked and
+    mounted, and thus accessible to the system and its programs; when <literal>inactive</literal> they are
+    not mounted and thus not accessible. Activation happens automatically at login of the user and usually
+    can only complete after a password (or other authentication token) has been supplied. Deactivation
+    happens after the user fully logged out. A home directory remains active as long as the user is logged in
+    at least once, i.e. has at least one login session. When the user logs in a second time simultaneously
+    the home directory remains active. It is deactivated only after the last of the user's sessions
+    ends.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>The following general options are understood (further options that control the various properties
+    of user records managed by <filename>systemd-homed.service</filename> are documented further
+    down):</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><option>--identity=</option><replaceable>FILE</replaceable></term>
+
+        <listitem><para>Read the user's JSON record from the specified file. If passed as
+        <literal>-</literal> reads the user record from standard input. The supplied JSON object must follow
+        the structure documented on <ulink url="https://systemd.io/USER_RECORDS">JSON User
+        Records</ulink>. This option may be used in conjunction with the <command>create</command> and
+        <command>update</command> commands (see below), where it allows configuring the user record in JSON
+        as-is, instead of setting the individual user record properties (see below).</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--json=</option><replaceable>FORMAT</replaceable></term>
+        <term><option>-J</option></term>
+
+        <listitem><para>Controls whether to output the user record in JSON format, if the
+        <command>inspect</command> command (see below) is used. Takes one of <literal>pretty</literal>,
+        <literal>short</literal> or <literal>off</literal>. If <literal>pretty</literal> human-friendly
+        whitespace and newlines are inserted in the output to make the JSON data more readable. If
+        <literal>short</literal> all superfluous whitespace is suppressed. If <literal>off</literal> (the
+        default) the user information is not shown in JSON format but in a friendly human readable formatting
+        instead. The <option>-J</option> option picks <literal>pretty</literal> when run interactively and
+        <literal>short</literal> otherwise.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--export-format=</option><replaceable>FORMAT</replaceable></term>
+        <term><option>-E</option></term>
+        <term><option>-EE</option></term>
+
+        <listitem><para>When used with the <command>inspect</command> verb in JSON mode (see above) may be
+        used to suppress certain aspects of the JSON user record on output. Specifically, if
+        <literal>stripped</literal> format is used the binding and runtime fields of the record are
+        removed. If <literal>minimal</literal> format is used the cryptographic signature is removed too. If
+        <literal>full</literal> format is used the full JSON record is shown (this is the default). This
+        option is useful for copying an existing user record to a different system in order to create a
+        similar user there with the same settings. Specifically: <command>homectl inspect -EE | ssh
+        root@othersystem homectl create -i-</command> may be used as simple command line for replicating a
+        user on another host. <option>-E</option> is equivalent to <option>-j --export-format=stripped</option>,
+        <option>-EE</option> to <option>-j --export-format=minimal</option>. Note that when replicating user
+        accounts user records acquired in <literal>stripped</literal> mode will retain the original
+        cryptographic signatures and thus may only be modified when the private key to update them is available
+        on the destination machine. When replicating users in <literal>minimal</literal> mode, the signature
+        is removed during the replication and thus the record will be implicitly signed with the key of the destination
+        machine and may be updated there without any private key replication.</para></listitem>
+      </varlistentry>
+
+      <xi:include href="user-system-options.xml" xpointer="host" />
+      <xi:include href="user-system-options.xml" xpointer="machine" />
+
+      <xi:include href="standard-options.xml" xpointer="no-pager" />
+      <xi:include href="standard-options.xml" xpointer="no-legend" />
+      <xi:include href="standard-options.xml" xpointer="no-ask-password" />
+      <xi:include href="standard-options.xml" xpointer="help" />
+      <xi:include href="standard-options.xml" xpointer="version" />
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>User Record Properties</title>
+
+    <para>The following options control various properties of the user records/home directories that
+    <filename>systemd-homed.service</filename> manages. These switches may be used in conjunction with the
+    <command>create</command> and <command>update</command> commands for configuring various aspects of the
+    home directory and the user account:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><option>--real-name=</option><replaceable>NAME</replaceable></term>
+        <term><option>-c</option> <replaceable>NAME</replaceable></term>
+
+        <listitem><para>The real name for the user. This corresponds with the GECOS field on classic UNIX NSS
+        records.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--realm=</option><replaceable>REALM</replaceable></term>
+
+        <listitem><para>The realm for the user. The realm associates a user with a specific organization or
+        installation, and allows distuingishing users of the same name defined in different contexts. The
+        realm can be any string that also qualifies as valid DNS domain name, and it is recommended to use
+        the organization's or installation's domain name for this purpose, but this is not enforced nor
+        required. On each system only a single user of the same name may exist, and if a user with the same
+        name and realm is seen it is assumed to refer to the same user while a user with the same name but
+        different realm is considered a different user. Note that this means that two users sharing the same
+        name but with distinct realms are not allowed on the same system. Assigning a realm to a user is
+        optional.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--email-address=</option><replaceable>EMAIL</replaceable></term>
+
+        <listitem><para>Takes an electronic mail address to associate with the user. On log-in the
+        <varname>$EMAIL</varname> environment variable is initialized from this value.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--location=</option><replaceable>TEXT</replaceable></term>
+
+        <listitem><para>Takes location specification for this user. This is free-form text, which might or
+        might not be usable by geo-location applications. Example: <option>--location="Berlin,
+        Germany"</option> or <option>--location="Basement, Room 3a"</option></para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--icon-name=</option><replaceable>ICON</replaceable></term>
+
+        <listitem><para>Takes an icon name to associate with the user, following the scheme defined by the <ulink
+        url="https://standards.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html">Icon Naming
+        Specification</ulink>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--home-dir=</option><replaceable>PATH</replaceable></term>
+        <term><option>-d</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Takes a path to use as home directory for the user. Note that this is the directory
+        the user's home directory is mounted to while the user is logged in. This is not where the user's
+        data is actually stored, see <option>--image-path=</option> for that. If not specified defaults to
+        <filename>/home/$USER</filename>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--uid=</option><replaceable>UID</replaceable></term>
+
+        <listitem><para>Takes a preferred numeric UNIX UID to assign this user. If a user is to be created
+        with the specified UID and it is already taken by a different user on the local system then creation
+        of the home directory is refused. Note though, if after creating the home directory it is used on a
+        different system and the configured UID is taken by another user there, then
+        <command>systemd-homed</command> may assign the user a different UID on that system. The specified
+        UID must be outside of the system user range. It is recommended to use the 60001…60513 UID range for
+        this purpose. If not specified the UID is automatically picked. When logging in and the home
+        directory is found to be owned by a UID not matching the user's assigned one the home directory and
+        all files and directories inside it will have their ownership changed automatically before login
+        completes.</para>
+
+        <para>Note that users managed by <command>systemd-homed</command> always have a matching group
+        associated with the same name as well as a GID matching the UID of the user. Thus, configuring the
+        GID separately is not permitted.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--member-of=</option><replaceable>GROUP</replaceable></term>
+        <term><option>-G</option> <replaceable>GROUP</replaceable></term>
+
+        <listitem><para>Takes a comma-separated list of auxiliary UNIX groups this user shall belong
+        to. Example: <option>--member-of=wheel</option> to provide the user with administrator
+        privileges. Note that <command>systemd-homed</command> does not manage any groups besides a group
+        matching the user in name and numeric UID/GID. Thus any groups listed here must be registered
+        independently, for example with <citerefentry
+        project='man-pages'><refentrytitle>groupadd</refentrytitle><manvolnum>8</manvolnum></citerefentry>. If
+        non-existant groups that are listed there are ignored. This option may be used more than once, in
+        which case all specified group lists are combined.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--skel=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Takes a file system path to a directory. Specifies the skeleton directory to
+        initialize the home directory with. All files and directories in the specified are copied into any
+        newly create home directory. If not specified defaults to
+        <filename>/etc/skel/</filename>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--shell=</option><replaceable>SHELL</replaceable></term>
+
+        <listitem><para>Takes a file system path. Specifies the shell binary to execute on terminal
+        logins. If not specified defaults to <filename>/bin/bash</filename>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--setenv=</option><replaceable>VARIABLE</replaceable>=<replaceable>VALUE</replaceable></term>
+
+        <listitem><para>Takes an environment variable assignment to set for all user processes. Note that a
+        number of other settings also result in environment variables to be set for the user, including
+        <option>--email=</option>, <option>--timezone=</option> and <option>--language=</option>. May be used
+        multiple times to set multiple environment variables.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--timezone=</option><replaceable>TIMEZONE</replaceable></term>
+
+        <listitem><para>Takes a timezone specification as string that sets the timezone for the specified
+        user. Expects a `tzdata` location string. When the user logs in the <varname>$TZ</varname>
+        environment variable is initialized from this setting. Example:
+        <option>--timezone=Europe/Amsterdam</option> will result in the environment variable
+        <literal>TZ=:Europe/Amsterdam</literal>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--language=</option><replaceable>LANG</replaceable></term>
+
+        <listitem><para>Takes a specifier indicating the preferred language of the user. The
+        <varname>$LANG</varname> environment variable is initialized from this value on login, and thus a
+        value suitable for this environment variable is accepted here, for example
+        <option>--language=de_DE.UTF8</option></para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--ssh-authorized-keys=</option><replaceable>KEYS</replaceable></term>
+        <listitem><para>Either takes a SSH authorized key line to associate with the user record or a
+        <literal>@</literal> character followed by a path to a file to read one or more such lines from. SSH
+        keys configured this way are made available to SSH to permit access to this home directory and user
+        record. This option may be used more than once to configure multiple SSH keys.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--pkcs11-token-uri=</option><replaceable>URI</replaceable></term>
+        <listitem><para>Takes an RFC 7512 PKCS#11 URI referencing a security token (e.g. YubiKey or PIV
+        smartcard) that shall be able to unlock the user account. The security token URI should reference a
+        security token with exactly one pair of X.509 certificate and private key. A random secret key is
+        then generated, encrypted with the public key of the X.509 certificate, and stored as part of the
+        user record. At login time it is decrypted with the PKCS#11 module and then used to unlock the
+        account and associated resources. See below for an example how to set up authentication with security
+        token.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--locked=</option><replaceable>BOOLEAN</replaceable></term>
+
+        <listitem><para>Takes a boolean argument. Specifies whether this user account shall be locked. If
+        true logins into this account are prohibited, if false (the default) they are permitted (of course,
+        only if authorization otherwise succeeds).</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--not-before=</option><replaceable>TIMESTAMP</replaceable></term>
+        <term><option>--not-after=</option><replaceable>TIMESTAMP</replaceable></term>
+
+        <listitem><para>These options take a timestamp string, in the format documented in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> and
+        configures points in time before and after logins into this account are not
+        permitted.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--rate-limit-interval=</option><replaceable>SECS</replaceable></term>
+        <term><option>--rate-limit-burst=</option><replaceable>NUMBER</replaceable></term>
+
+        <listitem><para>Configures a rate limit on authentication attempts for this user. If the user
+        attempts to authenticate more often than the specified number, on a specific system, within the
+        specified time interval authentication is refused until the time interval passes. Defaults to 10
+        times per 1min.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--password-hint=</option><replaceable>TEXT</replaceable></term>
+
+        <listitem><para>Takes a password hint to store alongside the user record. This string is stored
+        accessible only to privileged users and the user itself and may not be queried by other users.
+        Example: <option>--password-hint="My first pet's name"</option></para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--enforce-password-policy=</option><replaceable>BOOL</replaceable></term>
+        <term><option>-P</option></term>
+
+        <listitem><para>Takes a boolean argument. Configures whether to enforce the system's password policy
+        for this user, regarding quality and strength of selected passwords. Defaults to
+        on. <option>-P</option> is short for
+        <option>---enforce-password-policy=no</option>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--password-change-now=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>Takes a boolean argument. If true the user is asked to change their password on next
+        login.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--password-change-min=</option><replaceable>TIME</replaceable></term>
+        <term><option>--password-change-max=</option><replaceable>TIME</replaceable></term>
+        <term><option>--password-change-warn=</option><replaceable>TIME</replaceable></term>
+        <term><option>--password-change-inactive=</option><replaceable>TIME</replaceable></term>
+
+        <listitem><para>Each of these options takes a time span specification as argument (in the syntax
+        documented in
+        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>5</manvolnum></citerefentry>) and
+        configure various aspects of the user's password expiration policy. Specifically,
+        <option>--password-change-min=</option> configures how much time has to pass after changing the
+        password of the user until the password may be changed again. If the user tries to change their
+        password before this time passes the attempt is refused. <option>--password-change-max=</option>
+        configures how much time has to pass after the the password is changed until the password expires and
+        needs to be changed again. After this time passes any attempts to log in may only proceed after the
+        password is changed. <option>--password-change-warn=</option> specifies how much earlier than then
+        the time configured with <option>--password-change-max=</option> the user is warned at login to
+        change their password as it will expire soon. Finally <option>--password-change-inactive=</option>
+        configures the time which has to pass after the password as expired until the user is not permitted
+        to log in or change the password anymore. Note that these options only apply to password
+        authentication, and do not apply to other forms of authentication, for example PKCS#11-based security
+        token authentication.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--disk-size=</option><replaceable>BYTES</replaceable></term>
+        <listitem><para>Either takes a size in bytes as argument (possibly using the usual K, M, G, …
+        suffixes for 1024 base values), or a percentage value and configures the disk space to assign to the
+        user. If a percentage value is specified (i.e. the argument suffixed with <literal>%</literal>) it is
+        taken relative to the available disk space of the backing file system. If the LUKS2 backend is used
+        this configures the size of the loopback file and file system contained therein. For the other
+        storage backends configures disk quota using the filesystem's native quota logic, if available. If
+        not specified, defaults to 85% of the available disk space for the LUKS2 backend and to no quota for
+        the others.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--access-mode=</option><replaceable>MODE</replaceable></term>
+
+        <listitem><para>Takes a UNIX file access mode written in octal. Configures the access mode of the
+        home directory itself. Note that this is only used when the directory is first created, and the user
+        may change this any time afterwards. Example:
+        <option>--access-mode=0700</option></para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--umask=</option><replaceable>MASK</replaceable></term>
+
+        <listitem><para>Takes the access mode mask (in octal syntax) to apply to newly created files and
+        directories of the user ("umask"). If set this controls the initial umask set for all login sessions of
+        the user, possibly overriding the system's defaults.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--nice=</option><replaceable>NICE</replaceable></term>
+
+        <listitem><para>Takes the numeric scheduling priority ("nice level") to apply to the processes of the user at login
+        time. Takes a numeric value in the range -20 (highest priority) to 19 (lowest priority).</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--rlimit=</option><replaceable>LIMIT</replaceable>=<replaceable>VALUE</replaceable><optional>:<replaceable>VALUE</replaceable></optional></term>
+
+        <listitem><para>Allows configuration of resource limits for processes of this user, see <citerefentry
+        project='man-pages'><refentrytitle>getrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+        for details. Takes a resource limit name (e.g. <literal>LIMIT_NOFILE</literal>) followed by an equal
+        sign, followed by a numeric limit. Optionally, separated by colon a second numeric limit may be
+        specified. If two are specified this refers to the soft and hard limits, respectively. If only one
+        limit is specified the setting sets both limits in one.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--tasks-max=</option><replaceable>TASKS</replaceable></term>
+
+        <listitem><para>Takes a non-zero unsigned integer as argument. Configures the maximum numer of tasks
+        (i.e. processes and threads) the user may have at any given time. This limit applies to all tasks
+        forked off the user's sessions, even if they change user identity via <citerefentry
+        project='man-pages'><refentrytitle>su</refentrytitle><manvolnum>1</manvolnum></citerefentry> or a
+        similar tool. Use <option>--rlimit=LIMIT_NPROC=</option> to place a limit on the tasks actually
+        running under the UID of the user, thus excluding any child processes that might have changed user
+        identity. This controls the <varname>TasksMax=</varname> settting of the per-user systemd slice unit
+        <filename>user-$UID.slice</filename>. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for further details.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--memory-high=</option><replaceable>BYTES</replaceable></term>
+        <term><option>--memory-max=</option><replaceable>BYTES</replaceable></term>
+
+        <listitem><para>Set a limit on the memory a user may take up on a system at any given time in bytes
+        (the usual K, M, G, … suffixes are supported, to the base of 1024). This includes all memory used by
+        the user itself and all processes they forked off that changed user credentials. This controls the
+        <varname>MemoryHigh=</varname> and <varname>MemoryMax=</varname> settings of the per-user systemd
+        slice unit <filename>user-$UID.slice</filename>. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for further details.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--cpu-weight=</option><replaceable>WEIGHT</replaceable></term>
+        <term><option>--io-weight=</option><replaceable>WEIGHT</replaceable></term>
+
+        <listitem><para>Set a CPU and IO scheduling weights of the processes of the user, including those of
+        processes forked off by the user that changed user credentials. Takes a numeric value in the range
+        1…10000. This controls the <varname>CPUWeight=</varname> and <varname>IOWeight=</varname> settings of
+        the per-user systemd slice unit <filename>user-$UID.slice</filename>. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for further details.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--storage=</option><replaceable>STORAGE</replaceable></term>
+
+        <listitem><para>Selects the storage mechanism to use for this home directory. Takes one of
+        <literal>luks</literal>, <literal>fscrypt</literal>, <literal>directory</literal>,
+        <literal>subvolume</literal>, <literal>cifs</literal>. For details about these mechanisms, see
+        above. If a new home directory is created and the storage type is not specifically specified defaults
+        to <literal>luks</literal> if supported, <literal>subvolume</literal> as first fallback if supported,
+        and <literal>directory</literal> if not.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--image-path=</option><replaceable>PATH</replaceable></term>
+
+        <listitem><para>Takes a file system path. Configures where to place the user's home directory. When
+        LUKS2 storage is used refers to the path to the loopback file, otherwise to the path to the home
+        directory. When unspecified defaults to <filename>/home/$USER.home</filename> when LUKS storage is
+        used and <filename>/home/$USER.homedir</filename> for the other storage mechanisms. Not defined for
+        the <literal>cifs</literal> storage mechanism. To use LUKS2 storage on a regular block device (for
+        example a USB stick) pass the path to the block device here.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--fs-type=</option><replaceable>TYPE</replaceable></term>
+
+        <listitem><para>When LUKS2 storage is used configures the file system type to use inside the home
+        directory LUKS2 container. One of <literal>ext4</literal>, <literal>xfs</literal>,
+        <literal>btrfs</literal>. If not specified defaults to <literal>ext4</literal>. Note that
+        <literal>xfs</literal> is not recommended as its support for file system resizing is too
+        limited.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--luks-discard=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>When LUKS2 storage is used configures whether to enable the
+        <literal>discard</literal> feature of the file system. If enabled the file system on top of the LUKS2
+        volume will report empty block information to LUKS2 and the loopback file below, ensuring that empty
+        space in the home directory is returned to the backing file system below the LUKS2 volume, resulting
+        in a "sparse" loopback file. This option mostly defaults to off, since this permits over-committing
+        home directories which results in I/O errors if the underlying file system runs full while the upper
+        file system wants to allocate a block. Such I/O errors are generally not handled well by file systems
+        nor applications. When LUKS2 storage is used on top of regular block devices (instead of on top a
+        loopback file) the discard logic defaults to on.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--luks-cipher=</option><replaceable>CIPHER</replaceable></term>
+        <term><option>--luks-cipher-mode=</option><replaceable>MODE</replaceable></term>
+        <term><option>--luks-volume-key-size=</option><replaceable>BITS</replaceable></term>
+        <term><option>--luks-pbkdf-type=</option><replaceable>TYPE</replaceable></term>
+        <term><option>--luks-pbkdf-hash-algorithm=</option><replaceable>ALGORITHM</replaceable></term>
+        <term><option>--luks-pbkdf-time-cost=</option><replaceable>SECONDS</replaceable></term>
+        <term><option>--luks-pbkdf-memory-cost=</option><replaceable>BYTES</replaceable></term>
+        <term><option>--luks-pbkdf-parallel-threads=</option><replaceable>THREADS</replaceable></term>
+
+        <listitem><para>Configures various cryptographic parameters for the LUKS2 storage mechanism. See
+        <citerefentry
+        project='man-pages'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for details on the specific attributes.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--nosuid=</option><replaceable>BOOL</replaceable></term>
+        <term><option>--nodev=</option><replaceable>BOOL</replaceable></term>
+        <term><option>--noexec=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>Configures the <literal>nosuid</literal>, <literal>nodev</literal> and
+        <literal>noexec</literal> mount options for the home directories. By default <literal>nodev</literal>
+        and <literal>nosuid</literal> are on, while <literal>noexec</literal> is off. For details about these
+        mount options see <citerefentry
+        project='man-pages'><refentrytitle>mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--cifs-domain=</option><replaceable>DOMAIN</replaceable></term>
+        <term><option>--cifs-user-name=</option><replaceable>USER</replaceable></term>
+        <term><option>--cifs-service=</option><replaceable>SERVICE</replaceable></term>
+
+        <listitem><para>Configures the Windows File Sharing (CIFS) domain and user to associate with the home
+        directory/user account, as well as the file share ("service") to mount as directory. The latter is used when
+        <literal>cifs</literal> storage is selected.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--stop-delay=</option><replaceable>SECS</replaceable></term>
+
+        <listitem><para>Configures the time the per-user service manager shall continue to run after the all
+        sessions of the user ended. The default is configured in
+        <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry> (for
+        home directories of LUKS2 storage located on removable media this defaults to 0 though). A longer
+        time makes sure quick, repetitive logins are more efficient as the user's service manager doesn't
+        have to be started every time.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--kill-processes=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>Configures whether to kill all processes of the user on logout. The default is
+        configured in
+        <citerefentry><refentrytitle>logind.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--auto-login=</option><replaceable>BOOL</replaceable></term>
+
+        <listitem><para>Takes a boolean argument. Configures whether the graphical UI of the system should
+        automatically log this user in if possible. Defaults to off. If less or more than one user is marked
+        this way automatic login is disabled.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Commands</title>
+
+    <para>The following commands are understood:</para>
+
+    <variablelist>
+
+      <varlistentry>
+        <term><command>list</command></term>
+
+        <listitem><para>List all home directories (along with brief details) currently managed by
+        <filename>systemd-homed.service</filename>. This command is also executed if none is specified on the
+        command line. (Note that the list of users shown by this command does not include users managed by
+        other subsystems, such as system users or any traditional users listed in
+        <filename>/etc/passwd</filename>.)</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>activate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
+
+        <listitem><para>Activate one or more home directories. The home directories of each listed user will
+        be activated and made available under their mount points (typically in
+        <filename>/home/$USER</filename>). Note that any home activated this way stays active indefinitely,
+        until it is explicitly deactivated again (with <command>deactivate</command>, see below), or the user
+        logs in and out again and it thus is deactivated due to the automatic deactivation-on-logout
+        logic.</para>
+
+        <para>Activation of a home directory involves various operations that depend on the selected storage
+        mechanism. If the LUKS2 mechanism is used, this generally involves: inquiring the user for a
+        password, setting up a loopback device, validating and activating the LUKS2 volume, checking the file
+        system, mounting the file system, and potentiatlly changing the ownership of all included files to
+        the correct UID/GID.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>deactivate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
+
+        <listitem><para>Deactivate one or more home directories. This undoes the effect of
+        <command>activate</command>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>inspect</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
+
+        <listitem><para>Show various details about the specified home directories. This shows various
+        information about the home directory and its user account, including runtime data such as current
+        state, disk use and similar. Combine with <option>--json=</option> to show the detailed JSON user
+        record instead, possibly combined with <option>--export-format=</option> to suppress certain aspects
+        of the output.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>authenticate</command> <replaceable>USER</replaceable> [<replaceable>USER…</replaceable>]</term>
+
+        <listitem><para>Validate authentication credentials of a home directory. This queries the caller for
+        a password (or similar) and checks that it correctly unlocks the home directory. This leaves the home
+        directory in the state it is in, i.e. it leaves the home directory in inactive state if it was
+        inactive before, and in active state if it was active before.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>create</command> <replaceable>USER</replaceable></term>
+        <term><command>create</command> <option>--identity=</option><replaceable>PATH</replaceable> <optional><replaceable>USER</replaceable></optional></term>
+
+        <listitem><para>Create a new home directory/user account of the specified name. Use the various
+        user record property options (as documented above) to control various aspects of the home directory
+        and its user accounts.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>remove</command> <replaceable>USER</replaceable></term>
+
+        <listitem><para>Remove a home directory/user account. This will remove both the home directory's user
+        record and the home directory itself, and thus delete all files and directories owned by the
+        user.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>update</command> <replaceable>USER</replaceable></term>
+        <term><command>update</command> <option>--identity=</option><replaceable>PATH</replaceable> <optional><replaceable>USER</replaceable></optional></term>
+
+        <listitem><para>Update a home directory/user account. Use the various user record property options
+        (as documented above) to make changes to the account, or alternatively provide a full, updated JSON
+        user record via the <option>--identity=</option> option.</para>
+
+        <para>Note that changes to user records not signed by a cryptographic private key available locally
+        are not permitted, unless <option>--identity=</option> is used with a user record that is already
+        correctly signed by a recognized private key.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>passwd</command> <replaceable>USER</replaceable></term>
+
+        <listitem><para>Change the password of the specified home direcory/user account.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>resize</command> <replaceable>USER</replaceable> <replaceable>BYTES</replaceable></term>
+
+        <listitem><para>Change the disk space assigned to the specified home directory. If the LUKS2 storage
+        mechanism is used this will automatically resize the loopback file and the file system contained
+        within. Note that if <literal>ext4</literal> is used inside of the LUKS2 volume, it is necessary to
+        deactivate the home directory before shrinking it (i.e the user has to log out). Growing can be done
+        while the home directory is active. If <literal>xfs</literal> is used inside of the LUKS2 volume the
+        home directory may not be shrunk whatsoever. On all three of <literal>ext4</literal>,
+        <literal>xfs</literal> and <literal>btrfs</literal> the home directory may be grown while the user is
+        logged in, and on the latter also shrunk while the user is logged in. If the
+        <literal>subvolume</literal>, <literal>directory</literal>, <literal>fscrypt</literal> storage
+        mechanisms are used, resizing will change file system quota.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>lock</command> <replaceable>USER</replaceable></term>
+
+        <listitem><para>Temporarily suspend access to the user's home directory and remove any associated
+        cryptographic keys from memory. Any attempts to access the user's home directory will stall until the
+        home directory is unlocked again (i.e. re-authenticated). This functionality is primarily intended to
+        be used during system suspend to make sure the user's data cannot be accessed until the user
+        re-authenticates on resume. This operation is only defined for home directories that use the LUKS2
+        storage mechanism.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>unlock</command> <replaceable>USER</replaceable></term>
+
+        <listitem><para>Resume access to the user's home directory again, undoing the effect of
+        <command>lock</command> above. This requires authentication of the user, as the cryptographic keys
+        required for access to the home directory need to be reacquired.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>lock-all</command></term>
+
+        <listitem><para>Execute the <command>lock</command> command on all suitable home directories at
+        once. This operation is generally executed on system suspend (i.e. by <command>systemctl
+        suspend</command> and related commands), to ensure all active user's cryptographic keys for accessing
+        their home directories are removed from memory.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><command>with</command> <replaceable>USER</replaceable> <replaceable>COMMAND…</replaceable></term>
+
+        <listitem><para>Activate the specified user's home directory, run the specified command (under the
+        caller's identity, not the specified user's) and deactivate the home directory afterwards again
+        (unless the user is logged in otherwise). This command is useful for running privileged backup
+        scripts and such, but requires authentication with the user's credentials in order to be able to
+        unlock the user's home directory.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Exit status</title>
+
+    <para>On success, 0 is returned, a non-zero failure code otherwise.</para>
+  </refsect1>
+
+  <xi:include href="less-variables.xml" />
+
+  <refsect1>
+    <title>Examples</title>
+
+    <example>
+      <title>Create a user <literal>waldo</literal> in the administrator group <literal>wheel</literal>, and
+      assign 500 MiB disk space to them.</title>
+
+      <programlisting>homectl create waldo --real-name="Waldo McWaldo" -G wheel --disk-size=500M</programlisting>
+    </example>
+
+    <example>
+      <title>Create a user <literal>wally</literal> on a USB stick, and assign a maximum of 500 concurrent
+      tasks to them.</title>
+
+      <programlisting>homectl create wally --real-name="Wally McWally" --image-path=/dev/disk/by-id/usb-SanDisk_Ultra_Fit_476fff954b2b5c44-0:0 --tasks-max=500</programlisting>
+    </example>
+
+    <example>
+      <title>Change nice level of user <literal>odlaw</literal> to +5 and make sure the environment variable
+      <varname>$SOME</varname> is set to the string <literal>THING</literal> for them on login.</title>
+
+      <programlisting>homectl update odlaw --nice=5 --setenv=SOME=THING</programlisting>
+    </example>
+
+    <example>
+      <title>Set up authentication with a YubiKey security token:</title>
+
+      <programlisting># Clear the Yubikey from any old keys (careful!)
+ykman piv reset
+
+# Generate a new private/public key pair on the device, store the public key in 'pubkey.pem'.
+ykman piv generate-key -a RSA2048 9d pubkey.pem
+
+# Create a self-signed certificate from this public key, and store it on the device.
+ykman piv generate-certificate --subject "Knobelei" 9d pubkey.pem
+
+# We don't need the publibc key on disk anymore
+rm pubkey.pem
+
+# Check if the newly create key on the Yubikey shows up as token in PKCS#11. Have a look at the output, and
+# copy the resulting token URI to the clipboard.
+p11tool --list-tokens
+
+# Allow the security token referenced by the determined PKCS#11 URI to unlock the account of user
+# 'lafcadio'. (Replace the '…' by the URI from the clipboard.)
+homectl update lafcadio --pkcs11-token-uri=…</programlisting>
+    </example>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>useradd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index f6703b06d65c9f7b7cafec725d9132ba0fab6221..46b22be699358ba777b0bf26fc22d27322dd3fa3 100644 (file)
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--namespace=<replaceable>NAMESPACE</replaceable></option></term>
+
+        <listitem><para>Takes a journal namespace identifier string as argument. If not specified the data
+        collected by the default namespace is shown. If specified shows the log data of the specified
+        namespace instead. If the namespace is specified as <literal>*</literal> data from all namespaces is
+        shown, interleaved. If the namespace identifier is prefixed with <literal>+</literal> data from the
+        specified namespace and the default namespace is shown, interleaved, but no other. For details about
+        journal namespaces see
+        <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--header</option></term>
 
index d16cb185803a1d1611e2fb2fca84b4ef5b317db0..afbbe64bb85f2dabe519a9cf6c0e2a5210f22a71 100644 (file)
@@ -18,6 +18,7 @@
   <refnamediv>
     <refname>journald.conf</refname>
     <refname>journald.conf.d</refname>
+    <refname>journald@.conf</refname>
     <refpurpose>Journal service configuration files</refpurpose>
   </refnamediv>
 
@@ -26,6 +27,7 @@
     <para><filename>/etc/systemd/journald.conf.d/*.conf</filename></para>
     <para><filename>/run/systemd/journald.conf.d/*.conf</filename></para>
     <para><filename>/usr/lib/systemd/journald.conf.d/*.conf</filename></para>
+    <para><filename>/etc/systemd/journald@<replaceable>NAMESPACE</replaceable>.conf</filename></para>
   </refsynopsisdiv>
 
   <refsect1>
     <citerefentry><refentrytitle>systemd.syntax</refentrytitle><manvolnum>5</manvolnum></citerefentry>
     for a general description of the syntax.</para>
 
+    <para>The <command>systemd-journald</command> instance managing the default namespace is configured by
+    <filename>/etc/systemd/journald.conf</filename> and associated drop-ins. Instances managing other
+    namespaces read <filename>/etc/systemd/journald@<replaceable>NAMESPACE</replaceable>.conf</filename> with
+    the namespace identifier filled in. This allows each namespace to carry a distinct configuration. See
+    <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    for details about journal namespaces.</para>
   </refsect1>
 
   <xi:include href="standard-conf.xml" xpointer="main-conf" />
       <varlistentry>
         <term><varname>Storage=</varname></term>
 
-        <listitem><para>Controls where to store journal data. One of
-        <literal>volatile</literal>,
-        <literal>persistent</literal>,
-        <literal>auto</literal> and
-        <literal>none</literal>. If
-        <literal>volatile</literal>, journal
-        log data will be stored only in memory, i.e. below the
-        <filename>/run/log/journal</filename> hierarchy (which is
-        created if needed). If <literal>persistent</literal>, data
-        will be stored preferably on disk, i.e. below the
-        <filename>/var/log/journal</filename> hierarchy (which is
-        created if needed), with a fallback to
-        <filename>/run/log/journal</filename> (which is created if
-        needed), during early boot and if the disk is not writable.
-        <literal>auto</literal> is similar to
-        <literal>persistent</literal> but the directory
-        <filename>/var/log/journal</filename> is not created if
-        needed, so that its existence controls where log data goes.
-        <literal>none</literal> turns off all storage, all log data
-        received will be dropped. Forwarding to other targets, such as
-        the console, the kernel log buffer, or a syslog socket will
-        still work however. Defaults to
-        <literal>auto</literal>.</para></listitem>
+        <listitem><para>Controls where to store journal data. One of <literal>volatile</literal>,
+        <literal>persistent</literal>, <literal>auto</literal> and <literal>none</literal>. If
+        <literal>volatile</literal>, journal log data will be stored only in memory, i.e. below the
+        <filename>/run/log/journal</filename> hierarchy (which is created if needed). If
+        <literal>persistent</literal>, data will be stored preferably on disk, i.e. below the
+        <filename>/var/log/journal</filename> hierarchy (which is created if needed), with a fallback to
+        <filename>/run/log/journal</filename> (which is created if needed), during early boot and if the disk
+        is not writable.  <literal>auto</literal> is similar to <literal>persistent</literal> but the
+        directory <filename>/var/log/journal</filename> is not created if needed, so that its existence
+        controls where log data goes.  <literal>none</literal> turns off all storage, all log data received
+        will be dropped. Forwarding to other targets, such as the console, the kernel log buffer, or a syslog
+        socket will still work however. Defaults to <literal>auto</literal> in the default journal namespace,
+        and <literal>persistent</literal> in all others.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <term><varname>MaxLevelWall=</varname></term>
 
         <listitem><para>Controls the maximum log level of messages
-        that are stored on disk, forwarded to syslog, kmsg, the
+        that are stored in the journal, forwarded to syslog, kmsg, the
         console or wall (if that is enabled, see above). As argument,
         takes one of
         <literal>emerg</literal>,
         are stored/forwarded, messages above are dropped. Defaults to
         <literal>debug</literal> for <varname>MaxLevelStore=</varname>
         and <varname>MaxLevelSyslog=</varname>, to ensure that the all
-        messages are written to disk and forwarded to syslog. Defaults
-        to
+        messages are stored in the journal and forwarded to syslog.
+        Defaults to
         <literal>notice</literal> for <varname>MaxLevelKMsg=</varname>,
         <literal>info</literal> for <varname>MaxLevelConsole=</varname>,
         and <literal>emerg</literal> for
       <varlistentry>
         <term><varname>ReadKMsg=</varname></term>
 
-        <listitem><para>Takes a boolean value. If enabled (the
-        default), journal reads <filename>/dev/kmsg</filename>
-        messages generated by the kernel.</para></listitem>
+        <listitem><para>Takes a boolean value. If enabled <command>systemd-journal</command> processes
+        <filename>/dev/kmsg</filename> messages generated by the kernel. In the default journal namespace
+        this option is enabled by default, it is disabled in all others.</para></listitem>
       </varlistentry>
 
       <varlistentry>
diff --git a/man/pam_systemd_home.xml b/man/pam_systemd_home.xml
new file mode 100644 (file)
index 0000000..6dc1a83
--- /dev/null
@@ -0,0 +1,131 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="pam_systemd_home" conditional='ENABLE_PAM_HOME'>
+
+  <refentryinfo>
+    <title>pam_systemd_home</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>pam_systemd_home</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>pam_systemd_home</refname>
+    <refpurpose>Automatically mount home directories managed by <filename>systemd-homed.service</filename> on
+    login, and unmount them on logout</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><filename>pam_systemd_home.so</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>pam_systemd_home</command> ensures that home directories managed by
+    <citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    are automatically activated (mounted) on user login, and are deactivated (unmounted) when the last
+    session of the user ends.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>The following options are understood:</para>
+
+    <variablelist class='pam-directives'>
+
+      <varlistentry>
+        <term><varname>suspend=</varname></term>
+
+        <listitem><para>Takes a boolean argument. If true, the home directory of the user will be suspended
+        automatically during system suspend; if false it will remain active. Automatic suspending of the home
+        directory improves security substantially as secret key material is automatically removed from memory
+        before the system is put to sleep and must be re-acquired (through user re-authentication) when
+        coming back from suspend. It is recommended to set this parameter for all PAM applications that have
+        support for automatically re-authenticating via PAM on system resume. If multiple sessions of the
+        same user are open in parallel the user's home directory will be left unsuspended on system suspend
+        as long as at least one of the sessions does not set this parameter. Defaults to
+        off.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>debug</varname><optional>=</optional></term>
+
+        <listitem><para>Takes an optional boolean argument. If yes or without the argument, the module will log
+        debugging information as it operates.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Module Types Provided</title>
+
+    <para>The module provides all four management operations: <option>auth</option>, <option>account</option>,
+    <option>session</option>, <option>password</option>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Environment</title>
+
+    <para>The following environment variables are initialized by the module and available to the processes of the
+    user's session:</para>
+
+    <variablelist class='environment-variables'>
+      <varlistentry>
+        <term><varname>$SYSTEMD_HOME=1</varname></term>
+
+        <listitem><para>Indicates that the user's home directory is managed by <filename>systemd-homed.service</filename>.</para></listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Example</title>
+
+    <para>Here's an example PAM configuration fragment that permits users managed by
+    <filename>systemd-homed.service</filename> to log in:</para>
+
+    <programlisting>#%PAM-1.0
+auth      sufficient pam_unix.so
+-auth     sufficient pam_systemd_home.so
+auth      required   pam_deny.so
+
+account   required   pam_nologin.so
+-account  sufficient pam_systemd_home.so
+account   sufficient pam_unix.so
+account   required   pam_permit.so
+
+-password sufficient pam_systemd_home.so
+password  sufficient pam_unix.so sha512 shadow try_first_pass try_authtok
+password  required   pam_deny.so
+
+-session  optional   pam_keyinit.so revoke
+-session  optional   pam_loginuid.so
+-session  optional   pam_systemd_home.so
+-session  optional   pam_systemd.so
+session   required   pam_unix.so</programlisting>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-homed.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>homed.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>pam_systemd</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>pam.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>pam.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>pam</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
diff --git a/man/repart.d.xml b/man/repart.d.xml
new file mode 100644 (file)
index 0000000..4993608
--- /dev/null
@@ -0,0 +1,388 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<refentry id="repart.d" conditional='ENABLE_REPART'>
+
+  <refentryinfo>
+    <title>repart.d</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>repart.d</refentrytitle>
+    <manvolnum>5</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>repart.d</refname>
+    <refpurpose>Partition Definition Files for Automatic Boot-Time Repartitioning</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><literallayout><filename>/etc/repart.d/*.conf</filename>
+<filename>/run/repart.d/*.conf</filename>
+<filename>/usr/lib/repart.d/*.conf</filename>
+    </literallayout></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><filename>repart.d/*.conf</filename> files describe basic properties of partitions of block
+    devices of the local system. They may be used to declare types, names and sizes of partitions that shall
+    exist. The
+    <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    service reads these files and attempts to add new partitions currently missing and enlarge existing
+    partitions according to these definitions. Operation is generally incremental, i.e. when applied, what
+    exists already is left intact, and partitions are never shrunk, moved or deleted.</para>
+
+    <para>These definition files are useful for implementing operating system images that are prepared and
+    delivered with minimally sized images (for example lacking any state or swap partitions), and which on
+    first boot automatically take possession of any remaining disk space following a few basic rules.</para>
+
+    <para>Currently, support for partition definition files is only implemented for GPT partitition
+    tables.</para>
+
+    <para>Partition files are generally matched against any partitions already existing on disk in a simple
+    algorithm: the partition files are sorted by their filename (ignoring the directory prefix), and then
+    compared in order against existing partitions matching the same partition type UUID. Specifically, the
+    first existing partition with a specific partition type UUID is assigned the first definition file with
+    the same partition type UUID, and the second existing partition with a specific type UUID the second
+    partition file with the same type UUID, and so on. Any left-over partition files that have no matching
+    existing partition are assumed to define new partition that shall be created. Such partitions are
+    appended to the end of the partition table, in the order defined by their names utilizing the first
+    partition slot greater than the highest slot number currently in use. Any existing partitions that have
+    no matching partition file are left as they are.</para>
+
+    <para>Note that these partition definition files do not describe the contents of the partitions, such as
+    the file system used. Separate mechanisms, such as
+    <citerefentry><refentrytitle>systemd-growfs</refentrytitle><manvolnum>8</manvolnum></citerefentry> and
+    <command>systemd-makefs</command> maybe be used to initialize or grow the file systems inside of these
+    partitions.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>[Partition] Section Options</title>
+
+    <variablelist>
+      <varlistentry>
+        <term><varname>Type=</varname></term>
+
+        <listitem><para>The GPT partition type UUID to match. This may be a GPT partition type UUID such as
+        <constant>4f68bce3-e8cd-4db1-96e7-fbcaf984b709</constant>, or one of the following special
+        identifiers:</para>
+
+        <table>
+          <title>GPT partition type identifiers</title>
+
+          <tgroup cols='2' align='left' colsep='1' rowsep='1'>
+            <colspec colname="name" />
+            <colspec colname="explanation" />
+
+            <thead>
+              <row>
+                <entry>Identifier</entry>
+                <entry>Explanation</entry>
+              </row>
+            </thead>
+
+            <tbody>
+              <row>
+                <entry><constant>esp</constant></entry>
+                <entry>EFI System Partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>xbootldr</constant></entry>
+                <entry>Extended Boot Loader Partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>swap</constant></entry>
+                <entry>Swap partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>home</constant></entry>
+                <entry>Home (<filename>/home/</filename>) partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>srv</constant></entry>
+                <entry>Server data (<filename>/srv/</filename>) partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>var</constant></entry>
+                <entry>Variable data (<filename>/var/</filename>) partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>tmp</constant></entry>
+                <entry>Temporary data (<filename>/var/tmp/</filename>) partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>linux-generic</constant></entry>
+                <entry>Generic Linux file system partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>root</constant></entry>
+                <entry>Root file system partition type appropriate for the local architecture (an alias for an architecture root file system partition type listed below, e.g. <constant>root-x86-64</constant>)</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-verity</constant></entry>
+                <entry>Verity data for the root file system partition for the local architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-secondary</constant></entry>
+                <entry>Root file system partition of the secondary architecture of the local architecture; usually the matching 32bit architecture for the local 64bit architecture)</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-secondary-verity</constant></entry>
+                <entry>Verity data for the root file system partition of the secondary architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-x86</constant></entry>
+                <entry>Root file system partition for the x86 (32bit, aka i386) architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-x86-verity</constant></entry>
+                <entry>Verity data for the x86 (32bit) root file system partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-x86-64</constant></entry>
+                <entry>Root file system partition for the x86_64 (64bit, aka amd64) architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-x86-64-verity</constant></entry>
+                <entry>Verity data for the x86_64 (64bit) root file system partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-arm</constant></entry>
+                <entry>Root file system partition for the ARM (32bit) architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-arm-verity</constant></entry>
+                <entry>Verity data for the ARM (32bit) root file system partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-arm64</constant></entry>
+                <entry>Root file system partition for the ARM (64bit, aka aarch64) architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-arm64-verity</constant></entry>
+                <entry>Verity data for the ARM (64bit, aka aarch64) root file system partition</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-ia64</constant></entry>
+                <entry>Root file system partition for the ia64 architecture</entry>
+              </row>
+
+              <row>
+                <entry><constant>root-ia64-verity</constant></entry>
+                <entry>Verity data for the ia64 root file system partition</entry>
+              </row>
+            </tbody>
+          </tgroup>
+        </table>
+
+        <para>This setting defaults to <constant>linux-generic</constant>.</para>
+
+        <para>Most of the partition type UUIDs listed above are defined in the <ulink
+        url="https://systemd.io/DISCOVERABLE_PARTITIONS">Discoverable Partitions
+        Specification</ulink>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>Label=</varname></term>
+
+        <listitem><para>The textual label to assign to the partition if none is assigned yet. Note that this
+        setting is not used for matching. It is also not used when a label is already set for an existing
+        partition. It is thus only used when a partition is newly created or when an existing one had a no
+        label set (that is: an empty label). If not specified a label derived from the partition type is
+        automatically used.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>Priority=</varname></term>
+
+        <listitem><para>A numeric priority to assign to this partition, in the range -2147483648…2147483647,
+        with smaller values indicating higher priority, and higher values indicating smaller priority. This
+        priority is used in case the configured size constraints on the defined partitions do not permit
+        fitting all partitions onto the available disk space. If the partitions do not fit, the highest
+        numeric partition priority of all defined partitions is determined, and all defined partitions with
+        this priority are removed from the list of new partitions to create (which may be multiple, if the
+        same priority is used for multiple partitions). The fitting algorithm is then tried again. If the
+        partitions still do not fit, the now highest numeric partition priority is determined, and the
+        matching partitions removed too, and so on. Partitions of a priority of 0 or lower are never
+        removed. If all partitions with a priority above 0 are removed and the partitions still do not fit on
+        the device the operation fails. Note that this priority has no effect on ordering partitions, for
+        that use the alphabetical order of the filenames of the partition definition files. Defaults to
+        0.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>Weight=</varname></term>
+
+        <listitem><para>A numeric weight to assign to this partition in the range 0…1000000. Available disk
+        space is assigned the defined partitions according to their relative weights (subject to the size
+        constraints configured with <varname>SizeMinBytes=</varname>, <varname>SizeMaxBytes=</varname>), so
+        that a partition with weight 2000 gets double the space as one with weight 1000, and a partition with
+        weight 333 a third of that. Defaults to 1000.</para>
+
+        <para>The <varname>Weight=</varname> setting is used to distribute available disk space in an
+        "elastic" fashion, based on the disk size and existing partitions. If a partition shall have a fixed
+        size use both <varname>SizeMinBytes=</varname> and <varname>SizeMaxBytes=</varname> with the same
+        value in order to fixate the size to one value, in which case the weight has no
+        effect.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>PaddingWeight=</varname></term>
+
+        <listitem><para>Similar to <varname>Weight=</varname> but sets a weight for the free space after the
+        partition (the "padding"). When distributing available space the weights of all partitions and all
+        defined padding is summed, and then each partition and padding gets the fraction defined by its
+        weight. Defaults to 0, i.e. by default no padding is applied.</para>
+
+        <para>Padding is useful if empty space shall be left for later additions or a safety margin at the
+        end of the device or between partitions.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>SizeMinBytes=</varname></term>
+        <term><varname>SizeMaxBytes=</varname></term>
+
+        <listitem><para>Specifies minimum and maximum size constraints in bytes. Takes the usual K, M, G, T,
+        …  suffixes (to the base of 1024). If <varname>SizeMinBytes=</varname> is specified the partition is
+        created at or grown to at least the specified size. If <varname>SizeMaxBytes=</varname> is specified
+        the partition is created at or grown to at most the specified size. The precise size is determined
+        through the weight value value configured with <varname>Weight=</varname>, see above. When
+        <varname>SizeMinBytes=</varname> is set equal to <varname>SizeMaxBytes=</varname> the configured
+        weight has no effect as the partition is explicitly sized to the specified fixed value. Note that
+        partitions are never created smaller than 4096 bytes, and since partitions are never shrunk the
+        previous size of the partition (in case the partition already exists) is also enforced as lower bound
+        for the new size. The values should be specified as multiples of 4096 bytes, and are rounded upwards
+        (in case of <varname>SizeMinBytes=</varname>) or downwards (in case of
+        <varname>SizeMaxBytes=</varname>) otherwise. If the backing device does not provide enough space to
+        fulfill the constraints placing the partition will fail. For partitions that shall be created,
+        depending on the setting of <varname>Priority=</varname> (see above) the partition might be dropped
+        and the placing algorithm restarted. By default no size constraints are set.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>PaddingMinBytes=</varname></term>
+        <term><varname>PaddingMaxBytes=</varname></term>
+
+        <listitem><para>Specifies minimum and maximum size constrains in bytes for the free space after the
+        partition (the "padding"). Semantics are similar to <varname>SizeMinBytes=</varname> and
+        <varname>SizeMaxBytes=</varname>, except that unlike partition sizes free space can be shrunk and can
+        be as small as zero. By default no size constraints on padding are set, so that only
+        <varname>PaddingWeight=</varname> determines the size of the padding applied.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>FactoryReset=</varname></term>
+
+        <listitem><para>Takes a boolean argument. If specified the partition is marked for removal during a
+        factory reset operation. This functionality is useful to implement schemes where images can be reset
+        into their original state by removing partitions and creating them anew. Defaults to off.</para></listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Examples</title>
+
+    <example>
+      <title>Grow the root partition to the full disk size at first boot</title>
+
+      <para>With the following file the root partition is automatically grown to the full disk if possible during boot.</para>
+
+      <para><programlisting># /usr/lib/repart.d/50-root.conf
+[Partition]
+Type=root
+</programlisting></para>
+    </example>
+
+    <example>
+      <title>Create a swap and home partition automatically on boot, if missing</title>
+
+      <para>The home partition gets all available disk space while the swap partition gets 1G at most and 64M
+      at least. We set a priority > 0 on the swap partition to ensure the swap partition is not used if not
+      enough space is available. For every three bytes assigned to the home partition the swap partition gets
+      assigned one.</para>
+
+      <para><programlisting># /usr/lib/repart.d/60-home.conf
+[Partition]
+Type=home
+</programlisting></para>
+
+      <para><programlisting># /usr/lib/repart.d/70-swap.conf
+[Partition]
+Type=swap
+SizeMinBytes=64M
+SizeMaxBytes=1G
+Priority=1
+Weight=333
+</programlisting></para>
+    </example>
+
+    <example>
+      <title>Create B partitions in an A/B Verity setup, if missing</title>
+
+      <para>Let's say the vendor intends to update OS images in an A/B setup, i.e. with two root partitions
+      (and two matching Verity partitions) that shall be used alternatingly during upgrades. To minimize
+      image sizes the original image is shipped only with one root and one Verity partition (the "A" set),
+      and the second root and Verity partitions (the "B" set) shall be created on first boot on the free
+      space on the medium.</para>
+
+      <para><programlisting># /usr/lib/repart.d/50-root.conf
+[Partition]
+Type=root
+SizeMinBytes=512M
+SizeMaxBytes=512M
+</programlisting></para>
+
+      <para><programlisting># /usr/lib/repart.d/60-root-verity.conf
+[Partition]
+Type=root-verity
+SizeMinBytes=64M
+SizeMaxBytes=64M
+</programlisting></para>
+
+      <para>The definitions above cover the "A" set of root partition (of a fixed 512M size) and Verity
+      partition for the root partition (of a fixed 64M size). Let's use symlinks to create the "B" set of
+      partitions, since after all they shall have the same properties and sizes as the "A" set.</para>
+
+<para><programlisting># ln -s 50-root.conf /usr/lib/repart.d/70-root-b.conf
+# ln -s 60-root-verity.conf /usr/lib/repart.d/80-root-verity-b.conf
+</programlisting></para>
+    </example>
+
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>sfdisk</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index 90376da77592c40eb281486aff341f816d17cd0b..591d7c48b5ad02c2ae78573a8a5c9da5af5080f8 100644 (file)
@@ -17,13 +17,14 @@ manpages = [
  ['environment.d', '5', [], 'ENABLE_ENVIRONMENT_D'],
  ['file-hierarchy', '7', [], ''],
  ['halt', '8', ['poweroff', 'reboot'], ''],
+ ['homectl', '1', [], 'ENABLE_HOMED'],
  ['hostname', '5', [], ''],
  ['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'],
  ['hwdb', '7', [], 'ENABLE_HWDB'],
  ['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'],
  ['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'],
  ['journalctl', '1', [], ''],
- ['journald.conf', '5', ['journald.conf.d'], ''],
+ ['journald.conf', '5', ['journald.conf.d', 'journald@.conf'], ''],
  ['kernel-command-line', '7', [], ''],
  ['kernel-install', '8', [], ''],
  ['libudev', '3', [], ''],
@@ -45,8 +46,10 @@ manpages = [
  ['nss-systemd', '8', ['libnss_systemd.so.2'], 'ENABLE_NSS_SYSTEMD'],
  ['os-release', '5', [], ''],
  ['pam_systemd', '8', [], 'HAVE_PAM'],
+ ['pam_systemd_home', '8', [], 'ENABLE_PAM_HOME'],
  ['portablectl', '1', [], 'ENABLE_PORTABLED'],
  ['pstore.conf', '5', ['pstore.conf.d'], 'ENABLE_PSTORE'],
+ ['repart.d', '5', [], 'ENABLE_REPART'],
  ['resolvectl', '1', ['resolvconf'], 'ENABLE_RESOLVE'],
  ['resolved.conf', '5', ['resolved.conf.d'], 'ENABLE_RESOLVE'],
  ['runlevel', '8', [], ''],
@@ -230,6 +233,7 @@ manpages = [
   ''],
  ['sd_bus_message_append_strv', '3', [], ''],
  ['sd_bus_message_copy', '3', [], ''],
+ ['sd_bus_message_dump', '3', [], ''],
  ['sd_bus_message_get_cookie', '3', ['sd_bus_message_get_reply_cookie'], ''],
  ['sd_bus_message_get_monotonic_usec',
   '3',
@@ -547,7 +551,9 @@ manpages = [
   ''],
  ['sd_journal_open',
   '3',
-  ['SD_JOURNAL_CURRENT_USER',
+  ['SD_JOURNAL_ALL_NAMESPACES',
+   'SD_JOURNAL_CURRENT_USER',
+   'SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE',
    'SD_JOURNAL_LOCAL_ONLY',
    'SD_JOURNAL_OS_ROOT',
    'SD_JOURNAL_RUNTIME_ONLY',
@@ -707,6 +713,7 @@ manpages = [
   '8',
   ['systemd-hibernate-resume'],
   'ENABLE_HIBERNATE'],
+ ['systemd-homed.service', '8', ['systemd-homed'], 'ENABLE_HOMED'],
  ['systemd-hostnamed.service', '8', ['systemd-hostnamed'], 'ENABLE_HOSTNAMED'],
  ['systemd-hwdb', '8', [], 'ENABLE_HWDB'],
  ['systemd-id128', '1', [], ''],
@@ -733,7 +740,10 @@ manpages = [
   ['systemd-journald',
    'systemd-journald-audit.socket',
    'systemd-journald-dev-log.socket',
-   'systemd-journald.socket'],
+   'systemd-journald-varlink@.socket',
+   'systemd-journald.socket',
+   'systemd-journald@.service',
+   'systemd-journald@.socket'],
   ''],
  ['systemd-localed.service', '8', ['systemd-localed'], 'ENABLE_LOCALED'],
  ['systemd-logind.service', '8', ['systemd-logind'], 'ENABLE_LOGIND'],
@@ -769,6 +779,7 @@ manpages = [
   'ENABLE_RANDOMSEED'],
  ['systemd-rc-local-generator', '8', [], ''],
  ['systemd-remount-fs.service', '8', ['systemd-remount-fs'], ''],
+ ['systemd-repart', '8', ['systemd-repart.service'], 'ENABLE_REPART'],
  ['systemd-resolved.service', '8', ['systemd-resolved'], 'ENABLE_RESOLVE'],
  ['systemd-rfkill.service',
   '8',
index e9a66d87ddda2e438e872cd5d3492e4c172fc720..76865e1f8ee671c99e3eba6aea10857d6196f149 100644 (file)
@@ -58,6 +58,7 @@
 <citerefentry><refentrytitle>sd_bus_message_append_string_memfd</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
 <citerefentry><refentrytitle>sd_bus_message_append_strv</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
 <citerefentry><refentrytitle>sd_bus_message_copy</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
+<citerefentry><refentrytitle>sd_bus_message_dump</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
 <citerefentry><refentrytitle>sd_bus_message_get_cookie</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
 <citerefentry><refentrytitle>sd_bus_message_get_monotonic_usec</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
 <citerefentry><refentrytitle>sd_bus_message_get_signature</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
diff --git a/man/sd_bus_message_dump.xml b/man/sd_bus_message_dump.xml
new file mode 100644 (file)
index 0000000..db9e46d
--- /dev/null
@@ -0,0 +1,107 @@
+<?xml version='1.0'?>
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="sd_bus_message_dump"
+          xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>sd_bus_message_dump</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>sd_bus_message_dump</refentrytitle>
+    <manvolnum>3</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>sd_bus_message_dump</refname>
+
+    <refpurpose>Produce a string representation of a message for debugging purposes</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <funcsynopsis>
+      <funcsynopsisinfo>#include &lt;systemd/sd-bus.h&gt;</funcsynopsisinfo>
+
+      <funcprototype>
+        <funcdef>int sd_bus_message_dump</funcdef>
+        <paramdef>sd_bus_message *<parameter>m</parameter></paramdef>
+        <paramdef>FILE *<parameter>f</parameter></paramdef>
+        <paramdef>uint64_t <parameter>flags</parameter></paramdef>
+      </funcprototype>
+    </funcsynopsis>
+
+    <para>
+      <constant>SD_BUS_MESSAGE_DUMP_WITH_HEADER</constant>,
+      <constant>SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY</constant>
+    </para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para>The <function>sd_bus_message_dump()</function> function writes a textual representation of the
+    message <parameter>m</parameter> to the stream <parameter>f</parameter>. This function is intended to be
+    used for debugging purposes, and the output is neither stable nor designed to be machine readable.
+    </para>
+
+    <para>The <parameter>flags</parameter> parameter may be used to modify the output. With
+    <constant>SD_BUS_MESSAGE_DUMP_WITH_HEADER</constant>, a header that specifies the message type and flags
+    and some additional metadata is printed. When <constant>SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY</constant> is
+    not passed, the contents of the whole message are printed. When it <emphasis>is</emphasis> passed,
+    only the current container in printed.</para>
+
+    <para>Note that this function moves the read pointer of the message. It may be necessary to reset the
+    position afterwards, for example with
+    <citerefentry><refentrytitle>sd_bus_message_rewind</refentrytitle><manvolnum>3</manvolnum></citerefentry>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Examples</title>
+
+    <para>Output for a signal message (with <constant>SD_BUS_MESSAGE_DUMP_WITH_HEADER</constant>):
+    <programlisting>
+‣ Type=signal  Endian=l  Flags=1  Version=1  Priority=0 Cookie=22
+  Path=/value/a  Interface=org.freedesktop.DBus.Properties  Member=PropertiesChanged
+  MESSAGE "sa{sv}as" {
+          STRING "org.freedesktop.systemd.ValueTest";
+          ARRAY "{sv}" {
+                  DICT_ENTRY "sv" {
+                          STRING "Value";
+                          VARIANT "s" {
+                                  STRING "object 0x1e, path /value/a";
+                          };
+                  };
+          };
+          ARRAY "s" {
+                  STRING "Value2";
+                  STRING "AnExplicitProperty";
+          };
+  };
+    </programlisting>
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Return Value</title>
+
+    <para>On success, this function returns 0 or a positive integer. On failure, it returns a negative
+    errno-style error code. No error codes are currently defined.</para>
+  </refsect1>
+
+  <xi:include href="libsystemd-pkgconfig.xml" />
+
+  <refsect1>
+    <title>See Also</title>
+
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index 526fb0e70afec161b380b748f20af762b3dc2265..2afa44174229bba1e687e418bd13508462f790d4 100644 (file)
 
     <para>For each type specified in the type string, one or more arguments need to be specified
     after the <parameter>types</parameter> parameter, in the same order. The arguments must be
-    pointers to appropriate types (a pointer to <code>int8_t</code> for a <literal>y</literal> in
-    the type string, a pointer to <code>int32_t</code> for an <literal>i</literal>, a pointer to
-    <code>const char*</code> for an <literal>s</literal>, ...)  which are set based on the values in
+    pointers to appropriate types (a pointer to <type>int8_t</type> for a <literal>y</literal> in
+    the type string, a pointer to <type>int32_t</type> for an <literal>i</literal>, a pointer to
+    <type>const char*</type> for an <literal>s</literal>, ...)  which are set based on the values in
     the message. As an exception, in case or array and variant types, the first argument is an
     "input" argument that further specifies how the message should be read. See the table below for
     a complete list of allowed arguments and their types. Note that, if the basic type is a pointer
-    (e.g., <code>const char *</code> in the case of a string), the argument is a pointer to a
+    (e.g., <type>const char *</type> in the case of a string), the argument is a pointer to a
     pointer, and also the pointer value that is written is only borrowed and the contents must be
     copied if they are to be used after the end of the messages lifetime.</para>
 
@@ -99,7 +99,7 @@
             <entry><literal>a</literal></entry>
             <entry><constant>SD_BUS_TYPE_ARRAY</constant></entry>
             <entry>array</entry>
-            <entry>int, which specifies the expected length <parameter>n</parameter> of the array</entry>
+            <entry><type>int</type>, which specifies the expected length <parameter>n</parameter> of the array</entry>
             <entry><parameter>n</parameter> sets of arguments appropriate for the array element type</entry>
           </row>
 
@@ -174,6 +174,14 @@ int64_t x;
 
 sd_bus_message_read(m, "x", &amp;x);</programlisting>
 
+    <para>Read a boolean value:</para>
+
+    <programlisting>sd_bus_message *m;
+int x; /* Do not use C99 'bool' type here, it's typically smaller
+          in memory and would cause memory corruption */
+
+sd_bus_message_read(m, "b", &amp;x);</programlisting>
+
     <para>Read all types of integers:</para>
 
     <programlisting>uint8_t y;
index 117afa9e31e199483b28585454dcbd8aaa86935d..26e8ebae60b737b7c0ae8537da00c39d9a03b7b8 100644 (file)
     appropriate for the data type. The data is part of the message — it may not be modified and is
     valid only as long as the message is referenced. After this function returns, the "read pointer"
     points at the next element after the array.</para>
+
+    <para>Note that this function only supports arrays of trivial types, i.e. arrays of booleans, the various
+    integer types, as well as floating point numbers. In particular it may not be used for arrays of strings,
+    structures or similar.</para>
   </refsect1>
 
   <refsect1>
@@ -68,8 +72,8 @@
         <varlistentry>
           <term><constant>-EINVAL</constant></term>
 
-          <listitem><para>Specified type is invalid or the message parameter or one of the output
-          parameters are <constant>NULL</constant>.</para></listitem>
+          <listitem><para>Specified type is invalid or not a trivial type (see above), or the message
+          parameter or one of the output parameters are <constant>NULL</constant>.</para></listitem>
         </varlistentry>
 
         <varlistentry>
index 101f5d21a4998bf78cebf19314d8faa60ef85d55..0502976dce48701d333996dd86ee5f62e30e578b 100644 (file)
       If <parameter>p</parameter> is not <constant>NULL</constant>, it should contain
       a pointer to an appropriate object. For example, if <parameter>type</parameter>
       is <constant>'y'</constant>, the object passed in <parameter>p</parameter>
-      should have type <code>uint8_t *</code>. If <parameter>type</parameter> is
+      should have type <type>uint8_t *</type>. If <parameter>type</parameter> is
       <constant>'s'</constant>, the object passed in <parameter>p</parameter> should
-      have type <code>const char **</code>. Note that, if the basic type is a pointer
-      (e.g., <code>const char *</code> in the case of a string), the pointer is only
+      have type <type>const char **</type>. Note that, if the basic type is a pointer
+      (e.g., <type>const char *</type> in the case of a string), the pointer is only
       borrowed and the contents must be copied if they are to be used after the end
       of the messages lifetime. Similarly, during the lifetime of such a pointer, the
       message must not be modified. See the table below for a complete list of allowed
           <row>
             <entry><literal>y</literal></entry>
             <entry><constant>SD_BUS_TYPE_BYTE</constant></entry>
-            <entry>unsigned integer</entry>
-            <entry>uint8_t *</entry>
+            <entry>8bit unsigned integer</entry>
+            <entry><type>uint8_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>b</literal></entry>
             <entry><constant>SD_BUS_TYPE_BOOLEAN</constant></entry>
             <entry>boolean</entry>
-            <entry>int *</entry>
+            <entry><type>int *</type> (NB: not <type>bool *</type>)</entry>
           </row>
 
           <row>
             <entry><literal>n</literal></entry>
             <entry><constant>SD_BUS_TYPE_INT16</constant></entry>
-            <entry>signed integer</entry>
-            <entry>int16_t *</entry>
+            <entry>16bit signed integer</entry>
+            <entry><type>int16_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>q</literal></entry>
             <entry><constant>SD_BUS_TYPE_UINT16</constant></entry>
-            <entry>unsigned integer</entry>
-            <entry>uint16_t *</entry>
+            <entry>16bit unsigned integer</entry>
+            <entry><type>uint16_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>i</literal></entry>
             <entry><constant>SD_BUS_TYPE_INT32</constant></entry>
-            <entry>signed integer</entry>
-            <entry>int32_t *</entry>
+            <entry>32bit signed integer</entry>
+            <entry><type>int32_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>u</literal></entry>
             <entry><constant>SD_BUS_TYPE_UINT32</constant></entry>
-            <entry>unsigned integer</entry>
-            <entry>uint32_t *</entry>
+            <entry>32bit unsigned integer</entry>
+            <entry><type>uint32_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>x</literal></entry>
             <entry><constant>SD_BUS_TYPE_INT64</constant></entry>
-            <entry>signed integer</entry>
-            <entry>int64_t *</entry>
+            <entry>64bit signed integer</entry>
+            <entry><type>int64_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>t</literal></entry>
             <entry><constant>SD_BUS_TYPE_UINT64</constant></entry>
-            <entry>unsigned integer</entry>
-            <entry>uint64_t *</entry>
+            <entry>64bit unsigned integer</entry>
+            <entry><type>uint64_t *</type></entry>
           </row>
 
           <row>
             <entry><literal>d</literal></entry>
             <entry><constant>SD_BUS_TYPE_DOUBLE</constant></entry>
-            <entry>floating-point</entry>
-            <entry>double *</entry>
+            <entry>IEEE 754 double precision floating-point</entry>
+            <entry><type>double *</type></entry>
           </row>
 
           <row>
             <entry><literal>s</literal></entry>
             <entry><constant>SD_BUS_TYPE_STRING</constant></entry>
-            <entry>Unicode string</entry>
-            <entry>const char **</entry>
+            <entry>UTF-8 string</entry>
+            <entry><type>const char **</type></entry>
           </row>
 
           <row>
             <entry><literal>o</literal></entry>
             <entry><constant>SD_BUS_TYPE_OBJECT_PATH</constant></entry>
-            <entry>object path</entry>
-            <entry>const char **</entry>
+            <entry>D-Bus object path stringy</entry>
+            <entry><type>const char **</type></entry>
           </row>
 
           <row>
             <entry><literal>g</literal></entry>
             <entry><constant>SD_BUS_TYPE_SIGNATURE</constant></entry>
-            <entry>signature</entry>
-            <entry>const char **</entry>
+            <entry>D-Bus signature string</entry>
+            <entry><type>const char **</type></entry>
           </row>
 
           <row>
             <entry><literal>h</literal></entry>
             <entry><constant>SD_BUS_TYPE_UNIX_FD</constant></entry>
             <entry>UNIX file descriptor</entry>
-            <entry>int *</entry>
+            <entry><type>int *</type></entry>
           </row>
        </tbody>
       </tgroup>
index 47e57107cd0412a2c030a39f57c7292533647f4c..9884beae1a348718b9a3aec64cb5025b873c7a01 100644 (file)
@@ -29,6 +29,8 @@
     <refname>SD_JOURNAL_SYSTEM</refname>
     <refname>SD_JOURNAL_CURRENT_USER</refname>
     <refname>SD_JOURNAL_OS_ROOT</refname>
+    <refname>SD_JOURNAL_ALL_NAMESPACES</refname>
+    <refname>SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE</refname>
     <refpurpose>Open the system journal for reading</refpurpose>
   </refnamediv>
 
         <paramdef>int <parameter>flags</parameter></paramdef>
       </funcprototype>
 
+      <funcprototype>
+        <funcdef>int <function>sd_journal_open_namespace</function></funcdef>
+        <paramdef>sd_journal **<parameter>ret</parameter></paramdef>
+        <paramdef>const char *<parameter>namespace</parameter></paramdef>
+        <paramdef>int <parameter>flags</parameter></paramdef>
+      </funcprototype>
+
       <funcprototype>
         <funcdef>int <function>sd_journal_open_directory</function></funcdef>
         <paramdef>sd_journal **<parameter>ret</parameter></paramdef>
     <constant>SD_JOURNAL_CURRENT_USER</constant> are specified, all
     journal file types will be opened.</para>
 
+    <para><function>sd_journal_open_namespace()</function> is similar to
+    <function>sd_journal_open()</function> but takes an additional <parameter>namespace</parameter> parameter
+    that specifies which journal namespace to operate on. If specified as <constant>NULL</constant> the call
+    is identical to <function>sd_journal_open()</function>. If non-<constant>NULL</constant> only data from
+    the namespace identified by the specified parameter is accessed. This call understands two additional
+    flags: if <constant>SD_JOURNAL_ALL_NAMESPACES</constant> is specified the
+    <parameter>namespace</parameter> parameter is ignored and all defined namespaces are accessed
+    simultaneously; if <constant>SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE</constant> the specified namespace and
+    the default namespace are accessed but no others (this flag has no effect when
+    <parameter>namespace</parameter> is passed as <constant>NULL</constant>). For details about journal
+    namespaces see
+    <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+
     <para><function>sd_journal_open_directory()</function> is similar to <function>sd_journal_open()</function> but
     takes an absolute directory path as argument. All journal files in this directory will be opened and interleaved
     automatically. This call also takes a flags argument. The flags parameters accepted by this call are
index 82321c44bf85a4a692ba6a6e2c1f1c18e51d52f9..ab332588581b9c1ee39cefbe0591f276eb926b0e 100644 (file)
       <varlistentry>
         <term><varname>rootflags=</varname></term>
 
-        <listitem><para>Takes the root filesystem mount options to
-        use. <varname>rootflags=</varname> is honored by the
-        initrd.</para></listitem>
+        <listitem><para>Takes the root filesystem mount options to use. <varname>rootflags=</varname> is
+        honored by the initrd.</para>
+
+        <para>Note that unlike most kernel command line options this setting does not override settings made
+        in configuration files (specifically: the mount option string in
+        <filename>/etc/fstab</filename>). See
+        <citerefentry><refentrytitle>systemd-remount-fs.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para></listitem>
       </varlistentry>
 
       <varlistentry>
diff --git a/man/systemd-homed.service.xml b/man/systemd-homed.service.xml
new file mode 100644 (file)
index 0000000..26789a2
--- /dev/null
@@ -0,0 +1,57 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="systemd-homed.service" conditional='ENABLE_HOMED'>
+
+  <refentryinfo>
+    <title>systemd-homed.service</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-homed.service</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-homed.service</refname>
+    <refname>systemd-homed</refname>
+    <refpurpose>Home Directory/User Account Manager</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <para><filename>systemd-homed.service</filename></para>
+    <para><filename>/usr/lib/systemd/systemd-homed</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-homed</command> is a system service that may be used to create, remove, change or
+    inspect home directories.</para>
+
+    <para>Most of <command>systemd-homed</command>'s functionality is accessible through the
+    <citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry> command.</para>
+
+    <para>See the <ulink url="https://systemd.io/HOME_DIRECTORY">Home Directories</ulink> documentation for
+    details about the format and design of home directories managed by
+    <filename>systemd-homed.service</filename>.</para>
+
+    <para>Each home directory managed by <filename>systemd-homed.service</filename> synthesizes a local user
+    and group. These are made available to the system using the <ulink
+    url="https://systemd.io/USER_GROUP_API">User/Group Record Lookup API via Varlink</ulink>, and thus may be
+    browsed with
+    <citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>homectl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>userdbctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+</refentry>
index a5ab31ad6dfe5276e0e2221528a952fd19e14e67..747b703653209cc1f9df20d9f15eaea9464b4f6e 100644 (file)
     will be printed. This is available in systemd services. See
     <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
     </para>
+
+    <para>With <command>show</command>, well-known UUIDs are printed. When no arguments are specified, all
+    known UUIDs are shown. When arguments are specified, they must be the names or values of one or more
+    known UUIDs, which are then printed.</para>
   </refsect1>
 
   <refsect1>
index ec9f974f2da5ea7ba164253b882a726fd1cb59ab..f4ce4e4fe2079ac8d58461f3e54af71f3f33165d 100644 (file)
@@ -20,6 +20,9 @@
     <refname>systemd-journald.socket</refname>
     <refname>systemd-journald-dev-log.socket</refname>
     <refname>systemd-journald-audit.socket</refname>
+    <refname>systemd-journald@.service</refname>
+    <refname>systemd-journald@.socket</refname>
+    <refname>systemd-journald-varlink@.socket</refname>
     <refname>systemd-journald</refname>
     <refpurpose>Journal service</refpurpose>
   </refnamediv>
@@ -29,6 +32,9 @@
     <para><filename>systemd-journald.socket</filename></para>
     <para><filename>systemd-journald-dev-log.socket</filename></para>
     <para><filename>systemd-journald-audit.socket</filename></para>
+    <para><filename>systemd-journald@.service</filename></para>
+    <para><filename>systemd-journald@.socket</filename></para>
+    <para><filename>systemd-journald-varlink@.socket</filename></para>
     <para><filename>/usr/lib/systemd/systemd-journald</filename></para>
   </refsynopsisdiv>
 
@@ -129,6 +135,40 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
     <constant>EPIPE</constant> right from the beginning.</para>
   </refsect1>
 
+  <refsect1>
+    <title>Journal Namespaces</title>
+
+    <para>Journal 'namespaces' are both a mechanism for logically isolating the log stream of projects
+    consisting of one or more services from the rest of the system and a mechanism for improving
+    performance. Multiple journal namespaces may exist simultaneously, each defining its own, independent log
+    stream managed by its own instance of <command>systemd-journald</command>. Namespaces are independent of
+    each other, both in the data store and in the IPC interface. By default only a single 'default' namespace
+    exists, managed by <filename>systemd-journald.service</filename> (and its associated socket
+    units). Additional namespaces are created by starting an instance of the
+    <filename>systemd-journald@.service</filename> service template. The instance name is the namespace
+    identifier, which is a short string used for referencing the journal namespace. Service units may be
+    assigned to a specific journal namespace through the <varname>LogNamespace=</varname> unit file setting,
+    see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+    details. The <option>--namespace=</option> switch of
+    <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry> may be
+    used to view the log stream of a specific namespace. If the switch is not used the log stream of the
+    default namespace is shown, i.e. log data from other namespaces is not visible.</para>
+
+    <para>Services associated with a specific log namespace may log via syslog, the native logging protocol
+    of the journal and via stdout/stderr; the logging from all three transports is associated with the
+    namespace.</para>
+
+    <para>By default only the default namespace will collect kernel and audit log messages.</para>
+
+    <para>The <command>systemd-journald</command> instance of the default namespace is configured through
+    <filename>/etc/systemd/journald.conf</filename> (see below), while the other instances are configured
+    through <filename>/etc/systemd/journald@<replaceable>NAMESPACE</replaceable>.conf</filename>. The journal
+    log data for the default namespace is placed in
+    <filename>/var/log/journal/<replaceable>MACHINE_ID</replaceable></filename> (see below) while the data
+    for the other namespaces is located in
+    <filename>/var/log/journal/<replaceable>MACHINE_ID</replaceable>.<replaceable>NAMESPACE</replaceable></filename>.</para>
+  </refsect1>
+
   <refsect1>
     <title>Signals</title>
 
@@ -190,6 +230,9 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
 
       </varlistentry>
     </variablelist>
+
+    <para>Note that these kernel command line options are only honoured by the default namespace, see
+    above.</para>
   </refsect1>
 
   <refsect1>
@@ -279,12 +322,14 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
         <term><filename>/run/systemd/journal/socket</filename></term>
         <term><filename>/run/systemd/journal/stdout</filename></term>
 
-        <listitem><para>Sockets and other paths that
-        <command>systemd-journald</command> will listen on that are
-        visible in the file system. In addition to these, journald can
-        listen for audit events using netlink.</para></listitem>
+        <listitem><para>Sockets and other file node paths that <command>systemd-journald</command> will
+        listen on and are visible in the file system. In addition to these,
+        <command>systemd-journald</command> can listen for audit events using <citerefentry
+        project='man-pages'><refentrytitle>netlink</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para></listitem>
       </varlistentry>
     </variablelist>
+
+    <para>If journal namespacing is used these paths are slightly altered to include a namespace identifier, see above.</para>
   </refsect1>
 
   <refsect1>
@@ -296,7 +341,7 @@ systemd-tmpfiles --create --prefix /var/log/journal</programlisting>
       <citerefentry><refentrytitle>systemd.journal-fields</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>sd-journal</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-coredump</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
-      <citerefentry project='die-net'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry project='man-pages'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>sd_journal_print</refentrytitle><manvolnum>3</manvolnum></citerefentry>,
       <command>pydoc systemd.journal</command>
     </para>
index 8514af67bc4835a0fdd39cfc73672382611af9c5..d07d90315a4140e4c3ea117449d8dce2e8c1cbeb 100644 (file)
@@ -80,6 +80,7 @@
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd.mount</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>mkfs.btrfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>mkfs.cramfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>mkfs.ext4</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
new file mode 100644 (file)
index 0000000..f55be4f
--- /dev/null
@@ -0,0 +1,269 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+  "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<refentry id="systemd-repart" conditional='ENABLE_REPART'
+    xmlns:xi="http://www.w3.org/2001/XInclude">
+
+  <refentryinfo>
+    <title>systemd-repart</title>
+    <productname>systemd</productname>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-repart</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-repart</refname>
+    <refname>systemd-repart.service</refname>
+    <refpurpose>Automatically grow and add partitions</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-repart</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="opt" rep="repeat"><replaceable><optional>BLOCKDEVICE</optional></replaceable></arg>
+    </cmdsynopsis>
+
+    <para><filename>systemd-repart.service</filename></para>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para><command>systemd-repart</command> grows and adds partitions to a partition table, based on the
+    configuration files described in
+    <citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
+    </para>
+
+    <para>If invoked with no arguments, it operates on the block device backing the root file system partition
+    of the OS, thus growing and adding partitions of the booted OS image itself. When called in the initial
+    RAM disk it operates on the block device backing <filename>/sysroot/</filename> instead, i.e. on the
+    block device the system will soon transition into. The <filename>systemd-repart.service</filename>
+    service is generally run at boot in the initial RAM disk, in order to augment the partition table of the
+    OS before its partitions are mounted. <command>systemd-repart</command> (mostly) operates in a purely
+    incremental mode: it only grows existing and adds new partitions; it does not shrink, delete or move
+    existing partitions. The service is intended to be run on every boot, but when it detects that the
+    partition table already matches the installed <filename>repart.d/*.conf</filename> configuration
+    files, it executes no operation.</para>
+
+    <para><command>systemd-repart</command> is intended to be used when deploying OS images, to automatically
+    adjust them to the system they are running on, during first boot. This way the deployed image can be
+    minimal in size and may be augmented automatically at boot when needed, taking possession of disk space
+    available but not yet used. Specifically the following use cases are among those covered:</para>
+
+    <itemizedlist>
+      <listitem><para>The root partition may be grown to cover the whole available disk space</para></listitem>
+      <listitem><para>A <filename>/home/</filename>, swap or <filename>/srv</filename> partition can be added in</para></listitem>
+      <listitem><para>A second (or third, …) root partition may be added in, to cover A/B style setups
+      where a second version of the root file system is alternatingly used for implementing update
+      schemes. The deployed image would carry only a single partition ("A") but on first boot a second
+      partition ("B") for this purpose is automatically created.</para></listitem>
+    </itemizedlist>
+
+    <para>The algorithm executed by <command>systemd-repart</command> is roughly as follows:</para>
+
+    <orderedlist>
+      <listitem><para>The <filename>repart.d/*.conf</filename> configuration files are loaded and parsed,
+      and ordered by filename (without the directory suffix). </para></listitem>
+
+      <listitem><para>The partition table already existing on the block device is loaded and
+      parsed.</para></listitem>
+
+      <listitem><para>The existing partitions in the partition table are matched up with the
+      <filename>repart.d/*.conf</filename> files by GPT partition type UUID. The first existing partition
+      of a specific type is assigned the first configuration file declaring the same type. The second
+      existing partition of a specific type is then assigned the second configuration file declaring the same
+      type, and so on. After this iterative assigning is complete any left-over existing partitions that have
+      no matching configuration file are considered "foreign" and left as they are. And any configuration
+      files for which no partition currently exists are understood as a request to create such a
+      partition.</para></listitem>
+
+      <listitem><para>Taking the size constraints and weights declared in the configuration files into
+      account, all partitions that shall be created are now allocated to the disk, taking up all free space,
+      always respecting the size and padding requests. Similar, existing partitions that are determined to
+      grow are grown. New partitions are always appended to the end of the existing partition table, taking
+      the first partition table slot whose index is greater than the indexes of all existing
+      partitions. Partition table slots are never reordered and thus partition numbers are ensured to remain
+      stable. Note that this allocation happens in RAM only, the partition table on disk is not updated
+      yet.</para></listitem>
+
+      <listitem><para>All existing partitions for which configuration files exist and which currently have no
+      GPT partition label set will be assigned a label, either explicitly configured in the configuration or
+      (if that's missing) derived automatically from the partition type. The same is done for all partitions
+      that are newly created. These assignments are done in RAM only, too, the disk is not updated
+      yet.</para></listitem>
+
+      <listitem><para>Similarly, all existing partitions for which configuration files exist and which
+      currently have an all-zero identifying UUID will be assigned a new UUID. This UUID is cryptographically
+      hashed from a common seed value together with the partition type UUID (and a counter in case multiple
+      partitions of the same type are defined), see below. The same is done for all partitions that are
+      created anew. These assignments are done in RAM only, too, the disk is not updated
+      yet.</para></listitem>
+
+      <listitem><para>Similarly, if the disk's volume UUID is all zeroes it is also initialized, also
+      cryptographically hashed from the same common seed value. Also, in RAM only, too.</para></listitem>
+
+      <listitem><para>The disk space assigned to new partitions (i.e. what was previously considered free
+      space but is no longer) is now erased. Specifically, all file system signatures are removed, and if the
+      device supports it the <constant>BLKDISCARD</constant> I/O control command is issued to inform the
+      hardware that the space is empty now. In addition any "padding" between partitions and at the end of
+      the device is similarly erased.</para></listitem>
+
+      <listitem><para>The new partition table is finally written to disk. The kernel is asked to reread the
+      partition table.</para></listitem>
+    </orderedlist>
+
+    <para>As exception to the normally strictly incremental operation, when called in a special "factory
+    reset" mode <command>systemd-repart</command> may also be used to erase select existing partitions to
+    reset an installation back to vendor defaults. This mode of operation is used when either the
+    <option>--factory-reset=yes</option> switch is passed on the tool's command line, or the
+    <option>systemd.factory_reset=yes</option> option specified on the kernel command line, or the
+    <varname>FactoryReset</varname> EFI variable (vendor UUID
+    <constant>8cf2644b-4b0b-428f-9387-6d876050dc67</constant>) is set to "yes". It alters the algorithm above
+    slightly: between the 3rd and the 4th step above the any partition marked explicitly via the
+    <varname>FactoryReset=</varname> boolean is deleted, and the algorithm restarted, thus immediately
+    re-creating these partitions anew empty.</para>
+
+    <para>Note that <command>systemd-repart</command> only changes partition tables, it does not create or
+    resize any file systems within these partitions. A separate mechanism should be used for that, for
+    example
+    <citerefentry><refentrytitle>systemd-growfs</refentrytitle><manvolnum>8</manvolnum></citerefentry> and
+    <command>systemd-makefs</command>.</para>
+
+    <para>The UUIDs identifying the new partitions created (or assigned to existing partitions that have no
+    UUID yet), as well as the disk as a whole are hashed cryptographically from a common seed value. This
+    seed value is usually the
+    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> of the
+    system, so that the machine ID reproducibly determines the UUIDs assigned to all partitions. If the
+    machine ID cannot be read (or the user passes <option>--seed=random</option>, see below) the seed is
+    generated randomly instead, so that the partition UUIDs are also effectively random. The seed value may
+    also be set explicitly, formatted as UUID via the <option>--seed=</option> option. By hashing these UUIDs
+    from a common seed images prepared with this tool become reproducible and the result of the algorithm
+    above deterministic.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>The following options are understood:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--dry-run=</option></term>
+        <listitem><para>Takes a boolean. If this switch is not specified <option>--dry-run=yes</option> is
+        the implied default. Controls whether <filename>systemd-repart</filename> executes the requested
+        re-partition operations or whether it should only show what it would do. Unless
+        <option>--dry-run=no</option> is specified <filename>systemd-repart</filename> will not actually
+        touch the device's partition table.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--empty=</option></term>
+        <listitem><para>Takes one of <literal>refuse</literal>, <literal>allow</literal>,
+        <literal>require</literal> or <literal>force</literal>. Controls how to operate on block devices that
+        are entirely empty, i.e. carry no partition table/disk label yet. If this switch is not specified the
+        implied default is <literal>refuse</literal>.</para>
+
+        <para>If <literal>refuse</literal> <command>systemd-repart</command> requires that the block device
+        it shall operate on already carries a partition table and refuses operation if none is found. If
+        <literal>allow</literal> the command will extend an existing partition table or create a new one if
+        none exists. If <literal>require</literal> the command will create a new partition table if none
+        exists so far, and refuse operation if one already exists. If <literal>force</literal> it will create
+        a fresh partition table unconditionally, erasing the disk fully in effect. If
+        <literal>force</literal> no existing partitions will be taken into account or survive the
+        operation. Hence: use with care, this is a great way to lose all your data.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--discard=</option></term>
+
+        <listitem><para>Takes a boolean. If this switch is not specified <option>--discard=yes</option> is
+        the implied default. Controls whether to issue the <constant>BLKDISCARD</constant> I/O control
+        command on the space taken up by any added partitions or on the space in between them. Usually, it's
+        a good idea to issue this request since it tells the underlying hardware that the covered blocks
+        shall be considered empty, improving performance.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--factory-reset=</option></term>
+
+        <listitem><para>Takes boolean. If this switch is not specified <option>--factory=reset=no</option> is
+        the implied default. Controls whether to operate in "factory reset" mode, see above. If set to true
+        this will remove all existing partitions marked with <varname>FactoryReset=</varname> set to yes
+        early while executing the re-partitioning algorithm. Use with care, this is a great way to lose all
+        your data. Note that partition files need to explicitly turn <varname>FactoryReset=</varname> on, as
+        the option defaults to off. If no partitions are marked for factory reset this switch has no
+        effect. Note that there are two other methods to request factory reset operation: via the kernel
+        command line and via an EFI variable, see above.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--can-factory-reset</option></term>
+
+        <listitem><para>If this switch is specified the disk is not re-partitioned. Instead it is determined
+        if any existing partitions are marked with <varname>FactoryReset=</varname>. If there are the tool
+        will exit with exit status zero, otherwise non-zero. This switch may be used to quickly determine
+        whether the running system supports a factory reset mechanism built on
+        <command>systemd-repart</command>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--root=</option></term>
+
+        <listitem><para>Takes a path to a directory to use as root file system when searching for
+        <filename>repart.d/*.conf</filename> files and for the machine ID file to use as seed. By default
+        when invoked on the regular system this defaults to the host's root file system
+        <filename>/</filename>. If invoked from the initial RAM disk this defaults to
+        <filename>/sysroot/</filename>, so that the tool operates on the configuration and machine ID stored
+        in the root file system later transitioned into itself.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--seed=</option></term>
+
+        <listitem><para>Takes a UUID as argument or the special value <constant>random</constant>. If a UUID
+        is specified the UUIDs to assign to partitions and the partition table itself are derived via
+        cryptographic hashing from it. If not specified it is attempted to read the machine ID from the host
+        (or more precisely, the root directory configured via <option>--root=</option>) and use it as seed
+        instead, falling back to a randomized seed otherwise. Use <option>--seed=random</option> to force a
+        randomized seed. Explicitly specifying the seed may be used to generated strictly reproducible
+        partition tables.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--pretty=</option></term>
+
+        <listitem><para>Takes a boolean argument. If this switch is not specified, it defaults to on when
+        called from an interactive terminal and off otherwise. Controls whether to show a user friendly table
+        and graphic illustrating the changes applied.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--definitions=</option></term>
+
+        <listitem><para>Takes a file system path. If specified the <filename>*.conf</filename> are directly
+        read from the specified directory instead of searching in
+        <filename>/usr/lib/repart.d/*.conf</filename>, <filename>/etc/repart.d/*.conf</filename>,
+        <filename>/run/repart.d/*.conf</filename>.</para></listitem>
+      </varlistentry>
+
+      <xi:include href="standard-options.xml" xpointer="help" />
+      <xi:include href="standard-options.xml" xpointer="version" />
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+    </para>
+  </refsect1>
+
+</refentry>
index e403fa53086f5a5c3947554261380cabaa307145..e22b335d30d726affd00a25f6c18985c34358fdf 100644 (file)
         <term><varname>DefaultLimitRTPRIO=</varname></term>
         <term><varname>DefaultLimitRTTIME=</varname></term>
 
-        <listitem><para>These settings control various default
-        resource limits for units. See
-        <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry>
-        for details. The resource limit is possible to specify in two formats,
-        <option>value</option> to set soft and hard limits to the same value,
-        or <option>soft:hard</option> to set both limits individually (e.g. DefaultLimitAS=4G:16G).
-        Use the string <varname>infinity</varname> to
-        configure no limit on a specific resource. The multiplicative
-        suffixes K (=1024), M (=1024*1024) and so on for G, T, P and E
-        may be used for resource limits measured in bytes
-        (e.g. DefaultLimitAS=16G). For the limits referring to time values,
-        the usual time units ms, s, min, h and so on may be used (see
-        <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry>
-        for details). Note that if no time unit is specified for
-        <varname>DefaultLimitCPU=</varname> the default unit of seconds is
-        implied, while for <varname>DefaultLimitRTTIME=</varname> the default
-        unit of microseconds is implied. Also, note that the effective
-        granularity of the limits might influence their
-        enforcement. For example, time limits specified for
-        <varname>DefaultLimitCPU=</varname> will be rounded up implicitly to
-        multiples of 1s. These  settings may be overridden in individual units
-        using the corresponding LimitXXX= directives. Note that these resource
-        limits are only defaults for units, they are not applied to PID 1
-        itself.</para></listitem>
+        <listitem><para>These settings control various default resource limits for processes executed by
+        units. See
+        <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+        details. These settings may be overridden in individual units using the corresponding
+        <varname>LimitXXX=</varname> directives, see
+        <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>, for
+        details, and they accept the same parameter syntax. Note that these resource limits are only defaults
+        for units, they are not applied to the service manager process (i.e. PID 1) itself.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 9d98b2b411f87098ff87f96f2bcf1800468faf82..8f1695ad293f6113a4b32f58c18822687c4f8928 100644 (file)
       <option>syslog</option> or <option>kmsg</option> (or their combinations with console output, see below)
       automatically acquire dependencies of type <varname>After=</varname> on
       <filename>systemd-journald.socket</filename>.</para></listitem>
+
+      <listitem><para>Units using <varname>LogNamespace=</varname> will automatically gain ordering and
+      requirement dependencies on the two socket units associated with
+      <filename>systemd-journald@.service</filename> instances.</para></listitem>
     </itemizedlist>
   </refsect1>
 
         Linux systems.</para>
 
         <para>When used in conjunction with <varname>DynamicUser=</varname> the user/group name specified is
-        dynamically allocated at the time the service is started, and released at the time the service is stopped —
-        unless it is already allocated statically (see below). If <varname>DynamicUser=</varname> is not used the
-        specified user and group must have been created statically in the user database no later than the moment the
-        service is started, for example using the
-        <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry> facility, which
-        is applied at boot or package install time.</para>
+        dynamically allocated at the time the service is started, and released at the time the service is
+        stopped — unless it is already allocated statically (see below). If <varname>DynamicUser=</varname>
+        is not used the specified user and group must have been created statically in the user database no
+        later than the moment the service is started, for example using the
+        <citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        facility, which is applied at boot or package install time. If the user does not exist by then
+        program invocation will fail.</para>
 
         <para>If the <varname>User=</varname> setting is used the supplementary group list is initialized
         from the specified user's default group list, as defined in the system's user and group
@@ -404,11 +409,11 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
         <varname>RestrictAddressFamilies=</varname>, <varname>RestrictNamespaces=</varname>,
         <varname>PrivateDevices=</varname>, <varname>ProtectKernelTunables=</varname>,
         <varname>ProtectKernelModules=</varname>, <varname>ProtectKernelLogs=</varname>,
-        <varname>MemoryDenyWriteExecute=</varname>, <varname>RestrictRealtime=</varname>,
-        <varname>RestrictSUIDSGID=</varname>, <varname>DynamicUser=</varname> or <varname>LockPersonality=</varname>
-        are specified. Note that even if this setting is overridden by them, <command>systemctl show</command> shows the
-        original value of this setting. Also see <ulink
-        url="https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html">No New Privileges
+        <varname>ProtectClock=</varname>, <varname>MemoryDenyWriteExecute=</varname>,
+        <varname>RestrictRealtime=</varname>, <varname>RestrictSUIDSGID=</varname>, <varname>DynamicUser=</varname>
+        or <varname>LockPersonality=</varname> are specified. Note that even if this setting is overridden by them,
+        <command>systemctl show</command> shows the original value of this setting.
+        Also see <ulink url="https://www.kernel.org/doc/html/latest/userspace-api/no_new_privs.html">No New Privileges
         Flag</ulink>.</para></listitem>
       </varlistentry>
 
@@ -497,42 +502,51 @@ CapabilityBoundingSet=~CAP_B CAP_C</programlisting>
         <term><varname>LimitRTTIME=</varname></term>
 
         <listitem><para>Set soft and hard limits on various resources for executed processes. See
-        <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry> for details on
-        the resource limit concept. Resource limits may be specified in two formats: either as single value to set a
-        specific soft and hard limit to the same value, or as colon-separated pair <option>soft:hard</option> to set
-        both limits individually (e.g. <literal>LimitAS=4G:16G</literal>).  Use the string <option>infinity</option> to
-        configure no limit on a specific resource. The multiplicative suffixes K, M, G, T, P and E (to the base 1024)
-        may be used for resource limits measured in bytes (e.g. LimitAS=16G). For the limits referring to time values,
-        the usual time units ms, s, min, h and so on may be used (see
+        <citerefentry><refentrytitle>setrlimit</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+        details on the resource limit concept. Resource limits may be specified in two formats: either as
+        single value to set a specific soft and hard limit to the same value, or as colon-separated pair
+        <option>soft:hard</option> to set both limits individually (e.g. <literal>LimitAS=4G:16G</literal>).
+        Use the string <option>infinity</option> to configure no limit on a specific resource. The
+        multiplicative suffixes K, M, G, T, P and E (to the base 1024) may be used for resource limits
+        measured in bytes (e.g. <literal>LimitAS=16G</literal>). For the limits referring to time values, the
+        usual time units ms, s, min, h and so on may be used (see
         <citerefentry><refentrytitle>systemd.time</refentrytitle><manvolnum>7</manvolnum></citerefentry> for
-        details). Note that if no time unit is specified for <varname>LimitCPU=</varname> the default unit of seconds
-        is implied, while for <varname>LimitRTTIME=</varname> the default unit of microseconds is implied. Also, note
-        that the effective granularity of the limits might influence their enforcement. For example, time limits
-        specified for <varname>LimitCPU=</varname> will be rounded up implicitly to multiples of 1s. For
-        <varname>LimitNICE=</varname> the value may be specified in two syntaxes: if prefixed with <literal>+</literal>
-        or <literal>-</literal>, the value is understood as regular Linux nice value in the range -20..19. If not
-        prefixed like this the value is understood as raw resource limit parameter in the range 0..40 (with 0 being
-        equivalent to 1).</para>
-
-        <para>Note that most process resource limits configured with these options are per-process, and processes may
-        fork in order to acquire a new set of resources that are accounted independently of the original process, and
-        may thus escape limits set. Also note that <varname>LimitRSS=</varname> is not implemented on Linux, and
-        setting it has no effect. Often it is advisable to prefer the resource controls listed in
+        details). Note that if no time unit is specified for <varname>LimitCPU=</varname> the default unit of
+        seconds is implied, while for <varname>LimitRTTIME=</varname> the default unit of microseconds is
+        implied. Also, note that the effective granularity of the limits might influence their
+        enforcement. For example, time limits specified for <varname>LimitCPU=</varname> will be rounded up
+        implicitly to multiples of 1s. For <varname>LimitNICE=</varname> the value may be specified in two
+        syntaxes: if prefixed with <literal>+</literal> or <literal>-</literal>, the value is understood as
+        regular Linux nice value in the range -20..19. If not prefixed like this the value is understood as
+        raw resource limit parameter in the range 0..40 (with 0 being equivalent to 1).</para>
+
+        <para>Note that most process resource limits configured with these options are per-process, and
+        processes may fork in order to acquire a new set of resources that are accounted independently of the
+        original process, and may thus escape limits set. Also note that <varname>LimitRSS=</varname> is not
+        implemented on Linux, and setting it has no effect. Often it is advisable to prefer the resource
+        controls listed in
         <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
-        over these per-process limits, as they apply to services as a whole, may be altered dynamically at runtime, and
-        are generally more expressive. For example, <varname>MemoryLimit=</varname> is a more powerful (and working)
-        replacement for <varname>LimitRSS=</varname>.</para>
-
-        <para>For system units these resource limits may be chosen freely. For user units however (i.e. units run by a
-        per-user instance of
-        <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>), these limits are
-        bound by (possibly more restrictive) per-user limits enforced by the OS.</para>
+        over these per-process limits, as they apply to services as a whole, may be altered dynamically at
+        runtime, and are generally more expressive. For example, <varname>MemoryMax=</varname> is a more
+        powerful (and working) replacement for <varname>LimitRSS=</varname>.</para>
 
         <para>Resource limits not configured explicitly for a unit default to the value configured in the various
         <varname>DefaultLimitCPU=</varname>, <varname>DefaultLimitFSIZE=</varname>, … options available in
         <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>, and –
         if not configured there – the kernel or per-user defaults, as defined by the OS (the latter only for user
-        services, see above).</para>
+        services, see below).</para>
+
+        <para>For system units these resource limits may be chosen freely. When these settings are configured
+        in a user service (i.e. a service run by the per-user instance of the service manager) they cannot be
+        used to raise the limits above those set for the user manager itself when it was first invoked, as
+        the user's service manager generally lacks the privileges to do so. In user context these
+        configuration options are hence only useful to lower the limits passed in or to raise the soft limit
+        to the maximum of the hard limit as configured for the user. To raise the user's limits further, the
+        available configuration mechanisms differ between operating systems, but typically require
+        privileges. In most cases it is possible to configure higher per-user resource limits via PAM or by
+        setting limits on the system service encapsulating the user's service manager, i.e. the user's
+        instance of <filename>user@.service</filename>. After making such changes, make sure to restart the
+        user's service manager.</para>
 
         <table>
           <title>Resource limit directives, their equivalent <command>ulimit</command> shell commands and the unit used</title>
@@ -1286,6 +1300,21 @@ BindReadOnlyPaths=/var/lib/systemd</programlisting>
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>ProtectClock=</varname></term>
+
+        <listitem><para>Takes a boolean argument. If set, writes to the hardware clock or system clock will be denied.
+        It is recommended to turn this on for most services that do not need modify the clock. Defaults to off. Enabling
+        this option removes <constant>CAP_SYS_TIME</constant> and <constant>CAP_WAKE_ALARM</constant> from the
+        capability bounding set for this unit, installs a system call filter to block calls that can set the
+        clock, and <varname>DeviceAllow=char-rtc r</varname> is implied. This ensures <filename>/dev/rtc0</filename>,
+        <filename>/dev/rtc1</filename>, etc are made read only to the service. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the details about <varname>DeviceAllow=</varname>.</para>
+
+        <xi:include href="system-only.xml" xpointer="singular"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>ProtectKernelTunables=</varname></term>
 
@@ -1797,7 +1826,7 @@ SystemCallErrorNumber=EPERM</programlisting>
         mappings. Specifically these are the options <varname>PrivateTmp=</varname>,
         <varname>PrivateDevices=</varname>, <varname>ProtectSystem=</varname>, <varname>ProtectHome=</varname>,
         <varname>ProtectKernelTunables=</varname>, <varname>ProtectControlGroups=</varname>,
-        <varname>ProtectKernelLogs=</varname>, <varname>ReadOnlyPaths=</varname>,
+        <varname>ProtectKernelLogs=</varname>, <varname>ProtectClock=</varname>, <varname>ReadOnlyPaths=</varname>,
         <varname>InaccessiblePaths=</varname> and <varname>ReadWritePaths=</varname>.</para></listitem>
       </varlistentry>
 
@@ -2229,6 +2258,36 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>LogNamespace=</varname></term>
+
+        <listitem><para>Run the unit's processes in the specified journal namespace. Expects a short
+        user-defined string identifying the namespace. If not used the processes of the service are run in
+        the default journal namespace, i.e. their log stream is collected and processed by
+        <filename>systemd-journald.service</filename>. If this option is used any log data generated by
+        processes of this unit (regardless if via the <function>syslog()</function>, journal native logging
+        or stdout/stderr logging) is collected and processed by an instance of the
+        <filename>systemd-journald@.service</filename> template unit, which manages the specified
+        namespace. The log data is stored in a data store independent from the default log namespace's data
+        store. See
+        <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for details about journal namespaces.</para>
+
+        <para>Internally, journal namespaces are implemented through Linux mount namespacing and
+        over-mounting the directory that contains the relevant <constant>AF_UNIX</constant> sockets used for
+        logging in the unit's mount namespace. Since mount namespaces are used this setting disconnects
+        propagation of mounts from the unit's processes to the host, similar to how
+        <varname>ReadOnlyPaths=</varname> and similar settings (see above) work. Journal namespaces may hence
+        not be used for services that need to establish mount points on the host.</para>
+
+        <para>When this option is used the unit will automatically gain ordering and requirement dependencies
+        on the two socket units associated with the <filename>systemd-journald@.service</filename> instance
+        so that they are automatically established prior to the unit starting up. Note that when this option
+        is used log output of this service does not appear in the regular
+        <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        output, unless the <option>--namespace=</option> option is used.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>SyslogIdentifier=</varname></term>
 
index 9248726b9ec70a5b50dc2797ebe2c32d574e8d77..a0771f3c135a2504fdec1a2e9d5f4311396aaefd 100644 (file)
           marking the log line end.</para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>_NAMESPACE=</varname></term>
+
+        <listitem><para>If this file was written by a <command>systemd-journald</command> instance managing a
+        journal namespace that is not the default, this field contains the namespace identifier. See
+        <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        for details about journal namespaces.</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 9104dd7f8f45289ca627d676c46ab89c0485a799..3574dd41a129511aad1d13a994b21e7611fa5e1b 100644 (file)
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>ReceiveChecksumOffload=</varname></term>
+        <listitem>
+          <para>Takes a boolean. If set to true, the hardware offload for checksumming of ingress
+          network packets is enabled. When unset, the kernel's default will be used.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>TransmitChecksumOffload=</varname></term>
+        <listitem>
+          <para>Takes a boolean. If set to true, the hardware offload for checksumming of egress
+          network packets is enabled. When unset, the kernel's default will be used.</para>
+        </listitem>
+      </varlistentry>
       <varlistentry>
         <term><varname>TCPSegmentationOffload=</varname></term>
         <listitem>
           When unset, the kernel's default will be used.</para>
         </listitem>
       </varlistentry>
-    <varlistentry>
+      <varlistentry>
         <term><varname>GenericReceiveOffload=</varname></term>
         <listitem>
           <para>Takes a boolean. If set to true, the Generic Receive Offload (GRO) is enabled.
index fe8a6f00cee1ec0bc7ddb2f802b8fec80f110804..d775d74053fdf14180a05453f3d8381ce8e6c7b8 100644 (file)
         <term><option>x-systemd.before=</option></term>
         <term><option>x-systemd.after=</option></term>
 
-        <listitem><para>Configures a <varname>Before=</varname>
-        dependency or <varname>After=</varname> between the created
-        mount unit and another systemd unit, such as a mount unit.
+        <listitem><para>In the created mount unit, configures a
+        <varname>Before=</varname> or <varname>After=</varname>
+        dependency on another systemd unit, such as a mount unit.
         The argument should be a unit name or an absolute path
         to a mount point. This option may be specified more than once.
         This option is particularly useful for mount point declarations
         for details.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>x-systemd.wanted-by=</option></term>
+        <term><option>x-systemd.required-by=</option></term>
+
+        <listitem><para>In the created mount unit, configures a
+        <varname>WantedBy=</varname> or <varname>RequiredBy=</varname>
+        dependency on another unit.  This option may be
+        specified more than once. If this is specified, the normal
+        automatic dependencies on the created mount unit, e.g.,
+        <filename>local-fs.target</filename>, are not automatically
+        created. See <varname>WantedBy=</varname> and <varname>RequiredBy=</varname> in
+        <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for details.</para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>x-systemd.requires-mounts-for=</option></term>
 
index 6c4532b2b93cbdb27157f82404dab1db2f7d6c85..9362c1bff91443ed70d86dc48c1c67d0263d95a7 100644 (file)
@@ -2112,7 +2112,7 @@ Endpoint=wireguard.example.com:51820</programlisting>
 
     <example>
       <title>/etc/systemd/network/27-xfrm.netdev</title>
-      <programlisting>[Xfrm]
+      <programlisting>[NetDev]
 Name=xfrm0
 Kind=xfrm
 
index 8a75cfc1cde2ee1a669376e17dd894d89176649b..1492f2032a9d83746032ea6b45676959e1ee7ef6 100644 (file)
         <varlistentry>
           <term><varname>InvertRule=</varname></term>
           <listitem>
-            <para>A boolean. Specifies whether the rule to be inverted. Defaults to false.</para>
+            <para>A boolean. Specifies whether the rule is to be inverted. Defaults to false.</para>
           </listitem>
         </varlistentry>
         <varlistentry>
             <literal>ipv4</literal>.</para>
           </listitem>
         </varlistentry>
+        <varlistentry>
+          <term><varname>User=</varname></term>
+          <listitem>
+            <para>Takes a username, a user ID, or a range of user IDs separated by a dash. Defaults to
+            unset.</para>
+          </listitem>
+        </varlistentry>
+        <varlistentry>
+          <term><varname>SuppressPrefixLength=</varname></term>
+          <listitem>
+            <para>Takes a number <replaceable>N</replaceable> in the range 0-128 and rejects routing
+            decisions that have a prefix length of <replaceable>N</replaceable> or less. Defaults to
+            unset.</para>
+          </listitem>
+        </varlistentry>
       </variablelist>
     </refsect1>
 
             <para>Note that this configuration will overwrite others.
             In concrete, the following variables will be ignored:
             <varname>SendHostname=</varname>, <varname>ClientIdentifier=</varname>,
-            <varname>UseRoutes=</varname>, <varname>SendHostname=</varname>,
-            <varname>UseMTU=</varname>, <varname>VendorClassIdentifier=</varname>,
-            <varname>UseTimezone=</varname>.</para>
+            <varname>UseRoutes=</varname>, <varname>UseMTU=</varname>,
+            <varname>VendorClassIdentifier=</varname>, <varname>UseTimezone=</varname>.</para>
 
             <para>With this option enabled DHCP requests will mimic those generated by Microsoft Windows, in
             order to reduce the ability to fingerprint and recognize installations. This means DHCP request
index 39cca8cf514faf863112c2731f919d06d85e0423..f6fe3d83883eca518563535f522ed27c3d169503 100644 (file)
     limitations as inotify, and for example cannot be used to monitor
     files or directories changed by other machines on remote NFS file
     systems.</para>
+
+    <para>When a service unit triggered by a path unit terminates (regardless whether it exited successfully
+    or failed), monitored paths are checked immediately again, and the service accordingly restarted
+    instantly. As protection against busy looping in this trigger/start cycle, a start rate limit is enforced
+    on the service unit, see <varname>StartLimitIntervalSec=</varname> and
+    <varname>StartLimitBurst=</varname> in
+    <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>. Unlike
+    other service failures, the error condition that the start rate limit is hit is propagated from the
+    service unit to the path unit and causes the path unit to fail as well, thus ending the loop.</para>
   </refsect1>
 
   <refsect1>
index ec3f6acbd55e9dc54f95609b500a24a3f67b4fb0..fce7dc75052f26a86e298a4f5086f5577efa949f 100644 (file)
 
           <para>This setting replaces <varname>BlockIODeviceWeight=</varname> and disables settings prefixed with
           <varname>BlockIO</varname> or <varname>StartupBlockIO</varname>.</para>
+
+          <para>The specified device node should reference a block device that has an I/O scheduler
+          associated, i.e. should not refer to partition or loopback block devices, but to the originating,
+          physical device. When a path to a regular file or directory is specified it is attempted to
+          discover the correct originating device backing the file system of the specified path. This works
+          correctly only for simpler cases, where the file system is directly placed on a partition or
+          physical block device, or where simple 1:1 encryption using dm-crypt/LUKS is used. This discovery
+          does not cover complex storage and in particular RAID and volume management storage devices.</para>
         </listitem>
       </varlistentry>
 
           <para>These settings replace <varname>BlockIOReadBandwidth=</varname> and
           <varname>BlockIOWriteBandwidth=</varname> and disable settings prefixed with <varname>BlockIO</varname> or
           <varname>StartupBlockIO</varname>.</para>
+
+          <para>Similar restrictions on block device discovery as for <varname>IODeviceWeight=</varname> apply, see above.</para>
         </listitem>
       </varlistentry>
 
 
           <para>These settings are supported only if the unified control group hierarchy is used and disable settings
           prefixed with <varname>BlockIO</varname> or <varname>StartupBlockIO</varname>.</para>
+
+          <para>Similar restrictions on block device discovery as for <varname>IODeviceWeight=</varname> apply, see above.</para>
         </listitem>
       </varlistentry>
 
           <para>Implies <literal>IOAccounting=yes</literal>.</para>
 
           <para>These settings are supported only if the unified control group hierarchy is used.</para>
+
+          <para>Similar restrictions on block device discovery as for <varname>IODeviceWeight=</varname> apply, see above.</para>
         </listitem>
       </varlistentry>
 
index ebf6de4eb233cc5dc4b63aea18af0c2c4e897129..040b8e28939eadd20bbc887823f67c21e0c008e7 100644 (file)
         timers defined in the other directives.</para>
 
         <para>These are monotonic timers, independent of wall-clock time and timezones. If the computer is
-        temporarily suspended, the monotonic clock pauses, too.</para>
+        temporarily suspended, the monotonic clock generally pauses, too. Note that if
+        <varname>WakeSystem=</varname> is used, a different monotonic clock is selected that continues to
+        advance while the system is suspended and thus can be used as the trigger to resume the
+        system.</para>
 
         <para>If the empty string is assigned to any of these options, the list of timers is reset (both
         monotonic timers and <varname>OnCalendar=</varname> timers, see below), and all prior assignments
         <citerefentry><refentrytitle>prctl</refentrytitle><manvolnum>2</manvolnum></citerefentry>
         for details. To optimize power consumption, make sure to set
         this value as high as possible and as low as
-        necessary.</para></listitem>
+        necessary.</para>
+
+        <para>Note that this setting is primarily a power saving option that allows coalescing CPU
+        wake-ups. It should not be confused with <varname>RandomizedDelaySec=</varname> (see below) which
+        adds a random value to the time the timer shall elapse next and whose purpose is the opposite: to
+        stretch elapsing of timer events over a longer period to reduce workload spikes. For further details
+        and explanations and how both settings play together, see below.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         <varname>false</varname>.</para>
 
         <para>Note that this functionality requires privileges and is thus generally only available in the
-        system service manager.</para></listitem>
+        system service manager.</para>
+
+        <para>Note that behaviour of monotonic clock timers (as configured with
+        <varname>OnActiveSec=</varname>, <varname>OnBootSec=</varname>, <varname>OnStartupSec=</varname>,
+        <varname>OnUnitActiveSec=</varname>, <varname>OnUnitInactiveSec=</varname>, see above) is altered
+        depending on this option. If false, a monotonic clock is used that is paused during system suspend
+        (<constant>CLOCK_MONOTONIC</constant>), if true a different monotonic clock is used that continues
+        advancing during system suspend (<constant>CLOCK_BOOTTIME</constant>), see
+        <citerefentry><refentrytitle>clock_getres</refentrytitle><manvolnum>2</manvolnum></citerefentry> for
+        details.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index debb0c0d6c17aa848519889c4e57735bad941668..64830ae72373d362ea52159d939eba6a4d859c54 100644 (file)
         configured by <varname>Requires=</varname>, <varname>Wants=</varname>, <varname>Requisite=</varname>,
         or <varname>BindsTo=</varname>. It is a common pattern to include a unit name in both the
         <varname>After=</varname> and <varname>Wants=</varname> options, in which case the unit listed will
-        be started before the unit that is configured with these options.</para></listitem>
+        be started before the unit that is configured with these options.</para>
+
+        <para>Note that <varname>Before=</varname> dependencies on device units have no effect and are not
+        supported.  Devices generally become available as a result of an external hotplug event, and systemd
+        creates the corresponding device unit without delay.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index 2e93715be6fc11b03ceaed8fe414062af8bac8aa..72d8f623991131e8d71730471bd014569ceb7759 100644 (file)
@@ -101,8 +101,8 @@ u     root     0              "Superuser"           /root          /bin/zsh</pro
           <term><varname>u</varname></term>
           <listitem><para>Create a system user and group of the specified name should
           they not exist yet. The user's primary group will be set to the group
-          bearing the same name. The account will be created disabled, so that logins
-          are not allowed.</para></listitem>
+          bearing the same name unless the ID field specifies it. The account will be
+          created disabled, so that logins are not allowed.</para></listitem>
         </varlistentry>
 
         <varlistentry>
@@ -166,9 +166,10 @@ u     root     0              "Superuser"           /root          /bin/zsh</pro
       path's owner/group. This is useful to create users whose UID/GID
       match the owners of pre-existing files (such as SUID or SGID
       binaries).
-      The syntax <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> is also supported to
-      allow creating user and group pairs with different numeric UID and GID values. The group with the indicated GID must get created explicitly before or it must already exist. Specifying <literal>-</literal> for the UID in this syntax
-      is also supported.
+      The syntaxes <literal><replaceable>uid</replaceable>:<replaceable>gid</replaceable></literal> and
+      <literal><replaceable>uid</replaceable>:<replaceable>groupname</replaceable></literal> are supported to
+      allow creating users with specific primary groups. The given group must be created explicitly, or it
+      must already exist. Specifying <literal>-</literal> for the UID in these syntaxes is also supported.
       </para>
 
       <para>For <varname>m</varname> lines, this field should contain
index bfc83db065c41e629e289e68a619ea9ef4ad6bcf..cf492d863759601b59a70ee55e153d369233c318 100644 (file)
@@ -46,7 +46,8 @@ d     /directory/to/create-and-cleanup         mode user group cleanup-age -
 D     /directory/to/create-and-remove          mode user group cleanup-age -
 e     /directory/to/cleanup                    mode user group cleanup-age -
 v     /subvolume-or-directory/to/create        mode user group -           -
-Q     /subvolume/to/create                     mode user group -           -
+q     /subvolume-or-directory/to/create        mode user group -           -
+Q     /subvolume-or-directory/to/create        mode user group -           -
 p     /fifo/to/create                          mode user group -           -
 p+    /fifo/to/[re]create                      mode user group -           -
 L     /symlink/to/create                       -    -    -     -           symlink/target/path
index 09254f818ec16775e40a06bd4a87bc63d4dfca83..9b97ee63d91d943d629670ab87d8a1b8acdb6b5d 100644 (file)
             that is enforced on <filename>systemd-udevd.service</filename>.</para>
             <para>Please also note that <literal>:=</literal> and <literal>=</literal> are clearing
             both, program and builtin commands.</para>
+            <para>In order to activate long-running processes from udev rules, provide a service unit, and
+            pull it in from a udev device using the <varname>SYSTEMD_WANTS</varname> device property. See
+            <citerefentry><refentrytitle>systemd.device</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+            for details.</para>
           </listitem>
         </varlistentry>
 
index 46f3e9c7ba31179196b9d2b3a1251585a671e38f..184dbf5d243d9ca7197e580edffcabca31d397ee 100644 (file)
@@ -243,6 +243,7 @@ conf.set_quoted('SYSTEMD_EXPORT_PATH',                        join_paths(rootlib
 conf.set_quoted('VENDOR_KEYRING_PATH',                        join_paths(rootlibexecdir, 'import-pubring.gpg'))
 conf.set_quoted('USER_KEYRING_PATH',                          join_paths(pkgsysconfdir, 'import-pubring.gpg'))
 conf.set_quoted('DOCUMENT_ROOT',                              join_paths(pkgdatadir, 'gatewayd'))
+conf.set_quoted('SYSTEMD_HOMEWORK_PATH',                      join_paths(rootlibexecdir, 'systemd-homework'))
 conf.set_quoted('SYSTEMD_USERWORK_PATH',                      join_paths(rootlibexecdir, 'systemd-userwork'))
 conf.set10('MEMORY_ACCOUNTING_DEFAULT',                       memory_accounting_default)
 conf.set_quoted('MEMORY_ACCOUNTING_DEFAULT_YES_NO',           memory_accounting_default ? 'yes' : 'no')
@@ -281,7 +282,6 @@ substs.set('userenvgeneratordir',                             userenvgeneratordi
 substs.set('systemshutdowndir',                               systemshutdowndir)
 substs.set('systemsleepdir',                                  systemsleepdir)
 substs.set('CERTIFICATEROOT',                                 get_option('certificate-root'))
-substs.set('SYSTEMCTL',                                       join_paths(rootbindir, 'systemctl'))
 substs.set('RANDOM_SEED',                                     join_paths(randomseeddir, 'random-seed'))
 substs.set('SYSTEM_SYSVINIT_PATH',                            sysvinit_path)
 substs.set('SYSTEM_SYSVRCND_PATH',                            sysvrcnd_path)
@@ -449,9 +449,6 @@ conf.set('_GNU_SOURCE', true)
 conf.set('__SANE_USERSPACE_TYPES__', true)
 conf.set10('HAVE_WSTRINGOP_TRUNCATION', has_wstringop_truncation)
 
-conf.set('SIZEOF_PID_T', cc.sizeof('pid_t', prefix : '#include <sys/types.h>'))
-conf.set('SIZEOF_UID_T', cc.sizeof('uid_t', prefix : '#include <sys/types.h>'))
-conf.set('SIZEOF_GID_T', cc.sizeof('gid_t', prefix : '#include <sys/types.h>'))
 conf.set('SIZEOF_DEV_T', cc.sizeof('dev_t', prefix : '#include <sys/types.h>'))
 conf.set('SIZEOF_INO_T', cc.sizeof('ino_t', prefix : '#include <sys/types.h>'))
 conf.set('SIZEOF_TIME_T', cc.sizeof('time_t', prefix : '#include <sys/time.h>'))
@@ -874,6 +871,27 @@ endif
 libmount = dependency('mount',
                       version : fuzzer_build ? '>= 0' : '>= 2.30')
 
+want_libfdisk = get_option('fdisk')
+if want_libfdisk != 'false' and not skip_deps
+        libfdisk = dependency('fdisk',
+                              required : want_libfdisk == 'true')
+        have = libfdisk.found()
+else
+        have = false
+        libfdisk = []
+endif
+conf.set10('HAVE_LIBFDISK', have)
+
+want_pwquality = get_option('pwquality')
+if want_pwquality != 'false' and not skip_deps
+        libpwquality = dependency('pwquality', required : want_pwquality == 'true')
+        have = libpwquality.found()
+else
+        have = false
+        libpwquality = []
+endif
+conf.set10('HAVE_PWQUALITY', have)
+
 want_seccomp = get_option('seccomp')
 if want_seccomp != 'false' and not skip_deps
         libseccomp = dependency('libseccomp',
@@ -1001,6 +1019,9 @@ if want_libcryptsetup != 'false' and not skip_deps
                                    version : '>= 2.0.1',
                                    required : want_libcryptsetup == 'true')
         have = libcryptsetup.found()
+
+        conf.set10('HAVE_CRYPT_SET_METADATA_SIZE',
+                   have and cc.has_function('crypt_set_metadata_size', dependencies : libcryptsetup))
 else
         have = false
         libcryptsetup = []
@@ -1280,6 +1301,18 @@ conf.set('DEFAULT_DNS_OVER_TLS_MODE',
          'DNS_OVER_TLS_' + default_dns_over_tls.underscorify().to_upper())
 substs.set('DEFAULT_DNS_OVER_TLS_MODE', default_dns_over_tls)
 
+want_repart = get_option('repart')
+if want_repart != 'false'
+        have = (conf.get('HAVE_OPENSSL') == 1 and
+                conf.get('HAVE_LIBFDISK') == 1)
+        if want_repart == 'true' and not have
+                error('repart support was requested, but dependencies are not available')
+        endif
+else
+        have = false
+endif
+conf.set10('ENABLE_REPART', have)
+
 want_importd = get_option('importd')
 if want_importd != 'false'
         have = (conf.get('HAVE_LIBCURL') == 1 and
@@ -1294,6 +1327,22 @@ else
 endif
 conf.set10('ENABLE_IMPORTD', have)
 
+want_homed = get_option('homed')
+if want_homed != 'false'
+        have = (conf.get('HAVE_OPENSSL') == 1 and
+                conf.get('HAVE_LIBFDISK') == 1 and
+                conf.get('HAVE_LIBCRYPTSETUP') == 1)
+        if want_homed == 'true' and not have
+                error('homed support was requested, but dependencies are not available')
+        endif
+else
+        have = false
+endif
+conf.set10('ENABLE_HOMED', have)
+
+have = have and conf.get('HAVE_PAM') == 1
+conf.set10('ENABLE_PAM_HOME', have)
+
 want_remote = get_option('remote')
 if want_remote != 'false'
         have_deps = [conf.get('HAVE_MICROHTTPD') == 1,
@@ -1536,11 +1585,13 @@ subdir('src/coredump')
 subdir('src/pstore')
 subdir('src/hostname')
 subdir('src/import')
+subdir('src/partition')
 subdir('src/kernel-install')
 subdir('src/locale')
 subdir('src/machine')
 subdir('src/portable')
 subdir('src/userdb')
+subdir('src/home')
 subdir('src/nspawn')
 subdir('src/resolve')
 subdir('src/timedate')
@@ -2011,6 +2062,68 @@ if conf.get('ENABLE_USERDB') == 1
                    install_dir : rootbindir)
 endif
 
+if conf.get('ENABLE_HOMED') == 1
+        executable('systemd-homework',
+                   systemd_homework_sources,
+                   include_directories : includes,
+                   link_with : [libshared],
+                   dependencies : [threads,
+                                   libcryptsetup,
+                                   libblkid,
+                                   libcrypt,
+                                   libopenssl,
+                                   libfdisk,
+                                   libp11kit],
+                   install_rpath : rootlibexecdir,
+                   install : true,
+                   install_dir : rootlibexecdir)
+
+        executable('systemd-homed',
+                   systemd_homed_sources,
+                   include_directories : includes,
+                   link_with : [libshared],
+                   dependencies : [threads,
+                                   libcrypt,
+                                   libopenssl,
+                                   libpwquality],
+                   install_rpath : rootlibexecdir,
+                   install : true,
+                   install_dir : rootlibexecdir)
+
+        executable('homectl',
+                   homectl_sources,
+                   include_directories : includes,
+                   link_with : [libshared],
+                   dependencies : [threads,
+                                   libcrypt,
+                                   libopenssl,
+                                   libp11kit,
+                                   libpwquality],
+                   install_rpath : rootlibexecdir,
+                   install : true,
+                   install_dir : rootbindir)
+
+        if conf.get('HAVE_PAM') == 1
+                version_script_arg = join_paths(project_source_root, pam_systemd_home_sym)
+                pam_systemd = shared_library(
+                        'pam_systemd_home',
+                        pam_systemd_home_c,
+                        name_prefix : '',
+                        include_directories : includes,
+                        link_args : ['-shared',
+                                     '-Wl,--version-script=' + version_script_arg],
+                        link_with : [libsystemd_static,
+                                     libshared_static],
+                        dependencies : [threads,
+                                        libpam,
+                                        libpam_misc,
+                                        libcrypt],
+                        link_depends : pam_systemd_home_sym,
+                        install : true,
+                        install_dir : pamlibdir)
+        endif
+endif
+
 foreach alias : ['halt', 'poweroff', 'reboot', 'runlevel', 'shutdown', 'telinit']
         meson.add_install_script(meson_make_symlink,
                                  join_paths(rootbindir, 'systemctl'),
@@ -2382,6 +2495,21 @@ if conf.get('ENABLE_BINFMT') == 1
                                  mkdir_p.format(join_paths(sysconfdir, 'binfmt.d')))
 endif
 
+if conf.get('ENABLE_REPART') == 1
+        executable('systemd-repart',
+                   systemd_repart_sources,
+                   include_directories : includes,
+                   link_with : [libshared],
+                   dependencies : [threads,
+                                   libcryptsetup,
+                                   libblkid,
+                                   libfdisk,
+                                   libopenssl],
+                   install_rpath : rootlibexecdir,
+                   install : true,
+                   install_dir : rootbindir)
+endif
+
 if conf.get('ENABLE_VCONSOLE') == 1
         executable('systemd-vconsole-setup',
                    'src/vconsole/vconsole-setup.c',
@@ -3253,6 +3381,8 @@ missing = []
 foreach tuple : [
         ['libcryptsetup'],
         ['PAM'],
+        ['pwquality'],
+        ['libfdisk'],
         ['p11kit'],
         ['AUDIT'],
         ['IMA'],
@@ -3277,6 +3407,7 @@ foreach tuple : [
         ['libiptc'],
         ['elfutils'],
         ['binfmt'],
+        ['repart'],
         ['vconsole'],
         ['quotacheck'],
         ['tmpfiles'],
@@ -3290,6 +3421,7 @@ foreach tuple : [
         ['machined'],
         ['portabled'],
         ['userdb'],
+        ['homed'],
         ['importd'],
         ['hostnamed'],
         ['timedated'],
index 6736240f394fdff4dccc92fd2aabead5288c168f..d5b6f24344c2b9f96f4ab6ce245eb5bdbb3a727c 100644 (file)
@@ -80,6 +80,8 @@ option('environment-d', type : 'boolean',
        description : 'support for environment.d')
 option('binfmt', type : 'boolean',
        description : 'support for custom binary formats')
+option('repart', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'install the systemd-repart tool')
 option('coredump', type : 'boolean',
        description : 'install the coredump handler')
 option('pstore', type : 'boolean',
@@ -96,6 +98,8 @@ option('portabled', type : 'boolean',
        description : 'install the systemd-portabled stack')
 option('userdb', type : 'boolean',
        description : 'install the systemd-userdbd stack')
+option('homed', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'install the systemd-homed stack')
 option('networkd', type : 'boolean',
        description : 'install the systemd-networkd stack')
 option('timedated', type : 'boolean',
@@ -260,10 +264,14 @@ option('audit', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'libaudit support')
 option('blkid', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'libblkid support')
+option('fdisk', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'libfdisk support')
 option('kmod', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'support for loadable modules')
 option('pam', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'PAM support')
+option('pwquality', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'libpwquality support')
 option('microhttpd', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'libµhttpd support')
 option('libcryptsetup', type : 'combo', choices : ['auto', 'true', 'false'],
index 983f346f1154a9ef01b35ad655c0400d52d7185d..0346a19c82e43cb0154c49d2722ac423f47321cb 100644 (file)
@@ -1,4 +1,5 @@
 src/core/org.freedesktop.systemd1.policy.in
+src/home/org.freedesktop.home1.policy
 src/hostname/org.freedesktop.hostname1.policy
 src/import/org.freedesktop.import1.policy
 src/locale/org.freedesktop.locale1.policy
index 985be305eaa70b43dc6cd578fe614f222e57bd72..010452653b95c4c78f01bf49afe6f3e6e6571b20 100644 (file)
--- a/po/cs.po
+++ b/po/cs.po
@@ -7,8 +7,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: systemd master\n"
 "Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n"
-"POT-Creation-Date: 2019-09-17 14:31+0000\n"
-"PO-Revision-Date: 2019-09-19 15:48+0200\n"
+"POT-Creation-Date: 2020-01-30 15:31+0000\n"
+"PO-Revision-Date: 2020-02-03 16:18+0100\n"
 "Last-Translator: Daniel Rusek <mail@asciiwolf.com>\n"
 "Language-Team: Czech\n"
 "Language: cs\n"
@@ -17,7 +17,7 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
 "|| n%100>=20) ? 1 : 2);\n"
-"X-Generator: Poedit 2.2.3\n"
+"X-Generator: Poedit 2.2.4\n"
 
 #: src/core/org.freedesktop.systemd1.policy.in:22
 msgid "Send passphrase back to system"
@@ -64,7 +64,59 @@ msgstr "Znovu načíst stav systemd"
 
 #: src/core/org.freedesktop.systemd1.policy.in:65
 msgid "Authentication is required to reload the systemd state."
-msgstr "Pro znovu načtení stavu systemd je vyžadováno ověření."
+msgstr "Pro opětovné načtení stavu systemd je vyžadováno ověření."
+
+#: src/home/org.freedesktop.home1.policy:13
+msgid "Create a home"
+msgstr "Vytvořit domovský adresář"
+
+#: src/home/org.freedesktop.home1.policy:14
+msgid "Authentication is required for creating a user's home."
+msgstr "Pro vytvoření domovského adresáře uživatele je vyžadováno ověření."
+
+#: src/home/org.freedesktop.home1.policy:23
+msgid "Remove a home"
+msgstr "Odebrat domovský adresář"
+
+#: src/home/org.freedesktop.home1.policy:24
+msgid "Authentication is required for removing a user's home."
+msgstr "Pro odebrání domovského adresáře uživatele je vyžadováno ověření."
+
+#: src/home/org.freedesktop.home1.policy:33
+msgid "Check credentials of a home"
+msgstr "Zkontrolovat pověření domovského adresáře"
+
+#: src/home/org.freedesktop.home1.policy:34
+msgid ""
+"Authentication is required for checking credentials against a user's home."
+msgstr ""
+"Pro kontrolu pověření proti domovskému adresáři uživatele je vyžadováno "
+"ověření."
+
+#: src/home/org.freedesktop.home1.policy:43
+msgid "Update a home"
+msgstr "Aktualizovat domovský adresář"
+
+#: src/home/org.freedesktop.home1.policy:44
+msgid "Authentication is required for updating a user's home."
+msgstr "Pro aktualizaci domovského adresáře uživatele je vyžadováno ověření."
+
+#: src/home/org.freedesktop.home1.policy:53
+msgid "Resize a home"
+msgstr "Změnit velikost domovského adresáře"
+
+#: src/home/org.freedesktop.home1.policy:54
+msgid "Authentication is required for resizing a user's home."
+msgstr ""
+"Pro změnu velikosti domovského adresáře uživatele je vyžadováno ověření."
+
+#: src/home/org.freedesktop.home1.policy:63
+msgid "Change password of a home"
+msgstr "Změnit heslo domovského adresáře"
+
+#: src/home/org.freedesktop.home1.policy:64
+msgid "Authentication is required for changing the password of a user's home."
+msgstr "Pro změnu hesla domovského adresáře uživatele je vyžadováno ověření."
 
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set host name"
@@ -505,6 +557,14 @@ msgstr "Nastavit zprávu všem uživatelům"
 msgid "Authentication is required to set a wall message"
 msgstr "K nastavení zprávy všem uživatelům je vyžadováno ověření"
 
+#: src/login/org.freedesktop.login1.policy:395
+msgid "Change Session"
+msgstr "Změnit sezení"
+
+#: src/login/org.freedesktop.login1.policy:396
+msgid "Authentication is required to change the virtual terminal."
+msgstr "Pro změnu virtuálního terminálu je vyžadováno ověření."
+
 #: src/machine/org.freedesktop.machine1.policy:22
 msgid "Log into a local container"
 msgstr "Přihlásit se do lokálního kontejneru"
@@ -680,6 +740,30 @@ msgstr "Vrátit změny nastavení DNS"
 msgid "Authentication is required to reset DNS settings."
 msgstr "Pro resetování nastavení DNS je vyžadováno ověření."
 
+#: src/network/org.freedesktop.network1.policy:143
+msgid "Renew dynamic addresses"
+msgstr "Obnovit dynamické adresy"
+
+#: src/network/org.freedesktop.network1.policy:144
+msgid "Authentication is required to renew dynamic addresses."
+msgstr "Pro obnovení dynamických adres je vyžadováno ověření."
+
+#: src/network/org.freedesktop.network1.policy:154
+msgid "Reload network settings"
+msgstr "Znovu načíst nastavení sítě"
+
+#: src/network/org.freedesktop.network1.policy:155
+msgid "Authentication is required to reload network settings."
+msgstr "Pro opětovné načtení nastavení sítě je vyžadováno ověření."
+
+#: src/network/org.freedesktop.network1.policy:165
+msgid "Reconfigure network interface"
+msgstr "Přenastavit síťové rozhraní"
+
+#: src/network/org.freedesktop.network1.policy:166
+msgid "Authentication is required to reconfigure network interface."
+msgstr "Pro přenastavení síťového rozhraní je vyžadováno ověření."
+
 #: src/portable/org.freedesktop.portable1.policy:13
 msgid "Inspect a portable service image"
 msgstr "Prohlédnout obraz přenosné služby"
@@ -770,37 +854,37 @@ msgid ""
 "shall be enabled."
 msgstr "Pro kontrolu synchronizace času ze sítě je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:354
+#: src/core/dbus-unit.c:355
 msgid "Authentication is required to start '$(unit)'."
 msgstr "Pro spuštění „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:355
+#: src/core/dbus-unit.c:356
 msgid "Authentication is required to stop '$(unit)'."
 msgstr "Pro vypnutí „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:356
+#: src/core/dbus-unit.c:357
 msgid "Authentication is required to reload '$(unit)'."
-msgstr "Pro znovu načtení „$(unit)” je vyžadováno ověření."
+msgstr "Pro opětovné načtení „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:357 src/core/dbus-unit.c:358
+#: src/core/dbus-unit.c:358 src/core/dbus-unit.c:359
 msgid "Authentication is required to restart '$(unit)'."
 msgstr "Pro restart „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:530
+#: src/core/dbus-unit.c:531
 msgid ""
 "Authentication is required to send a UNIX signal to the processes of "
 "'$(unit)'."
 msgstr "Pro odeslání UNIX signálu procesům „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:561
+#: src/core/dbus-unit.c:562
 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
 msgstr "Pro resetování chybného stavu „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:594
+#: src/core/dbus-unit.c:595
 msgid "Authentication is required to set properties on '$(unit)'."
 msgstr "Pro nastavení vlastností na „$(unit)” je vyžadováno ověření."
 
-#: src/core/dbus-unit.c:703
+#: src/core/dbus-unit.c:704
 msgid ""
 "Authentication is required to delete files and directories associated with "
 "'$(unit)'."
index 3f0997885069ff5b6e57f7808d62e6e3eca65d16..d256c844ab9c7b344db5bf0d0674b234369cd925 100644 (file)
--- a/po/fr.po
+++ b/po/fr.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: systemd\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-03-07 22:43+0100\n"
+"POT-Creation-Date: 2020-02-03 01:21+0100\n"
 "PO-Revision-Date: 2019-03-07 23:09+0100\n"
 "Last-Translator: Sylvain Plantefève <sylvain.plantefeve@gmail.com>\n"
 "Language-Team: French\n"
@@ -67,6 +67,63 @@ msgstr "Recharger l'état de systemd"
 msgid "Authentication is required to reload the systemd state."
 msgstr "Authentification requise pour recharger l'état de systemd"
 
+#: src/home/org.freedesktop.home1.policy:13
+msgid "Create a home"
+msgstr "Créer un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:14
+msgid "Authentication is required for creating a user's home."
+msgstr ""
+"Authentification requise pour créer l'espace personnel d'un utilisateur."
+
+#: src/home/org.freedesktop.home1.policy:23
+msgid "Remove a home"
+msgstr "Retirer un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:24
+msgid "Authentication is required for removing a user's home."
+msgstr ""
+"Authentification requise pour retirer l'espace personnel d'un utilisateur."
+
+#: src/home/org.freedesktop.home1.policy:33
+msgid "Check credentials of a home"
+msgstr "Vérifier les identifiants d'un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:34
+msgid ""
+"Authentication is required for checking credentials against a user's home."
+msgstr ""
+"Authentification requise pour vérifier les identifiants de l'espace "
+"personnel d'un utilisateur."
+
+#: src/home/org.freedesktop.home1.policy:43
+msgid "Update a home"
+msgstr "Mettre à jour un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:44
+msgid "Authentication is required for updating a user's home."
+msgstr ""
+"Authentification requise pour mettre à jour l'espace personnel d'un "
+"utilisateur."
+
+#: src/home/org.freedesktop.home1.policy:53
+msgid "Resize a home"
+msgstr "Retailler un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:54
+msgid "Authentication is required for resizing a user's home."
+msgstr "Authentification requise pour retailler un espace personnel."
+
+#: src/home/org.freedesktop.home1.policy:63
+msgid "Change password of a home"
+msgstr "Changer le mot de passe d'un espace personnel"
+
+#: src/home/org.freedesktop.home1.policy:64
+msgid "Authentication is required for changing the password of a user's home."
+msgstr ""
+"Authentification requise pour changer le mot de passe de l'espace personnel "
+"d'un utilisateur."
+
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set host name"
 msgstr "Définir le nom d'hôte"
@@ -489,11 +546,20 @@ msgstr ""
 "actives."
 
 #: src/login/org.freedesktop.login1.policy:341
-msgid "Indicate to the firmware to boot to setup interface"
-msgstr ""
-"Indiquer au micrologiciel de démarrer sur l'interface de configuration"
+msgid "Set the reboot \"reason\" in the kernel"
+msgstr "Définir la « raison » du redémarrage dans le noyau"
 
 #: src/login/org.freedesktop.login1.policy:342
+msgid "Authentication is required to set the reboot \"reason\" in the kernel."
+msgstr ""
+"Authentification requise pour définir la « raison » du redémarrage dans le "
+"noyau."
+
+#: src/login/org.freedesktop.login1.policy:352
+msgid "Indicate to the firmware to boot to setup interface"
+msgstr "Indiquer au micrologiciel de démarrer sur l'interface de configuration"
+
+#: src/login/org.freedesktop.login1.policy:353
 msgid ""
 "Authentication is required to indicate to the firmware to boot to setup "
 "interface."
@@ -501,23 +567,23 @@ msgstr ""
 "Authentification requise pour indiquer au micrologiciel de démarrer sur "
 "l'interface de configuration."
 
-#: src/login/org.freedesktop.login1.policy:352
+#: src/login/org.freedesktop.login1.policy:363
 msgid "Indicate to the boot loader to boot to the boot loader menu"
 msgstr "Indiquer au programme d'amorçage d'afficher le menu au démarrage"
 
-#: src/login/org.freedesktop.login1.policy:353
+#: src/login/org.freedesktop.login1.policy:364
 msgid ""
 "Authentication is required to indicate to the boot loader to boot to the "
 "boot loader menu."
 msgstr ""
-"Authentification requise pour indiquer au programme d'amorçage d'afficher "
-"le menu au démarrage."
+"Authentification requise pour indiquer au programme d'amorçage d'afficher le "
+"menu au démarrage."
 
-#: src/login/org.freedesktop.login1.policy:363
+#: src/login/org.freedesktop.login1.policy:374
 msgid "Indicate to the boot loader to boot a specific entry"
 msgstr "Indiquer au programme d'amorçage de démarrer une entrée spécifique"
 
-#: src/login/org.freedesktop.login1.policy:364
+#: src/login/org.freedesktop.login1.policy:375
 msgid ""
 "Authentication is required to indicate to the boot loader to boot into a "
 "specific boot loader entry."
@@ -525,14 +591,22 @@ msgstr ""
 "Authentification requise pour indiquer au programme d'amorçage de démarrer "
 "une entrée spécifique."
 
-#: src/login/org.freedesktop.login1.policy:374
+#: src/login/org.freedesktop.login1.policy:385
 msgid "Set a wall message"
 msgstr "Définir un message wall"
 
-#: src/login/org.freedesktop.login1.policy:375
+#: src/login/org.freedesktop.login1.policy:386
 msgid "Authentication is required to set a wall message"
 msgstr "Authentification requise pour définir un message wall."
 
+#: src/login/org.freedesktop.login1.policy:395
+msgid "Change Session"
+msgstr "Changer de Session"
+
+#: src/login/org.freedesktop.login1.policy:396
+msgid "Authentication is required to change the virtual terminal."
+msgstr "Authentification requise pour changer de terminal virtuel."
+
 #: src/machine/org.freedesktop.machine1.policy:22
 msgid "Log into a local container"
 msgstr "Connexion dans un conteneur local"
@@ -612,6 +686,136 @@ msgstr ""
 "Authentification requise pour gérer les images locales de machines "
 "virtuelles (VM) et de conteneurs."
 
+#: src/network/org.freedesktop.network1.policy:22
+msgid "Set NTP servers"
+msgstr "Définir les serveurs NTP"
+
+#: src/network/org.freedesktop.network1.policy:23
+msgid "Authentication is required to set NTP servers."
+msgstr "Authentification requise pour définir les serveurs NTP."
+
+#: src/network/org.freedesktop.network1.policy:33
+#: src/resolve/org.freedesktop.resolve1.policy:44
+msgid "Set DNS servers"
+msgstr "Définir les serveurs DNS"
+
+#: src/network/org.freedesktop.network1.policy:34
+#: src/resolve/org.freedesktop.resolve1.policy:45
+msgid "Authentication is required to set DNS servers."
+msgstr "Authentification requise pour définir les serveurs DNS."
+
+#: src/network/org.freedesktop.network1.policy:44
+#: src/resolve/org.freedesktop.resolve1.policy:55
+msgid "Set domains"
+msgstr "Définir les domaines"
+
+#: src/network/org.freedesktop.network1.policy:45
+#: src/resolve/org.freedesktop.resolve1.policy:56
+msgid "Authentication is required to set domains."
+msgstr "Authentification requise pour définir les domaines."
+
+#: src/network/org.freedesktop.network1.policy:55
+#: src/resolve/org.freedesktop.resolve1.policy:66
+msgid "Set default route"
+msgstr "Définir la route par défaut"
+
+#: src/network/org.freedesktop.network1.policy:56
+#: src/resolve/org.freedesktop.resolve1.policy:67
+msgid "Authentication is required to set default route."
+msgstr "Authentification requise pour définir la route par défaut."
+
+#: src/network/org.freedesktop.network1.policy:66
+#: src/resolve/org.freedesktop.resolve1.policy:77
+msgid "Enable/disable LLMNR"
+msgstr "Activer/désactiver LLMNR"
+
+#: src/network/org.freedesktop.network1.policy:67
+#: src/resolve/org.freedesktop.resolve1.policy:78
+msgid "Authentication is required to enable or disable LLMNR."
+msgstr "Authentification requise pour activer ou désactiver LLMNR."
+
+#: src/network/org.freedesktop.network1.policy:77
+#: src/resolve/org.freedesktop.resolve1.policy:88
+msgid "Enable/disable multicast DNS"
+msgstr "Activer/désactiver la multidiffusion DNS"
+
+#: src/network/org.freedesktop.network1.policy:78
+#: src/resolve/org.freedesktop.resolve1.policy:89
+msgid "Authentication is required to enable or disable multicast DNS."
+msgstr ""
+"Authentification requise pour activer ou désactiver la multidiffusion DNS."
+
+#: src/network/org.freedesktop.network1.policy:88
+#: src/resolve/org.freedesktop.resolve1.policy:99
+msgid "Enable/disable DNS over TLS"
+msgstr "Activer/désactiver DNS sur TLS"
+
+#: src/network/org.freedesktop.network1.policy:89
+#: src/resolve/org.freedesktop.resolve1.policy:100
+msgid "Authentication is required to enable or disable DNS over TLS."
+msgstr "Authentification requise pour activer ou désactiver DNS sur TLS."
+
+#: src/network/org.freedesktop.network1.policy:99
+#: src/resolve/org.freedesktop.resolve1.policy:110
+msgid "Enable/disable DNSSEC"
+msgstr "Activer/désactiver DNSSEC"
+
+#: src/network/org.freedesktop.network1.policy:100
+#: src/resolve/org.freedesktop.resolve1.policy:111
+msgid "Authentication is required to enable or disable DNSSEC."
+msgstr "Authentification requise pour activer ou désactiver DNSSEC"
+
+#: src/network/org.freedesktop.network1.policy:110
+#: src/resolve/org.freedesktop.resolve1.policy:121
+msgid "Set DNSSEC Negative Trust Anchors"
+msgstr "Définir les Negative Trust Anchors DNSSEC"
+
+#: src/network/org.freedesktop.network1.policy:111
+#: src/resolve/org.freedesktop.resolve1.policy:122
+msgid "Authentication is required to set DNSSEC Negative Trust Anchors."
+msgstr ""
+"Authentification requise pour définir les Negative Trust Anchors DNSSEC."
+
+#: src/network/org.freedesktop.network1.policy:121
+msgid "Revert NTP settings"
+msgstr "Réinitialiser les paramètres NTP"
+
+#: src/network/org.freedesktop.network1.policy:122
+msgid "Authentication is required to reset NTP settings."
+msgstr "Authentification requise pour réinitialiser les paramètres NTP."
+
+#: src/network/org.freedesktop.network1.policy:132
+msgid "Revert DNS settings"
+msgstr "Réinitialiser les paramètres DNS"
+
+#: src/network/org.freedesktop.network1.policy:133
+msgid "Authentication is required to reset DNS settings."
+msgstr "Authentification requise pour réinitialiser les paramètres DNS."
+
+#: src/network/org.freedesktop.network1.policy:143
+msgid "Renew dynamic addresses"
+msgstr "Renouveler les adresses dynamiques"
+
+#: src/network/org.freedesktop.network1.policy:144
+msgid "Authentication is required to renew dynamic addresses."
+msgstr "Authentification requise pour renouveler les adresses dynamiques."
+
+#: src/network/org.freedesktop.network1.policy:154
+msgid "Reload network settings"
+msgstr "Recharger les paramètres réseau"
+
+#: src/network/org.freedesktop.network1.policy:155
+msgid "Authentication is required to reload network settings."
+msgstr "Authentification requise pour recharger les paramètres réseau."
+
+#: src/network/org.freedesktop.network1.policy:165
+msgid "Reconfigure network interface"
+msgstr "Reconfigurer une interface réseau"
+
+#: src/network/org.freedesktop.network1.policy:166
+msgid "Authentication is required to reconfigure network interface."
+msgstr "Authentification requise pour reconfigurer une interface réseau."
+
 #: src/portable/org.freedesktop.portable1.policy:13
 msgid "Inspect a portable service image"
 msgstr "Inspecter une image de service portable"
@@ -658,6 +862,16 @@ msgstr "Retirer un service DNS-SD"
 msgid "Authentication is required to unregister a DNS-SD service"
 msgstr "Authentification requise pour retirer un service DNS-SD"
 
+#: src/resolve/org.freedesktop.resolve1.policy:132
+msgid "Revert name resolution settings"
+msgstr "Réinitialiser les paramètres de résolution de noms"
+
+#: src/resolve/org.freedesktop.resolve1.policy:133
+msgid "Authentication is required to reset name resolution settings."
+msgstr ""
+"Authentification requise pour réinitialiser les paramètres de résolution de "
+"noms."
+
 #: src/timedate/org.freedesktop.timedate1.policy:22
 msgid "Set system time"
 msgstr "Définir l'heure du système"
@@ -700,23 +914,23 @@ msgstr ""
 "Authentification requise pour activer ou désactiver la synchronisation de "
 "l'heure avec le réseau."
 
-#: src/core/dbus-unit.c:326
+#: src/core/dbus-unit.c:355
 msgid "Authentication is required to start '$(unit)'."
 msgstr "Authentification requise pour démarrer « $(unit) »."
 
-#: src/core/dbus-unit.c:327
+#: src/core/dbus-unit.c:356
 msgid "Authentication is required to stop '$(unit)'."
 msgstr "Authentification requise pour arrêter « $(unit) »."
 
-#: src/core/dbus-unit.c:328
+#: src/core/dbus-unit.c:357
 msgid "Authentication is required to reload '$(unit)'."
 msgstr "Authentification requise pour recharger « $(unit) »."
 
-#: src/core/dbus-unit.c:329 src/core/dbus-unit.c:330
+#: src/core/dbus-unit.c:358 src/core/dbus-unit.c:359
 msgid "Authentication is required to restart '$(unit)'."
 msgstr "Authentification requise pour redémarrer « $(unit) »."
 
-#: src/core/dbus-unit.c:437
+#: src/core/dbus-unit.c:531
 msgid ""
 "Authentication is required to send a UNIX signal to the processes of "
 "'$(unit)'."
@@ -724,16 +938,24 @@ msgstr ""
 "Authentification requise pour envoyer un signal UNIX aux processus de "
 "« $(unit) »."
 
-#: src/core/dbus-unit.c:468
+#: src/core/dbus-unit.c:562
 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
 msgstr ""
 "Authentification requise pour réinitialiser l'état d'« échec » de "
 "« $(unit) »."
 
-#: src/core/dbus-unit.c:501
+#: src/core/dbus-unit.c:595
 msgid "Authentication is required to set properties on '$(unit)'."
 msgstr "Authentification requise pour définir des propriétés de « $(unit) »."
 
+#: src/core/dbus-unit.c:704
+msgid ""
+"Authentication is required to delete files and directories associated with "
+"'$(unit)'."
+msgstr ""
+"Authentification requise pour supprimer les fichiers et les dossiers "
+"associés à « $(unit) »."
+
 #~ msgid "Authentication is required to kill '$(unit)'."
 #~ msgstr "Authentification requise pour tuer « $(unit) »."
 
index 1eafe5a6586275d3d2be6f7d778bef075a4a5e2c..19c3ec10ca14a14afdbd2c2c210dd6e64c13cbea 100644 (file)
--- a/po/it.po
+++ b/po/it.po
@@ -2,14 +2,14 @@
 #
 # Italian translation for systemd package
 # Traduzione in italiano per il pacchetto systemd
-# Daniele Medri <dmedri@gmail.com>, 2013-2019.
+# Daniele Medri <dmedri@gmail.com>, 2013-2020.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: systemd\n"
-"Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n"
-"POT-Creation-Date: 2019-05-05 17:02+0200\n"
-"PO-Revision-Date: 2019-05-05 17:13+0200\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2020-01-19 11:37+0100\n"
+"PO-Revision-Date: 2020-01-19 11:46+0100\n"
 "Last-Translator: Daniele Medri <dmedri@gmail.com>\n"
 "Language-Team: Italian\n"
 "Language: it\n"
@@ -168,8 +168,8 @@ msgstr "Consenti alle applicazioni di ritardare lo spegnimento del sistema"
 #: src/login/org.freedesktop.login1.policy:34
 msgid "Authentication is required for an application to delay system shutdown."
 msgstr ""
-"Autenticazione richiesta ad un'applicazione per ritardare lo spegnimento "
-"del sistema."
+"Autenticazione richiesta ad un'applicazione per ritardare lo spegnimento del "
+"sistema."
 
 #: src/login/org.freedesktop.login1.policy:44
 msgid "Allow applications to inhibit system sleep"
@@ -206,8 +206,8 @@ msgstr ""
 #: src/login/org.freedesktop.login1.policy:75
 msgid "Allow applications to inhibit system handling of the power key"
 msgstr ""
-"Consenti alle applicazioni di inibire la gestione di sistema del tasto"
-"accensione"
+"Consenti alle applicazioni di inibire la gestione di sistema del "
+"tastoaccensione"
 
 #: src/login/org.freedesktop.login1.policy:76
 msgid ""
@@ -604,6 +604,137 @@ msgstr ""
 "Autenticazione richiesta per gestire le immagini delle virtual machine e dei "
 "container locali."
 
+#: src/network/org.freedesktop.network1.policy:22
+msgid "Set NTP servers"
+msgstr "Configura server NTP"
+
+#: src/network/org.freedesktop.network1.policy:23
+msgid "Authentication is required to set NTP servers."
+msgstr "Autenticazione richiesta per configurare i server NTP."
+
+#: src/network/org.freedesktop.network1.policy:33
+#: src/resolve/org.freedesktop.resolve1.policy:44
+msgid "Set DNS servers"
+msgstr "Configura i server DNS"
+
+#: src/network/org.freedesktop.network1.policy:34
+#: src/resolve/org.freedesktop.resolve1.policy:45
+msgid "Authentication is required to set DNS servers."
+msgstr "Autenticazione richiesta per configurare i server DNS."
+
+#: src/network/org.freedesktop.network1.policy:44
+#: src/resolve/org.freedesktop.resolve1.policy:55
+msgid "Set domains"
+msgstr "Configura domini"
+
+#: src/network/org.freedesktop.network1.policy:45
+#: src/resolve/org.freedesktop.resolve1.policy:56
+msgid "Authentication is required to set domains."
+msgstr "Autenticazione richiesta per configurare i domini."
+
+#: src/network/org.freedesktop.network1.policy:55
+#: src/resolve/org.freedesktop.resolve1.policy:66
+msgid "Set default route"
+msgstr "Configura la tabella di instradamento"
+
+#: src/network/org.freedesktop.network1.policy:56
+#: src/resolve/org.freedesktop.resolve1.policy:67
+msgid "Authentication is required to set default route."
+msgstr ""
+"Autenticazione richiesta per configurare la tabella di instradamento "
+"predefinita."
+
+#: src/network/org.freedesktop.network1.policy:66
+#: src/resolve/org.freedesktop.resolve1.policy:77
+msgid "Enable/disable LLMNR"
+msgstr "Abilita/disabilita LLMNR"
+
+#: src/network/org.freedesktop.network1.policy:67
+#: src/resolve/org.freedesktop.resolve1.policy:78
+msgid "Authentication is required to enable or disable LLMNR."
+msgstr "Autenticazione richiesta per attivare/disattivare LLMNR."
+
+#: src/network/org.freedesktop.network1.policy:77
+#: src/resolve/org.freedesktop.resolve1.policy:88
+msgid "Enable/disable multicast DNS"
+msgstr "Abilita/disabilita DNS multicast"
+
+#: src/network/org.freedesktop.network1.policy:78
+#: src/resolve/org.freedesktop.resolve1.policy:89
+msgid "Authentication is required to enable or disable multicast DNS."
+msgstr "Autenticazione richiesta per abilitare/disabilitare DNS multicast."
+
+#: src/network/org.freedesktop.network1.policy:88
+#: src/resolve/org.freedesktop.resolve1.policy:99
+msgid "Enable/disable DNS over TLS"
+msgstr "Abilita/disabilita DNS su TLS"
+
+#: src/network/org.freedesktop.network1.policy:89
+#: src/resolve/org.freedesktop.resolve1.policy:100
+msgid "Authentication is required to enable or disable DNS over TLS."
+msgstr "Autenticazione richiesta per abilitare o disabilitare DNS su TLS."
+
+#: src/network/org.freedesktop.network1.policy:99
+#: src/resolve/org.freedesktop.resolve1.policy:110
+msgid "Enable/disable DNSSEC"
+msgstr "Abilita/disabilita DNSSEC"
+
+#: src/network/org.freedesktop.network1.policy:100
+#: src/resolve/org.freedesktop.resolve1.policy:111
+msgid "Authentication is required to enable or disable DNSSEC."
+msgstr "Autenticazione richiesta per abilitare o disabilitare DNSSEC."
+
+#: src/network/org.freedesktop.network1.policy:110
+#: src/resolve/org.freedesktop.resolve1.policy:121
+msgid "Set DNSSEC Negative Trust Anchors"
+msgstr "Configura DNSSEC Negative Trust Anchors"
+
+#: src/network/org.freedesktop.network1.policy:111
+#: src/resolve/org.freedesktop.resolve1.policy:122
+msgid "Authentication is required to set DNSSEC Negative Trust Anchors."
+msgstr ""
+"Autenticazione richiesta per configurare DNSSEC Negative Trust Anchors."
+
+#: src/network/org.freedesktop.network1.policy:121
+msgid "Revert NTP settings"
+msgstr "Ripristina configurazioni NTP"
+
+#: src/network/org.freedesktop.network1.policy:122
+msgid "Authentication is required to reset NTP settings."
+msgstr "Autenticazione richiesta per ripristinare le configurazioni NTP."
+
+#: src/network/org.freedesktop.network1.policy:132
+msgid "Revert DNS settings"
+msgstr "Ripristina configurazioni DNS"
+
+#: src/network/org.freedesktop.network1.policy:133
+msgid "Authentication is required to reset DNS settings."
+msgstr "Autenticazione richiesta per ripristinare le configurazioni DNS."
+
+#: src/network/org.freedesktop.network1.policy:143
+msgid "Renew dynamic addresses"
+msgstr "Rinnova indirizzi dinamici"
+
+#: src/network/org.freedesktop.network1.policy:144
+msgid "Authentication is required to renew dynamic addresses."
+msgstr "Autenticazione richiesta per rinnovare gli indirizzi dinamici."
+
+#: src/network/org.freedesktop.network1.policy:154
+msgid "Reload network settings"
+msgstr "Ricarica configurazioni di rete"
+
+#: src/network/org.freedesktop.network1.policy:155
+msgid "Authentication is required to reload network settings."
+msgstr "Autenticazione richiesta per ricaricare le configurazioni di rete."
+
+#: src/network/org.freedesktop.network1.policy:165
+msgid "Reconfigure network interface"
+msgstr "Riconfigura interfaccia di rete"
+
+#: src/network/org.freedesktop.network1.policy:166
+msgid "Authentication is required to reconfigure network interface."
+msgstr "Autenticazione richiesta per riconfigurare l'interfaccia di rete."
+
 #: src/portable/org.freedesktop.portable1.policy:13
 msgid "Inspect a portable service image"
 msgstr "Ispeziona un'immagine di servizio portabile"
@@ -652,6 +783,16 @@ msgid "Authentication is required to unregister a DNS-SD service"
 msgstr ""
 "Autenticazione richiesta per annullare la registrazione di un servizio DNS-SD"
 
+#: src/resolve/org.freedesktop.resolve1.policy:132
+msgid "Revert name resolution settings"
+msgstr "Ripristina le configurazioni per la risoluzione dei nomi"
+
+#: src/resolve/org.freedesktop.resolve1.policy:133
+msgid "Authentication is required to reset name resolution settings."
+msgstr ""
+"Autenticazione richiesta per ripristinare le configurazioni per la "
+"risoluzione dei nomi."
+
 #: src/timedate/org.freedesktop.timedate1.policy:22
 msgid "Set system time"
 msgstr "Configura l'orario di sistema"
@@ -694,23 +835,23 @@ msgstr ""
 "Autenticazione richiesta per verificare se la sincronizzazione dell'orario "
 "in rete deve essere attivata."
 
-#: src/core/dbus-unit.c:317
+#: src/core/dbus-unit.c:354
 msgid "Authentication is required to start '$(unit)'."
 msgstr "Autenticazione richiesta per avviare '$(unit)'."
 
-#: src/core/dbus-unit.c:318
+#: src/core/dbus-unit.c:355
 msgid "Authentication is required to stop '$(unit)'."
 msgstr "Autenticazione richiesta per fermare '$(unit)'."
 
-#: src/core/dbus-unit.c:319
+#: src/core/dbus-unit.c:356
 msgid "Authentication is required to reload '$(unit)'."
 msgstr "Autenticazione richiesta per ricaricare '$(unit)'."
 
-#: src/core/dbus-unit.c:320 src/core/dbus-unit.c:321
+#: src/core/dbus-unit.c:357 src/core/dbus-unit.c:358
 msgid "Authentication is required to restart '$(unit)'."
 msgstr "Autenticazione richiesta per riavviare '$(unit)'."
 
-#: src/core/dbus-unit.c:493
+#: src/core/dbus-unit.c:530
 msgid ""
 "Authentication is required to send a UNIX signal to the processes of "
 "'$(unit)'."
@@ -718,11 +859,19 @@ msgstr ""
 "Autenticazione richiesta per inviare un segnale UNIX ai processi di "
 "'$(unit)'."
 
-#: src/core/dbus-unit.c:524
+#: src/core/dbus-unit.c:561
 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
 msgstr ""
 "Autenticazione richiesta per riconfigurare lo stato \"fallito\" di '$(unit)'."
 
-#: src/core/dbus-unit.c:557
+#: src/core/dbus-unit.c:594
 msgid "Authentication is required to set properties on '$(unit)'."
 msgstr "Autenticazione richiesta per configurare le proprietà di '$(unit)'."
+
+#: src/core/dbus-unit.c:703
+msgid ""
+"Authentication is required to delete files and directories associated with "
+"'$(unit)'."
+msgstr ""
+"Autenticazione richiesta per eliminare i file e le directory associate a "
+"'$(unit)'."
index 55106f4b01839702dd43bc37ec24acaf9a5de7c2..63a96c1238c15d1555b45ef12a2a72664e7f1b4b 100644 (file)
--- a/po/ja.po
+++ b/po/ja.po
@@ -6,7 +6,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: systemd\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2019-09-21 20:13+0900\n"
+"POT-Creation-Date: 2020-02-02 23:20+0900\n"
 "PO-Revision-Date: 2018-10-27 07:41+0900\n"
 "Last-Translator: Yu Watanabe <watanabe.yu+github@gmail.com>\n"
 "Language-Team: \n"
@@ -60,6 +60,55 @@ msgstr "systemdの状態の再読込"
 msgid "Authentication is required to reload the systemd state."
 msgstr "systemdの状態を再読込するには認証が必要です。"
 
+#: src/home/org.freedesktop.home1.policy:13
+msgid "Create a home"
+msgstr "ホームディレクトリの作成"
+
+#: src/home/org.freedesktop.home1.policy:14
+msgid "Authentication is required for creating a user's home."
+msgstr "ユーザのホームディレクトリを作成するには認証が必要です。"
+
+#: src/home/org.freedesktop.home1.policy:23
+msgid "Remove a home"
+msgstr "ホームディレクトリの削除"
+
+#: src/home/org.freedesktop.home1.policy:24
+msgid "Authentication is required for removing a user's home."
+msgstr "ユーザのホームディレクトリの削除には認証が必要です。"
+
+#: src/home/org.freedesktop.home1.policy:33
+msgid "Check credentials of a home"
+msgstr "ホームディレクトリの認証情報の確認"
+
+#: src/home/org.freedesktop.home1.policy:34
+msgid ""
+"Authentication is required for checking credentials against a user's home."
+msgstr "ユーザのホームディレクトリに対する認証情報の確認には認証が必要です。"
+
+#: src/home/org.freedesktop.home1.policy:43
+msgid "Update a home"
+msgstr "ホームディレクトリの更新"
+
+#: src/home/org.freedesktop.home1.policy:44
+msgid "Authentication is required for updating a user's home."
+msgstr "ユーザのホームディレクトリの更新には認証が必要です。"
+
+#: src/home/org.freedesktop.home1.policy:53
+msgid "Resize a home"
+msgstr "ホームディレクトリのサイズ変更"
+
+#: src/home/org.freedesktop.home1.policy:54
+msgid "Authentication is required for resizing a user's home."
+msgstr "ユーザのホームディレクトリのサイズ変更には認証が必要です。"
+
+#: src/home/org.freedesktop.home1.policy:63
+msgid "Change password of a home"
+msgstr "ホームディレクトリのパスワード変更"
+
+#: src/home/org.freedesktop.home1.policy:64
+msgid "Authentication is required for changing the password of a user's home."
+msgstr "ユーザのホームディレクトリのパスワードを変更するには認証が必要です。"
+
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set host name"
 msgstr "ホスト名の設定"
@@ -481,6 +530,14 @@ msgstr "全ユーザへのメッセージの設定"
 msgid "Authentication is required to set a wall message"
 msgstr "全ユーザへのメッセージを設定するには認証が必要です。"
 
+#: src/login/org.freedesktop.login1.policy:395
+msgid "Change Session"
+msgstr "セッションの変更"
+
+#: src/login/org.freedesktop.login1.policy:396
+msgid "Authentication is required to change the virtual terminal."
+msgstr "仮想ターミナルを変更するには認証が必要です。"
+
 #: src/machine/org.freedesktop.machine1.policy:22
 msgid "Log into a local container"
 msgstr "ローカルなコンテナへログイン"
@@ -653,6 +710,30 @@ msgstr "DNSの設定を破棄"
 msgid "Authentication is required to reset DNS settings."
 msgstr "DNSの設定を破棄するには認証が必要です。"
 
+#: src/network/org.freedesktop.network1.policy:143
+msgid "Renew dynamic addresses"
+msgstr "動的アドレスの更新"
+
+#: src/network/org.freedesktop.network1.policy:144
+msgid "Authentication is required to renew dynamic addresses."
+msgstr "動的アドレスの更新には認証が必要です。"
+
+#: src/network/org.freedesktop.network1.policy:154
+msgid "Reload network settings"
+msgstr "ネットワークの設定の再読み込み"
+
+#: src/network/org.freedesktop.network1.policy:155
+msgid "Authentication is required to reload network settings."
+msgstr "ネットワークの設定を再読み込みするには認証が必要です。"
+
+#: src/network/org.freedesktop.network1.policy:165
+msgid "Reconfigure network interface"
+msgstr "ネットワークインターフェイスの再設定"
+
+#: src/network/org.freedesktop.network1.policy:166
+msgid "Authentication is required to reconfigure network interface."
+msgstr "ネットワークインターフェイスの再設定には認証が必要です。"
+
 #: src/portable/org.freedesktop.portable1.policy:13
 msgid "Inspect a portable service image"
 msgstr "ポータブルサービスイメージの読み込み"
@@ -741,37 +822,37 @@ msgid ""
 "shall be enabled."
 msgstr "ネットワーク経由の時刻同期を有効もしくは無効にするには認証が必要です。"
 
-#: src/core/dbus-unit.c:354
+#: src/core/dbus-unit.c:355
 msgid "Authentication is required to start '$(unit)'."
 msgstr "'$(unit)'を開始するには認証が必要です。"
 
-#: src/core/dbus-unit.c:355
+#: src/core/dbus-unit.c:356
 msgid "Authentication is required to stop '$(unit)'."
 msgstr "'$(unit)'を停止するには認証が必要です。"
 
-#: src/core/dbus-unit.c:356
+#: src/core/dbus-unit.c:357
 msgid "Authentication is required to reload '$(unit)'."
 msgstr "'$(unit)'を再読込するには認証が必要です。"
 
-#: src/core/dbus-unit.c:357 src/core/dbus-unit.c:358
+#: src/core/dbus-unit.c:358 src/core/dbus-unit.c:359
 msgid "Authentication is required to restart '$(unit)'."
 msgstr "'$(unit)'を再起動するには認証が必要です。"
 
-#: src/core/dbus-unit.c:530
+#: src/core/dbus-unit.c:531
 msgid ""
 "Authentication is required to send a UNIX signal to the processes of "
 "'$(unit)'."
 msgstr "'$(unit)'のプロセスにUNIXシグナルを送るには認証が必要です。"
 
-#: src/core/dbus-unit.c:561
+#: src/core/dbus-unit.c:562
 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
 msgstr "'$(unit)'の「失敗」状態をリセットするには認証が必要です。"
 
-#: src/core/dbus-unit.c:594
+#: src/core/dbus-unit.c:595
 msgid "Authentication is required to set properties on '$(unit)'."
 msgstr "'$(unit)'のプロパティを設定するには認証が必要です。"
 
-#: src/core/dbus-unit.c:703
+#: src/core/dbus-unit.c:704
 msgid ""
 "Authentication is required to delete files and directories associated with "
 "'$(unit)'."
index 6441eb78dcc14320b8d106934fa09d26f029f105..4a0ff5af36a80945d86b2fdca70a8311d6fc7313 100644 (file)
--- a/po/pl.po
+++ b/po/pl.po
@@ -6,8 +6,8 @@ msgid ""
 msgstr ""
 "Project-Id-Version: systemd\n"
 "Report-Msgid-Bugs-To: https://github.com/systemd/systemd/issues\n"
-"POT-Creation-Date: 2019-10-26 14:02+0000\n"
-"PO-Revision-Date: 2019-10-26 16:05+0200\n"
+"POT-Creation-Date: 2020-01-30 15:31+0000\n"
+"PO-Revision-Date: 2020-02-02 13:20+0100\n"
 "Last-Translator: Piotr Drąg <piotrdrag@gmail.com>\n"
 "Language-Team: Polish <trans-pl@lists.fedoraproject.org>\n"
 "Language: pl\n"
@@ -69,6 +69,63 @@ msgstr "Ponowne wczytanie stanu systemd"
 msgid "Authentication is required to reload the systemd state."
 msgstr "Wymagane jest uwierzytelnienie, aby ponownie wczytać stan systemd."
 
+#: src/home/org.freedesktop.home1.policy:13
+msgid "Create a home"
+msgstr "Utworzenie katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:14
+msgid "Authentication is required for creating a user's home."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby utworzyć katalog domowy użytkownika."
+
+#: src/home/org.freedesktop.home1.policy:23
+msgid "Remove a home"
+msgstr "Usunięcie katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:24
+msgid "Authentication is required for removing a user's home."
+msgstr "Wymagane jest uwierzytelnienie, aby usunąć katalog domowy użytkownika."
+
+#: src/home/org.freedesktop.home1.policy:33
+msgid "Check credentials of a home"
+msgstr "Sprawdzenie danych uwierzytelniających katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:34
+msgid ""
+"Authentication is required for checking credentials against a user's home."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby sprawdzić dane uwierzytelniające "
+"katalogu domowego użytkownika."
+
+#: src/home/org.freedesktop.home1.policy:43
+msgid "Update a home"
+msgstr "Aktualizacja katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:44
+msgid "Authentication is required for updating a user's home."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zaktualizować katalog domowy użytkownika."
+
+#: src/home/org.freedesktop.home1.policy:53
+msgid "Resize a home"
+msgstr "Zmiana rozmiaru katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:54
+msgid "Authentication is required for resizing a user's home."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zmienić rozmiar katalogu domowego "
+"użytkownika."
+
+#: src/home/org.freedesktop.home1.policy:63
+msgid "Change password of a home"
+msgstr "Zmiana hasła katalogu domowego"
+
+#: src/home/org.freedesktop.home1.policy:64
+msgid "Authentication is required for changing the password of a user's home."
+msgstr ""
+"Wymagane jest uwierzytelnienie, aby zmienić hasło katalogu domowego "
+"użytkownika."
+
 #: src/hostname/org.freedesktop.hostname1.policy:20
 msgid "Set host name"
 msgstr "Ustawienie nazwy komputera"
@@ -522,6 +579,14 @@ msgstr "Ustawienie komunikatu wall"
 msgid "Authentication is required to set a wall message"
 msgstr "Wymagane jest uwierzytelnienie, aby ustawić komunikat wall"
 
+#: src/login/org.freedesktop.login1.policy:395
+msgid "Change Session"
+msgstr "Zmiana sesji"
+
+#: src/login/org.freedesktop.login1.policy:396
+msgid "Authentication is required to change the virtual terminal."
+msgstr "Wymagane jest uwierzytelnienie, aby zmienić terminal wirtualny."
+
 #: src/machine/org.freedesktop.machine1.policy:22
 msgid "Log into a local container"
 msgstr "Logowanie do lokalnego kontenera"
@@ -830,25 +895,25 @@ msgstr ""
 "Wymagane jest uwierzytelnienie, aby kontrolować, czy włączyć synchronizację "
 "czasu przez sieć."
 
-#: src/core/dbus-unit.c:354
+#: src/core/dbus-unit.c:355
 msgid "Authentication is required to start '$(unit)'."
 msgstr "Wymagane jest uwierzytelnienie, aby uruchomić jednostkę „$(unit)”."
 
-#: src/core/dbus-unit.c:355
+#: src/core/dbus-unit.c:356
 msgid "Authentication is required to stop '$(unit)'."
 msgstr "Wymagane jest uwierzytelnienie, aby zatrzymać jednostkę „$(unit)”."
 
-#: src/core/dbus-unit.c:356
+#: src/core/dbus-unit.c:357
 msgid "Authentication is required to reload '$(unit)'."
 msgstr ""
 "Wymagane jest uwierzytelnienie, aby ponownie wczytać jednostkę „$(unit)”."
 
-#: src/core/dbus-unit.c:357 src/core/dbus-unit.c:358
+#: src/core/dbus-unit.c:358 src/core/dbus-unit.c:359
 msgid "Authentication is required to restart '$(unit)'."
 msgstr ""
 "Wymagane jest uwierzytelnienie, aby ponownie uruchomić jednostkę „$(unit)”."
 
-#: src/core/dbus-unit.c:530
+#: src/core/dbus-unit.c:531
 msgid ""
 "Authentication is required to send a UNIX signal to the processes of "
 "'$(unit)'."
@@ -856,18 +921,18 @@ msgstr ""
 "Wymagane jest uwierzytelnienie, aby wysłać sygnał uniksowy do procesów "
 "jednostki „$(unit)”."
 
-#: src/core/dbus-unit.c:561
+#: src/core/dbus-unit.c:562
 msgid "Authentication is required to reset the \"failed\" state of '$(unit)'."
 msgstr ""
 "Wymagane jest uwierzytelnienie, aby przywrócić stan „failed” (niepowodzenia) "
 "jednostki „$(unit)”."
 
-#: src/core/dbus-unit.c:594
+#: src/core/dbus-unit.c:595
 msgid "Authentication is required to set properties on '$(unit)'."
 msgstr ""
 "Wymagane jest uwierzytelnienie, aby ustawić właściwości jednostki „$(unit)”."
 
-#: src/core/dbus-unit.c:703
+#: src/core/dbus-unit.c:704
 msgid ""
 "Authentication is required to delete files and directories associated with "
 "'$(unit)'."
index 11960e54236a42ca309298fae17cc70212a50508..4009cbb96e659b5a0c51a73cfef0d0067ac7a2e8 100644 (file)
@@ -7,8 +7,9 @@
 #  the Free Software Foundation; either version 2.1 of the License, or
 #  (at your option) any later version.
 
-# These ones should be enabled by default, even if distributions
-# generally follow a default-off policy.
+# Settings for systemd units distributed with systemd itself. Most of these
+# should be enabled by default, even if the distribution follows a general
+# default-off policy.
 
 enable remote-fs.target
 enable remote-cryptsetup.target
@@ -34,3 +35,18 @@ disable syslog.socket
 disable systemd-journal-gatewayd.*
 disable systemd-journal-remote.*
 disable systemd-journal-upload.*
+
+# Passive targets: always off by default, since they should only be pulled in
+# by dependent units.
+
+disable cryptsetup-pre.target
+disable getty-pre.target
+disable local-fs-pre.target
+disable network.target
+disable network-pre.target
+disable nss-lookup.target
+disable nss-user-lookup.target
+disable remote-fs-pre.target
+disable rpcbind.target
+disable time-set.target
+disable time-sync.target
index 22fe41fc337937ac0c34c65125263923cd62be16..fd402c8c11cf04fb01c6979e8c3113dd5e4e6feb 100644 (file)
@@ -7,8 +7,15 @@
 #  the Free Software Foundation; either version 2.1 of the License, or
 #  (at your option) any later version.
 
-# These ones should be enabled by default, even if distributions
-# generally follow a default-off policy.
+# Settings for systemd units distributed with systemd itself. These should be
+# enabled by default, even if the distribution follows a general default-off
+# policy.
 
 enable systemd-tmpfiles-setup.service
 enable systemd-tmpfiles-clean.timer
+
+# Passive targets: always off by default, since they should only be pulled in
+# by dependent units.
+
+disable graphical-session-pre.target
+disable graphical-session.target
index 504ada59ee0daee3a87e35e66f5840c4c3dfcb05..2bf8ce0d52eaea880c965696472f271b3b91de37 100755 (executable)
@@ -35,6 +35,8 @@ while [ -z "\$(ip route list 0/0)" ]; do sleep 1; done
 apt-get -q --allow-releaseinfo-change update
 apt-get -y dist-upgrade
 apt-get install -y eatmydata
+# The following four are needed as long as these deps are not covered by Debian's own packaging
+apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
 apt-get purge --auto-remove -y unattended-upgrades
 systemctl unmask systemd-networkd
 systemctl enable systemd-networkd
index 7fa17c99f52e0123d8344419f929dc02dd4f3564..ee782e5689ffec04ce11439729451747f1142804 100644 (file)
@@ -65,6 +65,7 @@ struct security_info {
         bool protect_kernel_modules;
         bool protect_kernel_tunables;
         bool protect_kernel_logs;
+        bool protect_clock;
 
         char *protect_home;
         char *protect_system;
@@ -746,7 +747,7 @@ static const struct security_assessor security_assessor_table[] = {
         {
                 .id = "ProtectControlGroups=",
                 .description_good = "Service cannot modify the control group file system",
-                .description_bad = "Service may modify to the control group file system",
+                .description_bad = "Service may modify the control group file system",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
                 .weight = 1000,
                 .range = 1,
@@ -783,6 +784,16 @@ static const struct security_assessor security_assessor_table[] = {
                 .assess = assess_bool,
                 .offset = offsetof(struct security_info, protect_kernel_logs),
         },
+        {
+                .id = "ProtectClock=",
+                .description_good = "Service cannot write to the hardware clock or system clock",
+                .description_bad = "Service may write to the hardware clock or system clock",
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectClock=",
+                .weight = 1000,
+                .range = 1,
+                .assess = assess_bool,
+                .offset = offsetof(struct security_info, protect_clock),
+        },
         {
                 .id = "ProtectHome=",
                 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
@@ -1907,6 +1918,7 @@ static int acquire_security_info(sd_bus *bus, const char *name, struct security_
                 { "ProtectKernelModules",    "b",       NULL,                                    offsetof(struct security_info, protect_kernel_modules)    },
                 { "ProtectKernelTunables",   "b",       NULL,                                    offsetof(struct security_info, protect_kernel_tunables)   },
                 { "ProtectKernelLogs",       "b",       NULL,                                    offsetof(struct security_info, protect_kernel_logs)       },
+                { "ProtectClock",            "b",       NULL,                                    offsetof(struct security_info, protect_clock)             },
                 { "ProtectSystem",           "s",       NULL,                                    offsetof(struct security_info, protect_system)            },
                 { "RemoveIPC",               "b",       NULL,                                    offsetof(struct security_info, remove_ipc)                },
                 { "RestrictAddressFamilies", "(bas)",   property_read_restrict_address_families, 0                                                         },
@@ -1984,6 +1996,10 @@ static int acquire_security_info(sd_bus *bus, const char *name, struct security_
         if (info->protect_kernel_logs)
                 info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYSLOG);
 
+        if (info->protect_clock)
+                info->capability_bounding_set &= ~((UINT64_C(1) << CAP_SYS_TIME) |
+                                                   (UINT64_C(1) << CAP_WAKE_ALARM));
+
         if (info->private_devices)
                 info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
                                                    (UINT64_C(1) << CAP_SYS_RAWIO));
index ea43abd7b3f63ca7d8342197ae4c20276df45270..502c3a0c4448aa54565674e7372bd660ec8c43f7 100644 (file)
@@ -20,6 +20,7 @@
 #include "strv.h"
 #include "time-util.h"
 #include "utf8.h"
+#include "virt.h"
 
 #if ENABLE_EFI
 
@@ -221,6 +222,41 @@ int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *v)
         return efi_set_variable(vendor, name, u16, (char16_strlen(u16) + 1) * sizeof(char16_t));
 }
 
+bool is_efi_boot(void) {
+        if (detect_container() > 0)
+                return false;
+
+        return access("/sys/firmware/efi/", F_OK) >= 0;
+}
+
+static int read_flag(const char *varname) {
+        _cleanup_free_ void *v = NULL;
+        uint8_t b;
+        size_t s;
+        int r;
+
+        if (!is_efi_boot()) /* If this is not an EFI boot, assume the queried flags are zero */
+                return 0;
+
+        r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s);
+        if (r < 0)
+                return r;
+
+        if (s != 1)
+                return -EINVAL;
+
+        b = *(uint8_t *)v;
+        return !!b;
+}
+
+bool is_efi_secure_boot(void) {
+        return read_flag("SecureBoot") > 0;
+}
+
+bool is_efi_secure_boot_setup_mode(void) {
+        return read_flag("SetupMode") > 0;
+}
+
 int systemd_efi_options_variable(char **line) {
         const char *e;
         int r;
index 46ca58d0a52fd2dec3119f44fbd1b2a431fbe627..13a33c66053de6c5446598b38b9761d32e9dd201 100644 (file)
@@ -28,6 +28,10 @@ int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p);
 int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size);
 int efi_set_variable_string(sd_id128_t vendor, const char *name, const char *p);
 
+bool is_efi_boot(void);
+bool is_efi_secure_boot(void);
+bool is_efi_secure_boot_setup_mode(void);
+
 int systemd_efi_options_variable(char **line);
 
 #else
@@ -52,6 +56,18 @@ static inline int efi_set_variable_string(sd_id128_t vendor, const char *name, c
         return -EOPNOTSUPP;
 }
 
+static inline bool is_efi_boot(void) {
+        return false;
+}
+
+static inline bool is_efi_secure_boot(void) {
+        return false;
+}
+
+static inline bool is_efi_secure_boot_setup_mode(void) {
+        return false;
+}
+
 static inline int systemd_efi_options_variable(char **line) {
         return -ENODATA;
 }
index 33a6f204f55f5efb92e673c752cfac3f72a27d91..c5c44d2e7d8e940b087edf28e10e7c69031745e8 100644 (file)
@@ -102,7 +102,7 @@ char *cescape(const char *s) {
         return cescape_length(s, strlen(s));
 }
 
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) {
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul) {
         int r = 1;
 
         assert(p);
@@ -171,7 +171,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                         return -EINVAL;
 
                 /* Don't allow NUL bytes */
-                if (a == 0 && b == 0)
+                if (a == 0 && b == 0 && !accept_nul)
                         return -EINVAL;
 
                 *ret = (a << 4U) | b;
@@ -199,7 +199,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                 c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3];
 
                 /* Don't allow 0 chars */
-                if (c == 0)
+                if (c == 0 && !accept_nul)
                         return -EINVAL;
 
                 *ret = c;
@@ -227,7 +227,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                     ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] <<  8U) | ((uint32_t) a[6] <<  4U) |  (uint32_t) a[7];
 
                 /* Don't allow 0 chars */
-                if (c == 0)
+                if (c == 0 && !accept_nul)
                         return -EINVAL;
 
                 /* Don't allow invalid code points */
@@ -267,7 +267,7 @@ int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit)
                         return -EINVAL;
 
                 /* don't allow NUL bytes */
-                if (a == 0 && b == 0 && c == 0)
+                if (a == 0 && b == 0 && c == 0 && !accept_nul)
                         return -EINVAL;
 
                 /* Don't allow bytes above 255 */
@@ -333,7 +333,7 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
                         return -EINVAL;
                 }
 
-                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit);
+                k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit, flags & UNESCAPE_ACCEPT_NUL);
                 if (k < 0) {
                         if (flags & UNESCAPE_RELAX) {
                                 /* Invalid escape code, let's take it literal then */
@@ -360,14 +360,6 @@ int cunescape_length_with_prefix(const char *s, size_t length, const char *prefi
         return t - r;
 }
 
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
-        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
-}
-
-int cunescape(const char *s, UnescapeFlags flags, char **ret) {
-        return cunescape_length(s, strlen(s), flags, ret);
-}
-
 char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits) {
         char *ans, *t, *prev, *prev2;
         const char *f;
index b26054c5df8abb765e7c68163c3c47ca41906c22..b8eb137c3d398bf4e6adf48c33d3f7721eaac7d4 100644 (file)
 #define SHELL_NEED_ESCAPE_POSIX "\\\'"
 
 typedef enum UnescapeFlags {
-        UNESCAPE_RELAX = 1,
+        UNESCAPE_RELAX      = 1 << 0,
+        UNESCAPE_ACCEPT_NUL = 1 << 1,
 } UnescapeFlags;
 
 typedef enum EscapeStyle {
         ESCAPE_BACKSLASH = 1,
-        ESCAPE_POSIX = 2,
+        ESCAPE_POSIX     = 2,
 } EscapeStyle;
 
 char *cescape(const char *s);
 char *cescape_length(const char *s, size_t n);
 int cescape_char(char c, char *buf);
 
-int cunescape(const char *s, UnescapeFlags flags, char **ret);
-int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret);
 int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret);
-int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit);
+static inline int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) {
+        return cunescape_length_with_prefix(s, length, NULL, flags, ret);
+}
+static inline int cunescape(const char *s, UnescapeFlags flags, char **ret) {
+        return cunescape_length(s, strlen(s), flags, ret);
+}
+int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit, bool accept_nul);
 
 char *xescape_full(const char *s, const char *bad, size_t console_width, bool eight_bits);
 static inline char *xescape(const char *s, const char *bad) {
index d7c215cb48e7a267e4bc5f3e2f28d19d62aaeec9..ac9bf6099d87f62a78f9d9f9c7d01475db97121e 100644 (file)
@@ -90,7 +90,7 @@ int extract_first_word(const char **p, char **ret, const char *separators, Extra
                                 bool eight_bit = false;
                                 char32_t u;
 
-                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit);
+                                r = cunescape_one(*p, (size_t) -1, &u, &eight_bit, false);
                                 if (r < 0) {
                                         if (flags & EXTRACT_CUNESCAPE_RELAX) {
                                                 s[sz++] = '\\';
index 59622508a333624a602895577361b76a2397ec0c..29fbc947b3b67223306198b4c51966d4d8f39743 100644 (file)
@@ -5,30 +5,17 @@
 #include <net/if.h>
 #include <stdbool.h>
 
-#if SIZEOF_PID_T == 4
-#  define PID_PRI PRIi32
-#elif SIZEOF_PID_T == 2
-#  define PID_PRI PRIi16
-#else
-#  error Unknown pid_t size
-#endif
+#include "macro.h"
+
+assert_cc(sizeof(pid_t) == sizeof(int32_t));
+#define PID_PRI PRIi32
 #define PID_FMT "%" PID_PRI
 
-#if SIZEOF_UID_T == 4
-#  define UID_FMT "%" PRIu32
-#elif SIZEOF_UID_T == 2
-#  define UID_FMT "%" PRIu16
-#else
-#  error Unknown uid_t size
-#endif
+assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+#define UID_FMT "%" PRIu32
 
-#if SIZEOF_GID_T == 4
-#  define GID_FMT "%" PRIu32
-#elif SIZEOF_GID_T == 2
-#  define GID_FMT "%" PRIu16
-#else
-#  error Unknown gid_t size
-#endif
+assert_cc(sizeof(gid_t) == sizeof(uint32_t));
+#define GID_FMT "%" PRIu32
 
 #if SIZEOF_TIME_T == 8
 #  define PRI_TIME PRIi64
index 5723c845e436285ec6f5b01a2e0fe4051789feaf..f8095e85d82cebe11ffa1e225e34beedbad31129 100644 (file)
@@ -797,6 +797,14 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (r < 0)
                         return r;
 
+                /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
+                 * end. While we won't resolve the root path we still simplify it. Note that dropping the
+                 * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
+                 * anyway. Moreover at the end of this function after processing everything we'll always turn
+                 * the empty string back to "/". */
+                delete_trailing_chars(root, "/");
+                path_simplify(root, true);
+
                 if (flags & CHASE_PREFIX_ROOT) {
                         /* We don't support relative paths in combination with a root directory */
                         if (!path_is_absolute(path))
@@ -810,7 +818,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
         if (r < 0)
                 return r;
 
-        fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+        fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
         if (fd < 0)
                 return -errno;
 
@@ -819,6 +827,31 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                         return -errno;
         }
 
+        if (root) {
+                _cleanup_free_ char *absolute = NULL;
+                const char *e;
+
+                /* If we are operating on a root directory, let's take the root directory as it is. */
+
+                e = path_startswith(buffer, root);
+                if (!e)
+                        return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
+                                              SYNTHETIC_ERRNO(ECHRNG),
+                                              "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+                                              path, root);
+
+                done = strdup(root);
+                if (!done)
+                        return -ENOMEM;
+
+                /* Make sure "todo" starts with a slash */
+                absolute = strjoin("/", e);
+                if (!absolute)
+                        return -ENOMEM;
+
+                free_and_replace(buffer, absolute);
+        }
+
         todo = buffer;
         for (;;) {
                 _cleanup_free_ char *first = NULL;
@@ -828,6 +861,15 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
 
                 /* Determine length of first component in the path */
                 n = strspn(todo, "/");                  /* The slashes */
+
+                if (n > 1) {
+                        /* If we are looking at more than a single slash then skip all but one, so that when
+                         * we are done with everything we have a normalized path with only single slashes
+                         * separating the path components. */
+                        todo += n - 1;
+                        n = 1;
+                }
+
                 m = n + strcspn(todo + n, "/");         /* The entire length of the component */
 
                 /* Extract the first component. */
@@ -930,7 +972,6 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                 if (fstat(child, &st) < 0)
                         return -errno;
                 if ((flags & CHASE_SAFE) &&
-                    (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) &&
                     unsafe_transition(&previous_stat, &st))
                         return log_unsafe_transition(fd, child, path, flags);
 
@@ -961,7 +1002,7 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags,
                                  * directory as base. */
 
                                 safe_close(fd);
-                                fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+                                fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
                                 if (fd < 0)
                                         return -errno;
 
index dc625119355f7c3e042f32d47d8acf05100f7bd3..96151ffbf8cc5c13058f0e0149cb8e60a9946f93 100644 (file)
@@ -345,6 +345,9 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_MU]                      = "u",
                         [SPECIAL_GLYPH_CHECK_MARK]              = "+",
                         [SPECIAL_GLYPH_CROSS_MARK]              = "-",
+                        [SPECIAL_GLYPH_LIGHT_SHADE]             = "-",
+                        [SPECIAL_GLYPH_DARK_SHADE]              = "X",
+                        [SPECIAL_GLYPH_SIGMA]                   = "S",
                         [SPECIAL_GLYPH_ARROW]                   = "->",
                         [SPECIAL_GLYPH_ELLIPSIS]                = "...",
                         [SPECIAL_GLYPH_ECSTATIC_SMILEY]         = ":-]",
@@ -371,6 +374,9 @@ const char *special_glyph(SpecialGlyph code) {
                         [SPECIAL_GLYPH_MU]                      = "\316\274",                 /* μ (actually called: GREEK SMALL LETTER MU) */
                         [SPECIAL_GLYPH_CHECK_MARK]              = "\342\234\223",             /* ✓ */
                         [SPECIAL_GLYPH_CROSS_MARK]              = "\342\234\227",             /* ✗ (actually called: BALLOT X) */
+                        [SPECIAL_GLYPH_LIGHT_SHADE]             = "\342\226\221",             /* ░ */
+                        [SPECIAL_GLYPH_DARK_SHADE]              = "\342\226\223",             /* ▒ */
+                        [SPECIAL_GLYPH_SIGMA]                   = "\316\243",                 /* Σ */
 
                         /* Single glyph in Unicode, two in ASCII */
                         [SPECIAL_GLYPH_ARROW]                   = "\342\206\222",             /* → (actually called: RIGHTWARDS ARROW) */
index 1df8ac4cb0ce631a78a0ecea9da113976dd5dafa..cefc4e7f0ed0ea1493b8f6ed881b8b427efe71e2 100644 (file)
@@ -51,6 +51,9 @@ typedef enum {
         SPECIAL_GLYPH_CROSS_MARK,
         SPECIAL_GLYPH_ARROW,
         SPECIAL_GLYPH_ELLIPSIS,
+        SPECIAL_GLYPH_LIGHT_SHADE,
+        SPECIAL_GLYPH_DARK_SHADE,
+        SPECIAL_GLYPH_SIGMA,
         _SPECIAL_GLYPH_FIRST_SMILEY,
         SPECIAL_GLYPH_ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_SMILEY,
         SPECIAL_GLYPH_HAPPY_SMILEY,
index b644c4a4d6043eb51069c55e112d93158d69cc13..e0094b0f370a8943774b8ef5066420990441487a 100644 (file)
@@ -698,6 +698,22 @@ int parse_ip_port_range(const char *s, uint16_t *low, uint16_t *high) {
         return 0;
 }
 
+int parse_ip_prefix_length(const char *s, int *ret) {
+        unsigned l;
+        int r;
+
+        r = safe_atou(s, &l);
+        if (r < 0)
+                return r;
+
+        if (l > 128)
+                return -ERANGE;
+
+        *ret = (int) l;
+
+        return 0;
+}
+
 int parse_dev(const char *s, dev_t *ret) {
         const char *major;
         unsigned x, y;
index c6d1d249672222eed0a5c0e52a1d41a05e86d8b5..6b30c727e3f98f1cb0e712a2983700caff6c5733 100644 (file)
@@ -112,4 +112,6 @@ 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_prefix_length(const char *s, int *ret);
+
 int parse_oom_score_adjust(const char *s, int *ret);
index d3d99d9a7f90228222eaa09de6b778a048b8a95c..1af58717c6868f514cc07df7d744a4b1a39954fb 100644 (file)
@@ -39,6 +39,18 @@ int proc_cmdline(char **ret) {
                 return read_one_line_file("/proc/cmdline", ret);
 }
 
+/* In SecureBoot mode this is probably not what you want. As your cmdline is
+ * cryptographically signed like when using Type #2 EFI Unified Kernel Images
+ * (https://systemd.io/BOOT_LOADER_SPECIFICATION/) The user's intention is then
+ * that the cmdline should not be modified.  You want to make sure that the
+ * system starts up as exactly specified in the signed artifact. */
+static int systemd_options_variable(char **line) {
+        if (is_efi_secure_boot())
+                return -ENODATA;
+
+        return systemd_efi_options_variable(line);
+}
+
 static int proc_cmdline_extract_first(const char **p, char **ret_word, ProcCmdlineFlags flags) {
         const char *q = *p;
         int r;
@@ -119,7 +131,7 @@ int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, ProcCmdlineF
 
         /* We parse the EFI variable first, because later settings have higher priority. */
 
-        r = systemd_efi_options_variable(&line);
+        r = systemd_options_variable(&line);
         if (r < 0 && r != -ENODATA)
                 log_debug_errno(r, "Failed to get SystemdOptions EFI variable, ignoring: %m");
 
@@ -250,7 +262,7 @@ int proc_cmdline_get_key(const char *key, ProcCmdlineFlags flags, char **ret_val
                 return r;
 
         line = mfree(line);
-        r = systemd_efi_options_variable(&line);
+        r = systemd_options_variable(&line);
         if (r == -ENODATA)
                 return false; /* Not found */
         if (r < 0)
index a238b25796a2e67510e5ec25bec4912379fda13a..4160af45ba7bcfef5bf9432180c54a29268a22ec 100644 (file)
@@ -172,7 +172,6 @@ int fork_agent(const char *name, const int except[], size_t n_except, pid_t *pid
 
 int set_oom_score_adjust(int value);
 
-#if SIZEOF_PID_T == 4
 /* The highest possibly (theoretic) pid_t value on this architecture. */
 #define PID_T_MAX ((pid_t) INT32_MAX)
 /* The maximum number of concurrent processes Linux allows on this architecture, as well as the highest valid PID value
@@ -182,12 +181,6 @@ int set_oom_score_adjust(int value);
  * these values are documented in proc(5) we feel quite confident that they are stable enough for the near future at
  * least to define them here too. */
 #define TASKS_MAX 4194303U
-#elif SIZEOF_PID_T == 2
-#define PID_T_MAX ((pid_t) INT16_MAX)
-#define TASKS_MAX 32767U
-#else
-#error "Unknown pid_t size"
-#endif
 
 assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX);
 
index 61809ab06559376f6c29a1893993ee1593ae4302..8f812d7cbe07ebdb31e3dd581796c8144327b68b 100644 (file)
@@ -1054,6 +1054,8 @@ bool string_is_safe(const char *p) {
         if (!p)
                 return false;
 
+        /* Checks if the specified string contains no quotes or control characters */
+
         for (t = p; *t; t++) {
                 if (*t > 0 && *t < ' ') /* no control characters */
                         return false;
index 74d20a9a95a6b5477bb2097f1dd764e0006814f9..096cb4e5d4d2fa545d99dba10d468d36471e4d9f 100644 (file)
@@ -57,20 +57,15 @@ char *strv_find_startswith(char * const *l, const char *name) {
         return NULL;
 }
 
-void strv_clear(char **l) {
+char **strv_free(char **l) {
         char **k;
 
         if (!l)
-                return;
+                return NULL;
 
         for (k = l; *k; k++)
                 free(*k);
 
-        *l = NULL;
-}
-
-char **strv_free(char **l) {
-        strv_clear(l);
         return mfree(l);
 }
 
index 85a49ab5c3fa24abd1eafeb3f4c688f6abf0714a..e7c2b1a604fc36835f7a13353f746a3794aa0043 100644 (file)
@@ -25,8 +25,6 @@ char **strv_free_erase(char **l);
 DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase);
 #define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep)
 
-void strv_clear(char **l);
-
 char **strv_copy(char * const *l);
 size_t strv_length(char * const *l) _pure_;
 
index 29c9ec9ac45d747ed6e55ad5b9606d9bca5fc3b5..caeba46db43ec191730db3a8a1a9bd339b34a0a8 100644 (file)
@@ -2,10 +2,15 @@
 
 #include <syslog.h>
 
+#include "sd-id128.h"
+
+#include "glob-util.h"
 #include "hexdecoct.h"
 #include "macro.h"
+#include "path-util.h"
 #include "string-table.h"
 #include "syslog-util.h"
+#include "unit-name.h"
 
 int syslog_parse_priority(const char **p, int *priority, bool with_facility) {
         int a = 0, b = 0, c = 0;
@@ -96,3 +101,31 @@ DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG);
 bool log_level_is_valid(int level) {
         return level >= 0 && level <= LOG_DEBUG;
 }
+
+/* The maximum size for a log namespace length. This is the file name size limit 255 minus the size of a
+ * formatted machine ID minus a separator char */
+#define LOG_NAMESPACE_MAX (NAME_MAX - (SD_ID128_STRING_MAX - 1) - 1)
+
+bool log_namespace_name_valid(const char *s) {
+        /* Let's make sure the namespace fits in a filename that is prefixed with the machine ID and a dot
+         * (so that /var/log/journal/<machine-id>.<namespace> can be created based on it). Also make sure it
+         * is suitable as unit instance name, and does not contain fishy characters. */
+
+        if (!filename_is_valid(s))
+                return false;
+
+        if (strlen(s) > LOG_NAMESPACE_MAX)
+                return false;
+
+        if (!unit_instance_is_valid(s))
+                return false;
+
+        if (!string_is_safe(s))
+                return false;
+
+        /* Let's avoid globbing for now */
+        if (string_is_glob(s))
+                return false;
+
+        return true;
+}
index 8f419e8151a40cad362eb0744109e070c4c8d621..998641fa01dc0f93f2d10f00cfb6078182642322 100644 (file)
@@ -12,3 +12,5 @@ int log_level_from_string(const char *s);
 bool log_level_is_valid(int level);
 
 int syslog_parse_priority(const char **p, int *priority, bool with_facility);
+
+bool log_namespace_name_valid(const char *s);
index 68c6dd0da78a3a238bc1fbb201657ddbb45939d7..a491f5505e210610825d79f2d6d85d98cdb5a400 100644 (file)
@@ -62,6 +62,29 @@ int parse_uid(const char *s, uid_t *ret) {
         return 0;
 }
 
+int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper) {
+        uint32_t u, l;
+        int r;
+
+        assert(s);
+        assert(ret_lower);
+        assert(ret_upper);
+
+        r = parse_range(s, &l, &u);
+        if (r < 0)
+                return r;
+
+        if (l > u)
+                return -EINVAL;
+
+        if (!uid_is_valid(l) || !uid_is_valid(u))
+                return -ENXIO;
+
+        *ret_lower = l;
+        *ret_upper = u;
+        return 0;
+}
+
 char* getlogname_malloc(void) {
         uid_t uid;
         struct stat st;
@@ -496,11 +519,9 @@ int getgroups_alloc(gid_t** gids) {
 
                 free(allocated);
 
-                allocated = new(gid_t, ngroups);
+                p = allocated = new(gid_t, ngroups);
                 if (!allocated)
                         return -ENOMEM;
-
-                p = allocated;
         }
 
         *gids = TAKE_PTR(p);
index 714e83082c9a15ee3b7d43e0ba65e83d9356bcf0..6796ac42c1df445a5cfb8bfa5b0d7ceeceede242 100644 (file)
@@ -19,6 +19,7 @@ static inline bool gid_is_valid(gid_t gid) {
 }
 
 int parse_uid(const char *s, uid_t* ret_uid);
+int parse_uid_range(const char *s, uid_t *ret_lower, uid_t *ret_upper);
 
 static inline int parse_gid(const char *s, gid_t *ret_gid) {
         return parse_uid(s, (uid_t*) ret_gid);
index 12bf77e7013a140930bffff0cdeabbb371fae98b..07831634da784666ed43c9015b433dbb5de87cf7 100644 (file)
@@ -20,6 +20,7 @@
 #include "string-util.h"
 #include "virt.h"
 
+#if defined(__i386__) || defined(__x86_64__)
 static const char *const vm_table[_VIRTUALIZATION_MAX] = {
         [VIRTUALIZATION_XEN]       = "XenVMMXenVMM",
         [VIRTUALIZATION_KVM]       = "KVMKVMKVM",
@@ -36,6 +37,7 @@ static const char *const vm_table[_VIRTUALIZATION_MAX] = {
 };
 
 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(vm, int);
+#endif
 
 static int detect_vm_cpuid(void) {
 
index e1a1a685279a7fddbeaa73b9c06c553f4fe2db74..99938c547a25cd7d846aa4c9707c57fdcb07f378 100644 (file)
@@ -404,10 +404,10 @@ static VOID print_status(Config *config, CHAR16 *loaded_image_path) {
                 Print(L"random-seed-mode:       off\n");
                 break;
         case RANDOM_SEED_WITH_SYSTEM_TOKEN:
-                Print(L"random-seed-node:       with-system-token\n");
+                Print(L"random-seed-mode:       with-system-token\n");
                 break;
         case RANDOM_SEED_ALWAYS:
-                Print(L"random-seed-node:       always\n");
+                Print(L"random-seed-mode:       always\n");
                 break;
         default:
                 ;
@@ -1893,8 +1893,8 @@ static VOID config_entry_add_linux(
                 UINTN bufsize = sizeof buf;
                 EFI_FILE_INFO *f;
                 CHAR8 *sections[] = {
-                        (UINT8 *)".osrel",
-                        (UINT8 *)".cmdline",
+                        (CHAR8 *)".osrel",
+                        (CHAR8 *)".cmdline",
                         NULL
                 };
                 UINTN offs[ELEMENTSOF(sections)-1] = {};
index 6a10cafff020e3b1638e8bc1488d00c9ffca5902..02aab1ec7fc29b8491e62cf6dd15a7949bc18f56 100644 (file)
@@ -22,10 +22,10 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) {
         UINTN size;
         BOOLEAN secure = FALSE;
         CHAR8 *sections[] = {
-                (UINT8 *)".cmdline",
-                (UINT8 *)".linux",
-                (UINT8 *)".initrd",
-                (UINT8 *)".splash",
+                (CHAR8 *)".cmdline",
+                (CHAR8 *)".linux",
+                (CHAR8 *)".initrd",
+                (CHAR8 *)".splash",
                 NULL
         };
         UINTN addrs[ELEMENTSOF(sections)-1] = {};
index da743dcb9f2ced5e98d48c43126e12f001c54b07..b44f051d95d0019ad267ca872add8dffb0f01cfc 100644 (file)
@@ -189,7 +189,7 @@ static INTN utf8_to_16(CHAR8 *stra, CHAR16 *c) {
         UINTN len;
         UINTN i;
 
-        if (stra[0] < 0x80)
+        if (!(stra[0] & 0x80))
                 len = 1;
         else if ((stra[0] & 0xe0) == 0xc0)
                 len = 2;
index 8518dfde77e0f6f53a1b42adc429de773857a69d..b13f22476c6737893a11e1df21b62abe849f58ba 100644 (file)
@@ -55,7 +55,7 @@ static inline void FileHandleClosep(EFI_FILE_HANDLE *handle) {
         uefi_call_wrapper((*handle)->Close, 1, *handle);
 }
 
-const EFI_GUID loader_guid;
+extern const EFI_GUID loader_guid;
 
 #define UINTN_MAX (~(UINTN)0)
 #define INTN_MAX ((INTN)(UINTN_MAX>>1))
index 659621256b1e67c7204780d2a8087fd358da66cb..5f3d5ddf390f19f23f13104ba477737acb802136 100644 (file)
@@ -1158,7 +1158,7 @@ static int introspect(int argc, char **argv, void *userdata) {
 }
 
 static int message_dump(sd_bus_message *m, FILE *f) {
-        return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER);
+        return sd_bus_message_dump(m, f, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 }
 
 static int message_pcap(sd_bus_message *m, FILE *f) {
@@ -2052,7 +2052,7 @@ static int call(int argc, char **argv, void *userdata) {
                 } else if (arg_verbose) {
                         (void) pager_open(arg_pager_flags);
 
-                        r = bus_message_dump(reply, stdout, 0);
+                        r = sd_bus_message_dump(reply, stdout, 0);
                         if (r < 0)
                                 return r;
                 } else {
@@ -2158,7 +2158,7 @@ static int get_property(int argc, char **argv, void *userdata) {
                 } else if (arg_verbose) {
                         (void) pager_open(arg_pager_flags);
 
-                        r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY);
+                        r = sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY);
                         if (r < 0)
                                 return r;
                 } else {
index 628b9ccaaa0dc3d40f0db00560a7bca6bd6b2e2d..8a2984a2d4c936c645898cad96bca285825294c5 100644 (file)
@@ -21,16 +21,16 @@ static int build_user_json(const char *user_name, uid_t uid, JsonVariant **ret)
         assert(ret);
 
         return json_build(ret, JSON_BUILD_OBJECT(
-                                          JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
-                                                                          JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
-                                                                          JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
-                                                                          JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(uid)),
-                                                                          JSON_BUILD_PAIR("realName", JSON_BUILD_STRING("Dynamic User")),
-                                                                          JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/")),
-                                                                          JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
-                                                                          JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
-                                                                          JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
-                                                                          JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
+                                   JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
+                                       JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
+                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(uid)),
+                                       JSON_BUILD_PAIR("realName", JSON_BUILD_STRING("Dynamic User")),
+                                       JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING("/")),
+                                       JSON_BUILD_PAIR("shell", JSON_BUILD_STRING(NOLOGIN)),
+                                       JSON_BUILD_PAIR("locked", JSON_BUILD_BOOLEAN(true)),
+                                       JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
+                                       JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
 }
 
 static bool user_match_lookup_parameters(LookupParameters *p, const char *name, uid_t uid) {
@@ -134,12 +134,12 @@ static int build_group_json(const char *group_name, gid_t gid, JsonVariant **ret
         assert(ret);
 
         return json_build(ret, JSON_BUILD_OBJECT(
-                                          JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
-                                                                          JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
-                                                                          JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
-                                                                          JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
-                                                                          JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
-}
+                                   JSON_BUILD_PAIR("record", JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(group_name)),
+                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid)),
+                                       JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.DynamicUser")),
+                                       JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("dynamic"))))));
+    }
 
 static bool group_match_lookup_parameters(LookupParameters *p, const char *name, gid_t gid) {
         assert(p);
index c6772ba843160968c314d4dbadcba1017f99de23..9ff3f157f556d8fa0a7982104eb6999f69c49d96 100644 (file)
@@ -766,6 +766,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
         SD_BUS_PROPERTY("LogRateLimitIntervalUSec", "t", bus_property_get_usec, offsetof(ExecContext, log_ratelimit_interval_usec), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogRateLimitBurst", "u", bus_property_get_unsigned, offsetof(ExecContext, log_ratelimit_burst), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("LogExtraFields", "aay", property_get_log_extra_fields, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("LogNamespace", "s", NULL, offsetof(ExecContext, log_namespace), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("CapabilityBoundingSet", "t", NULL, offsetof(ExecContext, capability_bounding_set), SD_BUS_VTABLE_PROPERTY_CONST),
         SD_BUS_PROPERTY("AmbientCapabilities", "t", NULL, offsetof(ExecContext, capability_ambient_set), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1284,6 +1285,9 @@ int bus_exec_context_set_transient_property(
         if (streq(name, "ProtectKernelLogs"))
                 return bus_set_transient_bool(u, name, &c->protect_kernel_logs, message, flags, error);
 
+        if (streq(name, "ProtectClock"))
+                return bus_set_transient_bool(u, name, &c->protect_clock, message, flags, error);
+
         if (streq(name, "ProtectControlGroups"))
                 return bus_set_transient_bool(u, name, &c->protect_control_groups, message, flags, error);
 
@@ -1433,6 +1437,32 @@ int bus_exec_context_set_transient_property(
 
                 return 1;
 
+        } else if (streq(name, "LogNamespace")) {
+                const char *n;
+
+                r = sd_bus_message_read(message, "s", &n);
+                if (r < 0)
+                        return r;
+
+                if (!isempty(n) && !log_namespace_name_valid(n))
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log namespace name not valid");
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+
+                        if (isempty(n)) {
+                                c->log_namespace = mfree(c->log_namespace);
+                                unit_write_settingf(u, flags, name, "%s=", name);
+                        } else {
+                                r = free_and_strdup(&c->log_namespace, n);
+                                if (r < 0)
+                                        return r;
+
+                                unit_write_settingf(u, flags, name, "%s=%s", name, n);
+                        }
+                }
+
+                return 1;
+
         } else if (streq(name, "LogExtraFields")) {
                 size_t n = 0;
 
index c751e84253e0f557c44545ac2752a14262e34559..b45d51f613b6af885d8eaabb1ea801181be59242 100644 (file)
@@ -1663,7 +1663,7 @@ static int method_lookup_dynamic_user_by_uid(sd_bus_message *message, void *user
         assert(message);
         assert(m);
 
-        assert_cc(sizeof(uid) == sizeof(uint32_t));
+        assert_cc(sizeof(uid_t) == sizeof(uint32_t));
         r = sd_bus_message_read_basic(message, 'u', &uid);
         if (r < 0)
                 return r;
index 1c5fd2a23b83b2f38379c7b8bd36f7b099278110..73d5b2ee1ebec925a30aba1be2f5aaea1b88c73f 100644 (file)
@@ -5,6 +5,7 @@
 #include "alloc-util.h"
 #include "bpf-firewall.h"
 #include "bus-common-errors.h"
+#include "bus-polkit.h"
 #include "cgroup-util.h"
 #include "condition.h"
 #include "dbus-job.h"
index 941219fa1ef0b368305b4d284516e6dc400ca602..c86e049c78b0b9235b544ecf9d9296449773bca7 100644 (file)
@@ -10,7 +10,7 @@
 #include "bus-common-errors.h"
 #include "bus-error.h"
 #include "bus-internal.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "dbus-automount.h"
 #include "dbus-cgroup.h"
 #include "dbus-device.h"
index e00109ee4ac979dd97999560d47c9e79d9d95718..f1819b36bc2350ebddb2becad0dd7228d2e0ec20 100644 (file)
@@ -770,7 +770,7 @@ int dynamic_creds_acquire(DynamicCreds *creds, Manager *m, const char *user, con
 
                 if (creds->user && (!group || streq_ptr(user, group)))
                         creds->group = dynamic_user_ref(creds->user);
-                else {
+                else if (group) {
                         r = dynamic_user_acquire(m, group, &creds->group);
                         if (r < 0) {
                                 if (acquired)
index f3d2005637f19dfe510e310238a239d0a0e95718..27777c0d85952d1d97c8a42a242c8939933f0eaa 100644 (file)
@@ -265,15 +265,27 @@ static int open_null_as(int flags, int nfd) {
         return move_fd(fd, nfd, false);
 }
 
-static int connect_journal_socket(int fd, uid_t uid, gid_t gid) {
-        static const union sockaddr_union sa = {
+static int connect_journal_socket(
+                int fd,
+                const char *log_namespace,
+                uid_t uid,
+                gid_t gid) {
+
+        union sockaddr_union sa = {
                 .un.sun_family = AF_UNIX,
-                .un.sun_path = "/run/systemd/journal/stdout",
         };
         uid_t olduid = UID_INVALID;
         gid_t oldgid = GID_INVALID;
+        const char *j;
         int r;
 
+        j = log_namespace ?
+                strjoina("/run/systemd/journal.", log_namespace, "/stdout") :
+                "/run/systemd/journal/stdout";
+        r = sockaddr_un_set_path(&sa.un, j);
+        if (r < 0)
+                return r;
+
         if (gid_is_valid(gid)) {
                 oldgid = getgid();
 
@@ -328,7 +340,7 @@ static int connect_logger_as(
         if (fd < 0)
                 return -errno;
 
-        r = connect_journal_socket(fd, uid, gid);
+        r = connect_journal_socket(fd, context->log_namespace, uid, gid);
         if (r < 0)
                 return r;
 
@@ -1402,6 +1414,7 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
                 c->restrict_realtime ||
                 c->restrict_suid_sgid ||
                 exec_context_restrict_namespaces_set(c) ||
+                c->protect_clock ||
                 c->protect_kernel_tunables ||
                 c->protect_kernel_modules ||
                 c->protect_kernel_logs ||
@@ -1564,6 +1577,19 @@ static int apply_protect_kernel_logs(const Unit *u, const ExecContext *c) {
         return seccomp_protect_syslog();
 }
 
+static int apply_protect_clock(const Unit *u, const ExecContext *c)  {
+        assert(u);
+        assert(c);
+
+        if (!c->protect_clock)
+                return 0;
+
+        if (skip_seccomp_unavailable(u, "ProtectClock="))
+                return 0;
+
+        return seccomp_load_syscall_filter_set(SCMP_ACT_ALLOW, syscall_filter_sets + SYSCALL_FILTER_SET_CLOCK, SCMP_ACT_ERRNO(EPERM), false);
+}
+
 static int apply_private_devices(const Unit *u, const ExecContext *c) {
         assert(u);
         assert(c);
@@ -1672,7 +1698,7 @@ static int build_environment(
         assert(p);
         assert(ret);
 
-        our_env = new0(char*, 14 + _EXEC_DIRECTORY_TYPE_MAX);
+        our_env = new0(char*, 15 + _EXEC_DIRECTORY_TYPE_MAX);
         if (!our_env)
                 return -ENOMEM;
 
@@ -1781,6 +1807,14 @@ static int build_environment(
                 our_env[n_env++] = x;
         }
 
+        if (c->log_namespace) {
+                x = strjoin("LOG_NAMESPACE=", c->log_namespace);
+                if (!x)
+                        return -ENOMEM;
+
+                our_env[n_env++] = x;
+        }
+
         for (t = 0; t < _EXEC_DIRECTORY_TYPE_MAX; t++) {
                 _cleanup_free_ char *pre = NULL, *joined = NULL;
                 const char *n;
@@ -1905,6 +1939,9 @@ static bool exec_needs_mount_namespace(
              !strv_isempty(context->directories[EXEC_DIRECTORY_LOGS].paths)))
                 return true;
 
+        if (context->log_namespace)
+                return true;
+
         return false;
 }
 
@@ -2503,6 +2540,9 @@ static bool insist_on_sandboxing(
                 if (!path_equal(bind_mounts[i].source, bind_mounts[i].destination))
                         return true;
 
+        if (context->log_namespace)
+                return true;
+
         return false;
 }
 
@@ -2586,10 +2626,11 @@ static int apply_mount_namespace(
                             context->n_temporary_filesystems,
                             tmp,
                             var,
+                            context->log_namespace,
                             needs_sandboxing ? context->protect_home : PROTECT_HOME_NO,
                             needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO,
                             context->mount_flags,
-                            DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK,
+                            DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK,
                             error_path);
 
         /* If we couldn't set up the namespace this is probably due to a missing capability. setup_namespace() reports
@@ -3797,6 +3838,12 @@ static int exec_child(
                         return log_unit_error_errno(unit, r, "Failed to apply kernel log restrictions: %m");
                 }
 
+                r = apply_protect_clock(unit, context);
+                if (r < 0) {
+                        *exit_status = EXIT_SECCOMP;
+                        return log_unit_error_errno(unit, r, "Failed to apply clock restrictions: %m");
+                }
+
                 r = apply_private_devices(unit, context);
                 if (r < 0) {
                         *exit_status = EXIT_SECCOMP;
@@ -4120,6 +4167,8 @@ void exec_context_done(ExecContext *c) {
         c->stdin_data_size = 0;
 
         c->network_namespace_path = mfree(c->network_namespace_path);
+
+        c->log_namespace = mfree(c->log_namespace);
 }
 
 int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
@@ -4437,6 +4486,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 "%sProtectKernelTunables: %s\n"
                 "%sProtectKernelModules: %s\n"
                 "%sProtectKernelLogs: %s\n"
+                "%sProtectClock: %s\n"
                 "%sProtectControlGroups: %s\n"
                 "%sPrivateNetwork: %s\n"
                 "%sPrivateUsers: %s\n"
@@ -4458,6 +4508,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 prefix, yes_no(c->protect_kernel_tunables),
                 prefix, yes_no(c->protect_kernel_modules),
                 prefix, yes_no(c->protect_kernel_logs),
+                prefix, yes_no(c->protect_clock),
                 prefix, yes_no(c->protect_control_groups),
                 prefix, yes_no(c->private_network),
                 prefix, yes_no(c->private_users),
@@ -4653,6 +4704,9 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 }
         }
 
+        if (c->log_namespace)
+                fprintf(f, "%sLogNamespace: %s\n", prefix, c->log_namespace);
+
         if (c->secure_bits) {
                 _cleanup_free_ char *str = NULL;
 
index c923b1fa21304fd64c8600105497e631c8874835..09c1510aafd3b9c28680036451251740830ceab1 100644 (file)
@@ -250,6 +250,8 @@ struct ExecContext {
 
         int log_level_max;
 
+        char *log_namespace;
+
         bool private_tmp;
         bool private_network;
         bool private_devices;
@@ -258,6 +260,7 @@ struct ExecContext {
         bool protect_kernel_tunables;
         bool protect_kernel_modules;
         bool protect_kernel_logs;
+        bool protect_clock;
         bool protect_control_groups;
         ProtectSystem protect_system;
         ProtectHome protect_home;
index c1f8ac7bb243a367158c1ee534eda8fcb0c79737..69abdb65ba704d018e37e8aa6afc32bf20ac6898 100644 (file)
@@ -116,8 +116,10 @@ $1.PrivateDevices,               config_parse_bool,                  0,
 $1.ProtectKernelTunables,        config_parse_bool,                  0,                             offsetof($1, exec_context.protect_kernel_tunables)
 $1.ProtectKernelModules,         config_parse_bool,                  0,                             offsetof($1, exec_context.protect_kernel_modules)
 $1.ProtectKernelLogs,            config_parse_bool,                  0,                             offsetof($1, exec_context.protect_kernel_logs)
+$1.ProtectClock,                 config_parse_bool,                  0,                             offsetof($1, exec_context.protect_clock)
 $1.ProtectControlGroups,         config_parse_bool,                  0,                             offsetof($1, exec_context.protect_control_groups)
 $1.NetworkNamespacePath,         config_parse_unit_path_printf,      0,                             offsetof($1, exec_context.network_namespace_path)
+$1.LogNamespace,                 config_parse_log_namespace,         0,                             offsetof($1, exec_context)
 $1.PrivateNetwork,               config_parse_bool,                  0,                             offsetof($1, exec_context.private_network)
 $1.PrivateUsers,                 config_parse_bool,                  0,                             offsetof($1, exec_context.private_users)
 $1.PrivateMounts,                config_parse_bool,                  0,                             offsetof($1, exec_context.private_mounts)
index c5ba7b10461231d7895584ba57e2bb21ec1d595b..71a9873da46fbe6404c967b32982c031de02a9cd 100644 (file)
 #include "stat-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "syslog-util.h"
+#include "time-util.h"
 #include "unit-name.h"
 #include "unit-printf.h"
 #include "user-util.h"
-#include "time-util.h"
 #include "web-util.h"
 
 static int parse_socket_protocol(const char *s) {
@@ -2519,6 +2520,48 @@ int config_parse_log_extra_fields(
         }
 }
 
+int config_parse_log_namespace(
+                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;
+        ExecContext *c = data;
+        const Unit *u = userdata;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(c);
+
+        if (isempty(rvalue)) {
+                c->log_namespace = mfree(c->log_namespace);
+                return 0;
+        }
+
+        r = unit_full_printf(u, rvalue, &k);
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", rvalue);
+                return 0;
+        }
+
+        if (!log_namespace_name_valid(k)) {
+                log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Specified log namespace name is not valid: %s", k);
+                return 0;
+        }
+
+        free_and_replace(c->log_namespace, k);
+        return 0;
+}
+
 int config_parse_unit_condition_path(
                 const char *unit,
                 const char *filename,
@@ -4703,7 +4746,9 @@ int unit_load_fragment(Unit *u) {
                         return r;
 
                 if (null_or_empty(&st)) {
-                        u->load_state = UNIT_MASKED;
+                        /* Unit file is masked */
+
+                        u->load_state = u->perpetual ? UNIT_LOADED : UNIT_MASKED; /* don't allow perpetual units to ever be masked */
                         u->fragment_mtime = 0;
                 } else {
                         u->load_state = UNIT_LOADED;
@@ -4769,7 +4814,7 @@ void unit_dump_config_items(FILE *f) {
                 { config_parse_unsigned,              "UNSIGNED" },
                 { config_parse_iec_size,              "SIZE" },
                 { config_parse_iec_uint64,            "SIZE" },
-                { config_parse_si_size,               "SIZE" },
+                { config_parse_si_uint64,             "SIZE" },
                 { config_parse_bool,                  "BOOLEAN" },
                 { config_parse_string,                "STRING" },
                 { config_parse_path,                  "PATH" },
index 28613ef5b38f8b7e8dfb7f101171cefb8a9475c0..b6b46b2449b118f6cad5876de780871991c1e250 100644 (file)
@@ -107,6 +107,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_keyring_mode);
 CONFIG_PARSER_PROTOTYPE(config_parse_job_timeout_sec);
 CONFIG_PARSER_PROTOTYPE(config_parse_job_running_timeout_sec);
 CONFIG_PARSER_PROTOTYPE(config_parse_log_extra_fields);
+CONFIG_PARSER_PROTOTYPE(config_parse_log_namespace);
 CONFIG_PARSER_PROTOTYPE(config_parse_collect_mode);
 CONFIG_PARSER_PROTOTYPE(config_parse_pid_file);
 CONFIG_PARSER_PROTOTYPE(config_parse_exit_status);
index cb0633e2ab4de10d2be18efa25cd1c46ce0f0f2a..a5290eba0c682789821f2ca784bf8bf88dbb56fa 100644 (file)
@@ -4480,7 +4480,7 @@ static void manager_deserialize_uid_refs_one_internal(
 
         r = parse_uid(value, &uid);
         if (r < 0 || uid == 0) {
-                log_debug("Unable to parse UID reference serialization");
+                log_debug("Unable to parse UID reference serialization: " UID_FMT, uid);
                 return;
         }
 
index 12b2e512e824102b0874431043c51c80ccb2807d..668c4d7e8955f89db286962623f340796ffddd04 100644 (file)
@@ -537,10 +537,9 @@ static int mount_verify(Mount *m) {
         }
 
         p = get_mount_parameters_fragment(m);
-        if (p && !p->what) {
-                log_unit_error(UNIT(m), "What= setting is missing. Refusing.");
-                return -ENOEXEC;
-        }
+        if (p && !p->what && !UNIT(m)->perpetual)
+                return log_unit_error_errno(UNIT(m), SYNTHETIC_ERRNO(ENOEXEC),
+                                            "What= setting is missing. Refusing.");
 
         if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) {
                 log_unit_error(UNIT(m), "Unit has PAM enabled. Kill mode must be set to control-group'. Refusing.");
index f121cb7ca412389f9d21535e77bc23669d632337..77854058304120c5ae617397a9dab6eb3eac4881 100644 (file)
@@ -1132,6 +1132,7 @@ static size_t namespace_calculate_mounts(
                 size_t n_temporary_filesystems,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
+                const char* log_namespace,
                 ProtectHome protect_home,
                 ProtectSystem protect_system) {
 
@@ -1166,7 +1167,8 @@ static size_t namespace_calculate_mounts(
                 (ns_info->protect_control_groups ? 1 : 0) +
                 protect_home_cnt + protect_system_cnt +
                 (ns_info->protect_hostname ? 2 : 0) +
-                (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0);
+                (namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0) +
+                !!log_namespace;
 }
 
 static void normalize_mounts(const char *root_directory, MountEntry *mounts, size_t *n_mounts) {
@@ -1247,6 +1249,7 @@ int setup_namespace(
                 size_t n_temporary_filesystems,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
+                const char *log_namespace,
                 ProtectHome protect_home,
                 ProtectSystem protect_system,
                 unsigned long mount_flags,
@@ -1323,6 +1326,7 @@ int setup_namespace(
                         n_bind_mounts,
                         n_temporary_filesystems,
                         tmp_dir, var_tmp_dir,
+                        log_namespace,
                         protect_home, protect_system);
 
         if (n_mounts > 0) {
@@ -1428,6 +1432,23 @@ int setup_namespace(
                         };
                 }
 
+                if (log_namespace) {
+                        _cleanup_free_ char *q;
+
+                        q = strjoin("/run/systemd/journal.", log_namespace);
+                        if (!q) {
+                                r = -ENOMEM;
+                                goto finish;
+                        }
+
+                        *(m++) = (MountEntry) {
+                                .path_const = "/run/systemd/journal",
+                                .mode = BIND_MOUNT_RECURSIVE,
+                                .read_only = true,
+                                .source_malloc = TAKE_PTR(q),
+                        };
+                }
+
                 assert(mounts + n_mounts == m);
 
                 /* Prepend the root directory where that's necessary */
index 60a6abcd45e2573481a95c33e43ce071a4f6b60d..ef6c9bdc9bdcaa8bb3c68b93ff4e6ee42164d1ef 100644 (file)
@@ -84,6 +84,7 @@ int setup_namespace(
                 size_t n_temporary_filesystems,
                 const char *tmp_dir,
                 const char *var_tmp_dir,
+                const char *log_namespace,
                 ProtectHome protect_home,
                 ProtectSystem protect_system,
                 unsigned long mount_flags,
index f6fa5d184b2aba6e85c31e80044c91fb736c9c16..c3f9fb47039e70ec103aaf8fd2a74a29ec344fab 100644 (file)
@@ -650,24 +650,37 @@ static int service_add_default_dependencies(Service *s) {
         return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, true, UNIT_DEPENDENCY_DEFAULT);
 }
 
-static void service_fix_output(Service *s) {
+static void service_fix_stdio(Service *s) {
         assert(s);
 
-        /* If nothing has been explicitly configured, patch default output in. If input is socket/tty we avoid this
-         * however, since in that case we want output to default to the same place as we read input from. */
+        /* Note that EXEC_INPUT_NULL and EXEC_OUTPUT_INHERIT play a special role here: they are both the
+         * default value that is subject to automatic overriding triggered by other settings and an explicit
+         * choice the user can make. We don't distuingish between these cases currently. */
+
+        if (s->exec_context.std_input == EXEC_INPUT_NULL &&
+            s->exec_context.stdin_data_size > 0)
+                s->exec_context.std_input = EXEC_INPUT_DATA;
+
+        if (IN_SET(s->exec_context.std_input,
+                    EXEC_INPUT_TTY,
+                    EXEC_INPUT_TTY_FORCE,
+                    EXEC_INPUT_TTY_FAIL,
+                    EXEC_INPUT_SOCKET,
+                    EXEC_INPUT_NAMED_FD))
+                return;
+
+        /* We assume these listed inputs refer to bidirectional streams, and hence duplicating them from
+         * stdin to stdout/stderr makes sense and hence leaving EXEC_OUTPUT_INHERIT in place makes sense,
+         * too. Outputs such as regular files or sealed data memfds otoh don't really make sense to be
+         * duplicated for both input and output at the same time (since they then would cause a feedback
+         * loop), hence override EXEC_OUTPUT_INHERIT with the default stderr/stdout setting.  */
 
         if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT &&
-            s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
-            s->exec_context.std_input == EXEC_INPUT_NULL)
+            s->exec_context.std_output == EXEC_OUTPUT_INHERIT)
                 s->exec_context.std_error = UNIT(s)->manager->default_std_error;
 
-        if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT &&
-            s->exec_context.std_input == EXEC_INPUT_NULL)
+        if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT)
                 s->exec_context.std_output = UNIT(s)->manager->default_std_output;
-
-        if (s->exec_context.std_input == EXEC_INPUT_NULL &&
-            s->exec_context.stdin_data_size > 0)
-                s->exec_context.std_input = EXEC_INPUT_DATA;
 }
 
 static int service_setup_bus_name(Service *s) {
@@ -715,7 +728,7 @@ static int service_add_extras(Service *s) {
         if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined)
                 s->timeout_start_usec = USEC_INFINITY;
 
-        service_fix_output(s);
+        service_fix_stdio(s);
 
         r = unit_patch_contexts(UNIT(s));
         if (r < 0)
index 5d1ddd7620c9666e751bbb0b11360d602a3095b9..8331832c7a208dcfe7d380cd30599682431da8f8 100644 (file)
@@ -21,6 +21,8 @@ systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/s
 systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user
 systemdsystemgeneratordir=${rootprefix}/lib/systemd/system-generators
 systemdusergeneratordir=${prefix}/lib/systemd/user-generators
+systemdsystemgeneratorpath=/run/systemd/system-generators:/etc/systemd/system-generators:/usr/local/lib/systemd/system-generators:${systemdsystemgeneratordir}
+systemdusergeneratorpath=/run/systemd/user-generators:/etc/systemd/user-generators:/usr/local/lib/systemd/user-generators:${systemdusergeneratordir}
 systemdsleepdir=${rootprefix}/lib/systemd/system-sleep
 systemdshutdowndir=${rootprefix}/lib/systemd/system-shutdown
 tmpfilesdir=${prefix}/lib/tmpfiles.d
index 0c03b363dbcdb08e08f542c49a4edebbffce1dad..6b97c35ffa1ebd639ab9dd05d644bc26b8baeb53 100644 (file)
@@ -1059,13 +1059,33 @@ int unit_add_exec_dependencies(Unit *u, ExecContext *c) {
             !IN_SET(c->std_error,
                     EXEC_OUTPUT_JOURNAL, EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
                     EXEC_OUTPUT_KMSG, EXEC_OUTPUT_KMSG_AND_CONSOLE,
-                    EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE))
+                    EXEC_OUTPUT_SYSLOG, EXEC_OUTPUT_SYSLOG_AND_CONSOLE) &&
+            !c->log_namespace)
                 return 0;
 
-        /* If syslog or kernel logging is requested, make sure our own
-         * logging daemon is run first. */
+        /* If syslog or kernel logging is requested (or log namespacing is), make sure our own logging daemon
+         * 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;
 
-        r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE);
+                r = unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, socket_unit, true, UNIT_DEPENDENCY_FILE);
+                if (r < 0)
+                        return r;
+
+                r = unit_name_build_from_type("systemd-journald-varlink", c->log_namespace, UNIT_SOCKET, &varlink_socket_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;
+        } else
+                r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, true, UNIT_DEPENDENCY_FILE);
         if (r < 0)
                 return r;
 
@@ -4287,6 +4307,9 @@ int unit_patch_contexts(Unit *u) {
                 if (ec->protect_kernel_logs)
                         ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYSLOG);
 
+                if (ec->protect_clock)
+                        ec->capability_bounding_set &= ~((UINT64_C(1) << CAP_SYS_TIME) | (UINT64_C(1) << CAP_WAKE_ALARM));
+
                 if (ec->dynamic_user) {
                         if (!ec->user) {
                                 r = user_from_unit_name(u, &ec->user);
@@ -4345,6 +4368,12 @@ int unit_patch_contexts(Unit *u) {
                         if (r < 0)
                                 return r;
                 }
+
+                if (ec->protect_clock) {
+                        r = cgroup_add_device_allow(cc, "char-rtc", "r");
+                        if (r < 0)
+                                return r;
+                }
         }
 
         return 0;
@@ -5066,14 +5095,21 @@ static void unit_unref_uid_internal(
         *ref_uid = UID_INVALID;
 }
 
-void unit_unref_uid(Unit *u, bool destroy_now) {
+static void unit_unref_uid(Unit *u, bool destroy_now) {
         unit_unref_uid_internal(u, &u->ref_uid, destroy_now, manager_unref_uid);
 }
 
-void unit_unref_gid(Unit *u, bool destroy_now) {
+static void unit_unref_gid(Unit *u, bool destroy_now) {
         unit_unref_uid_internal(u, (uid_t*) &u->ref_gid, destroy_now, manager_unref_gid);
 }
 
+void unit_unref_uid_gid(Unit *u, bool destroy_now) {
+        assert(u);
+
+        unit_unref_uid(u, destroy_now);
+        unit_unref_gid(u, destroy_now);
+}
+
 static int unit_ref_uid_internal(
                 Unit *u,
                 uid_t *ref_uid,
@@ -5112,11 +5148,11 @@ static int unit_ref_uid_internal(
         return 1;
 }
 
-int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc) {
+static int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc) {
         return unit_ref_uid_internal(u, &u->ref_uid, uid, clean_ipc, manager_ref_uid);
 }
 
-int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc) {
+static int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc) {
         return unit_ref_uid_internal(u, (uid_t*) &u->ref_gid, (uid_t) gid, clean_ipc, manager_ref_gid);
 }
 
@@ -5161,13 +5197,6 @@ int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid) {
         return r;
 }
 
-void unit_unref_uid_gid(Unit *u, bool destroy_now) {
-        assert(u);
-
-        unit_unref_uid(u, destroy_now);
-        unit_unref_gid(u, destroy_now);
-}
-
 void unit_notify_user_lookup(Unit *u, uid_t uid, gid_t gid) {
         int r;
 
index 67a63105b6dcd7c104d1f152f0731bd48860bfc6..68de900b0d523165ea86c1fbd44c869971e64122 100644 (file)
@@ -815,12 +815,6 @@ int unit_fail_if_noncanonical(Unit *u, const char* where);
 
 int unit_test_start_limit(Unit *u);
 
-void unit_unref_uid(Unit *u, bool destroy_now);
-int unit_ref_uid(Unit *u, uid_t uid, bool clean_ipc);
-
-void unit_unref_gid(Unit *u, bool destroy_now);
-int unit_ref_gid(Unit *u, gid_t gid, bool clean_ipc);
-
 int unit_ref_uid_gid(Unit *u, uid_t uid, gid_t gid);
 void unit_unref_uid_gid(Unit *u, bool destroy_now);
 
index 2fef95aa027b617216fac033fd974632e479be29..e1418419f7b628b221075440ce4bac8cf1c970e6 100644 (file)
@@ -11,6 +11,7 @@
 #include "log.h"
 #include "loop-util.h"
 #include "main-func.h"
+#include "parse-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "user-util.h"
@@ -22,7 +23,7 @@ static enum {
 } arg_action = ACTION_DISSECT;
 static const char *arg_image = NULL;
 static const char *arg_path = NULL;
-static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK;
+static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK;
 static void *arg_root_hash = NULL;
 static size_t arg_root_hash_size = 0;
 
@@ -36,6 +37,7 @@ static void help(void) {
                "     --version         Show package version\n"
                "  -m --mount           Mount the image to the specified directory\n"
                "  -r --read-only       Mount read-only\n"
+               "     --fsck=BOOL       Run fsck before mounting\n"
                "     --discard=MODE    Choose 'discard' mode (disabled, loop, all, crypto)\n"
                "     --root-hash=HASH  Specify root hash for verity\n",
                program_invocation_short_name,
@@ -48,6 +50,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_DISCARD,
                 ARG_ROOT_HASH,
+                ARG_FSCK,
         };
 
         static const struct option options[] = {
@@ -57,6 +60,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "read-only", no_argument,       NULL, 'r'           },
                 { "discard",   required_argument, NULL, ARG_DISCARD   },
                 { "root-hash", required_argument, NULL, ARG_ROOT_HASH },
+                { "fsck",      required_argument, NULL, ARG_FSCK      },
                 {}
         };
 
@@ -123,6 +127,14 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
                 }
 
+                case ARG_FSCK:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --fsck= parameter: %s", optarg);
+
+                        SET_FLAG(arg_flags, DISSECT_IMAGE_FSCK, r);
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -261,6 +273,8 @@ static int run(int argc, char *argv[]) {
                         return r;
 
                 r = dissected_image_mount(m, arg_path, UID_INVALID, arg_flags);
+                if (r == -EUCLEAN)
+                        return log_error_errno(r, "File system check on image failed: %m");
                 if (r < 0)
                         return log_error_errno(r, "Failed to mount image: %m");
 
index 6fd6fbdf1ccdf542b8cf6ae6b7ac3a16d36ff723..e0f1076326f2d7d3b1a926dac13a502cb91bceaa 100644 (file)
@@ -306,6 +306,7 @@ static int add_mount(
                 *automount_name = NULL,
                 *filtered = NULL,
                 *where_escaped = NULL;
+        _cleanup_strv_free_ char **wanted_by = NULL, **required_by = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
@@ -327,6 +328,14 @@ static int add_mount(
             mount_point_ignore(where))
                 return 0;
 
+        r = fstab_extract_values(opts, "x-systemd.wanted-by", &wanted_by);
+        if (r < 0)
+                return r;
+
+        r = fstab_extract_values(opts, "x-systemd.required-by", &required_by);
+        if (r < 0)
+                return r;
+
         if (path_equal(where, "/")) {
                 if (flags & NOAUTO)
                         log_warning("Ignoring \"noauto\" for root device");
@@ -334,7 +343,13 @@ static int add_mount(
                         log_warning("Ignoring \"nofail\" for root device");
                 if (flags & AUTOMOUNT)
                         log_warning("Ignoring automount option for root device");
+                if (!strv_isempty(wanted_by))
+                        log_warning("Ignoring \"x-systemd.wanted-by=\" for root device");
+                if (!strv_isempty(required_by))
+                        log_warning("Ignoring \"x-systemd.required-by=\" for root device");
 
+                required_by = strv_free(required_by);
+                wanted_by = strv_free(wanted_by);
                 SET_FLAG(flags, NOAUTO | NOFAIL | AUTOMOUNT, false);
         }
 
@@ -451,14 +466,28 @@ static int add_mount(
                         return r;
         }
 
-        if (!(flags & NOAUTO) && !(flags & AUTOMOUNT)) {
-                r = generator_add_symlink(dest, post,
-                                          (flags & NOFAIL) ? "wants" : "requires", name);
-                if (r < 0)
-                        return r;
-        }
-
-        if (flags & AUTOMOUNT) {
+        if (!FLAGS_SET(flags, AUTOMOUNT)) {
+                if (!FLAGS_SET(flags, NOAUTO) && strv_isempty(wanted_by) && strv_isempty(required_by)) {
+                        r = generator_add_symlink(dest, post,
+                                                  (flags & NOFAIL) ? "wants" : "requires", name);
+                        if (r < 0)
+                                return r;
+                } else {
+                        char **s;
+
+                        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;
+                        }
+                }
+        } else {
                 r = unit_name_from_path(where, ".automount", &automount_name);
                 if (r < 0)
                         return log_error_errno(r, "Failed to generate unit name: %m");
index aca82edad9628b465fa0c61b077c34c39da69ad7..3d1ac7e68a03ed56bb00d8e1a7644b0fef84e16a 100644 (file)
@@ -36,7 +36,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
         if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0)
                 assert_se(g = open_memstream_unlocked(&out, &out_size));
 
-        bus_message_dump(m, g ?: stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(m, g ?: stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         r = sd_bus_message_rewind(m, true);
         assert_se(r >= 0);
index 5e40933fdf28d47afc8927a854e34bc3e932b7a6..95af8efa7e2e96ff5ca3fc105fe25dcc847dd78a 100644 (file)
@@ -93,7 +93,7 @@ static int process_resume(void) {
                 return log_error_errno(r, "Failed to generate unit name: %m");
 
         r = write_drop_in(arg_dest, device_unit, 40, "device-timeout",
-                          "# Automatically generated by systemd-cryptsetup-generator\n\n"
+                          "# Automatically generated by systemd-hibernate-resume-generator\n\n"
                           "[Unit]\nJobTimeoutSec=0");
         if (r < 0)
                 log_warning_errno(r, "Failed to write device timeout drop-in: %m");
diff --git a/src/home/home-util.c b/src/home/home-util.c
new file mode 100644 (file)
index 0000000..82c50a6
--- /dev/null
@@ -0,0 +1,160 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "dns-domain.h"
+#include "errno-util.h"
+#include "home-util.h"
+#include "libcrypt-util.h"
+#include "memory-util.h"
+#include "path-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "user-util.h"
+
+bool suitable_user_name(const char *name) {
+
+        /* Checks whether the specified name is suitable for management via homed. Note that client-side we
+         * usually validate with the simple valid_user_group_name(), while server-side we are a bit more
+         * restrictive, so that we can change the rules server-side without having to update things
+         * client-side too. */
+
+        if (!valid_user_group_name(name))
+                return false;
+
+        /* We generally rely on NSS to tell us which users not to care for, but let's filter out some
+         * particularly well-known users. */
+        if (STR_IN_SET(name,
+                       "root",
+                       "nobody",
+                       NOBODY_USER_NAME, NOBODY_GROUP_NAME))
+                return false;
+
+        /* Let's also defend our own namespace, as well as Debian's (unwritten?) logic of prefixing system
+         * users with underscores. */
+        if (STARTSWITH_SET(name, "systemd-", "_"))
+                return false;
+
+        return true;
+}
+
+int suitable_realm(const char *realm) {
+        _cleanup_free_ char *normalized = NULL;
+        int r;
+
+        /* Similar to the above: let's validate the realm a bit stricter server-side than client side */
+
+        r = dns_name_normalize(realm, 0, &normalized); /* this also checks general validity */
+        if (r == -EINVAL)
+                return 0;
+        if (r < 0)
+                return r;
+
+        if (!streq(realm, normalized)) /* is this normalized? */
+                return false;
+
+        if (dns_name_is_root(realm)) /* Don't allow top level domain */
+                return false;
+
+        return true;
+}
+
+int suitable_image_path(const char *path) {
+
+        return !empty_or_root(path) &&
+                path_is_valid(path) &&
+                path_is_absolute(path);
+}
+
+int split_user_name_realm(const char *t, char **ret_user_name, char **ret_realm) {
+        _cleanup_free_ char *user_name = NULL, *realm = NULL;
+        const char *c;
+        int r;
+
+        assert(t);
+        assert(ret_user_name);
+        assert(ret_realm);
+
+        c = strchr(t, '@');
+        if (!c) {
+                user_name = strdup(t);
+                if (!user_name)
+                        return -ENOMEM;
+        } else {
+                user_name = strndup(t, c - t);
+                if (!user_name)
+                        return -ENOMEM;
+
+                realm = strdup(c + 1);
+                if (!realm)
+                        return -ENOMEM;
+        }
+
+        if (!suitable_user_name(user_name))
+                return -EINVAL;
+
+        if (realm) {
+                r = suitable_realm(realm);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EINVAL;
+        }
+
+        *ret_user_name = TAKE_PTR(user_name);
+        *ret_realm = TAKE_PTR(realm);
+
+        return 0;
+}
+
+int bus_message_append_secret(sd_bus_message *m, UserRecord *secret) {
+        _cleanup_(erase_and_freep) char *formatted = NULL;
+        JsonVariant *v;
+        int r;
+
+        assert(m);
+        assert(secret);
+
+        if (!FLAGS_SET(secret->mask, USER_RECORD_SECRET))
+                return sd_bus_message_append(m, "s", "{}");
+
+        v = json_variant_by_key(secret->json, "secret");
+        if (!v)
+                return -EINVAL;
+
+        r = json_variant_format(v, 0, &formatted);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append(m, "s", formatted);
+}
+
+int test_password_one(const char *hashed_password, const char *password) {
+        struct crypt_data cc = {};
+        const char *k;
+        bool b;
+
+        errno = 0;
+        k = crypt_r(password, hashed_password, &cc);
+        if (!k) {
+                explicit_bzero_safe(&cc, sizeof(cc));
+                return errno_or_else(EINVAL);
+        }
+
+        b = streq(k, hashed_password);
+        explicit_bzero_safe(&cc, sizeof(cc));
+        return b;
+}
+
+int test_password_many(char **hashed_password, const char *password) {
+        char **hpw;
+        int r;
+
+        STRV_FOREACH(hpw, hashed_password) {
+                r = test_password_one(*hpw, password);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return true;
+        }
+
+        return false;
+}
diff --git a/src/home/home-util.h b/src/home/home-util.h
new file mode 100644 (file)
index 0000000..df20c0a
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+#include "sd-bus.h"
+
+#include "time-util.h"
+#include "user-record.h"
+
+bool suitable_user_name(const char *name);
+int suitable_realm(const char *realm);
+int suitable_image_path(const char *path);
+
+int split_user_name_realm(const char *t, char **ret_user_name, char **ret_realm);
+
+int bus_message_append_secret(sd_bus_message *m, UserRecord *secret);
+
+/* Many of our operations might be slow due to crypto, fsck, recursive chown() and so on. For these
+ * operations permit a *very* long time-out */
+#define HOME_SLOW_BUS_CALL_TIMEOUT_USEC (2*USEC_PER_MINUTE)
+
+int test_password_one(const char *hashed_password, const char *password);
+int test_password_many(char **hashed_password, const char *password);
diff --git a/src/home/homectl.c b/src/home/homectl.c
new file mode 100644 (file)
index 0000000..d978897
--- /dev/null
@@ -0,0 +1,3607 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+
+#include "sd-bus.h"
+
+#include "alloc-util.h"
+#include "ask-password-api.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-util.h"
+#include "cgroup-util.h"
+#include "dns-domain.h"
+#include "env-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-table.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "hexdecoct.h"
+#include "home-util.h"
+#include "libcrypt-util.h"
+#include "locale-util.h"
+#include "main-func.h"
+#include "memory-util.h"
+#include "openssl-util.h"
+#include "pager.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pkcs11-util.h"
+#include "pretty-print.h"
+#include "process-util.h"
+#include "pwquality-util.h"
+#include "random-util.h"
+#include "rlimit-util.h"
+#include "spawn-polkit-agent.h"
+#include "terminal-util.h"
+#include "user-record-show.h"
+#include "user-record-util.h"
+#include "user-record.h"
+#include "user-util.h"
+#include "verbs.h"
+
+static PagerFlags arg_pager_flags = 0;
+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 const char *arg_identity = NULL;
+static JsonVariant *arg_identity_extra = NULL;
+static JsonVariant *arg_identity_extra_privileged = NULL;
+static JsonVariant *arg_identity_extra_this_machine = NULL;
+static JsonVariant *arg_identity_extra_rlimits = NULL;
+static char **arg_identity_filter = NULL; /* this one is also applied to 'privileged' and 'thisMachine' subobjects */
+static char **arg_identity_filter_rlimits = NULL;
+static uint64_t arg_disk_size = UINT64_MAX;
+static uint64_t arg_disk_size_relative = UINT64_MAX;
+static char **arg_pkcs11_token_uri = NULL;
+static bool arg_json = false;
+static JsonFormatFlags arg_json_format_flags = 0;
+static bool arg_and_resize = false;
+static bool arg_and_change_password = false;
+static enum {
+        EXPORT_FORMAT_FULL,          /* export the full record */
+        EXPORT_FORMAT_STRIPPED,      /* strip "state" + "binding", but leave signature in place */
+        EXPORT_FORMAT_MINIMAL,       /* also strip signature */
+} arg_export_format = EXPORT_FORMAT_FULL;
+
+STATIC_DESTRUCTOR_REGISTER(arg_identity_extra, json_variant_unrefp);
+STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_this_machine, json_variant_unrefp);
+STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_privileged, json_variant_unrefp);
+STATIC_DESTRUCTOR_REGISTER(arg_identity_extra_rlimits, json_variant_unrefp);
+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 bool identity_properties_specified(void) {
+        return
+                arg_identity ||
+                !json_variant_is_blank_object(arg_identity_extra) ||
+                !json_variant_is_blank_object(arg_identity_extra_privileged) ||
+                !json_variant_is_blank_object(arg_identity_extra_this_machine) ||
+                !json_variant_is_blank_object(arg_identity_extra_rlimits) ||
+                !strv_isempty(arg_identity_filter) ||
+                !strv_isempty(arg_identity_filter_rlimits) ||
+                !strv_isempty(arg_pkcs11_token_uri);
+}
+
+static int acquire_bus(sd_bus **bus) {
+        int r;
+
+        assert(bus);
+
+        if (*bus)
+                return 0;
+
+        r = bus_connect_transport(arg_transport, arg_host, false, bus);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to bus: %m");
+
+        (void) sd_bus_set_allow_interactive_authorization(*bus, arg_ask_password);
+
+        return 0;
+}
+
+static int list_homes(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_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(table_unrefp) Table *table = NULL;
+        int r;
+
+        (void) pager_open(arg_pager_flags);
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "ListHomes",
+                        &error,
+                        &reply,
+                        NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to list homes: %s", bus_error_message(&error, r));
+
+        table = table_new("name", "uid", "gid", "state", "realname", "home", "shell");
+        if (!table)
+                return log_oom();
+
+        r = sd_bus_message_enter_container(reply, 'a', "(susussso)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        for (;;) {
+                const char *name, *state, *realname, *home, *shell, *color;
+                TableCell *cell;
+                uint32_t uid, gid;
+
+                r = sd_bus_message_read(reply, "(susussso)", &name, &uid, &state, &gid, &realname, &home, &shell, NULL);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+                if (r == 0)
+                        break;
+
+                r = table_add_many(table,
+                                   TABLE_STRING, name,
+                                   TABLE_UID, uid,
+                                   TABLE_GID, gid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add row to table: %m");
+
+
+                r = table_add_cell(table, &cell, TABLE_STRING, state);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add field to table: %m");
+
+                color = user_record_state_color(state);
+                if (color)
+                        (void) table_set_color(table, cell, color);
+
+                r = table_add_many(table,
+                                   TABLE_STRING, strna(empty_to_null(realname)),
+                                   TABLE_STRING, home,
+                                   TABLE_STRING, strna(empty_to_null(shell)));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add row to table: %m");
+        }
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        if (table_get_rows(table) > 1 || arg_json) {
+                r = table_set_sort(table, (size_t) 0, (size_t) -1);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to sort table: %m");
+
+                table_set_header(table, arg_legend);
+
+                if (arg_json)
+                        r = table_print_json(table, stdout, arg_json_format_flags);
+                else
+                        r = table_print(table, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to show table: %m");
+        }
+
+        if (arg_legend && !arg_json) {
+                if (table_get_rows(table) > 1)
+                        printf("\n%zu homes listed.\n", table_get_rows(table) - 1);
+                else
+                        printf("No homes.\n");
+        }
+
+        return 0;
+}
+
+static int acquire_existing_password(const char *user_name, UserRecord *hr, bool emphasize_current) {
+        _cleanup_(strv_free_erasep) char **password = NULL;
+        _cleanup_free_ char *question = NULL;
+        char *e;
+        int r;
+
+        assert(user_name);
+        assert(hr);
+
+        e = getenv("PASSWORD");
+        if (e) {
+                /* People really shouldn't use environment variables for passing passwords. We support this
+                 * only for testing purposes, and do not document the behaviour, so that people won't
+                 * actually use this outside of testing. */
+
+                r = user_record_set_password(hr, STRV_MAKE(e), true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to store password: %m");
+
+                string_erase(e);
+
+                if (unsetenv("PASSWORD") < 0)
+                        return log_error_errno(errno, "Failed to unset $PASSWORD: %m");
+
+                return 0;
+        }
+
+        if (asprintf(&question, emphasize_current ?
+                     "Please enter current password for user %s:" :
+                     "Please enter password for user %s:",
+                     user_name) < 0)
+                return log_oom();
+
+        r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &password);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire password: %m");
+
+        r = user_record_set_password(hr, password, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to store password: %m");
+
+        return 0;
+}
+
+static int acquire_pkcs11_pin(const char *user_name, UserRecord *hr) {
+        _cleanup_(strv_free_erasep) char **pin = NULL;
+        _cleanup_free_ char *question = NULL;
+        char *e;
+        int r;
+
+        assert(user_name);
+        assert(hr);
+
+        e = getenv("PIN");
+        if (e) {
+                r = user_record_set_pkcs11_pin(hr, STRV_MAKE(e), false);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to store PKCS#11 PIN: %m");
+
+                string_erase(e);
+
+                if (unsetenv("PIN") < 0)
+                        return log_error_errno(errno, "Failed to unset $PIN: %m");
+
+                return 0;
+        }
+
+        if (asprintf(&question, "Please enter security token PIN for user %s:", user_name) < 0)
+                return log_oom();
+
+        /* We never cache or use cached PINs, since usually there are only very few attempts allowed before the PIN is blocked */
+        r = ask_password_auto(question, "user-home", NULL, "pkcs11-pin", USEC_INFINITY, 0, &pin);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire security token PIN: %m");
+
+        r = user_record_set_pkcs11_pin(hr, pin, false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to store security token PIN: %m");
+
+        return 0;
+}
+
+static int handle_generic_user_record_error(
+                const char *user_name,
+                UserRecord *hr,
+                const sd_bus_error *error,
+                int ret,
+                bool emphasize_current_password) {
+        int r;
+
+        assert(user_name);
+        assert(hr);
+
+        if (sd_bus_error_has_name(error, BUS_ERROR_HOME_ABSENT))
+                return log_error_errno(SYNTHETIC_ERRNO(EREMOTE),
+                                       "Home of user %s is currently absent, please plug in the necessary stroage device or backing file system.", user_name);
+
+        else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT))
+                return log_error_errno(SYNTHETIC_ERRNO(ETOOMANYREFS),
+                                       "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
+
+        else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
+
+                if (!strv_isempty(hr->password))
+                        log_notice("Password incorrect or not sufficient, please try again.");
+
+                r = acquire_existing_password(user_name, hr, emphasize_current_password);
+                if (r < 0)
+                        return r;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
+
+                if (strv_isempty(hr->password))
+                        log_notice("Security token not inserted, please enter password.");
+                else
+                        log_notice("Password incorrect or not sufficient, and configured security token not inserted, please try again.");
+
+                r = acquire_existing_password(user_name, hr, emphasize_current_password);
+                if (r < 0)
+                        return r;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
+
+                r = acquire_pkcs11_pin(user_name, hr);
+                if (r < 0)
+                        return r;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
+
+                log_notice("Please authenticate physically on security token.");
+
+                r = user_record_set_pkcs11_protected_authentication_path_permitted(hr, true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set PKCS#11 protected authentication path permitted flag: %m");
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_LOCKED))
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Security token PIN is locked, please unlock security token PIN first.");
+
+        else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
+
+                log_notice("Security token PIN incorrect, please try again.");
+
+                r = acquire_pkcs11_pin(user_name, hr);
+                if (r < 0)
+                        return r;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
+
+                log_notice("Security token PIN incorrect, please try again (only a few tries left!).");
+
+                r = acquire_pkcs11_pin(user_name, hr);
+                if (r < 0)
+                        return r;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
+
+                log_notice("Security token PIN incorrect, please try again (only one try left!).");
+
+                r = acquire_pkcs11_pin(user_name, hr);
+                if (r < 0)
+                        return r;
+        } else
+                return log_error_errno(ret, "Operation on home %s failed: %s", user_name, bus_error_message(error, ret));
+
+        return 0;
+}
+
+static int activate_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r, ret = 0;
+        char **i;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+
+                secret = user_record_new();
+                if (!secret)
+                        return log_oom();
+
+                for (;;) {
+                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                        r = sd_bus_message_new_method_call(
+                                        bus,
+                                        &m,
+                                        "org.freedesktop.home1",
+                                        "/org/freedesktop/home1",
+                                        "org.freedesktop.home1.Manager",
+                                        "ActivateHome");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append(m, "s", *i);
+                        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) {
+                                r = handle_generic_user_record_error(*i, secret, &error, r, false);
+                                if (r < 0) {
+                                        if (ret == 0)
+                                                ret = r;
+
+                                        break;
+                                }
+                        } else
+                                break;
+                }
+        }
+
+        return ret;
+}
+
+static int deactivate_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r, ret = 0;
+        char **i;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "DeactivateHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", *i);
+                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) {
+                        log_error_errno(r, "Failed to deactivate user home: %s", bus_error_message(&error, r));
+                        if (ret == 0)
+                                ret = r;
+                }
+        }
+
+        return ret;
+}
+
+static void dump_home_record(UserRecord *hr) {
+        int r;
+
+        assert(hr);
+
+        if (hr->incomplete) {
+                fflush(stdout);
+                log_warning("Warning: lacking rights to acquire privileged fields of user record of '%s', output incomplete.", hr->user_name);
+        }
+
+        if (arg_json) {
+                _cleanup_(user_record_unrefp) UserRecord *stripped = NULL;
+
+                if (arg_export_format == EXPORT_FORMAT_STRIPPED)
+                        r = user_record_clone(hr, USER_RECORD_EXTRACT_EMBEDDED, &stripped);
+                else if (arg_export_format == EXPORT_FORMAT_MINIMAL)
+                        r = user_record_clone(hr, USER_RECORD_EXTRACT_SIGNABLE, &stripped);
+                else
+                        r = 0;
+                if (r < 0)
+                        log_warning_errno(r, "Failed to strip user record, ignoring: %m");
+                if (stripped)
+                        hr = stripped;
+
+                json_variant_dump(hr->json, arg_json_format_flags, stdout, NULL);
+        } else
+                user_record_show(hr, true);
+}
+
+static char **mangle_user_list(char **list, char ***ret_allocated) {
+        _cleanup_free_ char *myself = NULL;
+        char **l;
+
+        if (!strv_isempty(list)) {
+                *ret_allocated = NULL;
+                return list;
+        }
+
+        myself = getusername_malloc();
+        if (!myself)
+                return NULL;
+
+        l = new(char*, 2);
+        if (!l)
+                return NULL;
+
+        l[0] = TAKE_PTR(myself);
+        l[1] = NULL;
+
+        *ret_allocated = l;
+        return l;
+}
+
+static int inspect_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(strv_freep) char **mangled_list = NULL;
+        int r, ret = 0;
+        char **items, **i;
+
+        (void) pager_open(arg_pager_flags);
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        items = mangle_user_list(strv_skip(argv, 1), &mangled_list);
+        if (!items)
+                return log_oom();
+
+        STRV_FOREACH(i, items) {
+                _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;
+                _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+                const char *json;
+                int incomplete;
+                uid_t uid;
+
+                r = parse_uid(*i, &uid);
+                if (r < 0) {
+                        if (!valid_user_group_name(*i)) {
+                                log_error("Invalid user name '%s'.", *i);
+                                if (ret == 0)
+                                        ret = -EINVAL;
+
+                                continue;
+                        }
+
+                        r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.home1",
+                                        "/org/freedesktop/home1",
+                                        "org.freedesktop.home1.Manager",
+                                        "GetUserRecordByName",
+                                        &error,
+                                        &reply,
+                                        "s",
+                                        *i);
+                } else {
+                        r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.home1",
+                                        "/org/freedesktop/home1",
+                                        "org.freedesktop.home1.Manager",
+                                        "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)
+                                ret = r;
+
+                        continue;
+                }
+
+                r = sd_bus_message_read(reply, "sbo", &json, &incomplete, NULL);
+                if (r < 0) {
+                        bus_log_parse_error(r);
+                        if (ret == 0)
+                                ret = r;
+
+                        continue;
+                }
+
+                r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to parse JSON identity: %m");
+                        if (ret == 0)
+                                ret = r;
+
+                        continue;
+                }
+
+                hr = user_record_new();
+                if (!hr)
+                        return log_oom();
+
+                r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET|USER_RECORD_LOG);
+                if (r < 0) {
+                        if (ret == 0)
+                                ret = r;
+
+                        continue;
+                }
+
+                hr->incomplete = incomplete;
+                dump_home_record(hr);
+        }
+
+        return ret;
+}
+
+static int authenticate_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(strv_freep) char **mangled_list = NULL;
+        int r, ret = 0;
+        char **i, **items;
+
+        items = mangle_user_list(strv_skip(argv, 1), &mangled_list);
+        if (!items)
+                return log_oom();
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        STRV_FOREACH(i, items) {
+                _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+
+                secret = user_record_new();
+                if (!secret)
+                        return log_oom();
+
+                for (;;) {
+                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                        r = sd_bus_message_new_method_call(
+                                        bus,
+                                        &m,
+                                        "org.freedesktop.home1",
+                                        "/org/freedesktop/home1",
+                                        "org.freedesktop.home1.Manager",
+                                        "AuthenticateHome");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append(m, "s", *i);
+                        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) {
+                                r = handle_generic_user_record_error(*i, secret, &error, r, false);
+                                if (r < 0) {
+                                        if (ret == 0)
+                                                ret = r;
+
+                                        break;
+                                }
+                        } else
+                                break;
+                }
+        }
+
+        return ret;
+}
+
+static int update_last_change(JsonVariant **v, bool with_password, bool override) {
+        JsonVariant *c;
+        usec_t n;
+        int r;
+
+        assert(v);
+
+        n = now(CLOCK_REALTIME);
+
+        c = json_variant_by_key(*v, "lastChangeUSec");
+        if (c) {
+                uintmax_t u;
+
+                if (!override)
+                        goto update_password;
+
+                if (!json_variant_is_unsigned(c))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastChangeUSec field is not an unsigned integer, refusing.");
+
+                u = json_variant_unsigned(c);
+                if (u >= n)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastChangeUSec is from the future, can't update.");
+        }
+
+        r = json_variant_set_field_unsigned(v, "lastChangeUSec", n);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update lastChangeUSec: %m");
+
+update_password:
+        if (!with_password)
+                return 0;
+
+        c = json_variant_by_key(*v, "lastPasswordChangeUSec");
+        if (c) {
+                uintmax_t u;
+
+                if (!override)
+                        return 0;
+
+                if (!json_variant_is_unsigned(c))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastPasswordChangeUSec field is not an unsigned integer, refusing.");
+
+                u = json_variant_unsigned(c);
+                if (u >= n)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "lastPasswordChangeUSec is from the future, can't update.");
+        }
+
+        r = json_variant_set_field_unsigned(v, "lastPasswordChangeUSec", n);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update lastPasswordChangeUSec: %m");
+
+        return 1;
+}
+
+static int apply_identity_changes(JsonVariant **_v) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
+        assert(_v);
+
+        v = json_variant_ref(*_v);
+
+        r = json_variant_filter(&v, arg_identity_filter);
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter identity: %m");
+
+        r = json_variant_merge(&v, arg_identity_extra);
+        if (r < 0)
+                return log_error_errno(r, "Failed to merge identities: %m");
+
+        if (arg_identity_extra_this_machine || !strv_isempty(arg_identity_filter)) {
+                _cleanup_(json_variant_unrefp) JsonVariant *per_machine = NULL, *mmid = NULL;
+                char mids[SD_ID128_STRING_MAX];
+                sd_id128_t mid;
+
+                r = sd_id128_get_machine(&mid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire machine ID: %m");
+
+                r = json_variant_new_string(&mmid, sd_id128_to_string(mid, mids));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate matchMachineId object: %m");
+
+                per_machine = json_variant_ref(json_variant_by_key(v, "perMachine"));
+                if (per_machine) {
+                        _cleanup_(json_variant_unrefp) JsonVariant *npm = NULL, *add = NULL;
+                        _cleanup_free_ JsonVariant **array = NULL;
+                        JsonVariant *z;
+                        size_t i = 0;
+
+                        if (!json_variant_is_array(per_machine))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "perMachine field is not an array, refusing.");
+
+                        array = new(JsonVariant*, json_variant_elements(per_machine) + 1);
+                        if (!array)
+                                return log_oom();
+
+                        JSON_VARIANT_ARRAY_FOREACH(z, per_machine) {
+                                JsonVariant *u;
+
+                                if (!json_variant_is_object(z))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "perMachine entry is not an object, refusing.");
+
+                                array[i++] = z;
+
+                                u = json_variant_by_key(z, "matchMachineId");
+                                if (!u)
+                                        continue;
+
+                                if (!json_variant_equal(u, mmid))
+                                        continue;
+
+                                r = json_variant_merge(&add, z);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to merge perMachine entry: %m");
+
+                                i--;
+                        }
+
+                        r = json_variant_filter(&add, arg_identity_filter);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to filter perMachine: %m");
+
+                        r = json_variant_merge(&add, arg_identity_extra_this_machine);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to merge in perMachine fields: %m");
+
+                        if (arg_identity_filter_rlimits || arg_identity_extra_rlimits) {
+                                _cleanup_(json_variant_unrefp) JsonVariant *rlv = NULL;
+
+                                rlv = json_variant_ref(json_variant_by_key(add, "resourceLimits"));
+
+                                r = json_variant_filter(&rlv, arg_identity_filter_rlimits);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to filter resource limits: %m");
+
+                                r = json_variant_merge(&rlv, arg_identity_extra_rlimits);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set resource limits: %m");
+
+                                if (json_variant_is_blank_object(rlv)) {
+                                        r = json_variant_filter(&add, STRV_MAKE("resourceLimits"));
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to drop resource limits field from identity: %m");
+                                } else {
+                                        r = json_variant_set_field(&add, "resourceLimits", rlv);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Failed to update resource limits of identity: %m");
+                                }
+                        }
+
+                        if (!json_variant_is_blank_object(add)) {
+                                r = json_variant_set_field(&add, "matchMachineId", mmid);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set matchMachineId field: %m");
+
+                                array[i++] = add;
+                        }
+
+                        r = json_variant_new_array(&npm, array, i);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to allocate new perMachine array: %m");
+
+                        json_variant_unref(per_machine);
+                        per_machine = TAKE_PTR(npm);
+                } else {
+                        _cleanup_(json_variant_unrefp) JsonVariant *item = json_variant_ref(arg_identity_extra_this_machine);
+
+                        if (arg_identity_extra_rlimits) {
+                                r = json_variant_set_field(&item, "resourceLimits", arg_identity_extra_rlimits);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to update resource limits of identity: %m");
+                        }
+
+                        r = json_variant_set_field(&item, "matchMachineId", mmid);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set matchMachineId field: %m");
+
+                        r = json_variant_append_array(&per_machine, item);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to append to perMachine array: %m");
+                }
+
+                r = json_variant_set_field(&v, "perMachine", per_machine);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to update per machine record: %m");
+        }
+
+        if (arg_identity_extra_privileged || arg_identity_filter) {
+                _cleanup_(json_variant_unrefp) JsonVariant *privileged = NULL;
+
+                privileged = json_variant_ref(json_variant_by_key(v, "privileged"));
+
+                r = json_variant_filter(&privileged, arg_identity_filter);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to filter identity (privileged part): %m");
+
+                r = json_variant_merge(&privileged, arg_identity_extra_privileged);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to merge identities (privileged part): %m");
+
+                if (json_variant_is_blank_object(privileged)) {
+                        r = json_variant_filter(&v, STRV_MAKE("privileged"));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to drop privileged part from identity: %m");
+                } else {
+                        r = json_variant_set_field(&v, "privileged", privileged);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to update privileged part of identity: %m");
+                }
+        }
+
+        if (arg_identity_filter_rlimits) {
+                _cleanup_(json_variant_unrefp) JsonVariant *rlv = NULL;
+
+                rlv = json_variant_ref(json_variant_by_key(v, "resourceLimits"));
+
+                r = json_variant_filter(&rlv, arg_identity_filter_rlimits);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to filter resource limits: %m");
+
+                /* Note that we only filter resource limits here, but don't apply them. We do that in the perMachine section */
+
+                if (json_variant_is_blank_object(rlv)) {
+                        r = json_variant_filter(&v, STRV_MAKE("resourceLimits"));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to drop resource limits field from identity: %m");
+                } else {
+                        r = json_variant_set_field(&v, "resourceLimits", rlv);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to update resource limits of identity: %m");
+                }
+        }
+
+        json_variant_unref(*_v);
+        *_v = TAKE_PTR(v);
+
+        return 0;
+}
+
+static int add_disposition(JsonVariant **v) {
+        int r;
+
+        assert(v);
+
+        if (json_variant_by_key(*v, "disposition"))
+                return 0;
+
+        /* Set the disposition to regular, if not configured explicitly */
+        r = json_variant_set_field_string(v, "disposition", "regular");
+        if (r < 0)
+                return log_error_errno(r, "Failed to set disposition field: %m");
+
+        return 1;
+}
+
+struct pkcs11_callback_data {
+        char *pin_used;
+        X509 *cert;
+};
+
+static void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
+        erase_and_free(data->pin_used);
+        X509_free(data->cert);
+}
+
+#if HAVE_P11KIT
+static int pkcs11_callback(
+                CK_FUNCTION_LIST *m,
+                CK_SESSION_HANDLE session,
+                CK_SLOT_ID slot_id,
+                const CK_SLOT_INFO *slot_info,
+                const CK_TOKEN_INFO *token_info,
+                P11KitUri *uri,
+                void *userdata) {
+
+        _cleanup_(erase_and_freep) char *pin_used = NULL;
+        struct pkcs11_callback_data *data = userdata;
+        CK_OBJECT_HANDLE object;
+        int r;
+
+        assert(m);
+        assert(slot_info);
+        assert(token_info);
+        assert(uri);
+        assert(data);
+
+        /* Called for every token matching our URI */
+
+        r = pkcs11_token_login(m, session, slot_id, token_info, "home directory operation", "user-home", "pkcs11-pin", UINT64_MAX, &pin_used);
+        if (r < 0)
+                return r;
+
+        r = pkcs11_token_find_x509_certificate(m, session, uri, &object);
+        if (r < 0)
+                return r;
+
+        r = pkcs11_token_read_x509_certificate(m, session, object, &data->cert);
+        if (r < 0)
+                return r;
+
+        /* 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;
+}
+#endif
+
+static int acquire_pkcs11_certificate(
+                const char *uri,
+                X509 **ret_cert,
+                char **ret_pin_used) {
+
+#if HAVE_P11KIT
+        _cleanup_(pkcs11_callback_data_release) struct pkcs11_callback_data data = {};
+        int r;
+
+        r = pkcs11_find_token(uri, pkcs11_callback, &data);
+        if (r == -EAGAIN) /* pkcs11_find_token() doesn't log about this error, but all others */
+                return log_error_errno(ENXIO, "Specified PKCS#11 token with URI '%s' not found.", uri);
+        if (r < 0)
+                return r;
+
+        *ret_cert = TAKE_PTR(data.cert);
+        *ret_pin_used = TAKE_PTR(data.pin_used);
+
+        return 0;
+#else
+        return log_error_errno(EOPNOTSUPP, "PKCS#11 tokens not supported on this build.");
+#endif
+}
+
+static int encrypt_bytes(
+                EVP_PKEY *pkey,
+                const void *decrypted_key,
+                size_t decrypted_key_size,
+                void **ret_encrypt_key,
+                size_t *ret_encrypt_key_size) {
+
+        _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
+        _cleanup_free_ void *b = NULL;
+        size_t l;
+
+        ctx = EVP_PKEY_CTX_new(pkey, NULL);
+        if (!ctx)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context");
+
+        if (EVP_PKEY_encrypt_init(ctx) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context");
+
+        if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding");
+
+        if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
+
+        b = malloc(l);
+        if (!b)
+                return log_oom();
+
+        if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size");
+
+        *ret_encrypt_key = TAKE_PTR(b);
+        *ret_encrypt_key_size = l;
+
+        return 0;
+}
+
+static int add_pkcs11_pin(JsonVariant **v, const char *pin) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL, *l = NULL;
+        _cleanup_(strv_free_erasep) char **pins = NULL;
+        int r;
+
+        assert(v);
+
+        if (isempty(pin))
+                return 0;
+
+        w = json_variant_ref(json_variant_by_key(*v, "secret"));
+        l = json_variant_ref(json_variant_by_key(w, "pkcs11Pin"));
+
+        r = json_variant_strv(l, &pins);
+        if (r < 0)
+                return log_error_errno(r, "Failed to convert PIN array: %m");
+
+        if (strv_find(pins, pin))
+                return 0;
+
+        r = strv_extend(&pins, pin);
+        if (r < 0)
+                return log_oom();
+
+        strv_uniq(pins);
+
+        l = json_variant_unref(l);
+
+        r = json_variant_new_array_strv(&l, pins);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate new PIN array JSON: %m");
+
+        json_variant_sensitive(l);
+
+        r = json_variant_set_field(&w, "pkcs11Pin", l);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update PIN field: %m");
+
+        r = json_variant_set_field(v, "secret", w);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update secret object: %m");
+
+        return 1;
+}
+
+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;
+        _cleanup_free_ char *salt = NULL;
+        struct crypt_data cd = {};
+        char *k;
+        int r;
+
+        assert(v);
+        assert(uri);
+        assert(encrypted_key);
+        assert(encrypted_key_size > 0);
+        assert(decrypted_key);
+        assert(decrypted_key_size > 0);
+
+        r = make_salt(&salt);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate salt: %m");
+
+        /* 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 */
+        r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded);
+        if (r < 0)
+                return log_error_errno(r, "Failed to base64 encode secret key: %m");
+
+        errno = 0;
+        k = crypt_r(base64_encoded, salt, &cd);
+        if (!k)
+                return log_error_errno(errno_or_else(EINVAL), "Failed to UNIX hash secret key: %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(k))));
+        if (r < 0)
+                return log_error_errno(r, "Failed to build encrypted JSON key object: %m");
+
+        w = json_variant_ref(json_variant_by_key(*v, "privileged"));
+        l = json_variant_ref(json_variant_by_key(w, "pkcs11EncryptedKey"));
+
+        r = json_variant_append_array(&l, e);
+        if (r < 0)
+                return log_error_errno(r, "Failed append PKCS#11 encrypted key: %m");
+
+        r = json_variant_set_field(&w, "pkcs11EncryptedKey", l);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set PKCS#11 encrypted key: %m");
+
+        r = json_variant_set_field(v, "privileged", w);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update privileged field: %m");
+
+        return 0;
+}
+
+static int add_pkcs11_token_uri(JsonVariant **v, const char *uri) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        _cleanup_strv_free_ char **l = NULL;
+        int r;
+
+        assert(v);
+        assert(uri);
+
+        w = json_variant_ref(json_variant_by_key(*v, "pkcs11TokenUri"));
+        if (w) {
+                r = json_variant_strv(w, &l);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse PKCS#11 token list: %m");
+
+                if (strv_contains(l, uri))
+                        return 0;
+        }
+
+        r = strv_extend(&l, uri);
+        if (r < 0)
+                return log_oom();
+
+        w = json_variant_unref(w);
+        r = json_variant_new_array_strv(&w, l);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create PKCS#11 token URI JSON: %m");
+
+        r = json_variant_set_field(v, "pkcs11TokenUri", w);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update PKCS#11 token URI list: %m");
+
+        return 0;
+}
+
+static int add_pkcs11_key_data(JsonVariant **v, const char *uri) {
+        _cleanup_(erase_and_freep) void *decrypted_key = NULL, *encrypted_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;
+        RSA *rsa;
+        int bits;
+        int r;
+
+        assert(v);
+
+        r = acquire_pkcs11_certificate(uri, &cert, &pin);
+        if (r < 0)
+                return r;
+
+        pkey = X509_get0_pubkey(cert);
+        if (!pkey)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to exract public key from X.509 certificate.");
+
+        if (EVP_PKEY_base_id(pkey) != EVP_PKEY_RSA)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "X.509 certificate does not refer to RSA key.");
+
+        rsa = EVP_PKEY_get0_RSA(pkey);
+        if (!rsa)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire RSA public key from X.509 certificate.");
+
+        bits = RSA_bits(rsa);
+        log_debug("Bits in RSA key: %i", bits);
+
+        /* We use PKCS#1 padding for the RSA cleartext, hence let's leave some extra space for it, hence only
+         * generate a random key half the size of the RSA length */
+        decrypted_key_size = bits / 8 / 2;
+
+        if (decrypted_key_size < 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Uh, RSA key size too short?");
+
+        log_debug("Generating %zu bytes random key.", decrypted_key_size);
+
+        decrypted_key = malloc(decrypted_key_size);
+        if (!decrypted_key)
+                return log_oom();
+
+        r = genuine_random_bytes(decrypted_key, decrypted_key_size, RANDOM_BLOCK);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate random key: %m");
+
+        r = 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");
+
+        /* Add the token URI to the public part of the record. */
+        r = add_pkcs11_token_uri(v, uri);
+        if (r < 0)
+                return r;
+
+        /* Include the encrypted version of the random key we just generated in the privileged part of the record */
+        r = add_pkcs11_encrypted_key(
+                        v,
+                        uri,
+                        encrypted_key, encrypted_key_size,
+                        decrypted_key, decrypted_key_size);
+        if (r < 0)
+                return r;
+
+        /* If we acquired the PIN also include it in the secret section of the record, so that systemd-homed
+         * can use it if it needs to, given that it likely needs to decrypt the key again to pass to LUKS or
+         * fscrypt. */
+        r = add_pkcs11_pin(v, pin);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int acquire_new_home_record(UserRecord **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        char **i;
+        int r;
+
+        assert(ret);
+
+        if (arg_identity) {
+                unsigned line, column;
+
+                r = json_parse_file(
+                                streq(arg_identity, "-") ? stdin : NULL,
+                                streq(arg_identity, "-") ? "<stdin>" : 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);
+        }
+
+        r = apply_identity_changes(&v);
+        if (r < 0)
+                return r;
+
+        r = add_disposition(&v);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, arg_pkcs11_token_uri) {
+                r = add_pkcs11_key_data(&v, *i);
+                if (r < 0)
+                        return r;
+        }
+
+        r = update_last_change(&v, true, false);
+        if (r < 0)
+                return r;
+
+        if (DEBUG_LOGGING)
+                json_variant_dump(v, JSON_FORMAT_PRETTY, NULL, NULL);
+
+        hr = user_record_new();
+        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);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(hr);
+        return 0;
+}
+
+static int acquire_new_password(
+                const char *user_name,
+                UserRecord *hr,
+                bool suggest) {
+
+        unsigned i = 5;
+        char *e;
+        int r;
+
+        assert(user_name);
+        assert(hr);
+
+        e = getenv("NEWPASSWORD");
+        if (e) {
+                /* As above, this is not for use, just for testing */
+
+                r = user_record_set_password(hr, STRV_MAKE(e), /* prepend = */ false);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to store password: %m");
+
+                string_erase(e);
+
+                if (unsetenv("NEWPASSWORD") < 0)
+                        return log_error_errno(errno, "Failed to unse $NEWPASSWORD: %m");
+
+                return 0;
+        }
+
+        if (suggest)
+                (void) suggest_passwords();
+
+        for (;;) {
+                _cleanup_(strv_free_erasep) char **first = NULL, **second = NULL;
+                _cleanup_free_ char *question = NULL;
+
+                if (--i == 0)
+                        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();
+
+                r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &first);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire password: %m");
+
+                question = mfree(question);
+                if (asprintf(&question, "Please enter new password for user %s (repeat):", user_name) < 0)
+                        return log_oom();
+
+                r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &second);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire password: %m");
+
+                if (strv_equal(first, second)) {
+                        r = user_record_set_password(hr, first, /* prepend = */ false);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to store password: %m");
+
+                        return 0;
+                }
+
+                log_error("Password didn't mach, try again.");
+        }
+}
+
+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;
+        _cleanup_strv_free_ char **original_hashed_passwords = NULL;
+        int r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        if (argc >= 2) {
+                /* If a username was specified, use it */
+
+                if (valid_user_group_name(argv[1]))
+                        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: %m", 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.");
+        }
+
+        r = acquire_new_home_record(&hr);
+        if (r < 0)
+                return r;
+
+        /* Remember the original hashed paswords before we add our own, so that we can return to them later,
+         * should the entered password turn out not to be acceptable. */
+        original_hashed_passwords = strv_copy(hr->hashed_password);
+        if (!original_hashed_passwords)
+                return log_oom();
+
+        /* If the JSON record carries no plain text password, then let's query it manually. */
+        if (!hr->password) {
+
+                if (strv_isempty(hr->hashed_password)) {
+                        /* No regular (i.e. non-PKCS#11) hashed passwords set in the record, let's fix that. */
+                        r = acquire_new_password(hr->user_name, hr, /* suggest = */ true);
+                        if (r < 0)
+                                return r;
+
+                        r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to hash password: %m");
+                } else {
+                        /* There's a hash password set in the record, acquire the unhashed version of it. */
+                        r = acquire_existing_password(hr->user_name, hr, false);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (hr->enforce_password_policy == 0) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+                /* If password quality enforcement is disabled, let's at least warn client side */
+
+                r = quality_check_password(hr, hr, &error);
+                if (r < 0)
+                        log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", bus_error_message(&error, 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;
+                _cleanup_(erase_and_freep) char *formatted = NULL;
+
+                r = json_variant_format(hr->json, 0, &formatted);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "CreateHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", formatted);
+                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))
+                                return log_error_errno(r, "Failed to create user home: %s", bus_error_message(&error, r));
+
+                        log_error_errno(r, "%s", bus_error_message(&error, r));
+                        log_info("(Use --enforce-password-policy=no to turn off password quality checks for this account.)");
+                } else
+                        break; /* done */
+
+                r = user_record_set_hashed_password(hr, original_hashed_passwords);
+                if (r < 0)
+                        return r;
+
+                r = acquire_new_password(hr->user_name, hr, /* suggest = */ false);
+                if (r < 0)
+                        return r;
+
+                r = user_record_make_hashed_password(hr, hr->password, /* extend = */ true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to hash passwords: %m");
+        }
+
+        return 0;
+}
+
+static int remove_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r, ret = 0;
+        char **i;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "RemoveHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", *i);
+                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) {
+                        log_error_errno(r, "Failed to remove home: %s", bus_error_message(&error, r));
+                        if (ret == 0)
+                                ret = r;
+                }
+        }
+
+        return ret;
+}
+
+static int acquire_updated_home_record(
+                sd_bus *bus,
+                const char *username,
+                UserRecord **ret) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *json = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        char **i;
+        int r;
+
+        assert(ret);
+
+        if (arg_identity) {
+                unsigned line, column;
+                JsonVariant *un;
+
+                r = json_parse_file(
+                                streq(arg_identity, "-") ? stdin : NULL,
+                                streq(arg_identity, "-") ? "<stdin>" : arg_identity, JSON_PARSE_SENSITIVE, &json, &line, &column);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
+
+                un = json_variant_by_key(json, "userName");
+                if (un) {
+                        if (!json_variant_is_string(un) || (username && !streq(json_variant_string(un), username)))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User name specified on command line and in JSON record do not match.");
+                } else {
+                        if (!username)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No username specified.");
+
+                        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");
+                }
+
+        } else {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+                int incomplete;
+                const char *text;
+
+                if (!identity_properties_specified())
+                        return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "No field to change specified.");
+
+                r = sd_bus_call_method(
+                                bus,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "GetUserRecordByName",
+                                &error,
+                                &reply,
+                                "s",
+                                username);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire user home record: %s", bus_error_message(&error, r));
+
+                r = sd_bus_message_read(reply, "sbo", &text, &incomplete, NULL);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (incomplete)
+                        return log_error_errno(SYNTHETIC_ERRNO(EACCES), "Lacking rights to acquire user record including privileged metadata, can't update record.");
+
+                r = json_parse(text, JSON_PARSE_SENSITIVE, &json, NULL, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse JSON identity: %m");
+
+                reply = sd_bus_message_unref(reply);
+
+                r = json_variant_filter(&json, STRV_MAKE("binding", "status", "signature"));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to strip binding and status from record to update: %m");
+        }
+
+        r = apply_identity_changes(&json);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, arg_pkcs11_token_uri) {
+                r = add_pkcs11_key_data(&json, *i);
+                if (r < 0)
+                        return r;
+        }
+
+        /* If the user supplied a full record, then add in lastChange, but do not override. Otherwise always
+         * override. */
+        r = update_last_change(&json, !!arg_pkcs11_token_uri, !arg_identity);
+        if (r < 0)
+                return r;
+
+        if (DEBUG_LOGGING)
+                json_variant_dump(json, JSON_FORMAT_PRETTY, NULL, NULL);
+
+        hr = user_record_new();
+        if (!hr)
+                return log_oom();
+
+        r = user_record_load(hr, json, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_SECRET|USER_RECORD_ALLOW_SIGNATURE|USER_RECORD_LOG);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(hr);
+        return 0;
+}
+
+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;
+        _cleanup_free_ char *buffer = NULL;
+        const char *username;
+        int r;
+
+        if (argc >= 2)
+                username = argv[1];
+        else if (!arg_identity) {
+                buffer = getusername_malloc();
+                if (!buffer)
+                        return log_oom();
+
+                username = buffer;
+        } else
+                username = NULL;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        r = acquire_updated_home_record(bus, username, &hr);
+        if (r < 0)
+                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;
+                _cleanup_free_ char *formatted = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "UpdateHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = json_variant_format(hr->json, 0, &formatted);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append(m, "s", formatted);
+                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 &&
+                            sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
+                                /* In the generic handler we'd ask for a password in this case, but when
+                                 * changing passwords that's not sufficient, as we need to acquire all keys
+                                 * first. */
+                                return log_error_errno(r, "Security token not inserted, refusing.");
+
+                        r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
+                        if (r < 0)
+                                return r;
+                } else
+                        break;
+        }
+
+        /* Also sync down disk size to underlying LUKS/fscrypt/quota */
+        while (arg_and_resize) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                log_debug("Resizing");
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "ResizeHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                /* Specify UINT64_MAX as size, in which case the underlying disk size will just be synced */
+                r = sd_bus_message_append(m, "st", hr->user_name, UINT64_MAX);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = bus_message_append_secret(m, hr);
+                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 &&
+                            sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
+                                return log_error_errno(r, "Security token not inserted, refusing.");
+
+                        r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
+                        if (r < 0)
+                                return r;
+                } else
+                        break;
+        }
+
+        /* Also sync down passwords to underlying LUKS/fscrypt */
+        while (arg_and_change_password) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                log_debug("Propagating password");
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "ChangePasswordHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                /* Specify an empty new secret, in which case the underlying LUKS/fscrypt password will just be synced */
+                r = sd_bus_message_append(m, "ss", hr->user_name, "{}");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = bus_message_append_secret(m, hr);
+                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_BAD_PASSWORD_AND_NO_TOKEN))
+                                return log_error_errno(r, "Security token not inserted, refusing.");
+
+                        r = handle_generic_user_record_error(hr->user_name, hr, &error, r, false);
+                        if (r < 0)
+                                return r;
+                } else
+                        break;
+        }
+
+        return 0;
+}
+
+static int passwd_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(user_record_unrefp) UserRecord *old_secret = NULL, *new_secret = NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_free_ char *buffer = NULL;
+        const char *username;
+        int r;
+
+        if (arg_pkcs11_token_uri)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "To change the PKCS#11 security token use 'homectl update --pkcs11-token-uri=…'.");
+        if (identity_properties_specified())
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The 'passwd' verb does not permit changing other record properties at the same time.");
+
+        if (argc >= 2)
+                username = argv[1];
+        else {
+                buffer = getusername_malloc();
+                if (!buffer)
+                        return log_oom();
+
+                username = buffer;
+        }
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        old_secret = user_record_new();
+        if (!old_secret)
+                return log_oom();
+
+        new_secret = user_record_new();
+        if (!new_secret)
+                return log_oom();
+
+        r = acquire_new_password(username, new_secret, /* suggest = */ true);
+        if (r < 0)
+                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;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "ChangePasswordHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", username);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = bus_message_append_secret(m, new_secret);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = bus_message_append_secret(m, old_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_LOW_PASSWORD_QUALITY)) {
+
+                                log_error_errno(r, "%s", bus_error_message(&error, r));
+
+                                r = acquire_new_password(username, new_secret, /* suggest = */ false);
+
+                        } else if (sd_bus_error_has_name(&error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN))
+
+                                /* In the generic handler we'd ask for a password in this case, but when
+                                 * changing passwords that's not sufficeint, as we need to acquire all keys
+                                 * first. */
+                                return log_error_errno(r, "Security token not inserted, refusing.");
+                        else
+                                r = handle_generic_user_record_error(username, old_secret, &error, r, true);
+                        if (r < 0)
+                                return r;
+                } else
+                        break;
+        }
+
+        return 0;
+}
+
+static int resize_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        uint64_t ds = UINT64_MAX;
+        int r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        (void) polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
+
+        if (arg_disk_size_relative != UINT64_MAX ||
+            (argc > 2 && parse_percent(argv[2]) >= 0))
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "Relative disk size specification currently not supported when resizing.");
+
+        if (argc > 2) {
+                r = parse_size(argv[2], 1024, &ds);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse disk size parameter: %s", argv[2]);
+        }
+
+        if (arg_disk_size != UINT64_MAX) {
+                if (ds != UINT64_MAX && ds != arg_disk_size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Disk size specified twice and doesn't match, refusing.");
+
+                ds = arg_disk_size;
+        }
+
+        secret = user_record_new();
+        if (!secret)
+                return log_oom();
+
+        for (;;) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "ResizeHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "st", argv[1], ds);
+                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) {
+                        r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
+                        if (r < 0)
+                                return r;
+                } else
+                        break;
+        }
+
+        return 0;
+}
+
+static int lock_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r, ret = 0;
+        char **i;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "LockHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", *i);
+                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) {
+                        log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r));
+                        if (ret == 0)
+                                ret = r;
+                }
+        }
+
+        return ret;
+}
+
+static int unlock_home(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r, ret = 0;
+        char **i;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(i, strv_skip(argv, 1)) {
+                _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+
+                secret = user_record_new();
+                if (!secret)
+                        return log_oom();
+
+                for (;;) {
+                        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                        r = sd_bus_message_new_method_call(
+                                        bus,
+                                        &m,
+                                        "org.freedesktop.home1",
+                                        "/org/freedesktop/home1",
+                                        "org.freedesktop.home1.Manager",
+                                        "UnlockHome");
+                        if (r < 0)
+                                return bus_log_create_error(r);
+
+                        r = sd_bus_message_append(m, "s", *i);
+                        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) {
+                                r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
+                                if (r < 0) {
+                                        if (ret == 0)
+                                                ret = r;
+
+                                        break;
+                                }
+                        } else
+                                break;
+                }
+        }
+
+        return ret;
+}
+
+static int with_home(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 *m = NULL, *reply = NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        _cleanup_close_ int acquired_fd = -1;
+        _cleanup_strv_free_ char **cmdline  = NULL;
+        const char *home;
+        int r, ret;
+        pid_t pid;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        if (argc < 3) {
+                _cleanup_free_ char *shell = NULL;
+
+                /* If no command is specified, spawn a shell */
+                r = get_shell(&shell);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire shell: %m");
+
+                cmdline = strv_new(shell);
+        } else
+                cmdline = strv_copy(argv + 2);
+        if (!cmdline)
+                return log_oom();
+
+        secret = user_record_new();
+        if (!secret)
+                return log_oom();
+
+        for (;;) {
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "AcquireHome");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_append(m, "s", argv[1]);
+                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_message_append(m, "b", /* please_suspend = */ getenv_bool("SYSTEMD_PLEASE_SUSPEND_HOME") > 0);
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, &reply);
+                m = sd_bus_message_unref(m);
+                if (r < 0) {
+                        r = handle_generic_user_record_error(argv[1], secret, &error, r, false);
+                        if (r < 0)
+                                return r;
+
+                        sd_bus_error_free(&error);
+                } else {
+                        int fd;
+
+                        r = sd_bus_message_read(reply, "h", &fd);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+                        if (acquired_fd < 0)
+                                return log_error_errno(errno, "Failed to duplicate acquired fd: %m");
+
+                        reply = sd_bus_message_unref(reply);
+                        break;
+                }
+        }
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "GetHomeByName",
+                        &error,
+                        &reply,
+                        "s",
+                        argv[1]);
+        if (r < 0)
+                return log_error_errno(r, "Failed to inspect home: %s", bus_error_message(&error, r));
+
+        r = sd_bus_message_read(reply, "usussso", NULL, NULL, NULL, NULL, &home, NULL, NULL);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        r = safe_fork("(with)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REOPEN_LOG, &pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                if (chdir(home) < 0) {
+                        log_error_errno(errno, "Failed to change to directory %s: %m", home);
+                        _exit(255);
+                }
+
+                execvp(cmdline[0], cmdline);
+                log_error_errno(errno, "Failed to execute %s: %m", cmdline[0]);
+                _exit(255);
+        }
+
+        ret = wait_for_terminate_and_check(cmdline[0], pid, WAIT_LOG_ABNORMAL);
+
+        /* Close the fd that pings the home now. */
+        acquired_fd = safe_close(acquired_fd);
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "ReleaseHome");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        r = sd_bus_message_append(m, "s", argv[1]);
+        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_BUSY))
+                        log_notice("Not deactivating home directory of %s, as it is still used.", argv[1]);
+                else
+                        return log_error_errno(r, "Failed to release user home: %s", bus_error_message(&error, r));
+        }
+
+        return ret;
+}
+
+static int lock_all_homes(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 *m = NULL;
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        r = acquire_bus(&bus);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "LockAllHomes");
+        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)
+                return log_error_errno(r, "Failed to lock home: %s", bus_error_message(&error, r));
+
+        return 0;
+}
+
+static int drop_from_identity(const char *field) {
+        int r;
+
+        assert(field);
+
+        /* If we are called to update an identity record and drop some field, let's keep track of what to
+         * remove from the old record */
+        r = strv_extend(&arg_identity_filter, field);
+        if (r < 0)
+                return log_oom();
+
+        /* Let's also drop the field if it was previously set to a new value on the same command line */
+        r = json_variant_filter(&arg_identity_extra, STRV_MAKE(field));
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter JSON identity data: %m");
+
+        r = json_variant_filter(&arg_identity_extra_this_machine, STRV_MAKE(field));
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter JSON identity data: %m");
+
+        r = json_variant_filter(&arg_identity_extra_privileged, STRV_MAKE(field));
+        if (r < 0)
+                return log_error_errno(r, "Failed to filter JSON identity data: %m");
+
+        return 0;
+}
+
+static int help(int argc, char *argv[], void *userdata) {
+        _cleanup_free_ char *link = NULL;
+        int r;
+
+        (void) pager_open(arg_pager_flags);
+
+        r = terminal_urlify_man("homectl", "1", &link);
+        if (r < 0)
+                return log_oom();
+
+        printf("%1$s [OPTIONS...] COMMAND ...\n\n"
+               "%2$sCreate, manipulate or inspect home directories.%3$s\n"
+               "\n%4$sCommands:%5$s\n"
+               "  list                        List homes\n"
+               "  activate USER…              Activate home\n"
+               "  deactivate USER…            Deactivate home\n"
+               "  inspect USER…               Inspect home\n"
+               "  authenticate USER…          Authenticate home\n"
+               "  create USER                 Create a home\n"
+               "  remove USER…                Remove a home\n"
+               "  update USER                 Update a home\n"
+               "  passwd USER                 Change password of a home\n"
+               "  resize USER SIZE            Resize a home\n"
+               "  lock USER…                  Temporarily lock an active home\n"
+               "  unlock USER…                Unlock a temporarily locked home\n"
+               "  lock-all                    Lock all suitable homes\n"
+               "  with USER [COMMAND…]        Run shell or command with access to home\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"
+               "  -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"
+               "     --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"
+               "     --export-format=         Strip JSON inspection data (full, stripped,\n"
+               "                              minimal)\n"
+               "  -E                          When specified once equals -j --export-format=\n"
+               "                              stripped, when specified twice equals\n"
+               "                              -j --export-format=minimal\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"
+               "     --email-address=EMAIL    Email address for user\n"
+               "     --location=LOCATION      Set location of user on earth\n"
+               "     --icon-name=NAME         Icon name for user\n"
+               "  -d --home-dir=PATH          Home directory\n"
+               "     --uid=UID                Numeric UID for user\n"
+               "  -G --member-of=GROUP        Add user to group\n"
+               "     --skel=PATH              Skeleton directory to use\n"
+               "     --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"
+               "     --ssh-authorized-keys=KEYS\n"
+               "                              Specify SSH public keys\n"
+               "     --pkcs11-token-uri=URI   URI to PKCS#11 security token containing\n"
+               "                              private key and matching X.509 certificate\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"
+               "     --rate-limit-interval=SECS\n"
+               "                              Login rate-limit interval in seconds\n"
+               "     --rate-limit-burst=NUMBER\n"
+               "                              Login rate-limit attempts per interval\n"
+               "\n%4$sPassword Policy User Record Properties:%5$s\n"
+               "     --password-hint=HINT     Set Password hint\n"
+               "     --enforce-password-policy=BOOL\n"
+               "                              Control whether to enforce system's password\n"
+               "                              policy for this user\n"
+               "  -P                          Equivalent to --enforce-password-password=no\n"
+               "     --password-change-now=BOOL\n"
+               "                              Require the password to be changed on next login\n"
+               "     --password-change-min=TIME\n"
+               "                              Require minimum time between password changes\n"
+               "     --password-change-max=TIME\n"
+               "                              Require maximum time between password changes\n"
+               "     --password-change-warn=TIME\n"
+               "                              How much time to warn before password expiry\n"
+               "     --password-change-inactive=TIME\n"
+               "                              How much time to block password after expiry\n"
+               "\n%4$sResource Management User Record Properties:%5$s\n"
+               "     --disk-size=BYTES        Size to assign the user on disk\n"
+               "     --access-mode=MODE       User home directory access mode\n"
+               "     --umask=MODE             Umask for user when logging in\n"
+               "     --nice=NICE              Nice level for user\n"
+               "     --rlimit=LIMIT=VALUE[:VALUE]\n"
+               "                              Set resource limits\n"
+               "     --tasks-max=MAX          Set maximum number of per-user tasks\n"
+               "     --memory-high=BYTES      Set high memory threshold in bytes\n"
+               "     --memory-max=BYTES       Set maximum memory limit\n"
+               "     --cpu-weight=WEIGHT      Set CPU weight\n"
+               "     --io-weight=WEIGHT       Set IO weight\n"
+               "\n%4$sStorage User Record Properties:%5$s\n"
+               "     --storage=STORAGE        Storage type to use (luks, fscrypt, directory,\n"
+               "                              subvolume, cifs)\n"
+               "     --image-path=PATH        Path to image file/directory\n"
+               "\n%4$sLUKS Storage User Record Properties:%5$s\n"
+               "     --fs-type=TYPE           File system type to use in case of luks\n"
+               "                              storage (ext4, xfs, btrfs)\n"
+               "     --luks-discard=BOOL      Whether to use 'discard' feature of file system\n"
+               "     --luks-cipher=CIPHER     Cipher to use for LUKS encryption\n"
+               "     --luks-cipher-mode=MODE  Cipher mode to use for LUKS encryption\n"
+               "     --luks-volume-key-size=BITS\n"
+               "                              Volume key size to use for LUKS encryption\n"
+               "     --luks-pbkdf-type=TYPE   Password-based Key Derivation Function to use\n"
+               "     --luks-pbkdf-hash-algorithm=ALGORITHM\n"
+               "                              PBKDF hash algorithm to use\n"
+               "     --luks-pbkdf-time-cost=SECS\n"
+               "                              Time cost for PBKDF in seconds\n"
+               "     --luks-pbkdf-memory-cost=BYTES\n"
+               "                              Memory cost for PBKDF in bytes\n"
+               "     --luks-pbkdf-parallel-threads=NUMBER\n"
+               "                              Number of parallel threads for PKBDF\n"
+               "\n%4$sMounting User Record Properties:%5$s\n"
+               "     --nosuid=BOOL            Control the 'nosuid' flag of the home mount\n"
+               "     --nodev=BOOL             Control the 'nodev' flag of the home mount\n"
+               "     --noexec=BOOL            Control the 'noexec' flag of the home mount\n"
+               "\n%4$sCIFS User Record Properties:%5$s\n"
+               "     --cifs-domain=DOMAIN     CIFS (Windows) domain\n"
+               "     --cifs-user-name=USER    CIFS (Windows) user name\n"
+               "     --cifs-service=SERVICE   CIFS (Windows) service to mount as home\n"
+               "\n%4$sLogin Behaviour User Record Properties:%5$s\n"
+               "     --stop-delay=SECS        How long to leave user services running after\n"
+               "                              logout\n"
+               "     --kill-processes=BOOL    Whether to kill user processes when sessions\n"
+               "                              terminate\n"
+               "     --auto-login=BOOL        Try to log this user in automatically\n"
+               "\nSee the %6$s for details.\n"
+               , program_invocation_short_name
+               , ansi_highlight(), ansi_normal()
+               , ansi_underline(), ansi_normal()
+               , link
+        );
+
+        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_REALM,
+                ARG_EMAIL_ADDRESS,
+                ARG_DISK_SIZE,
+                ARG_ACCESS_MODE,
+                ARG_STORAGE,
+                ARG_FS_TYPE,
+                ARG_IMAGE_PATH,
+                ARG_UMASK,
+                ARG_LUKS_DISCARD,
+                ARG_JSON,
+                ARG_SETENV,
+                ARG_TIMEZONE,
+                ARG_LANGUAGE,
+                ARG_LOCKED,
+                ARG_SSH_AUTHORIZED_KEYS,
+                ARG_LOCATION,
+                ARG_ICON_NAME,
+                ARG_PASSWORD_HINT,
+                ARG_NICE,
+                ARG_RLIMIT,
+                ARG_NOT_BEFORE,
+                ARG_NOT_AFTER,
+                ARG_LUKS_CIPHER,
+                ARG_LUKS_CIPHER_MODE,
+                ARG_LUKS_VOLUME_KEY_SIZE,
+                ARG_NOSUID,
+                ARG_NODEV,
+                ARG_NOEXEC,
+                ARG_CIFS_DOMAIN,
+                ARG_CIFS_USER_NAME,
+                ARG_CIFS_SERVICE,
+                ARG_TASKS_MAX,
+                ARG_MEMORY_HIGH,
+                ARG_MEMORY_MAX,
+                ARG_CPU_WEIGHT,
+                ARG_IO_WEIGHT,
+                ARG_LUKS_PBKDF_TYPE,
+                ARG_LUKS_PBKDF_HASH_ALGORITHM,
+                ARG_LUKS_PBKDF_TIME_COST,
+                ARG_LUKS_PBKDF_MEMORY_COST,
+                ARG_LUKS_PBKDF_PARALLEL_THREADS,
+                ARG_RATE_LIMIT_INTERVAL,
+                ARG_RATE_LIMIT_BURST,
+                ARG_STOP_DELAY,
+                ARG_KILL_PROCESSES,
+                ARG_ENFORCE_PASSWORD_POLICY,
+                ARG_PASSWORD_CHANGE_NOW,
+                ARG_PASSWORD_CHANGE_MIN,
+                ARG_PASSWORD_CHANGE_MAX,
+                ARG_PASSWORD_CHANGE_WARN,
+                ARG_PASSWORD_CHANGE_INACTIVE,
+                ARG_EXPORT_FORMAT,
+                ARG_AUTO_LOGIN,
+                ARG_PKCS11_TOKEN_URI,
+                ARG_AND_RESIZE,
+                ARG_AND_CHANGE_PASSWORD,
+        };
+
+        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-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-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 },
+                { "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                },
+                { "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            },
+                { "and-resize",                  required_argument, NULL, ARG_AND_RESIZE                  },
+                { "and-change-password",         required_argument, NULL, ARG_AND_CHANGE_PASSWORD         },
+                {}
+        };
+
+        int r;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        for (;;) {
+                int c;
+
+                c = getopt_long(argc, argv, "hH:M:I:c:d:u:k:s:e:G:jPE", 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 'I':
+                        arg_identity = optarg;
+                        break;
+
+                case 'c':
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("realName");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!valid_gecos(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Real name '%s' not a valid GECOS field.", optarg);
+
+                        r = json_variant_set_field_string(&arg_identity_extra, "realName", optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set realName field: %m");
+
+                        break;
+
+                case 'd': {
+                        _cleanup_free_ char *hd = NULL;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("homeDirectory");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_path_argument_and_warn(optarg, false, &hd);
+                        if (r < 0)
+                                return r;
+
+                        if (!valid_home(hd))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home directory '%s' not valid.", hd);
+
+                        r = json_variant_set_field_string(&arg_identity_extra, "homeDirectory", hd);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set homeDirectory field: %m");
+
+                        break;
+                }
+
+                case ARG_REALM:
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("realm");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = dns_name_is_valid(optarg);
+                        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);
+
+                        r = json_variant_set_field_string(&arg_identity_extra, "realm", optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set realm field: %m");
+                        break;
+
+                case ARG_EMAIL_ADDRESS:
+                case ARG_LOCATION:
+                case ARG_ICON_NAME:
+                case ARG_CIFS_USER_NAME:
+                case ARG_CIFS_DOMAIN:
+                case ARG_CIFS_SERVICE: {
+
+                        const char *field =
+                                c == ARG_EMAIL_ADDRESS ? "emailAddress" :
+                                     c == ARG_LOCATION ? "location" :
+                                    c == ARG_ICON_NAME ? "iconName" :
+                               c == ARG_CIFS_USER_NAME ? "cifsUserName" :
+                                  c == ARG_CIFS_DOMAIN ? "cifsDomain" :
+                                 c == ARG_CIFS_SERVICE ? "cifsService" :
+                                                         NULL;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = json_variant_set_field_string(&arg_identity_extra, field, optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case ARG_PASSWORD_HINT:
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("passwordHint");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = json_variant_set_field_string(&arg_identity_extra_privileged, "passwordHint", optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set passwordHint field: %m");
+
+                        string_erase(optarg);
+                        break;
+
+                case ARG_NICE: {
+                        int nc;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("niceLevel");
+                                if (r < 0)
+                                        return r;
+                                break;
+                        }
+
+                        r = parse_nice(optarg, &nc);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse nice level: %s", optarg);
+
+                        r = json_variant_set_field_integer(&arg_identity_extra, "niceLevel", nc);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set niceLevel field: %m");
+
+                        break;
+                }
+
+                case ARG_RLIMIT: {
+                        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *jcur = NULL, *jmax = NULL;
+                        _cleanup_free_ char *field = NULL, *t = NULL;
+                        const char *eq;
+                        struct rlimit rl;
+                        int l;
+
+                        if (isempty(optarg)) {
+                                /* Remove all resource limits */
+
+                                r = drop_from_identity("resourceLimits");
+                                if (r < 0)
+                                        return r;
+
+                                arg_identity_filter_rlimits = strv_free(arg_identity_filter_rlimits);
+                                arg_identity_extra_rlimits = json_variant_unref(arg_identity_extra_rlimits);
+                                break;
+                        }
+
+                        eq = strchr(optarg, '=');
+                        if (!eq)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't parse resource limit assignment: %s", optarg);
+
+                        field = strndup(optarg, eq - optarg);
+                        if (!field)
+                                return log_oom();
+
+                        l = rlimit_from_string_harder(field);
+                        if (l < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown resource limit type: %s", field);
+
+                        if (isempty(eq + 1)) {
+                                /* Remove only the specific rlimit */
+
+                                r = strv_extend(&arg_identity_filter_rlimits, rlimit_to_string(l));
+                                if (r < 0)
+                                        return r;
+
+                                r = json_variant_filter(&arg_identity_extra_rlimits, STRV_MAKE(field));
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to filter JSON identity data: %m");
+
+                                break;
+                        }
+
+                        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);
+
+                        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");
+
+                        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");
+
+                        r = json_build(&v,
+                                       JSON_BUILD_OBJECT(
+                                                       JSON_BUILD_PAIR("cur", JSON_BUILD_VARIANT(jcur)),
+                                                       JSON_BUILD_PAIR("max", JSON_BUILD_VARIANT(jmax))));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to build resource limit: %m");
+
+                        t = strjoin("RLIMIT_", rlimit_to_string(l));
+                        if (!t)
+                                return log_oom();
+
+                        r = json_variant_set_field(&arg_identity_extra_rlimits, t, v);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", rlimit_to_string(l));
+
+                        break;
+                }
+
+                case 'u': {
+                        uid_t uid;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("uid");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_uid(optarg, &uid);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse UID '%s'.", optarg);
+
+                        if (uid_is_system(uid))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in system range, refusing.", uid);
+                        if (uid_is_dynamic(uid))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is in dynamic range, refusing.", uid);
+                        if (uid == UID_NOBODY)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "UID " UID_FMT " is nobody UID, refusing.", uid);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, "uid", uid);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set realm field: %m");
+
+                        break;
+                }
+
+                case 'k':
+                case ARG_IMAGE_PATH: {
+                        const char *field = c == 'k' ? "skeletonDirectory" : "imagePath";
+                        _cleanup_free_ char *v = NULL;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_path_argument_and_warn(optarg, false, &v);
+                        if (r < 0)
+                                return r;
+
+                        r = json_variant_set_field_string(&arg_identity_extra_this_machine, field, v);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", v);
+
+                        break;
+                }
+
+                case 's':
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("shell");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!valid_shell(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Shell '%s' not valid.", optarg);
+
+                        r = json_variant_set_field_string(&arg_identity_extra, "shell", optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set shell field: %m");
+
+                        break;
+
+                case ARG_SETENV: {
+                        _cleanup_free_ char **l = NULL, **k = NULL;
+                        _cleanup_(json_variant_unrefp) JsonVariant *ne = NULL;
+                        JsonVariant *e;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("environment");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!env_assignment_is_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Environment assignment '%s' not valid.", optarg);
+
+                        e = json_variant_by_key(arg_identity_extra, "environment");
+                        if (e) {
+                                r = json_variant_strv(e, &l);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse JSON environment field: %m");
+                        }
+
+                        k = strv_env_set(l, optarg);
+                        if (!k)
+                                return log_oom();
+
+                        strv_sort(k);
+
+                        r = json_variant_new_array_strv(&ne, k);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to allocate environment list JSON: %m");
+
+                        r = json_variant_set_field(&arg_identity_extra, "environment", ne);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set environent list: %m");
+
+                        break;
+                }
+
+                case ARG_TIMEZONE:
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("timeZone");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!timezone_is_valid(optarg, LOG_DEBUG))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Timezone '%s' is not valid.", optarg);
+
+                        r = json_variant_set_field_string(&arg_identity_extra, "timeZone", optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set timezone field: %m");
+
+                        break;
+
+                case ARG_LANGUAGE:
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("language");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!locale_is_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Locale '%s' is not valid.", optarg);
+
+                        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");
+
+                        break;
+
+                case ARG_NOSUID:
+                case ARG_NODEV:
+                case ARG_NOEXEC:
+                case ARG_LOCKED:
+                case ARG_KILL_PROCESSES:
+                case ARG_ENFORCE_PASSWORD_POLICY:
+                case ARG_AUTO_LOGIN:
+                case ARG_PASSWORD_CHANGE_NOW: {
+                        const char *field =
+                                                 c == ARG_LOCKED ? "locked" :
+                                                 c == ARG_NOSUID ? "mountNoSuid" :
+                                                  c == ARG_NODEV ? "mountNoDevices" :
+                                                 c == ARG_NOEXEC ? "mountNoExecute" :
+                                         c == ARG_KILL_PROCESSES ? "killProcesses" :
+                                c == ARG_ENFORCE_PASSWORD_POLICY ? "enforcePasswordPolicy" :
+                                             c == ARG_AUTO_LOGIN ? "autoLogin" :
+                                    c == ARG_PASSWORD_CHANGE_NOW ? "passwordChangeNow" :
+                                                                   NULL;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s boolean: %m", field);
+
+                        r = json_variant_set_field_boolean(&arg_identity_extra, field, r > 0);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case 'P':
+                        r = json_variant_set_field_boolean(&arg_identity_extra, "enforcePasswordPolicy", false);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set enforcePasswordPolicy field: %m");
+
+                        break;
+
+                case ARG_DISK_SIZE:
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("diskSize");
+                                if (r < 0)
+                                        return r;
+
+                                r = drop_from_identity("diskSizeRelative");
+                                if (r < 0)
+                                        return r;
+
+                                arg_disk_size = arg_disk_size_relative = UINT64_MAX;
+                                break;
+                        }
+
+                        r = parse_permille(optarg);
+                        if (r < 0) {
+                                r = parse_size(optarg, 1024, &arg_disk_size);
+                                if (r < 0)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Disk size '%s' not valid.", optarg);
+
+                                r = drop_from_identity("diskSizeRelative");
+                                if (r < 0)
+                                        return r;
+
+                                r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, "diskSize", arg_disk_size);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set diskSize field: %m");
+
+                                arg_disk_size_relative = UINT64_MAX;
+                        } else {
+                                /* Normalize to UINT32_MAX == 100% */
+                                arg_disk_size_relative = (uint64_t) r * UINT32_MAX / 1000U;
+
+                                r = drop_from_identity("diskSize");
+                                if (r < 0)
+                                        return r;
+
+                                r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, "diskSizeRelative", arg_disk_size_relative);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set diskSizeRelative field: %m");
+
+                                arg_disk_size = UINT64_MAX;
+                        }
+
+                        break;
+
+                case ARG_ACCESS_MODE: {
+                        mode_t mode;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("accessMode");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_mode(optarg, &mode);
+                        if (r < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Access mode '%s' not valid.", optarg);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, "accessMode", mode);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set access mode field: %m");
+
+                        break;
+                }
+
+                case ARG_LUKS_DISCARD:
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("luksDiscard");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --luks-discard= parameter: %s", optarg);
+
+                        r = json_variant_set_field_boolean(&arg_identity_extra, "luksDiscard", r);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set discard field: %m");
+
+                        break;
+
+                case ARG_LUKS_VOLUME_KEY_SIZE:
+                case ARG_LUKS_PBKDF_PARALLEL_THREADS:
+                case ARG_RATE_LIMIT_BURST: {
+                        const char *field =
+                                       c == ARG_LUKS_VOLUME_KEY_SIZE ? "luksVolumeKeySize" :
+                                c == ARG_LUKS_PBKDF_PARALLEL_THREADS ? "luksPbkdfParallelThreads" :
+                                           c == ARG_RATE_LIMIT_BURST ? "rateLimitBurst" : NULL;
+                        unsigned n;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        r = safe_atou(optarg, &n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case ARG_UMASK: {
+                        mode_t m;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("umask");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_mode(optarg, &m);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse umask: %m");
+
+                        r = json_variant_set_field_integer(&arg_identity_extra, "umask", m);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set umask field: %m");
+
+                        break;
+                }
+
+                case ARG_SSH_AUTHORIZED_KEYS: {
+                        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+                        _cleanup_(strv_freep) char **l = NULL, **add = NULL;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("sshAuthorizedKeys");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (optarg[0] == '@') {
+                                _cleanup_fclose_ FILE *f = NULL;
+
+                                /* If prefixed with '@' read from a file */
+
+                                f = fopen(optarg+1, "re");
+                                if (!f)
+                                        return log_error_errno(errno, "Failed to open '%s': %m", optarg+1);
+
+                                for (;;) {
+                                        _cleanup_free_ char *line = NULL;
+
+                                        r = read_line(f, LONG_LINE_MAX, &line);
+                                        if (r < 0)
+                                                return log_error_errno(r, "Faile dto read from '%s': %m", optarg+1);
+                                        if (r == 0)
+                                                break;
+
+                                        if (isempty(line))
+                                                continue;
+
+                                        if (line[0] == '#')
+                                                continue;
+
+                                        r = strv_consume(&add, TAKE_PTR(line));
+                                        if (r < 0)
+                                                return log_oom();
+                                }
+                        } else {
+                                /* Otherwise, assume it's a literal key. Let's do some superficial checks
+                                 * before accept it though. */
+
+                                if (string_has_cc(optarg, NULL))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Authorized key contains control characters, refusing.");
+                                if (optarg[0] == '#')
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified key is a comment?");
+
+                                add = strv_new(optarg);
+                                if (!add)
+                                        return log_oom();
+                        }
+
+                        v = json_variant_ref(json_variant_by_key(arg_identity_extra_privileged, "sshAuthorizedKeys"));
+                        if (v) {
+                                r = json_variant_strv(v, &l);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse SSH authorized keys list: %m");
+                        }
+
+                        r = strv_extend_strv(&l, add, true);
+                        if (r < 0)
+                                return log_oom();
+
+                        v = json_variant_unref(v);
+
+                        r = json_variant_new_array_strv(&v, l);
+                        if (r < 0)
+                                return log_oom();
+
+                        r = json_variant_set_field(&arg_identity_extra_privileged, "sshAuthorizedKeys", v);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set authorized keys: %m");
+
+                        break;
+                }
+
+                case ARG_NOT_BEFORE:
+                case ARG_NOT_AFTER:
+                case 'e': {
+                        const char *field;
+                        usec_t n;
+
+                        field =           c == ARG_NOT_BEFORE ? "notBeforeUSec" :
+                                IN_SET(c, ARG_NOT_AFTER, 'e') ? "notAfterUSec" : NULL;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        /* Note the minor discrepancy regarding -e parsing here: we support that for compat
+                         * reasons, and in the original useradd(8) implementation it accepts dates in the
+                         * format YYYY-MM-DD. Coincidentally, we accept dates formatted like that too, but
+                         * with greater precision. */
+                        r = parse_timestamp(optarg, &n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s parameter: %m", field);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+                        break;
+                }
+
+                case ARG_PASSWORD_CHANGE_MIN:
+                case ARG_PASSWORD_CHANGE_MAX:
+                case ARG_PASSWORD_CHANGE_WARN:
+                case ARG_PASSWORD_CHANGE_INACTIVE: {
+                        const char *field;
+                        usec_t n;
+
+                        field =      c == ARG_PASSWORD_CHANGE_MIN ? "passwordChangeMinUSec" :
+                                     c == ARG_PASSWORD_CHANGE_MAX ? "passwordChangeMaxUSec" :
+                                    c == ARG_PASSWORD_CHANGE_WARN ? "passwordChangeWarnUSec" :
+                                c == ARG_PASSWORD_CHANGE_INACTIVE ? "passwordChangeInactiveUSec" :
+                                                                    NULL;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_sec(optarg, &n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s parameter: %m", field);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, field, n);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+                        break;
+                }
+
+                case ARG_STORAGE:
+                case ARG_FS_TYPE:
+                case ARG_LUKS_CIPHER:
+                case ARG_LUKS_CIPHER_MODE:
+                case ARG_LUKS_PBKDF_TYPE:
+                case ARG_LUKS_PBKDF_HASH_ALGORITHM: {
+
+                        const char *field =
+                                                  c == ARG_STORAGE ? "storage" :
+                                                  c == ARG_FS_TYPE ? "fileSytemType" :
+                                              c == ARG_LUKS_CIPHER ? "luksCipher" :
+                                         c == ARG_LUKS_CIPHER_MODE ? "luksCipherMode" :
+                                          c == ARG_LUKS_PBKDF_TYPE ? "luksPbkdfType" :
+                                c == ARG_LUKS_PBKDF_HASH_ALGORITHM ? "luksPbkdfHashAlgorithm" : NULL;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        if (!string_is_safe(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parameter for %s field not valid: %s", field, optarg);
+
+                        r = json_variant_set_field_string(
+                                        IN_SET(c, ARG_STORAGE, ARG_FS_TYPE) ?
+                                        &arg_identity_extra_this_machine :
+                                        &arg_identity_extra, field, optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case ARG_LUKS_PBKDF_TIME_COST:
+                case ARG_RATE_LIMIT_INTERVAL:
+                case ARG_STOP_DELAY: {
+                        const char *field =
+                                c == ARG_LUKS_PBKDF_TIME_COST ? "luksPbkdfTimeCostUSec" :
+                                 c == ARG_RATE_LIMIT_INTERVAL ? "rateLimitIntervalUSec" :
+                                          c == ARG_STOP_DELAY ? "stopDelayUSec" :
+                                                                NULL;
+                        usec_t t;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        r = parse_sec(optarg, &t);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s field: %s", field, optarg);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, field, t);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case 'G': {
+                        const char *p = optarg;
+
+                        if (isempty(p)) {
+                                r = drop_from_identity("memberOf");
+                                if (r < 0)
+                                        return r;
+
+                                break;
+                        }
+
+                        for (;;) {
+                                _cleanup_(json_variant_unrefp) JsonVariant *mo = NULL;
+                                _cleanup_strv_free_ char **list = NULL;
+                                _cleanup_free_ char *word = NULL;
+
+                                r = extract_first_word(&p, &word, ",", 0);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse group list: %m");
+                                if (r == 0)
+                                        break;
+
+                                if (!valid_user_group_name(word))
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid group name %s.", word);
+
+                                mo = json_variant_ref(json_variant_by_key(arg_identity_extra, "memberOf"));
+
+                                r = json_variant_strv(mo, &list);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse group list: %m");
+
+                                r = strv_extend(&list, word);
+                                if (r < 0)
+                                        return log_oom();
+
+                                strv_sort(list);
+                                strv_uniq(list);
+
+                                mo = json_variant_unref(mo);
+                                r = json_variant_new_array_strv(&mo, list);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to create group list JSON: %m");
+
+                                r = json_variant_set_field(&arg_identity_extra, "memberOf", mo);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to update group list: %m");
+                        }
+
+                        break;
+                }
+
+                case ARG_TASKS_MAX: {
+                        uint64_t u;
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity("tasksMax");
+                                if (r < 0)
+                                        return r;
+                                break;
+                        }
+
+                        r = safe_atou64(optarg, &u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --tasks-max= parameter: %s", optarg);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, "tasksMax", u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set tasksMax field: %m");
+
+                        break;
+                }
+
+                case ARG_MEMORY_MAX:
+                case ARG_MEMORY_HIGH:
+                case ARG_LUKS_PBKDF_MEMORY_COST: {
+                        const char *field =
+                                            c == ARG_MEMORY_MAX ? "memoryMax" :
+                                           c == ARG_MEMORY_HIGH ? "memoryHigh" :
+                                c == ARG_LUKS_PBKDF_MEMORY_COST ? "luksPbkdfMemoryCost" : NULL;
+
+                        uint64_t u;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+                                break;
+                        }
+
+                        r = parse_size(optarg, 1024, &u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse %s parameter: %s", field, optarg);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra_this_machine, field, u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case ARG_CPU_WEIGHT:
+                case ARG_IO_WEIGHT: {
+                        const char *field = c == ARG_CPU_WEIGHT ? "cpuWeight" :
+                                            c == ARG_IO_WEIGHT ? "ioWeight" : NULL;
+                        uint64_t u;
+
+                        assert(field);
+
+                        if (isempty(optarg)) {
+                                r = drop_from_identity(field);
+                                if (r < 0)
+                                        return r;
+                                break;
+                        }
+
+                        r = safe_atou64(optarg, &u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --cpu-weight=/--io-weight= parameter: %s", optarg);
+
+                        if (!CGROUP_WEIGHT_IS_OK(u))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Weight %" PRIu64 " is out of valid weight range.", u);
+
+                        r = json_variant_set_field_unsigned(&arg_identity_extra, field, u);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set %s field: %m", field);
+
+                        break;
+                }
+
+                case ARG_PKCS11_TOKEN_URI: {
+                        const char *p;
+
+                        /* If --pkcs11-token-uri= is specified we always drop everything old */
+                        FOREACH_STRING(p, "pkcs11TokenUri", "pkcs11EncryptedKey") {
+                                r = drop_from_identity(p);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        if (isempty(optarg)) {
+                                arg_pkcs11_token_uri = strv_free(arg_pkcs11_token_uri);
+                                break;
+                        }
+
+                        if (!pkcs11_uri_valid(optarg))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not a valid PKCS#11 URI: %s", optarg);
+
+                        r = strv_extend(&arg_pkcs11_token_uri, optarg);
+                        if (r < 0)
+                                return r;
+
+                        strv_uniq(arg_pkcs11_token_uri);
+                        break;
+                }
+
+                case 'j':
+                        arg_json = true;
+                        arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+                        break;
+
+                case ARG_JSON:
+                        if (streq(optarg, "pretty")) {
+                                arg_json = true;
+                                arg_json_format_flags = JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO;
+                        } else if (streq(optarg, "short")) {
+                                arg_json = true;
+                                arg_json_format_flags = JSON_FORMAT_NEWLINE;
+                        } else if (streq(optarg, "off")) {
+                                arg_json = false;
+                                arg_json_format_flags = 0;
+                        } else if (streq(optarg, "help")) {
+                                puts("pretty\n"
+                                     "short\n"
+                                     "off");
+                                return 0;
+                        } else
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown argument to --json=: %s", optarg);
+
+                        break;
+
+                case 'E':
+                        if (arg_export_format == EXPORT_FORMAT_FULL)
+                                arg_export_format = EXPORT_FORMAT_STRIPPED;
+                        else if (arg_export_format == EXPORT_FORMAT_STRIPPED)
+                                arg_export_format = EXPORT_FORMAT_MINIMAL;
+                        else
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specifying -E more than twice is not supported.");
+
+                        arg_json = true;
+                        if (arg_json_format_flags == 0)
+                                arg_json_format_flags = JSON_FORMAT_PRETTY_AUTO|JSON_FORMAT_COLOR_AUTO;
+                        break;
+
+                case ARG_EXPORT_FORMAT:
+                        if (streq(optarg, "full"))
+                                arg_export_format = EXPORT_FORMAT_FULL;
+                        else if (streq(optarg, "stripped"))
+                                arg_export_format = EXPORT_FORMAT_STRIPPED;
+                        else if (streq(optarg, "minimal"))
+                                arg_export_format = EXPORT_FORMAT_MINIMAL;
+                        else if (streq(optarg, "help")) {
+                                puts("full\n"
+                                     "stripped\n"
+                                     "minimal");
+                                return 0;
+                        }
+
+                        break;
+
+                case ARG_AND_RESIZE:
+                        arg_and_resize = true;
+                        break;
+
+                case ARG_AND_CHANGE_PASSWORD:
+                        arg_and_change_password = true;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+        }
+
+        if (!strv_isempty(arg_pkcs11_token_uri))
+                arg_and_change_password = true;
+
+        if (arg_disk_size != UINT64_MAX || arg_disk_size_relative != UINT64_MAX)
+                arg_and_resize = true;
+
+        return 1;
+}
+
+static int run(int argc, char *argv[]) {
+        static const Verb verbs[] = {
+                { "help",         VERB_ANY, VERB_ANY, 0,            help                },
+                { "list",         VERB_ANY, 1,        VERB_DEFAULT, list_homes          },
+                { "activate",     2,        VERB_ANY, 0,            activate_home       },
+                { "deactivate",   2,        VERB_ANY, 0,            deactivate_home     },
+                { "inspect",      VERB_ANY, VERB_ANY, 0,            inspect_home        },
+                { "authenticate", VERB_ANY, VERB_ANY, 0,            authenticate_home   },
+                { "create",       VERB_ANY, 2,        0,            create_home         },
+                { "remove",       2,        VERB_ANY, 0,            remove_home         },
+                { "update",       VERB_ANY, 2,        0,            update_home         },
+                { "passwd",       VERB_ANY, 2,        0,            passwd_home         },
+                { "resize",       2,        3,        0,            resize_home         },
+                { "lock",         2,        VERB_ANY, 0,            lock_home           },
+                { "unlock",       2,        VERB_ANY, 0,            unlock_home         },
+                { "with",         2,        VERB_ANY, 0,            with_home           },
+                { "lock-all",     VERB_ANY, 1,        0,            lock_all_homes      },
+                {}
+        };
+
+        int r;
+
+        log_show_color(true);
+        log_parse_environment();
+        log_open();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        return dispatch_verb(argc, argv, verbs, NULL);
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/home/homed-bus.c b/src/home/homed-bus.c
new file mode 100644 (file)
index 0000000..0193089
--- /dev/null
@@ -0,0 +1,64 @@
+#include "homed-bus.h"
+#include "strv.h"
+
+int bus_message_read_secret(sd_bus_message *m, UserRecord **ret, sd_bus_error *error) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *full = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        unsigned line = 0, column = 0;
+        const char *json;
+        int r;
+
+        assert(ret);
+
+        r = sd_bus_message_read(m, "s", &json);
+        if (r < 0)
+                return r;
+
+        r = json_parse(json, JSON_PARSE_SENSITIVE, &v, &line, &column);
+        if (r < 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON secret record at %u:%u: %m", line, column);
+
+        r = json_build(&full, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("secret", JSON_BUILD_VARIANT(v))));
+        if (r < 0)
+                return r;
+
+        hr = user_record_new();
+        if (!hr)
+                return -ENOMEM;
+
+        r = user_record_load(hr, full, USER_RECORD_REQUIRE_SECRET);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(hr);
+        return 0;
+}
+
+int bus_message_read_home_record(sd_bus_message *m, UserRecordLoadFlags flags, UserRecord **ret, sd_bus_error *error) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        unsigned line = 0, column = 0;
+        const char *json;
+        int r;
+
+        assert(ret);
+
+        r = sd_bus_message_read(m, "s", &json);
+        if (r < 0)
+                return r;
+
+        r = json_parse(json, JSON_PARSE_SENSITIVE, &v, &line, &column);
+        if (r < 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse JSON identity record at %u:%u: %m", line, column);
+
+        hr = user_record_new();
+        if (!hr)
+                return -ENOMEM;
+
+        r = user_record_load(hr, v, flags);
+        if (r < 0)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "JSON data is not a valid identity record");
+
+        *ret = TAKE_PTR(hr);
+        return 0;
+}
diff --git a/src/home/homed-bus.h b/src/home/homed-bus.h
new file mode 100644 (file)
index 0000000..20f13b4
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "user-record.h"
+#include "json.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);
diff --git a/src/home/homed-home-bus.c b/src/home/homed-home-bus.c
new file mode 100644 (file)
index 0000000..02a87a5
--- /dev/null
@@ -0,0 +1,877 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/capability.h>
+
+#include "bus-common-errors.h"
+#include "bus-polkit.h"
+#include "fd-util.h"
+#include "homed-bus.h"
+#include "homed-home-bus.h"
+#include "homed-home.h"
+#include "strv.h"
+#include "user-record-util.h"
+#include "user-util.h"
+
+static int property_get_unix_record(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+
+        assert(bus);
+        assert(reply);
+        assert(h);
+
+        return sd_bus_message_append(
+                        reply, "(suusss)",
+                        h->user_name,
+                        (uint32_t) h->uid,
+                        h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
+                        h->record ? user_record_real_name(h->record) : NULL,
+                        h->record ? user_record_home_directory(h->record) : NULL,
+                        h->record ? user_record_shell(h->record) : NULL);
+}
+
+static int property_get_state(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+
+        assert(bus);
+        assert(reply);
+        assert(h);
+
+        return sd_bus_message_append(reply, "s", home_state_to_string(home_get_state(h)));
+}
+
+int bus_home_client_is_trusted(Home *h, sd_bus_message *message) {
+        _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL;
+        uid_t euid;
+        int r;
+
+        assert(h);
+
+        if (!message)
+                return -EINVAL;
+
+        r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_creds_get_euid(creds, &euid);
+        if (r < 0)
+                return r;
+
+        return euid == 0 || h->uid == euid;
+}
+
+int bus_home_get_record_json(
+                Home *h,
+                sd_bus_message *message,
+                char **ret,
+                bool *ret_incomplete) {
+
+        _cleanup_(user_record_unrefp) UserRecord *augmented = NULL;
+        UserRecordLoadFlags flags;
+        int r, trusted;
+
+        assert(h);
+        assert(ret);
+
+        trusted = bus_home_client_is_trusted(h, message);
+        if (trusted < 0) {
+                log_warning_errno(trusted, "Failed to determine whether client is trusted, assuming untrusted.");
+                trusted = false;
+        }
+
+        flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
+        if (trusted)
+                flags |= USER_RECORD_ALLOW_PRIVILEGED;
+        else
+                flags |= USER_RECORD_STRIP_PRIVILEGED;
+
+        r = home_augment_status(h, flags, &augmented);
+        if (r < 0)
+                return r;
+
+        r = json_variant_format(augmented->json, 0, ret);
+        if (r < 0)
+                return r;
+
+        if (ret_incomplete)
+                *ret_incomplete = augmented->incomplete;
+
+        return 0;
+}
+
+static int property_get_user_record(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_free_ char *json = NULL;
+        Home *h = userdata;
+        bool incomplete;
+        int r;
+
+        assert(bus);
+        assert(reply);
+        assert(h);
+
+        r = bus_home_get_record_json(h, sd_bus_get_current_message(bus), &json, &incomplete);
+        if (r < 0)
+                return r;
+
+        return sd_bus_message_append(reply, "(sb)", json, incomplete);
+}
+
+int bus_home_method_activate(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = home_activate(h, secret, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        /* The operation is now in process, keep track of this message so that we can later reply to it. */
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_deactivate(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = home_deactivate(h, false, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_unregister(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.remove-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_unregister(h, error);
+        if (r < 0)
+                return r;
+
+        assert(r > 0);
+
+        /* Note that home_unregister() destroyed 'h' here, so no more accesses */
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
+int bus_home_method_realize(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        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);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        h->unregister_on_failure = false;
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_remove(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.remove-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_remove(h, error);
+        if (r < 0)
+                return r;
+        if (r > 0) /* Done already. Note that home_remove() destroyed 'h' here, so no more accesses */
+                return sd_bus_reply_method_return(message, NULL);
+
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_fixate(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = home_fixate(h, secret, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_authenticate(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.authenticate-home",
+                        NULL,
+                        true,
+                        h->uid,
+                        &h->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        r = home_authenticate(h, secret, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_update_record(Home *h, sd_bus_message *message, UserRecord *hr, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+        assert(message);
+        assert(hr);
+
+        r = user_record_is_supported(hr, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        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);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_update(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        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, &hr, error);
+        if (r < 0)
+                return r;
+
+        return bus_home_method_update_record(h, message, hr, error);
+}
+
+int bus_home_method_resize(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        uint64_t sz;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = sd_bus_message_read(message, "t", &sz);
+        if (r < 0)
+                return r;
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        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, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_change_password(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *new_secret = NULL, *old_secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &new_secret, error);
+        if (r < 0)
+                return r;
+
+        r = bus_message_read_secret(message, &old_secret, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.passwd-home",
+                        NULL,
+                        true,
+                        h->uid,
+                        &h->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        r = home_passwd(h, new_secret, old_secret, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_lock(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = home_lock(h, error);
+        if (r < 0)
+                return r;
+        if (r > 0) /* Done */
+                return sd_bus_reply_method_return(message, NULL);
+
+        /* The operation is now in process, keep track of this message so that we can later reply to it. */
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_unlock(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = home_unlock(h, secret, error);
+        if (r < 0)
+                return r;
+
+        assert(r == 0);
+        assert(!h->current_operation);
+
+        /* The operation is now in process, keep track of this message so that we can later reply to it. */
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_acquire(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        _cleanup_(operation_unrefp) Operation *o = NULL;
+        _cleanup_close_ int fd = -1;
+        int r, please_suspend;
+        Home *h = userdata;
+
+        assert(message);
+        assert(h);
+
+        r = bus_message_read_secret(message, &secret, error);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_read(message, "b", &please_suspend);
+        if (r < 0)
+                return r;
+
+        /* This operation might not be something we can executed immediately, hence queue it */
+        fd = home_create_fifo(h, please_suspend);
+        if (fd < 0)
+                return sd_bus_reply_method_errnof(message, fd, "Failed to allocate fifo for %s: %m", h->user_name);
+
+        o = operation_new(OPERATION_ACQUIRE, message);
+        if (!o)
+                return -ENOMEM;
+
+        o->secret = TAKE_PTR(secret);
+        o->send_fd = TAKE_FD(fd);
+
+        r = home_schedule_operation(h, o, error);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+int bus_home_method_ref(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_close_ int fd = -1;
+        Home *h = userdata;
+        HomeState state;
+        int please_suspend, r;
+
+        assert(message);
+        assert(h);
+
+        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:
+                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);
+        if (fd < 0)
+                return sd_bus_reply_method_errnof(message, fd, "Failed to allocate fifo for %s: %m", h->user_name);
+
+        return sd_bus_reply_method_return(message, "h", fd);
+}
+
+int bus_home_method_release(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(operation_unrefp) Operation *o = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(message);
+        assert(h);
+
+        o = operation_new(OPERATION_RELEASE, message);
+        if (!o)
+                return -ENOMEM;
+
+        r = home_schedule_operation(h, o, error);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+/* We map a uid_t as uint32_t bus property, let's ensure this is safe. */
+assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+
+const sd_bus_vtable home_vtable[] = {
+        SD_BUS_VTABLE_START(0),
+        SD_BUS_PROPERTY("UserName", "s", NULL, offsetof(Home, user_name), SD_BUS_VTABLE_PROPERTY_CONST),
+        SD_BUS_PROPERTY("UID", "u", NULL, offsetof(Home, uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("UnixRecord", "(suusss)", property_get_unix_record, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0),
+        SD_BUS_PROPERTY("UserRecord", "(sb)", property_get_user_record, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Activate", "s", NULL, bus_home_method_activate, 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("Realize", "s", NULL, bus_home_method_realize, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Remove", NULL, NULL, bus_home_method_remove, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("Fixate", "s", NULL, bus_home_method_fixate, SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Authenticate", "s", NULL, bus_home_method_authenticate, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Update", "s", NULL, bus_home_method_update, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Resize", "ts", NULL, bus_home_method_resize, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("ChangePassword", "ss", NULL, bus_home_method_change_password, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Lock", NULL, NULL, bus_home_method_lock, 0),
+        SD_BUS_METHOD("Unlock", "s", NULL, bus_home_method_unlock, SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Acquire", "sb", "h", bus_home_method_acquire, SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("Ref", "b", "h", bus_home_method_ref, 0),
+        SD_BUS_METHOD("Release", NULL, NULL, bus_home_method_release, 0),
+        SD_BUS_VTABLE_END
+};
+
+int bus_home_path(Home *h, char **ret) {
+        assert(ret);
+
+        return sd_bus_path_encode("/org/freedesktop/home1/home", h->user_name, ret);
+}
+
+int bus_home_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;
+        uid_t uid;
+        Home *h;
+        int r;
+
+        r = sd_bus_path_decode(path, "/org/freedesktop/home1/home", &e);
+        if (r <= 0)
+                return 0;
+
+        if (parse_uid(e, &uid) >= 0)
+                h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
+        else
+                h = hashmap_get(m->homes_by_name, e);
+        if (!h)
+                return 0;
+
+        *found = h;
+        return 1;
+}
+
+int bus_home_node_enumerator(
+                sd_bus *bus,
+                const char *path,
+                void *userdata,
+                char ***nodes,
+                sd_bus_error *error) {
+
+        _cleanup_strv_free_ char **l = NULL;
+        Manager *m = userdata;
+        size_t k = 0;
+        Iterator i;
+        Home *h;
+        int r;
+
+        assert(nodes);
+
+        l = new0(char*, hashmap_size(m->homes_by_uid) + 1);
+        if (!l)
+                return -ENOMEM;
+
+        HASHMAP_FOREACH(h, m->homes_by_uid, i) {
+                r = bus_home_path(h, l + k);
+                if (r < 0)
+                        return r;
+        }
+
+        *nodes = TAKE_PTR(l);
+        return 1;
+}
+
+static int on_deferred_change(sd_event_source *s, void *userdata) {
+        _cleanup_free_ char *path = NULL;
+        Home *h = userdata;
+        int r;
+
+        assert(h);
+
+        h->deferred_change_event_source = sd_event_source_unref(h->deferred_change_event_source);
+
+        r = bus_home_path(h, &path);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to generate home bus path, ignoring: %m");
+                return 0;
+        }
+
+        if (h->announced)
+                r = sd_bus_emit_properties_changed_strv(h->manager->bus, path, "org.freedesktop.home1.Home", NULL);
+        else
+                r = sd_bus_emit_object_added(h->manager->bus, path);
+        if (r < 0)
+                log_warning_errno(r, "Failed to send home change event, ignoring: %m");
+        else
+                h->announced = true;
+
+        return 0;
+}
+
+int bus_home_emit_change(Home *h) {
+        int r;
+
+        assert(h);
+
+        if (h->deferred_change_event_source)
+                return 1;
+
+        if (!h->manager->event)
+                return 0;
+
+        if (IN_SET(sd_event_get_state(h->manager->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
+                return 0;
+
+        r = sd_event_add_defer(h->manager->event, &h->deferred_change_event_source, on_deferred_change, h);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate deferred change event source: %m");
+
+        r = sd_event_source_set_priority(h->deferred_change_event_source, SD_EVENT_PRIORITY_IDLE+5);
+        if (r < 0)
+                log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
+
+        (void) sd_event_source_set_description(h->deferred_change_event_source, "deferred-change-event");
+        return 1;
+}
+
+int bus_home_emit_remove(Home *h) {
+        _cleanup_free_ char *path = NULL;
+        int r;
+
+        assert(h);
+
+        if (!h->announced)
+                return 0;
+
+        r = bus_home_path(h, &path);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_emit_object_removed(h->manager->bus, path);
+        if (r < 0)
+                return r;
+
+        h->announced = false;
+        return 1;
+}
diff --git a/src/home/homed-home-bus.h b/src/home/homed-home-bus.h
new file mode 100644 (file)
index 0000000..20516b1
--- /dev/null
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "homed-home.h"
+
+int bus_home_client_is_trusted(Home *h, sd_bus_message *message);
+int bus_home_get_record_json(Home *h, sd_bus_message *message, char **ret, bool *ret_incomplete);
+
+int bus_home_method_activate(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_deactivate(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_unregister(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_realize(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *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);
+int bus_home_method_unlock(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_home_method_acquire(sd_bus_message *message, void *userdata, sd_bus_error *error);
+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);
+
+extern const sd_bus_vtable home_vtable[];
+
+int bus_home_path(Home *h, char **ret);
+
+int bus_home_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error);
+int bus_home_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error);
+
+int bus_home_emit_change(Home *h);
+int bus_home_emit_remove(Home *h);
diff --git a/src/home/homed-home.c b/src/home/homed-home.c
new file mode 100644 (file)
index 0000000..f50de26
--- /dev/null
@@ -0,0 +1,2712 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#if HAVE_LINUX_MEMFD_H
+#include <linux/memfd.h>
+#endif
+
+#include <sys/mman.h>
+#include <sys/quota.h>
+#include <sys/vfs.h>
+
+#include "blockdev-util.h"
+#include "btrfs-util.h"
+#include "bus-common-errors.h"
+#include "env-util.h"
+#include "errno-list.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "home-util.h"
+#include "homed-home-bus.h"
+#include "homed-home.h"
+#include "missing_syscall.h"
+#include "mkdir.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "pwquality-util.h"
+#include "quota-util.h"
+#include "resize-fs.h"
+#include "set.h"
+#include "signal-util.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "strv.h"
+#include "user-record-sign.h"
+#include "user-record-util.h"
+#include "user-record.h"
+#include "user-util.h"
+
+#define HOME_USERS_MAX 500
+#define PENDING_OPERATIONS_MAX 100
+
+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);
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(operation_hash_ops, void, trivial_hash_func, trivial_compare_func, Operation, operation_unref);
+
+static int suitable_home_record(UserRecord *hr) {
+        int r;
+
+        assert(hr);
+
+        if (!hr->user_name)
+                return -EUNATCH;
+
+        /* We are a bit more restrictive with what we accept as homed-managed user than what we accept in
+         * home records in general. Let's enforce the stricter rule here. */
+        if (!suitable_user_name(hr->user_name))
+                return -EINVAL;
+        if (!uid_is_valid(hr->uid))
+                return -EINVAL;
+
+        /* Insist we are outside of the dynamic and system range */
+        if (uid_is_system(hr->uid) || gid_is_system(user_record_gid(hr)) ||
+            uid_is_dynamic(hr->uid) || gid_is_dynamic(user_record_gid(hr)))
+                return -EADDRNOTAVAIL;
+
+        /* Insist that GID and UID match */
+        if (user_record_gid(hr) != (gid_t) hr->uid)
+                return -EBADSLT;
+
+        /* Similar for the realm */
+        if (hr->realm) {
+                r = suitable_realm(hr->realm);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EINVAL;
+        }
+
+        return 0;
+}
+
+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;
+        int r;
+
+        assert(m);
+        assert(hr);
+
+        r = suitable_home_record(hr);
+        if (r < 0)
+                return r;
+
+        if (hashmap_contains(m->homes_by_name, hr->user_name))
+                return -EBUSY;
+
+        if (hashmap_contains(m->homes_by_uid, UID_TO_PTR(hr->uid)))
+                return -EBUSY;
+
+        if (sysfs && hashmap_contains(m->homes_by_sysfs, sysfs))
+                return -EBUSY;
+
+        if (hashmap_size(m->homes_by_name) >= HOME_USERS_MAX)
+                return -EUSERS;
+
+        nm = strdup(hr->user_name);
+        if (!nm)
+                return -ENOMEM;
+
+        if (sysfs) {
+                ns = strdup(sysfs);
+                if (!ns)
+                        return -ENOMEM;
+        }
+
+        home = new(Home, 1);
+        if (!home)
+                return -ENOMEM;
+
+        *home = (Home) {
+                .manager = m,
+                .user_name = TAKE_PTR(nm),
+                .uid = hr->uid,
+                .state = _HOME_STATE_INVALID,
+                .worker_stdout_fd = -1,
+                .sysfs = TAKE_PTR(ns),
+                .signed_locally = -1,
+        };
+
+        r = hashmap_put(m->homes_by_name, home->user_name, home);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(m->homes_by_uid, UID_TO_PTR(home->uid), home);
+        if (r < 0)
+                return r;
+
+        if (home->sysfs) {
+                r = hashmap_put(m->homes_by_sysfs, home->sysfs, home);
+                if (r < 0)
+                        return r;
+        }
+
+        r = user_record_clone(hr, USER_RECORD_LOAD_MASK_SECRET, &home->record);
+        if (r < 0)
+                return r;
+
+        (void) bus_manager_emit_auto_login_changed(m);
+        (void) bus_home_emit_change(home);
+
+        if (ret)
+                *ret = TAKE_PTR(home);
+        else
+                TAKE_PTR(home);
+
+        return 0;
+}
+
+Home *home_free(Home *h) {
+
+        if (!h)
+                return NULL;
+
+        if (h->manager) {
+                (void) bus_home_emit_remove(h);
+                (void) bus_manager_emit_auto_login_changed(h->manager);
+
+                if (h->user_name)
+                        (void) hashmap_remove_value(h->manager->homes_by_name, h->user_name, h);
+
+                if (uid_is_valid(h->uid))
+                        (void) hashmap_remove_value(h->manager->homes_by_uid, UID_TO_PTR(h->uid), h);
+
+                if (h->sysfs)
+                        (void) hashmap_remove_value(h->manager->homes_by_sysfs, h->sysfs, h);
+
+                if (h->worker_pid > 0)
+                        (void) hashmap_remove_value(h->manager->homes_by_worker_pid, PID_TO_PTR(h->worker_pid), h);
+
+                if (h->manager->gc_focus == h)
+                        h->manager->gc_focus = NULL;
+        }
+
+        user_record_unref(h->record);
+        user_record_unref(h->secret);
+
+        h->worker_event_source = sd_event_source_unref(h->worker_event_source);
+        safe_close(h->worker_stdout_fd);
+        free(h->user_name);
+        free(h->sysfs);
+
+        h->ref_event_source_please_suspend = sd_event_source_unref(h->ref_event_source_please_suspend);
+        h->ref_event_source_dont_suspend = sd_event_source_unref(h->ref_event_source_dont_suspend);
+
+        h->pending_operations = ordered_set_free(h->pending_operations);
+        h->pending_event_source = sd_event_source_unref(h->pending_event_source);
+        h->deferred_change_event_source = sd_event_source_unref(h->deferred_change_event_source);
+
+        h->current_operation = operation_unref(h->current_operation);
+
+        return mfree(h);
+}
+
+int home_set_record(Home *h, UserRecord *hr) {
+        _cleanup_(user_record_unrefp) UserRecord *new_hr = NULL;
+        Home *other;
+        int r;
+
+        assert(h);
+        assert(h->user_name);
+        assert(h->record);
+        assert(hr);
+
+        if (user_record_equal(h->record, hr))
+                return 0;
+
+        r = suitable_home_record(hr);
+        if (r < 0)
+                return r;
+
+        if (!user_record_compatible(h->record, hr))
+                return -EREMCHG;
+
+        if (!FLAGS_SET(hr->mask, USER_RECORD_REGULAR) ||
+            FLAGS_SET(hr->mask, USER_RECORD_SECRET))
+                return -EINVAL;
+
+        if (FLAGS_SET(h->record->mask, USER_RECORD_STATUS)) {
+                _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+
+                /* Hmm, the existing record has status fields? If so, copy them over */
+
+                v = json_variant_ref(hr->json);
+                r = json_variant_set_field(&v, "status", json_variant_by_key(h->record->json, "status"));
+                if (r < 0)
+                        return r;
+
+                new_hr = user_record_new();
+                if (!new_hr)
+                        return -ENOMEM;
+
+                r = user_record_load(new_hr, v, USER_RECORD_LOAD_REFUSE_SECRET);
+                if (r < 0)
+                        return r;
+
+                hr = new_hr;
+        }
+
+        other = hashmap_get(h->manager->homes_by_uid, UID_TO_PTR(hr->uid));
+        if (other && other != h)
+                return -EBUSY;
+
+        if (h->uid != hr->uid) {
+                r = hashmap_remove_and_replace(h->manager->homes_by_uid, UID_TO_PTR(h->uid), UID_TO_PTR(hr->uid), h);
+                if (r < 0)
+                        return r;
+        }
+
+        user_record_unref(h->record);
+        h->record = user_record_ref(hr);
+        h->uid = h->record->uid;
+
+        /* The updated record might have a different autologin setting, trigger a PropertiesChanged event for it */
+        (void) bus_manager_emit_auto_login_changed(h->manager);
+        (void) bus_home_emit_change(h);
+
+        return 0;
+}
+
+int home_save_record(Home *h) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_free_ char *text = NULL;
+        const char *fn;
+        int r;
+
+        assert(h);
+
+        v = json_variant_ref(h->record->json);
+        r = json_variant_normalize(&v);
+        if (r < 0)
+                log_warning_errno(r, "User record could not be normalized.");
+
+        r = json_variant_format(v, JSON_FORMAT_PRETTY|JSON_FORMAT_NEWLINE, &text);
+        if (r < 0)
+                return r;
+
+        (void) mkdir("/var/lib/systemd/", 0755);
+        (void) mkdir("/var/lib/systemd/home/", 0700);
+
+        fn = strjoina("/var/lib/systemd/home/", h->user_name, ".identity");
+
+        r = write_string_file(fn, text, WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MODE_0600);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int home_unlink_record(Home *h) {
+        const char *fn;
+
+        assert(h);
+
+        fn = strjoina("/var/lib/systemd/home/", h->user_name, ".identity");
+        if (unlink(fn) < 0 && errno != ENOENT)
+                return -errno;
+
+        fn = strjoina("/run/systemd/home/", h->user_name, ".ref");
+        if (unlink(fn) < 0 && errno != ENOENT)
+                return -errno;
+
+        return 0;
+}
+
+static void home_set_state(Home *h, HomeState state) {
+        HomeState old_state, new_state;
+
+        assert(h);
+
+        old_state = home_get_state(h);
+        h->state = state;
+        new_state = home_get_state(h); /* Query the new state, since the 'state' variable might be set to -1,
+                                        * in which case we synthesize an high-level state on demand */
+
+        log_info("%s: changing state %s → %s", h->user_name,
+                 home_state_to_string(old_state),
+                 home_state_to_string(new_state));
+
+        if (HOME_STATE_IS_EXECUTING_OPERATION(old_state) && !HOME_STATE_IS_EXECUTING_OPERATION(new_state)) {
+                /* If we just finished executing some operation, process the queue of pending operations. And
+                 * enqueue it for GC too. */
+
+                home_schedule_operation(h, NULL, NULL);
+                manager_enqueue_gc(h->manager, h);
+        }
+}
+
+static int home_parse_worker_stdout(int _fd, UserRecord **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_close_ int fd = _fd; /* take possession, even on failure */
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        unsigned line, column;
+        struct stat st;
+        int r;
+
+        if (fstat(fd, &st) < 0)
+                return log_error_errno(errno, "Failed to stat stdout fd: %m");
+
+        assert(S_ISREG(st.st_mode));
+
+        if (st.st_size == 0) { /* empty record */
+                *ret = NULL;
+                return 0;
+        }
+
+        if (lseek(fd, SEEK_SET, 0) == (off_t) -1)
+                return log_error_errno(errno, "Failed to seek to beginning of memfd: %m");
+
+        f = fdopen(fd, "r");
+        if (!f)
+                return log_error_errno(errno, "Failed to reopen memfd: %m");
+
+        TAKE_FD(fd);
+
+        if (DEBUG_LOGGING) {
+                _cleanup_free_ char *text = NULL;
+
+                r = read_full_stream(f, &text, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read from client: %m");
+
+                log_debug("Got from worker: %s", text);
+                rewind(f);
+        }
+
+        r = json_parse_file(f, "stdout", JSON_PARSE_SENSITIVE, &v, &line, &column);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse identity at %u:%u: %m", line, column);
+
+        hr = user_record_new();
+        if (!hr)
+                return log_oom();
+
+        r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load home record identity: %m");
+
+        *ret = TAKE_PTR(hr);
+        return 1;
+}
+
+static int home_verify_user_record(Home *h, UserRecord *hr, bool *ret_signed_locally, sd_bus_error *ret_error) {
+        int is_signed;
+
+        assert(h);
+        assert(hr);
+        assert(ret_signed_locally);
+
+        is_signed = manager_verify_user_record(h->manager, hr);
+        switch (is_signed) {
+
+        case USER_RECORD_SIGNED_EXCLUSIVE:
+                log_info("Home %s is signed exclusively by our key, accepting.", hr->user_name);
+                *ret_signed_locally = true;
+                return 0;
+
+        case USER_RECORD_SIGNED:
+                log_info("Home %s is signed by our key (and others), accepting.", hr->user_name);
+                *ret_signed_locally = false;
+                return 0;
+
+        case USER_RECORD_FOREIGN:
+                log_info("Home %s is signed by foreign key we like, accepting.", hr->user_name);
+                *ret_signed_locally = false;
+                return 0;
+
+        case USER_RECORD_UNSIGNED:
+                sd_bus_error_setf(ret_error, BUS_ERROR_BAD_SIGNATURE, "User record %s is not signed at all, refusing.", hr->user_name);
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Home %s contains user record that is not signed at all, refusing.", hr->user_name);
+
+        case -ENOKEY:
+                sd_bus_error_setf(ret_error, BUS_ERROR_BAD_SIGNATURE, "User record %s is not signed by any known key, refusing.", hr->user_name);
+                return log_error_errno(is_signed, "Home %s contians user record that is not signed by any known key, refusing.", hr->user_name);
+
+        default:
+                assert(is_signed < 0);
+                return log_error_errno(is_signed, "Failed to verify signature on user record for %s, refusing fixation: %m", hr->user_name);
+        }
+}
+
+static int convert_worker_errno(Home *h, int e, sd_bus_error *error) {
+        /* Converts the error numbers the worker process returned into somewhat sensible dbus errors */
+
+        switch (e) {
+
+        case -EMSGSIZE:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File systems of this type cannot shrinked");
+        case -ETXTBSY:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File systems of this type can only be shrinked offline");
+        case -ERANGE:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "File system size too small");
+        case -ENOLINK:
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "System does not support selected storage backend");
+        case -EPROTONOSUPPORT:
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "System does not support selected file system");
+        case -ENOTTY:
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Operation not supported on storage backend");
+        case -ESOCKTNOSUPPORT:
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Operation not supported on file system");
+        case -ENOKEY:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD, "Password for home %s is incorrect or not sufficient for authentication.", h->user_name);
+        case -EBADSLT:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN, "Password for home %s is incorrect or not sufficient, and configured security token not found either.", h->user_name);
+        case -ENOANO:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_NEEDED, "PIN for security token required.");
+        case -ERFKILL:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, "Security token requires protected authentication path.");
+        case -EOWNERDEAD:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_PIN_LOCKED, "PIN of security token locked.");
+        case -ENOLCK:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN, "Bad PIN of security token.");
+        case -ETOOMANYREFS:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, "Bad PIN of security token, and only a few tries left.");
+        case -EUCLEAN:
+                return sd_bus_error_setf(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT, "Bad PIN of security token, and only one try left.");
+        case -EBUSY:
+                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);
+        case -ENOEXEC:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s is currently not active", h->user_name);
+        case -ENOSPC:
+                return sd_bus_error_setf(error, BUS_ERROR_NO_DISK_SPACE, "Not enough disk space for home %s", h->user_name);
+        }
+
+        return 0;
+}
+
+static void home_count_bad_authentication(Home *h, bool save) {
+        int r;
+
+        assert(h);
+
+        r = user_record_bad_authentication(h->record);
+        if (r < 0) {
+                log_warning_errno(r, "Failed to increase bad authentication counter, ignoring: %m");
+                return;
+        }
+
+        if (save) {
+                r = home_save_record(h);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+        }
+}
+
+static void home_fixate_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(user_record_unrefp) UserRecord *secret = NULL;
+        bool signed_locally;
+        int r;
+
+        assert(h);
+        assert(IN_SET(h->state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
+
+        secret = TAKE_PTR(h->secret); /* Take possession */
+
+        if (ret < 0) {
+                if (ret == -ENOKEY)
+                        (void) home_count_bad_authentication(h, false);
+
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Fixation failed: %m");
+                goto fail;
+        }
+        if (!hr) {
+                r = log_error_errno(SYNTHETIC_ERRNO(EIO), "Did not receive user record from worker process, fixation failed.");
+                goto fail;
+        }
+
+        r = home_verify_user_record(h, hr, &signed_locally, &error);
+        if (r < 0)
+                goto fail;
+
+        r = home_set_record(h, hr);
+        if (r < 0) {
+                log_error_errno(r, "Failed to update home record: %m");
+                goto fail;
+        }
+
+        h->signed_locally = signed_locally;
+
+        /* When we finished fixating (and don't follow-up with activation), let's count this as good authentication */
+        if (h->state == HOME_FIXATING) {
+                r = user_record_good_authentication(h->record);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+        }
+
+        r = home_save_record(h);
+        if (r < 0)
+                log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+
+        if (IN_SET(h->state, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE)) {
+
+                r = home_start_work(h, "activate", h->record, secret);
+                if (r < 0) {
+                        h->current_operation = operation_result_unref(h->current_operation, r, NULL);
+                        home_set_state(h, _HOME_STATE_INVALID);
+                } else
+                        home_set_state(h, h->state == HOME_FIXATING_FOR_ACTIVATION ? HOME_ACTIVATING : HOME_ACTIVATING_FOR_ACQUIRE);
+
+                return;
+        }
+
+        log_debug("Fixation of %s completed.", h->user_name);
+
+        h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
+
+        /* Reset the state to "invalid", which makes home_get_state() test if the image exists and returns
+         * HOME_ABSENT vs. HOME_INACTIVE as necessary. */
+        home_set_state(h, _HOME_STATE_INVALID);
+        return;
+
+fail:
+        /* If fixation fails, we stay in unfixated state! */
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, HOME_UNFIXATED);
+}
+
+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;
+
+        assert(h);
+        assert(IN_SET(h->state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
+
+        if (ret < 0) {
+                if (ret == -ENOKEY)
+                        home_count_bad_authentication(h, true);
+
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Activation failed: %m");
+                goto finish;
+        }
+
+        if (hr) {
+                bool signed_locally;
+
+                r = home_verify_user_record(h, hr, &signed_locally, &error);
+                if (r < 0)
+                        goto finish;
+
+                r = home_set_record(h, hr);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to update home record, ignoring: %m");
+                        goto finish;
+                }
+
+                h->signed_locally = signed_locally;
+
+                r = user_record_good_authentication(h->record);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+
+                r = home_save_record(h);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+        }
+
+        log_debug("Activation of %s completed.", h->user_name);
+        r = 0;
+
+finish:
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_deactivate_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(h->state == HOME_DEACTIVATING);
+        assert(!hr); /* We don't expect a record on this operation */
+
+        if (ret < 0) {
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Deactivation of %s failed: %m", h->user_name);
+                goto finish;
+        }
+
+        log_debug("Deactivation of %s completed.", h->user_name);
+        r = 0;
+
+finish:
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_remove_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        Manager *m;
+        int r;
+
+        assert(h);
+        assert(h->state == HOME_REMOVING);
+        assert(!hr); /* We don't expect a record on this operation */
+
+        m = h->manager;
+
+        if (ret < 0 && ret != -EALREADY) {
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Removing %s failed: %m", h->user_name);
+                goto fail;
+        }
+
+        /* For a couple of storage types we can't delete the actual data storage when called (such as LUKS on
+         * partitions like USB sticks, or so). Sometimes these storage locations are among those we normally
+         * automatically discover in /home or in udev. When such a home is deleted let's hence issue a rescan
+         * after completion, so that "unfixated" entries are rediscovered.  */
+        if (!IN_SET(user_record_test_image_path(h->record), USER_TEST_UNDEFINED, USER_TEST_ABSENT))
+                manager_enqueue_rescan(m);
+
+        /* The image is now removed from disk. Now also remove our stored record */
+        r = home_unlink_record(h);
+        if (r < 0) {
+                log_error_errno(r, "Removing record file failed: %m");
+                goto fail;
+        }
+
+        log_debug("Removal of %s completed.", h->user_name);
+        h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
+
+        /* Unload this record from memory too now. */
+        h = home_free(h);
+        return;
+
+fail:
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_create_finish(Home *h, int ret, UserRecord *hr) {
+        int r;
+
+        assert(h);
+        assert(h->state == HOME_CREATING);
+
+        if (ret < 0) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+
+                (void) convert_worker_errno(h, ret, &error);
+                log_error_errno(ret, "Operation on %s failed: %m", h->user_name);
+                h->current_operation = operation_result_unref(h->current_operation, ret, &error);
+
+                if (h->unregister_on_failure) {
+                        (void) home_unlink_record(h);
+                        h = home_free(h);
+                        return;
+                }
+
+                home_set_state(h, _HOME_STATE_INVALID);
+                return;
+        }
+
+        if (hr) {
+                r = home_set_record(h, hr);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to update home record, ignoring: %m");
+        }
+
+        r = home_save_record(h);
+        if (r < 0)
+                log_warning_errno(r, "Failed to save record to disk, ignoring: %m");
+
+        log_debug("Creation of %s completed.", h->user_name);
+
+        h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_change_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+
+        if (ret < 0) {
+                if (ret == -ENOKEY)
+                        (void) home_count_bad_authentication(h, true);
+
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Change operation failed: %m");
+                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 {
+                        r = user_record_good_authentication(h->record);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+
+                        r = home_save_record(h);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+                }
+        }
+
+        log_debug("Change operation of %s completed.", h->user_name);
+        r = 0;
+
+finish:
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_locking_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(h->state == HOME_LOCKING);
+
+        if (ret < 0) {
+                (void) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Locking operation failed: %m");
+                goto finish;
+        }
+
+        log_debug("Locking operation of %s completed.", h->user_name);
+        h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
+        home_set_state(h, HOME_LOCKED);
+        return;
+
+finish:
+        /* If a specific home doesn't know the concept of locking, then that's totally OK, don't propagate
+         * the error if we are executing a LockAllHomes() operation. */
+
+        if (h->current_operation->type == OPERATION_LOCK_ALL && r == -ENOTTY)
+                h->current_operation = operation_result_unref(h->current_operation, 0, NULL);
+        else
+                h->current_operation = operation_result_unref(h->current_operation, r, &error);
+
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static void home_unlocking_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        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) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Unlocking operation failed: %m");
+
+                /* Revert to locked state */
+                home_set_state(h, HOME_LOCKED);
+                h->current_operation = operation_result_unref(h->current_operation, r, &error);
+                return;
+        }
+
+        r = user_record_good_authentication(h->record);
+        if (r < 0)
+                log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+        else {
+                r = home_save_record(h);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+        }
+
+        log_debug("Unlocking operation of %s completed.", h->user_name);
+
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+        return;
+}
+
+static void home_authenticating_finish(Home *h, int ret, UserRecord *hr) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        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) convert_worker_errno(h, ret, &error);
+                r = log_error_errno(ret, "Authentication failed: %m");
+                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 {
+                        r = user_record_good_authentication(h->record);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to increase good authentication counter, ignoring: %m");
+
+                        r = home_save_record(h);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to write home record to disk, ignoring: %m");
+                }
+        }
+
+        log_debug("Authentication of %s completed.", h->user_name);
+        r = 0;
+
+finish:
+        h->current_operation = operation_result_unref(h->current_operation, r, &error);
+        home_set_state(h, _HOME_STATE_INVALID);
+}
+
+static int home_on_worker_process(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        Home *h = userdata;
+        int ret;
+
+        assert(s);
+        assert(si);
+        assert(h);
+
+        assert(h->worker_pid == si->si_pid);
+        assert(h->worker_event_source);
+        assert(h->worker_stdout_fd >= 0);
+
+        (void) hashmap_remove_value(h->manager->homes_by_worker_pid, PID_TO_PTR(h->worker_pid), h);
+
+        h->worker_pid = 0;
+        h->worker_event_source = sd_event_source_unref(h->worker_event_source);
+
+        if (si->si_code != CLD_EXITED) {
+                assert(IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED));
+                ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Worker process died abnormally with signal %s.", signal_to_string(si->si_status));
+        } else if (si->si_status != EXIT_SUCCESS) {
+                /* If we received an error code via sd_notify(), use it */
+                if (h->worker_error_code != 0)
+                        ret = log_debug_errno(h->worker_error_code, "Worker reported error code %s.", errno_to_name(h->worker_error_code));
+                else
+                        ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Worker exited with exit code %i.", si->si_status);
+        } else
+                ret = home_parse_worker_stdout(TAKE_FD(h->worker_stdout_fd), &hr);
+
+        h->worker_stdout_fd = safe_close(h->worker_stdout_fd);
+
+        switch (h->state) {
+
+        case HOME_FIXATING:
+        case HOME_FIXATING_FOR_ACTIVATION:
+        case HOME_FIXATING_FOR_ACQUIRE:
+                home_fixate_finish(h, ret, hr);
+                break;
+
+        case HOME_ACTIVATING:
+        case HOME_ACTIVATING_FOR_ACQUIRE:
+                home_activate_finish(h, ret, hr);
+                break;
+
+        case HOME_DEACTIVATING:
+                home_deactivate_finish(h, ret, hr);
+                break;
+
+        case HOME_LOCKING:
+                home_locking_finish(h, ret, hr);
+                break;
+
+        case HOME_UNLOCKING:
+        case HOME_UNLOCKING_FOR_ACQUIRE:
+                home_unlocking_finish(h, ret, hr);
+                break;
+
+        case HOME_CREATING:
+                home_create_finish(h, ret, hr);
+                break;
+
+        case HOME_REMOVING:
+                home_remove_finish(h, ret, hr);
+                break;
+
+        case HOME_UPDATING:
+        case HOME_UPDATING_WHILE_ACTIVE:
+        case HOME_RESIZING:
+        case HOME_RESIZING_WHILE_ACTIVE:
+        case HOME_PASSWD:
+        case HOME_PASSWD_WHILE_ACTIVE:
+                home_change_finish(h, ret, hr);
+                break;
+
+        case HOME_AUTHENTICATING:
+        case HOME_AUTHENTICATING_WHILE_ACTIVE:
+        case HOME_AUTHENTICATING_FOR_ACQUIRE:
+                home_authenticating_finish(h, ret, hr);
+                break;
+
+        default:
+                assert_not_reached("Unexpected state after worker exited");
+        }
+
+        return 0;
+}
+
+static int home_start_work(Home *h, const char *verb, UserRecord *hr, UserRecord *secret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(erase_and_freep) char *formatted = NULL;
+        _cleanup_close_ int stdin_fd = -1, stdout_fd = -1;
+        pid_t pid = 0;
+        int r;
+
+        assert(h);
+        assert(verb);
+        assert(hr);
+
+        if (h->worker_pid != 0)
+                return -EBUSY;
+
+        assert(h->worker_stdout_fd < 0);
+        assert(!h->worker_event_source);
+
+        v = json_variant_ref(hr->json);
+
+        if (secret) {
+                JsonVariant *sub = NULL;
+
+                sub = json_variant_by_key(secret->json, "secret");
+                if (!sub)
+                        return -ENOKEY;
+
+                r = json_variant_set_field(&v, "secret", sub);
+                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);
+        if (stdin_fd < 0)
+                return stdin_fd;
+
+        log_debug("Sending to worker: %s", formatted);
+
+        stdout_fd = memfd_create("homework-stdout", MFD_CLOEXEC);
+        if (stdout_fd < 0)
+                return -errno;
+
+        r = safe_fork_full("(sd-homework)",
+                           (int[]) { stdin_fd, stdout_fd }, 2,
+                           FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG, &pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Child */
+
+                if (setenv("NOTIFY_SOCKET", "/run/systemd/home/notify", 1) < 0) {
+                        log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                r = rearrange_stdio(stdin_fd, stdout_fd, STDERR_FILENO);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to rearrange stdin/stdout/stderr: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                stdin_fd = stdout_fd = -1; /* have been invalidated by rearrange_stdio() */
+
+                execl(SYSTEMD_HOMEWORK_PATH, SYSTEMD_HOMEWORK_PATH, verb, NULL);
+                log_error_errno(errno, "Failed to invoke " SYSTEMD_HOMEWORK_PATH ": %m");
+                _exit(EXIT_FAILURE);
+        }
+
+        r = sd_event_add_child(h->manager->event, &h->worker_event_source, pid, WEXITED, home_on_worker_process, h);
+        if (r < 0)
+                return r;
+
+        (void) sd_event_source_set_description(h->worker_event_source, "worker");
+
+        r = hashmap_put(h->manager->homes_by_worker_pid, PID_TO_PTR(pid), h);
+        if (r < 0) {
+                h->worker_event_source = sd_event_source_unref(h->worker_event_source);
+                return r;
+        }
+
+        h->worker_stdout_fd = TAKE_FD(stdout_fd);
+        h->worker_pid = pid;
+        h->worker_error_code = 0;
+
+        return 0;
+}
+
+static int home_ratelimit(Home *h, sd_bus_error *error) {
+        int r, ret;
+
+        assert(h);
+
+        ret = user_record_ratelimit(h->record);
+        if (ret < 0)
+                return ret;
+
+        if (h->state != HOME_UNFIXATED) {
+                r = home_save_record(h);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to save updated record, ignoring: %m");
+        }
+
+        if (ret == 0) {
+                char buf[FORMAT_TIMESPAN_MAX];
+                usec_t t, n;
+
+                n = now(CLOCK_REALTIME);
+                t = user_record_ratelimit_next_try(h->record);
+
+                if (t != USEC_INFINITY && t > n)
+                        return sd_bus_error_setf(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT, "Too many login attempts, please try again in %s!",
+                                                 format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC));
+
+                return sd_bus_error_setf(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT, "Too many login attempts, please try again later.");
+        }
+
+        return 0;
+}
+
+static int home_fixate_internal(
+                Home *h,
+                UserRecord *secret,
+                HomeState for_state,
+                sd_bus_error *error) {
+
+        int r;
+
+        assert(h);
+        assert(IN_SET(for_state, HOME_FIXATING, HOME_FIXATING_FOR_ACTIVATION, HOME_FIXATING_FOR_ACQUIRE));
+
+        r = home_start_work(h, "inspect", h->record, secret);
+        if (r < 0)
+                return r;
+
+        if (for_state == HOME_FIXATING_FOR_ACTIVATION) {
+                /* Remember the secret data, since we need it for the activation again, later on. */
+                user_record_unref(h->secret);
+                h->secret = user_record_ref(secret);
+        }
+
+        home_set_state(h, for_state);
+        return 0;
+}
+
+int home_fixate(Home *h, UserRecord *secret, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        switch (home_get_state(h)) {
+        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_INACTIVE:
+        case HOME_ACTIVE:
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_FIXATED, "Home %s is already fixated.", h->user_name);
+        case HOME_UNFIXATED:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        return home_fixate_internal(h, secret, HOME_FIXATING, error);
+}
+
+static int home_activate_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+        assert(IN_SET(for_state, HOME_ACTIVATING, HOME_ACTIVATING_FOR_ACQUIRE));
+
+        r = home_start_work(h, "activate", h->record, secret);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, for_state);
+        return 0;
+}
+
+int home_activate(Home *h, UserRecord *secret, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        switch (home_get_state(h)) {
+        case HOME_UNFIXATED:
+                return home_fixate_internal(h, secret, HOME_FIXATING_FOR_ACTIVATION, error);
+        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_ACTIVE:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_ALREADY_ACTIVE, "Home %s is already 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);
+        case HOME_INACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        return home_activate_internal(h, secret, HOME_ACTIVATING, error);
+}
+
+static int home_authenticate_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+        assert(IN_SET(for_state, HOME_AUTHENTICATING, HOME_AUTHENTICATING_WHILE_ACTIVE, HOME_AUTHENTICATING_FOR_ACQUIRE));
+
+        r = home_start_work(h, "inspect", h->record, secret);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, for_state);
+        return 0;
+}
+
+int home_authenticate(Home *h, UserRecord *secret, sd_bus_error *error) {
+        HomeState state;
+        int r;
+
+        assert(h);
+
+        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_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_UNFIXATED:
+        case HOME_INACTIVE:
+        case HOME_ACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        return home_authenticate_internal(h, secret, state == HOME_ACTIVE ? HOME_AUTHENTICATING_WHILE_ACTIVE : HOME_AUTHENTICATING, error);
+}
+
+static int home_deactivate_internal(Home *h, bool force, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        r = home_start_work(h, force ? "deactivate-force" : "deactivate", h->record, NULL);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, HOME_DEACTIVATING);
+        return 0;
+}
+
+int home_deactivate(Home *h, bool force, sd_bus_error *error) {
+        assert(h);
+
+        switch (home_get_state(h)) {
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                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);
+        case HOME_ACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        return home_deactivate_internal(h, force, error);
+}
+
+int home_create(Home *h, UserRecord *secret, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        switch (home_get_state(h)) {
+        case HOME_INACTIVE:
+                if (h->record->storage < 0)
+                        break; /* if no storage is defined we don't know what precisely to look for, hence
+                                * HOME_INACTIVE is OK in that case too. */
+
+                if (IN_SET(user_record_test_image_path(h->record), USER_TEST_MAYBE, USER_TEST_UNDEFINED))
+                        break; /* And if the image path test isn't conclusive, let's also go on */
+
+                _fallthrough_;
+        case HOME_UNFIXATED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_EXISTS, "Home of user %s already exists.", h->user_name);
+        case HOME_ABSENT:
+                break;
+        case HOME_ACTIVE:
+        case HOME_LOCKED:
+        default:
+                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);
+        }
+
+        if (h->record->enforce_password_policy == false)
+                log_debug("Password quality check turned off for account, skipping.");
+        else {
+                r = quality_check_password(h->record, secret, error);
+                if (r < 0)
+                        return r;
+        }
+
+        r = home_start_work(h, "create", h->record, secret);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, HOME_CREATING);
+        return 0;
+}
+
+int home_remove(Home *h, sd_bus_error *error) {
+        HomeState state;
+        int r;
+
+        assert(h);
+
+        state = home_get_state(h);
+        switch (state) {
+        case HOME_ABSENT: /* If the home directory is absent, then this is just like unregistering */
+                return home_unregister(h, error);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_UNFIXATED:
+        case HOME_INACTIVE:
+                break;
+        case HOME_ACTIVE:
+        default:
+                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);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, HOME_REMOVING);
+        return 0;
+}
+
+static int user_record_extend_with_binding(UserRecord *hr, UserRecord *with_binding, UserRecordLoadFlags flags, UserRecord **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *nr = NULL;
+        JsonVariant *binding;
+        int r;
+
+        assert(hr);
+        assert(with_binding);
+        assert(ret);
+
+        assert_se(v = json_variant_ref(hr->json));
+
+        binding = json_variant_by_key(with_binding->json, "binding");
+        if (binding) {
+                r = json_variant_set_field(&v, "binding", binding);
+                if (r < 0)
+                        return r;
+        }
+
+        nr = user_record_new();
+        if (!nr)
+                return -ENOMEM;
+
+        r = user_record_load(nr, v, flags);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(nr);
+        return 0;
+}
+
+static int home_update_internal(Home *h, const char *verb, UserRecord *hr, UserRecord *secret, sd_bus_error *error) {
+        _cleanup_(user_record_unrefp) UserRecord *new_hr = NULL, *saved_secret = NULL, *signed_hr = NULL;
+        int r, c;
+
+        assert(h);
+        assert(verb);
+        assert(hr);
+
+        if (!user_record_compatible(hr, h->record))
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Updated user record is not compatible with existing one.");
+        c = user_record_compare_last_change(hr, h->record); /* refuse downgrades */
+        if (c < 0)
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_DOWNGRADE, "Refusing to update to older home record.");
+
+        if (!secret && FLAGS_SET(hr->mask, USER_RECORD_SECRET)) {
+                r = user_record_clone(hr, USER_RECORD_EXTRACT_SECRET, &saved_secret);
+                if (r < 0)
+                        return r;
+
+                secret = saved_secret;
+        }
+
+        r = manager_verify_user_record(h->manager, hr);
+        switch (r) {
+
+        case USER_RECORD_UNSIGNED:
+                if (h->signed_locally <= 0) /* If the existing record is not owned by us, don't accept an
+                                             * unsigned new record. i.e. only implicitly sign new records
+                                             * that where previously signed by us too. */
+                        return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_SIGNED, "Home %s is signed and cannot be modified locally.", h->user_name);
+
+                /* The updated record is not signed, then do so now */
+                r = manager_sign_user_record(h->manager, hr, &signed_hr, error);
+                if (r < 0)
+                        return r;
+
+                hr = signed_hr;
+                break;
+
+        case USER_RECORD_SIGNED_EXCLUSIVE:
+        case USER_RECORD_SIGNED:
+        case USER_RECORD_FOREIGN:
+                /* Has already been signed. Great! */
+                break;
+
+        case -ENOKEY:
+        default:
+                return r;
+        }
+
+        r = user_record_extend_with_binding(hr, h->record, USER_RECORD_LOAD_MASK_SECRET, &new_hr);
+        if (r < 0)
+                return r;
+
+        if (c == 0) {
+                /* different payload but same lastChangeUSec field? That's not cool! */
+
+                r = user_record_masked_equal(new_hr, h->record, USER_RECORD_REGULAR|USER_RECORD_PRIVILEGED|USER_RECORD_PER_MACHINE);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Home record different but timestamp remained the same, refusing.");
+        }
+
+        r = home_start_work(h, verb, new_hr, secret);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int home_update(Home *h, UserRecord *hr, sd_bus_error *error) {
+        HomeState state;
+        int r;
+
+        assert(h);
+        assert(hr);
+
+        state = home_get_state(h);
+        switch (state) {
+        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);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_INACTIVE:
+        case HOME_ACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        r = home_update_internal(h, "update", hr, NULL, error);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, state == HOME_ACTIVE ? HOME_UPDATING_WHILE_ACTIVE : HOME_UPDATING);
+        return 0;
+}
+
+int home_resize(Home *h, uint64_t disk_size, UserRecord *secret, sd_bus_error *error) {
+        _cleanup_(user_record_unrefp) UserRecord *c = NULL;
+        HomeState state;
+        int r;
+
+        assert(h);
+
+        state = home_get_state(h);
+        switch (state) {
+        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);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_INACTIVE:
+        case HOME_ACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        if (disk_size == UINT64_MAX || disk_size == h->record->disk_size) {
+                if (h->record->disk_size == UINT64_MAX)
+                        return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not disk size to resize to specified.");
+
+                c = user_record_ref(h->record); /* Shortcut if size is unspecified or matches the record */
+        } else {
+                _cleanup_(user_record_unrefp) UserRecord *signed_c = NULL;
+
+                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);
+
+                r = user_record_clone(h->record, USER_RECORD_LOAD_REFUSE_SECRET, &c);
+                if (r < 0)
+                        return r;
+
+                r = user_record_set_disk_size(c, disk_size);
+                if (r == -ERANGE)
+                        return sd_bus_error_setf(error, BUS_ERROR_BAD_HOME_SIZE, "Requested size for home %s out of acceptable range.", h->user_name);
+                if (r < 0)
+                        return r;
+
+                r = user_record_update_last_changed(c, false);
+                if (r == -ECHRNG)
+                        return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Record last change time of %s is newer than current time, cannot update.", h->user_name);
+                if (r < 0)
+                        return r;
+
+                r = manager_sign_user_record(h->manager, c, &signed_c, error);
+                if (r < 0)
+                        return r;
+
+                user_record_unref(c);
+                c = TAKE_PTR(signed_c);
+        }
+
+        r = home_update_internal(h, "resize", c, secret, error);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, state == HOME_ACTIVE ? HOME_RESIZING_WHILE_ACTIVE : HOME_RESIZING);
+        return 0;
+}
+
+static int home_may_change_password(
+                Home *h,
+                sd_bus_error *error) {
+
+        int r;
+
+        assert(h);
+
+        r = user_record_test_password_change_required(h->record);
+        if (IN_SET(r, -EKEYREVOKED, -EOWNERDEAD, -EKEYEXPIRED))
+                return 0; /* expired in some form, but chaning is allowed */
+        if (IN_SET(r, -EKEYREJECTED, -EROFS))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Expiration settings of account %s do not allow changing of password.", h->user_name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to test password expiry: %m");
+
+        return 0; /* not expired */
+}
+
+int home_passwd(Home *h,
+                UserRecord *new_secret,
+                UserRecord *old_secret,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *c = NULL, *merged_secret = NULL, *signed_c = NULL;
+        HomeState state;
+        int r;
+
+        assert(h);
+
+        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);
+
+        state = home_get_state(h);
+        switch (state) {
+        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);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_INACTIVE:
+        case HOME_ACTIVE:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        r = home_may_change_password(h, error);
+        if (r < 0)
+                return r;
+
+        r = user_record_clone(h->record, USER_RECORD_LOAD_REFUSE_SECRET, &c);
+        if (r < 0)
+                return r;
+
+        merged_secret = user_record_new();
+        if (!merged_secret)
+                return -ENOMEM;
+
+        r = user_record_merge_secret(merged_secret, old_secret);
+        if (r < 0)
+                return r;
+
+        r = user_record_merge_secret(merged_secret, new_secret);
+        if (r < 0)
+                return r;
+
+        if (!strv_isempty(new_secret->password)) {
+                /* Update the password only if one is specified, otherwise let's just reuse the old password
+                 * data. This is useful as a way to propagate updated user records into the LUKS backends
+                 * properly. */
+
+                r = user_record_make_hashed_password(c, new_secret->password, /* extend = */ false);
+                if (r < 0)
+                        return r;
+
+                r = user_record_set_password_change_now(c, -1 /* remove */);
+                if (r < 0)
+                        return r;
+        }
+
+        r = user_record_update_last_changed(c, true);
+        if (r == -ECHRNG)
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_RECORD_MISMATCH, "Record last change time of %s is newer than current time, cannot update.", h->user_name);
+        if (r < 0)
+                return r;
+
+        r = manager_sign_user_record(h->manager, c, &signed_c, error);
+        if (r < 0)
+                return r;
+
+        if (c->enforce_password_policy == false)
+                log_debug("Password quality check turned off for account, skipping.");
+        else {
+                r = quality_check_password(c, merged_secret, error);
+                if (r < 0)
+                        return r;
+        }
+
+        r = home_update_internal(h, "passwd", signed_c, merged_secret, error);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, state == HOME_ACTIVE ? HOME_PASSWD_WHILE_ACTIVE : HOME_PASSWD);
+        return 0;
+}
+
+int home_unregister(Home *h, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        switch (home_get_state(h)) {
+        case HOME_UNFIXATED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_UNFIXATED, "Home %s is not registered.", h->user_name);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is currently locked.", h->user_name);
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                break;
+        case HOME_ACTIVE:
+        default:
+                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_unlink_record(h);
+        if (r < 0)
+                return r;
+
+        /* And destroy the whole entry. The caller needs to be prepared for that. */
+        h = home_free(h);
+        return 1;
+}
+
+int home_lock(Home *h, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        switch (home_get_state(h)) {
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_ACTIVE, "Home %s is not active.", h->user_name);
+        case HOME_LOCKED:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_LOCKED, "Home %s is already locked.", h->user_name);
+        case HOME_ACTIVE:
+                break;
+        default:
+                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);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, HOME_LOCKING);
+        return 0;
+}
+
+static int home_unlock_internal(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+        assert(IN_SET(for_state, HOME_UNLOCKING, HOME_UNLOCKING_FOR_ACQUIRE));
+
+        r = home_start_work(h, "unlock", h->record, secret);
+        if (r < 0)
+                return r;
+
+        home_set_state(h, for_state);
+        return 0;
+}
+
+int home_unlock(Home *h, UserRecord *secret, sd_bus_error *error) {
+        int r;
+        assert(h);
+
+        r = home_ratelimit(h, error);
+        if (r < 0)
+                return r;
+
+        switch (home_get_state(h)) {
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+        case HOME_ACTIVE:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_NOT_LOCKED, "Home %s is not locked.", h->user_name);
+        case HOME_LOCKED:
+                break;
+        default:
+                return sd_bus_error_setf(error, BUS_ERROR_HOME_BUSY, "An operation on home %s is currently being executed.", h->user_name);
+        }
+
+        return home_unlock_internal(h, secret, HOME_UNLOCKING, error);
+}
+
+HomeState home_get_state(Home *h) {
+        assert(h);
+
+        /* When the state field is initialized, it counts. */
+        if (h->state >= 0)
+                return h->state;
+
+        /* Otherwise, let's see if the home directory is mounted. If so, we assume for sure the home
+         * directory is active */
+        if (user_record_test_home_directory(h->record) == USER_TEST_MOUNTED)
+                return HOME_ACTIVE;
+
+        /* And if we see the image being gone, we report this as absent */
+        if (user_record_test_image_path(h->record) == USER_TEST_ABSENT)
+                return HOME_ABSENT;
+
+        /* And for all other cases we return "inactive". */
+        return HOME_INACTIVE;
+}
+
+void home_process_notify(Home *h, char **l) {
+        const char *e;
+        int error;
+        int r;
+
+        assert(h);
+
+        e = strv_env_get(l, "ERRNO");
+        if (!e) {
+                log_debug("Got notify message lacking ERRNO= field, ignoring.");
+                return;
+        }
+
+        r = safe_atoi(e, &error);
+        if (r < 0) {
+                log_debug_errno(r, "Failed to parse receieved error number, ignoring: %s", e);
+                return;
+        }
+        if (error <= 0) {
+                log_debug("Error number is out of range: %i", error);
+                return;
+        }
+
+        h->worker_error_code = error;
+}
+
+int home_killall(Home *h) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_free_ char *unit = NULL;
+        int r;
+
+        assert(h);
+
+        if (!uid_is_valid(h->uid))
+                return 0;
+
+        assert(h->uid > 0); /* We never should be UID 0 */
+
+        /* Let's kill everything matching the specified UID */
+        r = safe_fork("(sd-killer)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT|FORK_LOG, NULL);
+        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);
+                        _exit(EXIT_FAILURE);
+                }
+
+                if (kill(-1, SIGKILL) < 0) {
+                        log_error_errno(errno, "Failed to kill all processes of UID " UID_FMT ": %m", h->uid);
+                        _exit(EXIT_FAILURE);
+                }
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        /* Let's also kill everything in the user's slice */
+        if (asprintf(&unit, "user-" UID_FMT ".slice", h->uid) < 0)
+                return log_oom();
+
+        r = sd_bus_call_method(
+                        h->manager->bus,
+                        "org.freedesktop.systemd1",
+                        "/org/freedesktop/systemd1",
+                        "org.freedesktop.systemd1.Manager",
+                        "KillUnit",
+                        &error,
+                        NULL,
+                        "ssi", unit, "all", SIGKILL);
+        if (r < 0)
+                log_full_errno(sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) ? LOG_DEBUG : LOG_WARNING,
+                               r, "Failed to kill login processes of user, ignoring: %s", bus_error_message(&error, r));
+
+        return 1;
+}
+
+static int home_get_disk_status_luks(
+                Home *h,
+                HomeState state,
+                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) {
+
+        uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
+                disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX,
+                stat_used = UINT64_MAX, fs_size = UINT64_MAX, header_size = 0;
+
+        struct statfs sfs;
+        const char *hd;
+        int r;
+
+        assert(h);
+        assert(ret_disk_size);
+        assert(ret_disk_usage);
+        assert(ret_disk_free);
+        assert(ret_disk_ceiling);
+
+        if (state != HOME_ABSENT) {
+                const char *ip;
+
+                ip = user_record_image_path(h->record);
+                if (ip) {
+                        struct stat st;
+
+                        if (stat(ip, &st) < 0)
+                                log_debug_errno(errno, "Failed to stat() %s, ignoring: %m", ip);
+                        else if (S_ISREG(st.st_mode)) {
+                                _cleanup_free_ char *parent = NULL;
+
+                                disk_size = st.st_size;
+                                stat_used = st.st_blocks * 512;
+
+                                parent = dirname_malloc(ip);
+                                if (!parent)
+                                        return log_oom();
+
+                                if (statfs(parent, &sfs) < 0)
+                                        log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", parent);
+                                else
+                                        disk_ceiling = stat_used + sfs.f_bsize * sfs.f_bavail;
+
+                        } else if (S_ISBLK(st.st_mode)) {
+                                _cleanup_free_ char *szbuf = NULL;
+                                char p[SYS_BLOCK_PATH_MAX("/size")];
+
+                                /* Let's read the size off sysfs, so that we don't have to open the device */
+                                xsprintf_sys_block_path(p, "/size", st.st_rdev);
+                                r = read_one_line_file(p, &szbuf);
+                                if (r < 0)
+                                        log_debug_errno(r, "Failed to read %s, ignoring: %m", p);
+                                else {
+                                        uint64_t sz;
+
+                                        r = safe_atou64(szbuf, &sz);
+                                        if (r < 0)
+                                                log_debug_errno(r, "Failed to parse %s, ignoring: %s", p, szbuf);
+                                        else
+                                                disk_size = sz * 512;
+                                }
+                        } else
+                                log_debug("Image path is not a block device or regular file, not able to acquire size.");
+                }
+        }
+
+        if (!HOME_STATE_IS_ACTIVE(state))
+                goto finish;
+
+        hd = user_record_home_directory(h->record);
+        if (!hd)
+                goto finish;
+
+        if (statfs(hd, &sfs) < 0) {
+                log_debug_errno(errno, "Failed  to statfs() %s, ignoring: %m", hd);
+                goto finish;
+        }
+
+        disk_free = sfs.f_bsize * sfs.f_bavail;
+        fs_size = sfs.f_bsize * sfs.f_blocks;
+        if (disk_size != UINT64_MAX && disk_size > fs_size)
+                header_size = disk_size - fs_size;
+
+        /* We take a perspective from the user here (as opposed to from the host): the used disk space is the
+         * difference from the limit and what's free. This makes a difference if sparse mode is not used: in
+         * that case the image is pre-allocated and thus appears all used from the host PoV but is not used
+         * up at all yet from the user's PoV.
+         *
+         * That said, we use use the stat() reported loopback file size as upper boundary: our footprint can
+         * never be larger than what we take up on the lowest layers. */
+
+        if (disk_size != UINT64_MAX && disk_size > disk_free) {
+                disk_usage = disk_size - disk_free;
+
+                if (stat_used != UINT64_MAX && disk_usage > stat_used)
+                        disk_usage = stat_used;
+        } else
+                disk_usage = stat_used;
+
+        /* If we have the magic, determine floor preferably by magic */
+        disk_floor = minimal_size_by_fs_magic(sfs.f_type) + header_size;
+
+finish:
+        /* If we don't know the magic, go by file system name */
+        if (disk_floor == UINT64_MAX)
+                disk_floor = minimal_size_by_fs_name(user_record_file_system_type(h->record));
+
+        *ret_disk_size = disk_size;
+        *ret_disk_usage = disk_usage;
+        *ret_disk_free = disk_free;
+        *ret_disk_ceiling = disk_ceiling;
+        *ret_disk_floor = disk_floor;
+
+        return 0;
+}
+
+static int home_get_disk_status_directory(
+                Home *h,
+                HomeState state,
+                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) {
+
+        uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX,
+                disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
+        struct statfs sfs;
+        struct dqblk req;
+        const char *path = NULL;
+        int r;
+
+        assert(ret_disk_size);
+        assert(ret_disk_usage);
+        assert(ret_disk_free);
+        assert(ret_disk_ceiling);
+        assert(ret_disk_floor);
+
+        if (HOME_STATE_IS_ACTIVE(state))
+                path = user_record_home_directory(h->record);
+
+        if (!path) {
+                if (state == HOME_ABSENT)
+                        goto finish;
+
+                path = user_record_image_path(h->record);
+        }
+
+        if (!path)
+                goto finish;
+
+        if (statfs(path, &sfs) < 0)
+                log_debug_errno(errno, "Failed to statfs() %s, ignoring: %m", path);
+        else {
+                disk_free = sfs.f_bsize * sfs.f_bavail;
+                disk_size = sfs.f_bsize * sfs.f_blocks;
+
+                /* We don't initialize disk_usage from statfs() data here, since the device is likely not used
+                 * by us alone, and disk_usage should only reflect our own use. */
+        }
+
+        if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME)) {
+
+                r = btrfs_is_subvol(path);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to determine whether %s is a btrfs subvolume: %m", path);
+                else if (r > 0) {
+                        BtrfsQuotaInfo qi;
+
+                        r = btrfs_subvol_get_subtree_quota(path, 0, &qi);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to query btrfs subtree quota, ignoring: %m");
+                        else {
+                                disk_usage = qi.referenced;
+
+                                if (disk_free != UINT64_MAX) {
+                                        disk_ceiling = qi.referenced + disk_free;
+
+                                        if (disk_size != UINT64_MAX && disk_ceiling > disk_size)
+                                                disk_ceiling = disk_size;
+                                }
+
+                                if (qi.referenced_max != UINT64_MAX) {
+                                        if (disk_size != UINT64_MAX)
+                                                disk_size = MIN(qi.referenced_max, disk_size);
+                                        else
+                                                disk_size = qi.referenced_max;
+                                }
+
+                                if (disk_size != UINT64_MAX) {
+                                        if (disk_size > disk_usage)
+                                                disk_free = disk_size - disk_usage;
+                                        else
+                                                disk_free = 0;
+                                }
+                        }
+
+                        goto finish;
+                }
+        }
+
+        if (IN_SET(h->record->storage, USER_CLASSIC, USER_DIRECTORY, USER_FSCRYPT)) {
+                r = quotactl_path(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), path, h->uid, &req);
+                if (r < 0) {
+                        if (ERRNO_IS_NOT_SUPPORTED(r)) {
+                                log_debug_errno(r, "No UID quota support on %s.", path);
+                                goto finish;
+                        }
+
+                        if (r != -ESRCH) {
+                                log_debug_errno(r, "Failed to query disk quota for UID " UID_FMT ": %m", h->uid);
+                                goto finish;
+                        }
+
+                        disk_usage = 0; /* No record of this user? then nothing was used */
+                } else {
+                        if (FLAGS_SET(req.dqb_valid, QIF_SPACE) && disk_free != UINT64_MAX) {
+                                disk_ceiling = req.dqb_curspace + disk_free;
+
+                                if (disk_size != UINT64_MAX && disk_ceiling > disk_size)
+                                        disk_ceiling = disk_size;
+                        }
+
+                        if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS)) {
+                                uint64_t q;
+
+                                /* Take the minimum of the quota and the available disk space here */
+                                q = req.dqb_bhardlimit * QIF_DQBLKSIZE;
+                                if (disk_size != UINT64_MAX)
+                                        disk_size = MIN(disk_size, q);
+                                else
+                                        disk_size = q;
+                        }
+                        if (FLAGS_SET(req.dqb_valid, QIF_SPACE)) {
+                                disk_usage = req.dqb_curspace;
+
+                                if (disk_size != UINT64_MAX) {
+                                        if (disk_size > disk_usage)
+                                                disk_free = disk_size - disk_usage;
+                                        else
+                                                disk_free = 0;
+                                }
+                        }
+                }
+        }
+
+finish:
+        *ret_disk_size = disk_size;
+        *ret_disk_usage = disk_usage;
+        *ret_disk_free = disk_free;
+        *ret_disk_ceiling = disk_ceiling;
+        *ret_disk_floor = disk_floor;
+
+        return 0;
+}
+
+int home_augment_status(
+                Home *h,
+                UserRecordLoadFlags flags,
+                UserRecord **ret) {
+
+        uint64_t disk_size = UINT64_MAX, disk_usage = UINT64_MAX, disk_free = UINT64_MAX, disk_ceiling = UINT64_MAX, disk_floor = UINT64_MAX;
+        _cleanup_(json_variant_unrefp) JsonVariant *j = NULL, *v = NULL, *m = NULL, *status = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+        char ids[SD_ID128_STRING_MAX];
+        HomeState state;
+        sd_id128_t id;
+        int r;
+
+        assert(h);
+        assert(ret);
+
+        /* We are supposed to add this, this can't be on hence. */
+        assert(!FLAGS_SET(flags, USER_RECORD_STRIP_STATUS));
+
+        r = sd_id128_get_machine(&id);
+        if (r < 0)
+                return r;
+
+        state = home_get_state(h);
+
+        switch (h->record->storage) {
+
+        case USER_LUKS:
+                r = home_get_disk_status_luks(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case USER_CLASSIC:
+        case USER_DIRECTORY:
+        case USER_SUBVOLUME:
+        case USER_FSCRYPT:
+        case USER_CIFS:
+                r = home_get_disk_status_directory(h, state, &disk_size, &disk_usage, &disk_free, &disk_ceiling, &disk_floor);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        default:
+                ; /* unset */
+        }
+
+        if (disk_floor == UINT64_MAX || (disk_usage != UINT64_MAX && disk_floor < disk_usage))
+                disk_floor = disk_usage;
+        if (disk_floor == UINT64_MAX || disk_floor < USER_DISK_SIZE_MIN)
+                disk_floor = USER_DISK_SIZE_MIN;
+        if (disk_ceiling == UINT64_MAX || disk_ceiling > USER_DISK_SIZE_MAX)
+                disk_ceiling = USER_DISK_SIZE_MAX;
+
+        r = json_build(&status,
+                       JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("state", JSON_BUILD_STRING(home_state_to_string(state))),
+                                       JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Home")),
+                                       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)),
+                                       JSON_BUILD_PAIR_CONDITION(disk_ceiling != UINT64_MAX, "diskCeiling", JSON_BUILD_UNSIGNED(disk_ceiling)),
+                                       JSON_BUILD_PAIR_CONDITION(disk_floor != UINT64_MAX, "diskFloor", JSON_BUILD_UNSIGNED(disk_floor)),
+                                       JSON_BUILD_PAIR_CONDITION(h->signed_locally >= 0, "signedLocally", JSON_BUILD_BOOLEAN(h->signed_locally))
+                       ));
+        if (r < 0)
+                return r;
+
+        j = json_variant_ref(h->record->json);
+        v = json_variant_ref(json_variant_by_key(j, "status"));
+        m = json_variant_ref(json_variant_by_key(v, sd_id128_to_string(id, ids)));
+
+        r = json_variant_filter(&m, STRV_MAKE("diskSize", "diskUsage", "diskFree", "diskCeiling", "diskFloor", "signedLocally"));
+        if (r < 0)
+                return r;
+
+        r = json_variant_merge(&m, status);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&v, ids, m);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&j, "status", v);
+        if (r < 0)
+                return r;
+
+        ur = user_record_new();
+        if (!ur)
+                return -ENOMEM;
+
+        r = user_record_load(ur, j, flags);
+        if (r < 0)
+                return r;
+
+        ur->incomplete =
+                FLAGS_SET(h->record->mask, USER_RECORD_PRIVILEGED) &&
+                !FLAGS_SET(ur->mask, USER_RECORD_PRIVILEGED);
+
+        *ret = TAKE_PTR(ur);
+        return 0;
+}
+
+static int on_home_ref_eof(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_(operation_unrefp) Operation *o = NULL;
+        Home *h = userdata;
+
+        assert(s);
+        assert(h);
+
+        if (h->ref_event_source_please_suspend == s)
+                h->ref_event_source_please_suspend = sd_event_source_disable_unref(h->ref_event_source_please_suspend);
+
+        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)
+                return 0;
+
+        log_info("Got notification that all sessions of user %s ended, deactivating automatically.", h->user_name);
+
+        o = operation_new(OPERATION_PIPE_EOF, NULL);
+        if (!o) {
+                log_oom();
+                return 0;
+        }
+
+        home_schedule_operation(h, o, NULL);
+        return 0;
+}
+
+int home_create_fifo(Home *h, bool please_suspend) {
+        _cleanup_close_ int ret_fd = -1;
+        sd_event_source **ss;
+        const char *fn, *suffix;
+        int r;
+
+        assert(h);
+
+        if (please_suspend) {
+                suffix = ".please-suspend";
+                ss = &h->ref_event_source_please_suspend;
+        } else {
+                suffix = ".dont-suspend";
+                ss = &h->ref_event_source_dont_suspend;
+        }
+
+        fn = strjoina("/run/systemd/home/", h->user_name, suffix);
+
+        if (!*ss) {
+                _cleanup_close_ int ref_fd = -1;
+
+                (void) mkdir("/run/systemd/home/", 0755);
+                if (mkfifo(fn, 0600) < 0 && errno != EEXIST)
+                        return log_error_errno(errno, "Failed to create FIFO %s: %m", fn);
+
+                ref_fd = open(fn, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+                if (ref_fd < 0)
+                        return log_error_errno(errno, "Failed to open FIFO %s for reading: %m", fn);
+
+                r = sd_event_add_io(h->manager->event, ss, ref_fd, 0, on_home_ref_eof, h);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate reference FIFO event source: %m");
+
+                (void) sd_event_source_set_description(*ss, "acquire-ref");
+
+                r = sd_event_source_set_priority(*ss, SD_EVENT_PRIORITY_IDLE-1);
+                if (r < 0)
+                        return r;
+
+                r = sd_event_source_set_io_fd_own(*ss, true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to pass ownership of FIFO event fd to event source: %m");
+
+                TAKE_FD(ref_fd);
+        }
+
+        ret_fd = open(fn, O_WRONLY|O_CLOEXEC|O_NONBLOCK);
+        if (ret_fd < 0)
+                return log_error_errno(errno, "Failed to open FIFO %s for writing: %m", fn);
+
+        return TAKE_FD(ret_fd);
+}
+
+static int home_dispatch_acquire(Home *h, Operation *o) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int (*call)(Home *h, UserRecord *secret, HomeState for_state, sd_bus_error *error) = NULL;
+        HomeState for_state;
+        int r;
+
+        assert(h);
+        assert(o);
+        assert(o->type == OPERATION_ACQUIRE);
+
+        switch (home_get_state(h)) {
+
+        case HOME_UNFIXATED:
+                for_state = HOME_FIXATING_FOR_ACQUIRE;
+                call = home_fixate_internal;
+                break;
+
+        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);
+                break;
+
+        case HOME_INACTIVE:
+                for_state = HOME_ACTIVATING_FOR_ACQUIRE;
+                call = home_activate_internal;
+                break;
+
+        case HOME_ACTIVE:
+                for_state = HOME_AUTHENTICATING_FOR_ACQUIRE;
+                call = home_authenticate_internal;
+                break;
+
+        case HOME_LOCKED:
+                for_state = HOME_UNLOCKING_FOR_ACQUIRE;
+                call = home_unlock_internal;
+                break;
+
+        default:
+                /* All other cases means we are currently executing an operation, which means the job remains
+                 * pending. */
+                return 0;
+        }
+
+        assert(!h->current_operation);
+
+        if (call) {
+                r = home_ratelimit(h, &error);
+                if (r >= 0)
+                        r = call(h, o->secret, for_state, &error);
+        }
+
+        if (r != 0) /* failure or completed */
+                operation_result(o, r, &error);
+        else /* ongoing */
+                h->current_operation = operation_ref(o);
+
+        return 1;
+}
+
+static int home_dispatch_release(Home *h, Operation *o) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(o);
+        assert(o->type == OPERATION_RELEASE);
+
+        if (h->ref_event_source_dont_suspend || h->ref_event_source_please_suspend)
+                /* 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:
+                        r = 0; /* 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:
+                        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);
+
+        if (r <= 0) /* failure or completed */
+                operation_result(o, r, &error);
+        else /* ongoing */
+                h->current_operation = operation_ref(o);
+
+        return 1;
+}
+
+static int home_dispatch_lock_all(Home *h, Operation *o) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(o);
+        assert(o->type == OPERATION_LOCK_ALL);
+
+        switch (home_get_state(h)) {
+
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                log_info("Home %s is not active, no locking necessary.", h->user_name);
+                r = 0; /* done */
+                break;
+
+        case HOME_LOCKED:
+                log_info("Home %s is already locked.", h->user_name);
+                r = 0; /* done */
+                break;
+
+        case HOME_ACTIVE:
+                log_info("Locking home %s.", h->user_name);
+                r = home_lock(h, &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);
+
+        if (r != 0) /* failure or completed */
+                operation_result(o, r, &error);
+        else /* ongoing */
+                h->current_operation = operation_ref(o);
+
+        return 1;
+}
+
+static int home_dispatch_pipe_eof(Home *h, Operation *o) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(o);
+        assert(o->type == OPERATION_PIPE_EOF);
+
+        if (h->ref_event_source_please_suspend || h->ref_event_source_dont_suspend)
+                return 1; /* Hmm, there's a reference again, let's cancel this */
+
+        switch (home_get_state(h)) {
+
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                log_info("Home %s already deactivated, no automatic deactivation needed.", h->user_name);
+                break;
+
+        case HOME_DEACTIVATING:
+                log_info("Home %s is already being deactivated, automatic deactivated unnecessary.", h->user_name);
+                break;
+
+        case HOME_ACTIVE:
+                r = home_deactivate_internal(h, false, &error);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
+                break;
+
+        case HOME_LOCKED:
+        default:
+                /* If the device is locked or any operation is being executed, let's leave this pending */
+                return 0;
+        }
+
+        /* Note that we don't call operation_fail() or operation_success() here, because this kind of
+         * operation has no message associated with it, and thus there's no need to propagate success. */
+
+        assert(!o->message);
+        return 1;
+}
+
+static int home_dispatch_deactivate_force(Home *h, Operation *o) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        int r;
+
+        assert(h);
+        assert(o);
+        assert(o->type == OPERATION_DEACTIVATE_FORCE);
+
+        switch (home_get_state(h)) {
+
+        case HOME_UNFIXATED:
+        case HOME_ABSENT:
+        case HOME_INACTIVE:
+                log_debug("Home %s already deactivated, no forced deactivation due to unplug needed.", h->user_name);
+                break;
+
+        case HOME_DEACTIVATING:
+                log_debug("Home %s is already being deactivated, forced deactivation due to unplug unnecessary.", h->user_name);
+                break;
+
+        case HOME_ACTIVE:
+        case HOME_LOCKED:
+                r = home_deactivate_internal(h, true, &error);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to forcibly deactivate %s, ignoring: %s", h->user_name, bus_error_message(&error, r));
+                break;
+
+        default:
+                /* If any operation is being executed, let's leave this pending */
+                return 0;
+        }
+
+        /* Note that we don't call operation_fail() or operation_success() here, because this kind of
+         * operation has no message associated with it, and thus there's no need to propagate success. */
+
+        assert(!o->message);
+        return 1;
+}
+
+static int on_pending(sd_event_source *s, void *userdata) {
+        Home *h = userdata;
+        Operation *o;
+        int r;
+
+        assert(s);
+        assert(h);
+
+        o = ordered_set_first(h->pending_operations);
+        if (o) {
+                static int (* const operation_table[_OPERATION_MAX])(Home *h, Operation *o) = {
+                        [OPERATION_ACQUIRE]          = home_dispatch_acquire,
+                        [OPERATION_RELEASE]          = home_dispatch_release,
+                        [OPERATION_LOCK_ALL]         = home_dispatch_lock_all,
+                        [OPERATION_PIPE_EOF]         = home_dispatch_pipe_eof,
+                        [OPERATION_DEACTIVATE_FORCE] = home_dispatch_deactivate_force,
+                };
+
+                assert(operation_table[o->type]);
+                r = operation_table[o->type](h, o);
+                if (r != 0) {
+                        /* The operation completed, let's remove it from the pending list, and exit while
+                         * leaving the event source enabled as it is. */
+                        assert_se(ordered_set_remove(h->pending_operations, o) == o);
+                        operation_unref(o);
+                        return 0;
+                }
+        }
+
+        /* Nothing to do anymore, let's turn off this event source */
+        r = sd_event_source_set_enabled(s, SD_EVENT_OFF);
+        if (r < 0)
+                return log_error_errno(r, "Failed to disable event source: %m");
+
+        return 0;
+}
+
+int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error) {
+        int r;
+
+        assert(h);
+
+        if (o) {
+                if (ordered_set_size(h->pending_operations) >= PENDING_OPERATIONS_MAX)
+                        return sd_bus_error_setf(error, BUS_ERROR_TOO_MANY_OPERATIONS, "Too many client operations requested");
+
+                r = ordered_set_ensure_allocated(&h->pending_operations, &operation_hash_ops);
+                if (r < 0)
+                        return r;
+
+                r = ordered_set_put(h->pending_operations, o);
+                if (r < 0)
+                        return r;
+
+                operation_ref(o);
+        }
+
+        if (!h->pending_event_source) {
+                r = sd_event_add_defer(h->manager->event, &h->pending_event_source, on_pending, h);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate pending defer event source: %m");
+
+                (void) sd_event_source_set_description(h->pending_event_source, "pending");
+
+                r = sd_event_source_set_priority(h->pending_event_source, SD_EVENT_PRIORITY_IDLE);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_event_source_set_enabled(h->pending_event_source, SD_EVENT_ON);
+        if (r < 0)
+                return log_error_errno(r, "Failed to trigger pending event source: %m");
+
+        return 0;
+}
+
+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;
+
+        assert(h);
+
+        if (user_record_storage(h->record) != USER_LUKS)
+                return -ENXIO;
+
+        ip = user_record_image_path(h->record);
+        if (!ip)
+                return -ENXIO;
+
+        if (!path_startswith(ip, "/dev/"))
+                return -ENXIO;
+
+        if (stat(ip, &st) < 0)
+                return -errno;
+
+        if (!S_ISBLK(st.st_mode))
+                return -ENOTBLK;
+
+        r = sd_device_new_from_devnum(&d, 'b', st.st_rdev);
+        if (r < 0)
+                return r;
+
+        r = sd_device_get_property_value(d, "ID_SEAT", &seat);
+        if (r == -ENOENT) /* no property means seat0 */
+                seat = "seat0";
+        else if (r < 0)
+                return r;
+
+        c = strdup(seat);
+        if (!c)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(c);
+        return 0;
+}
+
+int home_auto_login(Home *h, char ***ret_seats) {
+        _cleanup_free_ char *seat = NULL, *seat2 = NULL;
+
+        assert(h);
+        assert(ret_seats);
+
+        (void) home_get_image_path_seat(h, &seat);
+
+        if (h->record->auto_login > 0 && !streq_ptr(seat, "seat0")) {
+                /* For now, when the auto-login boolean is set for a user, let's make it mean
+                 * "seat0". Eventually we can extend the concept and allow configuration of any kind of seat,
+                 * but let's keep simple initially, most likely the feature is interesting on single-user
+                 * systems anyway, only.
+                 *
+                 * We filter out users marked for auto-login in we know for sure their home directory is
+                 * absent. */
+
+                if (user_record_test_image_path(h->record) != USER_TEST_ABSENT) {
+                        seat2 = strdup("seat0");
+                        if (!seat2)
+                                return -ENOMEM;
+                }
+        }
+
+        if (seat || seat2) {
+                _cleanup_strv_free_ char **list = NULL;
+                size_t i = 0;
+
+                list = new(char*, 3);
+                if (!list)
+                        return -ENOMEM;
+
+                if (seat)
+                        list[i++] = TAKE_PTR(seat);
+                if (seat2)
+                        list[i++] = TAKE_PTR(seat2);
+
+                list[i] = NULL;
+                *ret_seats = TAKE_PTR(list);
+                return 1;
+        }
+
+        *ret_seats = NULL;
+        return 0;
+}
+
+int home_set_current_message(Home *h, sd_bus_message *m) {
+        assert(h);
+
+        if (!m)
+                return 0;
+
+        if (h->current_operation)
+                return -EBUSY;
+
+        h->current_operation = operation_new(OPERATION_IMMEDIATE, m);
+        if (!h->current_operation)
+                return -ENOMEM;
+
+        return 1;
+}
+
+static const char* const home_state_table[_HOME_STATE_MAX] = {
+        [HOME_UNFIXATED]                   = "unfixated",
+        [HOME_ABSENT]                      = "absent",
+        [HOME_INACTIVE]                    = "inactive",
+        [HOME_FIXATING]                    = "fixating",
+        [HOME_FIXATING_FOR_ACTIVATION]     = "fixating-for-activation",
+        [HOME_FIXATING_FOR_ACQUIRE]        = "fixating-for-acquire",
+        [HOME_ACTIVATING]                  = "activating",
+        [HOME_ACTIVATING_FOR_ACQUIRE]      = "activating-for-acquire",
+        [HOME_DEACTIVATING]                = "deactivating",
+        [HOME_ACTIVE]                      = "active",
+        [HOME_LOCKING]                     = "locking",
+        [HOME_LOCKED]                      = "locked",
+        [HOME_UNLOCKING]                   = "unlocking",
+        [HOME_UNLOCKING_FOR_ACQUIRE]       = "unlocking-for-acquire",
+        [HOME_CREATING]                    = "creating",
+        [HOME_REMOVING]                    = "removing",
+        [HOME_UPDATING]                    = "updating",
+        [HOME_UPDATING_WHILE_ACTIVE]       = "updating-while-active",
+        [HOME_RESIZING]                    = "resizing",
+        [HOME_RESIZING_WHILE_ACTIVE]       = "resizing-while-active",
+        [HOME_PASSWD]                      = "passwd",
+        [HOME_PASSWD_WHILE_ACTIVE]         = "passwd-while-active",
+        [HOME_AUTHENTICATING]              = "authenticating",
+        [HOME_AUTHENTICATING_WHILE_ACTIVE] = "authenticating-while-active",
+        [HOME_AUTHENTICATING_FOR_ACQUIRE]  = "authenticating-for-acquire",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(home_state, HomeState);
diff --git a/src/home/homed-home.h b/src/home/homed-home.h
new file mode 100644 (file)
index 0000000..c75b067
--- /dev/null
@@ -0,0 +1,168 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+typedef struct Home Home;
+
+#include "homed-manager.h"
+#include "homed-operation.h"
+#include "list.h"
+#include "ordered-set.h"
+#include "user-record.h"
+
+typedef enum HomeState {
+        HOME_UNFIXATED,               /* home exists, but local record does not */
+        HOME_ABSENT,                  /* local record exists, but home does not */
+        HOME_INACTIVE,                /* record and home exist, but is not logged in */
+        HOME_FIXATING,                /* generating local record from home */
+        HOME_FIXATING_FOR_ACTIVATION, /* fixating in order to activate soon */
+        HOME_FIXATING_FOR_ACQUIRE,    /* fixating because Acquire() was called */
+        HOME_ACTIVATING,
+        HOME_ACTIVATING_FOR_ACQUIRE,  /* activating because Acquire() was called */
+        HOME_DEACTIVATING,
+        HOME_ACTIVE,                  /* logged in right now */
+        HOME_LOCKING,
+        HOME_LOCKED,
+        HOME_UNLOCKING,
+        HOME_UNLOCKING_FOR_ACQUIRE,   /* unlocking because Acquire() was called */
+        HOME_CREATING,
+        HOME_REMOVING,
+        HOME_UPDATING,
+        HOME_UPDATING_WHILE_ACTIVE,
+        HOME_RESIZING,
+        HOME_RESIZING_WHILE_ACTIVE,
+        HOME_PASSWD,
+        HOME_PASSWD_WHILE_ACTIVE,
+        HOME_AUTHENTICATING,
+        HOME_AUTHENTICATING_WHILE_ACTIVE,
+        HOME_AUTHENTICATING_FOR_ACQUIRE,  /* authenticating because Acquire() was called */
+        _HOME_STATE_MAX,
+        _HOME_STATE_INVALID = -1
+} HomeState;
+
+static inline bool HOME_STATE_IS_ACTIVE(HomeState state) {
+        return IN_SET(state,
+                      HOME_ACTIVE,
+                      HOME_UPDATING_WHILE_ACTIVE,
+                      HOME_RESIZING_WHILE_ACTIVE,
+                      HOME_PASSWD_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
+static inline bool HOME_STATE_IS_EXECUTING_OPERATION(HomeState state) {
+        return IN_SET(state,
+                      HOME_FIXATING,
+                      HOME_FIXATING_FOR_ACTIVATION,
+                      HOME_FIXATING_FOR_ACQUIRE,
+                      HOME_ACTIVATING,
+                      HOME_ACTIVATING_FOR_ACQUIRE,
+                      HOME_DEACTIVATING,
+                      HOME_LOCKING,
+                      HOME_UNLOCKING,
+                      HOME_UNLOCKING_FOR_ACQUIRE,
+                      HOME_CREATING,
+                      HOME_REMOVING,
+                      HOME_UPDATING,
+                      HOME_UPDATING_WHILE_ACTIVE,
+                      HOME_RESIZING,
+                      HOME_RESIZING_WHILE_ACTIVE,
+                      HOME_PASSWD,
+                      HOME_PASSWD_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING,
+                      HOME_AUTHENTICATING_WHILE_ACTIVE,
+                      HOME_AUTHENTICATING_FOR_ACQUIRE);
+}
+
+struct Home {
+        Manager *manager;
+        char *user_name;
+        uid_t uid;
+
+        char *sysfs; /* When found via plugged in device, the sysfs path to it */
+
+        /* Note that the 'state' field is only set to a state while we are doing something (i.e. activating,
+         * deactivating, creating, removing, and such), or when the home is an "unfixated" one. When we are
+         * done with an operation we invalidate the state. This is hint for home_get_state() to check the
+         * state on request as needed from the mount table and similar.*/
+        HomeState state;
+        int signed_locally; /* signed only by us */
+
+        UserRecord *record;
+
+        pid_t worker_pid;
+        int worker_stdout_fd;
+        sd_event_source *worker_event_source;
+        int worker_error_code;
+
+        /* The message we are currently processing, and thus need to reply to on completion */
+        Operation *current_operation;
+
+        /* Stores the raw, plaintext passwords, but only for short periods of time */
+        UserRecord *secret;
+
+        /* When we create a home and that fails, we should possibly unregister the record altogether
+         * again, which is remembered in this boolean. */
+        bool unregister_on_failure;
+
+        /* The reading side of a FIFO stored in /run/systemd/home/, the writing side being used for reference
+         * counting. The references dropped to zero as soon as we see EOF. This concept exists twice: once
+         * for clients that are fine if we suspend the home directory on system suspend, and once for cliets
+         * that are not ok with that. This allows us to determine for each home whether there are any clients
+         * that support unsuspend. */
+        sd_event_source *ref_event_source_please_suspend;
+        sd_event_source *ref_event_source_dont_suspend;
+
+        /* Any pending operations we still need to execute. These are for operations we want to queue if we
+         * can't execute them right-away. */
+        OrderedSet *pending_operations;
+
+        /* A defer event source that processes pending acquire/release/eof events. We have a common
+         * dispatcher that processes all three kinds of events. */
+        sd_event_source *pending_event_source;
+
+        /* Did we send out a D-Bus notification about this entry? */
+        bool announced;
+
+        /* Used to coalesce bus PropertiesChanged events */
+        sd_event_source *deferred_change_event_source;
+};
+
+int home_new(Manager *m, UserRecord *hr, const char *sysfs, Home **ret);
+Home *home_free(Home *h);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Home*, home_free);
+
+int home_set_record(Home *h, UserRecord *hr);
+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_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_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, 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);
+
+HomeState home_get_state(Home *h);
+
+void home_process_notify(Home *h, char **l);
+
+int home_killall(Home *h);
+
+int home_augment_status(Home *h, UserRecordLoadFlags flags, UserRecord **ret);
+
+int home_create_fifo(Home *h, bool please_suspend);
+int home_schedule_operation(Home *h, Operation *o, sd_bus_error *error);
+
+int home_auto_login(Home *h, char ***ret_seats);
+
+int home_set_current_message(Home *h, sd_bus_message *m);
+
+const char *home_state_to_string(HomeState state);
+HomeState home_state_from_string(const char *s);
diff --git a/src/home/homed-manager-bus.c b/src/home/homed-manager-bus.c
new file mode 100644 (file)
index 0000000..e1d6a99
--- /dev/null
@@ -0,0 +1,690 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/capability.h>
+
+#include "alloc-util.h"
+#include "bus-common-errors.h"
+#include "bus-polkit.h"
+#include "format-util.h"
+#include "homed-bus.h"
+#include "homed-home-bus.h"
+#include "homed-manager-bus.h"
+#include "homed-manager.h"
+#include "strv.h"
+#include "user-record-sign.h"
+#include "user-record-util.h"
+#include "user-util.h"
+
+static int property_get_auto_login(
+                sd_bus *bus,
+                const char *path,
+                const char *interface,
+                const char *property,
+                sd_bus_message *reply,
+                void *userdata,
+                sd_bus_error *error) {
+
+        Manager *m = userdata;
+        Iterator i;
+        Home *h;
+        int r;
+
+        assert(bus);
+        assert(reply);
+        assert(m);
+
+        r = sd_bus_message_open_container(reply, 'a', "(sso)");
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(h, m->homes_by_name, i) {
+                _cleanup_(strv_freep) char **seats = NULL;
+                _cleanup_free_ char *home_path = NULL;
+                char **s;
+
+                r = home_auto_login(h, &seats);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to determine whether home '%s' is candidate for auto-login, ignoring: %m", h->user_name);
+                        continue;
+                }
+                if (!r)
+                        continue;
+
+                r = bus_home_path(h, &home_path);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to generate home bus path: %m");
+
+                STRV_FOREACH(s, seats) {
+                        r = sd_bus_message_append(reply, "(sso)", h->user_name, *s, home_path);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        return sd_bus_message_close_container(reply);
+}
+
+static int method_get_home_by_name(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_free_ char *path = NULL;
+        const char *user_name;
+        Manager *m = userdata;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "s", &user_name);
+        if (r < 0)
+                return r;
+        if (!valid_user_group_name(user_name))
+                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 = bus_home_path(h, &path);
+        if (r < 0)
+                return r;
+
+        return sd_bus_reply_method_return(
+                        message, "usussso",
+                        (uint32_t) h->uid,
+                        home_state_to_string(home_get_state(h)),
+                        h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
+                        h->record ? user_record_real_name(h->record) : NULL,
+                        h->record ? user_record_home_directory(h->record) : NULL,
+                        h->record ? user_record_shell(h->record) : NULL,
+                        path);
+}
+
+static int method_get_home_by_uid(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_free_ char *path = NULL;
+        Manager *m = userdata;
+        uint32_t uid;
+        int r;
+        Home *h;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "u", &uid);
+        if (r < 0)
+                return r;
+        if (!uid_is_valid(uid))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid);
+
+        h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
+        if (!h)
+                return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid);
+
+        /* Note that we don't use bus_home_path() here, but build the path manually, since if we are queried
+         * for a UID we should also generate the bus path with a UID, and bus_home_path() uses our more
+         * typical bus path by name. */
+        if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0)
+                return -ENOMEM;
+
+        return sd_bus_reply_method_return(
+                        message, "ssussso",
+                        h->user_name,
+                        home_state_to_string(home_get_state(h)),
+                        h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
+                        h->record ? user_record_real_name(h->record) : NULL,
+                        h->record ? user_record_home_directory(h->record) : NULL,
+                        h->record ? user_record_shell(h->record) : NULL,
+                        path);
+}
+
+static int method_list_homes(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        Manager *m = userdata;
+        Iterator i;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(susussso)");
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(h, m->homes_by_uid, i) {
+                _cleanup_free_ char *path = NULL;
+
+                r = bus_home_path(h, &path);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append(
+                                reply, "(susussso)",
+                                h->user_name,
+                                (uint32_t) h->uid,
+                                home_state_to_string(home_get_state(h)),
+                                h->record ? (uint32_t) user_record_gid(h->record) : GID_INVALID,
+                                h->record ? user_record_real_name(h->record) : NULL,
+                                h->record ? user_record_home_directory(h->record) : NULL,
+                                h->record ? user_record_shell(h->record) : NULL,
+                                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_get_user_record_by_name(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_free_ char *json = NULL, *path = NULL;
+        Manager *m = userdata;
+        const char *user_name;
+        bool incomplete;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "s", &user_name);
+        if (r < 0)
+                return r;
+        if (!valid_user_group_name(user_name))
+                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 = bus_home_get_record_json(h, message, &json, &incomplete);
+        if (r < 0)
+                return r;
+
+        r = bus_home_path(h, &path);
+        if (r < 0)
+                return r;
+
+        return sd_bus_reply_method_return(
+                        message, "sbo",
+                        json,
+                        incomplete,
+                        path);
+}
+
+static int method_get_user_record_by_uid(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_free_ char *json = NULL, *path = NULL;
+        Manager *m = userdata;
+        bool incomplete;
+        uint32_t uid;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "u", &uid);
+        if (r < 0)
+                return r;
+        if (!uid_is_valid(uid))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "UID " UID_FMT " is not valid", uid);
+
+        h = hashmap_get(m->homes_by_uid, UID_TO_PTR(uid));
+        if (!h)
+                return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_HOME, "No home for UID " UID_FMT " known", uid);
+
+        r = bus_home_get_record_json(h, message, &json, &incomplete);
+        if (r < 0)
+                return r;
+
+        if (asprintf(&path, "/org/freedesktop/home1/home/" UID_FMT, h->uid) < 0)
+                return -ENOMEM;
+
+        return sd_bus_reply_method_return(
+                        message, "sbo",
+                        json,
+                        incomplete,
+                        path);
+}
+
+static int generic_home_method(
+                Manager *m,
+                sd_bus_message *message,
+                sd_bus_message_handler_t handler,
+                sd_bus_error *error) {
+
+        const char *user_name;
+        Home *h;
+        int r;
+
+        r = sd_bus_message_read(message, "s", &user_name);
+        if (r < 0)
+                return r;
+
+        if (!valid_user_group_name(user_name))
+                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);
+
+        return handler(message, h, error);
+}
+
+static int method_activate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_activate, error);
+}
+
+static int method_deactivate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        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) {
+        _cleanup_(user_record_unrefp) UserRecord *signed_hr = NULL;
+        struct passwd *pw;
+        struct group *gr;
+        bool signed_locally;
+        Home *other;
+        int r;
+
+        assert(m);
+        assert(hr);
+        assert(ret);
+
+        r = user_record_is_supported(hr, error);
+        if (r < 0)
+                return r;
+
+        other = hashmap_get(m->homes_by_name, hr->user_name);
+        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)
+                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);
+
+        gr = getgrnam(hr->user_name);
+        if (gr)
+                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);
+
+        r = manager_verify_user_record(m, hr);
+        switch (r) {
+
+        case USER_RECORD_UNSIGNED:
+                /* If the record is unsigned, then let's sign it with our own key */
+                r = manager_sign_user_record(m, hr, &signed_hr, error);
+                if (r < 0)
+                        return r;
+
+                hr = signed_hr;
+                _fallthrough_;
+
+        case USER_RECORD_SIGNED_EXCLUSIVE:
+                signed_locally = true;
+                break;
+
+        case USER_RECORD_SIGNED:
+        case USER_RECORD_FOREIGN:
+                signed_locally = false;
+                break;
+
+        case -ENOKEY:
+                return sd_bus_error_setf(error, BUS_ERROR_BAD_SIGNATURE, "Specified user record for %s is signed by a key we don't recognize, refusing.", hr->user_name);
+
+        default:
+                return sd_bus_error_set_errnof(error, r, "Failed to validate signature for '%s': %m", hr->user_name);
+        }
+
+        if (uid_is_valid(hr->uid)) {
+                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)
+                        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);
+
+                gr = getgrgid(hr->uid);
+                if (gr)
+                        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);
+        } else {
+                r = manager_augment_record_with_uid(m, hr);
+                if (r < 0)
+                        return sd_bus_error_set_errnof(error, r, "Failed to acquire UID for '%s': %m", hr->user_name);
+        }
+
+        r = home_new(m, hr, NULL, ret);
+        if (r < 0)
+                return r;
+
+        (*ret)->signed_locally = signed_locally;
+        return r;
+}
+
+static int method_register_home(
+                sd_bus_message *message,
+                void *userdata,
+                sd_bus_error *error) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        Manager *m = userdata;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = bus_message_read_home_record(message, USER_RECORD_LOAD_EMBEDDED, &hr, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.create-home",
+                        NULL,
+                        true,
+                        UID_INVALID,
+                        &m->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        r = validate_and_allocate_home(m, hr, &h, error);
+        if (r < 0)
+                return r;
+
+        r = home_save_record(h);
+        if (r < 0) {
+                home_free(h);
+                return r;
+        }
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_unregister_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        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) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        Manager *m = userdata;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        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, &hr, error);
+        if (r < 0)
+                return r;
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.home1.create-home",
+                        NULL,
+                        true,
+                        UID_INVALID,
+                        &m->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        r = validate_and_allocate_home(m, hr, &h, error);
+        if (r < 0)
+                return r;
+
+        r = home_create(h, hr, error);
+        if (r < 0)
+                goto fail;
+
+        assert(r == 0);
+        h->unregister_on_failure = true;
+        assert(!h->current_operation);
+
+        r = home_set_current_message(h, message);
+        if (r < 0)
+                return r;
+
+        return 1;
+
+fail:
+        (void) home_unlink_record(h);
+        h = home_free(h);
+        return r;
+}
+
+static int method_realize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_realize, error);
+}
+
+static int method_remove_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_remove, error);
+}
+
+static int method_fixate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_fixate, error);
+}
+
+static int method_authenticate_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_authenticate, error);
+}
+
+static int method_update_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        Manager *m = userdata;
+        Home *h;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        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, &hr, error);
+        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);
+}
+
+static int method_resize_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_resize, error);
+}
+
+static int method_change_password_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_change_password, error);
+}
+
+static int method_lock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_lock, error);
+}
+
+static int method_unlock_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_unlock, error);
+}
+
+static int method_acquire_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_acquire, error);
+}
+
+static int method_ref_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_ref, error);
+}
+
+static int method_release_home(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        return generic_home_method(userdata, message, bus_home_method_release, error);
+}
+
+static int method_lock_all_homes(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        _cleanup_(operation_unrefp) Operation *o = NULL;
+        bool waiting = false;
+        Manager *m = userdata;
+        Iterator i;
+        Home *h;
+        int r;
+
+        assert(m);
+
+        /* This is called from logind when we are preparing for system suspend. We enqueue a lock operation
+         * for every suitable home we have and only when all of them completed we send a reply indicating
+         * completion. */
+
+        HASHMAP_FOREACH(h, m->homes_by_name, i) {
+
+                /* 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)
+                        continue;
+
+                if (!o) {
+                        o = operation_new(OPERATION_LOCK_ALL, message);
+                        if (!o)
+                                return -ENOMEM;
+                }
+
+                log_info("Automatically locking of home of user %s.", h->user_name);
+
+                r = home_schedule_operation(h, o, error);
+                if (r < 0)
+                        return r;
+
+                waiting = true;
+        }
+
+        if (waiting) /* At least one lock operation was enqeued, let's leave here without a reply: it will
+                        * be sent as soon as the last of the lock operations completed. */
+                return 1;
+
+        return sd_bus_reply_method_return(message, NULL);
+}
+
+const sd_bus_vtable manager_vtable[] = {
+        SD_BUS_VTABLE_START(0),
+
+        SD_BUS_PROPERTY("AutoLogin", "a(sso)", property_get_auto_login, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+
+        SD_BUS_METHOD("GetHomeByName", "s", "usussso", method_get_home_by_name, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("GetHomeByUID", "u", "ssussso", method_get_home_by_uid, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("GetUserRecordByName", "s", "sbo", method_get_user_record_by_name, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("GetUserRecordByUID", "u", "sbo", method_get_user_record_by_uid, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("ListHomes", NULL, "a(susussso)", method_list_homes, SD_BUS_VTABLE_UNPRIVILEGED),
+
+        /* The following methods directly execute an operation on a home, without ref-counting, queing or
+         * anything, and are accessible through homectl. */
+        SD_BUS_METHOD("ActivateHome", "ss", NULL, method_activate_home, SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("DeactivateHome", "s", NULL, method_deactivate_home, 0),
+        SD_BUS_METHOD("RegisterHome", "s", NULL, method_register_home, SD_BUS_VTABLE_UNPRIVILEGED),                                  /* Add JSON record to homed, but don't create actual $HOME */
+        SD_BUS_METHOD("UnregisterHome", "s", NULL, method_unregister_home, SD_BUS_VTABLE_UNPRIVILEGED),                              /* Remove JSON record from homed, but don't remove actual $HOME  */
+        SD_BUS_METHOD("CreateHome", "s", NULL, method_create_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),              /* Add JSON record, and create $HOME for it */
+        SD_BUS_METHOD("RealizeHome", "ss", NULL, method_realize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),           /* Create $HOME for already registered JSON entry */
+        SD_BUS_METHOD("RemoveHome", "s", NULL, method_remove_home, SD_BUS_VTABLE_UNPRIVILEGED),                                      /* Remove JSON record and remove $HOME */
+        SD_BUS_METHOD("FixateHome", "ss", NULL, method_fixate_home, SD_BUS_VTABLE_SENSITIVE),                                        /* Investigate $HOME and propagate contained JSON record into our database */
+        SD_BUS_METHOD("AuthenticateHome", "ss", NULL, method_authenticate_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE), /* Just check credentials */
+        SD_BUS_METHOD("UpdateHome", "s", NULL, method_update_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),              /* Update JSON record of existing user */
+        SD_BUS_METHOD("ResizeHome", "sts", NULL, method_resize_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("ChangePasswordHome", "sss", NULL, method_change_password_home, SD_BUS_VTABLE_UNPRIVILEGED|SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("LockHome", "s", NULL, method_lock_home, 0),                                                                   /* Prepare active home for system suspend: flush out passwords, suspend access */
+        SD_BUS_METHOD("UnlockHome", "ss", NULL, method_unlock_home, SD_BUS_VTABLE_SENSITIVE),                                        /* Make $HOME usable after system resume again */
+
+        /* The following methods implement ref-counted activation, and are what the PAM module calls (and
+         * what "homectl with" runs). In contrast to the methods above which fail if an operation is already
+         * being executed on a home directory, these ones will queue the request, and are thus more
+         * reliable. Moreover, they are a bit smarter: AcquireHome() will fixate, activate, unlock, or
+         * authenticate depending on the state of the home, so that the end result is always the same
+         * (i.e. the home directory is accessible), and we always validate the specified passwords. RefHome()
+         * will not authenticate, and thus only works if home is already active. */
+        SD_BUS_METHOD("AcquireHome", "ssb", "h", method_acquire_home, SD_BUS_VTABLE_SENSITIVE),
+        SD_BUS_METHOD("RefHome", "sb", "h", method_ref_home, 0),
+        SD_BUS_METHOD("ReleaseHome", "s", NULL, method_release_home, 0),
+
+        /* An operation that acts on all homes that allow it */
+        SD_BUS_METHOD("LockAllHomes", NULL, NULL, method_lock_all_homes, 0),
+
+        SD_BUS_VTABLE_END
+};
+
+static int on_deferred_auto_login(sd_event_source *s, void *userdata) {
+        Manager *m = userdata;
+        int r;
+
+        assert(m);
+
+        m->deferred_auto_login_event_source = sd_event_source_unref(m->deferred_auto_login_event_source);
+
+        r = sd_bus_emit_properties_changed(
+                        m->bus,
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "AutoLogin", NULL);
+        if (r < 0)
+                log_warning_errno(r, "Failed to send AutoLogin property change event, ignoring: %m");
+
+        return 0;
+}
+
+int bus_manager_emit_auto_login_changed(Manager *m) {
+        int r;
+        assert(m);
+
+        if (m->deferred_auto_login_event_source)
+                return 0;
+
+        if (!m->event)
+                return 0;
+
+        if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
+                return 0;
+
+        r = sd_event_add_defer(m->event, &m->deferred_auto_login_event_source, on_deferred_auto_login, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate auto login event source: %m");
+
+        r = sd_event_source_set_priority(m->deferred_auto_login_event_source, SD_EVENT_PRIORITY_IDLE+10);
+        if (r < 0)
+                log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
+
+        (void) sd_event_source_set_description(m->deferred_auto_login_event_source, "deferred-auto-login");
+        return 1;
+}
diff --git a/src/home/homed-manager-bus.h b/src/home/homed-manager-bus.h
new file mode 100644 (file)
index 0000000..40e1cc3
--- /dev/null
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+extern const sd_bus_vtable manager_vtable[];
diff --git a/src/home/homed-manager.c b/src/home/homed-manager.c
new file mode 100644 (file)
index 0000000..bbe227c
--- /dev/null
@@ -0,0 +1,1672 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <grp.h>
+#include <linux/fs.h>
+#include <linux/magic.h>
+#include <openssl/pem.h>
+#include <pwd.h>
+#include <sys/ioctl.h>
+#include <sys/quota.h>
+#include <sys/stat.h>
+
+#include "btrfs-util.h"
+#include "bus-common-errors.h"
+#include "bus-error.h"
+#include "bus-polkit.h"
+#include "clean-ipc.h"
+#include "conf-files.h"
+#include "device-util.h"
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "gpt.h"
+#include "home-util.h"
+#include "homed-home-bus.h"
+#include "homed-home.h"
+#include "homed-manager-bus.h"
+#include "homed-manager.h"
+#include "homed-varlink.h"
+#include "io-util.h"
+#include "mkdir.h"
+#include "process-util.h"
+#include "quota-util.h"
+#include "random-util.h"
+#include "socket-util.h"
+#include "stat-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "udev-util.h"
+#include "user-record-sign.h"
+#include "user-record-util.h"
+#include "user-record.h"
+#include "user-util.h"
+
+/* Where to look for private/public keys that are used to sign the user records. We are not using
+ * CONF_PATHS_NULSTR() here since we want to insert /var/lib/systemd/home/ in the middle. And we insert that
+ * since we want to auto-generate a persistent private/public key pair if we need to. */
+#define KEY_PATHS_NULSTR                        \
+        "/etc/systemd/home/\0"                  \
+        "/run/systemd/home/\0"                  \
+        "/var/lib/systemd/home/\0"              \
+        "/usr/local/lib/systemd/home/\0"        \
+        "/usr/lib/systemd/home/\0"
+
+static bool uid_is_home(uid_t uid) {
+        return uid >= HOME_UID_MIN && uid <= HOME_UID_MAX;
+}
+/* Takes a value generated randomly or by hashing and turns it into a UID in the right range */
+
+#define UID_CLAMP_INTO_HOME_RANGE(rnd) (((uid_t) (rnd) % (HOME_UID_MAX - HOME_UID_MIN + 1)) + HOME_UID_MIN)
+
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_uid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_name_hash_ops, char, string_hash_func, string_compare_func, Home, home_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_worker_pid_hash_ops, void, trivial_hash_func, trivial_compare_func, Home, home_free);
+DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(homes_by_sysfs_hash_ops, char, path_hash_func, path_compare, Home, home_free);
+
+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_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);
+
+static void manager_watch_home(Manager *m) {
+        struct statfs sfs;
+        int r;
+
+        assert(m);
+
+        m->inotify_event_source = sd_event_source_unref(m->inotify_event_source);
+        m->scan_slash_home = false;
+
+        if (statfs("/home/", &sfs) < 0) {
+                log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
+                               "Failed to statfs() /home/ directory, disabling automatic scanning.");
+                return;
+        }
+
+        if (is_network_fs(&sfs)) {
+                log_info("/home/ is a network file system, disabling automatic scanning.");
+                return;
+        }
+
+        if (is_fs_type(&sfs, AUTOFS_SUPER_MAGIC)) {
+                log_info("/home/ is on autofs, disabling automatic scanning.");
+                return;
+        }
+
+        m->scan_slash_home = true;
+
+        r = sd_event_add_inotify(m->event, &m->inotify_event_source, "/home/", IN_CREATE|IN_CLOSE_WRITE|IN_DELETE_SELF|IN_MOVE_SELF|IN_ONLYDIR|IN_MOVED_TO|IN_MOVED_FROM|IN_DELETE, on_home_inotify, m);
+        if (r < 0)
+                log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
+                               "Failed to create inotify watch on /home/, ignoring.");
+
+        (void) sd_event_source_set_description(m->inotify_event_source, "home-inotify");
+}
+
+static int on_home_inotify(sd_event_source *s, const struct inotify_event *event, void *userdata) {
+        Manager *m = userdata;
+        const char *e, *n;
+
+        assert(m);
+        assert(event);
+
+        if ((event->mask & (IN_Q_OVERFLOW|IN_MOVE_SELF|IN_DELETE_SELF|IN_IGNORED|IN_UNMOUNT)) != 0) {
+
+                if (FLAGS_SET(event->mask, IN_Q_OVERFLOW))
+                        log_debug("/home/ inotify queue overflow, rescanning.");
+                else if (FLAGS_SET(event->mask, IN_MOVE_SELF))
+                        log_info("/home/ moved or renamed, recreating watch and rescanning.");
+                else if (FLAGS_SET(event->mask, IN_DELETE_SELF))
+                        log_info("/home/ deleted, recreating watch and rescanning.");
+                else if (FLAGS_SET(event->mask, IN_UNMOUNT))
+                        log_info("/home/ unmounted, recreating watch and rescanning.");
+                else if (FLAGS_SET(event->mask, IN_IGNORED))
+                        log_info("/home/ watch invalidated, recreating watch and rescanning.");
+
+                manager_watch_home(m);
+                (void) manager_gc_images(m);
+                (void) manager_enumerate_images(m);
+                (void) bus_manager_emit_auto_login_changed(m);
+                return 0;
+        }
+
+        /* For the other inotify events, let's ignore all events for file names that don't match our
+         * expectations */
+        if (isempty(event->name))
+                return 0;
+        e = endswith(event->name, FLAGS_SET(event->mask, IN_ISDIR) ? ".homedir" : ".home");
+        if (!e)
+                return 0;
+
+        n = strndupa(event->name, e - event->name);
+        if (!suitable_user_name(n))
+                return 0;
+
+        if ((event->mask & (IN_CREATE|IN_CLOSE_WRITE|IN_MOVED_TO)) != 0) {
+                if (FLAGS_SET(event->mask, IN_CREATE))
+                        log_debug("/home/%s has been created, having a look.", event->name);
+                else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
+                        log_debug("/home/%s has been modified, having a look.", event->name);
+                else if (FLAGS_SET(event->mask, IN_MOVED_TO))
+                        log_debug("/home/%s has been moved in, having a look.", event->name);
+
+                (void) manager_assess_image(m, -1, "/home/", event->name);
+                (void) bus_manager_emit_auto_login_changed(m);
+        }
+
+        if ((event->mask & (IN_DELETE|IN_MOVED_FROM|IN_DELETE)) != 0) {
+                Home *h;
+
+                if (FLAGS_SET(event->mask, IN_DELETE))
+                        log_debug("/home/%s has been deleted, revalidating.", event->name);
+                else if (FLAGS_SET(event->mask, IN_CLOSE_WRITE))
+                        log_debug("/home/%s has been closed after writing, revalidating.", event->name);
+                else if (FLAGS_SET(event->mask, IN_MOVED_FROM))
+                        log_debug("/home/%s has been moved away, revalidating.", event->name);
+
+                h = hashmap_get(m->homes_by_name, n);
+                if (h) {
+                        manager_revalidate_image(m, h);
+                        (void) bus_manager_emit_auto_login_changed(m);
+                }
+        }
+
+        return 0;
+}
+
+int manager_new(Manager **ret) {
+        _cleanup_(manager_freep) Manager *m = NULL;
+        int r;
+
+        assert(ret);
+
+        m = new0(Manager, 1);
+        if (!m)
+                return -ENOMEM;
+
+        r = sd_event_default(&m->event);
+        if (r < 0)
+                return r;
+
+        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);
+        if (r < 0)
+                return r;
+
+        (void) sd_event_set_watchdog(m->event, true);
+
+        m->homes_by_uid = hashmap_new(&homes_by_uid_hash_ops);
+        if (!m->homes_by_uid)
+                return -ENOMEM;
+
+        m->homes_by_name = hashmap_new(&homes_by_name_hash_ops);
+        if (!m->homes_by_name)
+                return -ENOMEM;
+
+        m->homes_by_worker_pid = hashmap_new(&homes_by_worker_pid_hash_ops);
+        if (!m->homes_by_worker_pid)
+                return -ENOMEM;
+
+        m->homes_by_sysfs = hashmap_new(&homes_by_sysfs_hash_ops);
+        if (!m->homes_by_sysfs)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(m);
+        return 0;
+}
+
+Manager* manager_free(Manager *m) {
+        assert(m);
+
+        hashmap_free(m->homes_by_uid);
+        hashmap_free(m->homes_by_name);
+        hashmap_free(m->homes_by_worker_pid);
+        hashmap_free(m->homes_by_sysfs);
+
+        m->inotify_event_source = sd_event_source_unref(m->inotify_event_source);
+
+        bus_verify_polkit_async_registry_free(m->polkit_registry);
+
+        sd_bus_flush_close_unref(m->bus);
+        sd_event_unref(m->event);
+
+        m->notify_socket_event_source = sd_event_source_unref(m->notify_socket_event_source);
+        m->device_monitor = sd_device_monitor_unref(m->device_monitor);
+
+        m->deferred_rescan_event_source = sd_event_source_unref(m->deferred_rescan_event_source);
+        m->deferred_gc_event_source = sd_event_source_unref(m->deferred_gc_event_source);
+        m->deferred_auto_login_event_source = sd_event_source_unref(m->deferred_auto_login_event_source);
+
+        if (m->private_key)
+                EVP_PKEY_free(m->private_key);
+
+        hashmap_free(m->public_keys);
+
+        varlink_server_unref(m->varlink_server);
+
+        return mfree(m);
+}
+
+int manager_verify_user_record(Manager *m, UserRecord *hr) {
+        EVP_PKEY *pkey;
+        Iterator i;
+        int r;
+
+        assert(m);
+        assert(hr);
+
+        if (!m->private_key && hashmap_isempty(m->public_keys)) {
+                r = user_record_has_signature(hr);
+                if (r < 0)
+                        return r;
+
+                return r ? -ENOKEY : USER_RECORD_UNSIGNED;
+        }
+
+        /* Is it our own? */
+        if (m->private_key) {
+                r = user_record_verify(hr, m->private_key);
+                switch (r) {
+
+                case USER_RECORD_FOREIGN:
+                        /* This record is not signed by this key, but let's see below */
+                        break;
+
+                case USER_RECORD_SIGNED:               /* Signed by us, but also by others, let's propagate that */
+                case USER_RECORD_SIGNED_EXCLUSIVE:     /* Signed by us, and nothing else, ditto */
+                case USER_RECORD_UNSIGNED:             /* Not signed at all, ditto  */
+                default:
+                        return r;
+                }
+        }
+
+        HASHMAP_FOREACH(pkey, m->public_keys, i) {
+                r = user_record_verify(hr, pkey);
+                switch (r) {
+
+                case USER_RECORD_FOREIGN:
+                        /* This record is not signed by this key, but let's see our other keys */
+                        break;
+
+                case USER_RECORD_SIGNED:            /* It's signed by this key we are happy with, but which is not our own. */
+                case USER_RECORD_SIGNED_EXCLUSIVE:
+                        return USER_RECORD_FOREIGN;
+
+                case USER_RECORD_UNSIGNED: /* It's not signed at all */
+                default:
+                        return r;
+                }
+        }
+
+        return -ENOKEY;
+}
+
+static int manager_add_home_by_record(
+                Manager *m,
+                const char *name,
+                int dir_fd,
+                const char *fname) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        unsigned line, column;
+        int r, is_signed;
+        Home *h;
+
+        assert(m);
+        assert(name);
+        assert(fname);
+
+        r = json_parse_file_at(NULL, dir_fd, fname, JSON_PARSE_SENSITIVE, &v, &line, &column);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse identity record at %s:%u%u: %m", fname, line, column);
+
+        hr = user_record_new();
+        if (!hr)
+                return log_oom();
+
+        r = user_record_load(hr, v, USER_RECORD_LOAD_REFUSE_SECRET);
+        if (r < 0)
+                return r;
+
+        if (!streq_ptr(hr->user_name, name))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Identity's user name %s does not match file name %s, refusing.", hr->user_name, name);
+
+        is_signed = manager_verify_user_record(m, hr);
+        switch (is_signed) {
+
+        case -ENOKEY:
+                return log_warning_errno(is_signed, "User record %s is not signed by any accepted key, ignoring.", fname);
+        case USER_RECORD_UNSIGNED:
+                return log_warning_errno(SYNTHETIC_ERRNO(EPERM), "User record %s is not signed at all, ignoring.", fname);
+        case USER_RECORD_SIGNED:
+                log_info("User record %s is signed by us (and others), accepting.", fname);
+                break;
+        case USER_RECORD_SIGNED_EXCLUSIVE:
+                log_info("User record %s is signed only by us, accepting.", fname);
+                break;
+        case USER_RECORD_FOREIGN:
+                log_info("User record %s is signed by registered key from others, accepting.", fname);
+                break;
+        default:
+                assert(is_signed < 0);
+                return log_error_errno(is_signed, "Failed to verify signature of user record in %s: %m", fname);
+        }
+
+        h = hashmap_get(m->homes_by_name, name);
+        if (h) {
+                r = home_set_record(h, hr);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to update home record for %s: %m", name);
+
+                /* If we acquired a record now for a previously unallocated entry, then reset the state. This
+                 * makes sure home_get_state() will check for the availability of the image file dynamically
+                 * in order to detect to distuingish HOME_INACTIVE and HOME_ABSENT. */
+                if (h->state == HOME_UNFIXATED)
+                        h->state = _HOME_STATE_INVALID;
+        } else {
+                r = home_new(m, hr, NULL, &h);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate new home object: %m");
+
+                log_info("Added registered home for user %s.", hr->user_name);
+        }
+
+        /* Only entries we exclusively signed are writable to us, hence remember the result */
+        h->signed_locally = is_signed == USER_RECORD_SIGNED_EXCLUSIVE;
+
+        return 1;
+}
+
+static int manager_enumerate_records(Manager *m) {
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+
+        assert(m);
+
+        d = opendir("/var/lib/systemd/home/");
+        if (!d)
+                return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
+                                      "Failed to open /var/lib/systemd/home/: %m");
+
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read record directory: %m")) {
+                _cleanup_free_ char *n = NULL;
+                const char *e;
+
+                if (!dirent_is_file(de))
+                        continue;
+
+                e = endswith(de->d_name, ".identity");
+                if (!e)
+                        continue;
+
+                n = strndup(de->d_name, e - de->d_name);
+                if (!n)
+                        return log_oom();
+
+                if (!suitable_user_name(n))
+                        continue;
+
+                (void) manager_add_home_by_record(m, n, dirfd(d), de->d_name);
+        }
+
+        return 0;
+}
+
+static int search_quota(uid_t uid, const char *exclude_quota_path) {
+        struct stat exclude_st = {};
+        dev_t previous_devno = 0;
+        const char *where;
+        int r;
+
+        /* Checks whether the specified UID owns any files on the files system, but ignore any file system
+         * backing the specified file. The file is used when operating on home directories, where it's OK if
+         * the UID of them already owns files. */
+
+        if (exclude_quota_path && stat(exclude_quota_path, &exclude_st) < 0) {
+                if (errno != ENOENT)
+                        return log_warning_errno(errno, "Failed to stat %s, ignoring: %m", exclude_quota_path);
+        }
+
+        /* Check a few usual suspects where regular users might own files. Note that this is by no means
+         * comprehensive, but should cover most cases. Note that in an ideal world every user would be
+         * registered in NSS and avoid our own UID range, but for all other cases, it's a good idea to be
+         * paranoid and check quota if we can. */
+        FOREACH_STRING(where, "/home/", "/tmp/", "/var/", "/var/mail/", "/var/tmp/", "/var/spool/") {
+                struct dqblk req;
+                struct stat st;
+
+                if (stat(where, &st) < 0) {
+                        log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
+                                       "Failed to stat %s, ignoring: %m", where);
+                        continue;
+                }
+
+                if (major(st.st_dev) == 0) {
+                        log_debug("Directory %s is not on a real block device, not checking quota for UID use.", where);
+                        continue;
+                }
+
+                if (st.st_dev == exclude_st.st_dev) { /* If an exclude path is specified, then ignore quota
+                                                       * reported on the same block device as that path. */
+                        log_debug("Directory %s is where the home directory is located, not checking quota for UID use.", where);
+                        continue;
+                }
+
+                if (st.st_dev == previous_devno) { /* Does this directory have the same devno as the previous
+                                                    * one we tested? If so, there's no point in testing this
+                                                    * again. */
+                        log_debug("Directory %s is on same device as previous tested directory, not checking quota for UID use a second time.", where);
+                        continue;
+                }
+
+                previous_devno = st.st_dev;
+
+                r = quotactl_devno(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), st.st_dev, uid, &req);
+                if (r < 0) {
+                        if (ERRNO_IS_NOT_SUPPORTED(r))
+                                log_debug_errno(r, "No UID quota support on %s, ignoring.", where);
+                        else
+                                log_warning_errno(r, "Failed to query quota on %s, ignoring.", where);
+
+                        continue;
+                }
+
+                if ((FLAGS_SET(req.dqb_valid, QIF_SPACE) && req.dqb_curspace > 0) ||
+                    (FLAGS_SET(req.dqb_valid, QIF_INODES) && req.dqb_curinodes > 0)) {
+                        log_debug_errno(errno, "Quota reports UID " UID_FMT " occupies disk space on %s.", uid, where);
+                        return 1;
+                }
+        }
+
+        return 0;
+}
+
+static int manager_acquire_uid(
+                Manager *m,
+                uid_t start_uid,
+                const char *user_name,
+                const char *exclude_quota_path,
+                uid_t *ret) {
+
+        static const uint8_t hash_key[] = {
+                0xa3, 0xb8, 0x82, 0x69, 0x9a, 0x71, 0xf7, 0xa9,
+                0xe0, 0x7c, 0xf6, 0xf1, 0x21, 0x69, 0xd2, 0x1e
+        };
+
+        enum {
+                PHASE_SUGGESTED,
+                PHASE_HASHED,
+                PHASE_RANDOM
+        } phase = PHASE_SUGGESTED;
+
+        unsigned n_tries = 100;
+        int r;
+
+        assert(m);
+        assert(ret);
+
+        for (;;) {
+                struct passwd *pw;
+                struct group *gr;
+                uid_t candidate;
+                Home *other;
+
+                if (--n_tries <= 0)
+                        return -EBUSY;
+
+                switch (phase) {
+
+                case PHASE_SUGGESTED:
+                        phase = PHASE_HASHED;
+
+                        if (!uid_is_home(start_uid))
+                                continue;
+
+                        candidate = start_uid;
+                        break;
+
+                case PHASE_HASHED:
+                        phase = PHASE_RANDOM;
+
+                        if (!user_name)
+                                continue;
+
+                        candidate = UID_CLAMP_INTO_HOME_RANGE(siphash24(user_name, strlen(user_name), hash_key));
+                        break;
+
+                case PHASE_RANDOM:
+                        random_bytes(&candidate, sizeof(candidate));
+                        candidate = UID_CLAMP_INTO_HOME_RANGE(candidate);
+                        break;
+
+                default:
+                        assert_not_reached("unknown phase");
+                }
+
+                other = hashmap_get(m->homes_by_uid, UID_TO_PTR(candidate));
+                if (other) {
+                        log_debug("Candidate UID " UID_FMT " already used by another home directory (%s), let's try another.", candidate, other->user_name);
+                        continue;
+                }
+
+                pw = getpwuid(candidate);
+                if (pw) {
+                        log_debug("Candidate UID " UID_FMT " already registered by another user in NSS (%s), let's try another.", candidate, pw->pw_name);
+                        continue;
+                }
+
+                gr = getgrgid((gid_t) candidate);
+                if (gr) {
+                        log_debug("Candidate UID " UID_FMT " already registered by another group in NSS (%s), let's try another.", candidate, gr->gr_name);
+                        continue;
+                }
+
+                r = search_ipc(candidate, (gid_t) candidate);
+                if (r < 0)
+                        continue;
+                if (r > 0) {
+                        log_debug_errno(r, "Candidate UID " UID_FMT " already owns IPC objects, let's try another: %m", candidate);
+                        continue;
+                }
+
+                r = search_quota(candidate, exclude_quota_path);
+                if (r != 0)
+                        continue;
+
+                *ret = candidate;
+                return 0;
+        }
+}
+
+static int manager_add_home_by_image(
+                Manager *m,
+                const char *user_name,
+                const char *realm,
+                const char *image_path,
+                const char *sysfs,
+                UserStorage storage,
+                uid_t start_uid) {
+
+        _cleanup_(user_record_unrefp) UserRecord *hr = NULL;
+        uid_t uid;
+        Home *h;
+        int r;
+
+        assert(m);
+
+        assert(m);
+        assert(user_name);
+        assert(image_path);
+        assert(storage >= 0);
+        assert(storage < _USER_STORAGE_MAX);
+
+        h = hashmap_get(m->homes_by_name, user_name);
+        if (h) {
+                bool same;
+
+                if (h->state != HOME_UNFIXATED) {
+                        log_debug("Found an image for user %s which already has a record, skipping.", user_name);
+                        return 0; /* ignore images that synthesize a user we already have a record for */
+                }
+
+                same = user_record_storage(h->record) == storage;
+                if (same) {
+                        if (h->sysfs && sysfs)
+                                same = path_equal(h->sysfs, sysfs);
+                        else if (!!h->sysfs != !!sysfs)
+                                same = false;
+                        else {
+                                const char *p;
+
+                                p = user_record_image_path(h->record);
+                                same = p && path_equal(p, image_path);
+                        }
+                }
+
+                if (!same) {
+                        log_debug("Found a multiple images for a user '%s', ignoring image '%s'.", user_name, image_path);
+                        return 0;
+                }
+        } else {
+                /* Check NSS, in case there's another user or group by this name */
+                if (getpwnam(user_name) || getgrnam(user_name)) {
+                        log_debug("Found an existing user or group by name '%s', ignoring image '%s'.", user_name, image_path);
+                        return 0;
+                }
+        }
+
+        if (h && uid_is_valid(h->uid))
+                uid = h->uid;
+        else {
+                r = manager_acquire_uid(m, start_uid, user_name, IN_SET(storage, USER_SUBVOLUME, USER_DIRECTORY, USER_FSCRYPT) ? image_path : NULL, &uid);
+                if (r < 0)
+                        return log_warning_errno(r, "Failed to acquire unused UID for %s: %m", user_name);
+        }
+
+        hr = user_record_new();
+        if (!hr)
+                return log_oom();
+
+        r = user_record_synthesize(hr, user_name, realm, image_path, storage, uid, (gid_t) uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to synthesize home record for %s (image %s): %m", user_name, image_path);
+
+        if (h) {
+                r = home_set_record(h, hr);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to update home record for %s: %m", user_name);
+        } else {
+                r = home_new(m, hr, sysfs, &h);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate new home object: %m");
+
+                h->state = HOME_UNFIXATED;
+
+                log_info("Discovered new home for user %s through image %s.", user_name, image_path);
+        }
+
+        return 1;
+}
+
+int manager_augment_record_with_uid(
+                Manager *m,
+                UserRecord *hr) {
+
+        const char *exclude_quota_path = NULL;
+        uid_t start_uid = UID_INVALID, uid;
+        int r;
+
+        assert(m);
+        assert(hr);
+
+        if (uid_is_valid(hr->uid))
+                return 0;
+
+        if (IN_SET(hr->storage, USER_CLASSIC, USER_SUBVOLUME, USER_DIRECTORY, USER_FSCRYPT)) {
+                const char * ip;
+
+                ip = user_record_image_path(hr);
+                if (ip) {
+                        struct stat st;
+
+                        if (stat(ip, &st) < 0) {
+                                if (errno != ENOENT)
+                                        log_warning_errno(errno, "Failed to stat(%s): %m", ip);
+                        }  else if (uid_is_home(st.st_uid)) {
+                                start_uid = st.st_uid;
+                                exclude_quota_path = ip;
+                        }
+                }
+        }
+
+        r = manager_acquire_uid(m, start_uid, hr->user_name, exclude_quota_path, &uid);
+        if (r < 0)
+                return r;
+
+        log_debug("Acquired new UID " UID_FMT " for %s.", uid, hr->user_name);
+
+        r = user_record_add_binding(
+                        hr,
+                        _USER_STORAGE_INVALID,
+                        NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        NULL,
+                        NULL,
+                        UINT64_MAX,
+                        NULL,
+                        NULL,
+                        uid,
+                        (gid_t) uid);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+static int manager_assess_image(
+                Manager *m,
+                int dir_fd,
+                const char *dir_path,
+                const char *dentry_name) {
+
+        char *luks_suffix, *directory_suffix;
+        _cleanup_free_ char *path = NULL;
+        struct stat st;
+        int r;
+
+        assert(m);
+        assert(dir_path);
+        assert(dentry_name);
+
+        luks_suffix = endswith(dentry_name, ".home");
+        if (luks_suffix)
+                directory_suffix = NULL;
+        else
+                directory_suffix = endswith(dentry_name, ".homedir");
+
+        /* Early filter out: by name */
+        if (!luks_suffix && !directory_suffix)
+                return 0;
+
+        path = path_join(dir_path, dentry_name);
+        if (!path)
+                return log_oom();
+
+        /* Follow symlinks here, to allow people to link in stuff to make them available locally. */
+        if (dir_fd >= 0)
+                r = fstatat(dir_fd, dentry_name, &st, 0);
+        else
+                r = stat(path, &st);
+        if (r < 0)
+                return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
+                                      "Failed to stat directory entry '%s', ignoring: %m", dentry_name);
+
+        if (S_ISREG(st.st_mode)) {
+                _cleanup_free_ char *n = NULL, *user_name = NULL, *realm = NULL;
+
+                if (!luks_suffix)
+                        return 0;
+
+                n = strndup(dentry_name, luks_suffix - dentry_name);
+                if (!n)
+                        return log_oom();
+
+                r = split_user_name_realm(n, &user_name, &realm);
+                if (r == -EINVAL) /* Not the right format: ignore */
+                        return 0;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to split image name into user name/realm: %m");
+
+                return manager_add_home_by_image(m, user_name, realm, path, NULL, USER_LUKS, UID_INVALID);
+        }
+
+        if (S_ISDIR(st.st_mode)) {
+                _cleanup_free_ char *n = NULL, *user_name = NULL, *realm = NULL;
+                _cleanup_close_ int fd = -1;
+                UserStorage storage;
+
+                if (!directory_suffix)
+                        return 0;
+
+                n = strndup(dentry_name, directory_suffix - dentry_name);
+                if (!n)
+                        return log_oom();
+
+                r = split_user_name_realm(n, &user_name, &realm);
+                if (r == -EINVAL) /* Not the right format: ignore */
+                        return 0;
+                if (r < 0)
+                        return log_error_errno(r, "Failed to split image name into user name/realm: %m");
+
+                if (dir_fd >= 0)
+                        fd = openat(dir_fd, dentry_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+                else
+                        fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC);
+                if (fd < 0)
+                        return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno,
+                                              "Failed to open directory '%s', ignoring: %m", path);
+
+                if (fstat(fd, &st) < 0)
+                        return log_warning_errno(errno, "Failed to fstat() %s, ignoring: %m", path);
+
+                assert(S_ISDIR(st.st_mode)); /* Must hold, we used O_DIRECTORY above */
+
+                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);
+                if (r > 0)
+                        storage = USER_SUBVOLUME;
+                else {
+                        struct fscrypt_policy policy;
+
+                        if (ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
+
+                                if (errno == ENODATA)
+                                        log_debug_errno(errno, "Determined %s is not fscrypt encrypted.", path);
+                                else if (ERRNO_IS_NOT_SUPPORTED(errno))
+                                        log_debug_errno(errno, "Determined %s is not fscrypt encrypted because kernel or file system don't support it.", path);
+                                else
+                                        log_debug_errno(errno, "FS_IOC_GET_ENCRYPTION_POLICY failed with unexpected error code on %s, ignoring: %m", path);
+
+                                storage = USER_DIRECTORY;
+                        } else
+                                storage = USER_FSCRYPT;
+                }
+
+                return manager_add_home_by_image(m, user_name, realm, path, NULL, storage, st.st_uid);
+        }
+
+        return 0;
+}
+
+int manager_enumerate_images(Manager *m) {
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+
+        assert(m);
+
+        if (!m->scan_slash_home)
+                return 0;
+
+        d = opendir("/home/");
+        if (!d)
+                return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno,
+                                      "Failed to open /home/: %m");
+
+        FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read /home/ directory: %m"))
+                (void) manager_assess_image(m, dirfd(d), "/home", de->d_name);
+
+        return 0;
+}
+
+static int manager_connect_bus(Manager *m) {
+        int r;
+
+        assert(m);
+        assert(!m->bus);
+
+        r = sd_bus_default_system(&m->bus);
+        if (r < 0)
+                return log_error_errno(r, "Failed to connect to system bus: %m");
+
+        r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/home1", "org.freedesktop.home1.Manager", manager_vtable, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add manager object vtable: %m");
+
+        r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/home1/home", "org.freedesktop.home1.Home", home_vtable, bus_home_object_find, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add image object vtable: %m");
+
+        r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/home1/home", bus_home_node_enumerator, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add image enumerator: %m");
+
+        r = sd_bus_add_object_manager(m->bus, NULL, "/org/freedesktop/home1/home");
+        if (r < 0)
+                return log_error_errno(r, "Failed to add object manager: %m");
+
+        r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.home1", 0, NULL, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to request name: %m");
+
+        r = sd_bus_attach_event(m->bus, m->event, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach bus to event loop: %m");
+
+        (void) sd_bus_set_exit_on_disconnect(m->bus, true);
+
+        return 0;
+}
+
+static int manager_bind_varlink(Manager *m) {
+        int r;
+
+        assert(m);
+        assert(!m->varlink_server);
+
+        r = varlink_server_new(&m->varlink_server, VARLINK_SERVER_ACCOUNT_UID);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate varlink server object: %m");
+
+        varlink_server_set_userdata(m->varlink_server, m);
+
+        r = varlink_server_bind_method_many(
+                        m->varlink_server,
+                        "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 register varlink methods: %m");
+
+        (void) mkdir_p("/run/systemd/userdb", 0755);
+
+        r = varlink_server_listen_address(m->varlink_server, "/run/systemd/userdb/io.systemd.Home", 0666);
+        if (r < 0)
+                return log_error_errno(r, "Failed to bind to varlink socket: %m");
+
+        r = varlink_server_attach_event(m->varlink_server, m->event, SD_EVENT_PRIORITY_NORMAL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach varlink connection to event loop: %m");
+
+        return 0;
+}
+
+static ssize_t read_datagram(int fd, struct ucred *ret_sender, void **ret) {
+        _cleanup_free_ void *buffer = NULL;
+        ssize_t n, m;
+
+        assert(fd >= 0);
+        assert(ret_sender);
+        assert(ret);
+
+        n = next_datagram_size_fd(fd);
+        if (n < 0)
+                return n;
+
+        buffer = malloc(n + 2);
+        if (!buffer)
+                return -ENOMEM;
+
+        if (ret_sender) {
+                union {
+                        struct cmsghdr cmsghdr;
+                        uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
+                } control;
+                bool found_ucred = false;
+                struct cmsghdr *cmsg;
+                struct msghdr mh;
+                struct iovec iov;
+
+                /* Pass one extra byte, as a size check */
+                iov = IOVEC_MAKE(buffer, n + 1);
+
+                mh = (struct msghdr) {
+                        .msg_iov = &iov,
+                        .msg_iovlen = 1,
+                        .msg_control = &control,
+                        .msg_controllen = sizeof(control),
+                };
+
+                m = recvmsg(fd, &mh, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+                if (m < 0)
+                        return -errno;
+
+                cmsg_close_all(&mh);
+
+                /* Ensure the size matches what we determined before */
+                if (m != n)
+                        return -EMSGSIZE;
+
+                CMSG_FOREACH(cmsg, &mh)
+                        if (cmsg->cmsg_level == SOL_SOCKET &&
+                            cmsg->cmsg_type == SCM_CREDENTIALS &&
+                            cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred))) {
+
+                                memcpy(ret_sender, CMSG_DATA(cmsg), sizeof(struct ucred));
+                                found_ucred = true;
+                        }
+
+                if (!found_ucred)
+                        *ret_sender = (struct ucred) {
+                                .pid = 0,
+                                .uid = UID_INVALID,
+                                .gid = GID_INVALID,
+                        };
+        } else {
+                m = recv(fd, buffer, n + 1, MSG_DONTWAIT);
+                if (m < 0)
+                        return -errno;
+
+                /* Ensure the size matches what we determined before */
+                if (m != n)
+                        return -EMSGSIZE;
+        }
+
+        /* For safety reasons: let's always NUL terminate.  */
+        ((char*) buffer)[n] = 0;
+        *ret = TAKE_PTR(buffer);
+
+        return 0;
+}
+
+static int on_notify_socket(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+        _cleanup_strv_free_ char **l = NULL;
+        _cleanup_free_ void *datagram = NULL;
+        struct ucred sender;
+        Manager *m = userdata;
+        ssize_t n;
+        Home *h;
+
+        assert(s);
+        assert(m);
+
+        n = read_datagram(fd, &sender, &datagram);
+        if (IN_SET(n, -EAGAIN, -EINTR))
+                return 0;
+        if (n < 0)
+                return log_error_errno(n, "Failed to read notify datagram: %m");
+
+        if (sender.pid <= 0) {
+                log_warning("Received notify datagram without valid sender PID, ignoring.");
+                return 0;
+        }
+
+        h = hashmap_get(m->homes_by_worker_pid, PID_TO_PTR(sender.pid));
+        if (!h) {
+                log_warning("Recieved notify datagram of unknown process, ignoring.");
+                return 0;
+        }
+
+        l = strv_split(datagram, "\n");
+        if (!l)
+                return log_oom();
+
+        home_process_notify(h, l);
+        return 0;
+}
+
+static int manager_listen_notify(Manager *m) {
+        _cleanup_close_ int fd = -1;
+        union sockaddr_union sa;
+        int r;
+
+        assert(m);
+        assert(!m->notify_socket_event_source);
+
+        fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+        if (fd < 0)
+                return log_error_errno(errno, "Failed to create listening socket: %m");
+
+        r = sockaddr_un_set_path(&sa.un, "/run/systemd/home/notify");
+        if (r < 0)
+                return log_error_errno(r, "Failed to set AF_UNIX socket path: %m");
+
+        (void) mkdir_parents(sa.un.sun_path, 0755);
+        (void) sockaddr_un_unlink(&sa.un);
+
+        if (bind(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
+                return log_error_errno(errno, "Failed to bind to socket: %m");
+
+        r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
+        if (r < 0)
+                return r;
+
+        r = sd_event_add_io(m->event, &m->notify_socket_event_source, fd, EPOLLIN, on_notify_socket, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate event source for notify socket: %m");
+
+        (void) sd_event_source_set_description(m->notify_socket_event_source, "notify-socket");
+
+        /* Make sure we process sd_notify() before SIGCHLD for any worker, so that we always know the error
+         * number of a client before it exits. */
+        r = sd_event_source_set_priority(m->notify_socket_event_source, SD_EVENT_PRIORITY_NORMAL - 5);
+        if (r < 0)
+                return log_error_errno(r, "Failed to alter priority of NOTIFY_SOCKET event source: %m");
+
+        r = sd_event_source_set_io_fd_own(m->notify_socket_event_source, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to pass ownership of notify socket: %m");
+
+        return TAKE_FD(fd);
+}
+
+static int manager_add_device(Manager *m, sd_device *d) {
+        _cleanup_free_ char *user_name = NULL, *realm = NULL, *node = NULL;
+        const char *tabletype, *parttype, *partname, *partuuid, *sysfs;
+        sd_id128_t id;
+        int r;
+
+        assert(m);
+        assert(d);
+
+        r = sd_device_get_syspath(d, &sysfs);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire sysfs path of device: %m");
+
+        r = sd_device_get_property_value(d, "ID_PART_TABLE_TYPE", &tabletype);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire ID_PART_TABLE_TYPE device property, ignoring: %m");
+
+        if (!streq(tabletype, "gpt")) {
+                log_debug("Found partition (%s) on non-GPT table, ignoring.", sysfs);
+                return 0;
+        }
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &parttype);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire ID_PART_ENTRY_TYPE device property, ignoring: %m");
+        r = sd_id128_from_string(parttype, &id);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to parse ID_PART_ENTRY_TYPE field '%s', ignoring: %m", parttype);
+        if (!sd_id128_equal(id, GPT_USER_HOME)) {
+                log_debug("Found partition (%s) we don't care about, ignoring.", sysfs);
+                return 0;
+        }
+
+        r = sd_device_get_property_value(d, "ID_PART_ENTRY_NAME", &partname);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to acquire ID_PART_ENTRY_NAME device property, ignoring: %m");
+
+        r = split_user_name_realm(partname, &user_name, &realm);
+        if (r == -EINVAL)
+                return log_warning_errno(r, "Found partition with correct partition type but a non-parsable partition name '%s', ignoring.", partname);
+        if (r < 0)
+                return log_error_errno(r, "Failed to validate partition name '%s': %m", partname);
+
+        r = sd_device_get_property_value(d, "ID_FS_UUID", &partuuid);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to acquire ID_FS_UUID device property, ignoring: %m");
+
+        r = sd_id128_from_string(partuuid, &id);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to parse ID_FS_UUID field '%s', ignoring: %m", partuuid);
+
+        if (asprintf(&node, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(id)) < 0)
+                return log_oom();
+
+        return manager_add_home_by_image(m, user_name, realm, node, sysfs, USER_LUKS, UID_INVALID);
+}
+
+static int manager_on_device(sd_device_monitor *monitor, sd_device *d, void *userdata) {
+        Manager *m = userdata;
+        int r;
+
+        assert(m);
+        assert(d);
+
+        if (device_for_action(d, DEVICE_ACTION_REMOVE)) {
+                const char *sysfs;
+                Home *h;
+
+                r = sd_device_get_syspath(d, &sysfs);
+                if (r < 0) {
+                        log_warning_errno(r, "Failed to acquire sysfs path from device: %m");
+                        return 0;
+                }
+
+                log_info("block device %s has been removed.", sysfs);
+
+                /* Let's see if we previously synthesized a home record from this device, if so, let's just
+                 * revalidate that. Otherwise let's revalidate them all, but asynchronously. */
+                h = hashmap_get(m->homes_by_sysfs, sysfs);
+                if (h)
+                        manager_revalidate_image(m, h);
+                else
+                        manager_enqueue_gc(m, NULL);
+        } else
+                (void) manager_add_device(m, d);
+
+        (void) bus_manager_emit_auto_login_changed(m);
+        return 0;
+}
+
+static int manager_watch_devices(Manager *m) {
+        int r;
+
+        assert(m);
+        assert(!m->device_monitor);
+
+        r = sd_device_monitor_new(&m->device_monitor);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate device monitor: %m");
+
+        r = sd_device_monitor_filter_add_match_subsystem_devtype(m->device_monitor, "block", NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to configure device monitor match: %m");
+
+        r = sd_device_monitor_attach_event(m->device_monitor, m->event);
+        if (r < 0)
+                return log_error_errno(r, "Failed to attach device monitor to event loop: %m");
+
+        r = sd_device_monitor_start(m->device_monitor, manager_on_device, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to start device monitor: %m");
+
+        return 0;
+}
+
+static int manager_enumerate_devices(Manager *m) {
+        _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+        sd_device *d;
+        int r;
+
+        assert(m);
+
+        r = sd_device_enumerator_new(&e);
+        if (r < 0)
+                return r;
+
+        r = sd_device_enumerator_add_match_subsystem(e, "block", true);
+        if (r < 0)
+                return r;
+
+        FOREACH_DEVICE(e, d)
+                (void) manager_add_device(m, d);
+
+        return 0;
+}
+
+static int manager_load_key_pair(Manager *m) {
+        _cleanup_(fclosep) FILE *f = NULL;
+        struct stat st;
+        int r;
+
+        assert(m);
+
+        if (m->private_key) {
+                EVP_PKEY_free(m->private_key);
+                m->private_key = NULL;
+        }
+
+        r = search_and_fopen_nulstr("local.private", "re", NULL, KEY_PATHS_NULSTR, &f);
+        if (r == -ENOENT)
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to read private key file: %m");
+
+        if (fstat(fileno(f), &st) < 0)
+                return log_error_errno(errno, "Failed to stat private key file: %m");
+
+        r = stat_verify_regular(&st);
+        if (r < 0)
+                return log_error_errno(r, "Private key file is not regular: %m");
+
+        if (st.st_uid != 0 || (st.st_mode & 0077) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Private key file is readable by more than the root user");
+
+        m->private_key = PEM_read_PrivateKey(f, NULL, NULL, NULL);
+        if (!m->private_key)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to load private key pair");
+
+        log_info("Successfully loaded private key pair.");
+
+        return 1;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_PKEY_CTX*, EVP_PKEY_CTX_free);
+
+static int manager_generate_key_pair(Manager *m) {
+        _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = NULL;
+        _cleanup_(unlink_and_freep) char *temp_public = NULL, *temp_private = NULL;
+        _cleanup_fclose_ FILE *fpublic = NULL, *fprivate = NULL;
+        int r;
+
+        if (m->private_key) {
+                EVP_PKEY_free(m->private_key);
+                m->private_key = NULL;
+        }
+
+        ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
+        if (!ctx)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate Ed25519 key generation context.");
+
+        if (EVP_PKEY_keygen_init(ctx) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize Ed25519 key generation context.");
+
+        log_info("Generating key pair for signing local user identity records.");
+
+        if (EVP_PKEY_keygen(ctx, &m->private_key) <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate Ed25519 key pair");
+
+        log_info("Successfully created Ed25519 key pair.");
+
+        (void) mkdir_p("/var/lib/systemd/home", 0755);
+
+        /* 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 ot 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.");
+
+        r = fflush_and_check(fpublic);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write private key: %m");
+
+        fpublic = safe_fclose(fpublic);
+
+        /* 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 ot 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.");
+
+        r = fflush_and_check(fprivate);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write private key: %m");
+
+        fprivate = safe_fclose(fprivate);
+
+        /* Both are written now, move them into place */
+
+        if (rename(temp_public, "/var/lib/systemd/home/local.public") < 0)
+                return log_error_errno(errno, "Failed to move public key file into place: %m");
+        temp_public = mfree(temp_public);
+
+        if (rename(temp_private, "/var/lib/systemd/home/local.private") < 0) {
+                (void) unlink_noerrno("/var/lib/systemd/home/local.public"); /* try to remove the file we already created */
+                return log_error_errno(errno, "Failed to move privtate key file into place: %m");
+        }
+        temp_private = mfree(temp_private);
+
+        return 1;
+}
+
+int manager_acquire_key_pair(Manager *m) {
+        int r;
+
+        assert(m);
+
+        /* Already there? */
+        if (m->private_key)
+                return 1;
+
+        /* First try to load key off disk */
+        r = manager_load_key_pair(m);
+        if (r != 0)
+                return r;
+
+        /* Didn't work, generate a new one */
+        return manager_generate_key_pair(m);
+}
+
+int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error) {
+        int r;
+
+        assert(m);
+        assert(u);
+        assert(ret);
+
+        r = manager_acquire_key_pair(m);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_KEY, "Can't sign without local key.");
+
+        return user_record_sign(u, m->private_key, ret);
+}
+
+DEFINE_PRIVATE_HASH_OPS_FULL(public_key_hash_ops, char, string_hash_func, string_compare_func, free, EVP_PKEY, EVP_PKEY_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_PKEY*, EVP_PKEY_free);
+
+static int manager_load_public_key_one(Manager *m, const char *path) {
+        _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
+        _cleanup_free_ char *fn = NULL;
+        struct stat st;
+        int r;
+
+        assert(m);
+
+        if (streq(basename(path), "local.public")) /* we already loaded the private key, which includes the public one */
+                return 0;
+
+        f = fopen(path, "re");
+        if (!f) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return log_error_errno(errno, "Failed to open public key %s: %m", path);
+        }
+
+        if (fstat(fileno(f), &st) < 0)
+                return log_error_errno(errno, "Failed to stat public key %s: %m", path);
+
+        r = stat_verify_regular(&st);
+        if (r < 0)
+                return log_error_errno(r, "Public key file %s is not a regular file: %m", path);
+
+        if (st.st_uid != 0 || (st.st_mode & 0022) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Public key file %s is writable by more than the root user, refusing.", path);
+
+        r = hashmap_ensure_allocated(&m->public_keys, &public_key_hash_ops);
+        if (r < 0)
+                return log_oom();
+
+        pkey = PEM_read_PUBKEY(f, &pkey, NULL, NULL);
+        if (!pkey)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse public key file %s.", path);
+
+        fn = strdup(basename(path));
+        if (!fn)
+                return log_oom();
+
+        r = hashmap_put(m->public_keys, fn, pkey);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add public key to set: %m");
+
+        TAKE_PTR(fn);
+        TAKE_PTR(pkey);
+
+        return 0;
+}
+
+static int manager_load_public_keys(Manager *m) {
+        _cleanup_strv_free_ char **files = NULL;
+        char **i;
+        int r;
+
+        assert(m);
+
+        m->public_keys = hashmap_free(m->public_keys);
+
+        r = conf_files_list_nulstr(
+                        &files,
+                        ".public",
+                        NULL,
+                        CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED,
+                        KEY_PATHS_NULSTR);
+        if (r < 0)
+                return log_error_errno(r, "Failed to assemble list of public key directories: %m");
+
+        STRV_FOREACH(i, files)
+                (void) manager_load_public_key_one(m, *i);
+
+        return 0;
+}
+
+int manager_startup(Manager *m) {
+        int r;
+
+        assert(m);
+
+        r = manager_listen_notify(m);
+        if (r < 0)
+                return r;
+
+        r = manager_connect_bus(m);
+        if (r < 0)
+                return r;
+
+        r = manager_bind_varlink(m);
+        if (r < 0)
+                return r;
+
+        r = manager_load_key_pair(m); /* only try to load it, don't generate any */
+        if (r < 0)
+                return r;
+
+        r = manager_load_public_keys(m);
+        if (r < 0)
+                return r;
+
+        manager_watch_home(m);
+        (void) manager_watch_devices(m);
+
+        (void) manager_enumerate_records(m);
+        (void) manager_enumerate_images(m);
+        (void) manager_enumerate_devices(m);
+
+        /* Let's clean up home directories whose devices got removed while we were not running */
+        (void) manager_enqueue_gc(m, NULL);
+
+        return 0;
+}
+
+void manager_revalidate_image(Manager *m, Home *h) {
+        int r;
+
+        assert(m);
+        assert(h);
+
+        /* Frees an automatically discovered image, if it's synthetic and its image disappeared. Unmounts any
+         * image if it's mounted but it's image vanished. */
+
+        if (h->current_operation || !ordered_set_isempty(h->pending_operations))
+                return;
+
+        if (h->state == HOME_UNFIXATED) {
+                r = user_record_test_image_path(h->record);
+                if (r < 0)
+                        log_warning_errno(r, "Can't determine if image of %s exists, freeing unfixated user: %m", h->user_name);
+                else if (r == USER_TEST_ABSENT)
+                        log_info("Image for %s disappeared, freeing unfixated user.", h->user_name);
+                else
+                        return;
+
+                home_free(h);
+
+        } else if (h->state < 0) {
+
+                r = user_record_test_home_directory(h->record);
+                if (r < 0) {
+                        log_warning_errno(r, "Unable to determine state of home directory, ignoring: %m");
+                        return;
+                }
+
+                if (r == USER_TEST_MOUNTED) {
+                        r = user_record_test_image_path(h->record);
+                        if (r < 0) {
+                                log_warning_errno(r, "Unable to determine state of image path, ignoring: %m");
+                                return;
+                        }
+
+                        if (r == USER_TEST_ABSENT) {
+                                _cleanup_(operation_unrefp) Operation *o = NULL;
+
+                                log_notice("Backing image disappeared while home directory %s was mounted, unmounting it forcibly.", h->user_name);
+                                /* Wowza, the thing is mounted, but the device is gone? Act on it. */
+
+                                r = home_killall(h);
+                                if (r < 0)
+                                        log_warning_errno(r, "Failed to kill processes of user %s, ignoring: %m", h->user_name);
+
+                                /* We enqueue the operation here, after all the home directory might
+                                 * currently already run some operation, and we can deactivate it only after
+                                 * that's complete. */
+                                o = operation_new(OPERATION_DEACTIVATE_FORCE, NULL);
+                                if (!o) {
+                                        log_oom();
+                                        return;
+                                }
+
+                                r = home_schedule_operation(h, o, NULL);
+                                if (r < 0)
+                                        log_warning_errno(r, "Failed to enqueue forced home directory %s deactivation, ignoring: %m", h->user_name);
+                        }
+                }
+        }
+}
+
+int manager_gc_images(Manager *m) {
+        Home *h;
+
+        assert_se(m);
+
+        if (m->gc_focus) {
+                /* Focus on a specific home */
+
+                h = TAKE_PTR(m->gc_focus);
+                manager_revalidate_image(m, h);
+        } else {
+                /* Gc all */
+                Iterator i;
+
+                HASHMAP_FOREACH(h, m->homes_by_name, i)
+                        manager_revalidate_image(m, h);
+        }
+
+        return 0;
+}
+
+static int on_deferred_rescan(sd_event_source *s, void *userdata) {
+        Manager *m = userdata;
+
+        assert(m);
+
+        m->deferred_rescan_event_source = sd_event_source_unref(m->deferred_rescan_event_source);
+
+        manager_enumerate_devices(m);
+        manager_enumerate_images(m);
+        return 0;
+}
+
+int manager_enqueue_rescan(Manager *m) {
+        int r;
+
+        assert(m);
+
+        if (m->deferred_rescan_event_source)
+                return 0;
+
+        if (!m->event)
+                return 0;
+
+        if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
+                return 0;
+
+        r = sd_event_add_defer(m->event, &m->deferred_rescan_event_source, on_deferred_rescan, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate rescan event source: %m");
+
+        r = sd_event_source_set_priority(m->deferred_rescan_event_source, SD_EVENT_PRIORITY_IDLE+1);
+        if (r < 0)
+                log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
+
+        (void) sd_event_source_set_description(m->deferred_rescan_event_source, "deferred-rescan");
+        return 1;
+}
+
+static int on_deferred_gc(sd_event_source *s, void *userdata) {
+        Manager *m = userdata;
+
+        assert(m);
+
+        m->deferred_gc_event_source = sd_event_source_unref(m->deferred_gc_event_source);
+
+        manager_gc_images(m);
+        return 0;
+}
+
+int manager_enqueue_gc(Manager *m, Home *focus) {
+        int r;
+
+        assert(m);
+
+        /* This enqueues a request to GC dead homes. It may be called with focus=NULL in which case all homes
+         * will be scanned, or with the parameter set, in which case only that home is checked. */
+
+        if (!m->event)
+                return 0;
+
+        if (IN_SET(sd_event_get_state(m->event), SD_EVENT_FINISHED, SD_EVENT_EXITING))
+                return 0;
+
+        /* If a focus home is specified, then remember to focus just on this home. Otherwise invalidate any
+         * focus that might be set to look at all homes. */
+
+        if (m->deferred_gc_event_source) {
+                if (m->gc_focus != focus) /* not the same focus, then look at everything */
+                        m->gc_focus = NULL;
+
+                return 0;
+        } else
+                m->gc_focus = focus; /* start focussed */
+
+        r = sd_event_add_defer(m->event, &m->deferred_gc_event_source, on_deferred_gc, m);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate gc event source: %m");
+
+        r = sd_event_source_set_priority(m->deferred_gc_event_source, SD_EVENT_PRIORITY_IDLE);
+        if (r < 0)
+                log_warning_errno(r, "Failed to tweak priority of event source, ignoring: %m");
+
+        (void) sd_event_source_set_description(m->deferred_gc_event_source, "deferred-gc");
+        return 1;
+}
diff --git a/src/home/homed-manager.h b/src/home/homed-manager.h
new file mode 100644 (file)
index 0000000..00298a3
--- /dev/null
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <openssl/evp.h>
+
+#include "sd-bus.h"
+#include "sd-device.h"
+#include "sd-event.h"
+
+typedef struct Manager Manager;
+
+#include "hashmap.h"
+#include "homed-home.h"
+#include "varlink.h"
+
+#define HOME_UID_MIN 60001
+#define HOME_UID_MAX 60513
+
+struct Manager {
+        sd_event *event;
+        sd_bus *bus;
+
+        Hashmap *polkit_registry;
+
+        Hashmap *homes_by_uid;
+        Hashmap *homes_by_name;
+        Hashmap *homes_by_worker_pid;
+        Hashmap *homes_by_sysfs;
+
+        bool scan_slash_home;
+
+        sd_event_source *inotify_event_source;
+
+        /* An even source we receieve sd_notify() messages from our worker from */
+        sd_event_source *notify_socket_event_source;
+
+        sd_device_monitor *device_monitor;
+
+        sd_event_source *deferred_rescan_event_source;
+        sd_event_source *deferred_gc_event_source;
+        sd_event_source *deferred_auto_login_event_source;
+
+        Home *gc_focus;
+
+        VarlinkServer *varlink_server;
+
+        EVP_PKEY *private_key; /* actually a pair of private and public key */
+        Hashmap *public_keys; /* key name [char*] → publick key [EVP_PKEY*] */
+};
+
+int manager_new(Manager **ret);
+Manager* manager_free(Manager *m);
+DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free);
+
+int manager_startup(Manager *m);
+
+int manager_augment_record_with_uid(Manager *m, UserRecord *hr);
+
+int manager_enqueue_rescan(Manager *m);
+int manager_enqueue_gc(Manager *m, Home *focus);
+
+int manager_verify_user_record(Manager *m, UserRecord *hr);
+
+int manager_acquire_key_pair(Manager *m);
+int manager_sign_user_record(Manager *m, UserRecord *u, UserRecord **ret, sd_bus_error *error);
+
+int bus_manager_emit_auto_login_changed(Manager *m);
diff --git a/src/home/homed-operation.c b/src/home/homed-operation.c
new file mode 100644 (file)
index 0000000..80dc555
--- /dev/null
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "fd-util.h"
+#include "homed-operation.h"
+
+Operation *operation_new(OperationType type, sd_bus_message *m) {
+        Operation *o;
+
+        assert(type >= 0);
+        assert(type < _OPERATION_MAX);
+
+        o = new(Operation, 1);
+        if (!o)
+                return NULL;
+
+        *o = (Operation) {
+                .type = type,
+                .n_ref = 1,
+                .message = sd_bus_message_ref(m),
+                .send_fd = -1,
+                .result = -1,
+        };
+
+        return o;
+}
+
+static Operation *operation_free(Operation *o) {
+        int r;
+
+        if (!o)
+                return NULL;
+
+        if (o->message && o->result >= 0) {
+
+                if (o->result) {
+                        /* Propagate success */
+                        if (o->send_fd < 0)
+                                r = sd_bus_reply_method_return(o->message, NULL);
+                        else
+                                r = sd_bus_reply_method_return(o->message, "h", o->send_fd);
+
+                } else {
+                        /* Propagate failure */
+                        if (sd_bus_error_is_set(&o->error))
+                                r = sd_bus_reply_method_error(o->message, &o->error);
+                        else
+                                r = sd_bus_reply_method_errnof(o->message, o->ret, "Failed to execute operation: %m");
+                }
+                if (r < 0)
+                        log_warning_errno(r, "Failed ot reply to %s method call, ignoring: %m", sd_bus_message_get_member(o->message));
+        }
+
+        sd_bus_message_unref(o->message);
+        user_record_unref(o->secret);
+        safe_close(o->send_fd);
+        sd_bus_error_free(&o->error);
+
+        return mfree(o);
+}
+
+DEFINE_TRIVIAL_REF_UNREF_FUNC(Operation, operation, operation_free);
+
+void operation_result(Operation *o, int ret, const sd_bus_error *error) {
+        assert(o);
+
+        if (ret >= 0)
+                o->result = true;
+        else {
+                o->ret = ret;
+
+                sd_bus_error_free(&o->error);
+                sd_bus_error_copy(&o->error, error);
+
+                o->result = false;
+        }
+}
diff --git a/src/home/homed-operation.h b/src/home/homed-operation.h
new file mode 100644 (file)
index 0000000..224de91
--- /dev/null
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <sd-bus.h>
+
+#include "user-record.h"
+
+typedef enum OperationType {
+        OPERATION_ACQUIRE,           /* enqueued on AcquireHome() */
+        OPERATION_RELEASE,           /* enqueued on ReleaseHome() */
+        OPERATION_LOCK_ALL,          /* enqueued on LockAllHomes() */
+        OPERATION_PIPE_EOF,          /* enqueued when we see EOF on the per-home reference pipes */
+        OPERATION_DEACTIVATE_FORCE,  /* enqueued on hard $HOME unplug */
+        OPERATION_IMMEDIATE,         /* this is never enqueued, it's just a marker we immediately started executing an operation without enqueuing anything first. */
+        _OPERATION_MAX,
+        _OPERATION_INVALID = -1,
+} OperationType;
+
+/* Encapsulates an operation on one or more home directories. This has two uses:
+ *
+ *     1) For queuing an operation when we need to execute one for some reason but there's already one being
+ *        executed.
+ *
+ *     2) When executing an operation without enqueuing it first (OPERATION_IMMEDIATE)
+ *
+ * Note that a single operation object can encapsulate operations on multiple home directories. This is used
+ * for the LockAllHomes() operation, which is one operation but applies to all homes at once. In case the
+ * operation applies to multiple homes the reference counter is increased once for each, and thus the
+ * operation is fully completed only after it reached zero again.
+ *
+ * The object (optionally) contains a reference of the D-Bus message triggering the operation, which is
+ * replied to when the operation is fully completed, i.e. when n_ref reaches zero.
+ */
+
+typedef struct Operation {
+        unsigned n_ref;
+        OperationType type;
+        sd_bus_message *message;
+
+        UserRecord *secret;
+        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 */
+        sd_bus_error error;
+        int ret;
+} Operation;
+
+Operation *operation_new(OperationType type, sd_bus_message *m);
+Operation *operation_ref(Operation *operation);
+Operation *operation_unref(Operation *operation);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Operation*, operation_unref);
+
+void operation_result(Operation *o, int ret, const sd_bus_error *error);
+
+static inline Operation* operation_result_unref(Operation *o, int ret, const sd_bus_error *error) {
+        if (!o)
+                return NULL;
+
+        operation_result(o, ret, error);
+        return operation_unref(o);
+}
diff --git a/src/home/homed-varlink.c b/src/home/homed-varlink.c
new file mode 100644 (file)
index 0000000..c5bbba6
--- /dev/null
@@ -0,0 +1,370 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "group-record.h"
+#include "homed-varlink.h"
+#include "strv.h"
+#include "user-record-util.h"
+#include "user-record.h"
+#include "user-util.h"
+#include "format-util.h"
+
+typedef struct LookupParameters {
+        const char *user_name;
+        const char *group_name;
+        union {
+                uid_t uid;
+                gid_t gid;
+        };
+        const char *service;
+} LookupParameters;
+
+static bool client_is_trusted(Varlink *link, Home *h) {
+        uid_t peer_uid;
+        int r;
+
+        assert(link);
+        assert(h);
+
+        r = varlink_get_peer_uid(link, &peer_uid);
+        if (r < 0) {
+                log_debug_errno(r, "Unable to query peer UID, ignoring: %m");
+                return false;
+        }
+
+        return peer_uid == 0 || peer_uid == h->uid;
+}
+
+static int build_user_json(Home *h, bool trusted, JsonVariant **ret) {
+        _cleanup_(user_record_unrefp) UserRecord *augmented = NULL;
+        UserRecordLoadFlags flags;
+        int r;
+
+        assert(h);
+        assert(ret);
+
+        flags = USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_ALLOW_BINDING|USER_RECORD_STRIP_SECRET|USER_RECORD_ALLOW_STATUS|USER_RECORD_ALLOW_SIGNATURE;
+        if (trusted)
+                flags |= USER_RECORD_ALLOW_PRIVILEGED;
+        else
+                flags |= USER_RECORD_STRIP_PRIVILEGED;
+
+        r = home_augment_status(h, flags, &augmented);
+        if (r < 0)
+                return r;
+
+        return json_build(ret, JSON_BUILD_OBJECT(
+                                          JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(augmented->json)),
+                                          JSON_BUILD_PAIR("incomplete", JSON_BUILD_BOOLEAN(augmented->incomplete))));
+}
+
+static bool home_user_match_lookup_parameters(LookupParameters *p, Home *h) {
+        assert(p);
+        assert(h);
+
+        if (p->user_name && !streq(p->user_name, h->user_name))
+                return false;
+
+        if (uid_is_valid(p->uid) && h->uid != p->uid)
+                return false;
+
+        return true;
+}
+
+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), JSON_SAFE },
+                { "service",        JSON_VARIANT_STRING,   json_dispatch_const_string, offsetof(LookupParameters, service),   0         },
+                {}
+        };
+
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        LookupParameters p = {
+                .uid = UID_INVALID,
+        };
+        Manager *m = userdata;
+        bool trusted;
+        Home *h;
+        int r;
+
+        assert(parameters);
+        assert(m);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
+        if (r < 0)
+                return r;
+
+        if (!streq_ptr(p.service, "io.systemd.Home"))
+                return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
+
+        if (uid_is_valid(p.uid))
+                h = hashmap_get(m->homes_by_uid, UID_TO_PTR(p.uid));
+        else if (p.user_name)
+                h = hashmap_get(m->homes_by_name, p.user_name);
+        else {
+                Iterator i;
+
+                /* If neither UID nor name was specified, then dump all homes. Do so with varlink_notify()
+                 * for all entries but the last, so that clients can stream the results, and easily process
+                 * them piecemeal. */
+
+                HASHMAP_FOREACH(h, m->homes_by_name, i) {
+
+                        if (!home_user_match_lookup_parameters(&p, h))
+                                continue;
+
+                        if (v) {
+                                /* An entry set from the previous iteration? Then send it now */
+                                r = varlink_notify(link, v);
+                                if (r < 0)
+                                        return r;
+
+                                v = json_variant_unref(v);
+                        }
+
+                        trusted = client_is_trusted(link, h);
+
+                        r = build_user_json(h, trusted, &v);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!v)
+                        return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+                return varlink_reply(link, v);
+        }
+
+        if (!h)
+                return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+        if (!home_user_match_lookup_parameters(&p, h))
+                return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
+
+        trusted = client_is_trusted(link, h);
+
+        r = build_user_json(h, trusted, &v);
+        if (r < 0)
+                return r;
+
+        return varlink_reply(link, v);
+}
+
+static int build_group_json(Home *h, JsonVariant **ret) {
+        _cleanup_(group_record_unrefp) GroupRecord *g = NULL;
+        int r;
+
+        assert(h);
+        assert(ret);
+
+        g = group_record_new();
+        if (!g)
+                return -ENOMEM;
+
+        r = group_record_synthesize(g, h->record);
+        if (r < 0)
+                return r;
+
+        assert(!FLAGS_SET(g->mask, USER_RECORD_SECRET));
+        assert(!FLAGS_SET(g->mask, USER_RECORD_PRIVILEGED));
+
+        return json_build(ret,
+                          JSON_BUILD_OBJECT(
+                                          JSON_BUILD_PAIR("record", JSON_BUILD_VARIANT(g->json))));
+}
+
+static bool home_group_match_lookup_parameters(LookupParameters *p, Home *h) {
+        assert(p);
+        assert(h);
+
+        if (p->group_name && !streq(h->user_name, p->group_name))
+                return false;
+
+        if (gid_is_valid(p->gid) && h->uid != (uid_t) p->gid)
+                return false;
+
+        return true;
+}
+
+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), JSON_SAFE },
+                { "service",   JSON_VARIANT_STRING,   json_dispatch_const_string, offsetof(LookupParameters, service),    0         },
+                {}
+        };
+
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        LookupParameters p = {
+                .gid = GID_INVALID,
+        };
+        Manager *m = userdata;
+        Home *h;
+        int r;
+
+        assert(parameters);
+        assert(m);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
+        if (r < 0)
+                return r;
+
+        if (!streq_ptr(p.service, "io.systemd.Home"))
+                return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
+
+        if (gid_is_valid(p.gid))
+                h = hashmap_get(m->homes_by_uid, UID_TO_PTR((uid_t) p.gid));
+        else if (p.group_name)
+                h = hashmap_get(m->homes_by_name, p.group_name);
+        else {
+                Iterator i;
+
+                HASHMAP_FOREACH(h, m->homes_by_name, i) {
+
+                        if (!home_group_match_lookup_parameters(&p, h))
+                                continue;
+
+                        if (v) {
+                                r = varlink_notify(link, v);
+                                if (r < 0)
+                                        return r;
+
+                                v = json_variant_unref(v);
+                        }
+
+                        r = build_group_json(h, &v);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!v)
+                        return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+                return varlink_reply(link, v);
+        }
+
+        if (!h)
+                return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+        if (!home_group_match_lookup_parameters(&p, h))
+                return varlink_error(link, "io.systemd.UserDatabase.ConflictingRecordFound", NULL);
+
+        r = build_group_json(h, &v);
+        if (r < 0)
+                return r;
+
+        return varlink_reply(link, v);
+}
+
+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),  JSON_SAFE },
+                { "groupName", JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, group_name), JSON_SAFE },
+                { "service",   JSON_VARIANT_STRING, json_dispatch_const_string, offsetof(LookupParameters, service),    0         },
+                {}
+        };
+
+        Manager *m = userdata;
+        LookupParameters p = {};
+        Home *h;
+        int r;
+
+        assert(parameters);
+        assert(m);
+
+        r = json_dispatch(parameters, dispatch_table, NULL, 0, &p);
+        if (r < 0)
+                return r;
+
+        if (!streq_ptr(p.service, "io.systemd.Home"))
+                return varlink_error(link, "io.systemd.UserDatabase.BadService", NULL);
+
+        if (p.user_name) {
+                const char *last = NULL;
+                char **i;
+
+                h = hashmap_get(m->homes_by_name, p.user_name);
+                if (!h)
+                        return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+                if (p.group_name) {
+                        if (!strv_contains(h->record->member_of, p.group_name))
+                                return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+
+                        return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
+                                                                      JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
+                }
+
+                STRV_FOREACH(i, h->record->member_of) {
+                        if (last) {
+                                r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
+                                                                            JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last))));
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        last = *i;
+                }
+
+                if (last)
+                        return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(h->user_name)),
+                                                                      JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last))));
+
+        } else if (p.group_name) {
+                const char *last = NULL;
+                Iterator i;
+
+                HASHMAP_FOREACH(h, m->homes_by_name, i) {
+
+                        if (!strv_contains(h->record->member_of, p.group_name))
+                                continue;
+
+                        if (last) {
+                                r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
+                                                                            JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        last = h->user_name;
+                }
+
+                if (last)
+                        return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last)),
+                                                                      JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(p.group_name))));
+        } else {
+                const char *last_user_name = NULL, *last_group_name = NULL;
+                Iterator i;
+
+                HASHMAP_FOREACH(h, m->homes_by_name, i) {
+                        char **j;
+
+                        STRV_FOREACH(j, h->record->member_of) {
+
+                                if (last_user_name) {
+                                        assert(last_group_name);
+
+                                        r = varlink_notifyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
+                                                                                    JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
+
+                                        if (r < 0)
+                                                return r;
+                                }
+
+                                last_user_name = h->user_name;
+                                last_group_name = *j;
+                        }
+                }
+
+                if (last_user_name) {
+                        assert(last_group_name);
+                        return varlink_replyb(link, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(last_user_name)),
+                                                                      JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(last_group_name))));
+                }
+        }
+
+        return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL);
+}
diff --git a/src/home/homed-varlink.h b/src/home/homed-varlink.h
new file mode 100644 (file)
index 0000000..4454d23
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "homed-manager.h"
+
+int vl_method_get_user_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
+int vl_method_get_group_record(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
+int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
diff --git a/src/home/homed.c b/src/home/homed.c
new file mode 100644 (file)
index 0000000..ca43558
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "daemon-util.h"
+#include "homed-manager.h"
+#include "log.h"
+#include "main-func.h"
+#include "signal-util.h"
+
+static int run(int argc, char *argv[]) {
+        _cleanup_(notify_on_cleanup) const char *notify_stop = NULL;
+        _cleanup_(manager_freep) Manager *m = NULL;
+        int r;
+
+        log_setup_service();
+
+        umask(0022);
+
+        if (argc != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments.");
+
+        if (setenv("SYSTEMD_BYPASS_USERDB", "io.systemd.Home", 1) < 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set $SYSTEMD_BYPASS_USERDB: %m");
+
+        assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGTERM, SIGINT, -1) >= 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/home/homework-cifs.c b/src/home/homework-cifs.c
new file mode 100644 (file)
index 0000000..27f2981
--- /dev/null
@@ -0,0 +1,214 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "dirent-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "homework-cifs.h"
+#include "homework-mount.h"
+#include "mount-util.h"
+#include "process-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+
+int home_prepare_cifs(
+                UserRecord *h,
+                bool already_activated,
+                HomeSetup *setup) {
+
+        assert(h);
+        assert(setup);
+        assert(user_record_storage(h) == USER_CIFS);
+
+        if (already_activated)
+                setup->root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        else {
+                bool mounted = false;
+                char **pw;
+                int r;
+
+                r = home_unshare_and_mount(NULL, NULL, false);
+                if (r < 0)
+                        return r;
+
+                STRV_FOREACH(pw, h->password) {
+                        _cleanup_(unlink_and_freep) char *p = NULL;
+                        _cleanup_free_ char *options = NULL;
+                        _cleanup_(fclosep) FILE *f = NULL;
+                        pid_t mount_pid;
+                        int exit_status;
+
+                        r = fopen_temporary(NULL, &f, &p);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to create temporary credentials file: %m");
+
+                        fprintf(f,
+                                "username=%s\n"
+                                "password=%s\n",
+                                user_record_cifs_user_name(h),
+                                *pw);
+
+                        if (h->cifs_domain)
+                                fprintf(f, "domain=%s\n", h->cifs_domain);
+
+                        r = fflush_and_check(f);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to write temporary credentials file: %m");
+
+                        f = safe_fclose(f);
+
+                        if (asprintf(&options, "credentials=%s,uid=" UID_FMT ",forceuid,gid=" UID_FMT ",forcegid,file_mode=0%3o,dir_mode=0%3o",
+                                     p, h->uid, h->uid, h->access_mode, h->access_mode) < 0)
+                                return log_oom();
+
+                        r = safe_fork("(mount)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &mount_pid);
+                        if (r < 0)
+                                return r;
+                        if (r == 0) {
+                                /* Child */
+                                execl("/bin/mount", "/bin/mount", "-n", "-t", "cifs",
+                                      h->cifs_service, "/run/systemd/user-home-mount",
+                                      "-o", options, NULL);
+
+                                log_error_errno(errno, "Failed to execute fsck: %m");
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        exit_status = wait_for_terminate_and_check("mount", mount_pid, WAIT_LOG_ABNORMAL|WAIT_LOG_NON_ZERO_EXIT_STATUS);
+                        if (exit_status < 0)
+                                return exit_status;
+                        if (exit_status != EXIT_SUCCESS)
+                                return -EPROTO;
+
+                        mounted = true;
+                        break;
+                }
+
+                if (!mounted)
+                        return log_error_errno(ENOKEY, "Failed to mount home directory with supplied password.");
+
+                setup->root_fd = open("/run/systemd/user-home-mount", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        }
+        if (setup->root_fd < 0)
+                return log_error_errno(errno, "Failed to open home directory: %m");
+
+        return 0;
+}
+
+int home_activate_cifs(
+                UserRecord *h,
+                char ***pkcs11_decrypted_passwords,
+                UserRecord **ret_home) {
+
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        const char *hdo, *hd;
+        int r;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_CIFS);
+        assert(ret_home);
+
+        if (!h->cifs_service)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
+
+        assert_se(hdo = user_record_home_directory(h));
+        hd = strdupa(hdo); /* copy the string out, since it might change later in the home record object */
+
+        r = home_prepare_cifs(h, false, &setup);
+        if (r < 0)
+                return r;
+
+        r = home_refresh(h, &setup, NULL, pkcs11_decrypted_passwords, NULL, &new_home);
+        if (r < 0)
+                return r;
+
+        setup.root_fd = safe_close(setup.root_fd);
+
+        r = home_move_mount(NULL, hd);
+        if (r < 0)
+                return r;
+
+        setup.undo_mount = false;
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1;
+}
+
+int home_create_cifs(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        _cleanup_(closedirp) DIR *d = NULL;
+        int r, copy;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_CIFS);
+        assert(ret_home);
+
+        if (!h->cifs_service)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks CIFS service, refusing.");
+
+        if (access("/sbin/mount.cifs", F_OK) < 0) {
+                if (errno == ENOENT)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "/sbin/mount.cifs is missing.");
+
+                return log_error_errno(errno, "Unable to detect whether /sbin/mount.cifs exists: %m");
+        }
+
+        r = home_prepare_cifs(h, false, &setup);
+        if (r < 0)
+                return r;
+
+        copy = fcntl(setup.root_fd, F_DUPFD_CLOEXEC, 3);
+        if (copy < 0)
+                return -errno;
+
+        d = fdopendir(copy);
+        if (!d) {
+                safe_close(copy);
+                return -errno;
+        }
+
+        errno = 0;
+        if (readdir_no_dot(d))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTEMPTY), "Selected CIFS directory not empty, refusing.");
+        if (errno != 0)
+                return log_error_errno(errno, "Failed to detect if CIFS directory is empty: %m");
+
+        r = home_populate(h, setup.root_fd);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(setup.root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
+        if (r < 0)
+                return log_error_errno(r, "Failed to clone record: %m");
+
+        r = user_record_add_binding(
+                        new_home,
+                        USER_CIFS,
+                        NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        NULL,
+                        NULL,
+                        UINT64_MAX,
+                        NULL,
+                        NULL,
+                        h->uid,
+                        (gid_t) h->uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add binding to record: %m");
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
diff --git a/src/home/homework-cifs.h b/src/home/homework-cifs.h
new file mode 100644 (file)
index 0000000..346be88
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "homework.h"
+#include "user-record.h"
+
+int home_prepare_cifs(UserRecord *h, bool already_activated, HomeSetup *setup);
+
+int home_activate_cifs(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
+
+int home_create_cifs(UserRecord *h, UserRecord **ret_home);
diff --git a/src/home/homework-directory.c b/src/home/homework-directory.c
new file mode 100644 (file)
index 0000000..8a4cb17
--- /dev/null
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sys/mount.h>
+
+#include "btrfs-util.h"
+#include "fd-util.h"
+#include "homework-directory.h"
+#include "homework-quota.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "path-util.h"
+#include "rm-rf.h"
+#include "tmpfile-util.h"
+#include "umask-util.h"
+
+int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup) {
+        assert(h);
+        assert(setup);
+
+        setup->root_fd = open(user_record_image_path(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+        if (setup->root_fd < 0)
+                return log_error_errno(errno, "Failed to open home directory: %m");
+
+        return 0;
+}
+
+int home_activate_directory(
+                UserRecord *h,
+                char ***pkcs11_decrypted_passwords,
+                UserRecord **ret_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL;
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        const char *hdo, *hd, *ipo, *ip;
+        int r;
+
+        assert(h);
+        assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
+        assert(ret_home);
+
+        assert_se(ipo = user_record_image_path(h));
+        ip = strdupa(ipo); /* copy out, since reconciliation might cause changing of the field */
+
+        assert_se(hdo = user_record_home_directory(h));
+        hd = strdupa(hdo);
+
+        r = home_prepare(h, false, pkcs11_decrypted_passwords, &setup, &header_home);
+        if (r < 0)
+                return r;
+
+        r = home_refresh(h, &setup, header_home, pkcs11_decrypted_passwords, NULL, &new_home);
+        if (r < 0)
+                return r;
+
+        setup.root_fd = safe_close(setup.root_fd);
+
+        /* Create mount point to mount over if necessary */
+        if (!path_equal(ip, hd))
+                (void) mkdir_p(hd, 0700);
+
+        /* Create a mount point (even if the directory is already placed correctly), as a way to indicate
+         * this mount point is now "activated". Moreover, we want to set per-user
+         * MS_NOSUID/MS_NOEXEC/MS_NODEV. */
+        r = mount_verbose(LOG_ERR, ip, hd, NULL, MS_BIND, NULL);
+        if (r < 0)
+                return r;
+
+        r = mount_verbose(LOG_ERR, NULL, hd, NULL, MS_BIND|MS_REMOUNT|user_record_mount_flags(h), NULL);
+        if (r < 0) {
+                (void) umount_verbose(hd);
+                return r;
+        }
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
+
+int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(rm_rf_subvolume_and_freep) char *temporary = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        _cleanup_close_ int root_fd = -1;
+        _cleanup_free_ char *d = NULL;
+        const char *ip;
+        int r;
+
+        assert(h);
+        assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME));
+        assert(ret_home);
+
+        assert_se(ip = user_record_image_path(h));
+
+        r = tempfn_random(ip, "homework", &d);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate temporary directory: %m");
+
+        (void) mkdir_parents(d, 0755);
+
+        switch (user_record_storage(h)) {
+
+        case USER_SUBVOLUME:
+                RUN_WITH_UMASK(0077)
+                        r = btrfs_subvol_make(d);
+
+                if (r >= 0) {
+                        log_info("Subvolume created.");
+
+                        if (h->disk_size != UINT64_MAX) {
+
+                                /* Enable quota for the subvolume we just created. Note we don't check for
+                                 * errors here and only log about debug level about this. */
+                                r = btrfs_quota_enable(d, true);
+                                if (r < 0)
+                                        log_debug_errno(r, "Failed to enable quota on %s, ignoring: %m", d);
+
+                                r = btrfs_subvol_auto_qgroup(d, 0, false);
+                                if (r < 0)
+                                        log_debug_errno(r, "Failed to set up automatic quota group on %s, ignoring: %m", d);
+
+                                /* Actually configure the quota. We also ignore errors here, but we do log
+                                 * about them loudly, to keep things discoverable even though we don't
+                                 * consider lacking quota support in kernel fatal. */
+                                (void) home_update_quota_btrfs(h, d);
+                        }
+
+                        break;
+                }
+                if (r != -ENOTTY)
+                        return log_error_errno(r, "Failed to create temporary home directory subvolume %s: %m", d);
+
+                log_info("Creating subvolume %s is not supported, as file system does not support subvolumes. Falling back to regular directory.", d);
+                _fallthrough_;
+
+        case USER_DIRECTORY:
+
+                if (mkdir(d, 0700) < 0)
+                        return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
+
+                (void) home_update_quota_classic(h, d);
+                break;
+
+        default:
+                assert_not_reached("unexpected storage");
+        }
+
+        temporary = TAKE_PTR(d); /* Needs to be destroyed now */
+
+        root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        if (root_fd < 0)
+                return log_error_errno(errno, "Failed to open temporary home directory: %m");
+
+        r = home_populate(h, root_fd);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
+        if (r < 0)
+                return log_error_errno(r, "Failed to clone record: %m");
+
+        r = user_record_add_binding(
+                        new_home,
+                        user_record_storage(h),
+                        ip,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        NULL,
+                        NULL,
+                        UINT64_MAX,
+                        NULL,
+                        NULL,
+                        h->uid,
+                        (gid_t) h->uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add binding to record: %m");
+
+        if (rename(temporary, ip) < 0)
+                return log_error_errno(errno, "Failed to rename %s to %s: %m", temporary, ip);
+
+        temporary = mfree(temporary);
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
+
+int home_resize_directory(
+                UserRecord *h,
+                bool already_activated,
+                char ***pkcs11_decrypted_passwords,
+                HomeSetup *setup,
+                UserRecord **ret_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
+        int r;
+
+        assert(h);
+        assert(setup);
+        assert(ret_home);
+        assert(IN_SET(user_record_storage(h), USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT));
+
+        r = home_prepare(h, already_activated, pkcs11_decrypted_passwords, setup, NULL);
+        if (r < 0)
+                return r;
+
+        r = home_load_embedded_identity(h, setup->root_fd, NULL, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
+        if (r < 0)
+                return r;
+
+        r = home_update_quota_auto(h, NULL);
+        if (ERRNO_IS_NOT_SUPPORTED(r))
+                return -ESOCKTNOSUPPORT; /* make recognizable */
+        if (r < 0)
+                return r;
+
+        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, setup);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(setup->root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = home_setup_undo(setup);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
diff --git a/src/home/homework-directory.h b/src/home/homework-directory.h
new file mode 100644 (file)
index 0000000..047c3a7
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "homework.h"
+#include "user-record.h"
+
+int home_prepare_directory(UserRecord *h, bool already_activated, HomeSetup *setup);
+int home_activate_directory(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
+int home_create_directory_or_subvolume(UserRecord *h, UserRecord **ret_home);
+int home_resize_directory(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c
new file mode 100644 (file)
index 0000000..696e265
--- /dev/null
@@ -0,0 +1,644 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <linux/fs.h>
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <sys/ioctl.h>
+#include <sys/xattr.h>
+
+#include "errno-util.h"
+#include "fd-util.h"
+#include "hexdecoct.h"
+#include "homework-fscrypt.h"
+#include "homework-quota.h"
+#include "memory-util.h"
+#include "missing_keyctl.h"
+#include "missing_syscall.h"
+#include "mkdir.h"
+#include "nulstr-util.h"
+#include "openssl-util.h"
+#include "parse-util.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "rm-rf.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+#include "xattr-util.h"
+
+static int fscrypt_upload_volume_key(
+                const uint8_t key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
+                const void *volume_key,
+                size_t volume_key_size,
+                key_serial_t where) {
+
+        _cleanup_free_ char *hex = NULL;
+        const char *description;
+        struct fscrypt_key key;
+        key_serial_t serial;
+
+        assert(key_descriptor);
+        assert(volume_key);
+        assert(volume_key_size > 0);
+
+        if (volume_key_size > sizeof(key.raw))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume key too long.");
+
+        hex = hexmem(key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
+        if (!hex)
+                return log_oom();
+
+        description = strjoina("fscrypt:", hex);
+
+        key = (struct fscrypt_key) {
+                .size = volume_key_size,
+        };
+        memcpy(key.raw, volume_key, volume_key_size);
+
+        /* Upload to the kernel */
+        serial = add_key("logon", description, &key, sizeof(key), where);
+        explicit_bzero_safe(&key, sizeof(key));
+
+        if (serial < 0)
+                return log_error_errno(errno, "Failed to install master key in keyring: %m");
+
+        log_info("Uploaded encryption key to kernel.");
+
+        return 0;
+}
+
+static void calculate_key_descriptor(
+                const void *key,
+                size_t key_size,
+                uint8_t ret_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE]) {
+
+        uint8_t hashed[512 / 8] = {}, hashed2[512 / 8] = {};
+
+        /* Derive the key descriptor from the volume key via double SHA512, in order to be compatible with e4crypt */
+
+        assert_se(SHA512(key, key_size, hashed) == hashed);
+        assert_se(SHA512(hashed, sizeof(hashed), hashed2) == hashed2);
+
+        assert_cc(sizeof(hashed2) >= FS_KEY_DESCRIPTOR_SIZE);
+
+        memcpy(ret_key_descriptor, hashed2, FS_KEY_DESCRIPTOR_SIZE);
+}
+
+static int fscrypt_slot_try_one(
+                const char *password,
+                const void *salt, size_t salt_size,
+                const void *encrypted, size_t encrypted_size,
+                const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
+                void **ret_decrypted, size_t *ret_decrypted_size) {
+
+
+        _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
+        _cleanup_(erase_and_freep) void *decrypted = NULL;
+        uint8_t key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
+        int decrypted_size_out1, decrypted_size_out2;
+        uint8_t derived[512 / 8] = {};
+        size_t decrypted_size;
+        const EVP_CIPHER *cc;
+        int r;
+
+        assert(password);
+        assert(salt);
+        assert(salt_size > 0);
+        assert(encrypted);
+        assert(encrypted_size > 0);
+        assert(match_key_descriptor);
+
+        /* Our construction is like this:
+         *
+         *   1. In each key slot we store a salt value plus the encrypted volume key
+         *
+         *   2. Unlocking is via calculating PBKDF2-HMAC-SHA512 of the supplied password (in combination with
+         *      the salt), then using the first 256 bit of the hash as key for decrypting the encrypted
+         *      volume key in AES256 counter mode.
+         *
+         *   3. Writing a password is similar: calculate PBKDF2-HMAC-SHA512 of the supplied password (in
+         *      combination with the salt), then encrypt the volume key in AES256 counter mode with the
+         *      resulting hash.
+         */
+
+        if (PKCS5_PBKDF2_HMAC(
+                            password, strlen(password),
+                            salt, salt_size,
+                            0xFFFF, EVP_sha512(),
+                            sizeof(derived), derived) != 1) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
+                goto finish;
+        }
+
+        context = EVP_CIPHER_CTX_new();
+        if (!context) {
+                r = log_oom();
+                goto finish;
+        }
+
+        /* We use AES256 in counter mode */
+        assert_se(cc = EVP_aes_256_ctr());
+
+        /* We only use the first half of the derived key */
+        assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
+
+        if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1)  {
+                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
+                goto finish;
+        }
+
+        /* Flush out the derived key now, we don't need it anymore */
+        explicit_bzero_safe(derived, sizeof(derived));
+
+        decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2;
+        decrypted = malloc(decrypted_size);
+        if (!decrypted)
+                return log_oom();
+
+        if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt volume key.");
+
+        assert((size_t) decrypted_size_out1 <= decrypted_size);
+
+        if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted_size + decrypted_size_out1, &decrypted_size_out2) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of volume key.");
+
+        assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size);
+        decrypted_size = (size_t) decrypted_size_out1 + (size_t) decrypted_size_out2;
+
+        calculate_key_descriptor(decrypted, decrypted_size, key_descriptor);
+
+        if (memcmp(key_descriptor, match_key_descriptor, FS_KEY_DESCRIPTOR_SIZE) != 0)
+                return -ENOANO; /* don't log here */
+
+        r = fscrypt_upload_volume_key(key_descriptor, decrypted, decrypted_size, KEY_SPEC_THREAD_KEYRING);
+        if (r < 0)
+                return r;
+
+        if (ret_decrypted)
+                *ret_decrypted = TAKE_PTR(decrypted);
+        if (ret_decrypted_size)
+                *ret_decrypted_size = decrypted_size;
+
+        return 0;
+
+finish:
+        explicit_bzero_safe(derived, sizeof(derived));
+        return r;
+}
+
+static int fscrypt_slot_try_many(
+                char **passwords,
+                const void *salt, size_t salt_size,
+                const void *encrypted, size_t encrypted_size,
+                const uint8_t match_key_descriptor[static FS_KEY_DESCRIPTOR_SIZE],
+                void **ret_decrypted, size_t *ret_decrypted_size) {
+
+        char **i;
+        int r;
+
+        STRV_FOREACH(i, passwords) {
+                r = fscrypt_slot_try_one(*i, salt, salt_size, encrypted, encrypted_size, match_key_descriptor, ret_decrypted, ret_decrypted_size);
+                if (r != -ENOANO)
+                        return r;
+        }
+
+        return -ENOANO;
+}
+
+static int fscrypt_setup(
+                char **pkcs11_decrypted_passwords,
+                char **password,
+                HomeSetup *setup,
+                void **ret_volume_key,
+                size_t *ret_volume_key_size) {
+
+        _cleanup_free_ char *xattr_buf = NULL;
+        const char *xa;
+        int r;
+
+        assert(setup);
+        assert(setup->root_fd >= 0);
+
+        r = flistxattr_malloc(setup->root_fd, &xattr_buf);
+        if (r < 0)
+                return log_error_errno(errno, "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;
+                int n;
+
+                /* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
+                nr = startswith(xa, "trusted.fscrypt_slot");
+                if (!nr)
+                        continue;
+                if (safe_atou32(nr, NULL) < 0)
+                        continue;
+
+                n = fgetxattr_malloc(setup->root_fd, xa, &value);
+                if (n == -ENODATA) /* deleted by now? */
+                        continue;
+                if (n < 0)
+                        return log_error_errno(n, "Failed to read %s xattr: %m", xa);
+
+                e = memchr(value, ':', n);
+                if (!e)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "xattr %s lacks ':' separator: %m", xa);
+
+                r = unbase64mem(value, e - value, &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);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to decode encrypted key of %s: %m", xa);
+
+                r = fscrypt_slot_try_many(
+                                pkcs11_decrypted_passwords,
+                                salt, salt_size,
+                                encrypted, encrypted_size,
+                                setup->fscrypt_key_descriptor,
+                                ret_volume_key, ret_volume_key_size);
+                if (r == -ENOANO)
+                        r = fscrypt_slot_try_many(
+                                        password,
+                                        salt, salt_size,
+                                        encrypted, encrypted_size,
+                                        setup->fscrypt_key_descriptor,
+                                        ret_volume_key, ret_volume_key_size);
+                if (r < 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.");
+}
+
+int home_prepare_fscrypt(
+                UserRecord *h,
+                bool already_activated,
+                char ***pkcs11_decrypted_passwords,
+                HomeSetup *setup) {
+
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        struct fscrypt_policy policy = {};
+        size_t volume_key_size = 0;
+        const char *ip;
+        int r;
+
+        assert(h);
+        assert(setup);
+        assert(user_record_storage(h) == USER_FSCRYPT);
+
+        assert_se(ip = user_record_image_path(h));
+
+        setup->root_fd = open(ip, O_RDONLY|O_CLOEXEC|O_DIRECTORY);
+        if (setup->root_fd < 0)
+                return log_error_errno(errno, "Failed to open home directory: %m");
+
+        if (ioctl(setup->root_fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
+                if (errno == ENODATA)
+                        return log_error_errno(errno, "Home directory %s is not encrypted.", ip);
+                if (ERRNO_IS_NOT_SUPPORTED(errno)) {
+                        log_error_errno(errno, "File system does not support fscrypt: %m");
+                        return -ENOLINK; /* make recognizable */
+                }
+                return log_error_errno(errno, "Failed to acquire encryption policy of %s: %m", ip);
+        }
+
+        memcpy(setup->fscrypt_key_descriptor, policy.master_key_descriptor, FS_KEY_DESCRIPTOR_SIZE);
+
+        r = fscrypt_setup(
+                        pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
+                        h->password,
+                        setup,
+                        &volume_key,
+                        &volume_key_size);
+        if (r < 0)
+                return r;
+
+        /* Also install the access key in the user's own keyring */
+
+        if (uid_is_valid(h->uid)) {
+                r = safe_fork("(sd-addkey)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
+                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);
+                                _exit(EXIT_FAILURE);
+                        }
+
+                        r = fscrypt_upload_volume_key(
+                                        setup->fscrypt_key_descriptor,
+                                        volume_key,
+                                        volume_key_size,
+                                        KEY_SPEC_USER_KEYRING);
+                        if (r < 0)
+                                _exit(EXIT_FAILURE);
+
+                        _exit(EXIT_SUCCESS);
+                }
+        }
+
+        return 0;
+}
+
+static int fscrypt_slot_set(
+                int root_fd,
+                const void *volume_key,
+                size_t volume_key_size,
+                const char *password,
+                uint32_t nr) {
+
+        _cleanup_free_ char *salt_base64 = NULL, *encrypted_base64 = NULL, *joined = NULL;
+        char label[STRLEN("trusted.fscrypt_slot") + DECIMAL_STR_MAX(nr) + 1];
+        _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
+        int r, encrypted_size_out1, encrypted_size_out2;
+        uint8_t salt[64], derived[512 / 8] = {};
+        _cleanup_free_ void *encrypted = NULL;
+        const EVP_CIPHER *cc;
+        size_t encrypted_size;
+
+        r = genuine_random_bytes(salt, sizeof(salt), RANDOM_BLOCK);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate salt: %m");
+
+        if (PKCS5_PBKDF2_HMAC(
+                            password, strlen(password),
+                            salt, sizeof(salt),
+                            0xFFFF, EVP_sha512(),
+                            sizeof(derived), derived) != 1) {
+                r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed");
+                goto finish;
+        }
+
+        context = EVP_CIPHER_CTX_new();
+        if (!context) {
+                r = log_oom();
+                goto finish;
+        }
+
+        /* We use AES256 in counter mode */
+        cc = EVP_aes_256_ctr();
+
+        /* We only use the first half of the derived key */
+        assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc));
+
+        if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1)  {
+                r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
+                goto finish;
+        }
+
+        /* Flush out the derived key now, we don't need it anymore */
+        explicit_bzero_safe(derived, sizeof(derived));
+
+        encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2;
+        encrypted = malloc(encrypted_size);
+        if (!encrypted)
+                return log_oom();
+
+        if (EVP_EncryptUpdate(context, (uint8_t*) encrypted, &encrypted_size_out1, volume_key, volume_key_size) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt volume key.");
+
+        assert((size_t) encrypted_size_out1 <= encrypted_size);
+
+        if (EVP_EncryptFinal_ex(context, (uint8_t*) encrypted_size + encrypted_size_out1, &encrypted_size_out2) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish encryption of volume key.");
+
+        assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size);
+        encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2;
+
+        r = base64mem(salt, sizeof(salt), &salt_base64);
+        if (r < 0)
+                return log_oom();
+
+        r = base64mem(encrypted, encrypted_size, &encrypted_base64);
+        if (r < 0)
+                return log_oom();
+
+        joined = strjoin(salt_base64, ":", encrypted_base64);
+        if (!joined)
+                return log_oom();
+
+        xsprintf(label, "trusted.fscrypt_slot%" PRIu32, nr);
+        if (fsetxattr(root_fd, label, joined, strlen(joined), 0) < 0)
+                return log_error_errno(errno, "Failed to write xattr %s: %m", label);
+
+        log_info("Written key slot %s.", label);
+
+        return 0;
+
+finish:
+        explicit_bzero_safe(derived, sizeof(derived));
+        return r;
+}
+
+int home_create_fscrypt(
+                UserRecord *h,
+                char **effective_passwords,
+                UserRecord **ret_home) {
+
+        _cleanup_(rm_rf_physical_and_freep) char *temporary = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        struct fscrypt_policy policy = {};
+        size_t volume_key_size = 512 / 8;
+        _cleanup_close_ int root_fd = -1;
+        _cleanup_free_ char *d = NULL;
+        uint32_t nr = 0;
+        const char *ip;
+        char **i;
+        int r;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_FSCRYPT);
+        assert(ret_home);
+
+        assert_se(ip = user_record_image_path(h));
+
+        r = tempfn_random(ip, "homework", &d);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate temporary directory: %m");
+
+        (void) mkdir_parents(d, 0755);
+
+        if (mkdir(d, 0700) < 0)
+                return log_error_errno(errno, "Failed to create temporary home directory %s: %m", d);
+
+        temporary = TAKE_PTR(d); /* Needs to be destroyed now */
+
+        root_fd = open(temporary, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        if (root_fd < 0)
+                return log_error_errno(errno, "Failed to open temporary home directory: %m");
+
+        if (ioctl(root_fd, FS_IOC_GET_ENCRYPTION_POLICY, &policy) < 0) {
+                if (ERRNO_IS_NOT_SUPPORTED(errno)) {
+                        log_error_errno(errno, "File system does not support fscrypt: %m");
+                        return -ENOLINK; /* make recognizable */
+                }
+                if (errno != ENODATA)
+                        return log_error_errno(errno, "Failed to get fscrypt policy of directory: %m");
+        } else
+                return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Parent of %s already encrypted, refusing.", d);
+
+        volume_key = malloc(volume_key_size);
+        if (!volume_key)
+                return log_oom();
+
+        r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire volume key: %m");
+
+        log_info("Generated volume key of size %zu.", volume_key_size);
+
+        policy = (struct fscrypt_policy) {
+                .contents_encryption_mode = FS_ENCRYPTION_MODE_AES_256_XTS,
+                .filenames_encryption_mode = FS_ENCRYPTION_MODE_AES_256_CTS,
+                .flags = FS_POLICY_FLAGS_PAD_32,
+        };
+
+        calculate_key_descriptor(volume_key, volume_key_size, policy.master_key_descriptor);
+
+        r = fscrypt_upload_volume_key(policy.master_key_descriptor, volume_key, volume_key_size, KEY_SPEC_THREAD_KEYRING);
+        if (r < 0)
+                return r;
+
+        log_info("Uploaded volume key to kernel.");
+
+        if (ioctl(root_fd, FS_IOC_SET_ENCRYPTION_POLICY, &policy) < 0)
+                return log_error_errno(errno, "Failed to set fscrypt policy on directory: %m");
+
+        log_info("Encryption policy set.");
+
+        STRV_FOREACH(i, effective_passwords) {
+                r = fscrypt_slot_set(root_fd, volume_key, volume_key_size, *i, nr);
+                if (r < 0)
+                        return r;
+
+                nr++;
+        }
+
+        (void) home_update_quota_classic(h, temporary);
+
+        r = home_populate(h, root_fd);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET, &new_home);
+        if (r < 0)
+                return log_error_errno(r, "Failed to clone record: %m");
+
+        r = user_record_add_binding(
+                        new_home,
+                        USER_FSCRYPT,
+                        ip,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        SD_ID128_NULL,
+                        NULL,
+                        NULL,
+                        UINT64_MAX,
+                        NULL,
+                        NULL,
+                        h->uid,
+                        (gid_t) h->uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add binding to record: %m");
+
+        if (rename(temporary, ip) < 0)
+                return log_error_errno(errno, "Failed to rename %s to %s: %m", temporary, ip);
+
+        temporary = mfree(temporary);
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
+
+int home_passwd_fscrypt(
+                UserRecord *h,
+                HomeSetup *setup,
+                char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
+                char **effective_passwords          /* new passwords */) {
+
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        _cleanup_free_ char *xattr_buf = NULL;
+        size_t volume_key_size = 0;
+        uint32_t slot = 0;
+        const char *xa;
+        char **p;
+        int r;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_FSCRYPT);
+        assert(setup);
+
+        r = fscrypt_setup(
+                        pkcs11_decrypted_passwords,
+                        h->password,
+                        setup,
+                        &volume_key,
+                        &volume_key_size);
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH(p, effective_passwords) {
+                r = fscrypt_slot_set(setup->root_fd, volume_key, volume_key_size, *p, slot);
+                if (r < 0)
+                        return r;
+
+                slot++;
+        }
+
+        r = flistxattr_malloc(setup->root_fd, &xattr_buf);
+        if (r < 0)
+                return log_error_errno(errno, "Failed to retrieve xattr list: %m");
+
+        NULSTR_FOREACH(xa, xattr_buf) {
+                const char *nr;
+                uint32_t z;
+
+                /* Check if this xattr has the format 'trusted.fscrypt_slot<nr>' where '<nr>' is a 32bit unsigned integer */
+                nr = startswith(xa, "trusted.fscrypt_slot");
+                if (!nr)
+                        continue;
+                if (safe_atou32(nr, &z) < 0)
+                        continue;
+
+                if (z < slot)
+                        continue;
+
+                if (fremovexattr(setup->root_fd, xa) < 0)
+
+                        if (errno != ENODATA)
+                                log_warning_errno(errno, "Failed to remove xattr %s: %m", xa);
+        }
+
+        return 0;
+}
diff --git a/src/home/homework-fscrypt.h b/src/home/homework-fscrypt.h
new file mode 100644 (file)
index 0000000..aa3bcd3
--- /dev/null
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "homework.h"
+#include "user-record.h"
+
+int home_prepare_fscrypt(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup);
+int home_create_fscrypt(UserRecord *h, char **effective_passwords, UserRecord **ret_home);
+
+int home_passwd_fscrypt(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c
new file mode 100644 (file)
index 0000000..0cd5902
--- /dev/null
@@ -0,0 +1,2954 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <libfdisk.h>
+#include <linux/loop.h>
+#include <poll.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+
+#include "blkid-util.h"
+#include "blockdev-util.h"
+#include "chattr-util.h"
+#include "dm-util.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "fs-util.h"
+#include "fsck-util.h"
+#include "homework-luks.h"
+#include "homework-mount.h"
+#include "id128-util.h"
+#include "io-util.h"
+#include "memory-util.h"
+#include "missing_magic.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "openssl-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "process-util.h"
+#include "random-util.h"
+#include "resize-fs.h"
+#include "stat-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+
+/* Round down to the nearest 1K size. Note that Linux generally handles block devices with 512 blocks only,
+ * but actually doesn't accept uneven numbers in many cases. To avoid any confusion around this we'll
+ * strictly round disk sizes down to the next 1K boundary.*/
+#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(1023))
+
+static bool supported_fstype(const char *fstype) {
+        /* Limit the set of supported file systems a bit, as protection against little tested kernel file
+         * systems. Also, we only support the resize ioctls for these file systems. */
+        return STR_IN_SET(fstype, "ext4", "btrfs", "xfs");
+}
+
+static int probe_file_system_by_fd(
+                int fd,
+                char **ret_fstype,
+                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;
+
+        assert(fd >= 0);
+        assert(ret_fstype);
+        assert(ret_uuid);
+
+        b = blkid_new_probe();
+        if (!b)
+                return -ENOMEM;
+
+        errno = 0;
+        r = blkid_probe_set_device(b, fd, 0, 0);
+        if (r != 0)
+                return errno > 0 ? -errno : -ENOMEM;
+
+        (void) blkid_probe_enable_superblocks(b, 1);
+        (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID);
+
+        errno = 0;
+        r = blkid_do_safeprobe(b);
+        if (IN_SET(r, -2, 1)) /* nothing found or ambiguous result */
+                return -ENOPKG;
+        if (r != 0)
+                return errno > 0 ? -errno : -EIO;
+
+        (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+        if (!fstype)
+                return -ENOPKG;
+
+        (void) blkid_probe_lookup_value(b, "UUID", &uuid, NULL);
+        if (!uuid)
+                return -ENOPKG;
+
+        r = sd_id128_from_string(uuid, &id);
+        if (r < 0)
+                return r;
+
+        s = strdup(fstype);
+        if (!s)
+                return -ENOMEM;
+
+        *ret_fstype = TAKE_PTR(s);
+        *ret_uuid = id;
+
+        return 0;
+}
+
+static int probe_file_system_by_path(const char *path, char **ret_fstype, sd_id128_t *ret_uuid) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+        if (fd < 0)
+                return -errno;
+
+        return probe_file_system_by_fd(fd, ret_fstype, ret_uuid);
+}
+
+static int block_get_size_by_fd(int fd, uint64_t *ret) {
+        struct stat st;
+
+        assert(fd >= 0);
+        assert(ret);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (!S_ISBLK(st.st_mode))
+                return -ENOTBLK;
+
+        if (ioctl(fd, BLKGETSIZE64, ret) < 0)
+                return -errno;
+
+        return 0;
+}
+
+static int block_get_size_by_path(const char *path, uint64_t *ret) {
+        _cleanup_close_ int fd = -1;
+
+        fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+        if (fd < 0)
+                return -errno;
+
+        return block_get_size_by_fd(fd, ret);
+}
+
+static int run_fsck(const char *node, const char *fstype) {
+        int r, exit_status;
+        pid_t fsck_pid;
+
+        assert(node);
+        assert(fstype);
+
+        r = fsck_exists(fstype);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if fsck for file system %s exists: %m", fstype);
+        if (r == 0) {
+                log_warning("No fsck for file system %s installed, ignoring.", fstype);
+                return 0;
+        }
+
+        r = safe_fork("(fsck)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &fsck_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Child */
+                execl("/sbin/fsck", "/sbin/fsck", "-aTl", node, NULL);
+                log_error_errno(errno, "Failed to execute fsck: %m");
+                _exit(FSCK_OPERATIONAL_ERROR);
+        }
+
+        exit_status = wait_for_terminate_and_check("fsck", fsck_pid, WAIT_LOG_ABNORMAL);
+        if (exit_status < 0)
+                return exit_status;
+        if ((exit_status & ~FSCK_ERROR_CORRECTED) != 0) {
+                log_warning("fsck failed with exit status %i.", exit_status);
+
+                if ((exit_status & (FSCK_SYSTEM_SHOULD_REBOOT|FSCK_ERRORS_LEFT_UNCORRECTED)) != 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "File system is corrupted, refusing.");
+
+                log_warning("Ignoring fsck error.");
+        }
+
+        log_info("File system check completed.");
+
+        return 1;
+}
+
+static int luks_try_passwords(
+                struct crypt_device *cd,
+                char **passwords,
+                void *volume_key,
+                size_t *volume_key_size) {
+
+        char **pp;
+        int r;
+
+        assert(cd);
+
+        STRV_FOREACH(pp, passwords) {
+                size_t vks = *volume_key_size;
+
+                r = crypt_volume_key_get(
+                                cd,
+                                CRYPT_ANY_SLOT,
+                                volume_key,
+                                &vks,
+                                *pp,
+                                strlen(*pp));
+                if (r >= 0) {
+                        *volume_key_size = vks;
+                        return 0;
+                }
+
+                log_debug_errno(r, "Password %zu didn't work for unlocking LUKS superblock: %m", (size_t) (pp - passwords));
+        }
+
+        return -ENOKEY;
+}
+
+static int luks_setup(
+                const char *node,
+                const char *dm_name,
+                sd_id128_t uuid,
+                const char *cipher,
+                const char *cipher_mode,
+                uint64_t volume_key_size,
+                char **passwords,
+                char **pkcs11_decrypted_passwords,
+                bool discard,
+                struct crypt_device **ret,
+                sd_id128_t *ret_found_uuid,
+                void **ret_volume_key,
+                size_t *ret_volume_key_size) {
+
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_(erase_and_freep) void *vk = NULL;
+        sd_id128_t p;
+        size_t vks;
+        int r;
+
+        assert(node);
+        assert(dm_name);
+        assert(ret);
+
+        r = crypt_init(&cd, node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        r = crypt_load(cd, CRYPT_LUKS2, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load LUKS superblock: %m");
+
+        r = crypt_get_volume_key_size(cd);
+        if (r <= 0)
+                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) {
+                const char *s;
+
+                s = crypt_get_uuid(cd);
+                if (!s)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has no UUID.");
+
+                r = sd_id128_from_string(s, &p);
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has invalid UUID.");
+
+                /* Check that the UUID matches, if specified */
+                if (!sd_id128_is_null(uuid) &&
+                    !sd_id128_equal(uuid, p))
+                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has wrong UUID.");
+        }
+
+        if (cipher && !streq_ptr(cipher, crypt_get_cipher(cd)))
+                return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock declares wrong cipher.");
+
+        if (cipher_mode && !streq_ptr(cipher_mode, crypt_get_cipher_mode(cd)))
+                return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock declares wrong cipher mode.");
+
+        if (volume_key_size != UINT64_MAX && vks != volume_key_size)
+                return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock declares wrong volume key size.");
+
+        vk = malloc(vks);
+        if (!vk)
+                return log_oom();
+
+        r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
+        if (r == -ENOKEY) {
+                r = luks_try_passwords(cd, passwords, vk, &vks);
+                if (r == -ENOKEY)
+                        return log_error_errno(r, "No valid password for LUKS superblock.");
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
+
+        r = crypt_activate_by_volume_key(
+                        cd,
+                        dm_name,
+                        vk, vks,
+                        discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to unlock LUKS superblock: %m");
+
+        log_info("Setting up LUKS device /dev/mapper/%s completed.", dm_name);
+
+        *ret = TAKE_PTR(cd);
+
+        if (ret_found_uuid) /* Return the UUID actually found if the caller wants to know */
+                *ret_found_uuid = p;
+        if (ret_volume_key)
+                *ret_volume_key = TAKE_PTR(vk);
+        if (ret_volume_key_size)
+                *ret_volume_key_size = vks;
+
+        return 0;
+}
+
+static int luks_open(
+                const char *dm_name,
+                char **passwords,
+                char **pkcs11_decrypted_passwords,
+                struct crypt_device **ret,
+                sd_id128_t *ret_found_uuid,
+                void **ret_volume_key,
+                size_t *ret_volume_key_size) {
+
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_(erase_and_freep) void *vk = NULL;
+        sd_id128_t p;
+        size_t vks;
+        int r;
+
+        assert(dm_name);
+        assert(ret);
+
+        /* Opens a LUKS device that is already set up. Re-validates the password while doing so (which also
+         * provides us with the volume key, which we want). */
+
+        r = crypt_init_by_name(&cd, dm_name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
+
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        r = crypt_load(cd, CRYPT_LUKS2, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to load LUKS superblock: %m");
+
+        r = crypt_get_volume_key_size(cd);
+        if (r <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine LUKS volume key size");
+        vks = (size_t) r;
+
+        if (ret_found_uuid) {
+                const char *s;
+
+                s = crypt_get_uuid(cd);
+                if (!s)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has no UUID.");
+
+                r = sd_id128_from_string(s, &p);
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "LUKS superblock has invalid UUID.");
+        }
+
+        vk = malloc(vks);
+        if (!vk)
+                return log_oom();
+
+        r = luks_try_passwords(cd, pkcs11_decrypted_passwords, vk, &vks);
+        if (r == -ENOKEY) {
+                r = luks_try_passwords(cd, passwords, vk, &vks);
+                if (r == -ENOKEY)
+                        return log_error_errno(r, "No valid password for LUKS superblock.");
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
+
+        log_info("Discovered used LUKS device /dev/mapper/%s, and validated password.", dm_name);
+
+        /* This is needed so that crypt_resize() can operate correctly for pre-existing LUKS devices. We need
+         * to tell libcryptsetup the volume key explicitly, so that it is in the kernel keyring. */
+        r = crypt_activate_by_volume_key(cd, NULL, vk, vks, CRYPT_ACTIVATE_KEYRING_KEY);
+        if (r < 0)
+                return log_error_errno(r, "Failed to upload volume key again: %m");
+
+        log_info("Successfully re-activated LUKS device.");
+
+        *ret = TAKE_PTR(cd);
+
+        if (ret_found_uuid)
+                *ret_found_uuid = p;
+        if (ret_volume_key)
+                *ret_volume_key = TAKE_PTR(vk);
+        if (ret_volume_key_size)
+                *ret_volume_key_size = vks;
+
+        return 0;
+}
+
+static int fs_validate(
+                const char *dm_node,
+                sd_id128_t uuid,
+                char **ret_fstype,
+                sd_id128_t *ret_found_uuid) {
+
+        _cleanup_free_ char *fstype = NULL;
+        sd_id128_t u;
+        int r;
+
+        assert(dm_node);
+        assert(ret_fstype);
+
+        r = probe_file_system_by_path(dm_node, &fstype, &u);
+        if (r < 0)
+                return log_error_errno(r, "Failed to probe file system: %m");
+
+        /* Limit the set of supported file systems a bit, as protection against little tested kernel file
+         * systems. Also, we only support the resize ioctls for these file systems. */
+        if (!supported_fstype(fstype))
+                return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Image contains unsupported file system: %s", strna(fstype));
+
+        if (!sd_id128_is_null(uuid) &&
+            !sd_id128_equal(uuid, u))
+                return log_error_errno(SYNTHETIC_ERRNO(EMEDIUMTYPE), "File system has wrong UUID.");
+
+        log_info("Probing file system completed (found %s).", fstype);
+
+        *ret_fstype = TAKE_PTR(fstype);
+
+        if (ret_found_uuid) /* Return the UUID actually found if the caller wants to know */
+                *ret_found_uuid = u;
+
+        return 0;
+}
+
+static int make_dm_names(const char *user_name, char **ret_dm_name, char **ret_dm_node) {
+        _cleanup_free_ char *name = NULL, *node = NULL;
+
+        assert(user_name);
+        assert(ret_dm_name);
+        assert(ret_dm_node);
+
+        name = strjoin("home-", user_name);
+        if (!name)
+                return log_oom();
+
+        node = path_join("/dev/mapper/", name);
+        if (!node)
+                return log_oom();
+
+        *ret_dm_name = TAKE_PTR(name);
+        *ret_dm_node = TAKE_PTR(node);
+        return 0;
+}
+
+static int luks_validate(
+                int fd,
+                const char *label,
+                sd_id128_t partition_uuid,
+                sd_id128_t *ret_partition_uuid,
+                uint64_t *ret_offset,
+                uint64_t *ret_size) {
+
+        _cleanup_(blkid_free_probep) blkid_probe b = NULL;
+        sd_id128_t found_partition_uuid = SD_ID128_NULL;
+        const char *fstype = NULL, *pttype = NULL;
+        blkid_loff_t offset = 0, size = 0;
+        blkid_partlist pl;
+        bool found = false;
+        int r, i, n;
+
+        assert(fd >= 0);
+        assert(label);
+        assert(ret_offset);
+        assert(ret_size);
+
+        b = blkid_new_probe();
+        if (!b)
+                return -ENOMEM;
+
+        errno = 0;
+        r = blkid_probe_set_device(b, fd, 0, 0);
+        if (r != 0)
+                return errno > 0 ? -errno : -ENOMEM;
+
+        (void) blkid_probe_enable_superblocks(b, 1);
+        (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE);
+        (void) blkid_probe_enable_partitions(b, 1);
+        (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
+
+        errno = 0;
+        r = blkid_do_safeprobe(b);
+        if (IN_SET(r, -2, 1)) /* nothing found or ambiguous result */
+                return -ENOPKG;
+        if (r != 0)
+                return errno > 0 ? -errno : -EIO;
+
+        (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL);
+        if (streq_ptr(fstype, "crypto_LUKS")) {
+                /* Directly a LUKS image */
+                *ret_offset = 0;
+                *ret_size = UINT64_MAX; /* full disk */
+                *ret_partition_uuid = SD_ID128_NULL;
+                return 0;
+        } else if (fstype)
+                return -ENOPKG;
+
+        (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
+        if (!streq_ptr(pttype, "gpt"))
+                return -ENOPKG;
+
+        errno = 0;
+        pl = blkid_probe_get_partitions(b);
+        if (!pl)
+                return errno > 0 ? -errno : -ENOMEM;
+
+        errno = 0;
+        n = blkid_partlist_numof_partitions(pl);
+        if (n < 0)
+                return errno > 0 ? -errno : -EIO;
+
+        for (i = 0; i < n; i++) {
+                blkid_partition pp;
+                sd_id128_t id;
+                const char *sid;
+
+                errno = 0;
+                pp = blkid_partlist_get_partition(pl, i);
+                if (!pp)
+                        return errno > 0 ? -errno : -EIO;
+
+                if (!streq_ptr(blkid_partition_get_type_string(pp), "773f91ef-66d4-49b5-bd83-d683bf40ad16"))
+                        continue;
+
+                if (!streq_ptr(blkid_partition_get_name(pp), label))
+                        continue;
+
+                sid = blkid_partition_get_uuid(pp);
+                if (sid) {
+                        r = sd_id128_from_string(sid, &id);
+                        if (r < 0)
+                                log_debug_errno(r, "Couldn't parse partition UUID %s, weird: %m", sid);
+
+                        if (!sd_id128_is_null(partition_uuid) && !sd_id128_equal(id, partition_uuid))
+                                continue;
+                }
+
+                if (found)
+                        return -ENOPKG;
+
+                offset = blkid_partition_get_start(pp);
+                size = blkid_partition_get_size(pp);
+                found_partition_uuid = id;
+
+                found = true;
+        }
+
+        if (!found)
+                return -ENOPKG;
+
+        if (offset < 0)
+                return -EINVAL;
+        if ((uint64_t) offset > UINT64_MAX / 512U)
+                return -EINVAL;
+        if (size <= 0)
+                return -EINVAL;
+        if ((uint64_t) size > UINT64_MAX / 512U)
+                return -EINVAL;
+
+        *ret_offset = offset * 512U;
+        *ret_size = size * 512U;
+        *ret_partition_uuid = found_partition_uuid;
+
+        return 0;
+}
+
+static int crypt_device_to_evp_cipher(struct crypt_device *cd, const EVP_CIPHER **ret) {
+        _cleanup_free_ char *cipher_name = NULL;
+        const char *cipher, *cipher_mode, *e;
+        size_t key_size, key_bits;
+        const EVP_CIPHER *cc;
+        int r;
+
+        assert(cd);
+
+        /* Let's find the right OpenSSL EVP_CIPHER object that matches the encryption settings of the LUKS
+         * device */
+
+        cipher = crypt_get_cipher(cd);
+        if (!cipher)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot get cipher from LUKS device.");
+
+        cipher_mode = crypt_get_cipher_mode(cd);
+        if (!cipher_mode)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot get cipher mode from LUKS device.");
+
+        e = strchr(cipher_mode, '-');
+        if (e)
+                cipher_mode = strndupa(cipher_mode, e - cipher_mode);
+
+        r = crypt_get_volume_key_size(cd);
+        if (r <= 0)
+                return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Cannot get volume key size from LUKS device.");
+
+        key_size = r;
+        key_bits = key_size * 8;
+        if (streq(cipher_mode, "xts"))
+                key_bits /= 2;
+
+        if (asprintf(&cipher_name, "%s-%zu-%s", cipher, key_bits, cipher_mode) < 0)
+                return log_oom();
+
+        cc = EVP_get_cipherbyname(cipher_name);
+        if (!cc)
+                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Selected cipher mode '%s' not supported, can't encrypt JSON record.", cipher_name);
+
+        /* Verify that our key length calculations match what OpenSSL thinks */
+        r = EVP_CIPHER_key_length(cc);
+        if (r < 0 || (uint64_t) r != key_size)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key size of selected cipher doesn't meet out expectations.");
+
+        *ret = cc;
+        return 0;
+}
+
+static int luks_validate_home_record(
+                struct crypt_device *cd,
+                UserRecord *h,
+                const void *volume_key,
+                char ***pkcs11_decrypted_passwords,
+                UserRecord **ret_luks_home_record) {
+
+        int r, token;
+
+        assert(cd);
+        assert(h);
+
+        for (token = 0;; token++) {
+                _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *rr = NULL;
+                _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
+                _cleanup_(user_record_unrefp) UserRecord *lhr = NULL;
+                _cleanup_free_ void *encrypted = NULL, *iv = NULL;
+                size_t decrypted_size, encrypted_size, iv_size;
+                int decrypted_size_out1, decrypted_size_out2;
+                _cleanup_free_ char *decrypted = NULL;
+                const char *text, *type;
+                crypt_token_info state;
+                JsonVariant *jr, *jiv;
+                unsigned line, column;
+                const EVP_CIPHER *cc;
+
+                state = crypt_token_status(cd, token, &type);
+                if (state == CRYPT_TOKEN_INACTIVE) /* First unconfigured token, give up */
+                        break;
+                if (IN_SET(state, CRYPT_TOKEN_INTERNAL, CRYPT_TOKEN_INTERNAL_UNKNOWN, CRYPT_TOKEN_EXTERNAL))
+                        continue;
+                if (state != CRYPT_TOKEN_EXTERNAL_UNKNOWN)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected token state of token %i: %i", token, (int) state);
+
+                if (!streq(type, "systemd-homed"))
+                        continue;
+
+                r = crypt_token_json_get(cd, token, &text);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read LUKS token %i: %m", token);
+
+                r = json_parse(text, JSON_PARSE_SENSITIVE, &v, &line, &column);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse LUKS token JSON data %u:%u: %m", line, column);
+
+                jr = json_variant_by_key(v, "record");
+                if (!jr)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS token lacks 'record' field.");
+                jiv = json_variant_by_key(v, "iv");
+                if (!jiv)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS token lacks 'iv' field.");
+
+                r = json_variant_unbase64(jr, &encrypted, &encrypted_size);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to base64 decode record: %m");
+
+                r = json_variant_unbase64(jiv, &iv, &iv_size);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to base64 decode IV: %m");
+
+                r = crypt_device_to_evp_cipher(cd, &cc);
+                if (r < 0)
+                        return r;
+                if (iv_size > INT_MAX || EVP_CIPHER_iv_length(cc) != (int) iv_size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "IV size doesn't match.");
+
+                context = EVP_CIPHER_CTX_new();
+                if (!context)
+                        return log_oom();
+
+                if (EVP_DecryptInit_ex(context, cc, NULL, volume_key, iv) != 1)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context.");
+
+                decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2;
+                decrypted = new(char, decrypted_size);
+                if (!decrypted)
+                        return log_oom();
+
+                if (EVP_DecryptUpdate(context, (uint8_t*) decrypted, &decrypted_size_out1, encrypted, encrypted_size) != 1)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decrypt JSON record.");
+
+                assert((size_t) decrypted_size_out1 <= decrypted_size);
+
+                if (EVP_DecryptFinal_ex(context, (uint8_t*) decrypted + decrypted_size_out1, &decrypted_size_out2) != 1)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finish decryption of JSON record.");
+
+                assert((size_t) decrypted_size_out1 + (size_t) decrypted_size_out2 < decrypted_size);
+                decrypted_size = (size_t) decrypted_size_out1 + (size_t) decrypted_size_out2;
+
+                if (memchr(decrypted, 0, decrypted_size))
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Inner NUL byte in JSON record, refusing.");
+
+                decrypted[decrypted_size] = 0;
+
+                r = json_parse(decrypted, JSON_PARSE_SENSITIVE, &rr, NULL, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse decrypted JSON record, refusing.");
+
+                lhr = user_record_new();
+                if (!lhr)
+                        return log_oom();
+
+                r = user_record_load(lhr, rr, USER_RECORD_LOAD_EMBEDDED);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse user record: %m");
+
+                if (!user_record_compatible(h, lhr))
+                        return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "LUKS home record not compatible with host record, refusing.");
+
+                r = user_record_authenticate(lhr, h, pkcs11_decrypted_passwords);
+                if (r < 0)
+                        return r;
+
+                *ret_luks_home_record = TAKE_PTR(lhr);
+                return 0;
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Couldn't find home record in LUKS2 header, refusing.");
+}
+
+static int format_luks_token_text(
+                struct crypt_device *cd,
+                UserRecord *hr,
+                const void *volume_key,
+                char **ret) {
+
+        int r, encrypted_size_out1 = 0, encrypted_size_out2 = 0, iv_size, key_size;
+        _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *context = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        _cleanup_free_ void *iv = NULL, *encrypted = NULL;
+        size_t text_length, encrypted_size;
+        _cleanup_free_ char *text = NULL;
+        const EVP_CIPHER *cc;
+
+        assert(cd);
+        assert(hr);
+        assert(volume_key);
+        assert(ret);
+
+        r = crypt_device_to_evp_cipher(cd, &cc);
+        if (r < 0)
+                return r;
+
+        key_size = EVP_CIPHER_key_length(cc);
+        iv_size = EVP_CIPHER_iv_length(cc);
+
+        if (iv_size > 0) {
+                iv = malloc(iv_size);
+                if (!iv)
+                        return log_oom();
+
+                r = genuine_random_bytes(iv, iv_size, RANDOM_BLOCK);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to generate IV: %m");
+        }
+
+        context = EVP_CIPHER_CTX_new();
+        if (!context)
+                return log_oom();
+
+        if (EVP_EncryptInit_ex(context, cc, NULL, volume_key, iv) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context.");
+
+        r = json_variant_format(hr->json, 0, &text);
+        if (r < 0)
+                return log_error_errno(r,"Failed to format user record for LUKS: %m");
+
+        text_length = strlen(text);
+        encrypted_size = text_length + 2*key_size - 1;
+
+        encrypted = malloc(encrypted_size);
+        if (!encrypted)
+                return log_oom();
+
+        if (EVP_EncryptUpdate(context, encrypted, &encrypted_size_out1, (uint8_t*) text, text_length) != 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to encrypt JSON record.");
+
+        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. ");
+
+        assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 <= encrypted_size);
+
+        r = json_build(&v,
+                       JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("type", JSON_BUILD_STRING("systemd-homed")),
+                                       JSON_BUILD_PAIR("keyslots", JSON_BUILD_EMPTY_ARRAY),
+                                       JSON_BUILD_PAIR("record", JSON_BUILD_BASE64(encrypted, encrypted_size_out1 + encrypted_size_out2)),
+                                       JSON_BUILD_PAIR("iv", JSON_BUILD_BASE64(iv, iv_size))));
+        if (r < 0)
+                return log_error_errno(r, "Failed to prepare LUKS JSON token object: %m");
+
+        r = json_variant_format(v, 0, ret);
+        if (r < 0)
+                return log_error_errno(r, "Failed to format encrypted user record for LUKS: %m");
+
+        return 0;
+}
+
+int home_store_header_identity_luks(
+                UserRecord *h,
+                HomeSetup *setup,
+                UserRecord *old_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *header_home = NULL;
+        _cleanup_free_ char *text = NULL;
+        int token = 0, r;
+
+        assert(h);
+
+        if (!setup->crypt_device)
+                return 0;
+
+        assert(setup->volume_key);
+
+        /* Let's store the user's identity record in the LUKS2 "token" header data fields, in an encrypted
+         * fashion. Why that? If we'd rely on the record being embedded in the payload file system itself we
+         * would have to mount the file system before we can validate the JSON record, its signatures and
+         * whether it matches what we are looking for. However, kernel file system implementations are
+         * generally not ready to be used on untrusted media. Hence let's store the record independently of
+         * the file system, so that we can validate it first, and only then mount the file system. To keep
+         * things simple we use the same encryption settings for this record as for the file system itself. */
+
+        r = user_record_clone(h, USER_RECORD_EXTRACT_EMBEDDED, &header_home);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine new header record: %m");
+
+        if (old_home && user_record_equal(old_home, header_home)) {
+                log_debug("Not updating header home record.");
+                return 0;
+        }
+
+        r = format_luks_token_text(setup->crypt_device, header_home, setup->volume_key, &text);
+        if (r < 0)
+                return r;
+
+        for (;; token++) {
+                crypt_token_info state;
+                const char *type;
+
+                state = crypt_token_status(setup->crypt_device, token, &type);
+                if (state == CRYPT_TOKEN_INACTIVE) /* First unconfigured token, we are done */
+                        break;
+                if (IN_SET(state, CRYPT_TOKEN_INTERNAL, CRYPT_TOKEN_INTERNAL_UNKNOWN, CRYPT_TOKEN_EXTERNAL))
+                        continue; /* Not ours */
+                if (state != CRYPT_TOKEN_EXTERNAL_UNKNOWN)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected token state of token %i: %i", token, (int) state);
+
+                if (!streq(type, "systemd-homed"))
+                        continue;
+
+                r = crypt_token_json_set(setup->crypt_device, token, text);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set JSON token for slot %i: %m", token);
+
+                /* Now, let's free the text so that for all further matching tokens we all crypt_json_token_set()
+                 * with a NULL text in order to invalidate the tokens. */
+                text = mfree(text);
+                token++;
+        }
+
+        if (text)
+                return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Didn't find any record token to update.");
+
+        log_info("Wrote LUKS header user record.");
+
+        return 1;
+}
+
+static int run_fitrim(int root_fd) {
+        char buf[FORMAT_BYTES_MAX];
+        struct fstrim_range range = {
+                .len = UINT64_MAX,
+        };
+
+        /* If discarding is on, discard everything right after mounting, so that the discard setting takes
+         * effect on activation. */
+
+        assert(root_fd >= 0);
+
+        if (ioctl(root_fd, FITRIM, &range) < 0) {
+                if (IN_SET(errno, ENOTTY, EOPNOTSUPP, EBADF)) {
+                        log_debug_errno(errno, "File system does not support FITRIM, not trimming.");
+                        return 0;
+                }
+
+                return log_warning_errno(errno, "Failed to invoke FITRIM, ignoring: %m");
+        }
+
+        log_info("Discarded unused %s.",
+                 format_bytes(buf, sizeof(buf), range.len));
+        return 1;
+}
+
+static int run_fallocate(int backing_fd, const struct stat *st) {
+        char buf[FORMAT_BYTES_MAX];
+
+        assert(backing_fd >= 0);
+        assert(st);
+
+        /* If discarding is off, let's allocate the whole image before mounting, so that the setting takes
+         * effect on activation */
+
+        if (!S_ISREG(st->st_mode))
+                return 0;
+
+        if (st->st_blocks >= DIV_ROUND_UP(st->st_size, 512)) {
+                log_info("Backing file is fully allocated already.");
+                return 0;
+        }
+
+        if (fallocate(backing_fd, FALLOC_FL_KEEP_SIZE, 0, st->st_size) < 0) {
+
+                if (ERRNO_IS_NOT_SUPPORTED(errno)) {
+                        log_debug_errno(errno, "fallocate() not supported on file system, ignoring.");
+                        return 0;
+                }
+
+                if (ERRNO_IS_DISK_SPACE(errno)) {
+                        log_debug_errno(errno, "Not enough disk space to fully allocate home.");
+                        return -ENOSPC; /* make recognizable */
+                }
+
+                return log_error_errno(errno, "Failed to allocate backing file blocks: %m");
+        }
+
+        log_info("Allocated additional %s.",
+                 format_bytes(buf, sizeof(buf), (DIV_ROUND_UP(st->st_size, 512) - st->st_blocks) * 512));
+        return 1;
+}
+
+int home_prepare_luks(
+                UserRecord *h,
+                bool already_activated,
+                const char *force_image_path,
+                char ***pkcs11_decrypted_passwords,
+                HomeSetup *setup,
+                UserRecord **ret_luks_home) {
+
+        sd_id128_t found_partition_uuid, found_luks_uuid, found_fs_uuid;
+        _cleanup_(user_record_unrefp) UserRecord *luks_home = NULL;
+        _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        bool dm_activated = false, mounted = false;
+        _cleanup_close_ int root_fd = -1;
+        size_t volume_key_size = 0;
+        uint64_t offset, size;
+        int r;
+
+        assert(h);
+        assert(setup);
+        assert(setup->dm_name);
+        assert(setup->dm_node);
+
+        assert(user_record_storage(h) == USER_LUKS);
+
+        if (already_activated) {
+                struct loop_info64 info;
+                const char *n;
+
+                r = luks_open(setup->dm_name,
+                              h->password,
+                              pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
+                              &cd,
+                              &found_luks_uuid,
+                              &volume_key,
+                              &volume_key_size);
+                if (r < 0)
+                        return r;
+
+                r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
+                if (r < 0)
+                        return r;
+
+                n = crypt_get_device_name(cd);
+                if (!n)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine backing device for DM %s.", setup->dm_name);
+
+                r = loop_device_open(n, O_RDWR, &loop);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to open loopback device %s: %m", n);
+
+                if (ioctl(loop->fd, LOOP_GET_STATUS64, &info) < 0) {
+                        _cleanup_free_ char *sysfs = NULL;
+                        struct stat st;
+
+                        if (!IN_SET(errno, ENOTTY, EINVAL))
+                                return log_error_errno(errno, "Failed to get block device metrics of %s: %m", n);
+
+                        if (ioctl(loop->fd, BLKGETSIZE64, &size) < 0)
+                                return log_error_errno(r, "Failed to read block device size of %s: %m", n);
+
+                        if (fstat(loop->fd, &st) < 0)
+                                return log_error_errno(r, "Failed to stat block device %s: %m", n);
+                        assert(S_ISBLK(st.st_mode));
+
+                        if (asprintf(&sysfs, "/sys/dev/block/%u:%u/partition", major(st.st_rdev), minor(st.st_rdev)) < 0)
+                                return log_oom();
+
+                        if (access(sysfs, F_OK) < 0) {
+                                if (errno != ENOENT)
+                                        return log_error_errno(errno, "Failed to determine whether %s exists: %m", sysfs);
+
+                                offset = 0;
+                        } else {
+                                _cleanup_free_ char *buffer = NULL;
+
+                                if (asprintf(&sysfs, "/sys/dev/block/%u:%u/start", major(st.st_rdev), minor(st.st_rdev)) < 0)
+                                        return log_oom();
+
+                                r = read_one_line_file(sysfs, &buffer);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to read partition start offset: %m");
+
+                                r = safe_atou64(buffer, &offset);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse partition start offset: %m");
+
+                                if (offset > UINT64_MAX / 512U)
+                                        return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Offset too large for 64 byte range, refusing.");
+
+                                offset *= 512U;
+                        }
+                } else {
+                        offset = info.lo_offset;
+                        size = info.lo_sizelimit;
+                }
+
+                found_partition_uuid = found_fs_uuid = SD_ID128_NULL;
+
+                log_info("Discovered used loopback device %s.", loop->node);
+
+                root_fd = open(user_record_home_directory(h), O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+                if (root_fd < 0) {
+                        r = log_error_errno(r, "Failed to open home directory: %m");
+                        goto fail;
+                }
+        } else {
+                _cleanup_free_ char *fstype = NULL, *subdir = NULL;
+                _cleanup_close_ int fd = -1;
+                const char *ip;
+                struct stat st;
+
+                ip = force_image_path ?: user_record_image_path(h);
+
+                subdir = path_join("/run/systemd/user-home-mount/", user_record_user_name_and_realm(h));
+                if (!subdir)
+                        return log_oom();
+
+                fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                if (fd < 0)
+                        return log_error_errno(errno, "Failed to open image file %s: %m", ip);
+
+                if (fstat(fd, &st) < 0)
+                        return log_error_errno(errno, "Failed to fstat() image file: %m");
+                if (!S_ISREG(st.st_mode) && !S_ISBLK(st.st_mode))
+                        return log_error_errno(errno, "Image file %s is not a regular file or block device: %m", ip);
+
+                r = luks_validate(fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to validate disk label: %m");
+
+                if (!user_record_luks_discard(h)) {
+                        r = run_fallocate(fd, &st);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = loop_device_make(fd, O_RDWR, offset, size, 0, &loop);
+                if (r == -ENOENT) {
+                        log_error_errno(r, "Loopback block device support is not available on this system.");
+                        return -ENOLINK; /* make recognizable */
+                }
+                if (r < 0)
+                        return log_error_errno(r, "Failed to allocate loopback context: %m");
+
+                log_info("Setting up loopback device %s completed.", loop->node ?: ip);
+
+                r = luks_setup(loop->node ?: ip,
+                               setup->dm_name,
+                               h->luks_uuid,
+                               h->luks_cipher,
+                               h->luks_cipher_mode,
+                               h->luks_volume_key_size,
+                               h->password,
+                               pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL,
+                               user_record_luks_discard(h),
+                               &cd,
+                               &found_luks_uuid,
+                               &volume_key,
+                               &volume_key_size);
+                if (r < 0)
+                        return r;
+
+                dm_activated = true;
+
+                r = luks_validate_home_record(cd, h, volume_key, pkcs11_decrypted_passwords, &luks_home);
+                if (r < 0)
+                        goto fail;
+
+                r = fs_validate(setup->dm_node, h->file_system_uuid, &fstype, &found_fs_uuid);
+                if (r < 0)
+                        goto fail;
+
+                r = run_fsck(setup->dm_node, fstype);
+                if (r < 0)
+                        goto fail;
+
+                r = home_unshare_and_mount(setup->dm_node, fstype, user_record_luks_discard(h));
+                if (r < 0)
+                        goto fail;
+
+                mounted = true;
+
+                root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+                if (root_fd < 0) {
+                        r = log_error_errno(r, "Failed to open home directory: %m");
+                        goto fail;
+                }
+
+                if (user_record_luks_discard(h))
+                        (void) run_fitrim(root_fd);
+        }
+
+        setup->loop = TAKE_PTR(loop);
+        setup->crypt_device = TAKE_PTR(cd);
+        setup->root_fd = TAKE_FD(root_fd);
+        setup->found_partition_uuid = found_partition_uuid;
+        setup->found_luks_uuid = found_luks_uuid;
+        setup->found_fs_uuid = found_fs_uuid;
+        setup->partition_offset = offset;
+        setup->partition_size = size;
+        setup->volume_key = TAKE_PTR(volume_key);
+        setup->volume_key_size = volume_key_size;
+
+        setup->undo_mount = mounted;
+        setup->undo_dm = dm_activated;
+
+        if (ret_luks_home)
+                *ret_luks_home = TAKE_PTR(luks_home);
+
+        return 0;
+
+fail:
+        if (mounted)
+                (void) umount_verbose("/run/systemd/user-home-mount");
+
+        if (dm_activated)
+                (void) crypt_deactivate(cd, setup->dm_name);
+
+        return r;
+}
+
+static void print_size_summary(uint64_t host_size, uint64_t encrypted_size, struct statfs *sfs) {
+        char buffer1[FORMAT_BYTES_MAX], buffer2[FORMAT_BYTES_MAX], buffer3[FORMAT_BYTES_MAX], buffer4[FORMAT_BYTES_MAX];
+
+        assert(sfs);
+
+        log_info("Image size is %s, file system size is %s, file system payload size is %s, file system free is %s.",
+                 format_bytes(buffer1, sizeof(buffer1), host_size),
+                 format_bytes(buffer2, sizeof(buffer2), encrypted_size),
+                 format_bytes(buffer3, sizeof(buffer3), (uint64_t) sfs->f_blocks * (uint64_t) sfs->f_frsize),
+                 format_bytes(buffer4, sizeof(buffer4), (uint64_t) sfs->f_bfree * (uint64_t) sfs->f_frsize));
+}
+
+int home_activate_luks(
+                UserRecord *h,
+                char ***pkcs11_decrypted_passwords,
+                UserRecord **ret_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *luks_home_record = NULL;
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        uint64_t host_size, encrypted_size;
+        const char *hdo, *hd;
+        struct statfs sfs;
+        int r;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_LUKS);
+        assert(ret_home);
+
+        assert_se(hdo = user_record_home_directory(h));
+        hd = strdupa(hdo); /* copy the string out, since it might change later in the home record object */
+
+        r = make_dm_names(h->user_name, &setup.dm_name, &setup.dm_node);
+        if (r < 0)
+                return r;
+
+        r = access(setup.dm_node, F_OK);
+        if (r < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to determine whether %s exists: %m", setup.dm_node);
+        } else
+                return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device mapper device %s already exists, refusing.", setup.dm_node);
+
+        r = home_prepare_luks(
+                        h,
+                        false,
+                        NULL,
+                        pkcs11_decrypted_passwords,
+                        &setup,
+                        &luks_home_record);
+        if (r < 0)
+                return r;
+
+        r = block_get_size_by_fd(setup.loop->fd, &host_size);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get loopback block device size: %m");
+
+        r = block_get_size_by_path(setup.dm_node, &encrypted_size);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get LUKS block device size: %m");
+
+        r = home_refresh(
+                        h,
+                        &setup,
+                        luks_home_record,
+                        pkcs11_decrypted_passwords,
+                        &sfs,
+                        &new_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, &setup);
+        if (r < 0)
+                return r;
+
+        setup.root_fd = safe_close(setup.root_fd);
+
+        r = home_move_mount(user_record_user_name_and_realm(h), hd);
+        if (r < 0)
+                return r;
+
+        setup.undo_mount = false;
+
+        loop_device_relinquish(setup.loop);
+
+        r = dm_deferred_remove(setup.dm_name);
+        if (r < 0)
+                log_warning_errno(r, "Failed to relinquish dm device, ignoring: %m");
+
+        setup.undo_dm = false;
+
+        log_info("Everything completed.");
+
+        print_size_summary(host_size, encrypted_size, &sfs);
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1;
+}
+
+int home_deactivate_luks(UserRecord *h) {
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
+        int r;
+
+        /* Note that the DM device and loopback device are set to auto-detach, hence strictly speaking we
+         * don't have to explicitly have to detach them. However, we do that nonetheless (in case of the DM
+         * device), to avoid races: by explicitly detaching them we know when the detaching is complete. We
+         * don't bother about the loopback device because unlike the DM device it doesn't have a fixed
+         * name. */
+
+        r = make_dm_names(h->user_name, &dm_name, &dm_node);
+        if (r < 0)
+                return r;
+
+        r = crypt_init_by_name(&cd, dm_name);
+        if (IN_SET(r, -ENODEV, -EINVAL, -ENOENT)) {
+                log_debug_errno(r, "LUKS device %s is already detached.", dm_name);
+                return false;
+        } else if (r < 0)
+                return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
+
+        log_info("Discovered used LUKS device %s.", dm_node);
+
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        r = crypt_deactivate(cd, dm_name);
+        if (IN_SET(r, -ENODEV, -EINVAL, -ENOENT))
+                log_debug_errno(r, "LUKS device %s is already detached.", dm_node);
+        else if (r < 0)
+                return log_info_errno(r, "LUKS device %s couldn't be deactivated: %m", dm_node);
+
+        log_info("LUKS device detaching completed.");
+        return true;
+}
+
+static int run_mkfs(
+                const char *node,
+                const char *fstype,
+                const char *label,
+                sd_id128_t uuid,
+                bool discard) {
+
+        int r;
+
+        assert(node);
+        assert(fstype);
+        assert(label);
+
+        r = mkfs_exists(fstype);
+        if (r < 0)
+                return log_error_errno(r, "Failed to check if mkfs for file system %s exists: %m", fstype);
+        if (r == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Nt mkfs for file system %s installed.", fstype);
+
+        r = safe_fork("(mkfs)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, NULL);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                const char *mkfs;
+                char suuid[37];
+
+                /* Child */
+
+                mkfs = strjoina("mkfs.", fstype);
+                id128_to_uuid_string(uuid, suuid);
+
+                if (streq(fstype, "ext4"))
+                        execlp(mkfs, mkfs,
+                               "-L", label,
+                               "-U", suuid,
+                               "-I", "256",
+                               "-O", "has_journal",
+                               "-m", "0",
+                               "-E", discard ? "lazy_itable_init=1,discard" : "lazy_itable_init=1,nodiscard",
+                               node, NULL);
+                else if (streq(fstype, "btrfs")) {
+                        if (discard)
+                                execlp(mkfs, mkfs, "-L", label, "-U", suuid, node, NULL);
+                        else
+                                execlp(mkfs, mkfs, "-L", label, "-U", suuid, "--nodiscard", node, NULL);
+                } else if (streq(fstype, "xfs")) {
+                        const char *j;
+
+                        j = strjoina("uuid=", suuid);
+                        if (discard)
+                                execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", node, NULL);
+                        else
+                                execlp(mkfs, mkfs, "-L", label, "-m", j, "-m", "reflink=1", "-K", node, NULL);
+                } else {
+                        log_error("Cannot make file system: %s", fstype);
+                        _exit(EXIT_FAILURE);
+                }
+
+                log_error_errno(errno, "Failed to execute %s: %m", mkfs);
+                _exit(EXIT_FAILURE);
+        }
+
+        return 0;
+}
+
+static struct crypt_pbkdf_type* build_good_pbkdf(struct crypt_pbkdf_type *buffer, UserRecord *hr) {
+        assert(buffer);
+        assert(hr);
+
+        *buffer = (struct crypt_pbkdf_type) {
+                .hash = user_record_luks_pbkdf_hash_algorithm(hr),
+                .type = user_record_luks_pbkdf_type(hr),
+                .time_ms = user_record_luks_pbkdf_time_cost_usec(hr) / USEC_PER_MSEC,
+                .max_memory_kb = user_record_luks_pbkdf_memory_cost(hr) / 1024,
+                .parallel_threads = user_record_luks_pbkdf_parallel_threads(hr),
+        };
+
+        return buffer;
+}
+
+static struct crypt_pbkdf_type* build_minimal_pbkdf(struct crypt_pbkdf_type *buffer, UserRecord *hr) {
+        assert(buffer);
+        assert(hr);
+
+        /* For PKCS#11 derived keys (which are generated randomly and are of high quality already) we use a
+         * minimal PBKDF */
+        *buffer = (struct crypt_pbkdf_type) {
+                .hash = user_record_luks_pbkdf_hash_algorithm(hr),
+                .type = CRYPT_KDF_PBKDF2,
+                .iterations = 1,
+                .time_ms = 1,
+        };
+
+        return buffer;
+}
+
+static int luks_format(
+                const char *node,
+                const char *dm_name,
+                sd_id128_t uuid,
+                const char *label,
+                char **pkcs11_decrypted_passwords,
+                char **effective_passwords,
+                bool discard,
+                UserRecord *hr,
+                struct crypt_device **ret) {
+
+        _cleanup_(user_record_unrefp) UserRecord *reduced = NULL;
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf;
+        _cleanup_free_ char *text = NULL;
+        size_t volume_key_size;
+        char suuid[37], **pp;
+        int slot = 0, r;
+
+        assert(node);
+        assert(dm_name);
+        assert(hr);
+        assert(ret);
+
+        r = crypt_init(&cd, node);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate libcryptsetup context: %m");
+
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        /* Normally we'd, just leave volume key generation to libcryptsetup. However, we can't, since we
+         * can't extract the volume key from the library again, but we need it in order to encrypt the JSON
+         * record. Hence, let's generate it on our own, so that we can keep track of it. */
+
+        volume_key_size = user_record_luks_volume_key_size(hr);
+        volume_key = malloc(volume_key_size);
+        if (!volume_key)
+                return log_oom();
+
+        r = genuine_random_bytes(volume_key, volume_key_size, RANDOM_BLOCK);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate volume key: %m");
+
+#if HAVE_CRYPT_SET_METADATA_SIZE
+        /* Increase the metadata space to 4M, the largest LUKS2 supports */
+        r = crypt_set_metadata_size(cd, 4096U*1024U, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to change LUKS2 metadata size: %m");
+#endif
+
+        build_good_pbkdf(&good_pbkdf, hr);
+        build_minimal_pbkdf(&minimal_pbkdf, hr);
+
+        r = crypt_format(cd,
+                         CRYPT_LUKS2,
+                         user_record_luks_cipher(hr),
+                         user_record_luks_cipher_mode(hr),
+                         id128_to_uuid_string(uuid, suuid),
+                         volume_key,
+                         volume_key_size,
+                         &(struct crypt_params_luks2) {
+                                 .label = label,
+                                 .subsystem = "systemd-home",
+                                 .sector_size = 512U,
+                                 .pbkdf = &good_pbkdf,
+                         });
+        if (r < 0)
+                return log_error_errno(r, "Failed to format LUKS image: %m");
+
+        log_info("LUKS formatting completed.");
+
+        STRV_FOREACH(pp, effective_passwords) {
+
+                if (strv_contains(pkcs11_decrypted_passwords, *pp)) {
+                        log_debug("Using minimal PBKDF for slot %i", slot);
+                        r = crypt_set_pbkdf_type(cd, &minimal_pbkdf);
+                } else {
+                        log_debug("Using good PBKDF for slot %i", slot);
+                        r = crypt_set_pbkdf_type(cd, &good_pbkdf);
+                }
+                if (r < 0)
+                        return log_error_errno(r, "Failed to tweak PBKDF for slot %i: %m", slot);
+
+                r = crypt_keyslot_add_by_volume_key(
+                                cd,
+                                slot,
+                                volume_key,
+                                volume_key_size,
+                                *pp,
+                                strlen(*pp));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set up LUKS password for slot %i: %m", slot);
+
+                log_info("Writing password to LUKS keyslot %i completed.", slot);
+                slot++;
+        }
+
+        r = crypt_activate_by_volume_key(
+                        cd,
+                        dm_name,
+                        volume_key,
+                        volume_key_size,
+                        discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to activate LUKS superblock: %m");
+
+        log_info("LUKS activation by volume key succeeded.");
+
+        r = user_record_clone(hr, USER_RECORD_EXTRACT_EMBEDDED, &reduced);
+        if (r < 0)
+                return log_error_errno(r, "Failed to prepare home record for LUKS: %m");
+
+        r = format_luks_token_text(cd, reduced, volume_key, &text);
+        if (r < 0)
+                return r;
+
+        r = crypt_token_json_set(cd, CRYPT_ANY_TOKEN, text);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set LUKS JSON token: %m");
+
+        log_info("Writing user record as LUKS token completed.");
+
+        if (ret)
+                *ret = TAKE_PTR(cd);
+
+        return 0;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_context*, fdisk_unref_context);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_partition*, fdisk_unref_partition);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_parttype*, fdisk_unref_parttype);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_table*, fdisk_unref_table);
+
+static int make_partition_table(
+                int fd,
+                const char *label,
+                sd_id128_t uuid,
+                uint64_t *ret_offset,
+                uint64_t *ret_size,
+                sd_id128_t *ret_disk_uuid) {
+
+        _cleanup_(fdisk_unref_partitionp) struct fdisk_partition *p = NULL, *q = NULL;
+        _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *t = NULL;
+        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
+        _cleanup_free_ char *path = NULL, *disk_uuid_as_string = NULL;
+        uint64_t offset, size;
+        sd_id128_t disk_uuid;
+        char uuids[37];
+        int r;
+
+        assert(fd >= 0);
+        assert(label);
+        assert(ret_offset);
+        assert(ret_size);
+
+        t = fdisk_new_parttype();
+        if (!t)
+                return log_oom();
+
+        r = fdisk_parttype_set_typestr(t, "773f91ef-66d4-49b5-bd83-d683bf40ad16");
+        if (r < 0)
+                return log_error_errno(r, "Failed to initialize partition type: %m");
+
+        c = fdisk_new_context();
+        if (!c)
+                return log_oom();
+
+        if (asprintf(&path, "/proc/self/fd/%i", fd) < 0)
+                return log_oom();
+
+        r = fdisk_assign_device(c, path, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open device: %m");
+
+        r = fdisk_create_disklabel(c, "gpt");
+        if (r < 0)
+                return log_error_errno(r, "Failed to create gpt disk label: %m");
+
+        p = fdisk_new_partition();
+        if (!p)
+                return log_oom();
+
+        r = fdisk_partition_set_type(p, t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set partition type: %m");
+
+        r = fdisk_partition_start_follow_default(p, 1);
+        if (r < 0)
+                return log_error_errno(r, "Failed to place partition at beginning of space: %m");
+
+        r = fdisk_partition_partno_follow_default(p, 1);
+        if (r < 0)
+                return log_error_errno(r, "Failed to place partition at first free partition index: %m");
+
+        r = fdisk_partition_end_follow_default(p, 1);
+        if (r < 0)
+                return log_error_errno(r, "Failed to make partition cover all free space: %m");
+
+        r = fdisk_partition_set_name(p, label);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set partition name: %m");
+
+        r = fdisk_partition_set_uuid(p, id128_to_uuid_string(uuid, uuids));
+        if (r < 0)
+                return log_error_errno(r, "Failed to set partition UUID: %m");
+
+        r = fdisk_add_partition(c, p, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to add partition: %m");
+
+        r = fdisk_write_disklabel(c);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write disk label: %m");
+
+        r = fdisk_get_disklabel_id(c, &disk_uuid_as_string);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine disk label UUID: %m");
+
+        r = sd_id128_from_string(disk_uuid_as_string, &disk_uuid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse disk label UUID: %m");
+
+        r = fdisk_get_partition(c, 0, &q);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read created partition metadata: %m");
+
+        assert(fdisk_partition_has_start(q));
+        offset = fdisk_partition_get_start(q);
+        if (offset > UINT64_MAX / 512U)
+                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition offset too large.");
+
+        assert(fdisk_partition_has_size(q));
+        size = fdisk_partition_get_size(q);
+        if (size > UINT64_MAX / 512U)
+                return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition size too large.");
+
+        *ret_offset = offset * 512U;
+        *ret_size = size * 512U;
+        *ret_disk_uuid = disk_uuid;
+
+        return 0;
+}
+
+static bool supported_fs_size(const char *fstype, uint64_t host_size) {
+        uint64_t m;
+
+        m = minimal_size_by_fs_name(fstype);
+        if (m == UINT64_MAX)
+                return false;
+
+        return host_size >= m;
+}
+
+static int wait_for_devlink(const char *path) {
+        _cleanup_close_ int inotify_fd = -1;
+        usec_t until;
+        int r;
+
+        /* let's wait for a device link to show up in /dev, with a time-out. This is good to do since we
+         * return a /dev/disk/by-uuid/… link to our callers and they likely want to access it right-away,
+         * hence let's wait until udev has caught up with our changes, and wait for the symlink to be
+         * created. */
+
+        until = usec_add(now(CLOCK_MONOTONIC), 45 * USEC_PER_SEC);
+
+        for (;;) {
+                _cleanup_free_ char *dn = NULL;
+                usec_t w;
+
+                if (laccess(path, F_OK) < 0) {
+                        if (errno != ENOENT)
+                                return log_error_errno(errno, "Failed to determine whether %s exists: %m", path);
+                } else
+                        return 0; /* Found it */
+
+                if (inotify_fd < 0) {
+                        /* We need to wait for the device symlink to show up, let's create an inotify watch for it */
+                        inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC);
+                        if (inotify_fd < 0)
+                                return log_error_errno(errno, "Failed to allocate inotify fd: %m");
+                }
+
+                dn = dirname_malloc(path);
+                for (;;) {
+                        if (!dn)
+                                return log_oom();
+
+                        log_info("Watching %s", dn);
+
+                        if (inotify_add_watch(inotify_fd, dn, IN_CREATE|IN_MOVED_TO|IN_ONLYDIR|IN_DELETE_SELF|IN_MOVE_SELF) < 0) {
+                                if (errno != ENOENT)
+                                        return log_error_errno(errno, "Failed to add watch on %s: %m", dn);
+                        } else
+                                break;
+
+                        if (empty_or_root(dn))
+                                break;
+
+                        dn = dirname_malloc(dn);
+                }
+
+                w = now(CLOCK_MONOTONIC);
+                if (w >= until)
+                        return log_error_errno(SYNTHETIC_ERRNO(ETIMEDOUT), "Device link %s still hasn't shown up, giving up.", path);
+
+                r = fd_wait_for_event(inotify_fd, POLLIN, usec_sub_unsigned(until, w));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to watch inotify: %m");
+
+                (void) flush_fd(inotify_fd);
+        }
+}
+
+static int calculate_disk_size(UserRecord *h, const char *parent_dir, uint64_t *ret) {
+        char buf[FORMAT_BYTES_MAX];
+        struct statfs sfs;
+        uint64_t m;
+
+        assert(h);
+        assert(parent_dir);
+        assert(ret);
+
+        if (h->disk_size != UINT64_MAX) {
+                *ret = DISK_SIZE_ROUND_DOWN(h->disk_size);
+                return 0;
+        }
+
+        if (statfs(parent_dir, &sfs) < 0)
+                return log_error_errno(errno, "statfs() on %s failed: %m", parent_dir);
+
+        m = sfs.f_bsize * sfs.f_bavail;
+
+        if (h->disk_size_relative == UINT64_MAX) {
+
+                if (m > UINT64_MAX / USER_DISK_SIZE_DEFAULT_PERCENT)
+                        return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Disk size too large.");
+
+                *ret = DISK_SIZE_ROUND_DOWN(m * USER_DISK_SIZE_DEFAULT_PERCENT / 100);
+
+                log_info("Sizing home to %u%% of available disk space, which is %s.",
+                         USER_DISK_SIZE_DEFAULT_PERCENT,
+                         format_bytes(buf, sizeof(buf), *ret));
+        } else {
+                *ret = DISK_SIZE_ROUND_DOWN((uint64_t) ((double) m * (double) h->disk_size_relative / (double) UINT32_MAX));
+
+                log_info("Sizing home to %" PRIu64 ".%01" PRIu64 "%% of available disk space, which is %s.",
+                         (h->disk_size_relative * 100) / UINT32_MAX,
+                         ((h->disk_size_relative * 1000) / UINT32_MAX) % 10,
+                         format_bytes(buf, sizeof(buf), *ret));
+        }
+
+        if (*ret < USER_DISK_SIZE_MIN)
+                *ret = USER_DISK_SIZE_MIN;
+
+        return 0;
+}
+
+int home_create_luks(
+                UserRecord *h,
+                char **pkcs11_decrypted_passwords,
+                char **effective_passwords,
+                UserRecord **ret_home) {
+
+        _cleanup_free_ char *dm_name = NULL, *dm_node = NULL, *subdir = NULL, *disk_uuid_path = NULL, *temporary_image_path = NULL;
+        uint64_t host_size, encrypted_size, partition_offset, partition_size;
+        bool image_created = false, dm_activated = false, mounted = false;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        sd_id128_t partition_uuid, fs_uuid, luks_uuid, disk_uuid;
+        _cleanup_(loop_device_unrefp) LoopDevice *loop = NULL;
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_close_ int image_fd = -1, root_fd = -1;
+        const char *fstype, *ip;
+        struct statfs sfs;
+        int r;
+
+        assert(h);
+        assert(h->storage < 0 || h->storage == USER_LUKS);
+        assert(ret_home);
+
+        assert_se(ip = user_record_image_path(h));
+
+        fstype = user_record_file_system_type(h);
+        if (!supported_fstype(fstype))
+                return log_error_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "Unsupported file system type: %s", h->file_system_type);
+
+        if (sd_id128_is_null(h->partition_uuid)) {
+                r = sd_id128_randomize(&partition_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire partition UUID: %m");
+        } else
+                partition_uuid = h->partition_uuid;
+
+        if (sd_id128_is_null(h->luks_uuid)) {
+                r = sd_id128_randomize(&luks_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire LUKS UUID: %m");
+        } else
+                luks_uuid = h->luks_uuid;
+
+        if (sd_id128_is_null(h->file_system_uuid)) {
+                r = sd_id128_randomize(&fs_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire file system UUID: %m");
+        } else
+                fs_uuid = h->file_system_uuid;
+
+        r = make_dm_names(h->user_name, &dm_name, &dm_node);
+        if (r < 0)
+                return r;
+
+        r = access(dm_node, F_OK);
+        if (r < 0) {
+                if (errno != ENOENT)
+                        return log_error_errno(errno, "Failed to determine whether %s exists: %m", dm_node);
+        } else
+                return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Device mapper device %s already exists, refusing.", dm_node);
+
+        if (path_startswith(ip, "/dev/")) {
+                _cleanup_free_ char *sysfs = NULL;
+                uint64_t block_device_size;
+                struct stat st;
+
+                /* Let's place the home directory on a real device, i.e. an USB stick or such */
+
+                image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                if (image_fd < 0)
+                        return log_error_errno(errno, "Failed to open device %s: %m", ip);
+
+                if (fstat(image_fd, &st) < 0)
+                        return log_error_errno(errno, "Failed to stat device %s: %m", ip);
+                if (!S_ISBLK(st.st_mode))
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Device is not a block device, refusing.");
+
+                if (asprintf(&sysfs, "/sys/dev/block/%u:%u/partition", major(st.st_rdev), minor(st.st_rdev)) < 0)
+                        return log_oom();
+                if (access(sysfs, F_OK) < 0) {
+                        if (errno != ENOENT)
+                                return log_error_errno(errno, "Failed to check whether %s exists: %m", sysfs);
+                } else
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Operating on partitions is currently not supported, sorry. Please specify a top-level block device.");
+
+                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);
+
+                if (ioctl(image_fd, BLKGETSIZE64, &block_device_size) < 0)
+                        return log_error_errno(errno, "Failed to read block device size: %m");
+
+                if (h->disk_size == UINT64_MAX) {
+
+                        /* If a relative disk size is requested, apply it relative to the block device size */
+                        if (h->disk_size_relative < UINT32_MAX)
+                                host_size = CLAMP(DISK_SIZE_ROUND_DOWN(block_device_size * h->disk_size_relative / UINT32_MAX),
+                                                  USER_DISK_SIZE_MIN, USER_DISK_SIZE_MAX);
+                        else
+                                host_size = block_device_size; /* Otherwise, take the full device */
+
+                } else if (h->disk_size > block_device_size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMSGSIZE), "Selected disk size larger than backing block device, refusing.");
+                else
+                        host_size = DISK_SIZE_ROUND_DOWN(h->disk_size);
+
+                if (!supported_fs_size(fstype, host_size))
+                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", h->file_system_type);
+
+                /* After creation we should reference this partition by its UUID instead of the block
+                 * device. That's preferable since the user might have specified a device node such as
+                 * /dev/sdb to us, which might look very different when replugged. */
+                if (asprintf(&disk_uuid_path, "/dev/disk/by-uuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(luks_uuid)) < 0)
+                        return log_oom();
+
+                if (user_record_luks_discard(h)) {
+                        if (ioctl(image_fd, BLKDISCARD, (uint64_t[]) { 0, block_device_size }) < 0)
+                                log_full_errno(errno == EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, errno,
+                                               "Failed to issue full-device BLKDISCARD on device, ignoring: %m");
+                        else
+                                log_info("Full device discard completed.");
+                }
+        } else {
+                _cleanup_free_ char *parent = NULL;
+
+                parent = dirname_malloc(ip);
+                if (!parent)
+                        return log_oom();
+
+                r = mkdir_p(parent, 0755);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create parent directory %s: %m", parent);
+
+                r = calculate_disk_size(h, parent, &host_size);
+                if (r < 0)
+                        return r;
+
+                if (!supported_fs_size(fstype, host_size))
+                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Selected file system size too small for %s.", h->file_system_type);
+
+                r = tempfn_random(ip, "homework", &temporary_image_path);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to derive temporary file name for %s: %m", ip);
+
+                image_fd = open(temporary_image_path, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+                if (image_fd < 0)
+                        return log_error_errno(errno, "Failed to create home image %s: %m", temporary_image_path);
+
+                image_created = true;
+
+                r = chattr_fd(image_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to set file attributes on %s, ignoring: %m", temporary_image_path);
+
+                if (user_record_luks_discard(h))
+                        r = ftruncate(image_fd, host_size);
+                else
+                        r = fallocate(image_fd, 0, 0, host_size);
+                if (r < 0) {
+                        if (ERRNO_IS_DISK_SPACE(errno)) {
+                                log_debug_errno(errno, "Not enough disk space to allocate home.");
+                                r = -ENOSPC; /* make recognizable */
+                                goto fail;
+                        }
+
+                        r = log_error_errno(errno, "Failed to truncate home image %s: %m", temporary_image_path);
+                        goto fail;
+                }
+
+                log_info("Allocating image file completed.");
+        }
+
+        r = make_partition_table(
+                        image_fd,
+                        user_record_user_name_and_realm(h),
+                        partition_uuid,
+                        &partition_offset,
+                        &partition_size,
+                        &disk_uuid);
+        if (r < 0)
+                goto fail;
+
+        log_info("Writing of partition table completed.");
+
+        r = loop_device_make(image_fd, O_RDWR, partition_offset, partition_size, 0, &loop);
+        if (r < 0) {
+                if (r == -ENOENT) { /* this means /dev/loop-control doesn't exist, i.e. we are in a container
+                                     * or similar and loopback bock devices are not available, return a
+                                     * recognizable error in this case. */
+                        log_error_errno(r, "Loopback block device support is not available on this system.");
+                        r = -ENOLINK;
+                        goto fail;
+                }
+
+                log_error_errno(r, "Failed to set up loopback device for %s: %m", temporary_image_path);
+                goto fail;
+        }
+
+        r = loop_device_flock(loop, LOCK_EX); /* make sure udev won't read before we are done */
+        if (r < 0) {
+                log_error_errno(r, "Failed to take lock on loop device: %m");
+                goto fail;
+        }
+
+        log_info("Setting up loopback device %s completed.", loop->node ?: ip);
+
+        r = luks_format(loop->node,
+                        dm_name,
+                        luks_uuid,
+                        user_record_user_name_and_realm(h),
+                        pkcs11_decrypted_passwords,
+                        effective_passwords,
+                        user_record_luks_discard(h),
+                        h,
+                        &cd);
+        if (r < 0)
+                goto fail;
+
+        dm_activated = true;
+
+        r = block_get_size_by_path(dm_node, &encrypted_size);
+        if (r < 0) {
+                log_error_errno(r, "Failed to get encrypted block device size: %m");
+                goto fail;
+        }
+
+        log_info("Setting up LUKS device %s completed.", dm_node);
+
+        r = run_mkfs(dm_node, fstype, user_record_user_name_and_realm(h), fs_uuid, user_record_luks_discard(h));
+        if (r < 0)
+                goto fail;
+
+        log_info("Formatting file system completed.");
+
+        r = home_unshare_and_mount(dm_node, fstype, user_record_luks_discard(h));
+        if (r < 0)
+                goto fail;
+
+        mounted = true;
+
+        subdir = path_join("/run/systemd/user-home-mount/", user_record_user_name_and_realm(h));
+        if (!subdir) {
+                r = log_oom();
+                goto fail;
+        }
+
+        if (mkdir(subdir, 0700) < 0) {
+                r = log_error_errno(errno, "Failed to create user directory in mounted image file: %m");
+                goto fail;
+        }
+
+        root_fd = open(subdir, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        if (root_fd < 0) {
+                r = log_error_errno(errno, "Failed to open user directory in mounted image file: %m");
+                goto fail;
+        }
+
+        r = home_populate(h, root_fd);
+        if (r < 0)
+                goto fail;
+
+        r = home_sync_and_statfs(root_fd, &sfs);
+        if (r < 0)
+                goto fail;
+
+        r = user_record_clone(h, USER_RECORD_LOAD_MASK_SECRET|USER_RECORD_LOG, &new_home);
+        if (r < 0) {
+                log_error_errno(r, "Failed to clone record: %m");
+                goto fail;
+        }
+
+        r = user_record_add_binding(
+                        new_home,
+                        USER_LUKS,
+                        disk_uuid_path ?: ip,
+                        partition_uuid,
+                        luks_uuid,
+                        fs_uuid,
+                        crypt_get_cipher(cd),
+                        crypt_get_cipher_mode(cd),
+                        luks_volume_key_size_convert(cd),
+                        fstype,
+                        NULL,
+                        h->uid,
+                        (gid_t) h->uid);
+        if (r < 0) {
+                log_error_errno(r, "Failed to add binding to record: %m");
+                goto fail;
+        }
+
+        root_fd = safe_close(root_fd);
+
+        r = umount_verbose("/run/systemd/user-home-mount");
+        if (r < 0)
+                goto fail;
+
+        mounted = false;
+
+        r = crypt_deactivate(cd, dm_name);
+        if (r < 0) {
+                log_error_errno(r, "Failed to deactivate LUKS device: %m");
+                goto fail;
+        }
+
+        dm_activated = false;
+
+        loop = loop_device_unref(loop);
+
+        if (disk_uuid_path)
+                (void) ioctl(image_fd, BLKRRPART, 0);
+
+        /* Let's close the image fd now. If we are operating on a real block device this will release the BSD
+         * lock that ensures udev doesn't interfere with what we are doing */
+        image_fd = safe_close(image_fd);
+
+        if (temporary_image_path) {
+                if (rename(temporary_image_path, ip) < 0) {
+                        log_error_errno(errno, "Failed to rename image file: %m");
+                        goto fail;
+                }
+
+                log_info("Moved image file into place.");
+        }
+
+        if (disk_uuid_path)
+                (void) wait_for_devlink(disk_uuid_path);
+
+        log_info("Everything completed.");
+
+        print_size_summary(host_size, encrypted_size, &sfs);
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+
+fail:
+        /* Let's close all files before we unmount the file system, to avoid EBUSY */
+        root_fd = safe_close(root_fd);
+
+        if (mounted)
+                (void) umount_verbose("/run/systemd/user-home-mount");
+
+        if (dm_activated)
+                (void) crypt_deactivate(cd, dm_name);
+
+        loop = loop_device_unref(loop);
+
+        if (image_created)
+                (void) unlink(temporary_image_path);
+
+        return r;
+}
+
+int home_validate_update_luks(UserRecord *h, HomeSetup *setup) {
+        _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
+        int r;
+
+        assert(h);
+        assert(setup);
+
+        r = make_dm_names(h->user_name, &dm_name, &dm_node);
+        if (r < 0)
+                return r;
+
+        r = access(dm_node, F_OK);
+        if (r < 0 && errno != ENOENT)
+                return log_error_errno(errno, "Failed to determine whether %s exists: %m", dm_node);
+
+        free_and_replace(setup->dm_name, dm_name);
+        free_and_replace(setup->dm_node, dm_node);
+
+        return r >= 0;
+}
+
+enum {
+        CAN_RESIZE_ONLINE,
+        CAN_RESIZE_OFFLINE,
+};
+
+static int can_resize_fs(int fd, uint64_t old_size, uint64_t new_size) {
+        struct statfs sfs;
+
+        assert(fd >= 0);
+
+        /* Filter out bogus requests early */
+        if (old_size == 0 || old_size == UINT64_MAX ||
+            new_size == 0 || new_size == UINT64_MAX)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid resize parameters.");
+
+        if ((old_size & 511) != 0 || (new_size & 511) != 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Resize parameters not multiple of 512.");
+
+        if (fstatfs(fd, &sfs) < 0)
+                return log_error_errno(errno, "Failed to fstatfs() file system: %m");
+
+        if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) {
+
+                if (new_size < BTRFS_MINIMAL_SIZE)
+                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "New file system size too small for btrfs (needs to be 256M at least.");
+
+                /* btrfs can grow and shrink online */
+
+        } else if (is_fs_type(&sfs, XFS_SB_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).");
+
+                /* XFS can grow, but not shrink */
+                if (new_size < old_size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EMSGSIZE), "Shrinking this type of file system is not supported.");
+
+        } else if (is_fs_type(&sfs, EXT4_SUPER_MAGIC)) {
+
+                if (new_size < EXT4_MINIMAL_SIZE)
+                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "New file system size too small for ext4 (needs to be 1M at least).");
+
+                /* ext4 can grow online, and shrink offline */
+                if (new_size < old_size)
+                        return CAN_RESIZE_OFFLINE;
+
+        } else
+                return log_error_errno(SYNTHETIC_ERRNO(ESOCKTNOSUPPORT), "Resizing this type of file system is not supported.");
+
+        return CAN_RESIZE_ONLINE;
+}
+
+static int ext4_offline_resize_fs(HomeSetup *setup, uint64_t new_size, bool discard) {
+        _cleanup_free_ char *size_str = NULL;
+        bool re_open = false, re_mount = false;
+        pid_t resize_pid, fsck_pid;
+        int r, exit_status;
+
+        assert(setup);
+        assert(setup->dm_node);
+
+        /* First, unmount the file system */
+        if (setup->root_fd >= 0) {
+                setup->root_fd = safe_close(setup->root_fd);
+                re_open = true;
+        }
+
+        if (setup->undo_mount) {
+                r = umount_verbose("/run/systemd/user-home-mount");
+                if (r < 0)
+                        return r;
+
+                setup->undo_mount = false;
+                re_mount = true;
+        }
+
+        log_info("Temporarary unmounting of file system completed.");
+
+        /* resize2fs requires that the file system is force checked first, do so. */
+        r = safe_fork("(e2fsck)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_STDOUT_TO_STDERR, &fsck_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Child */
+                execlp("e2fsck" ,"e2fsck", "-fp", setup->dm_node, NULL);
+                log_error_errno(errno, "Failed to execute e2fsck: %m");
+                _exit(EXIT_FAILURE);
+        }
+
+        exit_status = wait_for_terminate_and_check("e2fsck", fsck_pid, WAIT_LOG_ABNORMAL);
+        if (exit_status < 0)
+                return exit_status;
+        if ((exit_status & ~FSCK_ERROR_CORRECTED) != 0) {
+                log_warning("e2fsck failed with exit status %i.", exit_status);
+
+                if ((exit_status & (FSCK_SYSTEM_SHOULD_REBOOT|FSCK_ERRORS_LEFT_UNCORRECTED)) != 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "File system is corrupted, refusing.");
+
+                log_warning("Ignoring fsck error.");
+        }
+
+        log_info("Forced file system check completed.");
+
+        /* We use 512 sectors here, because resize2fs doesn't do byte sizes */
+        if (asprintf(&size_str, "%" PRIu64 "s", new_size / 512) < 0)
+                return log_oom();
+
+        /* Resize the thing */
+        r = safe_fork("(e2resize)", FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_LOG|FORK_WAIT|FORK_STDOUT_TO_STDERR, &resize_pid);
+        if (r < 0)
+                return r;
+        if (r == 0) {
+                /* Child */
+                execlp("resize2fs" ,"resize2fs", setup->dm_node, size_str, NULL);
+                log_error_errno(errno, "Failed to execute resize2fs: %m");
+                _exit(EXIT_FAILURE);
+        }
+
+        log_info("Offline file system resize completed.");
+
+        /* Re-establish mounts and reopen the directory */
+        if (re_mount) {
+                r = home_mount_node(setup->dm_node, "ext4", discard);
+                if (r < 0)
+                        return r;
+
+                setup->undo_mount = true;
+        }
+
+        if (re_open) {
+                setup->root_fd = open("/run/systemd/user-home-mount", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+                if (setup->root_fd < 0)
+                        return log_error_errno(errno, "Failed to reopen file system: %m");
+        }
+
+        log_info("File system mounted again.");
+
+        return 0;
+}
+
+static int prepare_resize_partition(
+                int fd,
+                uint64_t partition_offset,
+                uint64_t old_partition_size,
+                uint64_t new_partition_size,
+                sd_id128_t *ret_disk_uuid,
+                struct fdisk_table **ret_table) {
+
+        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
+        _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
+        _cleanup_free_ char *path = NULL, *disk_uuid_as_string = NULL;
+        size_t n_partitions, i;
+        sd_id128_t disk_uuid;
+        bool found = false;
+        int r;
+
+        assert(fd >= 0);
+        assert(ret_disk_uuid);
+        assert(ret_table);
+
+        assert((partition_offset & 511) == 0);
+        assert((old_partition_size & 511) == 0);
+        assert((new_partition_size & 511) == 0);
+        assert(UINT64_MAX - old_partition_size >= partition_offset);
+        assert(UINT64_MAX - new_partition_size >= partition_offset);
+
+        if (partition_offset == 0) {
+                /* If the offset is at the beginning we assume no partition table, let's exit early. */
+                log_debug("Not rewriting partition table, operating on naked device.");
+                *ret_disk_uuid = SD_ID128_NULL;
+                *ret_table = NULL;
+                return 0;
+        }
+
+        c = fdisk_new_context();
+        if (!c)
+                return log_oom();
+
+        if (asprintf(&path, "/proc/self/fd/%i", fd) < 0)
+                return log_oom();
+
+        r = fdisk_assign_device(c, path, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open device: %m");
+
+        if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOMEDIUM), "Disk has no GPT partition table.");
+
+        r = fdisk_get_disklabel_id(c, &disk_uuid_as_string);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire disk UUID: %m");
+
+        r = sd_id128_from_string(disk_uuid_as_string, &disk_uuid);
+        if (r < 0)
+                return log_error_errno(r, "Failed parse disk UUID: %m");
+
+        r = fdisk_get_partitions(c, &t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire partition table: %m");
+
+        n_partitions = fdisk_table_get_nents(t);
+        for (i = 0; i < n_partitions; i++)  {
+                struct fdisk_partition *p;
+
+                p = fdisk_table_get_partition(t, i);
+                if (!p)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
+
+                if (fdisk_partition_is_used(p) <= 0)
+                        continue;
+                if (fdisk_partition_has_start(p) <= 0 || fdisk_partition_has_size(p) <= 0 || fdisk_partition_has_end(p) <= 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found partition without a size.");
+
+                if (fdisk_partition_get_start(p) == partition_offset / 512U &&
+                    fdisk_partition_get_size(p) == old_partition_size / 512U) {
+
+                        if (found)
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing.");
+
+                        /* Found our partition, now patch it */
+                        r = fdisk_partition_size_explicit(p, 1);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to enable explicit partition size: %m");
+
+                        r = fdisk_partition_set_size(p, new_partition_size / 512U);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to change partition size: %m");
+
+                        found = true;
+                        continue;
+
+                } else {
+                        if (fdisk_partition_get_start(p) < partition_offset + new_partition_size / 512U &&
+                            fdisk_partition_get_end(p) >= partition_offset / 512)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, conflicting partition found.");
+                }
+        }
+
+        if (!found)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to find matching partition to resize.");
+
+        *ret_table = TAKE_PTR(t);
+        *ret_disk_uuid = disk_uuid;
+
+        return 1;
+}
+
+static int ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *userdata) {
+        char *result;
+
+        assert(c);
+
+        switch (fdisk_ask_get_type(ask)) {
+
+        case FDISK_ASKTYPE_STRING:
+                result = new(char, 37);
+                if (!result)
+                        return log_oom();
+
+                fdisk_ask_string_set_result(ask, id128_to_uuid_string(*(sd_id128_t*) userdata, result));
+                break;
+
+        default:
+                log_debug("Unexpected question from libfdisk, ignoring.");
+        }
+
+        return 0;
+}
+
+static int apply_resize_partition(int fd, sd_id128_t disk_uuids, struct fdisk_table *t) {
+        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
+        _cleanup_free_ void *two_zero_lbas = NULL;
+        _cleanup_free_ char *path = NULL;
+        ssize_t n;
+        int r;
+
+        assert(fd >= 0);
+
+        if (!t) /* no partition table to apply, exit early */
+                return 0;
+
+        two_zero_lbas = malloc0(1024U);
+        if (!two_zero_lbas)
+                return log_oom();
+
+        /* libfdisk appears to get confused by the existing PMBR. Let's explicitly flush it out. */
+        n = pwrite(fd, two_zero_lbas, 1024U, 0);
+        if (n < 0)
+                return log_error_errno(errno, "Failed to wipe partition table: %m");
+        if (n != 1024)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while whiping partition table.");
+
+        c = fdisk_new_context();
+        if (!c)
+                return log_oom();
+
+        if (asprintf(&path, "/proc/self/fd/%i", fd) < 0)
+                return log_oom();
+
+        r = fdisk_assign_device(c, path, 0);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open device: %m");
+
+        r = fdisk_create_disklabel(c, "gpt");
+        if (r < 0)
+                return log_error_errno(r, "Failed to create GPT disk label: %m");
+
+        r = fdisk_apply_table(c, t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to apply partition table: %m");
+
+        r = fdisk_set_ask(c, ask_cb, &disk_uuids);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set libfdisk query function: %m");
+
+        r = fdisk_set_disklabel_id(c);
+        if (r < 0)
+                return log_error_errno(r, "Failed to change disklabel ID: %m");
+
+        r = fdisk_write_disklabel(c);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write disk label: %m");
+
+        return 1;
+}
+
+int home_resize_luks(
+                UserRecord *h,
+                bool already_activated,
+                char ***pkcs11_decrypted_passwords,
+                HomeSetup *setup,
+                UserRecord **ret_home) {
+
+        char buffer1[FORMAT_BYTES_MAX], buffer2[FORMAT_BYTES_MAX], buffer3[FORMAT_BYTES_MAX],
+                buffer4[FORMAT_BYTES_MAX], buffer5[FORMAT_BYTES_MAX], buffer6[FORMAT_BYTES_MAX];
+        uint64_t old_image_size, new_image_size, old_fs_size, new_fs_size, crypto_offset, new_partition_size;
+        _cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
+        _cleanup_(fdisk_unref_tablep) struct fdisk_table *table = NULL;
+        _cleanup_free_ char *whole_disk = NULL;
+        _cleanup_close_ int image_fd = -1;
+        sd_id128_t disk_uuid;
+        const char *ip, *ipo;
+        struct statfs sfs;
+        struct stat st;
+        int r, resize_type;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_LUKS);
+        assert(setup);
+        assert(ret_home);
+
+        assert_se(ipo = user_record_image_path(h));
+        ip = strdupa(ipo); /* copy out since original might change later in home record object */
+
+        image_fd = open(ip, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+        if (image_fd < 0)
+                return log_error_errno(errno, "Failed to open image file %s: %m", ip);
+
+        if (fstat(image_fd, &st) < 0)
+                return log_error_errno(errno, "Failed to stat image file %s: %m", ip);
+        if (S_ISBLK(st.st_mode)) {
+                dev_t parent;
+
+                r = block_get_whole_disk(st.st_rdev, &parent);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire whole block device for %s: %m", ip);
+                if (r > 0) {
+                        /* If we shall resize a file system on a partition device, then let's figure out the
+                         * whole disk device and operate on that instead, since we need to rewrite the
+                         * partition table to resize the partition. */
+
+                        log_info("Operating on partition device %s, using parent device.", ip);
+
+                        r = device_path_make_major_minor(st.st_mode, parent, &whole_disk);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to derive whole disk path for %s: %m", ip);
+
+                        safe_close(image_fd);
+
+                        image_fd = open(whole_disk, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                        if (image_fd < 0)
+                                return log_error_errno(errno, "Failed to open whole block device %s: %m", whole_disk);
+
+                        if (fstat(image_fd, &st) < 0)
+                                return log_error_errno(errno, "Failed to stat whole block device %s: %m", whole_disk);
+                        if (!S_ISBLK(st.st_mode))
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Whole block device %s is not actually a block device, refusing.", whole_disk);
+                } 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");
+
+                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);
+
+                new_image_size = old_image_size; /* we can't resize physical block devices */
+        } else {
+                r = stat_verify_regular(&st);
+                if (r < 0)
+                        return log_error_errno(r, "Image file %s is not a block device nor regular: %m", ip);
+
+                old_image_size = st.st_size;
+
+                /* Note an asymetry here: when we operate on loopback files the specified disk size we get we
+                 * apply onto the loopback file as a whole. When we operate on block devices we instead apply
+                 * to the partition itself only. */
+
+                new_image_size = DISK_SIZE_ROUND_DOWN(h->disk_size);
+                if (new_image_size == old_image_size) {
+                        log_info("Image size already matching, skipping operation.");
+                        return 0;
+                }
+        }
+
+        r = home_prepare_luks(h, already_activated, whole_disk, pkcs11_decrypted_passwords, setup, &header_home);
+        if (r < 0)
+                return r;
+
+        r = home_load_embedded_identity(h, setup->root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, pkcs11_decrypted_passwords, &embedded_home, &new_home);
+        if (r < 0)
+                return r;
+
+        log_info("offset = %" PRIu64 ", size = %" PRIu64 ", image = %" PRIu64, setup->partition_offset, setup->partition_size, old_image_size);
+
+        if ((UINT64_MAX - setup->partition_offset) < setup->partition_size ||
+            setup->partition_offset + setup->partition_size > old_image_size)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Old partition doesn't fit in backing storage, refusing.");
+
+        if (S_ISREG(st.st_mode)) {
+                uint64_t partition_table_extra;
+
+                partition_table_extra = old_image_size - setup->partition_size;
+                if (new_image_size <= partition_table_extra)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New size smaller than partition table metadata.");
+
+                new_partition_size = new_image_size - partition_table_extra;
+        } else {
+                assert(S_ISBLK(st.st_mode));
+
+                new_partition_size = DISK_SIZE_ROUND_DOWN(h->disk_size);
+                if (new_partition_size == setup->partition_size) {
+                        log_info("Partition size already matching, skipping operation.");
+                        return 0;
+                }
+        }
+
+        if ((UINT64_MAX - setup->partition_offset) < new_partition_size ||
+            setup->partition_offset + new_partition_size > new_image_size)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New partition doesn't fit into backing storage, refusing.");
+
+        crypto_offset = crypt_get_data_offset(setup->crypt_device);
+        if (setup->partition_size / 512U <= crypto_offset)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Weird, old crypto payload offset doesn't actually fit in partition size?");
+        if (new_partition_size / 512U <= crypto_offset)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "New size smaller than crypto payload offset?");
+
+        old_fs_size = (setup->partition_size / 512U - crypto_offset) * 512U;
+        new_fs_size = (new_partition_size / 512U - crypto_offset) * 512U;
+
+        /* Before we start doing anything, let's figure out if we actually can */
+        resize_type = can_resize_fs(setup->root_fd, old_fs_size, new_fs_size);
+        if (resize_type < 0)
+                return resize_type;
+        if (resize_type == CAN_RESIZE_OFFLINE && already_activated)
+                return log_error_errno(SYNTHETIC_ERRNO(ETXTBSY), "File systems of this type can only be resized offline, but is currently online.");
+
+        log_info("Ready to resize image size %s → %s, partition size %s → %s, file system size %s → %s.",
+                 format_bytes(buffer1, sizeof(buffer1), old_image_size),
+                 format_bytes(buffer2, sizeof(buffer2), new_image_size),
+                 format_bytes(buffer3, sizeof(buffer3), setup->partition_size),
+                 format_bytes(buffer4, sizeof(buffer4), new_partition_size),
+                 format_bytes(buffer5, sizeof(buffer5), old_fs_size),
+                 format_bytes(buffer6, sizeof(buffer6), new_fs_size));
+
+        r = prepare_resize_partition(
+                        image_fd,
+                        setup->partition_offset,
+                        setup->partition_size,
+                        new_partition_size,
+                        &disk_uuid,
+                        &table);
+        if (r < 0)
+                return r;
+
+        if (new_fs_size > old_fs_size) {
+
+                if (S_ISREG(st.st_mode)) {
+                        /* Grow file size */
+
+                        if (user_record_luks_discard(h))
+                                r = ftruncate(image_fd, new_image_size);
+                        else
+                                r = fallocate(image_fd, 0, 0, new_image_size);
+                        if (r < 0) {
+                                if (ERRNO_IS_DISK_SPACE(errno)) {
+                                        log_debug_errno(errno, "Not enough disk space to grow home.");
+                                        return -ENOSPC; /* make recognizable */
+                                }
+
+                                return log_error_errno(errno, "Failed to grow image file %s: %m", ip);
+                        }
+
+                        log_info("Growing of image file completed.");
+                }
+
+                /* Make sure loopback device sees the new bigger size */
+                r = loop_device_refresh_size(setup->loop, UINT64_MAX, new_partition_size);
+                if (r == -ENOTTY)
+                        log_debug_errno(r, "Device is not a loopback device, not refreshing size.");
+                else if (r < 0)
+                        return log_error_errno(r, "Failed to refresh loopback device size: %m");
+                else
+                        log_info("Refreshing loop device size completed.");
+
+                r = apply_resize_partition(image_fd, disk_uuid, table);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        log_info("Growing of partition completed.");
+
+                if (ioctl(image_fd, BLKRRPART, 0) < 0)
+                        log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m");
+
+                /* Tell LUKS about the new bigger size too */
+                r = crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to grow LUKS device: %m");
+
+                log_info("LUKS device growing completed.");
+        } else {
+                r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+                if (r < 0)
+                        return r;
+
+                if (S_ISREG(st.st_mode)) {
+                        if (user_record_luks_discard(h))
+                                /* Before we shrink, let's trim the file system, so that we need less space on disk during the shrinking */
+                                (void) run_fitrim(setup->root_fd);
+                        else {
+                                /* If discard is off, let's ensure all backing blocks are allocated, so that our resize operation doesn't fail half-way */
+                                r = run_fallocate(image_fd, &st);
+                                if (r < 0)
+                                        return r;
+                        }
+                }
+        }
+
+        /* Now resize the file system */
+        if (resize_type == CAN_RESIZE_ONLINE)
+                r = resize_fs(setup->root_fd, new_fs_size, NULL);
+        else
+                r = ext4_offline_resize_fs(setup, new_fs_size, user_record_luks_discard(h));
+        if (r < 0)
+                return log_error_errno(r, "Failed to resize file system: %m");
+
+        log_info("File system resizing completed.");
+
+        /* Immediately sync afterwards */
+        r = home_sync_and_statfs(setup->root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        if (new_fs_size < old_fs_size) {
+
+                /* Shrink the LUKS device now, matching the new file system size */
+                r = crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to shrink LUKS device: %m");
+
+                log_info("LUKS device shrinking completed.");
+
+                if (S_ISREG(st.st_mode)) {
+                        /* Shrink the image file */
+                        if (ftruncate(image_fd, new_image_size) < 0)
+                                return log_error_errno(errno, "Failed to shrink image file %s: %m", ip);
+
+                        log_info("Shrinking of image file completed.");
+                }
+
+                /* Refresh the loop devices size */
+                r = loop_device_refresh_size(setup->loop, UINT64_MAX, new_partition_size);
+                if (r == -ENOTTY)
+                        log_debug_errno(r, "Device is not a loopback device, not refreshing size.");
+                else if (r < 0)
+                        return log_error_errno(r, "Failed to refresh loopback device size: %m");
+                else
+                        log_info("Refreshing loop device size completed.");
+
+                r = apply_resize_partition(image_fd, disk_uuid, table);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        log_info("Shrinking of partition completed.");
+
+                if (ioctl(image_fd, BLKRRPART, 0) < 0)
+                        log_debug_errno(errno, "BLKRRPART failed on block device, ignoring: %m");
+        } else {
+                r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+                if (r < 0)
+                        return r;
+        }
+
+        r = home_store_header_identity_luks(new_home, setup, header_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, setup);
+        if (r < 0)
+                return r;
+
+        if (user_record_luks_discard(h))
+                (void) run_fitrim(setup->root_fd);
+
+        r = home_sync_and_statfs(setup->root_fd, &sfs);
+        if (r < 0)
+                return r;
+
+        r = home_setup_undo(setup);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+
+        print_size_summary(new_image_size, new_fs_size, &sfs);
+
+        *ret_home = TAKE_PTR(new_home);
+        return 0;
+}
+
+int home_passwd_luks(
+                UserRecord *h,
+                HomeSetup *setup,
+                char **pkcs11_decrypted_passwords, /* the passwords acquired via PKCS#11 security tokens */
+                char **effective_passwords         /* new passwords */) {
+
+        size_t volume_key_size, i, max_key_slots, n_effective;
+        _cleanup_(erase_and_freep) void *volume_key = NULL;
+        struct crypt_pbkdf_type good_pbkdf, minimal_pbkdf;
+        const char *type;
+        int r;
+
+        assert(h);
+        assert(user_record_storage(h) == USER_LUKS);
+        assert(setup);
+
+        type = crypt_get_type(setup->crypt_device);
+        if (!type)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine crypto device type.");
+
+        r = crypt_keyslot_max(type);
+        if (r <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine number of key slots.");
+        max_key_slots = r;
+
+        r = crypt_get_volume_key_size(setup->crypt_device);
+        if (r <= 0)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to determine volume key size.");
+        volume_key_size = (size_t) r;
+
+        volume_key = malloc(volume_key_size);
+        if (!volume_key)
+                return log_oom();
+
+        r = luks_try_passwords(setup->crypt_device, pkcs11_decrypted_passwords, volume_key, &volume_key_size);
+        if (r == -ENOKEY) {
+                r = luks_try_passwords(setup->crypt_device, h->password, volume_key, &volume_key_size);
+                if (r == -ENOKEY)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Failed to unlock LUKS superblock with supplied passwords.");
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to unlocks LUKS superblock: %m");
+
+        n_effective = strv_length(effective_passwords);
+
+        build_good_pbkdf(&good_pbkdf, h);
+        build_minimal_pbkdf(&minimal_pbkdf, h);
+
+        for (i = 0; i < max_key_slots; i++) {
+                r = crypt_keyslot_destroy(setup->crypt_device, i);
+                if (r < 0 && !IN_SET(r, -ENOENT, -EINVAL)) /* Returns EINVAL or ENOENT if there's no key in this slot already */
+                        return log_error_errno(r, "Failed to destroy LUKS password: %m");
+
+                if (i >= n_effective) {
+                        if (r >= 0)
+                                log_info("Destroyed LUKS key slot %zu.", i);
+                        continue;
+                }
+
+                if (strv_find(pkcs11_decrypted_passwords, effective_passwords[i])) {
+                        log_debug("Using minimal PBKDF for slot %zu", i);
+                        r = crypt_set_pbkdf_type(setup->crypt_device, &minimal_pbkdf);
+                } else {
+                        log_debug("Using good PBKDF for slot %zu", i);
+                        r = crypt_set_pbkdf_type(setup->crypt_device, &good_pbkdf);
+                }
+                if (r < 0)
+                        return log_error_errno(r, "Failed to tweak PBKDF for slot %zu: %m", i);
+
+                r = crypt_keyslot_add_by_volume_key(
+                                setup->crypt_device,
+                                i,
+                                volume_key,
+                                volume_key_size,
+                                effective_passwords[i],
+                                strlen(effective_passwords[i]));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set up LUKS password: %m");
+
+                log_info("Updated LUKS key slot %zu.", i);
+        }
+
+        return 1;
+}
+
+int home_lock_luks(UserRecord *h) {
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
+        _cleanup_close_ int root_fd = -1;
+        const char *p;
+        int r;
+
+        assert(h);
+
+        assert_se(p = user_record_home_directory(h));
+        root_fd = open(p, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOFOLLOW);
+        if (root_fd < 0)
+                return log_error_errno(errno, "Failed to open home directory: %m");
+
+        r = make_dm_names(h->user_name, &dm_name, &dm_node);
+        if (r < 0)
+                return r;
+
+        r = crypt_init_by_name(&cd, dm_name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
+
+        log_info("Discovered used LUKS device %s.", dm_node);
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        if (syncfs(root_fd) < 0) /* Snake oil, but let's better be safe than sorry */
+                return log_error_errno(errno, "Failed to synchronize file system %s: %m", p);
+
+        root_fd = safe_close(root_fd);
+
+        log_info("File system synchronized.");
+
+        /* Note that we don't invoke FIFREEZE here, it appears libcryptsetup/device-mapper already does that on its own for us */
+
+        r = crypt_suspend(cd, dm_name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to suspend cryptsetup device: %s: %m", dm_node);
+
+        log_info("LUKS device suspended.");
+        return 0;
+}
+
+static int luks_try_resume(
+                struct crypt_device *cd,
+                const char *dm_name,
+                char **password) {
+
+        char **pp;
+        int r;
+
+        assert(cd);
+        assert(dm_name);
+
+        STRV_FOREACH(pp, password) {
+                r = 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, char ***pkcs11_decrypted_passwords) {
+        _cleanup_free_ char *dm_name = NULL, *dm_node = NULL;
+        _cleanup_(crypt_freep) struct crypt_device *cd = NULL;
+        int r;
+
+        assert(h);
+
+        r = make_dm_names(h->user_name, &dm_name, &dm_node);
+        if (r < 0)
+                return r;
+
+        r = crypt_init_by_name(&cd, dm_name);
+        if (r < 0)
+                return log_error_errno(r, "Failed to initialize cryptsetup context for %s: %m", dm_name);
+
+        log_info("Discovered used LUKS device %s.", dm_node);
+        crypt_set_log_callback(cd, cryptsetup_log_glue, NULL);
+
+        r = luks_try_resume(cd, dm_name, pkcs11_decrypted_passwords ? *pkcs11_decrypted_passwords : NULL);
+        if (r == -ENOKEY) {
+                r = luks_try_resume(cd, dm_name, h->password);
+                if (r == -ENOKEY)
+                        return log_error_errno(r, "No valid password for LUKS superblock.");
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to resume LUKS superblock: %m");
+
+        log_info("LUKS device resumed.");
+        return 0;
+}
diff --git a/src/home/homework-luks.h b/src/home/homework-luks.h
new file mode 100644 (file)
index 0000000..581255a
--- /dev/null
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "crypt-util.h"
+#include "homework.h"
+#include "user-record.h"
+
+int home_prepare_luks(UserRecord *h, bool already_activated, const char *force_image_path, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_luks_home);
+
+int home_activate_luks(UserRecord *h, char ***pkcs11_decrypted_passwords, UserRecord **ret_home);
+int home_deactivate_luks(UserRecord *h);
+
+int home_store_header_identity_luks(UserRecord *h, HomeSetup *setup, UserRecord *old_home);
+
+int home_create_luks(UserRecord *h, char **pkcs11_decrypted_passwords, char **effective_passwords, UserRecord **ret_home);
+
+int home_validate_update_luks(UserRecord *h, HomeSetup *setup);
+
+int home_resize_luks(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_home);
+
+int home_passwd_luks(UserRecord *h, HomeSetup *setup, char **pkcs11_decrypted_passwords, char **effective_passwords);
+
+int home_lock_luks(UserRecord *h);
+int home_unlock_luks(UserRecord *h, char ***pkcs11_decrypted_passwords);
+
+static inline uint64_t luks_volume_key_size_convert(struct crypt_device *cd) {
+        int k;
+
+        assert(cd);
+
+        /* Convert the "int" to uint64_t, which we usually use for byte sizes stored on disk. */
+
+        k = crypt_get_volume_key_size(cd);
+        if (k <= 0)
+                return UINT64_MAX;
+
+        return (uint64_t) k;
+}
diff --git a/src/home/homework-mount.c b/src/home/homework-mount.c
new file mode 100644 (file)
index 0000000..9e11168
--- /dev/null
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <sched.h>
+#include <sys/mount.h>
+
+#include "alloc-util.h"
+#include "homework-mount.h"
+#include "mkdir.h"
+#include "mount-util.h"
+#include "path-util.h"
+#include "string-util.h"
+
+static const char *mount_options_for_fstype(const char *fstype) {
+        if (streq(fstype, "ext4"))
+                return "noquota,user_xattr";
+        if (streq(fstype, "xfs"))
+                return "noquota";
+        if (streq(fstype, "btrfs"))
+                return "noacl";
+        return NULL;
+}
+
+int home_mount_node(const char *node, const char *fstype, bool discard) {
+        _cleanup_free_ char *joined = NULL;
+        const char *options, *discard_option;
+        int r;
+
+        options = mount_options_for_fstype(fstype);
+
+        discard_option = discard ? "discard" : "nodiscard";
+
+        if (options) {
+                joined = strjoin(options, ",", discard_option);
+                if (!joined)
+                        return log_oom();
+
+                options = joined;
+        } else
+                options = discard_option;
+
+        r = mount_verbose(LOG_ERR, node, "/run/systemd/user-home-mount", fstype, MS_NODEV|MS_NOSUID|MS_RELATIME, strempty(options));
+        if (r < 0)
+                return r;
+
+        log_info("Mounting file system completed.");
+        return 0;
+}
+
+int home_unshare_and_mount(const char *node, const char *fstype, bool discard) {
+        int r;
+
+        if (unshare(CLONE_NEWNS) < 0)
+                return log_error_errno(errno, "Couldn't unshare file system namespace: %m");
+
+        r = mount_verbose(LOG_ERR, "/run", "/run", NULL, MS_SLAVE|MS_REC, NULL); /* Mark /run as MS_SLAVE in our new namespace */
+        if (r < 0)
+                return r;
+
+        (void) mkdir_p("/run/systemd/user-home-mount", 0700);
+
+        if (node)
+                return home_mount_node(node, fstype, discard);
+
+        return 0;
+}
+
+int home_move_mount(const char *user_name_and_realm, const char *target) {
+        _cleanup_free_ char *subdir = NULL;
+        const char *d;
+        int r;
+
+        assert(user_name_and_realm);
+        assert(target);
+
+        if (user_name_and_realm) {
+                subdir = path_join("/run/systemd/user-home-mount/", user_name_and_realm);
+                if (!subdir)
+                        return log_oom();
+
+                d = subdir;
+        } else
+                d = "/run/systemd/user-home-mount/";
+
+        (void) mkdir_p(target, 0700);
+
+        r = mount_verbose(LOG_ERR, d, target, NULL, MS_BIND, NULL);
+        if (r < 0)
+                return r;
+
+        r = umount_verbose("/run/systemd/user-home-mount");
+        if (r < 0)
+                return r;
+
+        log_info("Moving to final mount point %s completed.", target);
+        return 0;
+}
diff --git a/src/home/homework-mount.h b/src/home/homework-mount.h
new file mode 100644 (file)
index 0000000..d926756
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <stdbool.h>
+
+int home_mount_node(const char *node, const char *fstype, bool discard);
+int home_unshare_and_mount(const char *node, const char *fstype, bool discard);
+int home_move_mount(const char *user_name_and_realm, const char *target);
diff --git a/src/home/homework-pkcs11.c b/src/home/homework-pkcs11.c
new file mode 100644 (file)
index 0000000..941ba23
--- /dev/null
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "hexdecoct.h"
+#include "homework-pkcs11.h"
+#include "pkcs11-util.h"
+#include "strv.h"
+
+int pkcs11_callback(
+                CK_FUNCTION_LIST *m,
+                CK_SESSION_HANDLE session,
+                CK_SLOT_ID slot_id,
+                const CK_SLOT_INFO *slot_info,
+                const CK_TOKEN_INFO *token_info,
+                P11KitUri *uri,
+                void *userdata) {
+
+        _cleanup_(erase_and_freep) void *decrypted_key = NULL;
+        struct pkcs11_callback_data *data = userdata;
+        _cleanup_free_ char *token_label = NULL;
+        CK_TOKEN_INFO updated_token_info;
+        size_t decrypted_key_size;
+        CK_OBJECT_HANDLE object;
+        char **i;
+        CK_RV rv;
+        int r;
+
+        assert(m);
+        assert(slot_info);
+        assert(token_info);
+        assert(uri);
+        assert(data);
+
+        /* Special return values:
+         *
+         * -ENOANO       → if we need a PIN but have none
+         * -ERFKILL      → if a "protected authentication path" is needed but we have no OK to use it
+         * -EOWNERDEAD   → if the PIN is locked
+         * -ENOLCK       → if the supplied PIN is incorrect
+         * -ETOOMANYREFS → ditto, but only a few tries left
+         * -EUCLEAN      → ditto, but only a single try left
+         */
+
+        token_label = pkcs11_token_label(token_info);
+        if (!token_label)
+                return log_oom();
+
+        if (FLAGS_SET(token_info->flags, CKF_PROTECTED_AUTHENTICATION_PATH)) {
+
+                if (data->secret->pkcs11_protected_authentication_path_permitted <= 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(ERFKILL), "Security token requires authentication through protected authentication path.");
+
+                rv = m->C_Login(session, CKU_USER, NULL, 0);
+                if (rv != CKR_OK)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv));
+
+                log_info("Successully logged into security token '%s' via protected authentication path.", token_label);
+                goto decrypt;
+        }
+
+        if (!FLAGS_SET(token_info->flags, CKF_LOGIN_REQUIRED)) {
+                log_info("No login into security token '%s' required.", token_label);
+                goto decrypt;
+        }
+
+        if (strv_isempty(data->secret->pkcs11_pin))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOANO), "Security Token requires PIN.");
+
+        STRV_FOREACH(i, data->secret->pkcs11_pin) {
+                rv = m->C_Login(session, CKU_USER, (CK_UTF8CHAR*) *i, strlen(*i));
+                if (rv == CKR_OK) {
+                        log_info("Successfully logged into security token '%s' with PIN.", token_label);
+                        goto decrypt;
+                }
+                if (rv == CKR_PIN_LOCKED)
+                        return log_error_errno(SYNTHETIC_ERRNO(EOWNERDEAD), "PIN of security token is blocked. Please unblock it first.");
+                if (!IN_SET(rv, CKR_PIN_INCORRECT, CKR_PIN_LEN_RANGE))
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to log into security token '%s': %s", token_label, p11_kit_strerror(rv));
+        }
+
+        rv = m->C_GetTokenInfo(slot_id, &updated_token_info);
+        if (rv != CKR_OK)
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire updated security token information for slot %lu: %s", slot_id, p11_kit_strerror(rv));
+
+        if (FLAGS_SET(updated_token_info.flags, CKF_USER_PIN_FINAL_TRY))
+                return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN), "PIN of security token incorrect, only a single try left.");
+        if (FLAGS_SET(updated_token_info.flags, CKF_USER_PIN_COUNT_LOW))
+                return log_error_errno(SYNTHETIC_ERRNO(ETOOMANYREFS), "PIN of security token incorrect, only a few tries left.");
+
+        return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), "PIN of security token incorrect.");
+
+decrypt:
+        r = pkcs11_token_find_private_key(m, session, uri, &object);
+        if (r < 0)
+                return r;
+
+        r = pkcs11_token_decrypt_data(m, session, object, data->encrypted_key->data, data->encrypted_key->size, &decrypted_key, &decrypted_key_size);
+        if (r < 0)
+                return r;
+
+        if (base64mem(decrypted_key, decrypted_key_size, &data->decrypted_password) < 0)
+                return log_oom();
+
+        return 1;
+}
diff --git a/src/home/homework-pkcs11.h b/src/home/homework-pkcs11.h
new file mode 100644 (file)
index 0000000..469ba71
--- /dev/null
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if HAVE_P11KIT
+#include "memory-util.h"
+#include "user-record.h"
+#include "pkcs11-util.h"
+
+struct pkcs11_callback_data {
+        UserRecord *user_record;
+        UserRecord *secret;
+        Pkcs11EncryptedKey *encrypted_key;
+        char *decrypted_password;
+};
+
+static inline void pkcs11_callback_data_release(struct pkcs11_callback_data *data) {
+        erase_and_free(data->decrypted_password);
+}
+
+int pkcs11_callback(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slot_id, const CK_SLOT_INFO *slot_info, const CK_TOKEN_INFO *token_info, P11KitUri *uri, void *userdata);
+#endif
diff --git a/src/home/homework-quota.c b/src/home/homework-quota.c
new file mode 100644 (file)
index 0000000..ba3917b
--- /dev/null
@@ -0,0 +1,124 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#include <sys/quota.h>
+
+#include "blockdev-util.h"
+#include "btrfs-util.h"
+#include "errno-util.h"
+#include "format-util.h"
+#include "homework-quota.h"
+#include "missing_magic.h"
+#include "quota-util.h"
+#include "stat-util.h"
+#include "user-util.h"
+
+int home_update_quota_btrfs(UserRecord *h, const char *path) {
+        int r;
+
+        assert(h);
+        assert(path);
+
+        if (h->disk_size == UINT64_MAX)
+                return 0;
+
+        /* If the user wants quota, enable it */
+        r = btrfs_quota_enable(path, true);
+        if (r == -ENOTTY)
+                return log_error_errno(r, "No btrfs quota support on subvolume %s.", path);
+        if (r < 0)
+                return log_error_errno(r, "Failed to enable btrfs quota support on %s.", path);
+
+        r = btrfs_qgroup_set_limit(path, 0, h->disk_size);
+        if (r < 0)
+                return log_error_errno(r, "Faled to set disk quota on subvolume %s: %m", path);
+
+        log_info("Set btrfs quota.");
+
+        return 0;
+}
+
+int home_update_quota_classic(UserRecord *h, const char *path) {
+        struct dqblk req;
+        dev_t devno;
+        int r;
+
+        assert(h);
+        assert(uid_is_valid(h->uid));
+        assert(path);
+
+        if (h->disk_size == UINT64_MAX)
+                return 0;
+
+        r = get_block_device(path, &devno);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine block device of %s: %m", path);
+        if (devno == 0)
+                return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system %s not backed by a block device.", path);
+
+        r = quotactl_devno(QCMD_FIXED(Q_GETQUOTA, USRQUOTA), devno, h->uid, &req);
+        if (r < 0) {
+                if (ERRNO_IS_NOT_SUPPORTED(r))
+                        return log_error_errno(r, "No UID quota support on %s.", path);
+
+                if (r != -ESRCH)
+                        return log_error_errno(r, "Failed to query disk quota for UID " UID_FMT ": %m", h->uid);
+
+                zero(req);
+        } else {
+                /* Shortcut things if everything is set up properly already */
+                if (FLAGS_SET(req.dqb_valid, QIF_BLIMITS) && h->disk_size / QIF_DQBLKSIZE == req.dqb_bhardlimit) {
+                        log_info("Configured quota already matches the intended setting, not updating quota.");
+                        return 0;
+                }
+        }
+
+        req.dqb_valid = QIF_BLIMITS;
+        req.dqb_bsoftlimit = req.dqb_bhardlimit = h->disk_size / QIF_DQBLKSIZE;
+
+        r = quotactl_devno(QCMD_FIXED(Q_SETQUOTA, USRQUOTA), devno, h->uid, &req);
+        if (r < 0) {
+                if (r == -ESRCH)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "UID quota not available on %s.", path);
+
+                return log_error_errno(r, "Failed to set disk quota for UID " UID_FMT ": %m", h->uid);
+        }
+
+        log_info("Updated per-UID quota.");
+
+        return 0;
+}
+
+int home_update_quota_auto(UserRecord *h, const char *path) {
+        struct statfs sfs;
+        int r;
+
+        assert(h);
+
+        if (h->disk_size == UINT64_MAX)
+                return 0;
+
+        if (!path) {
+                path = user_record_image_path(h);
+                if (!path)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Home record lacks image 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) ||
+            is_fs_type(&sfs, EXT4_SUPER_MAGIC))
+                return home_update_quota_classic(h, path);
+
+        if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC)) {
+
+                r = btrfs_is_subvol(path);
+                if (r < 0)
+                        return log_error_errno(errno, "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);
+
+                return home_update_quota_btrfs(h, path);
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Type of directory %s not known, cannot apply quota.", path);
+}
diff --git a/src/home/homework-quota.h b/src/home/homework-quota.h
new file mode 100644 (file)
index 0000000..e6cc16d
--- /dev/null
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "user-record.h"
+
+int home_update_quota_btrfs(UserRecord *h, const char *path);
+int home_update_quota_classic(UserRecord *h, const char *path);
+int home_update_quota_auto(UserRecord *h, const char *path);
diff --git a/src/home/homework.c b/src/home/homework.c
new file mode 100644 (file)
index 0000000..ecf07ff
--- /dev/null
@@ -0,0 +1,1482 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <stddef.h>
+#include <sys/mount.h>
+
+#include "chown-recursive.h"
+#include "copy.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "home-util.h"
+#include "homework-cifs.h"
+#include "homework-directory.h"
+#include "homework-fscrypt.h"
+#include "homework-luks.h"
+#include "homework-mount.h"
+#include "homework-pkcs11.h"
+#include "homework.h"
+#include "main-func.h"
+#include "memory-util.h"
+#include "missing_magic.h"
+#include "mount-util.h"
+#include "path-util.h"
+#include "pkcs11-util.h"
+#include "rm-rf.h"
+#include "stat-util.h"
+#include "strv.h"
+#include "tmpfile-util.h"
+#include "user-util.h"
+#include "virt.h"
+
+/* Make sure a bad password always results in a 3s delay, no matter what */
+#define BAD_PASSWORD_DELAY_USEC (3 * USEC_PER_SEC)
+
+int user_record_authenticate(
+                UserRecord *h,
+                UserRecord *secret,
+                char ***pkcs11_decrypted_passwords) {
+
+        bool need_password = false, need_token = false, need_pin = false, need_protected_authentication_path_permitted = false,
+                pin_locked = false, pin_incorrect = false, pin_incorrect_few_tries_left = false, pin_incorrect_one_try_left = false;
+        size_t n;
+        int r;
+
+        assert(h);
+        assert(secret);
+
+        /* 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
+         * configured PKCS#11 token is around and can unlock the record.
+         *
+         * Note that the pkcs11_decrypted_passwords parameter is both an input and and output parameter: it
+         * is a list of configured, decrypted PKCS#11 passwords. We typically have to call this function
+         * multiple 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 an potentially
+         * interactive) PKCS#11 dance for the relevant token again and again. */
+
+        /* First, let's see if the supplied plain-text passwords work? */
+        r = user_record_test_secret(h, secret);
+        if (r == -ENOKEY) {
+                log_info_errno(r, "None of the supplied plaintext passwords unlocks the user record's hashed passwords.");
+                need_password = true;
+        } else if (r == -ENXIO)
+                log_debug_errno(r, "User record has no hashed passwords, plaintext passwords not tested.");
+        else if (r < 0)
+                return log_error_errno(r, "Failed to validate password of record: %m");
+        else {
+                log_info("Provided password unlocks user record.");
+                return 0;
+        }
+
+        /* Second, let's see if any of the PKCS#11 security tokens are plugged in and help us */
+        for (n = 0; n < h->n_pkcs11_encrypted_key; n++) {
+#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,
+                };
+                char **pp;
+
+                /* See if any of the previously calculated passwords work */
+                STRV_FOREACH(pp, *pkcs11_decrypted_passwords) {
+                        r = test_password_one(data.encrypted_key->hashed_password, *pp);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to check supplied PKCS#11 password: %m");
+                        if (r > 0) {
+                                log_info("Previously acquired PKCS#11 password unlocks user record.");
+                                return 0;
+                        }
+                }
+
+                r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
+                switch (r) {
+                case -EAGAIN:
+                        need_token = true;
+                        break;
+                case -ENOANO:
+                        need_pin = true;
+                        break;
+                case -ERFKILL:
+                        need_protected_authentication_path_permitted = true;
+                        break;
+                case -EOWNERDEAD:
+                        pin_locked = true;
+                        break;
+                case -ENOLCK:
+                        pin_incorrect = true;
+                        break;
+                case -ETOOMANYREFS:
+                        pin_incorrect = pin_incorrect_few_tries_left = true;
+                        break;
+                case -EUCLEAN:
+                        pin_incorrect = pin_incorrect_few_tries_left = pin_incorrect_one_try_left = true;
+                        break;
+                default:
+                        if (r < 0)
+                                return r;
+
+                        r = test_password_one(data.encrypted_key->hashed_password, data.decrypted_password);
+                        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);
+
+                        log_info("Decrypted password from PKCS#11 security token %s unlocks user record.", data.encrypted_key->uri);
+
+                        r = strv_extend(pkcs11_decrypted_passwords, data.decrypted_password);
+                        if (r < 0)
+                                return log_oom();
+
+                        return 0;
+                }
+#else
+                need_token = true;
+                break;
+#endif
+        }
+
+        /* Ordered by "relevance", i.e. the most "important" or "interesting" error condition is returned. */
+        if (pin_incorrect_one_try_left)
+                return -EUCLEAN;
+        if (pin_incorrect_few_tries_left)
+                return -ETOOMANYREFS;
+        if (pin_incorrect)
+                return -ENOLCK;
+        if (pin_locked)
+                return -EOWNERDEAD;
+        if (need_protected_authentication_path_permitted)
+                return -ERFKILL;
+        if (need_pin)
+                return -ENOANO;
+        if (need_token)
+                return -EBADSLT;
+        if (need_password)
+                return -ENOKEY;
+
+        /* Hmm, this means neither PCKS#11 nor classic hashed passwords were supplied, we cannot authenticate this reasonably */
+        return log_debug_errno(SYNTHETIC_ERRNO(EKEYREVOKED), "No hashed passwords and no PKCS#11 tokens defined, cannot authenticate user record.");
+}
+
+int home_setup_undo(HomeSetup *setup) {
+        int r = 0, q;
+
+        assert(setup);
+
+        setup->root_fd = safe_close(setup->root_fd);
+
+        if (setup->undo_mount) {
+                q = umount_verbose("/run/systemd/user-home-mount");
+                if (q < 0)
+                        r = q;
+        }
+
+        if (setup->undo_dm && setup->crypt_device && setup->dm_name) {
+                q = crypt_deactivate(setup->crypt_device, setup->dm_name);
+                if (q < 0)
+                        r = q;
+        }
+
+        setup->undo_mount = false;
+        setup->undo_dm = false;
+
+        setup->dm_name = mfree(setup->dm_name);
+        setup->dm_node = mfree(setup->dm_node);
+
+        setup->loop = loop_device_unref(setup->loop);
+        crypt_free(setup->crypt_device);
+        setup->crypt_device = NULL;
+
+        explicit_bzero_safe(setup->volume_key, setup->volume_key_size);
+        setup->volume_key = mfree(setup->volume_key);
+        setup->volume_key_size = 0;
+
+        return r;
+}
+
+int home_prepare(
+                UserRecord *h,
+                bool already_activated,
+                char ***pkcs11_decrypted_passwords,
+                HomeSetup *setup,
+                UserRecord **ret_header_home) {
+
+        int r;
+
+        assert(h);
+        assert(setup);
+        assert(!setup->loop);
+        assert(!setup->crypt_device);
+        assert(setup->root_fd < 0);
+        assert(!setup->undo_dm);
+        assert(!setup->undo_mount);
+
+        /* Makes a home directory accessible (through the root_fd file descriptor, not by path!). */
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                return home_prepare_luks(h, already_activated, NULL, pkcs11_decrypted_passwords, setup, ret_header_home);
+
+        case USER_SUBVOLUME:
+        case USER_DIRECTORY:
+                r = home_prepare_directory(h, already_activated, setup);
+                break;
+
+        case USER_FSCRYPT:
+                r = home_prepare_fscrypt(h, already_activated, pkcs11_decrypted_passwords, setup);
+                break;
+
+        case USER_CIFS:
+                r = home_prepare_cifs(h, already_activated, setup);
+                break;
+
+        default:
+                return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Processing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+        }
+
+        if (r < 0)
+                return r;
+
+        if (ret_header_home)
+                *ret_header_home = NULL;
+
+        return r;
+}
+
+int home_sync_and_statfs(int root_fd, struct statfs *ret) {
+        assert(root_fd >= 0);
+
+        /* Let's sync this to disk, so that the disk space reported by fstatfs() below is accurate (for file
+         * systems such as btrfs where this is determined lazily). */
+
+        if (syncfs(root_fd) < 0)
+                return log_error_errno(errno, "Failed to synchronize file system: %m");
+
+        if (ret)
+                if (fstatfs(root_fd, ret) < 0)
+                        return log_error_errno(errno, "Failed to statfs() file system: %m");
+
+        log_info("Synchronized disk.");
+
+        return 0;
+}
+
+static int read_identity_file(int root_fd, JsonVariant **ret) {
+        _cleanup_(fclosep) FILE *identity_file = NULL;
+        _cleanup_close_ int identity_fd = -1;
+        unsigned line, column;
+        int r;
+
+        assert(root_fd >= 0);
+        assert(ret);
+
+        identity_fd = openat(root_fd, ".identity", O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
+        if (identity_fd < 0)
+                return log_error_errno(errno, "Failed to open .identity file in home directory: %m");
+
+        r = fd_verify_regular(identity_fd);
+        if (r < 0)
+                return log_error_errno(r, "Embedded identity file is not a regular file, refusing: %m");
+
+        identity_file = fdopen(identity_fd, "r");
+        if (!identity_file)
+                return log_oom();
+
+        identity_fd = -1;
+
+        r = json_parse_file(identity_file, ".identity", JSON_PARSE_SENSITIVE, ret, &line, &column);
+        if (r < 0)
+                return log_error_errno(r, "[.identity:%u:%u] Failed to parse JSON data: %m", line, column);
+
+        log_info("Read embedded .identity file.");
+
+        return 0;
+}
+
+static int write_identity_file(int root_fd, JsonVariant *v, uid_t uid) {
+        _cleanup_(json_variant_unrefp) JsonVariant *normalized = NULL;
+        _cleanup_(fclosep) FILE *identity_file = NULL;
+        _cleanup_close_ int identity_fd = -1;
+        _cleanup_free_ char *fn = NULL;
+        int r;
+
+        assert(root_fd >= 0);
+        assert(v);
+
+        normalized = json_variant_ref(v);
+
+        r = json_variant_normalize(&normalized);
+        if (r < 0)
+                log_warning_errno(r, "Failed to normalize user record, ignoring: %m");
+
+        r = tempfn_random(".identity", NULL, &fn);
+        if (r < 0)
+                return r;
+
+        identity_fd = openat(root_fd, fn, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600);
+        if (identity_fd < 0)
+                return log_error_errno(errno, "Failed to create .identity file in home directory: %m");
+
+        identity_file = fdopen(identity_fd, "w");
+        if (!identity_file) {
+                r = log_oom();
+                goto fail;
+        }
+
+        identity_fd = -1;
+
+        json_variant_dump(normalized, JSON_FORMAT_PRETTY, identity_file, NULL);
+
+        r = fflush_and_check(identity_file);
+        if (r < 0) {
+                log_error_errno(r, "Failed to write .identity file: %m");
+                goto fail;
+        }
+
+        if (fchown(fileno(identity_file), uid, uid) < 0) {
+                log_error_errno(r, "Failed to change ownership of identity file: %m");
+                goto fail;
+        }
+
+        if (renameat(root_fd, fn, root_fd, ".identity") < 0) {
+                r = log_error_errno(errno, "Failed to move identity file into place: %m");
+                goto fail;
+        }
+
+        log_info("Wrote embedded .identity file.");
+
+        return 0;
+
+fail:
+        (void) unlinkat(root_fd, fn, 0);
+        return r;
+}
+
+int home_load_embedded_identity(
+                UserRecord *h,
+                int root_fd,
+                UserRecord *header_home,
+                UserReconcileMode mode,
+                char ***pkcs11_decrypted_passwords,
+                UserRecord **ret_embedded_home,
+                UserRecord **ret_new_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *intermediate_home = NULL, *new_home = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        int r;
+
+        assert(h);
+        assert(root_fd >= 0);
+
+        r = read_identity_file(root_fd, &v);
+        if (r < 0)
+                return r;
+
+        embedded_home = user_record_new();
+        if (!embedded_home)
+                return log_oom();
+
+        r = user_record_load(embedded_home, v, USER_RECORD_LOAD_EMBEDDED);
+        if (r < 0)
+                return r;
+
+        if (!user_record_compatible(h, embedded_home))
+                return log_error_errno(SYNTHETIC_ERRNO(EREMCHG), "Hmbedded home record not compatible with host record, refusing.");
+
+        /* Insist that credentials the user supplies also unlocks any embedded records. */
+        r = user_record_authenticate(embedded_home, h, pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        /* At this point we have three records to deal with:
+         *
+         *      · 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)
+         *
+         *  Now we have to reconcile all three, and let the newest one win. */
+
+        if (header_home) {
+                /* Note we relax the requirements here. Instead of insisting that the host record is strictly
+                 * newer, let's also be OK if its equally new. If it is, we'll however insist that the
+                 * embedded record must be newer, so that we update at least one of the two. */
+
+                r = user_record_reconcile(h, header_home, mode == USER_RECONCILE_REQUIRE_NEWER ? USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL : mode, &intermediate_home);
+                if (r == -EREMCHG) /* this was supposed to be checked earlier already, but let's check this again */
+                        return log_error_errno(r, "Identity stored on host and in header don't match, refusing.");
+                if (r == -ESTALE)
+                        return log_error_errno(r, "Embedded identity record is newer than supplied record, refusing.");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to reconcile host and header identities: %m");
+                if (r == USER_RECONCILE_EMBEDDED_WON)
+                        log_info("Reconciling header user identity completed (header version was newer).");
+                else if (r == USER_RECONCILE_HOST_WON) {
+                        log_info("Reconciling header user identity completed (host version was newer).");
+
+                        if (mode == USER_RECONCILE_REQUIRE_NEWER) /* Host version is newer than the header
+                                                                   * version, hence we'll update
+                                                                   * something. This means we can relax the
+                                                                   * requirements on the embedded
+                                                                   * identity. */
+                                mode = USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL;
+                } else {
+                        assert(r == USER_RECONCILE_IDENTICAL);
+                        log_info("Reconciling user identities completed (host and header version were identical).");
+                }
+
+                h = intermediate_home;
+        }
+
+        r = user_record_reconcile(h, embedded_home, mode, &new_home);
+        if (r == -EREMCHG)
+                return log_error_errno(r, "Identity stored on host and in home don't match, refusing.");
+        if (r == -ESTALE)
+                return log_error_errno(r, "Embedded identity record is equally new or newer than supplied record, refusing.");
+        if (r < 0)
+                return log_error_errno(r, "Failed to reconcile host and embedded identities: %m");
+        if (r == USER_RECONCILE_EMBEDDED_WON)
+                log_info("Reconciling embedded user identity completed (embedded version was newer).");
+        else if (r == USER_RECONCILE_HOST_WON)
+                log_info("Reconciling embedded user identity completed (host version was newer).");
+        else {
+                assert(r == USER_RECONCILE_IDENTICAL);
+                log_info("Reconciling embedded user identity completed (host and embedded version were identical).");
+        }
+
+        if (ret_embedded_home)
+                *ret_embedded_home = TAKE_PTR(embedded_home);
+
+        if (ret_new_home)
+                *ret_new_home = TAKE_PTR(new_home);
+
+        return 0;
+}
+
+int home_store_embedded_identity(UserRecord *h, int root_fd, uid_t uid, 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, &embedded);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine new embedded record: %m");
+
+        if (old_home && user_record_equal(old_home, embedded)) {
+                log_debug("Not updating embedded home record.");
+                return 0;
+        }
+
+        /* The identity has changed, let's update it in the image */
+        r = write_identity_file(root_fd, embedded->json, h->uid);
+        if (r < 0)
+                return r;
+
+        return 1;
+}
+
+static const char *file_system_type_fd(int fd) {
+        struct statfs sfs;
+
+        assert(fd >= 0);
+
+        if (fstatfs(fd, &sfs) < 0) {
+                log_debug_errno(errno, "Failed to statfs(): %m");
+                return NULL;
+        }
+
+        if (is_fs_type(&sfs, XFS_SB_MAGIC))
+                return "xfs";
+        if (is_fs_type(&sfs, EXT4_SUPER_MAGIC))
+                return "ext4";
+        if (is_fs_type(&sfs, BTRFS_SUPER_MAGIC))
+                return "btrfs";
+
+        return NULL;
+}
+
+int home_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup) {
+        int r;
+
+        assert(h);
+        assert(used);
+        assert(setup);
+
+        r = user_record_add_binding(
+                        h,
+                        user_record_storage(used),
+                        user_record_image_path(used),
+                        setup->found_partition_uuid,
+                        setup->found_luks_uuid,
+                        setup->found_fs_uuid,
+                        setup->crypt_device ? crypt_get_cipher(setup->crypt_device) : NULL,
+                        setup->crypt_device ? crypt_get_cipher_mode(setup->crypt_device) : NULL,
+                        setup->crypt_device ? luks_volume_key_size_convert(setup->crypt_device) : UINT64_MAX,
+                        file_system_type_fd(setup->root_fd),
+                        user_record_home_directory(used),
+                        used->uid,
+                        (gid_t) used->uid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to update binding in record: %m");
+
+        return 0;
+}
+
+static int chown_recursive_directory(int root_fd, uid_t uid) {
+        int r;
+
+        assert(root_fd >= 0);
+        assert(uid_is_valid(uid));
+
+        r = fd_chown_recursive(root_fd, uid, (gid_t) uid, 0777);
+        if (r < 0)
+                return log_error_errno(r, "Failed to change ownership of files and directories: %m");
+        if (r == 0)
+                log_info("Recursive changing of ownership not necessary, skipped.");
+        else
+                log_info("Recursive changing of ownership completed.");
+
+        return 0;
+}
+
+int home_refresh(
+                UserRecord *h,
+                HomeSetup *setup,
+                UserRecord *header_home,
+                char ***pkcs11_decrypted_passwords,
+                struct statfs *ret_statfs,
+                UserRecord **ret_new_home) {
+
+        _cleanup_(user_record_unrefp) UserRecord *embedded_home = NULL, *new_home = NULL;
+        int r;
+
+        assert(h);
+        assert(setup);
+        assert(ret_new_home);
+
+        /* 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, pkcs11_decrypted_passwords, &embedded_home, &new_home);
+        if (r < 0)
+                return r;
+
+        r = home_store_header_identity_luks(new_home, setup, header_home);
+        if (r < 0)
+                return r;
+
+        r = home_store_embedded_identity(new_home, setup->root_fd, h->uid, embedded_home);
+        if (r < 0)
+                return r;
+
+        r = chown_recursive_directory(setup->root_fd, h->uid);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(setup->root_fd, ret_statfs);
+        if (r < 0)
+                return r;
+
+        *ret_new_home = TAKE_PTR(new_home);
+        return 0;
+}
+
+static int home_activate(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        int r;
+
+        assert(h);
+
+        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), "Activating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r == USER_TEST_MOUNTED)
+                return log_error_errno(SYNTHETIC_ERRNO(EALREADY), "Home directory %s is already mounted, refusing.", user_record_home_directory(h));
+
+        r = user_record_test_image_path_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r == USER_TEST_ABSENT)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Image path %s is missing, refusing.", user_record_image_path(h));
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                r = home_activate_luks(h, &pkcs11_decrypted_passwords, &new_home);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case USER_SUBVOLUME:
+        case USER_DIRECTORY:
+        case USER_FSCRYPT:
+                r = home_activate_directory(h, &pkcs11_decrypted_passwords, &new_home);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        case USER_CIFS:
+                r = home_activate_cifs(h, &pkcs11_decrypted_passwords, &new_home);
+                if (r < 0)
+                        return r;
+
+                break;
+
+        default:
+                assert_not_reached("unexpected type");
+        }
+
+        /* Note that the returned object might either be a reference to an updated version of the existing
+         * home object, or a reference to a newly allocated home object. The caller has to be able to deal
+         * with both, and consider the old object out-of-date. */
+        if (user_record_equal(h, new_home)) {
+                *ret_home = NULL;
+                return 0; /* no identity change */
+        }
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1; /* identity updated */
+}
+
+static int home_deactivate(UserRecord *h, bool force) {
+        bool done = false;
+        int r;
+
+        assert(h);
+
+        if (!h->user_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, 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), "Deactivating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r == USER_TEST_MOUNTED) {
+                if (umount2(user_record_home_directory(h), UMOUNT_NOFOLLOW | (force ? MNT_FORCE|MNT_DETACH : 0)) < 0)
+                        return log_error_errno(errno, "Failed to unmount %s: %m", user_record_home_directory(h));
+
+                log_info("Unmounting completed.");
+                done = true;
+        } else
+                log_info("Directory %s is already unmounted.", user_record_home_directory(h));
+
+        if (user_record_storage(h) == USER_LUKS) {
+                r = home_deactivate_luks(h);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        done = true;
+        }
+
+        if (!done)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOEXEC), "Home is not active.");
+
+        log_info("Everything completed.");
+        return 0;
+}
+
+static int copy_skel(int root_fd, const char *skel) {
+        int r;
+
+        assert(root_fd >= 0);
+
+        r = copy_tree_at(AT_FDCWD, skel, root_fd, ".", UID_INVALID, GID_INVALID, COPY_MERGE|COPY_REPLACE);
+        if (r == -ENOENT) {
+                log_info("Skeleton directory %s missing, ignoring.", skel);
+                return 0;
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to copy in %s: %m", skel);
+
+        log_info("Copying in %s completed.", skel);
+        return 0;
+}
+
+static int change_access_mode(int root_fd, mode_t m) {
+        assert(root_fd >= 0);
+
+        if (fchmod(root_fd, m) < 0)
+                return log_error_errno(errno, "Failed to change access mode of top-level directory: %m");
+
+        log_info("Changed top-level directory access mode to 0%o.", m);
+        return 0;
+}
+
+int home_populate(UserRecord *h, int dir_fd) {
+        int r;
+
+        assert(h);
+        assert(dir_fd >= 0);
+
+        r = copy_skel(dir_fd, user_record_skeleton_directory(h));
+        if (r < 0)
+                return r;
+
+        r = home_store_embedded_identity(h, dir_fd, h->uid, NULL);
+        if (r < 0)
+                return r;
+
+        r = chown_recursive_directory(dir_fd, h->uid);
+        if (r < 0)
+                return r;
+
+        r = change_access_mode(dir_fd, user_record_access_mode(h));
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int user_record_compile_effective_passwords(
+                UserRecord *h,
+                char ***ret_effective_passwords,
+                char ***ret_pkcs11_decrypted_passwords) {
+
+        _cleanup_(strv_free_erasep) char **effective = NULL, **pkcs11_passwords = NULL;
+        size_t n;
+        char **i;
+        int r;
+
+        assert(h);
+
+        /* We insist on at least one classic hashed password to be defined in addition to any PKCS#11 one, as
+         * a safe fallback, but also to simplify the password changing algorithm: there we require providing
+         * the old literal password only (and do not care for the old PKCS#11 token) */
+
+        if (strv_isempty(h->hashed_password))
+                return log_error_errno(EINVAL, "User record has no hashed passwords, refusing.");
+
+        /* Generates the list of plaintext passwords to propagate to LUKS/fscrypt devices, and checks whether
+         * we have a plaintext password for each hashed one. If we are missing one we'll fail, since we
+         * couldn't sync fscrypt/LUKS to the login account properly. */
+
+        STRV_FOREACH(i, h->hashed_password) {
+                bool found = false;
+                char **j;
+
+                log_debug("Looking for plaintext password for: %s", *i);
+
+                /* Let's scan all provided plaintext passwords */
+                STRV_FOREACH(j, h->password) {
+                        r = test_password_one(*i, *j);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to test plain text password: %m");
+                        if (r > 0) {
+                                if (ret_effective_passwords) {
+                                        r = strv_extend(&effective, *j);
+                                        if (r < 0)
+                                                return log_oom();
+                                }
+
+                                log_debug("Found literal plaintext password.");
+                                found = true;
+                                break;
+                        }
+                }
+
+                if (!found)
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOKEY), "Missing plaintext password for defined hashed password");
+        }
+
+        for (n = 0; n < h->n_pkcs11_encrypted_key; n++) {
+#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,
+                };
+
+                r = pkcs11_find_token(data.encrypted_key->uri, pkcs11_callback, &data);
+                if (r == -EAGAIN)
+                        return -EBADSLT;
+                if (r < 0)
+                        return r;
+
+                r = test_password_one(data.encrypted_key->hashed_password, data.decrypted_password);
+                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), "Decrypted password from token is not correct, refusing.");
+
+                if (ret_effective_passwords) {
+                        r = strv_extend(&effective, data.decrypted_password);
+                        if (r < 0)
+                                return log_oom();
+                }
+
+                if (ret_pkcs11_decrypted_passwords) {
+                        r = strv_extend(&pkcs11_passwords, data.decrypted_password);
+                        if (r < 0)
+                                return log_oom();
+                }
+#else
+                return -EBADSLT;
+#endif
+        }
+
+        if (ret_effective_passwords)
+                *ret_effective_passwords = TAKE_PTR(effective);
+        if (ret_pkcs11_decrypted_passwords)
+                *ret_pkcs11_decrypted_passwords = TAKE_PTR(pkcs11_passwords);
+
+        return 0;
+}
+
+static int home_create(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL;
+        int r;
+
+        assert(h);
+
+        if (!h->user_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks name, refusing.");
+        if (!uid_is_valid(h->uid))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks UID, refusing.");
+
+        r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r != USER_TEST_ABSENT)
+                return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Home directory %s already exists, refusing.", user_record_home_directory(h));
+
+        /* When the user didn't specify the storage type to use, fix it to be LUKS -- unless we run in a
+         * container where loopback devices and LUKS/DM are not available. Note that we typically default to
+         * the assumption of "classic" storage for most operations. However, if we create a new home, then
+         * let's user LUKS if nothing is specified. */
+        if (h->storage < 0) {
+                UserStorage new_storage;
+
+                r = detect_container();
+                if (r < 0)
+                        return log_error_errno(r, "Failed to determine whether we are in a container: %m");
+                if (r > 0) {
+                        new_storage = USER_DIRECTORY;
+
+                        r = path_is_fs_type("/home", BTRFS_SUPER_MAGIC);
+                        if (r < 0)
+                                log_debug_errno(r, "Failed to determine file system of /home, ignoring: %m");
+
+                        new_storage = r > 0 ? USER_SUBVOLUME : USER_DIRECTORY;
+                } else
+                        new_storage = USER_LUKS;
+
+                r = user_record_add_binding(
+                                h,
+                                new_storage,
+                                NULL,
+                                SD_ID128_NULL,
+                                SD_ID128_NULL,
+                                SD_ID128_NULL,
+                                NULL,
+                                NULL,
+                                UINT64_MAX,
+                                NULL,
+                                NULL,
+                                UID_INVALID,
+                                GID_INVALID);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to change storage type to LUKS: %m");
+
+                if (!h->image_path_auto) {
+                        h->image_path_auto = strjoin("/home/", user_record_user_name_and_realm(h), new_storage == USER_LUKS ? ".home" : ".homedir");
+                        if (!h->image_path_auto)
+                                return log_oom();
+                }
+        }
+
+        r = user_record_test_image_path_and_warn(h);
+        if (r < 0)
+                return r;
+        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));
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                r = home_create_luks(h, pkcs11_decrypted_passwords, effective_passwords, &new_home);
+                break;
+
+        case USER_DIRECTORY:
+        case USER_SUBVOLUME:
+                r = home_create_directory_or_subvolume(h, &new_home);
+                break;
+
+        case USER_FSCRYPT:
+                r = home_create_fscrypt(h, effective_passwords, &new_home);
+                break;
+
+        case USER_CIFS:
+                r = home_create_cifs(h, &new_home);
+                break;
+
+        default:
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTTY),
+                                       "Creating home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+        }
+        if (r < 0)
+                return r;
+
+        if (user_record_equal(h, new_home)) {
+                *ret_home = NULL;
+                return 0;
+        }
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1;
+}
+
+static int home_remove(UserRecord *h) {
+        bool deleted = false;
+        const char *ip, *hd;
+        int r;
+
+        assert(h);
+
+        if (!h->user_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record lacks user name, 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), "Removing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        hd = user_record_home_directory(h);
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r == USER_TEST_MOUNTED)
+                return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Directory %s is still mounted, refusing.", hd);
+
+        assert(hd);
+
+        r = user_record_test_image_path_and_warn(h);
+        if (r < 0)
+                return r;
+
+        ip = user_record_image_path(h);
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS: {
+                struct stat st;
+
+                assert(ip);
+
+                if (stat(ip, &st) < 0) {
+                        if (errno != -ENOENT)
+                                return log_error_errno(errno, "Failed to stat %s: %m", ip);
+
+                } else {
+                        if (S_ISREG(st.st_mode)) {
+                                if (unlink(ip) < 0) {
+                                        if (errno != ENOENT)
+                                                return log_error_errno(errno, "Failed to remove %s: %m", ip);
+                                } else
+                                        deleted = true;
+
+                        } else if (S_ISBLK(st.st_mode))
+                                log_info("Not removing file system on block device %s.", ip);
+                        else
+                                return log_error_errno(SYNTHETIC_ERRNO(ENOTBLK), "Image file %s is neither block device, nor regular, refusing removal.", ip);
+                }
+
+                break;
+        }
+
+        case USER_SUBVOLUME:
+        case USER_DIRECTORY:
+        case USER_FSCRYPT:
+                assert(ip);
+
+                r = rm_rf(ip, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME);
+                if (r < 0) {
+                        if (r != -ENOENT)
+                                return log_warning_errno(r, "Failed to remove %s: %m", ip);
+                } else
+                        deleted = true;
+
+                /* If the image path and the home directory are the same invalidate the home directory, so
+                 * that we don't remove it anymore */
+                if (path_equal(ip, hd))
+                        hd = NULL;
+
+                break;
+
+        case USER_CIFS:
+                /* Nothing else to do here: we won't remove remote stuff. */
+                log_info("Not removing home directory on remote server.");
+                break;
+
+        default:
+                assert_not_reached("unknown storage type");
+        }
+
+        if (hd) {
+                if (rmdir(hd) < 0) {
+                        if (errno != ENOENT)
+                                return log_error_errno(errno, "Failed to remove %s, ignoring: %m", hd);
+                } else
+                        deleted = true;
+        }
+
+        if (deleted)
+                log_info("Everything completed.");
+        else {
+                log_notice("Nothing to remove.");
+                return -EALREADY;
+        }
+
+        return 0;
+}
+
+static int home_validate_update(UserRecord *h, HomeSetup *setup) {
+        bool has_mount = false;
+        int r;
+
+        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)));
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+
+        has_mount = r == USER_TEST_MOUNTED;
+
+        r = user_record_test_image_path_and_warn(h);
+        if (r < 0)
+                return r;
+        if (r == USER_TEST_ABSENT)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Image path %s does not exist", user_record_image_path(h));
+
+        switch (user_record_storage(h)) {
+
+        case USER_DIRECTORY:
+        case USER_SUBVOLUME:
+        case USER_FSCRYPT:
+        case USER_CIFS:
+                break;
+
+        case USER_LUKS: {
+                r = home_validate_update_luks(h, setup);
+                if (r < 0)
+                        return r;
+                if ((r > 0) != has_mount)
+                        return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Home mount incompletely set up.");
+
+                break;
+        }
+
+        default:
+                assert_not_reached("unexpected storage type");
+        }
+
+        return has_mount; /* return true if the home record is already active */
+}
+
+static int home_update(UserRecord *h, UserRecord **ret) {
+        _cleanup_(user_record_unrefp) UserRecord *new_home = NULL, *header_home = NULL, *embedded_home = NULL;
+        _cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        bool already_activated = false;
+        int r;
+
+        assert(h);
+        assert(ret);
+
+        r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = home_validate_update(h, &setup);
+        if (r < 0)
+                return r;
+
+        already_activated = r > 0;
+
+        r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
+        if (r < 0)
+                return r;
+
+        r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
+        if (r < 0)
+                return r;
+
+        r = home_store_header_identity_luks(new_home, &setup, header_home);
+        if (r < 0)
+                return r;
+
+        r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, &setup);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(setup.root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = home_setup_undo(&setup);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+
+        *ret = TAKE_PTR(new_home);
+        return 0;
+}
+
+static int home_resize(UserRecord *h, UserRecord **ret) {
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        _cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
+        bool already_activated = false;
+        int r;
+
+        assert(h);
+        assert(ret);
+
+        if (h->disk_size == UINT64_MAX)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No target size specified, refusing.");
+
+        r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = home_validate_update(h, &setup);
+        if (r < 0)
+                return r;
+
+        already_activated = r > 0;
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                return home_resize_luks(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
+
+        case USER_DIRECTORY:
+        case USER_SUBVOLUME:
+        case USER_FSCRYPT:
+                return home_resize_directory(h, already_activated, &pkcs11_decrypted_passwords, &setup, ret);
+
+        default:
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Resizing home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+        }
+}
+
+static int home_passwd(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *embedded_home = NULL, *new_home = NULL;
+        _cleanup_(strv_free_erasep) char **effective_passwords = NULL, **pkcs11_decrypted_passwords = NULL;
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        bool already_activated = false;
+        int r;
+
+        assert(h);
+        assert(ret_home);
+
+        if (!IN_SET(user_record_storage(h), USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Changing password of home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        r = user_record_compile_effective_passwords(h, &effective_passwords, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = home_validate_update(h, &setup);
+        if (r < 0)
+                return r;
+
+        already_activated = r > 0;
+
+        r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
+        if (r < 0)
+                return r;
+
+        r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, &pkcs11_decrypted_passwords, &embedded_home, &new_home);
+        if (r < 0)
+                return r;
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                r = home_passwd_luks(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
+                if (r < 0)
+                        return r;
+                break;
+
+        case USER_FSCRYPT:
+                r = home_passwd_fscrypt(h, &setup, pkcs11_decrypted_passwords, effective_passwords);
+                if (r < 0)
+                        return r;
+                break;
+
+        default:
+                break;
+        }
+
+        r = home_store_header_identity_luks(new_home, &setup, header_home);
+        if (r < 0)
+                return r;
+
+        r = home_store_embedded_identity(new_home, setup.root_fd, h->uid, embedded_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, &setup);
+        if (r < 0)
+                return r;
+
+        r = home_sync_and_statfs(setup.root_fd, NULL);
+        if (r < 0)
+                return r;
+
+        r = home_setup_undo(&setup);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1;
+}
+
+static int home_inspect(UserRecord *h, UserRecord **ret_home) {
+        _cleanup_(user_record_unrefp) UserRecord *header_home = NULL, *new_home = NULL;
+        _cleanup_(home_setup_undo) HomeSetup setup = HOME_SETUP_INIT;
+        _cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
+        bool already_activated = false;
+        int r;
+
+        assert(h);
+        assert(ret_home);
+
+        r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = home_validate_update(h, &setup);
+        if (r < 0)
+                return r;
+
+        already_activated = r > 0;
+
+        r = home_prepare(h, already_activated, &pkcs11_decrypted_passwords, &setup, &header_home);
+        if (r < 0)
+                return r;
+
+        r = home_load_embedded_identity(h, setup.root_fd, header_home, USER_RECONCILE_ANY, &pkcs11_decrypted_passwords, NULL, &new_home);
+        if (r < 0)
+                return r;
+
+        r = home_extend_embedded_identity(new_home, h, &setup);
+        if (r < 0)
+                return r;
+
+        r = home_setup_undo(&setup);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+
+        *ret_home = TAKE_PTR(new_home);
+        return 1;
+}
+
+static int home_lock(UserRecord *h) {
+        int r;
+
+        assert(h);
+
+        if (!h->user_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, refusing.");
+        if (user_record_storage(h) != USER_LUKS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Locking home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        r = user_record_test_home_directory_and_warn(h);
+        if (r < 0)
+                return r;
+        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);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+        return 1;
+}
+
+static int home_unlock(UserRecord *h) {
+        _cleanup_(strv_free_erasep) char **pkcs11_decrypted_passwords = NULL;
+        int r;
+
+        assert(h);
+
+        if (!h->user_name)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "User record incomplete, refusing.");
+        if (user_record_storage(h) != USER_LUKS)
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTTY), "Unlocking home directories of type '%s' currently not supported.", user_storage_to_string(user_record_storage(h)));
+
+        /* Note that we don't check if $HOME is actually mounted, since we want to avoid disk accesses on
+         * that mount until we have resumed the device. */
+
+        r = user_record_authenticate(h, h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        r = home_unlock_luks(h, &pkcs11_decrypted_passwords);
+        if (r < 0)
+                return r;
+
+        log_info("Everything completed.");
+        return 1;
+}
+
+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_(fclosep) FILE *opened_file = NULL;
+        unsigned line = 0, column = 0;
+        const char *json_path = NULL;
+        FILE *json_file;
+        usec_t start;
+        int r;
+
+        start = now(CLOCK_MONOTONIC);
+
+        log_setup_service();
+
+        umask(0022);
+
+        if (argc < 2 || argc > 3)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program takes one or two arguments.");
+
+        if (argc > 2) {
+                json_path = argv[2];
+
+                opened_file = fopen(json_path, "re");
+                if (!opened_file)
+                        return log_error_errno(errno, "Failed to open %s: %m", json_path);
+
+                json_file = opened_file;
+        } else {
+                json_path = "<stdin>";
+                json_file = stdin;
+        }
+
+        r = json_parse_file(json_file, json_path, JSON_PARSE_SENSITIVE, &v, &line, &column);
+        if (r < 0)
+                return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON data: %m", json_path, line, column);
+
+        home = user_record_new();
+        if (!home)
+                return log_oom();
+
+        r = user_record_load(home, v, USER_RECORD_LOAD_FULL|USER_RECORD_LOG);
+        if (r < 0)
+                return r;
+
+        /* Well known return values of these operations, that systemd-homed knows and converts to proper D-Bus errors:
+         *
+         * EMSGSIZE        → file systems of this type cannnot be shrinked
+         * ETXTBSY         → file systems of this type can only be shrinked offline
+         * ERANGE          → file system size too small
+         * ENOLINK         → system does not support selected storage backend
+         * EPROTONOSUPPORT → system does not support selected file system
+         * ENOTTY          → operation not support on this storage
+         * ESOCKTNOSUPPORT → operation not support on this file system
+         * ENOKEY          → password incorrect (or not sufficient, or not supplied)
+         * EBADSLT         → similar, but PKCS#11 device is defined and might be able to provide password, if it was plugged in which it is not
+         * ENOANO          → suitable PKCS#11 device found, but PIN is missing to unlock it
+         * ERFKILL         → suitable PKCS#11 device found, but OK to ask for on-device interactive authentication not given
+         * EOWNERDEAD      → suitable PKCS#11 device found, but its PIN is locked
+         * ENOLCK          → suitable PKCS#11 device found, but PIN incorrect
+         * ETOOMANYREFS    → suitable PKCS#11 device found, but PIN incorrect, and only few tries left
+         * EUCLEAN         → suitable PKCS#11 device found, but PIN incorrect, and only one try left
+         * EBUSY           → file system is currently active
+         * ENOEXEC         → file system is currently not active
+         * ENOSPC          → not enough disk space for operation
+         */
+
+        if (streq(argv[1], "activate"))
+                r = home_activate(home, &new_home);
+        else if (streq(argv[1], "deactivate"))
+                r = home_deactivate(home, false);
+        else if (streq(argv[1], "deactivate-force"))
+                r = home_deactivate(home, true);
+        else if (streq(argv[1], "create"))
+                r = home_create(home, &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"))
+                r = home_resize(home, &new_home);
+        else if (streq(argv[1], "passwd"))
+                r = home_passwd(home, &new_home);
+        else if (streq(argv[1], "inspect"))
+                r = home_inspect(home, &new_home);
+        else if (streq(argv[1], "lock"))
+                r = home_lock(home);
+        else if (streq(argv[1], "unlock"))
+                r = home_unlock(home);
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb '%s'.", argv[1]);
+        if (r == -ENOKEY && !strv_isempty(home->password)) { /* There were passwords specified but they were incorrect */
+                usec_t end, n, d;
+
+                /* Make sure bad password replies always take at least 3s, and if longer multiples of 3s, so
+                 * that it's not clear how long we actually needed for our calculations. */
+                n = now(CLOCK_MONOTONIC);
+                assert(n >= start);
+
+                d = usec_sub_unsigned(n, start);
+                if (d > BAD_PASSWORD_DELAY_USEC)
+                        end = start + DIV_ROUND_UP(d, BAD_PASSWORD_DELAY_USEC) * BAD_PASSWORD_DELAY_USEC;
+                else
+                        end = start + BAD_PASSWORD_DELAY_USEC;
+
+                if (n < end)
+                        (void) usleep(usec_sub_unsigned(end, n));
+        }
+        if (r < 0)
+                return r;
+
+        /* We always pass the new record back, regardless if it changed or not. This allows our caller to
+         * prepare a fresh record, send to us, and only if it works use it without having to keep a local
+         * copy. */
+        if (new_home)
+                json_variant_dump(new_home->json, JSON_FORMAT_NEWLINE, stdout, NULL);
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);
diff --git a/src/home/homework.h b/src/home/homework.h
new file mode 100644 (file)
index 0000000..81698b7
--- /dev/null
@@ -0,0 +1,57 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <linux/fs.h>
+#include <sys/vfs.h>
+
+#include "sd-id128.h"
+
+#include "loop-util.h"
+#include "user-record.h"
+#include "user-record-util.h"
+
+typedef struct HomeSetup {
+        char *dm_name;
+        char *dm_node;
+
+        LoopDevice *loop;
+        struct crypt_device *crypt_device;
+        int root_fd;
+        sd_id128_t found_partition_uuid;
+        sd_id128_t found_luks_uuid;
+        sd_id128_t found_fs_uuid;
+
+        uint8_t fscrypt_key_descriptor[FS_KEY_DESCRIPTOR_SIZE];
+
+        void *volume_key;
+        size_t volume_key_size;
+
+        bool undo_dm;
+        bool undo_mount;
+
+        uint64_t partition_offset;
+        uint64_t partition_size;
+} HomeSetup;
+
+#define HOME_SETUP_INIT                                 \
+        {                                               \
+                .root_fd = -1,                          \
+                .partition_offset = UINT64_MAX,         \
+                .partition_size = UINT64_MAX,           \
+        }
+
+int home_setup_undo(HomeSetup *setup);
+
+int home_prepare(UserRecord *h, bool already_activated, char ***pkcs11_decrypted_passwords, HomeSetup *setup, UserRecord **ret_header_home);
+
+int home_refresh(UserRecord *h, HomeSetup *setup, UserRecord *header_home, char ***pkcs11_decrypted_passwords, struct statfs *ret_statfs, UserRecord **ret_new_home);
+
+int home_populate(UserRecord *h, int dir_fd);
+
+int home_load_embedded_identity(UserRecord *h, int root_fd, UserRecord *header_home, UserReconcileMode mode, char ***pkcs11_decrypted_passwords, 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_extend_embedded_identity(UserRecord *h, UserRecord *used, HomeSetup *setup);
+
+int user_record_authenticate(UserRecord *h, UserRecord *secret, char ***pkcs11_decrypted_passwords);
+
+int home_sync_and_statfs(int root_fd, struct statfs *ret);
diff --git a/src/home/meson.build b/src/home/meson.build
new file mode 100644 (file)
index 0000000..eb6da0b
--- /dev/null
@@ -0,0 +1,81 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+systemd_homework_sources = files('''
+        home-util.c
+        home-util.h
+        homework-cifs.c
+        homework-cifs.h
+        homework-directory.c
+        homework-directory.h
+        homework-fscrypt.c
+        homework-fscrypt.h
+        homework-luks.c
+        homework-luks.h
+        homework-mount.c
+        homework-mount.h
+        homework-pkcs11.h
+        homework-quota.c
+        homework-quota.h
+        homework.c
+        homework.h
+        user-record-util.c
+        user-record-util.h
+'''.split())
+
+if conf.get('HAVE_P11KIT') == 1
+        systemd_homework_sources += files('homework-pkcs11.c')
+endif
+
+systemd_homed_sources = files('''
+        home-util.c
+        home-util.h
+        homed-bus.c
+        homed-bus.h
+        homed-home-bus.c
+        homed-home-bus.h
+        homed-home.c
+        homed-home.h
+        homed-manager-bus.c
+        homed-manager-bus.h
+        homed-manager.c
+        homed-manager.h
+        homed-operation.c
+        homed-operation.h
+        homed-varlink.c
+        homed-varlink.h
+        homed.c
+        pwquality-util.c
+        pwquality-util.h
+        user-record-sign.c
+        user-record-sign.h
+        user-record-util.c
+        user-record-util.h
+'''.split())
+
+homectl_sources = files('''
+        home-util.c
+        home-util.h
+        homectl.c
+        pwquality-util.c
+        pwquality-util.h
+        user-record-util.c
+        user-record-util.h
+'''.split())
+
+pam_systemd_home_sym = 'src/home/pam_systemd_home.sym'
+pam_systemd_home_c = files('''
+        home-util.c
+        home-util.h
+        pam_systemd_home.c
+        user-record-util.c
+        user-record-util.h
+'''.split())
+
+if conf.get('ENABLE_HOMED') == 1
+        install_data('org.freedesktop.home1.conf',
+                     install_dir : dbuspolicydir)
+        install_data('org.freedesktop.home1.service',
+                     install_dir : dbussystemservicedir)
+        install_data('org.freedesktop.home1.policy',
+                     install_dir : polkitpolicydir)
+endif
diff --git a/src/home/org.freedesktop.home1.conf b/src/home/org.freedesktop.home1.conf
new file mode 100644 (file)
index 0000000..d615501
--- /dev/null
@@ -0,0 +1,193 @@
+<?xml version="1.0"?> <!--*-nxml-*-->
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<busconfig>
+
+        <policy user="root">
+                <allow own="org.freedesktop.home1"/>
+                <allow send_destination="org.freedesktop.home1"/>
+                <allow receive_sender="org.freedesktop.home1"/>
+        </policy>
+
+        <policy context="default">
+                <deny send_destination="org.freedesktop.home1"/>
+
+                <!-- generic interfaces -->
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.DBus.Introspectable"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.DBus.Peer"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="Get"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.DBus.Properties"
+                       send_member="GetAll"/>
+
+                <!-- Manager object -->
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="GetHomeByName"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="GetHomeByUID"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="GetUserRecordByName"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="GetUserRecordByUID"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="ListHomes"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="ActivateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="DeactivateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="RegisterHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="UnregisterHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="CreateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="RealizeHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="RemoveHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="FixateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="AuthenticateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="UpdateHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="ResizeHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="ChangePasswordHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="LockHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="UnlockHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="AcquireHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="RefHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="ReleaseHome"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Manager"
+                       send_member="LockAllHomes"/>
+
+                <!-- Home object -->
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Activate"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Deactivate"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Unregister"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Realize"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Remove"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Fixate"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Authenticate"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Update"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Resize"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="ChangePassword"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Lock"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Unlock"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Acquire"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Ref"/>
+
+                <allow send_destination="org.freedesktop.home1"
+                       send_interface="org.freedesktop.home1.Home"
+                       send_member="Release"/>
+
+                <allow receive_sender="org.freedesktop.home1"/>
+        </policy>
+
+</busconfig>
diff --git a/src/home/org.freedesktop.home1.policy b/src/home/org.freedesktop.home1.policy
new file mode 100644 (file)
index 0000000..66ef8e0
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?> <!--*-nxml-*-->
+<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
+        "http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
+
+<!-- SPDX-License-Identifier: LGPL-2.1+ -->
+
+<policyconfig>
+
+        <vendor>The systemd Project</vendor>
+        <vendor_url>http://www.freedesktop.org/wiki/Software/systemd</vendor_url>
+
+        <action id="org.freedesktop.home1.create-home">
+                <description gettext-domain="systemd">Create a home</description>
+                <message gettext-domain="systemd">Authentication is required for creating a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.home1.remove-home">
+                <description gettext-domain="systemd">Remove a home</description>
+                <message gettext-domain="systemd">Authentication is required for removing a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.home1.authenticate-home">
+                <description gettext-domain="systemd">Check credentials of a home</description>
+                <message gettext-domain="systemd">Authentication is required for checking credentials against a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.home1.update-home">
+                <description gettext-domain="systemd">Update a home</description>
+                <message gettext-domain="systemd">Authentication is required for updating a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.home1.resize-home">
+                <description gettext-domain="systemd">Resize a home</description>
+                <message gettext-domain="systemd">Authentication is required for resizing a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+        <action id="org.freedesktop.home1.passwd-home">
+                <description gettext-domain="systemd">Change password of a home</description>
+                <message gettext-domain="systemd">Authentication is required for changing the password of a user's home.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>auth_admin_keep</allow_active>
+                </defaults>
+        </action>
+
+</policyconfig>
diff --git a/src/home/org.freedesktop.home1.service b/src/home/org.freedesktop.home1.service
new file mode 100644 (file)
index 0000000..cff19b3
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+[D-BUS Service]
+Name=org.freedesktop.home1
+Exec=/bin/false
+User=root
+SystemdService=dbus-org.freedesktop.home1.service
diff --git a/src/home/pam_systemd_home.c b/src/home/pam_systemd_home.c
new file mode 100644 (file)
index 0000000..a2235bf
--- /dev/null
@@ -0,0 +1,951 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <security/pam_ext.h>
+#include <security/pam_modules.h>
+
+#include "sd-bus.h"
+
+#include "bus-common-errors.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "home-util.h"
+#include "memory-util.h"
+#include "pam-util.h"
+#include "parse-util.h"
+#include "strv.h"
+#include "user-record-util.h"
+#include "user-record.h"
+#include "user-util.h"
+
+/* Used for the "systemd-user-record-is-homed" PAM data field, to indicate whether we know whether this user
+ * record is managed by homed or by something else. */
+#define USER_RECORD_IS_HOMED INT_TO_PTR(1)
+#define USER_RECORD_IS_OTHER INT_TO_PTR(2)
+
+static int parse_argv(
+                pam_handle_t *handle,
+                int argc, const char **argv,
+                bool *please_suspend,
+                bool *debug) {
+
+        int i;
+
+        assert(argc >= 0);
+        assert(argc == 0 || argv);
+
+        for (i = 0; i < argc; i++) {
+                const char *v;
+
+                if ((v = startswith(argv[1], "suspend="))) {
+                        int k;
+
+                        k = parse_boolean(v);
+                        if (k < 0)
+                                pam_syslog(handle, LOG_WARNING, "Failed to parse suspend-please= argument, ignoring: %s", v);
+                        else if (please_suspend)
+                                *please_suspend = k;
+
+                } else if ((v = startswith(argv[i], "debug="))) {
+                        int k;
+
+                        k = parse_boolean(v);
+                        if (k < 0)
+                                pam_syslog(handle, LOG_WARNING, "Failed to parse debug= argument, ignoring: %s", v);
+                        else if (debug)
+                                *debug = k;
+
+                } else
+                        pam_syslog(handle, LOG_WARNING, "Unknown parameter '%s', ignoring", argv[i]);
+        }
+
+        return 0;
+}
+
+static int acquire_user_record(
+                pam_handle_t *handle,
+                UserRecord **ret_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;
+        const char *username = NULL, *json = NULL;
+        const void *b = NULL;
+        int r;
+
+        assert(handle);
+
+        r = pam_get_user(handle, &username, NULL);
+        if (r != PAM_SUCCESS) {
+                pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
+                return r;
+        }
+
+        if (isempty(username)) {
+                pam_syslog(handle, LOG_ERR, "User name not set.");
+                return PAM_SERVICE_ERR;
+        }
+
+        /* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
+         * user names we don't consider valid. */
+        if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username))
+                return PAM_USER_UNKNOWN;
+
+        /* Let's check if a previous run determined that this user is not managed by homed. If so, let's exit early */
+        r = pam_get_data(handle, "systemd-user-record-is-homed", &b);
+        if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
+                /* Failure */
+                pam_syslog(handle, LOG_ERR, "Failed to get PAM user record is homed flag: %s", pam_strerror(handle, r));
+                return r;
+        } else if (b == NULL)
+                /* Nothing cached yet, need to acquire fresh */
+                json = NULL;
+        else if (b != USER_RECORD_IS_HOMED)
+                /* Definitely not a homed record */
+                return PAM_USER_UNKNOWN;
+        else {
+                /* It's a homed record, let's use the cache, so that we can share it between the session and
+                 * the authentication hooks */
+                r = pam_get_data(handle, "systemd-user-record", (const void**) &json);
+                if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA)) {
+                        pam_syslog(handle, LOG_ERR, "Failed to get PAM user record data: %s", pam_strerror(handle, r));
+                        return r;
+                }
+        }
+
+        if (!json) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_free_ char *json_copy = NULL;
+
+                r = pam_acquire_bus_connection(handle, &bus);
+                if (r != PAM_SUCCESS)
+                        return r;
+
+                r = sd_bus_call_method(
+                                bus,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "GetUserRecordByName",
+                                &error,
+                                &reply,
+                                "s",
+                                username);
+                if (r < 0) {
+                        if (sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
+                            sd_bus_error_has_name(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
+                                pam_syslog(handle, LOG_DEBUG, "systemd-homed is not available: %s", bus_error_message(&error, r));
+                                goto user_unknown;
+                        }
+
+                        if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_HOME)) {
+                                pam_syslog(handle, LOG_DEBUG, "Not a user managed by systemd-homed: %s", bus_error_message(&error, r));
+                                goto user_unknown;
+                        }
+
+                        pam_syslog(handle, LOG_ERR, "Failed to query user record: %s", bus_error_message(&error, r));
+                        return PAM_SERVICE_ERR;
+                }
+
+                r = sd_bus_message_read(reply, "sbo", &json, NULL, NULL);
+                if (r < 0)
+                        return pam_bus_log_parse_error(handle, r);
+
+                json_copy = strdup(json);
+                if (!json_copy)
+                        return pam_log_oom(handle);
+
+                r = pam_set_data(handle, "systemd-user-record", json_copy, pam_cleanup_free);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set PAM user record data: %s", pam_strerror(handle, r));
+                        return r;
+                }
+
+                TAKE_PTR(json_copy);
+
+                r = pam_set_data(handle, "systemd-user-record-is-homed", USER_RECORD_IS_HOMED, NULL);
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set PAM user record is homed flag: %s", pam_strerror(handle, r));
+                        return r;
+                }
+        }
+
+        r = json_parse(json, JSON_PARSE_SENSITIVE, &v, NULL, NULL);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Failed to parse JSON user record: %s", strerror_safe(r));
+                return PAM_SERVICE_ERR;
+        }
+
+        ur = user_record_new();
+        if (!ur)
+                return pam_log_oom(handle);
+
+        r = user_record_load(ur, v, USER_RECORD_LOAD_REFUSE_SECRET);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Failed to load user record: %s", strerror_safe(r));
+                return PAM_SERVICE_ERR;
+        }
+
+        if (!streq_ptr(username, ur->user_name)) {
+                pam_syslog(handle, LOG_ERR, "Acquired user record does not match user name.");
+                return PAM_SERVICE_ERR;
+        }
+
+        if (ret_record)
+                *ret_record = TAKE_PTR(ur);
+
+        return PAM_SUCCESS;
+
+user_unknown:
+        /* Cache this, so that we don't check again */
+        r = pam_set_data(handle, "systemd-user-record-is-homed", USER_RECORD_IS_OTHER, NULL);
+        if (r != PAM_SUCCESS)
+                pam_syslog(handle, LOG_ERR, "Failed to set PAM user record is homed flag, ignoring: %s", pam_strerror(handle, r));
+
+        return PAM_USER_UNKNOWN;
+}
+
+static int release_user_record(pam_handle_t *handle) {
+        int r, k;
+
+        r = pam_set_data(handle, "systemd-user-record", NULL, NULL);
+        if (r != PAM_SUCCESS)
+                pam_syslog(handle, LOG_ERR, "Failed to release PAM user record data: %s", pam_strerror(handle, r));
+
+        k = pam_set_data(handle, "systemd-user-record-is-homed", NULL, NULL);
+        if (k != PAM_SUCCESS)
+                pam_syslog(handle, LOG_ERR, "Failed to release PAM user record is homed flag: %s", pam_strerror(handle, k));
+
+        return IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA) ? k : r;
+}
+
+static void cleanup_home_fd(pam_handle_t *handle, void *data, int error_status) {
+        safe_close(PTR_TO_FD(data));
+}
+
+static int handle_generic_user_record_error(
+                pam_handle_t *handle,
+                const char *user_name,
+                UserRecord *secret,
+                int ret,
+                const sd_bus_error *error) {
+
+        assert(user_name);
+        assert(secret);
+        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, "Home of user %s is currently absent, please plug in the necessary storage device or backing file system.", user_name);
+                pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
+                return PAM_PERM_DENIED;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_AUTHENTICATION_LIMIT_HIT)) {
+                (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too frequent unsuccessful login attempts for user %s, try again later.", user_name);
+                pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
+                return PAM_MAXTRIES;
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                /* This didn't work? Ask for an (additional?) password */
+
+                if (strv_isempty(secret->password))
+                        r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password: ");
+                else
+                        r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password incorrect or not sufficient for authentication of user %s, please try again: ", user_name);
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_password(secret, STRV_MAKE(newp), true);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                if (strv_isempty(secret->password))
+                        r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token of user %s not inserted, please enter password: ", user_name);
+                else
+                        r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Password incorrect or not sufficient, and configured security token of user %s not inserted, please enter password: ", user_name);
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_password(secret, STRV_MAKE(newp), true);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PIN_NEEDED)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Please enter security token PIN: ");
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED)) {
+
+                (void) pam_prompt(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) {
+                        pam_syslog(handle, LOG_ERR, "Failed to set PKCS#11 protected authentication path permitted flag: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect, please enter PIN for security token of user %s again: ", user_name);
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect (only a few tries left!), please enter PIN for security token of user %s again: ", user_name);
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else if (sd_bus_error_has_name(error, BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT)) {
+                _cleanup_(erase_and_freep) char *newp = NULL;
+
+                r = pam_prompt(handle, PAM_PROMPT_ECHO_OFF, &newp, "Security token PIN incorrect (only one try left!), please enter PIN for security token of user %s again: ", user_name);
+                if (r != PAM_SUCCESS)
+                        return PAM_CONV_ERR; /* no logging here */
+
+                if (isempty(newp)) {
+                        pam_syslog(handle, LOG_DEBUG, "PIN request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = user_record_set_pkcs11_pin(secret, STRV_MAKE(newp), false);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store PIN: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+
+        } else {
+                pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", user_name, bus_error_message(error, ret));
+                return PAM_SERVICE_ERR;
+        }
+
+        return PAM_SUCCESS;
+}
+
+static int acquire_home(
+                pam_handle_t *handle,
+                bool please_authenticate,
+                bool please_suspend,
+                bool debug) {
+
+        _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;
+        _cleanup_close_ int acquired_fd = -1;
+        const void *home_fd_ptr = NULL;
+        unsigned n_attempts = 0;
+        int r;
+
+        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().
+         *
+         * 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. */
+
+        /* If we already have acquired the fd, let's shortcut this */
+        r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
+        if (r == PAM_SUCCESS && PTR_TO_INT(home_fd_ptr) >= 0)
+                return PAM_SUCCESS;
+
+        r = pam_acquire_bus_connection(handle, &bus);
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = acquire_user_record(handle, &ur);
+        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 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. */
+
+        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;
+
+                if (do_auth && !secret) {
+                        const char *cached_password = NULL;
+
+                        secret = user_record_new();
+                        if (!secret)
+                                return pam_log_oom(handle);
+
+                        /* If there's already a cached password, use it. But if not let's authenticate
+                         * without anything, maybe some other authentication mechanism systemd-homed
+                         * implements (such as PKCS#11) allows us to authenticate without anything else. */
+                        r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &cached_password);
+                        if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
+                                pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
+                                return r;
+                        }
+
+                        if (!isempty(cached_password)) {
+                                r = user_record_set_password(secret, STRV_MAKE(cached_password), true);
+                                if (r < 0) {
+                                        pam_syslog(handle, LOG_ERR, "Failed to store password: %s", strerror_safe(r));
+                                        return PAM_SERVICE_ERR;
+                                }
+                        }
+                }
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                do_auth ? "AcquireHome" : "RefHome");
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = sd_bus_message_append(m, "s", ur->user_name);
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                if (do_auth) {
+                        r = bus_message_append_secret(m, secret);
+                        if (r < 0)
+                                return pam_bus_log_create_error(handle, r);
+                }
+
+                r = sd_bus_message_append(m, "b", 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))
+                                /* 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))
+                                home_locked = true; /* Similar */
+                        else {
+                                r = handle_generic_user_record_error(handle, ur->user_name, secret, r, &error);
+                                if (r == PAM_CONV_ERR) {
+                                        /* Password/PIN prompts will fail in certain environments, for example when
+                                         * we are called from OpenSSH's account or session hooks, or in systemd's
+                                         * 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);
+
+                                        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;
+                                }
+                                if (r != PAM_SUCCESS)
+                                        return r;
+                        }
+
+                } else {
+                        int fd;
+
+                        r = sd_bus_message_read(reply, "h", &fd);
+                        if (r < 0)
+                                return pam_bus_log_parse_error(handle, r);
+
+                        acquired_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+                        if (acquired_fd < 0) {
+                                pam_syslog(handle, LOG_ERR, "Failed to duplicate acquired fd: %s", bus_error_message(&error, r));
+                                return PAM_SERVICE_ERR;
+                        }
+
+                        break;
+                }
+
+                if (++n_attempts >= 5) {
+                        (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many unsuccessful login attempts for user %s, refusing.", ur->user_name);
+                        pam_syslog(handle, LOG_ERR, "Failed to acquire home for user %s: %s", ur->user_name, bus_error_message(&error, r));
+                        return PAM_MAXTRIES;
+                }
+
+                /* Try again, this time with authentication if we didn't do that before. */
+                do_auth = true;
+        }
+
+        r = pam_set_data(handle, "systemd-home-fd", FD_TO_PTR(acquired_fd), cleanup_home_fd);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Failed to set PAM bus data: %s", pam_strerror(handle, r));
+                return r;
+        }
+        TAKE_FD(acquired_fd);
+
+        if (do_auth) {
+                /* We likely just activated the home directory, let's flush out the user record, since a
+                 * newer embedded user record might have been acquired from the activation. */
+
+                r = release_user_record(handle);
+                if (!IN_SET(r, PAM_SUCCESS, PAM_NO_MODULE_DATA))
+                        return r;
+        }
+
+        pam_syslog(handle, LOG_NOTICE, "Home for user %s successfully acquired.", ur->user_name);
+
+        return PAM_SUCCESS;
+}
+
+static int release_home_fd(pam_handle_t *handle) {
+        const void *home_fd_ptr = NULL;
+        int r;
+
+        r = pam_get_data(handle, "systemd-home-fd", &home_fd_ptr);
+        if (r == PAM_NO_MODULE_DATA || PTR_TO_FD(home_fd_ptr) < 0)
+                return PAM_NO_MODULE_DATA;
+
+        r = pam_set_data(handle, "systemd-home-fd", NULL, NULL);
+        if (r != PAM_SUCCESS)
+                pam_syslog(handle, LOG_ERR, "Failed to release PAM home reference fd: %s", pam_strerror(handle, r));
+
+        return r;
+}
+
+_public_ PAM_EXTERN int pam_sm_authenticate(
+                pam_handle_t *handle,
+                int flags,
+                int argc, const char **argv) {
+
+        bool debug = false, suspend_please = false;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       &suspend_please,
+                       &debug) < 0)
+                return PAM_AUTH_ERR;
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed authenticating");
+
+        return acquire_home(handle, /* please_authenticate= */ true, suspend_please, debug);
+}
+
+_public_ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) {
+        return PAM_SUCCESS;
+}
+
+_public_ PAM_EXTERN int pam_sm_open_session(
+                pam_handle_t *handle,
+                int flags,
+                int argc, const char **argv) {
+
+        bool debug = false, suspend_please = false;
+        int r;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       &suspend_please,
+                       &debug) < 0)
+                return PAM_SESSION_ERR;
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session start");
+
+        r = acquire_home(handle, /* please_authenticate = */ false, suspend_please, debug);
+        if (r == PAM_USER_UNKNOWN) /* Not managed by us? Don't complain. */
+                return PAM_SUCCESS;
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = pam_putenv(handle, "SYSTEMD_HOME=1");
+        if (r != PAM_SUCCESS) {
+                pam_syslog(handle, LOG_ERR, "Failed to set PAM environment variable $SYSTEMD_HOME: %s", pam_strerror(handle, r));
+                return r;
+        }
+
+        /* Let's release the D-Bus connection, after all the session might live quite a long time, and we are
+         * not going to process the bus connection in that time, so let's better close before the daemon
+         * kicks us off because we are not processing anything. */
+        (void) pam_release_bus_connection(handle);
+        return PAM_SUCCESS;
+}
+
+_public_ PAM_EXTERN int pam_sm_close_session(
+                pam_handle_t *handle,
+                int 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;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       NULL,
+                       &debug) < 0)
+                return PAM_SESSION_ERR;
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed session end");
+
+        /* Let's explicitly drop the reference to the homed session, so that the subsequent ReleaseHome()
+         * call will be able to do its thing. */
+        r = release_home_fd(handle);
+        if (r == PAM_NO_MODULE_DATA) /* Nothing to do, we never acquired an fd */
+                return PAM_SUCCESS;
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = pam_get_user(handle, &username, NULL);
+        if (r != PAM_SUCCESS) {
+                pam_syslog(handle, LOG_ERR, "Failed to get user name: %s", pam_strerror(handle, r));
+                return r;
+        }
+
+        r = pam_acquire_bus_connection(handle, &bus);
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "ReleaseHome");
+        if (r < 0)
+                return pam_bus_log_create_error(handle, r);
+
+        r = sd_bus_message_append(m, "s", username);
+        if (r < 0)
+                return pam_bus_log_create_error(handle, 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_BUSY))
+                        pam_syslog(handle, LOG_NOTICE, "Not deactivating home directory of %s, as it is still used.", username);
+                else {
+                        pam_syslog(handle, LOG_ERR, "Failed to release user home: %s", bus_error_message(&error, r));
+                        return PAM_SESSION_ERR;
+                }
+        }
+
+        return PAM_SUCCESS;
+}
+
+_public_ PAM_EXTERN int pam_sm_acct_mgmt(
+                pam_handle_t *handle,
+                int flags,
+                int argc,
+                const char **argv) {
+
+        _cleanup_(user_record_unrefp) UserRecord *ur = NULL;
+        bool debug = false, please_suspend = false;
+        usec_t t;
+        int r;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       &please_suspend,
+                       &debug) < 0)
+                return PAM_AUTH_ERR;
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
+
+        r = acquire_home(handle, /* please_authenticate = */ false, please_suspend, debug);
+        if (r == PAM_USER_UNKNOWN)
+                return PAM_SUCCESS; /* we don't have anything to say about users we don't manage */
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = acquire_user_record(handle, &ur);
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = user_record_test_blocked(ur);
+        switch (r) {
+
+        case -ESTALE:
+                (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record is newer than current system time, prohibiting access.");
+                return PAM_ACCT_EXPIRED;
+
+        case -ENOLCK:
+                (void) pam_prompt(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.");
+                return PAM_ACCT_EXPIRED;
+
+        case -EL3HLT:
+                (void) pam_prompt(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.");
+                        return PAM_ACCT_EXPIRED;
+                }
+
+                break;
+        }
+
+        t = user_record_ratelimit_next_try(ur);
+        if (t != USEC_INFINITY) {
+                usec_t n = now(CLOCK_REALTIME);
+
+                if (t > n) {
+                        char buf[FORMAT_TIMESPAN_MAX];
+                        (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "Too many logins, try again in %s.",
+                                          format_timespan(buf, sizeof(buf), t - n, USEC_PER_SEC));
+
+                        return PAM_MAXTRIES;
+                }
+        }
+
+        r = user_record_test_password_change_required(ur);
+        switch (r) {
+
+        case -EKEYREVOKED:
+                (void) pam_prompt(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 requird.");
+                return PAM_NEW_AUTHTOK_REQD;
+
+        case -EKEYREJECTED:
+                /* 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. */
+                (void) pam_prompt(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.");
+                break;
+
+        case -EROFS:
+                /* All good, just means the password if we wanted to change we couldn't, but we don't need to */
+                break;
+
+        default:
+                if (r < 0) {
+                        (void) pam_prompt(handle, PAM_ERROR_MSG, NULL, "User record not valid, prohibiting access.");
+                        return PAM_AUTHTOK_EXPIRED;
+                }
+
+                break;
+        }
+
+        return PAM_SUCCESS;
+}
+
+_public_ PAM_EXTERN int pam_sm_chauthtok(
+                pam_handle_t *handle,
+                int 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;
+
+        if (parse_argv(handle,
+                       argc, argv,
+                       NULL,
+                       &debug) < 0)
+                return PAM_AUTH_ERR;
+
+        if (debug)
+                pam_syslog(handle, LOG_DEBUG, "pam-systemd-homed account management");
+
+        r = pam_acquire_bus_connection(handle, &bus);
+        if (r != PAM_SUCCESS)
+                return r;
+
+        r = acquire_user_record(handle, &ur);
+        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)) {
+                pam_syslog(handle, LOG_ERR, "Failed to get old password: %s", pam_strerror(handle, r));
+                return r;
+        }
+        r = pam_get_item(handle, PAM_AUTHTOK, (const void**) &new_password);
+        if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS)) {
+                pam_syslog(handle, LOG_ERR, "Failed to get cached password: %s", pam_strerror(handle, r));
+                return r;
+        }
+
+        if (isempty(new_password)) {
+                /* No, it's not cached, then let's ask for the password and its verification, and cache
+                 * it. */
+
+                r = pam_get_authtok_noverify(handle, &new_password, "New password: ");
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to get new password: %s", pam_strerror(handle, r));
+                        return r;
+                }
+                if (isempty(new_password)) {
+                        pam_syslog(handle, LOG_DEBUG, "Password request aborted.");
+                        return PAM_AUTHTOK_ERR;
+                }
+
+                r = pam_get_authtok_verify(handle, &new_password, "new password: "); /* Lower case, since PAM prefixes 'Repeat' */
+                if (r != PAM_SUCCESS) {
+                        pam_syslog(handle, LOG_ERR, "Failed to get password again: %s", pam_strerror(handle, r));
+                        return r;
+                }
+
+                // FIXME: pam_pwquality will ask for the password a third time. It really shouldn't do
+                // that, and instead assume the password was already verified once when it is found to be
+                // cached already. needs to be fixed in pam_pwquality
+        }
+
+        /* Now everything is cached and checked, let's exit from the preliminary check */
+        if (FLAGS_SET(flags, PAM_PRELIM_CHECK))
+                return PAM_SUCCESS;
+
+
+        old_secret = user_record_new();
+        if (!old_secret)
+                return pam_log_oom(handle);
+
+        if (!isempty(old_password)) {
+                r = user_record_set_password(old_secret, STRV_MAKE(old_password), true);
+                if (r < 0) {
+                        pam_syslog(handle, LOG_ERR, "Failed to store old password: %s", strerror_safe(r));
+                        return PAM_SERVICE_ERR;
+                }
+        }
+
+        new_secret = user_record_new();
+        if (!new_secret)
+                return pam_log_oom(handle);
+
+        r = user_record_set_password(new_secret, STRV_MAKE(new_password), true);
+        if (r < 0) {
+                pam_syslog(handle, LOG_ERR, "Failed to store new password: %s", strerror_safe(r));
+                return PAM_SERVICE_ERR;
+        }
+
+        for (;;) {
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
+
+                r = sd_bus_message_new_method_call(
+                                bus,
+                                &m,
+                                "org.freedesktop.home1",
+                                "/org/freedesktop/home1",
+                                "org.freedesktop.home1.Manager",
+                                "ChangePasswordHome");
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = sd_bus_message_append(m, "s", ur->user_name);
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = bus_message_append_secret(m, new_secret);
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = bus_message_append_secret(m, old_secret);
+                if (r < 0)
+                        return pam_bus_log_create_error(handle, r);
+
+                r = sd_bus_call(bus, m, HOME_SLOW_BUS_CALL_TIMEOUT_USEC, &error, NULL);
+                if (r < 0) {
+                        r = handle_generic_user_record_error(handle, ur->user_name, old_secret, r, &error);
+                        if (r == PAM_CONV_ERR) {
+                                pam_syslog(handle, LOG_ERR, "Failed to prompt for password/prompt.");
+                                return PAM_CONV_ERR;
+                        }
+                        if (r != PAM_SUCCESS)
+                                return r;
+                } else {
+                        pam_syslog(handle, LOG_NOTICE, "Successfully changed password for user %s.", ur->user_name);
+                        return PAM_SUCCESS;
+                }
+
+                if (++n_attempts >= 5)
+                        break;
+
+                /* Try again */
+        };
+
+        pam_syslog(handle, LOG_NOTICE, "Failed to change password for user %s: %m", ur->user_name);
+        return PAM_MAXTRIES;
+}
diff --git a/src/home/pam_systemd_home.sym b/src/home/pam_systemd_home.sym
new file mode 100644 (file)
index 0000000..daec049
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+{
+global:
+        pam_sm_authenticate;
+        pam_sm_setcred;
+        pam_sm_open_session;
+        pam_sm_close_session;
+        pam_sm_acct_mgmt;
+        pam_sm_chauthtok;
+local: *;
+};
diff --git a/src/home/pwquality-util.c b/src/home/pwquality-util.c
new file mode 100644 (file)
index 0000000..f2342b2
--- /dev/null
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <unistd.h>
+
+#if HAVE_PWQUALITY
+/* pwquality.h uses size_t but doesn't include sys/types.h on its own */
+#include <sys/types.h>
+#include <pwquality.h>
+#endif
+
+#include "bus-common-errors.h"
+#include "home-util.h"
+#include "memory-util.h"
+#include "pwquality-util.h"
+#include "strv.h"
+
+#if HAVE_PWQUALITY
+DEFINE_TRIVIAL_CLEANUP_FUNC(pwquality_settings_t*, pwquality_free_settings);
+
+static void pwquality_maybe_disable_dictionary(
+                pwquality_settings_t *pwq) {
+
+        char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
+        const char *path;
+        int r;
+
+        r = pwquality_get_str_value(pwq, PWQ_SETTING_DICT_PATH, &path);
+        if (r < 0) {
+                log_warning("Failed to read libpwquality dictionary path, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL));
+                return;
+        }
+
+        // REMOVE THIS AS SOON AS https://github.com/libpwquality/libpwquality/pull/21 IS MERGED AND RELEASED
+        if (isempty(path))
+                path = "/usr/share/cracklib/pw_dict.pwd.gz";
+
+        if (isempty(path)) {
+                log_warning("Weird, no dictionary file configured, ignoring.");
+                return;
+        }
+
+        if (access(path, F_OK) >= 0)
+                return;
+
+        if (errno != ENOENT) {
+                log_warning_errno(errno, "Failed to check if dictionary file %s exists, ignoring: %m", path);
+                return;
+        }
+
+        r = pwquality_set_int_value(pwq, PWQ_SETTING_DICT_CHECK, 0);
+        if (r < 0) {
+                log_warning("Failed to disable libpwquality dictionary check, ignoring: %s", pwquality_strerror(buf, sizeof(buf), r, NULL));
+                return;
+        }
+}
+
+int quality_check_password(
+                UserRecord *hr,
+                UserRecord *secret,
+                sd_bus_error *error) {
+
+        _cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL;
+        char buf[PWQ_MAX_ERROR_MESSAGE_LEN], **pp;
+        void *auxerror;
+        int r;
+
+        assert(hr);
+        assert(secret);
+
+        pwq = pwquality_default_settings();
+        if (!pwq)
+                return log_oom();
+
+        r = pwquality_read_config(pwq, NULL, &auxerror);
+        if (r < 0)
+                log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuation, ignoring: %s",
+                                  pwquality_strerror(buf, sizeof(buf), r, auxerror));
+
+        pwquality_maybe_disable_dictionary(pwq);
+
+        /* This is a bit more complex than one might think at first. pwquality_check() would like to know the
+         * old password to make security checks. We support arbitrary numbers of passwords however, hence we
+         * call the function once for each combination of old and new password. */
+
+        /* Iterate through all new passwords */
+        STRV_FOREACH(pp, secret->password) {
+                bool called = false;
+                char **old;
+
+                r = test_password_many(hr->hashed_password, *pp);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* This is an old password as it isn't listed in the hashedPassword field, skip it */
+                        continue;
+
+                /* Check this password against all old passwords */
+                STRV_FOREACH(old, secret->password) {
+
+                        if (streq(*pp, *old))
+                                continue;
+
+                        r = test_password_many(hr->hashed_password, *old);
+                        if (r < 0)
+                                return r;
+                        if (r > 0) /* This is a new password, not suitable as old password */
+                                continue;
+
+                        r = pwquality_check(pwq, *pp, *old, hr->user_name, &auxerror);
+                        if (r < 0)
+                                return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s",
+                                                         pwquality_strerror(buf, sizeof(buf), r, auxerror));
+
+                        called = true;
+                }
+
+                if (called)
+                        continue;
+
+                /* If there are no old passwords, let's call pwquality_check() without any. */
+                r = pwquality_check(pwq, *pp, NULL, hr->user_name, &auxerror);
+                if (r < 0)
+                        return sd_bus_error_setf(error, BUS_ERROR_LOW_PASSWORD_QUALITY, "Password too weak: %s",
+                                                 pwquality_strerror(buf, sizeof(buf), r, auxerror));
+        }
+
+        return 0;
+}
+
+#define N_SUGGESTIONS 6
+
+int suggest_passwords(void) {
+        _cleanup_(pwquality_free_settingsp) pwquality_settings_t *pwq = NULL;
+        _cleanup_strv_free_erase_ char **suggestions = NULL;
+        _cleanup_(erase_and_freep) char *joined = NULL;
+        char buf[PWQ_MAX_ERROR_MESSAGE_LEN];
+        void *auxerror;
+        size_t i;
+        int r;
+
+        pwq = pwquality_default_settings();
+        if (!pwq)
+                return log_oom();
+
+        r = pwquality_read_config(pwq, NULL, &auxerror);
+        if (r < 0)
+                log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to read libpwquality configuation, ignoring: %s",
+                                  pwquality_strerror(buf, sizeof(buf), r, auxerror));
+
+        pwquality_maybe_disable_dictionary(pwq);
+
+        suggestions = new0(char*, N_SUGGESTIONS);
+        if (!suggestions)
+                return log_oom();
+
+        for (i = 0; i < N_SUGGESTIONS; i++) {
+                r = pwquality_generate(pwq, 64, suggestions + i);
+                if (r < 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate password, ignoring: %s",
+                                               pwquality_strerror(buf, sizeof(buf), r, NULL));
+        }
+
+        joined = strv_join(suggestions, " ");
+        if (!joined)
+                return log_oom();
+
+        log_info("Password suggestions: %s", joined);
+        return 0;
+}
+
+#else
+
+int quality_check_password(
+                UserRecord *hr,
+                UserRecord *secret,
+                sd_bus_error *error) {
+
+        assert(hr);
+        assert(secret);
+
+        return 0;
+}
+
+int suggest_passwords(void) {
+        return 0;
+}
+#endif
diff --git a/src/home/pwquality-util.h b/src/home/pwquality-util.h
new file mode 100644 (file)
index 0000000..d61c04c
--- /dev/null
@@ -0,0 +1,9 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+#include "user-record.h"
+
+int quality_check_password(UserRecord *hr, UserRecord *secret, sd_bus_error *error);
+
+int suggest_passwords(void);
diff --git a/src/home/user-record-sign.c b/src/home/user-record-sign.c
new file mode 100644 (file)
index 0000000..91f8639
--- /dev/null
@@ -0,0 +1,174 @@
+#include <openssl/pem.h>
+
+#include "fd-util.h"
+#include "user-record-sign.h"
+#include "fileio.h"
+
+static int user_record_signable_json(UserRecord *ur, char **ret) {
+        _cleanup_(user_record_unrefp) UserRecord *reduced = NULL;
+        _cleanup_(json_variant_unrefp) JsonVariant *j = NULL;
+        int r;
+
+        assert(ur);
+        assert(ret);
+
+        r = user_record_clone(ur, USER_RECORD_REQUIRE_REGULAR|USER_RECORD_ALLOW_PRIVILEGED|USER_RECORD_ALLOW_PER_MACHINE|USER_RECORD_STRIP_SECRET|USER_RECORD_STRIP_BINDING|USER_RECORD_STRIP_STATUS|USER_RECORD_STRIP_SIGNATURE, &reduced);
+        if (r < 0)
+                return r;
+
+        j = json_variant_ref(reduced->json);
+
+        r = json_variant_normalize(&j);
+        if (r < 0)
+                return r;
+
+        return json_variant_format(j, 0, ret);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(EVP_MD_CTX*, EVP_MD_CTX_free);
+
+int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret) {
+        _cleanup_(json_variant_unrefp) JsonVariant *encoded = NULL, *v = NULL;
+        _cleanup_(user_record_unrefp) UserRecord *signed_ur = NULL;
+        _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
+        _cleanup_free_ char *text = NULL, *key = NULL;
+        size_t signature_size = 0, key_size = 0;
+        _cleanup_free_ void *signature = NULL;
+        _cleanup_fclose_ FILE *mf = NULL;
+        int r;
+
+        assert(ur);
+        assert(private_key);
+        assert(ret);
+
+        r = user_record_signable_json(ur, &text);
+        if (r < 0)
+                return r;
+
+        md_ctx = EVP_MD_CTX_new();
+        if (!md_ctx)
+                return -ENOMEM;
+
+        if (EVP_DigestSignInit(md_ctx, NULL, NULL, NULL, private_key) <= 0)
+                return -EIO;
+
+        /* Request signature size */
+        if (EVP_DigestSign(md_ctx, NULL, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
+                return -EIO;
+
+        signature = malloc(signature_size);
+        if (!signature)
+                return -ENOMEM;
+
+        if (EVP_DigestSign(md_ctx, signature, &signature_size, (uint8_t*) text, strlen(text)) <= 0)
+                return -EIO;
+
+        mf = open_memstream_unlocked(&key, &key_size);
+        if (!mf)
+                return -ENOMEM;
+
+        if (PEM_write_PUBKEY(mf, private_key) <= 0)
+                return -EIO;
+
+        r = fflush_and_check(mf);
+        if (r < 0)
+                return r;
+
+        r = json_build(&encoded, JSON_BUILD_ARRAY(
+                                       JSON_BUILD_OBJECT(JSON_BUILD_PAIR("data", JSON_BUILD_BASE64(signature, signature_size)),
+                                                         JSON_BUILD_PAIR("key", JSON_BUILD_STRING(key)))));
+        if (r < 0)
+                return r;
+
+        v = json_variant_ref(ur->json);
+
+        r = json_variant_set_field(&v, "signature", encoded);
+        if (r < 0)
+                return r;
+
+        if (DEBUG_LOGGING)
+                json_variant_dump(v, JSON_FORMAT_PRETTY|JSON_FORMAT_COLOR_AUTO, NULL, NULL);
+
+        signed_ur = user_record_new();
+        if (!signed_ur)
+                return log_oom();
+
+        r = user_record_load(signed_ur, v, USER_RECORD_LOAD_FULL);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(signed_ur);
+        return 0;
+}
+
+int user_record_verify(UserRecord *ur, EVP_PKEY *public_key) {
+        _cleanup_free_ char *text = NULL;
+        unsigned n_good = 0, n_bad = 0;
+        JsonVariant *array, *e;
+        int r;
+
+        assert(ur);
+        assert(public_key);
+
+        array = json_variant_by_key(ur->json, "signature");
+        if (!array)
+                return USER_RECORD_UNSIGNED;
+
+        if (!json_variant_is_array(array))
+                return -EINVAL;
+
+        if (json_variant_elements(array) == 0)
+                return USER_RECORD_UNSIGNED;
+
+        r = user_record_signable_json(ur, &text);
+        if (r < 0)
+                return r;
+
+        JSON_VARIANT_ARRAY_FOREACH(e, array) {
+                _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *md_ctx = NULL;
+                _cleanup_free_ void *signature = NULL;
+                size_t signature_size = 0;
+                JsonVariant *data;
+
+                if (!json_variant_is_object(e))
+                        return -EINVAL;
+
+                data = json_variant_by_key(e, "data");
+                if (!data)
+                        return -EINVAL;
+
+                r = json_variant_unbase64(data, &signature, &signature_size);
+                if (r < 0)
+                        return r;
+
+                md_ctx = EVP_MD_CTX_new();
+                if (!md_ctx)
+                        return -ENOMEM;
+
+                if (EVP_DigestVerifyInit(md_ctx, NULL, NULL, NULL, public_key) <= 0)
+                        return -EIO;
+
+                if (EVP_DigestVerify(md_ctx, signature, signature_size, (uint8_t*) text, strlen(text)) <= 0) {
+                        n_bad ++;
+                        continue;
+                }
+
+                n_good ++;
+        }
+
+        return n_good > 0 ? (n_bad == 0 ? USER_RECORD_SIGNED_EXCLUSIVE : USER_RECORD_SIGNED) :
+                (n_bad == 0 ? USER_RECORD_UNSIGNED : USER_RECORD_FOREIGN);
+}
+
+int user_record_has_signature(UserRecord *ur) {
+        JsonVariant *array;
+
+        array = json_variant_by_key(ur->json, "signature");
+        if (!array)
+                return false;
+
+        if (!json_variant_is_array(array))
+                return -EINVAL;
+
+        return json_variant_elements(array) > 0;
+}
diff --git a/src/home/user-record-sign.h b/src/home/user-record-sign.h
new file mode 100644 (file)
index 0000000..f045c88
--- /dev/null
@@ -0,0 +1,19 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include <openssl/evp.h>
+
+#include "user-record.h"
+
+int user_record_sign(UserRecord *ur, EVP_PKEY *private_key, UserRecord **ret);
+
+enum {
+        USER_RECORD_UNSIGNED,           /* user record has no signature */
+        USER_RECORD_SIGNED_EXCLUSIVE,   /* user record has only a signature by our own key */
+        USER_RECORD_SIGNED,             /* user record is signed by us, but by others too */
+        USER_RECORD_FOREIGN,            /* user record is not signed by us, but by others */
+};
+
+int user_record_verify(UserRecord *ur, EVP_PKEY *public_key);
+
+int user_record_has_signature(UserRecord *ur);
diff --git a/src/home/user-record-util.c b/src/home/user-record-util.c
new file mode 100644 (file)
index 0000000..34f9d76
--- /dev/null
@@ -0,0 +1,1227 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "errno-util.h"
+#include "home-util.h"
+#include "id128-util.h"
+#include "libcrypt-util.h"
+#include "mountpoint-util.h"
+#include "path-util.h"
+#include "stat-util.h"
+#include "user-record-util.h"
+#include "user-util.h"
+
+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) {
+
+        _cleanup_free_ char *hd = NULL, *un = NULL, *ip = NULL, *rr = NULL, *user_name_and_realm = NULL;
+        char smid[SD_ID128_STRING_MAX];
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+        assert(user_name);
+        assert(image_path);
+        assert(IN_SET(storage, USER_LUKS, USER_SUBVOLUME, USER_FSCRYPT, USER_DIRECTORY));
+        assert(uid_is_valid(uid));
+        assert(gid_is_valid(gid));
+
+        /* Fill in a home record from just a username and an image path. */
+
+        if (h->json)
+                return -EBUSY;
+
+        if (!suitable_user_name(user_name))
+                return -EINVAL;
+
+        if (realm) {
+                r = suitable_realm(realm);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return -EINVAL;
+        }
+
+        if (!suitable_image_path(image_path))
+                return -EINVAL;
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        un = strdup(user_name);
+        if (!un)
+                return -ENOMEM;
+
+        if (realm) {
+                rr = strdup(realm);
+                if (!rr)
+                        return -ENOMEM;
+
+                user_name_and_realm = strjoin(user_name, "@", realm);
+                if (!user_name_and_realm)
+                        return -ENOMEM;
+        }
+
+        ip = strdup(image_path);
+        if (!ip)
+                return -ENOMEM;
+
+        hd = path_join("/home/", user_name);
+        if (!hd)
+                return -ENOMEM;
+
+        r = json_build(&h->json,
+                       JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("userName", JSON_BUILD_STRING(user_name)),
+                                       JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(realm)),
+                                       JSON_BUILD_PAIR("disposition", JSON_BUILD_STRING("regular")),
+                                       JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT(
+                                                                       JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
+                                                                                                       JSON_BUILD_PAIR("imagePath", JSON_BUILD_STRING(image_path)),
+                                                                                                       JSON_BUILD_PAIR("homeDirectory", JSON_BUILD_STRING(hd)),
+                                                                                                       JSON_BUILD_PAIR("storage", JSON_BUILD_STRING(user_storage_to_string(storage))),
+                                                                                                       JSON_BUILD_PAIR("uid", JSON_BUILD_UNSIGNED(uid)),
+                                                                                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(gid))))))));
+        if (r < 0)
+                return r;
+
+        free_and_replace(h->user_name, un);
+        free_and_replace(h->realm, rr);
+        free_and_replace(h->user_name_and_realm_auto, user_name_and_realm);
+        free_and_replace(h->image_path, ip);
+        free_and_replace(h->home_directory, hd);
+        h->storage = storage;
+        h->uid = uid;
+
+        h->mask = USER_RECORD_REGULAR|USER_RECORD_BINDING;
+        return 0;
+}
+
+int group_record_synthesize(GroupRecord *g, UserRecord *h) {
+        _cleanup_free_ char *un = NULL, *rr = NULL, *group_name_and_realm = NULL;
+        char smid[SD_ID128_STRING_MAX];
+        sd_id128_t mid;
+        int r;
+
+        assert(g);
+        assert(h);
+
+        if (g->json)
+                return -EBUSY;
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        un = strdup(h->user_name);
+        if (!un)
+                return -ENOMEM;
+
+        if (h->realm) {
+                rr = strdup(h->realm);
+                if (!rr)
+                        return -ENOMEM;
+
+                group_name_and_realm = strjoin(un, "@", rr);
+                if (!group_name_and_realm)
+                        return -ENOMEM;
+        }
+
+        r = json_build(&g->json,
+                       JSON_BUILD_OBJECT(
+                                       JSON_BUILD_PAIR("groupName", JSON_BUILD_STRING(un)),
+                                       JSON_BUILD_PAIR_CONDITION(!!rr, "realm", JSON_BUILD_STRING(rr)),
+                                       JSON_BUILD_PAIR("binding", JSON_BUILD_OBJECT(
+                                                                       JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
+                                                                                                       JSON_BUILD_PAIR("gid", JSON_BUILD_UNSIGNED(user_record_gid(h))))))),
+                                       JSON_BUILD_PAIR_CONDITION(h->disposition >= 0, "disposition", JSON_BUILD_STRING(user_disposition_to_string(user_record_disposition(h)))),
+                                       JSON_BUILD_PAIR("status", JSON_BUILD_OBJECT(
+                                                                       JSON_BUILD_PAIR(sd_id128_to_string(mid, smid), JSON_BUILD_OBJECT(
+                                                                                                       JSON_BUILD_PAIR("service", JSON_BUILD_STRING("io.systemd.Home"))))))));
+        if (r < 0)
+                return r;
+
+        free_and_replace(g->group_name, un);
+        free_and_replace(g->realm, rr);
+        free_and_replace(g->group_name_and_realm_auto, group_name_and_realm);
+        g->gid = user_record_gid(h);
+        g->disposition = h->disposition;
+
+        g->mask = USER_RECORD_REGULAR|USER_RECORD_BINDING;
+        return 0;
+}
+
+int user_record_reconcile(
+                UserRecord *host,
+                UserRecord *embedded,
+                UserReconcileMode mode,
+                UserRecord **ret) {
+
+        int r, result;
+
+        /* Reconciles the identity record stored on the host with the one embedded in a $HOME
+         * directory. Returns the following error codes:
+         *
+         *     -EINVAL: one of the records not valid
+         *     -REMCHG: identity records are not about the same user
+         *     -ESTALE: embedded identity record is equally new or newer than supplied record
+         *
+         * Return the new record to use, which is either the the embedded record updated with the host
+         * binding or the host record. In both cases the secret data is stripped. */
+
+        assert(host);
+        assert(embedded);
+
+        /* Make sure both records are initialized */
+        if (!host->json || !embedded->json)
+                return -EINVAL;
+
+        /* Ensure these records actually contain user data */
+        if (!(embedded->mask & host->mask & USER_RECORD_REGULAR))
+                return -EINVAL;
+
+        /* Make sure the user name and realm matches */
+        if (!user_record_compatible(host, embedded))
+                return -EREMCHG;
+
+        /* Embedded identities may not contain secrets or binding info*/
+        if ((embedded->mask & (USER_RECORD_SECRET|USER_RECORD_BINDING)) != 0)
+                return -EINVAL;
+
+        /* The embedded record checked out, let's now figure out which of the two identities we'll consider
+         * in effect from now on. We do this by checking the last change timestamp, and in doubt always let
+         * the embedded data win. */
+        if (host->last_change_usec != UINT64_MAX &&
+            (embedded->last_change_usec == UINT64_MAX || host->last_change_usec > embedded->last_change_usec))
+
+                /* The host version is definitely newer, either because it has a version at all and the
+                 * embedded version doesn't or because it is numerically newer. */
+                result = USER_RECONCILE_HOST_WON;
+
+        else if (host->last_change_usec == embedded->last_change_usec) {
+
+                /* The nominal version number of the host and the embedded identity is the same. If so, let's
+                 * verify that, and tell the caller if we are ignoring embedded data. */
+
+                r = user_record_masked_equal(host, embedded, USER_RECORD_REGULAR|USER_RECORD_PRIVILEGED|USER_RECORD_PER_MACHINE);
+                if (r < 0)
+                        return r;
+                if (r > 0) {
+                        if (mode == USER_RECONCILE_REQUIRE_NEWER)
+                                return -ESTALE;
+
+                        result = USER_RECONCILE_IDENTICAL;
+                } else
+                        result = USER_RECONCILE_HOST_WON;
+        } else {
+                _cleanup_(json_variant_unrefp) JsonVariant *extended = NULL;
+                _cleanup_(user_record_unrefp) UserRecord *merged = NULL;
+                JsonVariant *e;
+
+                /* The embedded version is newer */
+
+                if (mode == USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL)
+                        return -ESTALE;
+
+                /* Copy in the binding data */
+                extended = json_variant_ref(embedded->json);
+
+                e = json_variant_by_key(host->json, "binding");
+                if (e) {
+                        r = json_variant_set_field(&extended, "binding", e);
+                        if (r < 0)
+                                return r;
+                }
+
+                merged = user_record_new();
+                if (!merged)
+                        return -ENOMEM;
+
+                r = user_record_load(merged, extended, USER_RECORD_LOAD_MASK_SECRET);
+                if (r < 0)
+                        return r;
+
+                *ret = TAKE_PTR(merged);
+                return USER_RECONCILE_EMBEDDED_WON; /* update */
+        }
+
+        /* Strip out secrets */
+        r = user_record_clone(host, USER_RECORD_LOAD_MASK_SECRET, ret);
+        if (r < 0)
+                return r;
+
+        return result;
+}
+
+int user_record_add_binding(
+                UserRecord *h,
+                UserStorage storage,
+                const char *image_path,
+                sd_id128_t partition_uuid,
+                sd_id128_t luks_uuid,
+                sd_id128_t fs_uuid,
+                const char *luks_cipher,
+                const char *luks_cipher_mode,
+                uint64_t luks_volume_key_size,
+                const char *file_system_type,
+                const char *home_directory,
+                uid_t uid,
+                gid_t gid) {
+
+        _cleanup_(json_variant_unrefp) JsonVariant *new_binding_entry = NULL, *binding = NULL;
+        char smid[SD_ID128_STRING_MAX], partition_uuids[37], luks_uuids[37], fs_uuids[37];
+        _cleanup_free_ char *ip = NULL, *hd = NULL;
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+
+        if (!h->json)
+                return -EUNATCH;
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+        sd_id128_to_string(mid, smid);
+
+        if (image_path) {
+                ip = strdup(image_path);
+                if (!ip)
+                        return -ENOMEM;
+        }
+
+        if (home_directory) {
+                hd = strdup(home_directory);
+                if (!hd)
+                        return -ENOMEM;
+        }
+
+        r = json_build(&new_binding_entry,
+                       JSON_BUILD_OBJECT(
+                                       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(id128_to_uuid_string(partition_uuid, partition_uuids))),
+                                       JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(luks_uuid), "luksUuid", JSON_BUILD_STRING(id128_to_uuid_string(luks_uuid, luks_uuids))),
+                                       JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(fs_uuid), "fileSystemUuid", JSON_BUILD_STRING(id128_to_uuid_string(fs_uuid, fs_uuids))),
+                                       JSON_BUILD_PAIR_CONDITION(!!luks_cipher, "luksCipher", JSON_BUILD_STRING(luks_cipher)),
+                                       JSON_BUILD_PAIR_CONDITION(!!luks_cipher_mode, "luksCipherMode", JSON_BUILD_STRING(luks_cipher_mode)),
+                                       JSON_BUILD_PAIR_CONDITION(luks_volume_key_size != UINT64_MAX, "luksVolumeKeySize", JSON_BUILD_UNSIGNED(luks_volume_key_size)),
+                                       JSON_BUILD_PAIR_CONDITION(!!file_system_type, "fileSystemType", JSON_BUILD_STRING(file_system_type)),
+                                       JSON_BUILD_PAIR_CONDITION(!!home_directory, "homeDirectory", JSON_BUILD_STRING(home_directory)),
+                                       JSON_BUILD_PAIR_CONDITION(uid_is_valid(uid), "uid", JSON_BUILD_UNSIGNED(uid)),
+                                       JSON_BUILD_PAIR_CONDITION(gid_is_valid(gid), "gid", JSON_BUILD_UNSIGNED(gid)),
+                                       JSON_BUILD_PAIR_CONDITION(storage >= 0, "storage", JSON_BUILD_STRING(user_storage_to_string(storage)))));
+        if (r < 0)
+                return r;
+
+        binding = json_variant_ref(json_variant_by_key(h->json, "binding"));
+        if (binding) {
+                _cleanup_(json_variant_unrefp) JsonVariant *be = NULL;
+
+                /* Merge the new entry with an old one, if that exists */
+                be = json_variant_ref(json_variant_by_key(binding, smid));
+                if (be) {
+                        r = json_variant_merge(&be, new_binding_entry);
+                        if (r < 0)
+                                return r;
+
+                        json_variant_unref(new_binding_entry);
+                        new_binding_entry = TAKE_PTR(be);
+                }
+        }
+
+        r = json_variant_set_field(&binding, smid, new_binding_entry);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&h->json, "binding", binding);
+        if (r < 0)
+                return r;
+
+        if (storage >= 0)
+                h->storage = storage;
+
+        if (ip)
+                free_and_replace(h->image_path, ip);
+
+        if (!sd_id128_is_null(partition_uuid))
+                h->partition_uuid = partition_uuid;
+
+        if (!sd_id128_is_null(luks_uuid))
+                h->luks_uuid = luks_uuid;
+
+        if (!sd_id128_is_null(fs_uuid))
+                h->file_system_uuid = fs_uuid;
+
+        if (hd)
+                free_and_replace(h->home_directory, hd);
+
+        if (uid_is_valid(uid))
+                h->uid = uid;
+
+        h->mask |= USER_RECORD_BINDING;
+        return 1;
+}
+
+int user_record_test_home_directory(UserRecord *h) {
+        const char *hd;
+        int r;
+
+        assert(h);
+
+        /* Returns one of USER_TEST_ABSENT, USER_TEST_MOUNTED, USER_TEST_EXISTS on success */
+
+        hd = user_record_home_directory(h);
+        if (!hd)
+                return -ENXIO;
+
+        r = is_dir(hd, false);
+        if (r == -ENOENT)
+                return USER_TEST_ABSENT;
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -ENOTDIR;
+
+        r = path_is_mount_point(hd, NULL, 0);
+        if (r < 0)
+                return r;
+        if (r > 0)
+                return USER_TEST_MOUNTED;
+
+        /* If the image path and the home directory are identical, then it's OK if the directory is
+         * populated. */
+        if (IN_SET(user_record_storage(h), USER_CLASSIC, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT)) {
+                const char *ip;
+
+                ip = user_record_image_path(h);
+                if (ip && path_equal(ip, hd))
+                        return USER_TEST_EXISTS;
+        }
+
+        /* Otherwise it's not OK */
+        r = dir_is_empty(hd);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return -EBUSY;
+
+        return USER_TEST_EXISTS;
+}
+
+int user_record_test_home_directory_and_warn(UserRecord *h) {
+        int r;
+
+        assert(h);
+
+        r = user_record_test_home_directory(h);
+        if (r == -ENXIO)
+                return log_error_errno(r, "User record lacks home directory, refusing.");
+        if (r == -ENOTDIR)
+                return log_error_errno(r, "Home directory %s is not a directory, refusing.", user_record_home_directory(h));
+        if (r == -EBUSY)
+                return log_error_errno(r, "Home directory %s exists, is not mounted but populated, refusing.", user_record_home_directory(h));
+        if (r < 0)
+                return log_error_errno(r, "Failed to test whether the home directory %s exists: %m", user_record_home_directory(h));
+
+        return r;
+}
+
+int user_record_test_image_path(UserRecord *h) {
+        const char *ip;
+        struct stat st;
+
+        assert(h);
+
+        if (user_record_storage(h) == USER_CIFS)
+                return USER_TEST_UNDEFINED;
+
+        ip = user_record_image_path(h);
+        if (!ip)
+                return -ENXIO;
+
+        if (stat(ip, &st) < 0) {
+                if (errno == ENOENT)
+                        return USER_TEST_ABSENT;
+
+                return -errno;
+        }
+
+        switch (user_record_storage(h)) {
+
+        case USER_LUKS:
+                if (S_ISREG(st.st_mode))
+                        return USER_TEST_EXISTS;
+                if (S_ISBLK(st.st_mode)) {
+                        /* For block devices we can't really be sure if the device referenced actually is the
+                         * fs we look for or some other file system (think: what does /dev/sdb1 refer
+                         * to?). Hence, let's return USER_TEST_MAYBE as an ambigious return value for these
+                         * case, except if the device path used is one of the paths that is based on a
+                         * filesystem or partition UUID or label, because in those cases we can be sure we
+                         * are referring to the right device. */
+
+                        if (PATH_STARTSWITH_SET(ip,
+                                                "/dev/disk/by-uuid/",
+                                                "/dev/disk/by-partuuid/",
+                                                "/dev/disk/by-partlabel/",
+                                                "/dev/disk/by-label/"))
+                                return USER_TEST_EXISTS;
+
+                        return USER_TEST_MAYBE;
+                }
+
+                return -EBADFD;
+
+        case USER_CLASSIC:
+        case USER_DIRECTORY:
+        case USER_SUBVOLUME:
+        case USER_FSCRYPT:
+                if (S_ISDIR(st.st_mode))
+                        return USER_TEST_EXISTS;
+
+                return -ENOTDIR;
+
+        default:
+                assert_not_reached("Unexpected record type");
+        }
+}
+
+int user_record_test_image_path_and_warn(UserRecord *h) {
+        int r;
+
+        assert(h);
+
+        r = user_record_test_image_path(h);
+        if (r == -ENXIO)
+                return log_error_errno(r, "User record lacks image path, refusing.");
+        if (r == -EBADFD)
+                return log_error_errno(r, "Image path %s is not a regular file or block device, refusing.", user_record_image_path(h));
+        if (r == -ENOTDIR)
+                return log_error_errno(r, "Image path %s is not a directory, refusing.", user_record_image_path(h));
+        if (r < 0)
+                return log_error_errno(r, "Failed to test whether image path %s exists: %m", user_record_image_path(h));
+
+        return r;
+}
+
+int user_record_test_secret(UserRecord *h, UserRecord *secret) {
+        char **i;
+        int r;
+
+        assert(h);
+
+        /* Checks whether any of the specified passwords matches any of the hashed passwords of the entry */
+
+        if (strv_isempty(h->hashed_password))
+                return -ENXIO;
+
+        STRV_FOREACH(i, secret->password) {
+                r = test_password_many(h->hashed_password, *i);
+                if (r < 0)
+                        return r;
+                if (r > 0)
+                        return 0;
+        }
+
+        return -ENOKEY;
+}
+
+int user_record_set_disk_size(UserRecord *h, uint64_t disk_size) {
+        _cleanup_(json_variant_unrefp) JsonVariant *new_per_machine = NULL, *midv = NULL, *midav = NULL, *ne = NULL;
+        _cleanup_free_ JsonVariant **array = NULL;
+        char smid[SD_ID128_STRING_MAX];
+        size_t idx = SIZE_MAX, n;
+        JsonVariant *per_machine;
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+
+        if (!h->json)
+                return -EUNATCH;
+
+        if (disk_size < USER_DISK_SIZE_MIN || disk_size > USER_DISK_SIZE_MAX)
+                return -ERANGE;
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        sd_id128_to_string(mid, smid);
+
+        r = json_variant_new_string(&midv, smid);
+        if (r < 0)
+                return r;
+
+        r = json_variant_new_array(&midav, (JsonVariant*[]) { midv }, 1);
+        if (r < 0)
+                return r;
+
+        per_machine = json_variant_by_key(h->json, "perMachine");
+        if (per_machine) {
+                size_t i;
+
+                if (!json_variant_is_array(per_machine))
+                        return -EINVAL;
+
+                n = json_variant_elements(per_machine);
+
+                array = new(JsonVariant*, n + 1);
+                if (!array)
+                        return -ENOMEM;
+
+                for (i = 0; i < n; i++) {
+                        JsonVariant *m;
+
+                        array[i] = json_variant_by_index(per_machine, i);
+
+                        if (!json_variant_is_object(array[i]))
+                                return -EINVAL;
+
+                        m = json_variant_by_key(array[i], "matchMachineId");
+                        if (!m) {
+                                /* No machineId field? Let's ignore this, but invalidate what we found so far */
+                                idx = SIZE_MAX;
+                                continue;
+                        }
+
+                        if (json_variant_equal(m, midv) ||
+                            json_variant_equal(m, midav)) {
+                                /* Matches exactly what we are looking for. Let's use this */
+                                idx = i;
+                                continue;
+                        }
+
+                        r = per_machine_id_match(m, JSON_PERMISSIVE);
+                        if (r < 0)
+                                return r;
+                        if (r > 0)
+                                /* Also matches what we are looking for, but with a broader match. In this
+                                 * case let's ignore this entry, and add a new specific one to the end. */
+                                idx = SIZE_MAX;
+                }
+
+                if (idx == SIZE_MAX)
+                        idx = n++; /* Nothing suitable found, place new entry at end */
+                else
+                        ne = json_variant_ref(array[idx]);
+
+        } else {
+                array = new(JsonVariant*, 1);
+                if (!array)
+                        return -ENOMEM;
+
+                idx = 0;
+                n = 1;
+        }
+
+        if (!ne) {
+                r = json_variant_set_field(&ne, "matchMachineId", midav);
+                if (r < 0)
+                        return r;
+        }
+
+        r = json_variant_set_field_unsigned(&ne, "diskSize", disk_size);
+        if (r < 0)
+                return r;
+
+        assert(idx < n);
+        array[idx] = ne;
+
+        r = json_variant_new_array(&new_per_machine, array, n);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&h->json, "perMachine", new_per_machine);
+        if (r < 0)
+                return r;
+
+        h->disk_size = disk_size;
+        h->mask |= USER_RECORD_PER_MACHINE;
+        return 0;
+}
+
+int user_record_update_last_changed(UserRecord *h, bool with_password) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL;
+        usec_t n;
+        int r;
+
+        assert(h);
+
+        if (!h->json)
+                return -EUNATCH;
+
+        n = now(CLOCK_REALTIME);
+
+        /* refuse downgrading */
+        if (h->last_change_usec != UINT64_MAX && h->last_change_usec >= n)
+                return -ECHRNG;
+        if (h->last_password_change_usec != UINT64_MAX && h->last_password_change_usec >= n)
+                return -ECHRNG;
+
+        v = json_variant_ref(h->json);
+
+        r = json_variant_set_field_unsigned(&v, "lastChangeUSec", n);
+        if (r < 0)
+                return r;
+
+        if (with_password) {
+                r = json_variant_set_field_unsigned(&v, "lastPasswordChangeUSec", n);
+                if (r < 0)
+                        return r;
+
+                h->last_password_change_usec = n;
+        }
+
+        h->last_change_usec = n;
+
+        json_variant_unref(h->json);
+        h->json = TAKE_PTR(v);
+
+        h->mask |= USER_RECORD_REGULAR;
+        return 0;
+}
+
+int user_record_make_hashed_password(UserRecord *h, char **secret, bool extend) {
+        _cleanup_(json_variant_unrefp) JsonVariant *priv = NULL;
+        _cleanup_strv_free_ char **np = NULL;
+        char **i;
+        int r;
+
+        assert(h);
+        assert(secret);
+
+        /* Initializes the hashed password list from the specified plaintext passwords */
+
+        if (extend) {
+                np = strv_copy(h->hashed_password);
+                if (!np)
+                        return -ENOMEM;
+
+                strv_uniq(np);
+        }
+
+        STRV_FOREACH(i, secret) {
+                _cleanup_free_ char *salt = NULL;
+                struct crypt_data cd = {};
+                char *k;
+
+                r = make_salt(&salt);
+                if (r < 0)
+                        return r;
+
+                errno = 0;
+                k = crypt_r(*i, salt, &cd);
+                if (!k)
+                        return errno_or_else(EINVAL);
+
+                r = strv_extend(&np, k);
+                if (r < 0)
+                        return r;
+        }
+
+        priv = json_variant_ref(json_variant_by_key(h->json, "privileged"));
+
+        if (strv_isempty(np))
+                r = json_variant_filter(&priv, STRV_MAKE("hashedPassword"));
+        else {
+                _cleanup_(json_variant_unrefp) JsonVariant *new_array = NULL;
+
+                r = json_variant_new_array_strv(&new_array, np);
+                if (r < 0)
+                        return r;
+
+                r = json_variant_set_field(&priv, "hashedPassword", new_array);
+                if (r < 0)
+                        return r;
+        }
+
+        r = json_variant_set_field(&h->json, "privileged", priv);
+        if (r < 0)
+                return r;
+
+        strv_free_and_replace(h->hashed_password, np);
+
+        SET_FLAG(h->mask, USER_RECORD_PRIVILEGED, !json_variant_is_blank_object(priv));
+        return 0;
+}
+
+int user_record_set_hashed_password(UserRecord *h, char **hashed_password) {
+        _cleanup_(json_variant_unrefp) JsonVariant *priv = NULL;
+        _cleanup_strv_free_ char **copy = NULL;
+        int r;
+
+        assert(h);
+
+        priv = json_variant_ref(json_variant_by_key(h->json, "privileged"));
+
+        if (strv_isempty(hashed_password))
+                r = json_variant_filter(&priv, STRV_MAKE("hashedPassword"));
+        else {
+                _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+
+                copy = strv_copy(hashed_password);
+                if (!copy)
+                        return -ENOMEM;
+
+                strv_uniq(copy);
+
+                r = json_variant_new_array_strv(&array, copy);
+                if (r < 0)
+                        return r;
+
+                r = json_variant_set_field(&priv, "hashedPassword", array);
+        }
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&h->json, "privileged", priv);
+        if (r < 0)
+                return r;
+
+        strv_free_and_replace(h->hashed_password, copy);
+
+        SET_FLAG(h->mask, USER_RECORD_PRIVILEGED, !json_variant_is_blank_object(priv));
+        return 0;
+}
+
+int user_record_set_password(UserRecord *h, char **password, bool prepend) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        _cleanup_(strv_free_erasep) char **e = NULL;
+        int r;
+
+        assert(h);
+
+        if (prepend) {
+                e = strv_copy(password);
+                if (!e)
+                        return -ENOMEM;
+
+                r = strv_extend_strv(&e, h->password, true);
+                if (r < 0)
+                        return r;
+
+                strv_uniq(e);
+
+                if (strv_equal(h->password, e))
+                        return 0;
+
+        } else {
+                if (strv_equal(h->password, password))
+                        return 0;
+
+                e = strv_copy(password);
+                if (!e)
+                        return -ENOMEM;
+
+                strv_uniq(e);
+        }
+
+        w = json_variant_ref(json_variant_by_key(h->json, "secret"));
+
+        if (strv_isempty(e))
+                r = json_variant_filter(&w, STRV_MAKE("password"));
+        else {
+                _cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
+
+                r = json_variant_new_array_strv(&l, e);
+                if (r < 0)
+                        return r;
+
+                json_variant_sensitive(l);
+
+                r = json_variant_set_field(&w, "password", l);
+        }
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&h->json, "secret", w);
+        if (r < 0)
+                return r;
+
+        strv_free_and_replace(h->password, e);
+
+        SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
+        return 0;
+}
+
+int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        _cleanup_(strv_free_erasep) char **e = NULL;
+        int r;
+
+        assert(h);
+
+        if (prepend) {
+                e = strv_copy(pin);
+                if (!e)
+                        return -ENOMEM;
+
+                r = strv_extend_strv(&e, h->pkcs11_pin, true);
+                if (r < 0)
+                        return r;
+
+                strv_uniq(e);
+
+                if (strv_equal(h->pkcs11_pin, e))
+                        return 0;
+
+        } else {
+                if (strv_equal(h->pkcs11_pin, pin))
+                        return 0;
+
+                e = strv_copy(pin);
+                if (!e)
+                        return -ENOMEM;
+
+                strv_uniq(e);
+        }
+
+        w = json_variant_ref(json_variant_by_key(h->json, "secret"));
+
+        if (strv_isempty(e))
+                r = json_variant_filter(&w, STRV_MAKE("pkcs11Pin"));
+        else {
+                _cleanup_(json_variant_unrefp) JsonVariant *l = NULL;
+
+                r = json_variant_new_array_strv(&l, e);
+                if (r < 0)
+                        return r;
+
+                json_variant_sensitive(l);
+
+                r = json_variant_set_field(&w, "pkcs11Pin", l);
+        }
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&h->json, "secret", w);
+        if (r < 0)
+                return r;
+
+        strv_free_and_replace(h->pkcs11_pin, e);
+
+        SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
+        return 0;
+}
+
+int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        int r;
+
+        assert(h);
+
+        w = json_variant_ref(json_variant_by_key(h->json, "secret"));
+
+        if (b < 0)
+                r = json_variant_filter(&w, STRV_MAKE("pkcs11ProtectedAuthenticationPathPermitted"));
+        else
+                r = json_variant_set_field_boolean(&w, "pkcs11ProtectedAuthenticationPathPermitted", b);
+        if (r < 0)
+                return r;
+
+        if (json_variant_is_blank_object(w))
+                r = json_variant_filter(&h->json, STRV_MAKE("secret"));
+        else
+                r = json_variant_set_field(&h->json, "secret", w);
+        if (r < 0)
+                return r;
+
+        h->pkcs11_protected_authentication_path_permitted = b;
+
+        SET_FLAG(h->mask, USER_RECORD_SECRET, !json_variant_is_blank_object(w));
+        return 0;
+}
+
+static bool per_machine_entry_empty(JsonVariant *v) {
+        const char *k;
+        _unused_ JsonVariant *e;
+
+        JSON_VARIANT_OBJECT_FOREACH(k, e, v)
+                if (!STR_IN_SET(k, "matchMachineId", "matchHostname"))
+                        return false;
+
+        return true;
+}
+
+int user_record_set_password_change_now(UserRecord *h, int b) {
+        _cleanup_(json_variant_unrefp) JsonVariant *w = NULL;
+        JsonVariant *per_machine;
+        int r;
+
+        assert(h);
+
+        w = json_variant_ref(h->json);
+
+        if (b < 0)
+                r = json_variant_filter(&w, STRV_MAKE("passwordChangeNow"));
+        else
+                r = json_variant_set_field_boolean(&w, "passwordChangeNow", b);
+        if (r < 0)
+                return r;
+
+        /* Also drop the field from all perMachine entries */
+        per_machine = json_variant_by_key(w, "perMachine");
+        if (per_machine) {
+                _cleanup_(json_variant_unrefp) JsonVariant *array = NULL;
+                JsonVariant *e;
+
+                JSON_VARIANT_ARRAY_FOREACH(e, per_machine) {
+                        _cleanup_(json_variant_unrefp) JsonVariant *z = NULL;
+
+                        if (!json_variant_is_object(e))
+                                return -EINVAL;
+
+                        z = json_variant_ref(e);
+
+                        r = json_variant_filter(&z, STRV_MAKE("passwordChangeNow"));
+                        if (r < 0)
+                                return r;
+
+                        if (per_machine_entry_empty(z))
+                                continue;
+
+                        r = json_variant_append_array(&array, z);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (json_variant_is_blank_array(array))
+                        r = json_variant_filter(&w, STRV_MAKE("perMachine"));
+                else
+                        r = json_variant_set_field(&w, "perMachine", array);
+                if (r < 0)
+                        return r;
+
+                SET_FLAG(h->mask, USER_RECORD_PER_MACHINE, !json_variant_is_blank_array(array));
+        }
+
+        json_variant_unref(h->json);
+        h->json = TAKE_PTR(w);
+
+        h->password_change_now = b;
+
+        return 0;
+}
+
+int user_record_merge_secret(UserRecord *h, UserRecord *secret) {
+        int r;
+
+        assert(h);
+
+        /* Merges the secrets from 'secret' into 'h'. */
+
+        r = user_record_set_password(h, secret->password, true);
+        if (r < 0)
+                return r;
+
+        r = user_record_set_pkcs11_pin(h, secret->pkcs11_pin, true);
+        if (r < 0)
+                return r;
+
+        if (secret->pkcs11_protected_authentication_path_permitted >= 0) {
+                r = user_record_set_pkcs11_protected_authentication_path_permitted(h, secret->pkcs11_protected_authentication_path_permitted);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+int user_record_good_authentication(UserRecord *h) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
+        char buf[SD_ID128_STRING_MAX];
+        uint64_t counter, usec;
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+
+        switch (h->good_authentication_counter) {
+        case UINT64_MAX:
+                counter = 1;
+                break;
+        case UINT64_MAX-1:
+                counter = h->good_authentication_counter; /* saturate */
+                break;
+        default:
+                counter = h->good_authentication_counter + 1;
+                break;
+        }
+
+        usec = now(CLOCK_REALTIME);
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        v = json_variant_ref(h->json);
+        w = json_variant_ref(json_variant_by_key(v, "status"));
+        z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
+
+        r = json_variant_set_field_unsigned(&z, "goodAuthenticationCounter", counter);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field_unsigned(&z, "lastGoodAuthenticationUSec", usec);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&w, buf, z);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&v, "status", w);
+        if (r < 0)
+                return r;
+
+        json_variant_unref(h->json);
+        h->json = TAKE_PTR(v);
+
+        h->good_authentication_counter = counter;
+        h->last_good_authentication_usec = usec;
+
+        h->mask |= USER_RECORD_STATUS;
+        return 0;
+}
+
+int user_record_bad_authentication(UserRecord *h) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
+        char buf[SD_ID128_STRING_MAX];
+        uint64_t counter, usec;
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+
+        switch (h->bad_authentication_counter) {
+        case UINT64_MAX:
+                counter = 1;
+                break;
+        case UINT64_MAX-1:
+                counter = h->bad_authentication_counter; /* saturate */
+                break;
+        default:
+                counter = h->bad_authentication_counter + 1;
+                break;
+        }
+
+        usec = now(CLOCK_REALTIME);
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        v = json_variant_ref(h->json);
+        w = json_variant_ref(json_variant_by_key(v, "status"));
+        z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
+
+        r = json_variant_set_field_unsigned(&z, "badAuthenticationCounter", counter);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field_unsigned(&z, "lastBadAuthenticationUSec", usec);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&w, buf, z);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&v, "status", w);
+        if (r < 0)
+                return r;
+
+        json_variant_unref(h->json);
+        h->json = TAKE_PTR(v);
+
+        h->bad_authentication_counter = counter;
+        h->last_bad_authentication_usec = usec;
+
+        h->mask |= USER_RECORD_STATUS;
+        return 0;
+}
+
+int user_record_ratelimit(UserRecord *h) {
+        _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *w = NULL, *z = NULL;
+        usec_t usec, new_ratelimit_begin_usec, new_ratelimit_count;
+        char buf[SD_ID128_STRING_MAX];
+        sd_id128_t mid;
+        int r;
+
+        assert(h);
+
+        usec = now(CLOCK_REALTIME);
+
+        if (h->ratelimit_begin_usec != UINT64_MAX && h->ratelimit_begin_usec > usec)
+                /* Hmm, time is running backwards? Say no! */
+                return 0;
+        else if (h->ratelimit_begin_usec == UINT64_MAX ||
+                 usec_add(h->ratelimit_begin_usec, user_record_ratelimit_interval_usec(h)) <= usec) {
+                /* Fresh start */
+                new_ratelimit_begin_usec = usec;
+                new_ratelimit_count = 1;
+        } else if (h->ratelimit_count < user_record_ratelimit_burst(h)) {
+                /* Count up */
+                new_ratelimit_begin_usec = h->ratelimit_begin_usec;
+                new_ratelimit_count = h->ratelimit_count + 1;
+        } else
+                /* Limit hit */
+                return 0;
+
+        r = sd_id128_get_machine(&mid);
+        if (r < 0)
+                return r;
+
+        v = json_variant_ref(h->json);
+        w = json_variant_ref(json_variant_by_key(v, "status"));
+        z = json_variant_ref(json_variant_by_key(w, sd_id128_to_string(mid, buf)));
+
+        r = json_variant_set_field_unsigned(&z, "rateLimitBeginUSec", new_ratelimit_begin_usec);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field_unsigned(&z, "rateLimitCount", new_ratelimit_count);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&w, buf, z);
+        if (r < 0)
+                return r;
+
+        r = json_variant_set_field(&v, "status", w);
+        if (r < 0)
+                return r;
+
+        json_variant_unref(h->json);
+        h->json = TAKE_PTR(v);
+
+        h->ratelimit_begin_usec = new_ratelimit_begin_usec;
+        h->ratelimit_count = new_ratelimit_count;
+
+        h->mask |= USER_RECORD_STATUS;
+        return 1;
+}
+
+int user_record_is_supported(UserRecord *hr, sd_bus_error *error) {
+        assert(hr);
+
+        if (hr->disposition >= 0 && hr->disposition != USER_REGULAR)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot manage anything but regular users.");
+
+        if (hr->storage >= 0 && !IN_SET(hr->storage, USER_LUKS, USER_DIRECTORY, USER_SUBVOLUME, USER_FSCRYPT, USER_CIFS))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User record has storage type this service cannot manage.");
+
+        if (gid_is_valid(hr->gid) && hr->uid != (uid_t) hr->gid)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "User record has to have matching UID/GID fields.");
+
+        if (hr->service && !streq(hr->service, "io.systemd.Home"))
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not accepted with service not matching io.systemd.Home.");
+
+        return 0;
+}
diff --git a/src/home/user-record-util.h b/src/home/user-record-util.h
new file mode 100644 (file)
index 0000000..6afc8df
--- /dev/null
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "user-record.h"
+#include "group-record.h"
+
+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);
+
+typedef enum UserReconcileMode {
+        USER_RECONCILE_ANY,
+        USER_RECONCILE_REQUIRE_NEWER,          /* host version must be newer than embedded version */
+        USER_RECONCILE_REQUIRE_NEWER_OR_EQUAL, /* similar, but may also be equal */
+        _USER_RECONCILE_MODE_MAX,
+        _USER_RECONCILE_MODE_INVALID = -1,
+} UserReconcileMode;
+
+enum { /* return values */
+        USER_RECONCILE_HOST_WON,
+        USER_RECONCILE_EMBEDDED_WON,
+        USER_RECONCILE_IDENTICAL,
+};
+
+int user_record_reconcile(UserRecord *host, UserRecord *embedded, UserReconcileMode mode, UserRecord **ret);
+int user_record_add_binding(UserRecord *h, UserStorage storage, const char *image_path, sd_id128_t partition_uuid, sd_id128_t luks_uuid, sd_id128_t fs_uuid, const char *luks_cipher, const char *luks_cipher_mode, uint64_t luks_volume_key_size, const char *file_system_type, const char *home_directory, uid_t uid, gid_t gid);
+
+/* Results of the two test functions below. */
+enum {
+        USER_TEST_UNDEFINED, /* Returned by user_record_test_image_path() if the storage type knows no image paths */
+        USER_TEST_ABSENT,
+        USER_TEST_EXISTS,
+        USER_TEST_MOUNTED,   /* Only applies to user_record_test_home_directory(), when the home directory exists. */
+        USER_TEST_MAYBE,     /* Only applies to LUKS devices: block device exists, but we don't know if it's the right one */
+};
+
+int user_record_test_home_directory(UserRecord *h);
+int user_record_test_home_directory_and_warn(UserRecord *h);
+int user_record_test_image_path(UserRecord *h);
+int user_record_test_image_path_and_warn(UserRecord *h);
+
+int user_record_test_secret(UserRecord *h, UserRecord *secret);
+
+int user_record_update_last_changed(UserRecord *h, bool with_password);
+int user_record_set_disk_size(UserRecord *h, uint64_t disk_size);
+int user_record_set_password(UserRecord *h, char **password, bool prepend);
+int user_record_make_hashed_password(UserRecord *h, char **password, bool extend);
+int user_record_set_hashed_password(UserRecord *h, char **hashed_password);
+int user_record_set_pkcs11_pin(UserRecord *h, char **pin, bool prepend);
+int user_record_set_pkcs11_protected_authentication_path_permitted(UserRecord *h, int b);
+int user_record_set_password_change_now(UserRecord *h, int b);
+int user_record_merge_secret(UserRecord *h, UserRecord *secret);
+int user_record_good_authentication(UserRecord *h);
+int user_record_bad_authentication(UserRecord *h);
+int user_record_ratelimit(UserRecord *h);
+
+int user_record_is_supported(UserRecord *hr, sd_bus_error *error);
index deecd9b8db30d310a7d2607ec9cbff57c5b72f68..21f647149514ab1a77f3c6ccf6ec15b8169057e0 100644 (file)
@@ -8,7 +8,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "def.h"
 #include "env-file-label.h"
 #include "env-file.h"
index cd4d5414503bbbbb0b220972a96f4561da528ccf..19435f80fea40726960b69848872e6dff57741fa 100644 (file)
@@ -4,9 +4,12 @@
 #include <stdio.h>
 
 #include "alloc-util.h"
+#include "gpt.h"
 #include "id128-print.h"
 #include "main-func.h"
 #include "pretty-print.h"
+#include "strv.h"
+#include "format-table.h"
 #include "terminal-util.h"
 #include "util.h"
 #include "verbs.h"
@@ -63,6 +66,85 @@ static int verb_invocation_id(int argc, char **argv, void *userdata) {
         return id128_pretty_print(id, arg_mode);
 }
 
+static int show_one(Table **table, const char *name, sd_id128_t uuid, bool first) {
+        int r;
+
+        if (arg_mode == ID128_PRINT_PRETTY) {
+                _cleanup_free_ char *id = NULL;
+
+                id = strreplace(name, "-", "_");
+                if (!id)
+                        return log_oom();
+
+                ascii_strupper(id);
+
+                r = id128_pretty_print_sample(id, uuid);
+                if (r < 0)
+                        return r;
+                if (!first)
+                        puts("");
+                return 0;
+
+        } else {
+                if (!*table) {
+                        *table = table_new("name", "id");
+                        if (!*table)
+                                return log_oom();
+                        table_set_width(*table, 0);
+                }
+
+                return table_add_many(*table,
+                                      TABLE_STRING, name,
+                                      arg_mode == ID128_PRINT_ID128 ? TABLE_ID128 : TABLE_UUID,
+                                      uuid);
+        }
+}
+
+static int verb_show(int argc, char **argv, void *userdata) {
+        _cleanup_(table_unrefp) Table *table = NULL;
+        char **p;
+        int r;
+
+        argv = strv_skip(argv, 1);
+        if (strv_isempty(argv))
+                for (const GptPartitionType *e = gpt_partition_type_table; e->name; e++) {
+                        r = show_one(&table, e->name, e->uuid, e == gpt_partition_type_table);
+                        if (r < 0)
+                                return r;
+                }
+        else
+                STRV_FOREACH(p, argv) {
+                        sd_id128_t uuid;
+                        bool have_uuid;
+                        const char *id;
+
+                        /* Check if the argument is an actual UUID first */
+                        have_uuid = sd_id128_from_string(*p, &uuid) >= 0;
+
+                        if (have_uuid)
+                                id = gpt_partition_type_uuid_to_string(uuid) ?: "XYZ";
+                        else {
+                                r = gpt_partition_type_uuid_from_string(*p, &uuid);
+                                if (r < 0)
+                                        return log_error_errno(r, "Unknown identifier \"%s\".", *p);
+
+                                id = *p;
+                        }
+
+                        r = show_one(&table, id, uuid, p == argv);
+                        if (r < 0)
+                                return r;
+                }
+
+        if (table) {
+                r = table_print(table, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to print table: %m");
+        }
+
+        return 0;
+}
+
 static int help(void) {
         _cleanup_free_ char *link = NULL;
         int r;
@@ -74,10 +156,11 @@ static int help(void) {
         printf("%s [OPTIONS...] COMMAND\n\n"
                "%sGenerate and print 128bit identifiers.%s\n"
                "\nCommands:\n"
-               "  new                     Generate a new id128 string\n"
+               "  new                     Generate a new ID\n"
                "  machine-id              Print the ID of current machine\n"
                "  boot-id                 Print the ID of current boot\n"
                "  invocation-id           Print the ID of current invocation\n"
+               "  show [NAME]             Print one or more well-known IDs\n"
                "  help                    Show this help\n"
                "\nOptions:\n"
                "  -h --help               Show this help\n"
@@ -155,6 +238,7 @@ static int id128_main(int argc, char *argv[]) {
                 { "machine-id",     VERB_ANY, 1,        0,  verb_machine_id    },
                 { "boot-id",        VERB_ANY, 1,        0,  verb_boot_id       },
                 { "invocation-id",  VERB_ANY, 1,        0,  verb_invocation_id },
+                { "show",           VERB_ANY, VERB_ANY, 0,  verb_show          },
                 { "help",           VERB_ANY, VERB_ANY, 0,  verb_help          },
                 {}
         };
index 96cf696652f684ac866a06aed4c41046365f1062..5f21033db5a04ab01aef226175528cef7219819b 100644 (file)
@@ -247,6 +247,15 @@ int curl_glue_make(CURL **ret, const char *url, void *userdata) {
         if (curl_easy_setopt(c, CURLOPT_FOLLOWLOCATION, 1L) != CURLE_OK)
                 return -EIO;
 
+        if (curl_easy_setopt(c, CURLOPT_NOSIGNAL, 1L) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_TIME, 60L) != CURLE_OK)
+                return -EIO;
+
+        if (curl_easy_setopt(c, CURLOPT_LOW_SPEED_LIMIT, 30L) != CURLE_OK)
+                return -EIO;
+
         *ret = TAKE_PTR(c);
         return 0;
 }
index a12a6d63b139a74a516f8f7b4aebeabdeea0c227..0606cf5406b38defd706c7759b135881301b0fa0 100644 (file)
@@ -7,7 +7,6 @@
 
 #include "alloc-util.h"
 #include "btrfs-util.h"
-#include "chattr-util.h"
 #include "copy.h"
 #include "fd-util.h"
 #include "fs-util.h"
@@ -174,9 +173,7 @@ static int raw_import_maybe_convert_qcow2(RawImport *i) {
         if (converted_fd < 0)
                 return log_error_errno(errno, "Failed to create %s: %m", t);
 
-        r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
-        if (r < 0)
-                log_warning_errno(r, "Failed to set file attributes on %s: %m", t);
+        (void) import_set_nocow_and_log(converted_fd, t);
 
         log_info("Unpacking QCOW2 file.");
 
@@ -259,10 +256,7 @@ static int raw_import_open_disk(RawImport *i) {
         if (i->output_fd < 0)
                 return log_error_errno(errno, "Failed to open destination %s: %m", i->temp_path);
 
-        r = chattr_fd(i->output_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
-        if (r < 0)
-                log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path);
-
+        (void) import_set_nocow_and_log(i->output_fd, i->temp_path);
         return 0;
 }
 
index a75ab6bc070b9dcb2f450f054d5f210ca7819b9b..93e704ed612de17304155811fa77da99aaf8472d 100644 (file)
@@ -7,7 +7,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "def.h"
 #include "fd-util.h"
 #include "float.h"
index 4f76421bc7954557446c33dca1f125d141252d4c..51c12444e00af53491ff855f1b55aa24447795ea 100644 (file)
@@ -8,7 +8,6 @@
 
 #include "alloc-util.h"
 #include "btrfs-util.h"
-#include "chattr-util.h"
 #include "copy.h"
 #include "curl-util.h"
 #include "fd-util.h"
@@ -242,9 +241,7 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) {
         if (converted_fd < 0)
                 return log_error_errno(errno, "Failed to create %s: %m", t);
 
-        r = chattr_fd(converted_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
-        if (r < 0)
-                log_warning_errno(r, "Failed to set file attributes on %s: %m", t);
+        (void) import_set_nocow_and_log(converted_fd, t);
 
         log_info("Unpacking QCOW2 file.");
 
@@ -354,13 +351,9 @@ static int raw_pull_make_local_copy(RawPull *i) {
         if (dfd < 0)
                 return log_error_errno(errno, "Failed to create writable copy of image: %m");
 
-        /* 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. */
-        r = chattr_fd(dfd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
-        if (r < 0)
-                log_warning_errno(r, "Failed to set file attributes on %s: %m", tp);
+        /* 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);
 
         r = copy_bytes(i->raw_job->disk_fd, dfd, (uint64_t) -1, COPY_REFLINK);
         if (r < 0) {
@@ -600,10 +593,7 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) {
         if (r < 0)
                 return r;
 
-        r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
-        if (r < 0)
-                log_warning_errno(r, "Failed to set file attributes on %s, ignoring: %m", i->temp_path);
-
+        (void) import_set_nocow_and_log(j->disk_fd, i->temp_path);
         return 0;
 }
 
index cc968252887104cf185a969d7b0b5da1325c4815..031e82587d9005a58435f8c88f3053b035bc5f62 100644 (file)
@@ -754,9 +754,13 @@ static int open_journal(sd_journal **j) {
                 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
         else if (arg_file)
                 r = sd_journal_open_files(j, (const char**) arg_file, 0);
-        else if (arg_machine)
+        else if (arg_machine) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+                /* FIXME: replace with D-Bus call OpenMachineRootDirectory() so that things also work with raw disk images */
                 r = sd_journal_open_container(j, arg_machine, 0);
-        else
+#pragma GCC diagnostic pop
+        } else
                 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
         if (r < 0)
                 log_error_errno(r, "Failed to open %s: %m",
index 80a69da4d8cc2691f04d59096526b5cbf2145698..1454df602b8594ad8466d8f2121c59c74328d3b9 100644 (file)
@@ -69,6 +69,7 @@ struct sd_journal {
 
         char *path;
         char *prefix;
+        char *namespace;
 
         OrderedHashmap *files;
         IteratedCache *files_cache;
index 17565abe2128b53d9a9e8578289fc88ea54c8c9c..4e2a8f6d1184ebac1499ec129412c8aed8fd9521 100644 (file)
 #include "varlink.h"
 
 #define DEFAULT_FSS_INTERVAL_USEC (15*USEC_PER_MINUTE)
-
 #define PROCESS_INOTIFY_INTERVAL 1024   /* Every 1,024 messages processed */
 
-#if HAVE_PCRE2
-DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free);
-DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free);
-
-static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) {
-        int errorcode, r;
-        PCRE2_SIZE erroroffset;
-        pcre2_code *p;
-
-        p = pcre2_compile((PCRE2_SPTR8) pattern,
-                          PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL);
-        if (!p) {
-                unsigned char buf[LINE_MAX];
-
-                r = pcre2_get_error_message(errorcode, buf, sizeof buf);
-
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
-                                       "Bad pattern \"%s\": %s", pattern,
-                                       r < 0 ? "unknown error" : (char *)buf);
-        }
-
-        *out = p;
-        return 0;
-}
-
-#endif
-
 enum {
         /* Special values for arg_lines */
         ARG_LINES_DEFAULT = -2,
@@ -143,13 +115,14 @@ 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 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 char **arg_output_fields = NULL;
-
 #if HAVE_PCRE2
 static const char *arg_pattern = NULL;
 static pcre2_code *arg_compiled_pattern = NULL;
@@ -184,6 +157,33 @@ typedef struct BootId {
         LIST_FIELDS(struct BootId, boot_list);
 } BootId;
 
+#if HAVE_PCRE2
+DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_match_data*, pcre2_match_data_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(pcre2_code*, pcre2_code_free);
+
+static int pattern_compile(const char *pattern, unsigned flags, pcre2_code **out) {
+        int errorcode, r;
+        PCRE2_SIZE erroroffset;
+        pcre2_code *p;
+
+        p = pcre2_compile((PCRE2_SPTR8) pattern,
+                          PCRE2_ZERO_TERMINATED, flags, &errorcode, &erroroffset, NULL);
+        if (!p) {
+                unsigned char buf[LINE_MAX];
+
+                r = pcre2_get_error_message(errorcode, buf, sizeof buf);
+
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Bad pattern \"%s\": %s", pattern,
+                                       r < 0 ? "unknown error" : (char *)buf);
+        }
+
+        *out = p;
+        return 0;
+}
+
+#endif
+
 static int add_matches_for_device(sd_journal *j, const char *devpath) {
         _cleanup_(sd_device_unrefp) sd_device *device = NULL;
         sd_device *d = NULL;
@@ -313,9 +313,9 @@ static int help(void) {
         if (r < 0)
                 return log_oom();
 
-        printf("%s [OPTIONS...] [MATCHES...]\n\n"
-               "%sQuery the journal.%s\n\n"
-               "Options:\n"
+        printf("%1$s [OPTIONS...] [MATCHES...]\n\n"
+               "%5$sQuery the journal.%6$s\n\n"
+               "%3$sOptions:%4$s\n"
                "     --system                Show the system journal\n"
                "     --user                  Show the user journal for the current user\n"
                "  -M --machine=CONTAINER     Operate on local container\n"
@@ -356,10 +356,11 @@ static int help(void) {
                "  -D --directory=PATH        Show journal files from directory\n"
                "     --file=PATH             Show journal file\n"
                "     --root=ROOT             Operate on files below a root directory\n"
+               "     --namespace=NAMESPACE   Show journal data from specified namespace\n"
                "     --interval=TIME         Time interval for changing the FSS sealing key\n"
                "     --verify-key=KEY        Specify FSS verification key\n"
                "     --force                 Override of the FSS key pair with --setup-keys\n"
-               "\nCommands:\n"
+               "\n%3$sCommands:%4$s\n"
                "  -h --help                  Show this help text\n"
                "     --version               Show package version\n"
                "  -N --fields                List all field names currently used\n"
@@ -379,10 +380,11 @@ static int help(void) {
                "     --dump-catalog          Show entries in the message catalog\n"
                "     --update-catalog        Update the message catalog database\n"
                "     --setup-keys            Generate a new FSS key pair\n"
-               "\nSee the %s for details.\n"
+               "\nSee the %2$s for details.\n"
                , program_invocation_short_name
-               , ansi_highlight(), ansi_normal()
                , link
+               , ansi_underline(), ansi_normal()
+               , ansi_highlight(), ansi_normal()
         );
 
         return 0;
@@ -428,6 +430,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VACUUM_TIME,
                 ARG_NO_HOSTNAME,
                 ARG_OUTPUT_FIELDS,
+                ARG_NAMESPACE,
         };
 
         static const struct option options[] = {
@@ -492,6 +495,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "vacuum-time",          required_argument, NULL, ARG_VACUUM_TIME          },
                 { "no-hostname",          no_argument,       NULL, ARG_NO_HOSTNAME          },
                 { "output-fields",        required_argument, NULL, ARG_OUTPUT_FIELDS        },
+                { "namespace",            required_argument, NULL, ARG_NAMESPACE            },
                 {}
         };
 
@@ -533,10 +537,8 @@ static int parse_argv(int argc, char *argv[]) {
                         }
 
                         arg_output = output_mode_from_string(optarg);
-                        if (arg_output < 0) {
-                                log_error("Unknown output format '%s'.", optarg);
-                                return -EINVAL;
-                        }
+                        if (arg_output < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown output format '%s'.", optarg);
 
                         if (IN_SET(arg_output, OUTPUT_EXPORT, OUTPUT_JSON, OUTPUT_JSON_PRETTY, OUTPUT_JSON_SSE, OUTPUT_JSON_SEQ, OUTPUT_CAT))
                                 arg_quiet = true;
@@ -561,10 +563,8 @@ static int parse_argv(int argc, char *argv[]) {
                                         arg_lines = ARG_LINES_ALL;
                                 else {
                                         r = safe_atoi(optarg, &arg_lines);
-                                        if (r < 0 || arg_lines < 0) {
-                                                log_error("Failed to parse lines '%s'", optarg);
-                                                return -EINVAL;
-                                        }
+                                        if (r < 0 || arg_lines < 0)
+                                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse lines '%s'", optarg);
                                 }
                         } else {
                                 arg_lines = 10;
@@ -656,6 +656,23 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_machine = optarg;
                         break;
 
+                case ARG_NAMESPACE:
+                        if (streq(optarg, "*")) {
+                                arg_namespace_flags = SD_JOURNAL_ALL_NAMESPACES;
+                                arg_namespace = NULL;
+                        } else if (startswith(optarg, "+")) {
+                                arg_namespace_flags = SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE;
+                                arg_namespace = optarg + 1;
+                        } else if (isempty(optarg)) {
+                                arg_namespace_flags = 0;
+                                arg_namespace = NULL;
+                        } else {
+                                arg_namespace_flags = 0;
+                                arg_namespace = optarg;
+                        }
+
+                        break;
+
                 case 'D':
                         arg_directory = optarg;
                         break;
@@ -710,30 +727,24 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case ARG_VACUUM_SIZE:
                         r = parse_size(optarg, 1024, &arg_vacuum_size);
-                        if (r < 0) {
-                                log_error("Failed to parse vacuum size: %s", optarg);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse vacuum size: %s", optarg);
 
                         arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM;
                         break;
 
                 case ARG_VACUUM_FILES:
                         r = safe_atou64(optarg, &arg_vacuum_n_files);
-                        if (r < 0) {
-                                log_error("Failed to parse vacuum files: %s", optarg);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse vacuum files: %s", optarg);
 
                         arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM;
                         break;
 
                 case ARG_VACUUM_TIME:
                         r = parse_sec(optarg, &arg_vacuum_time);
-                        if (r < 0) {
-                                log_error("Failed to parse vacuum time: %s", optarg);
-                                return r;
-                        }
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse vacuum time: %s", optarg);
 
                         arg_action = arg_action == ACTION_ROTATE ? ACTION_ROTATE_AND_VACUUM : ACTION_VACUUM;
                         break;
@@ -748,7 +759,6 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_VERIFY_KEY:
-                        arg_action = ACTION_VERIFY;
                         r = free_and_strdup(&arg_verify_key, optarg);
                         if (r < 0)
                                 return r;
@@ -756,23 +766,23 @@ static int parse_argv(int argc, char *argv[]) {
                          * in ps or htop output. */
                         memset(optarg, 'x', strlen(optarg));
 
+                        arg_action = ACTION_VERIFY;
                         arg_merge = false;
                         break;
 
                 case ARG_INTERVAL:
                         r = parse_sec(optarg, &arg_interval);
-                        if (r < 0 || arg_interval <= 0) {
-                                log_error("Failed to parse sealing key change interval: %s", optarg);
-                                return -EINVAL;
-                        }
+                        if (r < 0 || arg_interval <= 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Failed to parse sealing key change interval: %s", optarg);
                         break;
 #else
                 case ARG_SETUP_KEYS:
                 case ARG_VERIFY_KEY:
                 case ARG_INTERVAL:
                 case ARG_FORCE:
-                        log_error("Compiled without forward-secure sealing support.");
-                        return -EOPNOTSUPP;
+                        return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+                                               "Compiled without forward-secure sealing support.");
 #endif
 
                 case 'p': {
@@ -780,7 +790,7 @@ static int parse_argv(int argc, char *argv[]) {
 
                         dots = strstr(optarg, "..");
                         if (dots) {
-                                char *a;
+                                _cleanup_free_ char *a = NULL;
                                 int from, to, i;
 
                                 /* a range */
@@ -790,12 +800,10 @@ static int parse_argv(int argc, char *argv[]) {
 
                                 from = log_level_from_string(a);
                                 to = log_level_from_string(dots + 2);
-                                free(a);
 
-                                if (from < 0 || to < 0) {
-                                        log_error("Failed to parse log level range %s", optarg);
-                                        return -EINVAL;
-                                }
+                                if (from < 0 || to < 0)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                               "Failed to parse log level range %s", optarg);
 
                                 arg_priorities = 0;
 
@@ -811,10 +819,9 @@ static int parse_argv(int argc, char *argv[]) {
                                 int p, i;
 
                                 p = log_level_from_string(optarg);
-                                if (p < 0) {
-                                        log_error("Unknown log level %s", optarg);
-                                        return -EINVAL;
-                                }
+                                if (p < 0)
+                                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                               "Unknown log level %s", optarg);
 
                                 arg_priorities = 0;
 
@@ -848,19 +855,17 @@ static int parse_argv(int argc, char *argv[]) {
 
                 case 'S':
                         r = parse_timestamp(optarg, &arg_since);
-                        if (r < 0) {
-                                log_error("Failed to parse timestamp: %s", optarg);
-                                return -EINVAL;
-                        }
+                        if (r < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Failed to parse timestamp: %s", optarg);
                         arg_since_set = true;
                         break;
 
                 case 'U':
                         r = parse_timestamp(optarg, &arg_until);
-                        if (r < 0) {
-                                log_error("Failed to parse timestamp: %s", optarg);
-                                return -EINVAL;
-                        }
+                        if (r < 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Failed to parse timestamp: %s", optarg);
                         arg_until_set = true;
                         break;
 
@@ -1417,7 +1422,6 @@ static int add_boot(sd_journal *j) {
          * 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);
 
         boot_id = arg_boot_id;
@@ -1944,15 +1948,19 @@ static int verify(sd_journal *j) {
 
 static int simple_varlink_call(const char *option, const char *method) {
         _cleanup_(varlink_flush_close_unrefp) Varlink *link = NULL;
-        const char *error;
+        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);
 
-        r = varlink_connect_address(&link, "/run/systemd/journal/io.systemd.journal");
+        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 /run/systemd/journal/io.systemd.journal: %m");
+                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);
@@ -2122,10 +2130,9 @@ int main(int argc, char *argv[]) {
                 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) {
-                int ifd = STDIN_FILENO;
-                r = sd_journal_open_files_fd(&j, &ifd, 1, 0);
-        } else if (arg_file)
+        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) {
                 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
@@ -2136,8 +2143,7 @@ int main(int argc, char *argv[]) {
                 if (geteuid() != 0) {
                         /* The file descriptor returned by OpenMachineRootDirectory() will be owned by users/groups of
                          * the container, thus we need root privileges to override them. */
-                        log_error("Using the --machine= switch requires root privileges.");
-                        r = -EPERM;
+                        r = log_error_errno(SYNTHETIC_ERRNO(EPERM), "Using the --machine= switch requires root privileges.");
                         goto finish;
                 }
 
@@ -2177,7 +2183,11 @@ int main(int argc, char *argv[]) {
                 if (r < 0)
                         safe_close(fd);
         } else
-                r = sd_journal_open(&j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
+                r = sd_journal_open_namespace(
+                                &j,
+                                arg_namespace,
+                                (arg_merge ? 0 : SD_JOURNAL_LOCAL_ONLY) |
+                                arg_namespace_flags | arg_journal_type);
         if (r < 0) {
                 log_error_errno(r, "Failed to open %s: %m", arg_directory ?: arg_file ? "files" : "journal");
                 goto finish;
index cd4cfbee48c78b1216a7f7f8bf86a2f06b52f359..bac67036b0c94b7177f29e89f41df9dbc8d3c210 100644 (file)
@@ -117,23 +117,24 @@ static int client_context_new(Server *s, pid_t pid, ClientContext **ret) {
         if (r < 0)
                 return r;
 
-        c = new0(ClientContext, 1);
+        c = new(ClientContext, 1);
         if (!c)
                 return -ENOMEM;
 
-        c->pid = pid;
-
-        c->uid = UID_INVALID;
-        c->gid = GID_INVALID;
-        c->auditid = AUDIT_SESSION_INVALID;
-        c->loginuid = UID_INVALID;
-        c->owner_uid = UID_INVALID;
-        c->lru_index = PRIOQ_IDX_NULL;
-        c->timestamp = USEC_INFINITY;
-        c->extra_fields_mtime = NSEC_INFINITY;
-        c->log_level_max = -1;
-        c->log_ratelimit_interval = s->ratelimit_interval;
-        c->log_ratelimit_burst = s->ratelimit_burst;
+        *c = (ClientContext) {
+                .pid = pid,
+                .uid = UID_INVALID,
+                .gid = GID_INVALID,
+                .auditid = AUDIT_SESSION_INVALID,
+                .loginuid = UID_INVALID,
+                .owner_uid = UID_INVALID,
+                .lru_index = PRIOQ_IDX_NULL,
+                .timestamp = USEC_INFINITY,
+                .extra_fields_mtime = NSEC_INFINITY,
+                .log_level_max = -1,
+                .log_ratelimit_interval = s->ratelimit_interval,
+                .log_ratelimit_burst = s->ratelimit_burst,
+        };
 
         r = hashmap_put(s->client_contexts, PID_TO_PTR(pid), c);
         if (r < 0) {
@@ -779,7 +780,9 @@ void client_context_acquire_default(Server *s) {
                         log_warning_errno(r, "Failed to acquire our own context, ignoring: %m");
         }
 
-        if (!s->pid1_context) {
+        if (!s->namespace && !s->pid1_context) {
+                /* Acquire PID1's context, but only if we are in non-namespaced mode, since PID 1 is only
+                 * going to log to the non-namespaced journal instance. */
 
                 r = client_context_acquire(s, 1, NULL, NULL, 0, NULL, &s->pid1_context);
                 if (r < 0)
index 366298758c65f006bb7fce0d9e6911f8c01fe920..ec404145eec906c53616fb4bbbf85f99ee9b6a0f 100644 (file)
 #include "string-util.h"
 
 void server_forward_kmsg(
-        Server *s,
-        int priority,
-        const char *identifier,
-        const char *message,
-        const struct ucred *ucred) {
+                Server *s,
+                int priority,
+                const char *identifier,
+                const char *message,
+                const struct ucred *ucred) {
 
         _cleanup_free_ char *ident_buf = NULL;
         struct iovec iovec[5];
@@ -416,19 +416,23 @@ fail:
 }
 
 int server_open_kernel_seqnum(Server *s) {
-        _cleanup_close_ int fd;
+        _cleanup_close_ int fd = -1;
+        const char *fn;
         uint64_t *p;
         int r;
 
         assert(s);
 
-        /* We store the seqnum we last read in an mmaped file. That
-         * way we can just use it like a variable, but it is
-         * persistent and automatically flushed at reboot. */
+        /* We store the seqnum we last read in an mmaped file. That way we can just use it like a variable,
+         * but it is persistent and automatically flushed at reboot. */
 
-        fd = open("/run/systemd/journal/kernel-seqnum", O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644);
+        if (!s->read_kmsg)
+                return 0;
+
+        fn = strjoina(s->runtime_directory, "/kernel-seqnum");
+        fd = open(fn, O_RDWR|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644);
         if (fd < 0) {
-                log_error_errno(errno, "Failed to open /run/systemd/journal/kernel-seqnum, ignoring: %m");
+                log_error_errno(errno, "Failed to open %s, ignoring: %m", fn);
                 return 0;
         }
 
index 73a96da9f7a99d65b91f39a559dd1a0fe328997d..30d988544f38727bcb6c290faae435d23056cc06 100644 (file)
@@ -450,17 +450,21 @@ void server_process_native_file(
         }
 }
 
-int server_open_native_socket(Server *s) {
-
-        static const union sockaddr_union sa = {
-                .un.sun_family = AF_UNIX,
-                .un.sun_path = "/run/systemd/journal/socket",
-        };
+int server_open_native_socket(Server *s, const char *native_socket) {
         int r;
 
         assert(s);
+        assert(native_socket);
 
         if (s->native_fd < 0) {
+                union sockaddr_union sa = {
+                        .un.sun_family = AF_UNIX,
+                };
+
+                r = sockaddr_un_set_path(&sa.un, native_socket);
+                if (r < 0)
+                        return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", native_socket);
+
                 s->native_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
                 if (s->native_fd < 0)
                         return log_error_errno(errno, "socket() failed: %m");
index 2a33ef74c5a39e309f368fd7801cbce160dcf5e2..8d06636262ef933210b9f1e8298fd908565e74be 100644 (file)
@@ -20,4 +20,4 @@ void server_process_native_file(
                 const char *label,
                 size_t label_len);
 
-int server_open_native_socket(Server *s);
+int server_open_native_socket(Server *s, const char *native_socket);
index af7196c6dee35266588f72834d19a3e78d8bd371..fa21e7da29c8c226fef3beea79d9fdac107e63fc 100644 (file)
@@ -99,7 +99,7 @@ void journal_ratelimit_free(JournalRateLimit *r) {
         free(r);
 }
 
-_pure_ static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
+static bool journal_ratelimit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
         unsigned i;
 
         assert(g);
index f6caf0e212ab02fb7ba714696abb58691998bdca..eb124aa73f4c05b56c31a321cad14d2adcc57561 100644 (file)
@@ -44,6 +44,7 @@
 #include "missing_audit.h"
 #include "mkdir.h"
 #include "parse-util.h"
+#include "path-util.h"
 #include "proc-cmdline.h"
 #include "process-util.h"
 #include "rm-rf.h"
 
 #define DEFERRED_CLOSES_MAX (4096)
 
-static int determine_path_usage(Server *s, const char *path, uint64_t *ret_used, uint64_t *ret_free) {
+#define IDLE_TIMEOUT_USEC (30*USEC_PER_SEC)
+
+static int determine_path_usage(
+                Server *s,
+                const char *path,
+                uint64_t *ret_used,
+                uint64_t *ret_free) {
+
         _cleanup_closedir_ DIR *d = NULL;
         struct dirent *de;
         struct statvfs ss;
 
+        assert(s);
+        assert(path);
         assert(ret_used);
         assert(ret_free);
 
@@ -163,13 +173,19 @@ static void patch_min_use(JournalStorage *storage) {
         storage->metrics.min_use = MAX(storage->metrics.min_use, storage->space.vfs_used);
 }
 
+static JournalStorage* server_current_storage(Server *s) {
+        assert(s);
+
+        return s->system_journal ? &s->system_storage : &s->runtime_storage;
+}
+
 static int determine_space(Server *s, uint64_t *available, uint64_t *limit) {
         JournalStorage *js;
         int r;
 
         assert(s);
 
-        js = s->system_journal ? &s->system_storage : &s->runtime_storage;
+        js = server_current_storage(s);
 
         r = cache_space_refresh(s, js);
         if (r >= 0) {
@@ -189,7 +205,7 @@ void server_space_usage_message(Server *s, JournalStorage *storage) {
         assert(s);
 
         if (!storage)
-                storage = s->system_journal ? &s->system_storage : &s->runtime_storage;
+                storage = server_current_storage(s);
 
         if (cache_space_refresh(s, storage) < 0)
                 return;
@@ -280,8 +296,18 @@ static int open_journal(
         return r;
 }
 
-static bool flushed_flag_is_set(void) {
-        return access("/run/systemd/journal/flushed", F_OK) >= 0;
+static bool flushed_flag_is_set(Server *s) {
+        const char *fn;
+
+        assert(s);
+
+        /* We don't support the "flushing" concept for namespace instances, we assume them to always have
+         * access to /var */
+        if (s->namespace)
+                return true;
+
+        fn = strjoina(s->runtime_directory, "/flushed");
+        return access(fn, F_OK) >= 0;
 }
 
 static int system_journal_open(Server *s, bool flush_requested, bool relinquish_requested) {
@@ -290,17 +316,15 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_
 
         if (!s->system_journal &&
             IN_SET(s->storage, STORAGE_PERSISTENT, STORAGE_AUTO) &&
-            (flush_requested || flushed_flag_is_set()) &&
+            (flush_requested || flushed_flag_is_set(s)) &&
             !relinquish_requested) {
 
-                /* If in auto mode: first try to create the machine
-                 * path, but not the prefix.
+                /* If in auto mode: first try to create the machine path, but not the prefix.
                  *
-                 * If in persistent mode: create /var/log/journal and
-                 * the machine path */
+                 * If in persistent mode: create /var/log/journal and the machine path */
 
                 if (s->storage == STORAGE_PERSISTENT)
-                        (void) mkdir_p("/var/log/journal/", 0755);
+                        (void) mkdir_parents(s->system_storage.path, 0755);
 
                 (void) mkdir(s->system_storage.path, 0755);
 
@@ -317,12 +341,11 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_
                         r = 0;
                 }
 
-                /* If the runtime journal is open, and we're post-flush, we're
-                 * recovering from a failed system journal rotate (ENOSPC)
-                 * for which the runtime journal was reopened.
+                /* If the runtime journal is open, and we're post-flush, we're recovering from a failed
+                 * system journal rotate (ENOSPC) for which the runtime journal was reopened.
                  *
-                 * Perform an implicit flush to var, leaving the runtime
-                 * journal closed, now that the system journal is back.
+                 * Perform an implicit flush to var, leaving the runtime journal closed, now that the system
+                 * journal is back.
                  */
                 if (!flush_requested)
                         (void) server_flush_to_var(s, true);
@@ -349,12 +372,10 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_
 
                 } else {
 
-                        /* OK, we really need the runtime journal, so create
-                         * it if necessary. */
+                        /* OK, we really need the runtime journal, so create it if necessary. */
 
-                        (void) mkdir("/run/log", 0755);
-                        (void) mkdir("/run/log/journal", 0755);
-                        (void) mkdir_parents(fn, 0750);
+                        (void) mkdir_parents(s->runtime_storage.path, 0755);
+                        (void) mkdir(s->runtime_storage.path, 0750);
 
                         r = open_journal(s, true, fn, O_RDWR|O_CREAT, false, &s->runtime_storage.metrics, &s->runtime_journal);
                         if (r < 0)
@@ -373,27 +394,23 @@ static int system_journal_open(Server *s, bool flush_requested, bool relinquish_
 
 static JournalFile* find_journal(Server *s, uid_t uid) {
         _cleanup_free_ char *p = NULL;
-        int r;
         JournalFile *f;
-        sd_id128_t machine;
+        int r;
 
         assert(s);
 
-        /* A rotate that fails to create the new journal (ENOSPC) leaves the
-         * rotated journal as NULL.  Unless we revisit opening, even after
-         * space is made available we'll continue to return NULL indefinitely.
+        /* A rotate that fails to create the new journal (ENOSPC) leaves the rotated journal as NULL.  Unless
+         * we revisit opening, even after space is made available we'll continue to return NULL indefinitely.
          *
-         * system_journal_open() is a noop if the journals are already open, so
-         * we can just call it here to recover from failed rotates (or anything
-         * else that's left the journals as NULL).
+         * system_journal_open() is a noop if the journals are already open, so we can just call it here to
+         * recover from failed rotates (or anything else that's left the journals as NULL).
          *
          * Fixes https://github.com/systemd/systemd/issues/3968 */
         (void) system_journal_open(s, false, false);
 
-        /* We split up user logs only on /var, not on /run. If the
-         * runtime file is open, we write to it exclusively, in order
-         * to guarantee proper order as soon as we flush /run to
-         * /var and close the runtime file. */
+        /* We split up user logs only on /var, not on /run. If the runtime file is open, we write to it
+         * exclusively, in order to guarantee proper order as soon as we flush /run to /var and close the
+         * runtime file. */
 
         if (s->runtime_journal)
                 return s->runtime_journal;
@@ -405,22 +422,14 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
         if (f)
                 return f;
 
-        r = sd_id128_get_machine(&machine);
-        if (r < 0) {
-                log_debug_errno(r, "Failed to determine machine ID, using system log: %m");
-                return s->system_journal;
-        }
-
-        if (asprintf(&p, "/var/log/journal/" SD_ID128_FORMAT_STR "/user-"UID_FMT".journal",
-                     SD_ID128_FORMAT_VAL(machine), uid) < 0) {
+        if (asprintf(&p, "%s/user-" UID_FMT ".journal", s->system_storage.path, uid) < 0) {
                 log_oom();
                 return s->system_journal;
         }
 
+        /* Too many open? Then let's close one (or more) */
         while (ordered_hashmap_size(s->user_journals) >= USER_JOURNALS_MAX) {
-                /* Too many open? Then let's close one */
-                f = ordered_hashmap_steal_first(s->user_journals);
-                assert(f);
+                assert_se(f = ordered_hashmap_steal_first(s->user_journals));
                 (void) journal_file_close(f);
         }
 
@@ -428,14 +437,13 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
         if (r < 0)
                 return s->system_journal;
 
-        server_add_acls(f, uid);
-
         r = ordered_hashmap_put(s->user_journals, UID_TO_PTR(uid), f);
         if (r < 0) {
                 (void) journal_file_close(f);
                 return s->system_journal;
         }
 
+        server_add_acls(f, uid);
         return f;
 }
 
@@ -461,7 +469,6 @@ static int do_rotate(
         }
 
         server_add_acls(*f, uid);
-
         return r;
 }
 
@@ -498,36 +505,110 @@ static void server_vacuum_deferred_closes(Server *s) {
         }
 }
 
-static int open_user_journal_directory(Server *s, DIR **ret_dir, char **ret_path) {
-        _cleanup_closedir_ DIR *dir = NULL;
-        _cleanup_free_ char *path = NULL;
-        sd_id128_t machine;
+static int vacuum_offline_user_journals(Server *s) {
+        _cleanup_closedir_ DIR *d = NULL;
         int r;
 
         assert(s);
 
-        r = sd_id128_get_machine(&machine);
-        if (r < 0)
-                return log_error_errno(r, "Failed to determine machine ID, ignoring: %m");
+        d = opendir(s->system_storage.path);
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
 
-        if (asprintf(&path, "/var/log/journal/" SD_ID128_FORMAT_STR "/", SD_ID128_FORMAT_VAL(machine)) < 0)
-                return log_oom();
+                return log_error_errno(errno, "Failed to open %s: %m", s->system_storage.path);
+        }
 
-        dir = opendir(path);
-        if (!dir)
-                return log_error_errno(errno, "Failed to open user journal directory '%s': %m", path);
+        for (;;) {
+                _cleanup_free_ char *u = NULL, *full = NULL;
+                _cleanup_close_ int fd = -1;
+                const char *a, *b;
+                struct dirent *de;
+                JournalFile *f;
+                uid_t uid;
+
+                errno = 0;
+                de = readdir_no_dot(d);
+                if (!de) {
+                        if (errno != 0)
+                                log_warning_errno(errno, "Failed to enumerate %s, ignoring: %m", s->system_storage.path);
+
+                        break;
+                }
+
+                a = startswith(de->d_name, "user-");
+                if (!a)
+                        continue;
+                b = endswith(de->d_name, ".journal");
+                if (!b)
+                        continue;
+
+                u = strndup(a, b-a);
+                if (!u)
+                        return log_oom();
+
+                r = parse_uid(u, &uid);
+                if (r < 0) {
+                        log_debug_errno(r, "Failed to parse UID from file name '%s', ignoring: %m", de->d_name);
+                        continue;
+                }
+
+                /* Already rotated in the above loop? i.e. is it an open user journal? */
+                if (ordered_hashmap_contains(s->user_journals, UID_TO_PTR(uid)))
+                        continue;
 
-        if (ret_dir)
-                *ret_dir = TAKE_PTR(dir);
-        if (ret_path)
-                *ret_path = TAKE_PTR(path);
+                full = path_join(s->system_storage.path, de->d_name);
+                if (!full)
+                        return log_oom();
+
+                fd = openat(dirfd(d), de->d_name, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
+                if (fd < 0) {
+                        log_full_errno(IN_SET(errno, ELOOP, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
+                                       "Failed to open journal file '%s' for rotation: %m", full);
+                        continue;
+                }
+
+                /* Make some room in the set of deferred close()s */
+                server_vacuum_deferred_closes(s);
+
+                /* Open the file briefly, so that we can archive it */
+                r = journal_file_open(fd,
+                                      full,
+                                      O_RDWR,
+                                      0640,
+                                      s->compress.enabled,
+                                      s->compress.threshold_bytes,
+                                      s->seal,
+                                      &s->system_storage.metrics,
+                                      s->mmap,
+                                      s->deferred_closes,
+                                      NULL,
+                                      &f);
+                if (r < 0) {
+                        log_warning_errno(r, "Failed to read journal file %s for rotation, trying to move it out of the way: %m", full);
+
+                        r = journal_file_dispose(dirfd(d), de->d_name);
+                        if (r < 0)
+                                log_warning_errno(r, "Failed to move %s out of the way, ignoring: %m", full);
+                        else
+                                log_debug("Successfully moved %s out of the way.", full);
+
+                        continue;
+                }
+
+                TAKE_FD(fd); /* Donated to journal_file_open() */
+
+                r = journal_file_archive(f);
+                if (r < 0)
+                        log_debug_errno(r, "Failed to archive journal file '%s', ignoring: %m", full);
+
+                f = journal_initiate_close(f, s->deferred_closes);
+        }
 
         return 0;
 }
 
 void server_rotate(Server *s) {
-        _cleanup_free_ char *path = NULL;
-        _cleanup_closedir_ DIR *d = NULL;
         JournalFile *f;
         Iterator i;
         void *k;
@@ -549,92 +630,10 @@ void server_rotate(Server *s) {
                         ordered_hashmap_remove(s->user_journals, k);
         }
 
-        /* Finally, also rotate all user journals we currently do not have open. (But do so only if we actually have
-         * access to /var, i.e. are not in the log-to-runtime-journal mode). */
-        if (!s->runtime_journal &&
-            open_user_journal_directory(s, &d, &path) >= 0) {
-
-                struct dirent *de;
-
-                FOREACH_DIRENT(de, d, log_warning_errno(errno, "Failed to enumerate %s, ignoring: %m", path)) {
-                        _cleanup_free_ char *u = NULL, *full = NULL;
-                        _cleanup_close_ int fd = -1;
-                        const char *a, *b;
-                        uid_t uid;
-
-                        a = startswith(de->d_name, "user-");
-                        if (!a)
-                                continue;
-                        b = endswith(de->d_name, ".journal");
-                        if (!b)
-                                continue;
-
-                        u = strndup(a, b-a);
-                        if (!u) {
-                                log_oom();
-                                break;
-                        }
-
-                        r = parse_uid(u, &uid);
-                        if (r < 0) {
-                                log_debug_errno(r, "Failed to parse UID from file name '%s', ignoring: %m", de->d_name);
-                                continue;
-                        }
-
-                        /* Already rotated in the above loop? i.e. is it an open user journal? */
-                        if (ordered_hashmap_contains(s->user_journals, UID_TO_PTR(uid)))
-                                continue;
-
-                        full = strjoin(path, de->d_name);
-                        if (!full) {
-                                log_oom();
-                                break;
-                        }
-
-                        fd = openat(dirfd(d), de->d_name, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|O_NONBLOCK);
-                        if (fd < 0) {
-                                log_full_errno(IN_SET(errno, ELOOP, ENOENT) ? LOG_DEBUG : LOG_WARNING, errno,
-                                               "Failed to open journal file '%s' for rotation: %m", full);
-                                continue;
-                        }
-
-                        /* Make some room in the set of deferred close()s */
-                        server_vacuum_deferred_closes(s);
-
-                        /* Open the file briefly, so that we can archive it */
-                        r = journal_file_open(fd,
-                                              full,
-                                              O_RDWR,
-                                              0640,
-                                              s->compress.enabled,
-                                              s->compress.threshold_bytes,
-                                              s->seal,
-                                              &s->system_storage.metrics,
-                                              s->mmap,
-                                              s->deferred_closes,
-                                              NULL,
-                                              &f);
-                        if (r < 0) {
-                                log_warning_errno(r, "Failed to read journal file %s for rotation, trying to move it out of the way: %m", full);
-
-                                r = journal_file_dispose(dirfd(d), de->d_name);
-                                if (r < 0)
-                                        log_warning_errno(r, "Failed to move %s out of the way, ignoring: %m", full);
-                                else
-                                        log_debug("Successfully moved %s out of the way.", full);
-
-                                continue;
-                        }
-
-                        TAKE_FD(fd); /* Donated to journal_file_open() */
-
-                        r = journal_file_archive(f);
-                        if (r < 0)
-                                log_debug_errno(r, "Failed to archive journal file '%s', ignoring: %m", full);
-
-                        f = journal_initiate_close(f, s->deferred_closes);
-                }
-        }
+        /* Finally, also rotate all user journals we currently do not have open. (But do so only if we
+         * actually have access to /var, i.e. are not in the log-to-runtime-journal mode). */
+        if (!s->runtime_journal)
+                (void) vacuum_offline_user_journals(s);
 
         server_process_deferred_closes(s);
 }
@@ -741,8 +740,7 @@ static void server_cache_hostname(Server *s) {
         if (!x)
                 return;
 
-        free(s->hostname_field);
-        s->hostname_field = x;
+        free_and_replace(s->hostname_field, x);
 }
 
 static bool shall_try_append_again(JournalFile *f, int r) {
@@ -1003,6 +1001,9 @@ static void dispatch_message_real(
         if (!isempty(s->hostname_field))
                 iovec[n++] = IOVEC_MAKE_STRING(s->hostname_field);
 
+        if (!isempty(s->namespace_field))
+                iovec[n++] = IOVEC_MAKE_STRING(s->namespace_field);
+
         assert(n <= m);
 
         if (s->split_mode == SPLIT_UID && c && uid_is_valid(c->uid))
@@ -1115,10 +1116,11 @@ void server_dispatch_message(
 }
 
 int server_flush_to_var(Server *s, bool require_flag_file) {
-        sd_journal *j = NULL;
         char ts[FORMAT_TIMESPAN_MAX];
-        usec_t start;
+        sd_journal *j = NULL;
+        const char *fn;
         unsigned n = 0;
+        usec_t start;
         int r, k;
 
         assert(s);
@@ -1126,10 +1128,13 @@ int server_flush_to_var(Server *s, bool require_flag_file) {
         if (!IN_SET(s->storage, STORAGE_AUTO, STORAGE_PERSISTENT))
                 return 0;
 
-        if (!s->runtime_journal)
+        if (s->namespace) /* Flushing concept does not exist for namespace instances */
                 return 0;
 
-        if (require_flag_file && !flushed_flag_is_set())
+        if (!s->runtime_journal) /* Nothing to flush? */
+                return 0;
+
+        if (require_flag_file && !flushed_flag_is_set(s))
                 return 0;
 
         (void) system_journal_open(s, true, false);
@@ -1137,7 +1142,7 @@ int server_flush_to_var(Server *s, bool require_flag_file) {
         if (!s->system_journal)
                 return 0;
 
-        log_debug("Flushing to /var...");
+        log_debug("Flushing to %s...", s->system_storage.path);
 
         start = now(CLOCK_MONOTONIC);
 
@@ -1197,33 +1202,40 @@ finish:
         s->runtime_journal = journal_file_close(s->runtime_journal);
 
         if (r >= 0)
-                (void) rm_rf("/run/log/journal", REMOVE_ROOT);
+                (void) rm_rf(s->runtime_storage.path, REMOVE_ROOT);
 
         sd_journal_close(j);
 
         server_driver_message(s, 0, NULL,
-                              LOG_MESSAGE("Time spent on flushing to /var is %s for %u entries.",
+                              LOG_MESSAGE("Time spent on flushing to %s is %s for %u entries.",
+                                          s->system_storage.path,
                                           format_timespan(ts, sizeof(ts), now(CLOCK_MONOTONIC) - start, 0),
                                           n),
                               NULL);
 
-        k = touch("/run/systemd/journal/flushed");
+        fn = strjoina(s->runtime_directory, "/flushed");
+        k = touch(fn);
         if (k < 0)
-                log_warning_errno(k, "Failed to touch /run/systemd/journal/flushed, ignoring: %m");
+                log_warning_errno(k, "Failed to touch %s, ignoring: %m", fn);
 
+        server_refresh_idle_timer(s);
         return r;
 }
 
 static int server_relinquish_var(Server *s) {
+        const char *fn;
         assert(s);
 
         if (s->storage == STORAGE_NONE)
                 return 0;
 
+        if (s->namespace) /* Concept does not exist for namespaced instances */
+                return -EOPNOTSUPP;
+
         if (s->runtime_journal && !s->system_journal)
                 return 0;
 
-        log_debug("Relinquishing /var...");
+        log_debug("Relinquishing %s...", s->system_storage.path);
 
         (void) system_journal_open(s, false, true);
 
@@ -1231,13 +1243,20 @@ static int server_relinquish_var(Server *s) {
         ordered_hashmap_clear_with_destructor(s->user_journals, journal_file_close);
         set_clear_with_destructor(s->deferred_closes, journal_file_close);
 
-        if (unlink("/run/systemd/journal/flushed") < 0 && errno != ENOENT)
-                log_warning_errno(errno, "Failed to unlink /run/systemd/journal/flushed, ignoring: %m");
+        fn = strjoina(s->runtime_directory, "/flushed");
+        if (unlink(fn) < 0 && errno != ENOENT)
+                log_warning_errno(errno, "Failed to unlink %s, ignoring: %m", fn);
 
+        server_refresh_idle_timer(s);
         return 0;
 }
 
-int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata) {
+int server_process_datagram(
+                sd_event_source *es,
+                int fd,
+                uint32_t revents,
+                void *userdata) {
+
         Server *s = userdata;
         struct ucred *ucred = NULL;
         struct timeval *tv = NULL;
@@ -1352,6 +1371,8 @@ int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void
         }
 
         close_many(fds, n_fds);
+
+        server_refresh_idle_timer(s);
         return 0;
 }
 
@@ -1363,6 +1384,8 @@ static void server_full_flush(Server *s) {
         server_vacuum(s, false);
 
         server_space_usage_message(s, NULL);
+
+        server_refresh_idle_timer(s);
 }
 
 static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
@@ -1370,6 +1393,11 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *
 
         assert(s);
 
+        if (s->namespace) {
+                log_error("Received SIGUSR1 signal from PID " PID_FMT ", but flushing runtime journals not supported for namespaced instances.", si->ssi_pid);
+                return 0;
+        }
+
         log_info("Received SIGUSR1 signal from PID " PID_FMT ", as request to flush runtime journal.", si->ssi_pid);
         server_full_flush(s);
 
@@ -1377,6 +1405,7 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo *
 }
 
 static void server_full_rotate(Server *s) {
+        const char *fn;
         int r;
 
         assert(s);
@@ -1390,9 +1419,10 @@ static void server_full_rotate(Server *s) {
                 patch_min_use(&s->runtime_storage);
 
         /* Let clients know when the most recent rotation happened. */
-        r = write_timestamp_file_atomic("/run/systemd/journal/rotated", now(CLOCK_MONOTONIC));
+        fn = strjoina(s->runtime_directory, "/rotated");
+        r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC));
         if (r < 0)
-                log_warning_errno(r, "Failed to write /run/systemd/journal/rotated, ignoring: %m");
+                log_warning_errno(r, "Failed to write %s, ignoring: %m", fn);
 }
 
 static int dispatch_sigusr2(sd_event_source *es, const struct signalfd_siginfo *si, void *userdata) {
@@ -1418,6 +1448,7 @@ static int dispatch_sigterm(sd_event_source *es, const struct signalfd_siginfo *
 }
 
 static void server_full_sync(Server *s) {
+        const char *fn;
         int r;
 
         assert(s);
@@ -1425,9 +1456,10 @@ static void server_full_sync(Server *s) {
         server_sync(s);
 
         /* Let clients know when the most recent sync happened. */
-        r = write_timestamp_file_atomic("/run/systemd/journal/synced", now(CLOCK_MONOTONIC));
+        fn = strjoina(s->runtime_directory, "/synced");
+        r = write_timestamp_file_atomic(fn, now(CLOCK_MONOTONIC));
         if (r < 0)
-                log_warning_errno(r, "Failed to write /run/systemd/journal/synced, ignoring: %m");
+                log_warning_errno(r, "Failed to write %s, ignoring: %m", fn);
 
         return;
 }
@@ -1592,8 +1624,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
 }
 
 static int server_parse_config_file(Server *s) {
+        int r;
+
         assert(s);
 
+        if (s->namespace) {
+                const char *namespaced;
+
+                /* If we are running in namespace mode, load the namespace specific configuration file, and nothing else */
+                namespaced = strjoina(PKGSYSCONFDIR "/journald@", s->namespace, ".conf");
+
+                r = config_parse(
+                                NULL,
+                                namespaced, NULL,
+                                "Journal\0",
+                                config_item_perf_lookup, journald_gperf_lookup,
+                                CONFIG_PARSE_WARN, s);
+                if (r < 0)
+                        return r;
+
+                return 0;
+        }
+
         return config_parse_many_nulstr(PKGSYSCONFDIR "/journald.conf",
                                         CONF_PATHS_NULSTR("systemd/journald.conf.d"),
                                         "Journal\0",
@@ -1936,6 +1988,8 @@ static int vl_method_flush_to_var(Varlink *link, JsonVariant *parameters, Varlin
 
         if (json_variant_elements(parameters) > 0)
                 return varlink_error_invalid_parameter(link, parameters);
+        if (s->namespace)
+                return varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL);
 
         log_info("Received client request to flush runtime journal.");
         server_full_flush(s);
@@ -1951,14 +2005,38 @@ static int vl_method_relinquish_var(Varlink *link, JsonVariant *parameters, Varl
 
         if (json_variant_elements(parameters) > 0)
                 return varlink_error_invalid_parameter(link, parameters);
+        if (s->namespace)
+                return varlink_error(link, "io.systemd.Journal.NotSupportedByNamespaces", NULL);
 
-        log_info("Received client request to relinquish /var access.");
+        log_info("Received client request to relinquish %s access.", s->system_storage.path);
         server_relinquish_var(s);
 
         return varlink_reply(link, NULL);
 }
 
-static int server_open_varlink(Server *s) {
+static int vl_connect(VarlinkServer *server, Varlink *link, void *userdata) {
+        Server *s = userdata;
+
+        assert(server);
+        assert(link);
+        assert(s);
+
+        (void) server_start_or_stop_idle_timer(s); /* maybe we are no longer idle */
+
+        return 0;
+}
+
+static void vl_disconnect(VarlinkServer *server, Varlink *link, void *userdata) {
+        Server *s = userdata;
+
+        assert(server);
+        assert(link);
+        assert(s);
+
+        (void) server_start_or_stop_idle_timer(s); /* maybe we are idle now */
+}
+
+static int server_open_varlink(Server *s, const char *socket, int fd) {
         int r;
 
         assert(s);
@@ -1978,7 +2056,18 @@ static int server_open_varlink(Server *s) {
         if (r < 0)
                 return r;
 
-        r = varlink_server_listen_address(s->varlink_server, "/run/systemd/journal/io.systemd.journal", 0600);
+        r = varlink_server_bind_connect(s->varlink_server, vl_connect);
+        if (r < 0)
+                return r;
+
+        r = varlink_server_bind_disconnect(s->varlink_server, vl_disconnect);
+        if (r < 0)
+                return r;
+
+        if (fd < 0)
+                r = varlink_server_listen_address(s->varlink_server, socket, 0600);
+        else
+                r = varlink_server_listen_fd(s->varlink_server, fd);
         if (r < 0)
                 return r;
 
@@ -1989,9 +2078,117 @@ static int server_open_varlink(Server *s) {
         return 0;
 }
 
-int server_init(Server *s) {
+static bool server_is_idle(Server *s) {
+        assert(s);
+
+        /* The server for the main namespace is never idle */
+        if (!s->namespace)
+                return false;
+
+        /* If a retention maximum is set larger than the idle time we need to be running to enforce it, hence
+         * turn off the idle logic. */
+        if (s->max_retention_usec > IDLE_TIMEOUT_USEC)
+                return false;
+
+        /* We aren't idle if we have a varlink client */
+        if (varlink_server_current_connections(s->varlink_server) > 0)
+                return false;
+
+        /* If we have stdout streams we aren't idle */
+        if (s->n_stdout_streams > 0)
+                return false;
+
+        return true;
+}
+
+static int server_idle_handler(sd_event_source *source, uint64_t usec, void *userdata) {
+        Server *s = userdata;
+
+        assert(source);
+        assert(s);
+
+        log_debug("Server is idle, exiting.");
+        sd_event_exit(s->event, 0);
+        return 0;
+}
+
+int server_start_or_stop_idle_timer(Server *s) {
+        _cleanup_(sd_event_source_unrefp) sd_event_source *source = NULL;
+        usec_t when;
+        int r;
+
+        assert(s);
+
+        if (!server_is_idle(s)) {
+                s->idle_event_source = sd_event_source_disable_unref(s->idle_event_source);
+                return 0;
+        }
+
+        if (s->idle_event_source)
+                return 1;
+
+        r = sd_event_now(s->event, CLOCK_MONOTONIC, &when);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine current time: %m");
+
+        r = sd_event_add_time(s->event, &source, CLOCK_MONOTONIC, usec_add(when, IDLE_TIMEOUT_USEC), 0, server_idle_handler, s);
+        if (r < 0)
+                return log_error_errno(r, "Failed to allocate idle timer: %m");
+
+        r = sd_event_source_set_priority(source, SD_EVENT_PRIORITY_IDLE);
+        if (r < 0)
+                return log_error_errno(r, "Failed to set idle timer priority: %m");
+
+        (void) sd_event_source_set_description(source, "idle-timer");
+
+        s->idle_event_source = TAKE_PTR(source);
+        return 1;
+}
+
+int server_refresh_idle_timer(Server *s) {
+        usec_t when;
+        int r;
+
+        assert(s);
+
+        if (!s->idle_event_source)
+                return 0;
+
+        r = sd_event_now(s->event, CLOCK_MONOTONIC, &when);
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine current time: %m");
+
+        r = sd_event_source_set_time(s->idle_event_source, usec_add(when, IDLE_TIMEOUT_USEC));
+        if (r < 0)
+                return log_error_errno(r, "Failed to refresh idle timer: %m");
+
+        return 1;
+}
+
+static int set_namespace(Server *s, const char *namespace) {
+        assert(s);
+
+        if (!namespace)
+                return 0;
+
+        if (!log_namespace_name_valid(namespace))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified namespace name not valid, refusing: %s", namespace);
+
+        s->namespace = strdup(namespace);
+        if (!s->namespace)
+                return log_oom();
+
+        s->namespace_field = strjoin("_NAMESPACE=", namespace);
+        if (!s->namespace_field)
+                return log_oom();
+
+        return 1;
+}
+
+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;
+        int n, r, fd, varlink_fd = -1;
         bool no_sockets;
 
         assert(s);
@@ -2008,7 +2205,6 @@ int server_init(Server *s) {
                 .compress.enabled = true,
                 .compress.threshold_bytes = (uint64_t) -1,
                 .seal = true,
-                .read_kmsg = true,
 
                 .watchdog_usec = USEC_INFINITY,
 
@@ -2034,22 +2230,43 @@ int server_init(Server *s) {
                 .system_storage.name = "System Journal",
         };
 
+        r = set_namespace(s, namespace);
+        if (r < 0)
+                return r;
+
+        /* By default, only read from /dev/kmsg if are the main namespace */
+        s->read_kmsg = !s->namespace;
+        s->storage = s->namespace ? STORAGE_PERSISTENT : STORAGE_AUTO;
+
         journal_reset_metrics(&s->system_storage.metrics);
         journal_reset_metrics(&s->runtime_storage.metrics);
 
         server_parse_config_file(s);
 
-        r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX);
-        if (r < 0)
-                log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+        if (!s->namespace) {
+                /* Parse kernel command line, but only if we are not a namespace instance */
+                r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX);
+                if (r < 0)
+                        log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
+        }
 
-        if (!!s->ratelimit_interval ^ !!s->ratelimit_burst) {
+        if (!!s->ratelimit_interval != !!s->ratelimit_burst) { /* One set to 0 and the other not? */
                 log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0",
                           s->ratelimit_interval, s->ratelimit_burst);
                 s->ratelimit_interval = s->ratelimit_burst = 0;
         }
 
-        (void) mkdir_p("/run/systemd/journal", 0755);
+        e = getenv("RUNTIME_DIRECTORY");
+        if (e)
+                s->runtime_directory = strdup(e);
+        else if (s->namespace)
+                s->runtime_directory = strjoin("/run/systemd/journal.", s->namespace);
+        else
+                s->runtime_directory = strdup("/run/systemd/journal");
+        if (!s->runtime_directory)
+                return log_oom();
+
+        (void) mkdir_p(s->runtime_directory, 0755);
 
         s->user_journals = ordered_hashmap_new(NULL);
         if (!s->user_journals)
@@ -2071,9 +2288,14 @@ int server_init(Server *s) {
         if (n < 0)
                 return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
 
+        native_socket = strjoina(s->runtime_directory, "/socket");
+        stdout_socket = strjoina(s->runtime_directory, "/stdout");
+        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++) {
 
-                if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/socket", 0) > 0) {
+                if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, native_socket, 0) > 0) {
 
                         if (s->native_fd >= 0)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -2081,7 +2303,7 @@ int server_init(Server *s) {
 
                         s->native_fd = fd;
 
-                } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, "/run/systemd/journal/stdout", 0) > 0) {
+                } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, stdout_socket, 0) > 0) {
 
                         if (s->stdout_fd >= 0)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -2089,8 +2311,7 @@ int server_init(Server *s) {
 
                         s->stdout_fd = fd;
 
-                } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/dev/log", 0) > 0 ||
-                           sd_is_socket_unix(fd, SOCK_DGRAM, -1, "/run/systemd/journal/dev-log", 0) > 0) {
+                } else if (sd_is_socket_unix(fd, SOCK_DGRAM, -1, syslog_socket, 0) > 0) {
 
                         if (s->syslog_fd >= 0)
                                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
@@ -2098,6 +2319,13 @@ int server_init(Server *s) {
 
                         s->syslog_fd = fd;
 
+                } else if (sd_is_socket_unix(fd, SOCK_STREAM, 1, varlink_socket, 0) > 0) {
+
+                        if (varlink_fd >= 0)
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Too many varlink sockets passed.");
+
+                        varlink_fd = fd;
                 } else if (sd_is_socket(fd, AF_NETLINK, SOCK_RAW, -1) > 0) {
 
                         if (s->audit_fd >= 0)
@@ -2128,22 +2356,22 @@ int server_init(Server *s) {
                 fds = fdset_free(fds);
         }
 
-        no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0;
+        no_sockets = s->native_fd < 0 && s->stdout_fd < 0 && s->syslog_fd < 0 && s->audit_fd < 0 && varlink_fd < 0;
 
         /* always open stdout, syslog, native, and kmsg sockets */
 
         /* systemd-journald.socket: /run/systemd/journal/stdout */
-        r = server_open_stdout_socket(s);
+        r = server_open_stdout_socket(s, stdout_socket);
         if (r < 0)
                 return r;
 
         /* systemd-journald-dev-log.socket: /run/systemd/journal/dev-log */
-        r = server_open_syslog_socket(s);
+        r = server_open_syslog_socket(s, syslog_socket);
         if (r < 0)
                 return r;
 
         /* systemd-journald.socket: /run/systemd/journal/socket */
-        r = server_open_native_socket(s);
+        r = server_open_native_socket(s, native_socket);
         if (r < 0)
                 return r;
 
@@ -2159,7 +2387,7 @@ int server_init(Server *s) {
                         return r;
         }
 
-        r = server_open_varlink(s);
+        r = server_open_varlink(s, varlink_socket, varlink_fd);
         if (r < 0)
                 return r;
 
@@ -2177,26 +2405,43 @@ int server_init(Server *s) {
 
         s->ratelimit = journal_ratelimit_new();
         if (!s->ratelimit)
-                return -ENOMEM;
+                return log_oom();
 
         r = cg_get_root_path(&s->cgroup_root);
         if (r < 0)
-                return r;
+                return log_error_errno(r, "Failed to acquire cgroup root path: %m");
 
         server_cache_hostname(s);
         server_cache_boot_id(s);
         server_cache_machine_id(s);
 
-        s->runtime_storage.path = path_join("/run/log/journal", SERVER_MACHINE_ID(s));
-        s->system_storage.path  = path_join("/var/log/journal", SERVER_MACHINE_ID(s));
-        if (!s->runtime_storage.path || !s->system_storage.path)
-                return -ENOMEM;
+        if (s->namespace)
+                s->runtime_storage.path = strjoin("/run/log/journal/", SERVER_MACHINE_ID(s), ".", s->namespace);
+        else
+                s->runtime_storage.path = strjoin("/run/log/journal/", SERVER_MACHINE_ID(s));
+        if (!s->runtime_storage.path)
+                return log_oom();
+
+        e = getenv("LOGS_DIRECTORY");
+        if (e)
+                s->system_storage.path = strdup(e);
+        else if (s->namespace)
+                s->system_storage.path = strjoin("/var/log/journal/", SERVER_MACHINE_ID(s), ".", s->namespace);
+        else
+                s->system_storage.path = strjoin("/var/log/journal/", SERVER_MACHINE_ID(s));
+        if (!s->system_storage.path)
+                return log_oom();
 
         (void) server_connect_notify(s);
 
         (void) client_context_acquire_default(s);
 
-        return system_journal_open(s, false, false);
+        r = system_journal_open(s, false, false);
+        if (r < 0)
+                return r;
+
+        server_start_or_stop_idle_timer(s);
+        return 0;
 }
 
 void server_maybe_append_tags(Server *s) {
@@ -2218,6 +2463,9 @@ void server_maybe_append_tags(Server *s) {
 void server_done(Server *s) {
         assert(s);
 
+        free(s->namespace);
+        free(s->namespace_field);
+
         set_free_with_destructor(s->deferred_closes, journal_file_close);
 
         while (s->stdout_streams)
@@ -2246,6 +2494,7 @@ void server_done(Server *s) {
         sd_event_source_unref(s->hostname_event_source);
         sd_event_source_unref(s->notify_event_source);
         sd_event_source_unref(s->watchdog_event_source);
+        sd_event_source_unref(s->idle_event_source);
         sd_event_unref(s->event);
 
         safe_close(s->syslog_fd);
@@ -2268,6 +2517,7 @@ void server_done(Server *s) {
         free(s->hostname_field);
         free(s->runtime_storage.path);
         free(s->system_storage.path);
+        free(s->runtime_directory);
 
         mmap_cache_unref(s->mmap);
 }
index a609e9db06d1fcad8c8c15b428546bf6f985f122..f3405e967cd0fbd357d7dc003722d461fd6ad6f7 100644 (file)
@@ -60,6 +60,8 @@ typedef struct JournalStorage {
 } JournalStorage;
 
 struct Server {
+        char *namespace;
+
         int syslog_fd;
         int native_fd;
         int stdout_fd;
@@ -84,6 +86,7 @@ struct Server {
         sd_event_source *hostname_event_source;
         sd_event_source *notify_event_source;
         sd_event_source *watchdog_event_source;
+        sd_event_source *idle_event_source;
 
         JournalFile *runtime_journal;
         JournalFile *system_journal;
@@ -147,6 +150,8 @@ struct Server {
         char machine_id_field[sizeof("_MACHINE_ID=") + 32];
         char boot_id_field[sizeof("_BOOT_ID=") + 32];
         char *hostname_field;
+        char *namespace_field;
+        char *runtime_directory;
 
         /* Cached cgroup root, so that we don't have to query that all the time */
         char *cgroup_root;
@@ -172,7 +177,7 @@ struct Server {
 #define SERVER_MACHINE_ID(s) ((s)->machine_id_field + STRLEN("_MACHINE_ID="))
 
 /* Extra fields for any log messages */
-#define N_IOVEC_META_FIELDS 22
+#define N_IOVEC_META_FIELDS 23
 
 /* Extra fields for log messages that contain OBJECT_PID= (i.e. log about another process) */
 #define N_IOVEC_OBJECT_FIELDS 18
@@ -204,7 +209,7 @@ 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_init(Server *s);
+int server_init(Server *s, const char *namespace);
 void server_done(Server *s);
 void server_sync(Server *s);
 int server_vacuum(Server *s, bool verbose);
@@ -214,3 +219,6 @@ 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);
index 22a70ce5a69ff217098fa3b7bf20cd89730080c0..d5147cb61a4acf69ce1f065173b8f275a0539a3a 100644 (file)
@@ -17,6 +17,7 @@
 #include "escape.h"
 #include "fd-util.h"
 #include "fileio.h"
+#include "fs-util.h"
 #include "io-util.h"
 #include "journald-console.h"
 #include "journald-context.h"
@@ -109,6 +110,8 @@ void stdout_stream_free(StdoutStream *s) {
 
                 if (s->in_notify_queue)
                         LIST_REMOVE(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
+
+                (void) server_start_or_stop_idle_timer(s->server); /* Maybe we are idle now? */
         }
 
         if (s->event_source) {
@@ -139,7 +142,7 @@ void stdout_stream_destroy(StdoutStream *s) {
 }
 
 static int stdout_stream_save(StdoutStream *s) {
-        _cleanup_free_ char *temp_path = NULL;
+        _cleanup_(unlink_and_freep) char *temp_path = NULL;
         _cleanup_fclose_ FILE *f = NULL;
         int r;
 
@@ -156,11 +159,12 @@ static int stdout_stream_save(StdoutStream *s) {
                         return log_warning_errno(errno, "Failed to stat connected stream: %m");
 
                 /* We use device and inode numbers as identifier for the stream */
-                if (asprintf(&s->state_file, "/run/systemd/journal/streams/%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0)
+                r = asprintf(&s->state_file, "%s/streams/%lu:%lu", s->server->runtime_directory, (unsigned long) st.st_dev, (unsigned long) st.st_ino);
+                if (r < 0)
                         return log_oom();
         }
 
-        (void) mkdir_p("/run/systemd/journal/streams", 0755);
+        (void) mkdir_parents(s->state_file, 0755);
 
         r = fopen_temporary(s->state_file, &f, &temp_path);
         if (r < 0)
@@ -214,6 +218,8 @@ static int stdout_stream_save(StdoutStream *s) {
                 goto fail;
         }
 
+        temp_path = mfree(temp_path);
+
         if (!s->fdstore && !s->in_notify_queue) {
                 LIST_PREPEND(stdout_stream_notify_queue, s->server->stdout_streams_notify_queue, s);
                 s->in_notify_queue = true;
@@ -229,10 +235,6 @@ static int stdout_stream_save(StdoutStream *s) {
 
 fail:
         (void) unlink(s->state_file);
-
-        if (temp_path)
-                (void) unlink(temp_path);
-
         return log_error_errno(r, "Failed to save stream data %s: %m", s->state_file);
 }
 
@@ -590,12 +592,14 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
         if (r < 0)
                 return log_error_errno(r, "Failed to generate stream ID: %m");
 
-        stream = new0(StdoutStream, 1);
+        stream = new(StdoutStream, 1);
         if (!stream)
                 return log_oom();
 
-        stream->fd = -1;
-        stream->priority = LOG_INFO;
+        *stream = (StdoutStream) {
+                .fd = -1,
+                .priority = LOG_INFO,
+        };
 
         xsprintf(stream->id_field, "_STREAM_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(id));
 
@@ -629,11 +633,12 @@ int stdout_stream_install(Server *s, int fd, StdoutStream **ret) {
         LIST_PREPEND(stdout_stream, s->stdout_streams, stream);
         s->n_stdout_streams++;
 
+        (void) server_start_or_stop_idle_timer(s); /* Maybe no longer idle? */
+
         if (ret)
                 *ret = stream;
 
-        stream = NULL;
-
+        TAKE_PTR(stream);
         return 0;
 }
 
@@ -676,7 +681,7 @@ static int stdout_stream_new(sd_event_source *es, int listen_fd, uint32_t revent
         if (r < 0)
                 return r;
 
-        fd = -1;
+        TAKE_FD(fd);
         return 0;
 }
 
@@ -694,7 +699,7 @@ static int stdout_stream_load(StdoutStream *stream, const char *fname) {
         assert(fname);
 
         if (!stream->state_file) {
-                stream->state_file = path_join("/run/systemd/journal/streams", fname);
+                stream->state_file = path_join(stream->server->runtime_directory, "streams", fname);
                 if (!stream->state_file)
                         return log_oom();
         }
@@ -783,14 +788,16 @@ static int stdout_stream_restore(Server *s, const char *fname, int fd) {
 int server_restore_streams(Server *s, FDSet *fds) {
         _cleanup_closedir_ DIR *d = NULL;
         struct dirent *de;
+        const char *path;
         int r;
 
-        d = opendir("/run/systemd/journal/streams");
+        path = strjoina(s->runtime_directory, "/streams");
+        d = opendir(path);
         if (!d) {
                 if (errno == ENOENT)
                         return 0;
 
-                return log_warning_errno(errno, "Failed to enumerate /run/systemd/journal/streams: %m");
+                return log_warning_errno(errno, "Failed to enumerate %s: %m", path);
         }
 
         FOREACH_DIRENT(de, d, goto fail) {
@@ -818,8 +825,7 @@ int server_restore_streams(Server *s, FDSet *fds) {
                         /* No file descriptor? Then let's delete the state file */
                         log_debug("Cannot restore stream file %s", de->d_name);
                         if (unlinkat(dirfd(d), de->d_name, 0) < 0)
-                                log_warning_errno(errno, "Failed to remove /run/systemd/journal/streams/%s: %m",
-                                                  de->d_name);
+                                log_warning_errno(errno, "Failed to remove %s%s: %m", path, de->d_name);
                         continue;
                 }
 
@@ -836,16 +842,21 @@ fail:
         return log_error_errno(errno, "Failed to read streams directory: %m");
 }
 
-int server_open_stdout_socket(Server *s) {
-        static const union sockaddr_union sa = {
-                .un.sun_family = AF_UNIX,
-                .un.sun_path = "/run/systemd/journal/stdout",
-        };
+int server_open_stdout_socket(Server *s, const char *stdout_socket) {
         int r;
 
         assert(s);
+        assert(stdout_socket);
 
         if (s->stdout_fd < 0) {
+                union sockaddr_union sa = {
+                        .un.sun_family = AF_UNIX,
+                };
+
+                r = sockaddr_un_set_path(&sa.un, stdout_socket);
+                if (r < 0)
+                        return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", stdout_socket);
+
                 s->stdout_fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
                 if (s->stdout_fd < 0)
                         return log_error_errno(errno, "socket() failed: %m");
index 487376e76361221cdef167f549e7831799ee9810..7ab0016618489cb53f19bad94dc9014d39b9414e 100644 (file)
@@ -6,7 +6,7 @@ typedef struct StdoutStream StdoutStream;
 #include "fdset.h"
 #include "journald-server.h"
 
-int server_open_stdout_socket(Server *s);
+int server_open_stdout_socket(Server *s, const char *stdout_socket);
 int server_restore_streams(Server *s, FDSet *fds);
 
 void stdout_stream_free(StdoutStream *s);
index a60a259bc49e587d5b1f2c3aba3708dea906a307..59ce983bb937d500f714e4e57c03ae3e4ff6ac8f 100644 (file)
 /* Warn once every 30s if we missed syslog message */
 #define WARN_FORWARD_SYSLOG_MISSED_USEC (30 * USEC_PER_SEC)
 
-static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned n_iovec, const struct ucred *ucred, const struct timeval *tv) {
+static void forward_syslog_iovec(
+                Server *s,
+                const struct iovec *iovec,
+                unsigned n_iovec,
+                const struct ucred *ucred,
+                const struct timeval *tv) {
 
-        static const union sockaddr_union sa = {
+        union sockaddr_union sa = {
                 .un.sun_family = AF_UNIX,
-                .un.sun_path = "/run/systemd/journal/syslog",
         };
         struct msghdr msghdr = {
                 .msg_iov = (struct iovec *) iovec,
@@ -42,11 +46,20 @@ static void forward_syslog_iovec(Server *s, const struct iovec *iovec, unsigned
                 struct cmsghdr cmsghdr;
                 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
         } control;
+        const char *j;
+        int r;
 
         assert(s);
         assert(iovec);
         assert(n_iovec > 0);
 
+        j = strjoina(s->runtime_directory, "/syslog");
+        r = sockaddr_un_set_path(&sa.un, j);
+        if (r < 0) {
+                log_debug_errno(r, "Forwarding socket path %s too long for AF_UNIX, not forwarding: %m", j);
+                return;
+        }
+
         if (ucred) {
                 zero(control);
                 msghdr.msg_control = &control;
@@ -441,17 +454,21 @@ void server_process_syslog_message(
         server_dispatch_message(s, iovec, n, m, context, tv, priority, 0);
 }
 
-int server_open_syslog_socket(Server *s) {
-
-        static const union sockaddr_union sa = {
-                .un.sun_family = AF_UNIX,
-                .un.sun_path = "/run/systemd/journal/dev-log",
-        };
+int server_open_syslog_socket(Server *s, const char *syslog_socket) {
         int r;
 
         assert(s);
+        assert(syslog_socket);
 
         if (s->syslog_fd < 0) {
+                union sockaddr_union sa = {
+                        .un.sun_family = AF_UNIX,
+                };
+
+                r = sockaddr_un_set_path(&sa.un, syslog_socket);
+                if (r < 0)
+                        return log_error_errno(r, "Unable to use namespace path %s for AF_UNIX socket: %m", syslog_socket);
+
                 s->syslog_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
                 if (s->syslog_fd < 0)
                         return log_error_errno(errno, "socket() failed: %m");
index 7ca58972649b69ecbd6374386c7808d7f8d27faf..5ad601001ca3e8f27ea7dd3946932708b2c26de3 100644 (file)
@@ -10,6 +10,6 @@ size_t syslog_parse_identifier(const char **buf, char **identifier, char **pid);
 void server_forward_syslog(Server *s, int priority, const char *identifier, const char *message, const struct ucred *ucred, const struct timeval *tv);
 
 void server_process_syslog_message(Server *s, const char *buf, size_t buf_len, const struct ucred *ucred, const struct timeval *tv, const char *label, size_t label_len);
-int server_open_syslog_socket(Server *s);
+int server_open_syslog_socket(Server *s, const char *syslog_socket);
 
 void server_maybe_warn_forward_syslog_missed(Server *s);
index 5e7b1dcb4ac3476067e2abc599a5f361970ae94c..9a7cb3e1fc457aa89da9e41ac8466b54fe51b484 100644 (file)
 #include "sigbus.h"
 
 int main(int argc, char *argv[]) {
+        const char *namespace;
         Server server;
         int r;
 
-        if (argc > 1) {
-                log_error("This program does not take arguments.");
+        if (argc > 2) {
+                log_error("This program takes one or no arguments.");
                 return EXIT_FAILURE;
         }
 
+        namespace = argc > 1 ? empty_to_null(argv[1]) : NULL;
+
         log_set_prohibit_ipc(true);
         log_set_target(LOG_TARGET_AUTO);
         log_set_facility(LOG_SYSLOG);
@@ -32,7 +35,7 @@ int main(int argc, char *argv[]) {
 
         sigbus_install();
 
-        r = server_init(&server);
+        r = server_init(&server, namespace);
         if (r < 0)
                 goto finish;
 
@@ -40,7 +43,11 @@ int main(int argc, char *argv[]) {
         server_flush_to_var(&server, true);
         server_flush_dev_kmsg(&server);
 
-        log_debug("systemd-journald running as pid "PID_FMT, getpid_cached());
+        if (server.namespace)
+                log_debug("systemd-journald running as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace);
+        else
+                log_debug("systemd-journald running as PID "PID_FMT" for the system.", getpid_cached());
+
         server_driver_message(&server, 0,
                               "MESSAGE_ID=" SD_MESSAGE_JOURNAL_START_STR,
                               LOG_MESSAGE("Journal started"),
@@ -55,8 +62,10 @@ int main(int argc, char *argv[]) {
                 usec_t t = USEC_INFINITY, n;
 
                 r = sd_event_get_state(server.event);
-                if (r < 0)
+                if (r < 0) {
+                        log_error_errno(r, "Failed to get event loop state: %m");
                         goto finish;
+                }
                 if (r == SD_EVENT_FINISHED)
                         break;
 
@@ -99,7 +108,11 @@ int main(int argc, char *argv[]) {
                 server_maybe_warn_forward_syslog_missed(&server);
         }
 
-        log_debug("systemd-journald stopped as pid "PID_FMT, getpid_cached());
+        if (server.namespace)
+                log_debug("systemd-journald stopped as PID "PID_FMT" for namespace '%s'.", getpid_cached(), server.namespace);
+        else
+                log_debug("systemd-journald stopped as PID "PID_FMT" for the system.", getpid_cached());
+
         server_driver_message(&server, 0,
                               "MESSAGE_ID=" SD_MESSAGE_JOURNAL_STOP_STR,
                               LOG_MESSAGE("Journal stopped"),
index 358f2fd738ef9aa2be9f9fbc5ed002f95e46940f..bced8af3e3abb20208f604319b0082a095a6813b 100644 (file)
@@ -39,6 +39,7 @@
 #include "stdio-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "syslog-util.h"
 
 #define JOURNAL_FILES_MAX 7168
 
@@ -161,7 +162,7 @@ static int match_is_valid(const void *data, size_t size) {
         if (size < 2)
                 return false;
 
-        if (startswith(data, "__"))
+        if (((char*) data)[0] == '_' && ((char*) data)[1] == '_')
                 return false;
 
         b = data;
@@ -204,16 +205,17 @@ static bool same_field(const void *_a, size_t s, const void *_b, size_t t) {
 static Match *match_new(Match *p, MatchType t) {
         Match *m;
 
-        m = new0(Match, 1);
+        m = new(Match, 1);
         if (!m)
                 return NULL;
 
-        m->type = t;
+        *m = (Match) {
+                .type = t,
+                .parent = p,
+        };
 
-        if (p) {
-                m->parent = p;
+        if (p)
                 LIST_PREPEND(matches, p->matches, m);
-        }
 
         return m;
 }
@@ -1434,22 +1436,63 @@ static void remove_file_real(sd_journal *j, JournalFile *f) {
 
 static int dirname_is_machine_id(const char *fn) {
         sd_id128_t id, machine;
+        const char *e;
         int r;
 
+        /* Returns true if the specified directory name matches the local machine ID */
+
         r = sd_id128_get_machine(&machine);
         if (r < 0)
                 return r;
 
-        r = sd_id128_from_string(fn, &id);
+        e = strchr(fn, '.');
+        if (e) {
+                const char *k;
+
+                /* Looks like it has a namespace suffix. Verify that. */
+                if (!log_namespace_name_valid(e + 1))
+                        return false;
+
+                k = strndupa(fn, e - fn);
+                r = sd_id128_from_string(k, &id);
+        } else
+                r = sd_id128_from_string(fn, &id);
         if (r < 0)
                 return r;
 
         return sd_id128_equal(id, machine);
 }
 
+static int dirname_has_namespace(const char *fn, const char *namespace) {
+        const char *e;
+
+        /* Returns true if the specified directory name matches the specified namespace */
+
+        e = strchr(fn, '.');
+        if (e) {
+                const char *k;
+
+                if (!namespace)
+                        return false;
+
+                if (!streq(e + 1, namespace))
+                        return false;
+
+                k = strndupa(fn, e - fn);
+                return id128_is_valid(k);
+        }
+
+        if (namespace)
+                return false;
+
+        return id128_is_valid(fn);
+}
+
 static bool dirent_is_journal_file(const struct dirent *de) {
         assert(de);
 
+        /* Returns true if the specified directory entry looks like a journal file we might be interested in */
+
         if (!IN_SET(de->d_type, DT_REG, DT_LNK, DT_UNKNOWN))
                 return false;
 
@@ -1457,13 +1500,26 @@ static bool dirent_is_journal_file(const struct dirent *de) {
                 endswith(de->d_name, ".journal~");
 }
 
-static bool dirent_is_id128_subdir(const struct dirent *de) {
+static bool dirent_is_journal_subdir(const struct dirent *de) {
+        const char *e, *n;
         assert(de);
 
+        /* returns true if the specified directory entry looks like a directory that might contain journal
+         * files we might be interested in, i.e. is either a 128bit ID or a 128bit ID suffixed by a
+         * namespace. */
+
         if (!IN_SET(de->d_type, DT_DIR, DT_LNK, DT_UNKNOWN))
                 return false;
 
-        return id128_is_valid(de->d_name);
+        e = strchr(de->d_name, '.');
+        if (!e)
+                return id128_is_valid(de->d_name); /* No namespace */
+
+        n = strndupa(de->d_name, e - de->d_name);
+        if (!id128_is_valid(n))
+                return false;
+
+        return log_namespace_name_valid(e + 1);
 }
 
 static int directory_open(sd_journal *j, const char *path, DIR **ret) {
@@ -1500,7 +1556,7 @@ static void directory_enumerate(sd_journal *j, Directory *m, DIR *d) {
                 if (dirent_is_journal_file(de))
                         (void) add_file_by_name(j, m->path, de->d_name);
 
-                if (m->is_root && dirent_is_id128_subdir(de))
+                if (m->is_root && dirent_is_journal_subdir(de))
                         (void) add_directory(j, m->path, de->d_name);
         }
 
@@ -1540,7 +1596,11 @@ static void directory_watch(sd_journal *j, Directory *m, int fd, uint32_t mask)
         }
 }
 
-static int add_directory(sd_journal *j, const char *prefix, const char *dirname) {
+static int add_directory(
+                sd_journal *j,
+                const char *prefix,
+                const char *dirname) {
+
         _cleanup_free_ char *path = NULL;
         _cleanup_closedir_ DIR *d = NULL;
         Directory *m;
@@ -1565,6 +1625,11 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname)
             !((dirname && dirname_is_machine_id(dirname) > 0) || path_has_prefix(j, path, "/run")))
                 return 0;
 
+        if (!(FLAGS_SET(j->flags, SD_JOURNAL_ALL_NAMESPACES) ||
+              dirname_has_namespace(dirname, j->namespace) > 0 ||
+              (FLAGS_SET(j->flags, SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE) && dirname_has_namespace(dirname, NULL) > 0)))
+                return 0;
+
         r = directory_open(j, path, &d);
         if (r < 0) {
                 log_debug_errno(r, "Failed to open directory '%s': %m", path);
@@ -1573,14 +1638,16 @@ static int add_directory(sd_journal *j, const char *prefix, const char *dirname)
 
         m = hashmap_get(j->directories_by_path, path);
         if (!m) {
-                m = new0(Directory, 1);
+                m = new(Directory, 1);
                 if (!m) {
                         r = -ENOMEM;
                         goto fail;
                 }
 
-                m->is_root = false;
-                m->path = path;
+                *m = (Directory) {
+                        .is_root = false,
+                        .path = path,
+                };
 
                 if (hashmap_put(j->directories_by_path, m->path, m) < 0) {
                         free(m);
@@ -1803,7 +1870,7 @@ static int allocate_inotify(sd_journal *j) {
         return hashmap_ensure_allocated(&j->directories_by_wd, NULL);
 }
 
-static sd_journal *journal_new(int flags, const char *path) {
+static sd_journal *journal_new(int flags, const char *path, const char *namespace) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
 
         j = new0(sd_journal, 1);
@@ -1829,6 +1896,12 @@ static sd_journal *journal_new(int flags, const char *path) {
                         j->path = t;
         }
 
+        if (namespace) {
+                j->namespace = strdup(namespace);
+                if (!j->namespace)
+                        return NULL;
+        }
+
         j->files = ordered_hashmap_new(&path_hash_ops);
         if (!j->files)
                 return NULL;
@@ -1845,16 +1918,19 @@ static sd_journal *journal_new(int flags, const char *path) {
 #define OPEN_ALLOWED_FLAGS                              \
         (SD_JOURNAL_LOCAL_ONLY |                        \
          SD_JOURNAL_RUNTIME_ONLY |                      \
-         SD_JOURNAL_SYSTEM | SD_JOURNAL_CURRENT_USER)
+         SD_JOURNAL_SYSTEM |                            \
+         SD_JOURNAL_CURRENT_USER |                      \
+         SD_JOURNAL_ALL_NAMESPACES |                    \
+         SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE)
 
-_public_ int sd_journal_open(sd_journal **ret, int flags) {
+_public_ int sd_journal_open_namespace(sd_journal **ret, const char *namespace, int flags) {
         _cleanup_(sd_journal_closep) sd_journal *j = NULL;
         int r;
 
         assert_return(ret, -EINVAL);
         assert_return((flags & ~OPEN_ALLOWED_FLAGS) == 0, -EINVAL);
 
-        j = journal_new(flags, NULL);
+        j = journal_new(flags, NULL, namespace);
         if (!j)
                 return -ENOMEM;
 
@@ -1866,6 +1942,10 @@ _public_ int sd_journal_open(sd_journal **ret, int flags) {
         return 0;
 }
 
+_public_ int sd_journal_open(sd_journal **ret, int flags) {
+        return sd_journal_open_namespace(ret, NULL, flags);
+}
+
 #define OPEN_CONTAINER_ALLOWED_FLAGS                    \
         (SD_JOURNAL_LOCAL_ONLY | SD_JOURNAL_SYSTEM)
 
@@ -1875,7 +1955,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in
         char *p;
         int r;
 
-        /* This is pretty much deprecated, people should use machined's OpenMachineRootDirectory() call instead in
+        /* This is deprecated, people should use machined's OpenMachineRootDirectory() call instead in
          * combination with sd_journal_open_directory_fd(). */
 
         assert_return(machine, -EINVAL);
@@ -1897,7 +1977,7 @@ _public_ int sd_journal_open_container(sd_journal **ret, const char *machine, in
         if (!streq_ptr(class, "container"))
                 return -EIO;
 
-        j = journal_new(flags, root);
+        j = journal_new(flags, root, NULL);
         if (!j)
                 return -ENOMEM;
 
@@ -1921,7 +2001,7 @@ _public_ int sd_journal_open_directory(sd_journal **ret, const char *path, int f
         assert_return(path, -EINVAL);
         assert_return((flags & ~OPEN_DIRECTORY_ALLOWED_FLAGS) == 0, -EINVAL);
 
-        j = journal_new(flags, path);
+        j = journal_new(flags, path, NULL);
         if (!j)
                 return -ENOMEM;
 
@@ -1944,7 +2024,7 @@ _public_ int sd_journal_open_files(sd_journal **ret, const char **paths, int fla
         assert_return(ret, -EINVAL);
         assert_return(flags == 0, -EINVAL);
 
-        j = journal_new(flags, NULL);
+        j = journal_new(flags, NULL, NULL);
         if (!j)
                 return -ENOMEM;
 
@@ -1979,7 +2059,7 @@ _public_ int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags) {
         if (!S_ISDIR(st.st_mode))
                 return -EBADFD;
 
-        j = journal_new(flags, NULL);
+        j = journal_new(flags, NULL, NULL);
         if (!j)
                 return -ENOMEM;
 
@@ -2007,7 +2087,7 @@ _public_ int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fd
         assert_return(n_fds > 0, -EBADF);
         assert_return(flags == 0, -EINVAL);
 
-        j = journal_new(flags, NULL);
+        j = journal_new(flags, NULL, NULL);
         if (!j)
                 return -ENOMEM;
 
@@ -2079,6 +2159,7 @@ _public_ void sd_journal_close(sd_journal *j) {
 
         free(j->path);
         free(j->prefix);
+        free(j->namespace);
         free(j->unique_field);
         free(j->fields_buffer);
         free(j);
@@ -2480,7 +2561,7 @@ static void process_q_overflow(sd_journal *j) {
         log_debug("Reiteration complete.");
 }
 
-static void process_inotify_event(sd_journal *j, struct inotify_event *e) {
+static void process_inotify_event(sd_journal *j, const struct inotify_event *e) {
         Directory *d;
 
         assert(j);
index 5417ba8c5febc40265c24a97f4347026f1899dcb..eac2e725cce7b4a8e66ff595f2a2301ef9237361 100644 (file)
@@ -675,8 +675,7 @@ static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void
 }
 
 static usec_t client_timeout_compute_random(usec_t val) {
-        return val - val / 10 +
-                (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC;
+        return val - (random_u32() % USEC_PER_SEC) * val / 10 / USEC_PER_SEC;
 }
 
 static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) {
@@ -686,7 +685,6 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         usec_t max_retransmit_duration = 0;
         uint8_t max_retransmit_count = 0;
         char time_string[FORMAT_TIMESPAN_MAX];
-        uint32_t expire = 0;
 
         assert(s);
         assert(client);
@@ -735,8 +733,9 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 max_retransmit_time = DHCP6_REB_MAX_RT;
 
                 if (event_source_is_enabled(client->timeout_resend_expire) <= 0) {
-                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia,
-                                                         &expire);
+                        uint32_t expire = 0;
+
+                        r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, &expire);
                         if (r < 0) {
                                 client_stop(client, r);
                                 return 0;
@@ -751,7 +750,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                 return 0;
         }
 
-        if (max_retransmit_count &&
+        if (max_retransmit_count > 0 &&
             client->retransmit_count >= max_retransmit_count) {
                 client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX);
                 return 0;
@@ -765,7 +764,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         if (r >= 0)
                 client->retransmit_count++;
 
-        if (!client->retransmit_time) {
+        if (client->retransmit_time == 0) {
                 client->retransmit_time =
                         client_timeout_compute_random(init_retransmit_time);
 
@@ -773,7 +772,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
                         client->retransmit_time += init_retransmit_time / 10;
 
         } else {
-                if (max_retransmit_time &&
+                if (max_retransmit_time > 0 &&
                     client->retransmit_time > max_retransmit_time / 2)
                         client->retransmit_time = client_timeout_compute_random(max_retransmit_time);
                 else
@@ -791,7 +790,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userda
         if (r < 0)
                 goto error;
 
-        if (max_retransmit_duration && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
+        if (max_retransmit_duration > 0 && event_source_is_enabled(client->timeout_resend_expire) <= 0) {
 
                 log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs",
                                  max_retransmit_duration / USEC_PER_SEC);
index a3dd9f68b7d38c0a1b6da69c25b8261f88a0d6d9..ccc23e1257b1997e526b69f09b82b790cd1da9c2 100644 (file)
@@ -685,6 +685,7 @@ global:
 
 LIBSYSTEMD_245 {
 global:
+        sd_bus_message_dump;
         sd_bus_message_sensitive;
         sd_event_add_child_pidfd;
         sd_event_source_get_child_pidfd;
@@ -693,4 +694,5 @@ global:
         sd_event_source_get_child_process_own;
         sd_event_source_set_child_process_own;
         sd_event_source_send_child_signal;
+        sd_journal_open_namespace;
 } LIBSYSTEMD_243;
index 4e23edd923273ea4cf48968ca8d2bb73e4cfb8c8..174f1228af29fdc35cdbd2c14322de7543fb105f 100644 (file)
@@ -105,5 +105,35 @@ BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = {
         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_NO_SUCH_HOME,                 EEXIST),
+        SD_BUS_ERROR_MAP(BUS_ERROR_UID_IN_USE,                   EEXIST),
+        SD_BUS_ERROR_MAP(BUS_ERROR_USER_NAME_EXISTS,             EEXIST),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_EXISTS,                  EEXIST),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_ACTIVE,          EALREADY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ALREADY_FIXATED,         EALREADY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_UNFIXATED,               EADDRNOTAVAIL),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_ACTIVE,              EALREADY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_ABSENT,                  EREMOTE),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_BUSY,                    EBUSY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD,                 ENOKEY),
+        SD_BUS_ERROR_MAP(BUS_ERROR_LOW_PASSWORD_QUALITY,         EUCLEAN),
+        SD_BUS_ERROR_MAP(BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN,    EBADSLT),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_NEEDED,             ENOANO),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED, ERFKILL),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_PIN_LOCKED,             EOWNERDEAD),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN,                ENOLCK),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT, ETOOMANYREFS),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT,   EUCLEAN),
+        SD_BUS_ERROR_MAP(BUS_ERROR_BAD_SIGNATURE,                EKEYREJECTED),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_MISMATCH,         EUCLEAN),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_DOWNGRADE,        ESTALE),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_RECORD_SIGNED,           EROFS),
+        SD_BUS_ERROR_MAP(BUS_ERROR_BAD_HOME_SIZE,                ERANGE),
+        SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_KEY,               ENOPKG),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_LOCKED,                  ENOEXEC),
+        SD_BUS_ERROR_MAP(BUS_ERROR_HOME_NOT_LOCKED,              ENOEXEC),
+        SD_BUS_ERROR_MAP(BUS_ERROR_TOO_MANY_OPERATIONS,          ENOBUFS),
+        SD_BUS_ERROR_MAP(BUS_ERROR_AUTHENTICATION_LIMIT_HIT,     ETOOMANYREFS),
+
         SD_BUS_ERROR_MAP_END
 };
index 8da56551f697111b0a64a0e29509a4026ea30a94..e5f92b9ec261e3c669cf502348ffc6b2625a9967 100644 (file)
 #define BUS_ERROR_SPEED_METER_INACTIVE "org.freedesktop.network1.SpeedMeterInactive"
 #define BUS_ERROR_UNMANAGED_INTERFACE "org.freedesktop.network1.UnmanagedInterface"
 
+#define BUS_ERROR_NO_SUCH_HOME "org.freedesktop.home1.NoSuchHome"
+#define BUS_ERROR_UID_IN_USE "org.freedesktop.home1.UIDInUse"
+#define BUS_ERROR_USER_NAME_EXISTS "org.freedesktop.home1.UserNameExists"
+#define BUS_ERROR_HOME_EXISTS "org.freedesktop.home1.HomeExists"
+#define BUS_ERROR_HOME_ALREADY_ACTIVE "org.freedesktop.home1.HomeAlreadyActive"
+#define BUS_ERROR_HOME_ALREADY_FIXATED "org.freedesktop.home1.HomeAlreadyFixated"
+#define BUS_ERROR_HOME_UNFIXATED "org.freedesktop.home1.HomeUnfixated"
+#define BUS_ERROR_HOME_NOT_ACTIVE "org.freedesktop.home1.HomeNotActive"
+#define BUS_ERROR_HOME_ABSENT "org.freedesktop.home1.HomeAbsent"
+#define BUS_ERROR_HOME_BUSY "org.freedesktop.home1.HomeBusy"
+#define BUS_ERROR_BAD_PASSWORD "org.freedesktop.home1.BadPassword"
+#define BUS_ERROR_LOW_PASSWORD_QUALITY "org.freedesktop.home1.LowPasswordQuality"
+#define BUS_ERROR_BAD_PASSWORD_AND_NO_TOKEN "org.freedesktop.home1.BadPasswordAndNoToken"
+#define BUS_ERROR_TOKEN_PIN_NEEDED "org.freedesktop.home1.TokenPinNeeded"
+#define BUS_ERROR_TOKEN_PROTECTED_AUTHENTICATION_PATH_NEEDED "org.freedesktop.home1.TokenProtectedAuthenticationPathNeeded"
+#define BUS_ERROR_TOKEN_PIN_LOCKED "org.freedesktop.home1.TokenPinLocked"
+#define BUS_ERROR_TOKEN_BAD_PIN "org.freedesktop.home1.BadPin"
+#define BUS_ERROR_TOKEN_BAD_PIN_FEW_TRIES_LEFT "org.freedesktop.home1.BadPinFewTriesLeft"
+#define BUS_ERROR_TOKEN_BAD_PIN_ONE_TRY_LEFT "org.freedesktop.home1.BadPinOneTryLeft"
+#define BUS_ERROR_BAD_SIGNATURE "org.freedesktop.home1.BadSignature"
+#define BUS_ERROR_HOME_RECORD_MISMATCH "org.freedesktop.home1.RecordMismatch"
+#define BUS_ERROR_HOME_RECORD_DOWNGRADE "org.freedesktop.home1.RecordDowngrade"
+#define BUS_ERROR_HOME_RECORD_SIGNED "org.freedesktop.home1.RecordSigned"
+#define BUS_ERROR_BAD_HOME_SIZE "org.freedesktop.home1.BadHomeSize"
+#define BUS_ERROR_NO_PRIVATE_KEY "org.freedesktop.home1.NoPrivateKey"
+#define BUS_ERROR_HOME_LOCKED "org.freedesktop.home1.HomeLocked"
+#define BUS_ERROR_HOME_NOT_LOCKED "org.freedesktop.home1.HomeNotLocked"
+#define BUS_ERROR_NO_DISK_SPACE "org.freedesktop.home1.NoDiskSpace"
+#define BUS_ERROR_TOO_MANY_OPERATIONS "org.freedesktop.home1.TooManyOperations"
+#define BUS_ERROR_AUTHENTICATION_LIMIT_HIT "org.freedesktop.home1.AuthenticationLimitHit"
+
 BUS_ERROR_MAP_ELF_USE(bus_common_errors);
index 9a6a81d7aaa5c18e2c6da5e62775ccba62248af0..caab5e5ebe65c873649021dbccd0addb55ae86f2 100644 (file)
 #include "terminal-util.h"
 #include "util.h"
 
-static char *indent(unsigned level, unsigned flags) {
+static char *indent(unsigned level, uint64_t flags) {
         char *p;
         unsigned n, i = 0;
 
         n = 0;
 
-        if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0)
+        if (flags & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0)
                 level -= 1;
 
-        if (flags & BUS_MESSAGE_DUMP_WITH_HEADER)
+        if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER)
                 n += 2;
 
         p = new(char, n + level*8 + 1);
         if (!p)
                 return NULL;
 
-        if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+        if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER) {
                 p[i++] = ' ';
                 p[i++] = ' ';
         }
@@ -45,7 +45,7 @@ static char *indent(unsigned level, unsigned flags) {
         return p;
 }
 
-int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
+_public_ int sd_bus_message_dump(sd_bus_message *m, FILE *f, uint64_t flags) {
         unsigned level = 1;
         int r;
 
@@ -54,7 +54,7 @@ int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
         if (!f)
                 f = stdout;
 
-        if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) {
+        if (flags & SD_BUS_MESSAGE_DUMP_WITH_HEADER) {
                 fprintf(f,
                         "%s%s%s Type=%s%s%s  Endian=%c  Flags=%u  Version=%u  Priority=%"PRIi64,
                         m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() :
@@ -118,11 +118,11 @@ int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
                 bus_creds_dump(&m->creds, f, true);
         }
 
-        r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY));
+        r = sd_bus_message_rewind(m, !(flags & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY));
         if (r < 0)
                 return log_error_errno(r, "Failed to rewind: %m");
 
-        if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+        if (!(flags & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
                 _cleanup_free_ char *prefix = NULL;
 
                 prefix = indent(0, flags);
@@ -259,7 +259,7 @@ int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) {
                 }
         }
 
-        if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
+        if (!(flags & SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY)) {
                 _cleanup_free_ char *prefix = NULL;
 
                 prefix = indent(0, flags);
index a1b67c6b14f8692dc4b23d5226dc06ffb85c485b..f138791c04b3ef7f71c41f5b31dbc6fc8373f4bc 100644 (file)
@@ -6,13 +6,6 @@
 
 #include "sd-bus.h"
 
-enum {
-        BUS_MESSAGE_DUMP_WITH_HEADER  = 1 << 0,
-        BUS_MESSAGE_DUMP_SUBTREE_ONLY = 1 << 1,
-};
-
-int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags);
-
 int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse);
 
 int bus_pcap_header(size_t snaplen, FILE *f);
index beab80687d6f1a6e370f3ee2009d6d3924cbee34..e8934489b581f1b07a35609cbedeb35fb628aea6 100644 (file)
@@ -160,7 +160,7 @@ int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v) {
                 case _SD_BUS_VTABLE_SIGNAL:
                         fprintf(i->f, "  <signal name=\"%s\">\n", v->x.signal.member);
                         if (bus_vtable_has_names(vtable))
-                                names = strempty(v->x.method.names);
+                                names = strempty(v->x.signal.names);
                         introspect_write_arguments(i, strempty(v->x.signal.signature), &names, NULL);
                         introspect_write_flags(i, v->type, v->flags);
                         fputs("  </signal>\n", i->f);
index d9716ae74514f80fd83c82b41bb19c50b7093f5e..05127f0e0ce4930fbdd0c83d6a4421698bb145bd 100644 (file)
@@ -145,7 +145,7 @@ static int server(sd_bus *bus) {
                          strna(sd_bus_message_get_member(m)),
                          pid,
                          strna(label));
-                /* bus_message_dump(m); */
+                /* sd_bus_message_dump(m); */
                 /* sd_bus_message_rewind(m, true); */
 
                 if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) {
index 1a9a35d56b469a27427ca22dc1722cd831d2fb47..d248bd4da133ab599b8edda02d3abe8cb1bbb9ed 100644 (file)
@@ -175,7 +175,7 @@ static int test_marshal(void) {
         }
 #endif
 
-        assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
+        assert_se(sd_bus_message_dump(m, NULL, SD_BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
 
         assert_se(bus_message_get_blob(m, &blob, &sz) >= 0);
 
@@ -196,7 +196,7 @@ static int test_marshal(void) {
         assert_se(bus_message_from_malloc(bus, blob, sz, NULL, 0, NULL, &n) >= 0);
         blob = NULL;
 
-        assert_se(bus_message_dump(n, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
+        assert_se(sd_bus_message_dump(n, NULL, SD_BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
 
         m = sd_bus_message_unref(m);
 
@@ -205,7 +205,7 @@ static int test_marshal(void) {
         assert_se(sd_bus_message_append(m, "as", 0) >= 0);
 
         assert_se(sd_bus_message_seal(m, 4712, 0) >= 0);
-        assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
+        assert_se(sd_bus_message_dump(m, NULL, SD_BUS_MESSAGE_DUMP_WITH_HEADER) >= 0);
 
         return EXIT_SUCCESS;
 }
index d7d51ba9504f4b2bc9196d8c8851d0de37503b51..107eea390e0bcccab0fd61bd454d6d988c24f920 100644 (file)
@@ -188,10 +188,10 @@ int main(int argc, char *argv[]) {
         r = sd_bus_message_seal(m, 4711, 0);
         assert_se(r >= 0);
 
-        bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(m, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         ms = open_memstream_unlocked(&first, &first_size);
-        bus_message_dump(m, ms, 0);
+        sd_bus_message_dump(m, ms, 0);
         fflush(ms);
         assert_se(!ferror(ms));
 
@@ -244,11 +244,11 @@ int main(int argc, char *argv[]) {
         r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m);
         assert_se(r >= 0);
 
-        bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(m, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         fclose(ms);
         ms = open_memstream_unlocked(&second, &second_size);
-        bus_message_dump(m, ms, 0);
+        sd_bus_message_dump(m, ms, 0);
         fflush(ms);
         assert_se(!ferror(ms));
         assert_se(first_size == second_size);
@@ -354,7 +354,7 @@ int main(int argc, char *argv[]) {
 
         fclose(ms);
         ms = open_memstream_unlocked(&third, &third_size);
-        bus_message_dump(copy, ms, 0);
+        sd_bus_message_dump(copy, ms, 0);
         fflush(ms);
         assert_se(!ferror(ms));
 
index 3c5bb88f4e1d2dbceb1af7eb071f2c2e9a221d29..41cf8c167006793b462ba93ace85fad500ff1e44 100644 (file)
@@ -399,7 +399,7 @@ static int client(struct context *c) {
         r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "");
         assert_se(r >= 0);
 
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -417,7 +417,7 @@ static int client(struct context *c) {
         r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, "");
         assert_se(r >= 0);
 
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -429,7 +429,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -441,7 +441,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -453,7 +453,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -465,7 +465,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -477,7 +477,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
@@ -489,7 +489,7 @@ static int client(struct context *c) {
         assert_se(r > 0);
 
         assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"));
-        bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER);
+        sd_bus_message_dump(reply, stdout, SD_BUS_MESSAGE_DUMP_WITH_HEADER);
 
         sd_bus_message_unref(reply);
         reply = NULL;
index e38bcdcc76fd827d710e1d0c7fa45949c8da87f5..82eb35e5b16db960c0b3c38840ced85a5c65bcb1 100644 (file)
@@ -108,7 +108,7 @@ fail:
 static int client(struct context *c) {
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
         _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL;
-        sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         int r;
 
         assert_se(sd_bus_new(&bus) >= 0);
index c83575c7c876c84234c1ba3cff97e29297031e7f..d790e8fd0b197208f63b741696e5995839b89886 100644 (file)
@@ -446,7 +446,7 @@ static int insert_data(struct trie *trie, char **match_list, char *line, const c
 
         value = strchr(line, '=');
         if (!value)
-                return log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
+                return log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
                                   "Key-value pair expected but got \"%s\", ignoring", line);
 
         value[0] = '\0';
@@ -457,7 +457,7 @@ static int insert_data(struct trie *trie, char **match_list, char *line, const c
                 line++;
 
         if (isempty(line + 1) || isempty(value))
-                return log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
+                return log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
                                   "Empty %s in \"%s=%s\", ignoring",
                                   isempty(line + 1) ? "key" : "value",
                                   line, value);
@@ -477,7 +477,6 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
         _cleanup_fclose_ FILE *f = NULL;
         _cleanup_strv_free_ char **match_list = NULL;
         uint32_t line_number = 0;
-        char *match = NULL;
         int r = 0, err;
 
         f = fopen(filename, "re");
@@ -518,20 +517,15 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
                                 break;
 
                         if (line[0] == ' ') {
-                                log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
-                                           "Match expected but got indented property \"%s\", ignoring line", line);
-                                r = -EINVAL;
+                                r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
+                                               "Match expected but got indented property \"%s\", ignoring line", line);
                                 break;
                         }
 
                         /* start of record, first match */
                         state = HW_MATCH;
 
-                        match = strdup(line);
-                        if (!match)
-                                return -ENOMEM;
-
-                        err = strv_consume(&match_list, match);
+                        err = strv_extend(&match_list, line);
                         if (err < 0)
                                 return err;
 
@@ -539,21 +533,16 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
 
                 case HW_MATCH:
                         if (len == 0) {
-                                log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
-                                           "Property expected, ignoring record with no properties");
-                                r = -EINVAL;
+                                r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
+                                               "Property expected, ignoring record with no properties");
                                 state = HW_NONE;
-                                strv_clear(match_list);
+                                match_list = strv_free(match_list);
                                 break;
                         }
 
                         if (line[0] != ' ') {
                                 /* another match */
-                                match = strdup(line);
-                                if (!match)
-                                        return -ENOMEM;
-
-                                err = strv_consume(&match_list, match);
+                                err = strv_extend(&match_list, line);
                                 if (err < 0)
                                         return err;
 
@@ -571,16 +560,15 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
                         if (len == 0) {
                                 /* end of record */
                                 state = HW_NONE;
-                                strv_clear(match_list);
+                                match_list = strv_free(match_list);
                                 break;
                         }
 
                         if (line[0] != ' ') {
-                                log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
-                                           "Property or empty line expected, got \"%s\", ignoring record", line);
-                                r = -EINVAL;
+                                r = log_syntax(NULL, LOG_WARNING, filename, line_number, SYNTHETIC_ERRNO(EINVAL),
+                                               "Property or empty line expected, got \"%s\", ignoring record", line);
                                 state = HW_NONE;
-                                strv_clear(match_list);
+                                match_list = strv_free(match_list);
                                 break;
                         }
 
@@ -592,7 +580,7 @@ static int import_file(struct trie *trie, const char *filename, uint16_t file_pr
         }
 
         if (state == HW_MATCH)
-                log_syntax(NULL, LOG_WARNING, filename, line_number, EINVAL,
+                log_syntax(NULL, LOG_WARNING, filename, line_number, 0,
                            "Property expected, ignoring record with no properties");
 
         return r;
index 985872b82da48300a4bf9e430a568ce83afad457..335f22b9208da3b797b8c9e2b99435f872184b3c 100644 (file)
@@ -190,4 +190,17 @@ int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) {
         return memcmp(a, b, 16);
 }
 
+sd_id128_t id128_make_v4_uuid(sd_id128_t id) {
+        /* Stolen from generate_random_uuid() of drivers/char/random.c
+         * in the kernel sources */
+
+        /* Set UUID version to 4 --- truly random generation */
+        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
+
+        /* Set the UUID variant to DCE */
+        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
+
+        return id;
+}
+
 DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func);
index fe0149a8aa1b370d29489eca53e4bc370608f6b0..1901bf119fffb565b8606fc84cd15aa73102bd6d 100644 (file)
@@ -30,3 +30,5 @@ int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync);
 void id128_hash_func(const sd_id128_t *p, struct siphash *state);
 int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_;
 extern const struct hash_ops id128_hash_ops;
+
+sd_id128_t id128_make_v4_uuid(sd_id128_t id);
index b331a6b432ffe84facd92c8599d580e03a6ef07f..9b38ef0c563206f79b00dc397e9c1c55926d8adb 100644 (file)
@@ -250,19 +250,6 @@ _public_ int sd_id128_get_invocation(sd_id128_t *ret) {
         return 0;
 }
 
-static sd_id128_t make_v4_uuid(sd_id128_t id) {
-        /* Stolen from generate_random_uuid() of drivers/char/random.c
-         * in the kernel sources */
-
-        /* Set UUID version to 4 --- truly random generation */
-        id.bytes[6] = (id.bytes[6] & 0x0F) | 0x40;
-
-        /* Set the UUID variant to DCE */
-        id.bytes[8] = (id.bytes[8] & 0x3F) | 0x80;
-
-        return id;
-}
-
 _public_ int sd_id128_randomize(sd_id128_t *ret) {
         sd_id128_t t;
         int r;
@@ -279,7 +266,7 @@ _public_ int sd_id128_randomize(sd_id128_t *ret) {
          * only guarantee this for newly generated UUIDs, not for
          * pre-existing ones. */
 
-        *ret = make_v4_uuid(t);
+        *ret = id128_make_v4_uuid(t);
         return 0;
 }
 
@@ -306,7 +293,7 @@ static int get_app_specific(sd_id128_t base, sd_id128_t app_id, sd_id128_t *ret)
         /* We chop off the trailing 16 bytes */
         memcpy(&result, p, MIN(khash_get_size(h), sizeof(result)));
 
-        *ret = make_v4_uuid(result);
+        *ret = id128_make_v4_uuid(result);
         return 0;
 }
 
index 519dd0d188cfea6abd2f3efb26578989d7c291c0..30669a9359e58104898dcc1e9c460db27fee9562 100644 (file)
@@ -5,7 +5,7 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "env-file-label.h"
 #include "env-file.h"
 #include "env-util.h"
index baf0bd102ba99d58fdf25749affb35e1f69fad87..09f16d25f4c20e5fa928dcce2dabaf34e3d0982b 100644 (file)
@@ -15,7 +15,7 @@
 #include "alloc-util.h"
 #include "bus-error.h"
 #include "bus-message.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "def.h"
 #include "keymap-util.h"
 #include "locale-util.h"
@@ -33,7 +33,7 @@ static int locale_update_system_manager(Context *c, sd_bus *bus) {
         _cleanup_free_ char **l_unset = NULL;
         _cleanup_strv_free_ char **l_set = NULL;
         _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
-        sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         size_t c_set, c_unset;
         LocaleVariable p;
         int r;
index bff63541771fcbfa2feacca5c45a1354d35d3912..2e39f557632fffb8e020478704a23b5b6bc6936f 100644 (file)
@@ -576,6 +576,7 @@ static int print_session_status_info(sd_bus *bus, const char *path, bool *new_li
                         show_journal_by_unit(
                                         stdout,
                                         i.scope,
+                                        NULL,
                                         arg_output,
                                         0,
                                         i.timestamp.monotonic,
@@ -660,6 +661,7 @@ static int print_user_status_info(sd_bus *bus, const char *path, bool *new_line)
                 show_journal_by_unit(
                                 stdout,
                                 i.slice,
+                                NULL,
                                 arg_output,
                                 0,
                                 i.timestamp.monotonic,
@@ -981,7 +983,6 @@ static int show_seat(int argc, char *argv[], void *userdata) {
 static int activate(int argc, char *argv[], void *userdata) {
         _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
         sd_bus *bus = userdata;
-        char *short_argv[3];
         int r, i;
 
         assert(bus);
@@ -990,12 +991,20 @@ static int activate(int argc, char *argv[], void *userdata) {
         polkit_agent_open_if_enabled(arg_transport, arg_ask_password);
 
         if (argc < 2) {
-                short_argv[0] = argv[0];
-                short_argv[1] = (char*) "";
-                short_argv[2] = NULL;
+                r = sd_bus_call_method(
+                                bus,
+                                "org.freedesktop.login1",
+                                "/org/freedesktop/login1/session/auto",
+                                "org.freedesktop.login1.Session",
+                                streq(argv[0], "lock-session")      ? "Lock" :
+                                streq(argv[0], "unlock-session")    ? "Unlock" :
+                                streq(argv[0], "terminate-session") ? "Terminate" :
+                                                                      "Activate",
+                                &error, NULL, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r));
 
-                argv = short_argv;
-                argc = 2;
+                return 0;
         }
 
         for (i = 1; i < argc; i++) {
index d3aa6815a6c106c5a1d69b62be9eb1d71d4498d7..52a7ea3c77e9ff8f1418045ecab709613be3d188 100644 (file)
 #include "bootspec.h"
 #include "bus-common-errors.h"
 #include "bus-error.h"
+#include "bus-polkit.h"
 #include "bus-unit-util.h"
 #include "bus-util.h"
 #include "cgroup-util.h"
 #include "device-util.h"
 #include "dirent-util.h"
-#include "efivars.h"
 #include "efi-loader.h"
+#include "efivars.h"
 #include "env-util.h"
 #include "escape.h"
 #include "fd-util.h"
@@ -1015,6 +1016,8 @@ static int method_activate_session(sd_bus_message *message, void *userdata, sd_b
         if (r < 0)
                 return r;
 
+        /* PolicyKit is done by bus_session_method_activate() */
+
         return bus_session_method_activate(message, session, error);
 }
 
@@ -1046,6 +1049,20 @@ static int method_activate_session_on_seat(sd_bus_message *message, void *userda
                 return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT,
                                          "Session %s not on seat %s", session_name, seat_name);
 
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &m->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
         r = session_activate(session);
         if (r < 0)
                 return r;
index 34ac0350f2dd93b69d455b5740ae71396e82dc9d..0a5df937cc1a630523383bbf85ec98462647587f 100644 (file)
@@ -5,6 +5,7 @@
 #include "alloc-util.h"
 #include "bus-common-errors.h"
 #include "bus-label.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "logind-dbus.h"
 #include "logind-seat-dbus.h"
@@ -177,6 +178,20 @@ static int method_activate_session(sd_bus_message *message, void *userdata, sd_b
         if (session->seat != s)
                 return sd_bus_error_setf(error, BUS_ERROR_SESSION_NOT_ON_SEAT, "Session %s not on seat %s", name, s->id);
 
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &s->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
         r = session_activate(session);
         if (r < 0)
                 return r;
@@ -197,7 +212,21 @@ static int method_switch_to(sd_bus_message *message, void *userdata, sd_bus_erro
                 return r;
 
         if (to <= 0)
-                return -EINVAL;
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid virtual terminal");
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &s->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
 
         r = seat_switch_to(s, to);
         if (r < 0)
@@ -213,6 +242,20 @@ static int method_switch_to_next(sd_bus_message *message, void *userdata, sd_bus
         assert(message);
         assert(s);
 
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &s->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
         r = seat_switch_to_next(s);
         if (r < 0)
                 return r;
@@ -227,6 +270,20 @@ static int method_switch_to_previous(sd_bus_message *message, void *userdata, sd
         assert(message);
         assert(s);
 
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &s->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
         r = seat_switch_to_previous(s);
         if (r < 0)
                 return r;
index 80f3e432de80a77aa23c3190cd24a70e60af9e0a..80ec89ba0a0084eeef3cb08f5e881d864fea7aa4 100644 (file)
@@ -5,6 +5,7 @@
 #include "alloc-util.h"
 #include "bus-common-errors.h"
 #include "bus-label.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "fd-util.h"
 #include "logind-brightness.h"
@@ -190,6 +191,20 @@ int bus_session_method_activate(sd_bus_message *message, void *userdata, sd_bus_
         assert(message);
         assert(s);
 
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.login1.chvt",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &s->manager->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
         r = session_activate(s);
         if (r < 0)
                 return r;
index 7943a007e140f74e03351f0c502447cda985a8e6..9bf68a610e409ac2f9cef55e673008c19864f0ac 100644 (file)
@@ -3,6 +3,7 @@
 #include <errno.h>
 
 #include "alloc-util.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "format-util.h"
 #include "logind-dbus.h"
index fdc6448a651d38da6820da84d927e35230c1cddf..9ceb33cde911b60d014c756e914b5ea33d03d4ad 100644 (file)
@@ -405,35 +405,23 @@ static int user_update_slice(User *u) {
         if (r < 0)
                 return bus_log_create_error(r);
 
-        if (u->user_record->tasks_max != UINT64_MAX) {
-                r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", u->user_record->tasks_max);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
-
-        if (u->user_record->memory_max != UINT64_MAX) {
-                r = sd_bus_message_append(m, "(sv)", "MemoryMax", "t", u->user_record->memory_max);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
-
-        if (u->user_record->memory_high != UINT64_MAX) {
-                r = sd_bus_message_append(m, "(sv)", "MemoryHigh", "t", u->user_record->memory_high);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
-
-        if (u->user_record->cpu_weight != UINT64_MAX) {
-                r = sd_bus_message_append(m, "(sv)", "CPUWeight", "t", u->user_record->cpu_weight);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
+        const struct {
+                const char *name;
+                uint64_t value;
+        } settings[] = {
+                { "TasksMax",   u->user_record->tasks_max   },
+                { "MemoryMax",  u->user_record->memory_max  },
+                { "MemoryHigh", u->user_record->memory_high },
+                { "CPUWeight",  u->user_record->cpu_weight  },
+                { "IOWeight",   u->user_record->io_weight   },
+        };
 
-        if (u->user_record->io_weight != UINT64_MAX) {
-                r = sd_bus_message_append(m, "(sv)", "IOWeight", "t", u->user_record->io_weight);
-                if (r < 0)
-                        return bus_log_create_error(r);
-        }
+        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);
+                }
 
         r = sd_bus_message_close_container(m);
         if (r < 0)
index d8c1bbe15b95eec66d3ed11776a7821b1d405d2b..8f3708d2a4f00671d6dc8d0444315bfd6f9c3038 100644 (file)
@@ -9,7 +9,7 @@
 
 #include "alloc-util.h"
 #include "bus-error.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "cgroup-util.h"
 #include "def.h"
 #include "device-util.h"
index 6dc79aa32aa4d875294d2ed2903dd1c9f83278ae..37098bdf71ac46fda909a7713318af7498325897 100644 (file)
                 </defaults>
         </action>
 
+        <action id="org.freedesktop.login1.chvt">
+                <description gettext-domain="systemd">Change Session</description>
+                <message gettext-domain="systemd">Authentication is required to change the virtual terminal.</message>
+                <defaults>
+                        <allow_any>auth_admin_keep</allow_any>
+                        <allow_inactive>auth_admin_keep</allow_inactive>
+                        <allow_active>yes</allow_active>
+                </defaults>
+        </action>
+
 </policyconfig>
index c7fe858b921df59f6b47ee6865eb8ec063a9e990..8447e1c555f9a2a54d3dda4fe45b9a625fbb9522 100644 (file)
@@ -11,6 +11,7 @@
 #include <security/pam_modutil.h>
 #include <sys/file.h>
 #include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -219,10 +220,11 @@ static int socket_from_display(const char *display, char **path) {
 
 static int get_seat_from_display(const char *display, const char **seat, uint32_t *vtnr) {
         union sockaddr_union sa = {};
-        _cleanup_free_ char *p = NULL, *tty = NULL;
+        _cleanup_free_ char *p = NULL, *sys_path = NULL, *tty = NULL;
         _cleanup_close_ int fd = -1;
         struct ucred ucred;
         int v, r, salen;
+        dev_t display_ctty;
 
         assert(display);
         assert(vtnr);
@@ -251,7 +253,13 @@ static int get_seat_from_display(const char *display, const char **seat, uint32_
         if (r < 0)
                 return r;
 
-        r = get_ctty(ucred.pid, NULL, &tty);
+        r = get_ctty_devnr(ucred.pid, &display_ctty);
+        if (r < 0)
+                return r;
+
+        if (asprintf(&sys_path, "/sys/dev/char/%d:%d", major(display_ctty), minor(display_ctty)) < 0)
+                return -ENOMEM;
+        r = readlink_value(sys_path, &tty);
         if (r < 0)
                 return r;
 
index b45355d86fce7d322c2ee9bf8f33682b791d6aa8..294ef349321f4c0397d9d9aecc9a3dfa8b1484b6 100644 (file)
@@ -5,6 +5,7 @@
 
 #include "alloc-util.h"
 #include "bus-label.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "copy.h"
 #include "dissect-image.h"
index 3b2ac3829859618b633587d21864136cf82339ea..a2990452af17973de8844844f13bdb5c575ac0b9 100644 (file)
@@ -14,6 +14,7 @@
 #include "bus-common-errors.h"
 #include "bus-internal.h"
 #include "bus-label.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "copy.h"
 #include "env-file.h"
index 2377416b4406a034549ff346a6f9b1dc48de0b68..61234582a3405e50b3700bb7926f256c86e66d77 100644 (file)
@@ -626,6 +626,7 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
                         show_journal_by_unit(
                                         stdout,
                                         i->unit,
+                                        NULL,
                                         arg_output,
                                         0,
                                         i->timestamp.monotonic,
index 6fc3b930570be25e6c9ac4c2efe590228d4bfc92..d0cc07678fa25e4193c1590dbef32fbace3bf7f2 100644 (file)
@@ -8,6 +8,7 @@
 #include "alloc-util.h"
 #include "btrfs-util.h"
 #include "bus-common-errors.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "cgroup-util.h"
 #include "errno-util.h"
index a3bed035dc80c95169491506e078f1514f9f9c62..ace2131c2deaa512bb9960d73277dd2093b0fe3c 100644 (file)
@@ -10,7 +10,7 @@
 
 #include "alloc-util.h"
 #include "bus-error.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "cgroup-util.h"
 #include "dirent-util.h"
 #include "fd-util.h"
index 8b0e3d27eae3194510802c6c50dac03fee5ba060..72b33ed9e59302f1a7b92e9d845097d410644042 100644 (file)
@@ -729,7 +729,7 @@ static void ipip_sit_init(NetDev *n) {
         assert(t);
 
         t->pmtudisc = true;
-        t->fou_encap_type = FOU_ENCAP_DIRECT;
+        t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
         t->isatap = -1;
 }
 
@@ -771,7 +771,7 @@ static void gre_erspan_init(NetDev *n) {
 
         t->pmtudisc = true;
         t->gre_erspan_sequence = -1;
-        t->fou_encap_type = FOU_ENCAP_DIRECT;
+        t->fou_encap_type = NETDEV_FOO_OVER_UDP_ENCAP_DIRECT;
 }
 
 static void ip6gre_init(NetDev *n) {
index 629bce29b9c616072b334c10d31d76e762a5569d..a470488664b6bb4316647a2f05e4c2d96cc67b61 100644 (file)
@@ -170,7 +170,7 @@ typedef struct LinkInfo {
 
         /* ethtool info */
         int autonegotiation;
-        size_t speed;
+        uint64_t speed;
         Duplex duplex;
         NetDevPort port;
 
@@ -1495,7 +1495,7 @@ static int link_status_one(
                         r = table_add_many(table,
                                            TABLE_EMPTY,
                                            TABLE_STRING, "Speed:",
-                                           TABLE_BPS, (uint64_t) info->speed);
+                                           TABLE_BPS, info->speed);
                         if (r < 0)
                                 return table_log_add_error(r);
                 }
index bdd88659c5415f4c481ed457babdafb0e641d0e2..5087dbbc2ea1a7fb348c7fa5fbf921f5a1440f22 100644 (file)
@@ -101,7 +101,7 @@ static int link_set_can(Link *link) {
                 };
 
                 if (link->network->can_bitrate > UINT32_MAX) {
-                        log_link_error(link, "bitrate (%zu) too big.", link->network->can_bitrate);
+                        log_link_error(link, "bitrate (%" PRIu64 ") too big.", link->network->can_bitrate);
                         return -ERANGE;
                 }
 
index 6465a8cfe9c77e7e30d6a84faa83baee1da34057..8664d8cdc0d43782def4aa85ac2f7060e7c7ab6e 100644 (file)
@@ -390,7 +390,7 @@ int config_parse_dhcp_send_option(
                 break;
         }
         case DHCP_OPTION_DATA_STRING:
-                sz = cunescape(p, 0, &q);
+                sz = cunescape(p, UNESCAPE_ACCEPT_NUL, &q);
                 if (sz < 0) {
                         log_syntax(unit, LOG_ERR, filename, line, sz,
                                    "Failed to decode DHCPv4 option data, ignoring assignment: %s", p);
index 8f3b2e92f8fff739fb6ae14b2054bea2ed754b74..efb72b60e79aa0994d173aef792fa476f091291f 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "dns-domain.h"
 #include "networkd-link-bus.h"
@@ -620,6 +621,12 @@ int bus_link_method_reconfigure(sd_bus_message *message, void *userdata, sd_bus_
         if (r < 0)
                 return r;
 
+        link_set_state(l, LINK_STATE_INITIALIZED);
+        r = link_save(l);
+        if (r < 0)
+                return r;
+        link_clean(l);
+
         return sd_bus_reply_method_return(message, NULL);
 }
 
index 07c6cf345109e912e7f649b386fe7412888b0954..f3744e2354ce42b29d4f124fdb2244413e06327c 100644 (file)
@@ -3023,9 +3023,6 @@ static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool for
         Network *network;
         int r;
 
-        if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_LINGER))
-                return 0;
-
         if (m) {
                 _cleanup_strv_free_ char **s = NULL;
 
@@ -3089,6 +3086,7 @@ static int link_reconfigure_internal(Link *link, sd_netlink_message *m, bool for
                 return r;
 
         link_set_state(link, LINK_STATE_INITIALIZED);
+        link_dirty(link);
 
         /* link_configure_duid() returns 0 if it requests product UUID. In that case,
          * link_configure() is called later asynchronously. */
@@ -3127,6 +3125,9 @@ int link_reconfigure(Link *link, bool force) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL;
         int r;
 
+        if (IN_SET(link->state, LINK_STATE_PENDING, LINK_STATE_LINGER))
+                return 0;
+
         r = sd_rtnl_message_new_link(link->manager->rtnl, &req, RTM_GETLINK,
                                      link->ifindex);
         if (r < 0)
index 660c2847eb385824fb5cc1e7ca47877de807d480..b7279a582ce4389d92146aeae34dd10536c2cb8e 100644 (file)
@@ -6,7 +6,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "networkd-link-bus.h"
 #include "networkd-link.h"
 #include "networkd-manager-bus.h"
index 380f5c1c61f14613e113284964e9f29c931b092e..5a442038c48758bd25881d2ef868bf663a92251d 100644 (file)
@@ -11,6 +11,7 @@
 #include "sd-netlink.h"
 
 #include "alloc-util.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "conf-parser.h"
 #include "def.h"
@@ -961,6 +962,7 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi
         _cleanup_free_ char *from = NULL, *to = NULL;
         RoutingPolicyRule *rule = NULL;
         const char *iif = NULL, *oif = NULL;
+        uint32_t suppress_prefixlen;
         Manager *m = userdata;
         unsigned flags;
         uint16_t type;
@@ -1137,6 +1139,20 @@ int manager_rtnl_process_rule(sd_netlink *rtnl, sd_netlink_message *message, voi
                 return 0;
         }
 
+        r = sd_netlink_message_read(message, FRA_UID_RANGE, sizeof(tmp->uid_range), &tmp->uid_range);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: could not get FRA_UID_RANGE attribute, ignoring: %m");
+                return 0;
+        }
+
+        r = sd_netlink_message_read_u32(message, FRA_SUPPRESS_PREFIXLEN, &suppress_prefixlen);
+        if (r < 0 && r != -ENODATA) {
+                log_warning_errno(r, "rtnl: could not get FRA_SUPPRESS_PREFIXLEN attribute, ignoring: %m");
+                return 0;
+        }
+        if (r >= 0)
+                tmp->suppress_prefixlen = (int) suppress_prefixlen;
+
         (void) routing_policy_rule_get(m, tmp, &rule);
 
         if (DEBUG_LOGGING) {
index 67b3789ee6c1e0de88fac93d6d2cbe6521a496be..00a8914f8f70fd87f7b2b33339d5f855afd448a8 100644 (file)
@@ -27,228 +27,230 @@ struct ConfigPerfItem;
 %struct-type
 %includes
 %%
-Match.MACAddress,                       config_parse_hwaddrs,                            0,                             offsetof(Network, match_mac)
-Match.PermanentMACAddress,              config_parse_hwaddrs,                            0,                             offsetof(Network, match_permanent_mac)
-Match.Path,                             config_parse_match_strv,                         0,                             offsetof(Network, match_path)
-Match.Driver,                           config_parse_match_strv,                         0,                             offsetof(Network, match_driver)
-Match.Type,                             config_parse_match_strv,                         0,                             offsetof(Network, match_type)
-Match.WLANInterfaceType,                config_parse_match_strv,                         0,                             offsetof(Network, match_wlan_iftype)
-Match.SSID,                             config_parse_match_strv,                         0,                             offsetof(Network, match_ssid)
-Match.BSSID,                            config_parse_hwaddrs,                            0,                             offsetof(Network, match_bssid)
-Match.Name,                             config_parse_match_ifnames,                      1,                             offsetof(Network, match_name)
-Match.Property,                         config_parse_match_property,                     0,                             offsetof(Network, match_property)
-Match.Host,                             config_parse_net_condition,                      CONDITION_HOST,                offsetof(Network, conditions)
-Match.Virtualization,                   config_parse_net_condition,                      CONDITION_VIRTUALIZATION,      offsetof(Network, conditions)
-Match.KernelCommandLine,                config_parse_net_condition,                      CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
-Match.KernelVersion,                    config_parse_net_condition,                      CONDITION_KERNEL_VERSION,      offsetof(Network, conditions)
-Match.Architecture,                     config_parse_net_condition,                      CONDITION_ARCHITECTURE,        offsetof(Network, conditions)
-Link.MACAddress,                        config_parse_hwaddr,                             0,                             offsetof(Network, mac)
-Link.MTUBytes,                          config_parse_mtu,                                AF_UNSPEC,                     offsetof(Network, mtu)
-Link.ARP,                               config_parse_tristate,                           0,                             offsetof(Network, arp)
-Link.Multicast,                         config_parse_tristate,                           0,                             offsetof(Network, multicast)
-Link.AllMulticast,                      config_parse_tristate,                           0,                             offsetof(Network, allmulticast)
-Link.Unmanaged,                         config_parse_bool,                               0,                             offsetof(Network, unmanaged)
-Link.RequiredForOnline,                 config_parse_required_for_online,                0,                             0
-Network.Description,                    config_parse_string,                             0,                             offsetof(Network, description)
-Network.Bridge,                         config_parse_ifname,                             0,                             offsetof(Network, bridge_name)
-Network.Bond,                           config_parse_ifname,                             0,                             offsetof(Network, bond_name)
-Network.VLAN,                           config_parse_stacked_netdev,                     NETDEV_KIND_VLAN,              offsetof(Network, stacked_netdev_names)
-Network.MACVLAN,                        config_parse_stacked_netdev,                     NETDEV_KIND_MACVLAN,           offsetof(Network, stacked_netdev_names)
-Network.MACVTAP,                        config_parse_stacked_netdev,                     NETDEV_KIND_MACVTAP,           offsetof(Network, stacked_netdev_names)
-Network.IPVLAN,                         config_parse_stacked_netdev,                     NETDEV_KIND_IPVLAN,            offsetof(Network, stacked_netdev_names)
-Network.IPVTAP,                         config_parse_stacked_netdev,                     NETDEV_KIND_IPVTAP,            offsetof(Network, stacked_netdev_names)
-Network.VXLAN,                          config_parse_stacked_netdev,                     NETDEV_KIND_VXLAN,             offsetof(Network, stacked_netdev_names)
-Network.L2TP,                           config_parse_stacked_netdev,                     NETDEV_KIND_L2TP,              offsetof(Network, stacked_netdev_names)
-Network.MACsec,                         config_parse_stacked_netdev,                     NETDEV_KIND_MACSEC,            offsetof(Network, stacked_netdev_names)
-Network.Tunnel,                         config_parse_stacked_netdev,                     _NETDEV_KIND_TUNNEL,           offsetof(Network, stacked_netdev_names)
-Network.Xfrm,                           config_parse_stacked_netdev,                     NETDEV_KIND_XFRM,              offsetof(Network, stacked_netdev_names)
-Network.VRF,                            config_parse_ifname,                             0,                             offsetof(Network, vrf_name)
-Network.DHCP,                           config_parse_dhcp,                               0,                             offsetof(Network, dhcp)
-Network.DHCPServer,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_server)
-Network.LinkLocalAddressing,            config_parse_link_local_address_family,          0,                             offsetof(Network, link_local)
-Network.IPv4LLRoute,                    config_parse_bool,                               0,                             offsetof(Network, ipv4ll_route)
-Network.DefaultRouteOnDevice,           config_parse_bool,                               0,                             offsetof(Network, default_route_on_device)
-Network.IPv6Token,                      config_parse_ipv6token,                          0,                             offsetof(Network, ipv6_token)
-Network.LLDP,                           config_parse_lldp_mode,                          0,                             offsetof(Network, lldp_mode)
-Network.EmitLLDP,                       config_parse_lldp_emit,                          0,                             offsetof(Network, lldp_emit)
-Network.Address,                        config_parse_address,                            0,                             0
-Network.Gateway,                        config_parse_gateway,                            0,                             0
-Network.Domains,                        config_parse_domains,                            0,                             0
-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)
-Network.MulticastDNS,                   config_parse_resolve_support,                    0,                             offsetof(Network, mdns)
-Network.DNSOverTLS,                     config_parse_dns_over_tls_mode,                  0,                             offsetof(Network, dns_over_tls_mode)
-Network.DNSSEC,                         config_parse_dnssec_mode,                        0,                             offsetof(Network, dnssec_mode)
-Network.DNSSECNegativeTrustAnchors,     config_parse_dnssec_negative_trust_anchors,      0,                             0
-Network.NTP,                            config_parse_ntp,                                0,                             offsetof(Network, ntp)
-Network.IPForward,                      config_parse_address_family_with_kernel,         0,                             offsetof(Network, ip_forward)
-Network.IPMasquerade,                   config_parse_bool,                               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.IPv6DuplicateAddressDetection,  config_parse_int,                                0,                             offsetof(Network, ipv6_dad_transmits)
-Network.IPv6HopLimit,                   config_parse_int,                                0,                             offsetof(Network, ipv6_hop_limit)
-Network.IPv6ProxyNDP,                   config_parse_tristate,                           0,                             offsetof(Network, ipv6_proxy_ndp)
-Network.IPv6MTUBytes,                   config_parse_mtu,                                AF_INET6,                      offsetof(Network, ipv6_mtu)
-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.ProxyARP,                       config_parse_tristate,                           0,                             offsetof(Network, proxy_arp)
-Network.IPv6ProxyNDPAddress,            config_parse_ipv6_proxy_ndp_address,             0,                             0
-Network.BindCarrier,                    config_parse_strv,                               0,                             offsetof(Network, bind_carrier)
-Network.ConfigureWithoutCarrier,        config_parse_bool,                               0,                             offsetof(Network, configure_without_carrier)
-Network.IgnoreCarrierLoss,              config_parse_bool,                               0,                             offsetof(Network, ignore_carrier_loss)
-Network.KeepConfiguration,              config_parse_keep_configuration,                 0,                             offsetof(Network, keep_configuration)
-Address.Address,                        config_parse_address,                            0,                             0
-Address.Peer,                           config_parse_address,                            0,                             0
-Address.Broadcast,                      config_parse_broadcast,                          0,                             0
-Address.Label,                          config_parse_label,                              0,                             0
-Address.PreferredLifetime,              config_parse_lifetime,                           0,                             0
-Address.HomeAddress,                    config_parse_address_flags,                      0,                             0
-Address.ManageTemporaryAddress,         config_parse_address_flags,                      0,                             0
-Address.PrefixRoute,                    config_parse_address_flags,                      0,                             0 /* deprecated */
-Address.AddPrefixRoute,                 config_parse_address_flags,                      0,                             0
-Address.AutoJoin,                       config_parse_address_flags,                      0,                             0
-Address.DuplicateAddressDetection,      config_parse_duplicate_address_detection,        0,                             0
-Address.Scope,                          config_parse_address_scope,                      0,                             0
-IPv6AddressLabel.Prefix,                config_parse_address_label_prefix,               0,                             0
-IPv6AddressLabel.Label,                 config_parse_address_label,                      0,                             0
-Neighbor.Address,                       config_parse_neighbor_address,                   0,                             0
-Neighbor.LinkLayerAddress,              config_parse_neighbor_lladdr,                    0,                             0
-Neighbor.MACAddress,                    config_parse_neighbor_hwaddr,                    0,                             0 /* deprecated */
-RoutingPolicyRule.TypeOfService,        config_parse_routing_policy_rule_tos,            0,                             0
-RoutingPolicyRule.Priority,             config_parse_routing_policy_rule_priority,       0,                             0
-RoutingPolicyRule.Table,                config_parse_routing_policy_rule_table,          0,                             0
-RoutingPolicyRule.FirewallMark,         config_parse_routing_policy_rule_fwmark_mask,    0,                             0
-RoutingPolicyRule.From,                 config_parse_routing_policy_rule_prefix,         0,                             0
-RoutingPolicyRule.To,                   config_parse_routing_policy_rule_prefix,         0,                             0
-RoutingPolicyRule.IncomingInterface,    config_parse_routing_policy_rule_device,         0,                             0
-RoutingPolicyRule.OutgoingInterface,    config_parse_routing_policy_rule_device,         0,                             0
-RoutingPolicyRule.IPProtocol,           config_parse_routing_policy_rule_ip_protocol,    0,                             0
-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.Family,               config_parse_routing_policy_rule_family,         0,                             0
-Route.Gateway,                          config_parse_gateway,                            0,                             0
-Route.Destination,                      config_parse_destination,                        0,                             0
-Route.Source,                           config_parse_destination,                        0,                             0
-Route.Metric,                           config_parse_route_priority,                     0,                             0
-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_gateway_onlink,                     0,                             0
-Route.GatewayOnlink,                    config_parse_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.InitialCongestionWindow,          config_parse_tcp_window,                         0,                             0
-Route.InitialAdvertisedReceiveWindow,   config_parse_tcp_window,                         0,                             0
-Route.QuickAck,                         config_parse_quickack,                           0,                             0
-Route.FastOpenNoCookie,                 config_parse_fast_open_no_cookie,                0,                             0
-Route.TTLPropagate,                     config_parse_route_ttl_propagate,                0,                             0
-Route.MultiPathRoute,                   config_parse_multipath_route,                    0,                             0
-NextHop.Id,                             config_parse_nexthop_id,                         0,                             0
-NextHop.Gateway,                        config_parse_nexthop_gateway,                    0,                             0
-DHCPv4.ClientIdentifier,                config_parse_dhcp_client_identifier,             0,                             offsetof(Network, dhcp_client_identifier)
-DHCPv4.UseDNS,                          config_parse_bool,                               0,                             offsetof(Network, dhcp_use_dns)
-DHCPv4.RoutesToDNS,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_routes_to_dns)
-DHCPv4.UseNTP,                          config_parse_bool,                               0,                             offsetof(Network, dhcp_use_ntp)
-DHCPv4.UseSIP,                          config_parse_bool,                               0,                             offsetof(Network, dhcp_use_sip)
-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,                   0,                             offsetof(Network, dhcp_use_domains)
-DHCPv4.UseRoutes,                       config_parse_bool,                               0,                             offsetof(Network, dhcp_use_routes)
-DHCPv4.RequestOptions,                  config_parse_dhcp_request_options,               0,                             0
-DHCPv4.Anonymize,                       config_parse_bool,                               0,                             offsetof(Network, dhcp_anonymize)
-DHCPv4.SendHostname,                    config_parse_bool,                               0,                             offsetof(Network, dhcp_send_hostname)
-DHCPv4.Hostname,                        config_parse_hostname,                           0,                             offsetof(Network, dhcp_hostname)
-DHCPv4.RequestBroadcast,                config_parse_bool,                               0,                             offsetof(Network, dhcp_broadcast)
-DHCPv4.VendorClassIdentifier,           config_parse_string,                             0,                             offsetof(Network, dhcp_vendor_class_identifier)
-DHCPv4.MaxAttempts,                     config_parse_dhcp_max_attempts,                  0,                             0
-DHCPv4.UserClass,                       config_parse_dhcp_user_class,                    0,                             offsetof(Network, dhcp_user_class)
-DHCPv4.DUIDType,                        config_parse_duid_type,                          0,                             offsetof(Network, duid)
-DHCPv4.DUIDRawData,                     config_parse_duid_rawdata,                       0,                             offsetof(Network, duid)
-DHCPv4.RouteMetric,                     config_parse_unsigned,                           0,                             offsetof(Network, dhcp_route_metric)
-DHCPv4.RouteTable,                      config_parse_section_route_table,                0,                             0
-DHCPv4.UseTimezone,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_use_timezone)
-DHCPv4.IAID,                            config_parse_iaid,                               0,                             0
-DHCPv4.ListenPort,                      config_parse_uint16,                             0,                             offsetof(Network, dhcp_client_port)
-DHCPv4.SendRelease,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_send_release)
-DHCPv4.SendDecline,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_send_decline)
-DHCPv4.BlackList,                       config_parse_dhcp_black_listed_ip_address,       0,                             0
-DHCPv4.IPServiceType,                   config_parse_ip_service_type,                    0,                             offsetof(Network, ip_service_type)
-DHCPv4.SendOption,                      config_parse_dhcp_send_option,                   0,                             offsetof(Network, dhcp_client_send_options)
-DHCPv4.RouteMTUBytes,                   config_parse_mtu,                                AF_INET,                       offsetof(Network, dhcp_route_mtu)
-DHCPv6.UseDNS,                          config_parse_bool,                               0,                             offsetof(Network, dhcp6_use_dns)
-DHCPv6.UseNTP,                          config_parse_bool,                               0,                             offsetof(Network, dhcp6_use_ntp)
-DHCPv6.RapidCommit,                     config_parse_bool,                               0,                             offsetof(Network, rapid_commit)
-DHCPv6.ForceDHCPv6PDOtherInformation,   config_parse_bool,                               0,                             offsetof(Network, dhcp6_force_pd_other_information)
-DHCPv6.PrefixDelegationHint,            config_parse_dhcp6_pd_hint,                      0,                             0
-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.UseDNS,                    config_parse_bool,                               0,                             offsetof(Network, ipv6_accept_ra_use_dns)
-IPv6AcceptRA.UseDomains,                config_parse_dhcp_use_domains,                   0,                             offsetof(Network, ipv6_accept_ra_use_domains)
-IPv6AcceptRA.RouteTable,                config_parse_section_route_table,                0,                             0
-IPv6AcceptRA.BlackList,                 config_parse_ndisc_black_listed_prefix,          0,                             0
-DHCPServer.MaxLeaseTimeSec,             config_parse_sec,                                0,                             offsetof(Network, dhcp_server_max_lease_time_usec)
-DHCPServer.DefaultLeaseTimeSec,         config_parse_sec,                                0,                             offsetof(Network, dhcp_server_default_lease_time_usec)
-DHCPServer.EmitDNS,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_server_emit_dns)
-DHCPServer.DNS,                         config_parse_dhcp_server_dns,                    0,                             0
-DHCPServer.EmitNTP,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_server_emit_ntp)
-DHCPServer.NTP,                         config_parse_dhcp_server_ntp,                    0,                             0
-DHCPServer.EmitSIP,                     config_parse_bool,                               0,                             offsetof(Network, dhcp_server_emit_sip)
-DHCPServer.SIP,                         config_parse_dhcp_server_sip,                    0,                             0
-DHCPServer.EmitRouter,                  config_parse_bool,                               0,                             offsetof(Network, dhcp_server_emit_router)
-DHCPServer.EmitTimezone,                config_parse_bool,                               0,                             offsetof(Network, dhcp_server_emit_timezone)
-DHCPServer.Timezone,                    config_parse_timezone,                           0,                             offsetof(Network, dhcp_server_timezone)
-DHCPServer.PoolOffset,                  config_parse_uint32,                             0,                             offsetof(Network, dhcp_server_pool_offset)
-DHCPServer.PoolSize,                    config_parse_uint32,                             0,                             offsetof(Network, dhcp_server_pool_size)
-DHCPServer.SendOption,                  config_parse_dhcp_send_option,                   0,                             offsetof(Network, dhcp_server_send_options)
-Bridge.Cost,                            config_parse_uint32,                             0,                             offsetof(Network, cost)
-Bridge.UseBPDU,                         config_parse_tristate,                           0,                             offsetof(Network, use_bpdu)
-Bridge.HairPin,                         config_parse_tristate,                           0,                             offsetof(Network, hairpin)
-Bridge.FastLeave,                       config_parse_tristate,                           0,                             offsetof(Network, fast_leave)
-Bridge.AllowPortToBeRoot,               config_parse_tristate,                           0,                             offsetof(Network, allow_port_to_be_root)
-Bridge.UnicastFlood,                    config_parse_tristate,                           0,                             offsetof(Network, unicast_flood)
-Bridge.MulticastFlood,                  config_parse_tristate,                           0,                             offsetof(Network, multicast_flood)
-Bridge.MulticastToUnicast,              config_parse_tristate,                           0,                             offsetof(Network, multicast_to_unicast)
-Bridge.NeighborSuppression,             config_parse_tristate,                           0,                             offsetof(Network, neighbor_suppression)
-Bridge.Learning,                        config_parse_tristate,                           0,                             offsetof(Network, learning)
-Bridge.ProxyARP,                        config_parse_tristate,                           0,                             offsetof(Network, bridge_proxy_arp)
-Bridge.ProxyARPWiFi,                    config_parse_tristate,                           0,                             offsetof(Network, bridge_proxy_arp_wifi)
-Bridge.Priority,                        config_parse_bridge_port_priority,               0,                             offsetof(Network, priority)
-Bridge.MulticastRouter,                 config_parse_multicast_router,                   0,                             offsetof(Network, multicast_router)
-BridgeFDB.MACAddress,                   config_parse_fdb_hwaddr,                         0,                             0
-BridgeFDB.VLANId,                       config_parse_fdb_vlan_id,                        0,                             0
-BridgeFDB.Destination,                  config_parse_fdb_destination,                    0,                             0
-BridgeFDB.VNI,                          config_parse_fdb_vxlan_vni,                      0,                             0
-BridgeFDB.AssociatedWith,               config_parse_fdb_ntf_flags,                      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
-Network.IPv6PrefixDelegation,           config_parse_router_prefix_delegation,           0,                             0
-IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec,                                0,                             offsetof(Network, router_lifetime_usec)
-IPv6PrefixDelegation.Managed,           config_parse_bool,                               0,                             offsetof(Network, router_managed)
-IPv6PrefixDelegation.OtherInformation,  config_parse_bool,                               0,                             offsetof(Network, router_other_information)
-IPv6PrefixDelegation.RouterPreference,  config_parse_router_preference,                  0,                             0
-IPv6PrefixDelegation.EmitDNS,           config_parse_bool,                               0,                             offsetof(Network, router_emit_dns)
-IPv6PrefixDelegation.DNS,               config_parse_radv_dns,                           0,                             0
-IPv6PrefixDelegation.EmitDomains,       config_parse_bool,                               0,                             offsetof(Network, router_emit_domains)
-IPv6PrefixDelegation.Domains,           config_parse_radv_search_domains,                0,                             0
-IPv6PrefixDelegation.DNSLifetimeSec,    config_parse_sec,                                0,                             offsetof(Network, router_dns_lifetime_usec)
-IPv6Prefix.Prefix,                      config_parse_prefix,                             0,                             0
-IPv6Prefix.OnLink,                      config_parse_prefix_flags,                       0,                             0
-IPv6Prefix.AddressAutoconfiguration,    config_parse_prefix_flags,                       0,                             0
-IPv6Prefix.ValidLifetimeSec,            config_parse_prefix_lifetime,                    0,                             0
-IPv6Prefix.PreferredLifetimeSec,        config_parse_prefix_lifetime,                    0,                             0
-IPv6RoutePrefix.Route,                  config_parse_route_prefix,                       0,                             0
-IPv6RoutePrefix.LifetimeSec,            config_parse_route_prefix_lifetime,              0,                             0
-CAN.BitRate,                            config_parse_si_size,                            0,                             offsetof(Network, can_bitrate)
-CAN.SamplePoint,                        config_parse_permille,                           0,                             offsetof(Network, can_sample_point)
-CAN.RestartSec,                         config_parse_sec,                                0,                             offsetof(Network, can_restart_us)
-CAN.TripleSampling,                     config_parse_tristate,                           0,                             offsetof(Network, can_triple_sampling)
+Match.MACAddress,                       config_parse_hwaddrs,                                0,                             offsetof(Network, match_mac)
+Match.PermanentMACAddress,              config_parse_hwaddrs,                                0,                             offsetof(Network, match_permanent_mac)
+Match.Path,                             config_parse_match_strv,                             0,                             offsetof(Network, match_path)
+Match.Driver,                           config_parse_match_strv,                             0,                             offsetof(Network, match_driver)
+Match.Type,                             config_parse_match_strv,                             0,                             offsetof(Network, match_type)
+Match.WLANInterfaceType,                config_parse_match_strv,                             0,                             offsetof(Network, match_wlan_iftype)
+Match.SSID,                             config_parse_match_strv,                             0,                             offsetof(Network, match_ssid)
+Match.BSSID,                            config_parse_hwaddrs,                                0,                             offsetof(Network, match_bssid)
+Match.Name,                             config_parse_match_ifnames,                          1,                             offsetof(Network, match_name)
+Match.Property,                         config_parse_match_property,                         0,                             offsetof(Network, match_property)
+Match.Host,                             config_parse_net_condition,                          CONDITION_HOST,                offsetof(Network, conditions)
+Match.Virtualization,                   config_parse_net_condition,                          CONDITION_VIRTUALIZATION,      offsetof(Network, conditions)
+Match.KernelCommandLine,                config_parse_net_condition,                          CONDITION_KERNEL_COMMAND_LINE, offsetof(Network, conditions)
+Match.KernelVersion,                    config_parse_net_condition,                          CONDITION_KERNEL_VERSION,      offsetof(Network, conditions)
+Match.Architecture,                     config_parse_net_condition,                          CONDITION_ARCHITECTURE,        offsetof(Network, conditions)
+Link.MACAddress,                        config_parse_hwaddr,                                 0,                             offsetof(Network, mac)
+Link.MTUBytes,                          config_parse_mtu,                                    AF_UNSPEC,                     offsetof(Network, mtu)
+Link.ARP,                               config_parse_tristate,                               0,                             offsetof(Network, arp)
+Link.Multicast,                         config_parse_tristate,                               0,                             offsetof(Network, multicast)
+Link.AllMulticast,                      config_parse_tristate,                               0,                             offsetof(Network, allmulticast)
+Link.Unmanaged,                         config_parse_bool,                                   0,                             offsetof(Network, unmanaged)
+Link.RequiredForOnline,                 config_parse_required_for_online,                    0,                             0
+Network.Description,                    config_parse_string,                                 0,                             offsetof(Network, description)
+Network.Bridge,                         config_parse_ifname,                                 0,                             offsetof(Network, bridge_name)
+Network.Bond,                           config_parse_ifname,                                 0,                             offsetof(Network, bond_name)
+Network.VLAN,                           config_parse_stacked_netdev,                         NETDEV_KIND_VLAN,              offsetof(Network, stacked_netdev_names)
+Network.MACVLAN,                        config_parse_stacked_netdev,                         NETDEV_KIND_MACVLAN,           offsetof(Network, stacked_netdev_names)
+Network.MACVTAP,                        config_parse_stacked_netdev,                         NETDEV_KIND_MACVTAP,           offsetof(Network, stacked_netdev_names)
+Network.IPVLAN,                         config_parse_stacked_netdev,                         NETDEV_KIND_IPVLAN,            offsetof(Network, stacked_netdev_names)
+Network.IPVTAP,                         config_parse_stacked_netdev,                         NETDEV_KIND_IPVTAP,            offsetof(Network, stacked_netdev_names)
+Network.VXLAN,                          config_parse_stacked_netdev,                         NETDEV_KIND_VXLAN,             offsetof(Network, stacked_netdev_names)
+Network.L2TP,                           config_parse_stacked_netdev,                         NETDEV_KIND_L2TP,              offsetof(Network, stacked_netdev_names)
+Network.MACsec,                         config_parse_stacked_netdev,                         NETDEV_KIND_MACSEC,            offsetof(Network, stacked_netdev_names)
+Network.Tunnel,                         config_parse_stacked_netdev,                         _NETDEV_KIND_TUNNEL,           offsetof(Network, stacked_netdev_names)
+Network.Xfrm,                           config_parse_stacked_netdev,                         NETDEV_KIND_XFRM,              offsetof(Network, stacked_netdev_names)
+Network.VRF,                            config_parse_ifname,                                 0,                             offsetof(Network, vrf_name)
+Network.DHCP,                           config_parse_dhcp,                                   0,                             offsetof(Network, dhcp)
+Network.DHCPServer,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_server)
+Network.LinkLocalAddressing,            config_parse_link_local_address_family,              0,                             offsetof(Network, link_local)
+Network.IPv4LLRoute,                    config_parse_bool,                                   0,                             offsetof(Network, ipv4ll_route)
+Network.DefaultRouteOnDevice,           config_parse_bool,                                   0,                             offsetof(Network, default_route_on_device)
+Network.IPv6Token,                      config_parse_ipv6token,                              0,                             offsetof(Network, ipv6_token)
+Network.LLDP,                           config_parse_lldp_mode,                              0,                             offsetof(Network, lldp_mode)
+Network.EmitLLDP,                       config_parse_lldp_emit,                              0,                             offsetof(Network, lldp_emit)
+Network.Address,                        config_parse_address,                                0,                             0
+Network.Gateway,                        config_parse_gateway,                                0,                             0
+Network.Domains,                        config_parse_domains,                                0,                             0
+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)
+Network.MulticastDNS,                   config_parse_resolve_support,                        0,                             offsetof(Network, mdns)
+Network.DNSOverTLS,                     config_parse_dns_over_tls_mode,                      0,                             offsetof(Network, dns_over_tls_mode)
+Network.DNSSEC,                         config_parse_dnssec_mode,                            0,                             offsetof(Network, dnssec_mode)
+Network.DNSSECNegativeTrustAnchors,     config_parse_dnssec_negative_trust_anchors,          0,                             0
+Network.NTP,                            config_parse_ntp,                                    0,                             offsetof(Network, ntp)
+Network.IPForward,                      config_parse_address_family_with_kernel,             0,                             offsetof(Network, ip_forward)
+Network.IPMasquerade,                   config_parse_bool,                                   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.IPv6DuplicateAddressDetection,  config_parse_int,                                    0,                             offsetof(Network, ipv6_dad_transmits)
+Network.IPv6HopLimit,                   config_parse_int,                                    0,                             offsetof(Network, ipv6_hop_limit)
+Network.IPv6ProxyNDP,                   config_parse_tristate,                               0,                             offsetof(Network, ipv6_proxy_ndp)
+Network.IPv6MTUBytes,                   config_parse_mtu,                                    AF_INET6,                      offsetof(Network, ipv6_mtu)
+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.ProxyARP,                       config_parse_tristate,                               0,                             offsetof(Network, proxy_arp)
+Network.IPv6ProxyNDPAddress,            config_parse_ipv6_proxy_ndp_address,                 0,                             0
+Network.BindCarrier,                    config_parse_strv,                                   0,                             offsetof(Network, bind_carrier)
+Network.ConfigureWithoutCarrier,        config_parse_bool,                                   0,                             offsetof(Network, configure_without_carrier)
+Network.IgnoreCarrierLoss,              config_parse_bool,                                   0,                             offsetof(Network, ignore_carrier_loss)
+Network.KeepConfiguration,              config_parse_keep_configuration,                     0,                             offsetof(Network, keep_configuration)
+Address.Address,                        config_parse_address,                                0,                             0
+Address.Peer,                           config_parse_address,                                0,                             0
+Address.Broadcast,                      config_parse_broadcast,                              0,                             0
+Address.Label,                          config_parse_label,                                  0,                             0
+Address.PreferredLifetime,              config_parse_lifetime,                               0,                             0
+Address.HomeAddress,                    config_parse_address_flags,                          0,                             0
+Address.ManageTemporaryAddress,         config_parse_address_flags,                          0,                             0
+Address.PrefixRoute,                    config_parse_address_flags,                          0,                             0 /* deprecated */
+Address.AddPrefixRoute,                 config_parse_address_flags,                          0,                             0
+Address.AutoJoin,                       config_parse_address_flags,                          0,                             0
+Address.DuplicateAddressDetection,      config_parse_duplicate_address_detection,            0,                             0
+Address.Scope,                          config_parse_address_scope,                          0,                             0
+IPv6AddressLabel.Prefix,                config_parse_address_label_prefix,                   0,                             0
+IPv6AddressLabel.Label,                 config_parse_address_label,                          0,                             0
+Neighbor.Address,                       config_parse_neighbor_address,                       0,                             0
+Neighbor.LinkLayerAddress,              config_parse_neighbor_lladdr,                        0,                             0
+Neighbor.MACAddress,                    config_parse_neighbor_hwaddr,                        0,                             0 /* deprecated */
+RoutingPolicyRule.TypeOfService,        config_parse_routing_policy_rule_tos,                0,                             0
+RoutingPolicyRule.Priority,             config_parse_routing_policy_rule_priority,           0,                             0
+RoutingPolicyRule.Table,                config_parse_routing_policy_rule_table,              0,                             0
+RoutingPolicyRule.FirewallMark,         config_parse_routing_policy_rule_fwmark_mask,        0,                             0
+RoutingPolicyRule.From,                 config_parse_routing_policy_rule_prefix,             0,                             0
+RoutingPolicyRule.To,                   config_parse_routing_policy_rule_prefix,             0,                             0
+RoutingPolicyRule.IncomingInterface,    config_parse_routing_policy_rule_device,             0,                             0
+RoutingPolicyRule.OutgoingInterface,    config_parse_routing_policy_rule_device,             0,                             0
+RoutingPolicyRule.IPProtocol,           config_parse_routing_policy_rule_ip_protocol,        0,                             0
+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.Family,               config_parse_routing_policy_rule_family,             0,                             0
+RoutingPolicyRule.User,                 config_parse_routing_policy_rule_uid_range,          0,                             0
+RoutingPolicyRule.SuppressPrefixLength, config_parse_routing_policy_rule_suppress_prefixlen, 0,                             0
+Route.Gateway,                          config_parse_gateway,                                0,                             0
+Route.Destination,                      config_parse_destination,                            0,                             0
+Route.Source,                           config_parse_destination,                            0,                             0
+Route.Metric,                           config_parse_route_priority,                         0,                             0
+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_gateway_onlink,                         0,                             0
+Route.GatewayOnlink,                    config_parse_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.InitialCongestionWindow,          config_parse_tcp_window,                             0,                             0
+Route.InitialAdvertisedReceiveWindow,   config_parse_tcp_window,                             0,                             0
+Route.QuickAck,                         config_parse_quickack,                               0,                             0
+Route.FastOpenNoCookie,                 config_parse_fast_open_no_cookie,                    0,                             0
+Route.TTLPropagate,                     config_parse_route_ttl_propagate,                    0,                             0
+Route.MultiPathRoute,                   config_parse_multipath_route,                        0,                             0
+NextHop.Id,                             config_parse_nexthop_id,                             0,                             0
+NextHop.Gateway,                        config_parse_nexthop_gateway,                        0,                             0
+DHCPv4.ClientIdentifier,                config_parse_dhcp_client_identifier,                 0,                             offsetof(Network, dhcp_client_identifier)
+DHCPv4.UseDNS,                          config_parse_bool,                                   0,                             offsetof(Network, dhcp_use_dns)
+DHCPv4.RoutesToDNS,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_routes_to_dns)
+DHCPv4.UseNTP,                          config_parse_bool,                                   0,                             offsetof(Network, dhcp_use_ntp)
+DHCPv4.UseSIP,                          config_parse_bool,                                   0,                             offsetof(Network, dhcp_use_sip)
+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,                       0,                             offsetof(Network, dhcp_use_domains)
+DHCPv4.UseRoutes,                       config_parse_bool,                                   0,                             offsetof(Network, dhcp_use_routes)
+DHCPv4.RequestOptions,                  config_parse_dhcp_request_options,                   0,                             0
+DHCPv4.Anonymize,                       config_parse_bool,                                   0,                             offsetof(Network, dhcp_anonymize)
+DHCPv4.SendHostname,                    config_parse_bool,                                   0,                             offsetof(Network, dhcp_send_hostname)
+DHCPv4.Hostname,                        config_parse_hostname,                               0,                             offsetof(Network, dhcp_hostname)
+DHCPv4.RequestBroadcast,                config_parse_bool,                                   0,                             offsetof(Network, dhcp_broadcast)
+DHCPv4.VendorClassIdentifier,           config_parse_string,                                 0,                             offsetof(Network, dhcp_vendor_class_identifier)
+DHCPv4.MaxAttempts,                     config_parse_dhcp_max_attempts,                      0,                             0
+DHCPv4.UserClass,                       config_parse_dhcp_user_class,                        0,                             offsetof(Network, dhcp_user_class)
+DHCPv4.DUIDType,                        config_parse_duid_type,                              0,                             offsetof(Network, duid)
+DHCPv4.DUIDRawData,                     config_parse_duid_rawdata,                           0,                             offsetof(Network, duid)
+DHCPv4.RouteMetric,                     config_parse_unsigned,                               0,                             offsetof(Network, dhcp_route_metric)
+DHCPv4.RouteTable,                      config_parse_section_route_table,                    0,                             0
+DHCPv4.UseTimezone,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_use_timezone)
+DHCPv4.IAID,                            config_parse_iaid,                                   0,                             0
+DHCPv4.ListenPort,                      config_parse_uint16,                                 0,                             offsetof(Network, dhcp_client_port)
+DHCPv4.SendRelease,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_send_release)
+DHCPv4.SendDecline,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_send_decline)
+DHCPv4.BlackList,                       config_parse_dhcp_black_listed_ip_address,           0,                             0
+DHCPv4.IPServiceType,                   config_parse_ip_service_type,                        0,                             offsetof(Network, ip_service_type)
+DHCPv4.SendOption,                      config_parse_dhcp_send_option,                       0,                             offsetof(Network, dhcp_client_send_options)
+DHCPv4.RouteMTUBytes,                   config_parse_mtu,                                    AF_INET,                       offsetof(Network, dhcp_route_mtu)
+DHCPv6.UseDNS,                          config_parse_bool,                                   0,                             offsetof(Network, dhcp6_use_dns)
+DHCPv6.UseNTP,                          config_parse_bool,                                   0,                             offsetof(Network, dhcp6_use_ntp)
+DHCPv6.RapidCommit,                     config_parse_bool,                                   0,                             offsetof(Network, rapid_commit)
+DHCPv6.ForceDHCPv6PDOtherInformation,   config_parse_bool,                                   0,                             offsetof(Network, dhcp6_force_pd_other_information)
+DHCPv6.PrefixDelegationHint,            config_parse_dhcp6_pd_hint,                          0,                             0
+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.UseDNS,                    config_parse_bool,                                   0,                             offsetof(Network, ipv6_accept_ra_use_dns)
+IPv6AcceptRA.UseDomains,                config_parse_dhcp_use_domains,                       0,                             offsetof(Network, ipv6_accept_ra_use_domains)
+IPv6AcceptRA.RouteTable,                config_parse_section_route_table,                    0,                             0
+IPv6AcceptRA.BlackList,                 config_parse_ndisc_black_listed_prefix,              0,                             0
+DHCPServer.MaxLeaseTimeSec,             config_parse_sec,                                    0,                             offsetof(Network, dhcp_server_max_lease_time_usec)
+DHCPServer.DefaultLeaseTimeSec,         config_parse_sec,                                    0,                             offsetof(Network, dhcp_server_default_lease_time_usec)
+DHCPServer.EmitDNS,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_server_emit_dns)
+DHCPServer.DNS,                         config_parse_dhcp_server_dns,                        0,                             0
+DHCPServer.EmitNTP,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_server_emit_ntp)
+DHCPServer.NTP,                         config_parse_dhcp_server_ntp,                        0,                             0
+DHCPServer.EmitSIP,                     config_parse_bool,                                   0,                             offsetof(Network, dhcp_server_emit_sip)
+DHCPServer.SIP,                         config_parse_dhcp_server_sip,                        0,                             0
+DHCPServer.EmitRouter,                  config_parse_bool,                                   0,                             offsetof(Network, dhcp_server_emit_router)
+DHCPServer.EmitTimezone,                config_parse_bool,                                   0,                             offsetof(Network, dhcp_server_emit_timezone)
+DHCPServer.Timezone,                    config_parse_timezone,                               0,                             offsetof(Network, dhcp_server_timezone)
+DHCPServer.PoolOffset,                  config_parse_uint32,                                 0,                             offsetof(Network, dhcp_server_pool_offset)
+DHCPServer.PoolSize,                    config_parse_uint32,                                 0,                             offsetof(Network, dhcp_server_pool_size)
+DHCPServer.SendOption,                  config_parse_dhcp_send_option,                       0,                             offsetof(Network, dhcp_server_send_options)
+Bridge.Cost,                            config_parse_uint32,                                 0,                             offsetof(Network, cost)
+Bridge.UseBPDU,                         config_parse_tristate,                               0,                             offsetof(Network, use_bpdu)
+Bridge.HairPin,                         config_parse_tristate,                               0,                             offsetof(Network, hairpin)
+Bridge.FastLeave,                       config_parse_tristate,                               0,                             offsetof(Network, fast_leave)
+Bridge.AllowPortToBeRoot,               config_parse_tristate,                               0,                             offsetof(Network, allow_port_to_be_root)
+Bridge.UnicastFlood,                    config_parse_tristate,                               0,                             offsetof(Network, unicast_flood)
+Bridge.MulticastFlood,                  config_parse_tristate,                               0,                             offsetof(Network, multicast_flood)
+Bridge.MulticastToUnicast,              config_parse_tristate,                               0,                             offsetof(Network, multicast_to_unicast)
+Bridge.NeighborSuppression,             config_parse_tristate,                               0,                             offsetof(Network, neighbor_suppression)
+Bridge.Learning,                        config_parse_tristate,                               0,                             offsetof(Network, learning)
+Bridge.ProxyARP,                        config_parse_tristate,                               0,                             offsetof(Network, bridge_proxy_arp)
+Bridge.ProxyARPWiFi,                    config_parse_tristate,                               0,                             offsetof(Network, bridge_proxy_arp_wifi)
+Bridge.Priority,                        config_parse_bridge_port_priority,                   0,                             offsetof(Network, priority)
+Bridge.MulticastRouter,                 config_parse_multicast_router,                       0,                             offsetof(Network, multicast_router)
+BridgeFDB.MACAddress,                   config_parse_fdb_hwaddr,                             0,                             0
+BridgeFDB.VLANId,                       config_parse_fdb_vlan_id,                            0,                             0
+BridgeFDB.Destination,                  config_parse_fdb_destination,                        0,                             0
+BridgeFDB.VNI,                          config_parse_fdb_vxlan_vni,                          0,                             0
+BridgeFDB.AssociatedWith,               config_parse_fdb_ntf_flags,                          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
+Network.IPv6PrefixDelegation,           config_parse_router_prefix_delegation,               0,                             0
+IPv6PrefixDelegation.RouterLifetimeSec, config_parse_sec,                                    0,                             offsetof(Network, router_lifetime_usec)
+IPv6PrefixDelegation.Managed,           config_parse_bool,                                   0,                             offsetof(Network, router_managed)
+IPv6PrefixDelegation.OtherInformation,  config_parse_bool,                                   0,                             offsetof(Network, router_other_information)
+IPv6PrefixDelegation.RouterPreference,  config_parse_router_preference,                      0,                             0
+IPv6PrefixDelegation.EmitDNS,           config_parse_bool,                                   0,                             offsetof(Network, router_emit_dns)
+IPv6PrefixDelegation.DNS,               config_parse_radv_dns,                               0,                             0
+IPv6PrefixDelegation.EmitDomains,       config_parse_bool,                                   0,                             offsetof(Network, router_emit_domains)
+IPv6PrefixDelegation.Domains,           config_parse_radv_search_domains,                    0,                             0
+IPv6PrefixDelegation.DNSLifetimeSec,    config_parse_sec,                                    0,                             offsetof(Network, router_dns_lifetime_usec)
+IPv6Prefix.Prefix,                      config_parse_prefix,                                 0,                             0
+IPv6Prefix.OnLink,                      config_parse_prefix_flags,                           0,                             0
+IPv6Prefix.AddressAutoconfiguration,    config_parse_prefix_flags,                           0,                             0
+IPv6Prefix.ValidLifetimeSec,            config_parse_prefix_lifetime,                        0,                             0
+IPv6Prefix.PreferredLifetimeSec,        config_parse_prefix_lifetime,                        0,                             0
+IPv6RoutePrefix.Route,                  config_parse_route_prefix,                           0,                             0
+IPv6RoutePrefix.LifetimeSec,            config_parse_route_prefix_lifetime,                  0,                             0
+CAN.BitRate,                            config_parse_si_uint64,                              0,                             offsetof(Network, can_bitrate)
+CAN.SamplePoint,                        config_parse_permille,                               0,                             offsetof(Network, can_sample_point)
+CAN.RestartSec,                         config_parse_sec,                                    0,                             offsetof(Network, can_restart_us)
+CAN.TripleSampling,                     config_parse_tristate,                               0,                             offsetof(Network, can_triple_sampling)
 TrafficControlQueueingDiscipline.Parent,                                     config_parse_tc_qdiscs_parent,                               0, 0
 TrafficControlQueueingDiscipline.NetworkEmulatorDelaySec,                    config_parse_tc_network_emulator_delay,                      0, 0
 TrafficControlQueueingDiscipline.NetworkEmulatorDelayJitterSec,              config_parse_tc_network_emulator_delay,                      0, 0
index e1c1c17241df4ad937e5bff8be921af79ef6c0e2..1cfbd0b6b6dfaa2b8872f145149cd95a296f4733 100644 (file)
@@ -194,7 +194,7 @@ struct Network {
         uint32_t br_untagged_bitmap[BRIDGE_VLAN_BITMAP_LEN];
 
         /* CAN support */
-        size_t can_bitrate;
+        uint64_t can_bitrate;
         unsigned can_sample_point;
         usec_t can_restart_us;
         int can_triple_sampling;
index e7d09c4ad94a0689efba763565580cc2e8a08135..641f8840845c66d76c8468df0d810f9d25f1808e 100644 (file)
@@ -7,6 +7,7 @@
 #include "alloc-util.h"
 #include "conf-parser.h"
 #include "fileio.h"
+#include "format-util.h"
 #include "ip-protocol-list.h"
 #include "networkd-routing-policy-rule.h"
 #include "netlink-util.h"
@@ -16,6 +17,7 @@
 #include "socket-util.h"
 #include "string-util.h"
 #include "strv.h"
+#include "user-util.h"
 
 int routing_policy_rule_new(RoutingPolicyRule **ret) {
         RoutingPolicyRule *rule;
@@ -26,6 +28,9 @@ int routing_policy_rule_new(RoutingPolicyRule **ret) {
 
         *rule = (RoutingPolicyRule) {
                 .table = RT_TABLE_MAIN,
+                .uid_range.start = UID_INVALID,
+                .uid_range.end = UID_INVALID,
+                .suppress_prefixlen = -1,
         };
 
         *ret = rule;
@@ -93,6 +98,8 @@ static int routing_policy_rule_copy(RoutingPolicyRule *dest, RoutingPolicyRule *
         dest->protocol = src->protocol;
         dest->sport = src->sport;
         dest->dport = src->dport;
+        dest->uid_range = src->uid_range;
+        dest->suppress_prefixlen = src->suppress_prefixlen;
 
         return 0;
 }
@@ -118,10 +125,12 @@ static void routing_policy_rule_hash_func(const RoutingPolicyRule *rule, struct
                 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->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);
 
                 if (rule->iif)
                         siphash24_compress(rule->iif, strlen(rule->iif), state);
@@ -186,6 +195,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro
                 if (r != 0)
                         return r;
 
+                r = CMP(a->suppress_prefixlen, b->suppress_prefixlen);
+                if (r != 0)
+                        return r;
+
                 r = CMP(a->protocol, b->protocol);
                 if (r != 0)
                         return r;
@@ -198,6 +211,10 @@ static int routing_policy_rule_compare_func(const RoutingPolicyRule *a, const Ro
                 if (r != 0)
                         return r;
 
+                r = memcmp(&a->uid_range, &b->uid_range, sizeof(a->uid_range));
+                if (r != 0)
+                        return r;
+
                 r = strcmp_ptr(a->iif, b->iif);
                 if (r != 0)
                         return r;
@@ -554,12 +571,24 @@ int routing_policy_rule_configure(RoutingPolicyRule *rule, Link *link, link_netl
                         return log_link_error_errno(link, r, "Could not append FRA_DPORT_RANGE attribute: %m");
         }
 
+        if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) {
+                r = sd_netlink_message_append_data(m, FRA_UID_RANGE, &rule->uid_range, sizeof(rule->uid_range));
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not append FRA_UID_RANGE attribute: %m");
+        }
+
         if (rule->invert_rule) {
                 r = sd_rtnl_message_routing_policy_rule_set_flags(m, FIB_RULE_INVERT);
                 if (r < 0)
                         return log_link_error_errno(link, r, "Could not append FIB_RULE_INVERT attribute: %m");
         }
 
+        if (rule->suppress_prefixlen >= 0) {
+                r = sd_netlink_message_append_u32(m, FRA_SUPPRESS_PREFIXLEN, (uint32_t) rule->suppress_prefixlen);
+                if (r < 0)
+                        return log_link_error_errno(link, r, "Could not append FRA_SUPPRESS_PREFIXLEN attribute: %m");
+        }
+
         rule->link = link;
 
         r = netlink_call_async(link->manager->rtnl, NULL, m,
@@ -1056,6 +1085,93 @@ int config_parse_routing_policy_rule_family(
         return 0;
 }
 
+int config_parse_routing_policy_rule_uid_range(
+                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;
+        uid_t start, end;
+        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 r;
+
+        r = get_user_creds(&rvalue, &start, NULL, NULL, NULL, 0);
+        if (r >= 0)
+                end = start;
+        else {
+                r = parse_uid_range(rvalue, &start, &end);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Invalid uid or uid range '%s', ignoring: %m", rvalue);
+                        return 0;
+                }
+        }
+
+        n->uid_range.start = start;
+        n->uid_range.end = end;
+        n = NULL;
+
+        return 0;
+}
+
+int config_parse_routing_policy_rule_suppress_prefixlen(
+                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 r;
+
+        r = parse_ip_prefix_length(rvalue, &n->suppress_prefixlen);
+        if (r == -ERANGE) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Prefix length outside of valid range 0-128, ignoring: %s", rvalue);
+                return 0;
+        }
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse RPDB rule suppress_prefixlen, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        n = NULL;
+
+        return 0;
+}
+
 static int routing_policy_rule_read_full_file(const char *state_file, char **ret) {
         _cleanup_free_ char *s = NULL;
         size_t size;
@@ -1170,6 +1286,21 @@ int routing_policy_serialize_rules(Set *rules, FILE *f) {
                         space = true;
                 }
 
+                if (rule->uid_range.start != UID_INVALID && rule->uid_range.end != UID_INVALID) {
+                        assert_cc(sizeof(uid_t) == sizeof(uint32_t));
+                        fprintf(f, "%suidrange="UID_FMT"-"UID_FMT,
+                                space ? " " : "",
+                                rule->uid_range.start, rule->uid_range.end);
+                        space = true;
+                }
+
+                if (rule->suppress_prefixlen >= 0) {
+                        fprintf(f, "%ssuppress_prefixlen=%d",
+                                space ? " " : "",
+                                rule->suppress_prefixlen);
+                        space = true;
+                }
+
                 fprintf(f, "%stable=%"PRIu32 "\n",
                         space ? " " : "",
                         rule->table);
@@ -1269,14 +1400,12 @@ int routing_policy_load_rules(const char *state_file, Set **rules) {
                                         continue;
                                 }
                         } else if (streq(a, "fwmark")) {
-
                                 r = parse_fwmark_fwmask(b, &rule->fwmark, &rule->fwmask);
                                 if (r < 0) {
                                         log_error_errno(r, "Failed to parse RPDB rule firewall mark or mask, ignoring: %s", a);
                                         continue;
                                 }
                         } else if (streq(a, "iif")) {
-
                                 if (free_and_strdup(&rule->iif, b) < 0)
                                         return log_oom();
 
@@ -1291,26 +1420,44 @@ int routing_policy_load_rules(const char *state_file, Set **rules) {
                                         continue;
                                 }
                         } else if (streq(a, "sourceport")) {
-
                                 r = parse_ip_port_range(b, &low, &high);
                                 if (r < 0) {
-                                        log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment:'%s'", b);
+                                        log_error_errno(r, "Invalid routing policy rule source port range, ignoring assignment: '%s'", b);
                                         continue;
                                 }
 
                                 rule->sport.start = low;
                                 rule->sport.end = high;
-
                         } else if (streq(a, "destinationport")) {
-
                                 r = parse_ip_port_range(b, &low, &high);
                                 if (r < 0) {
-                                        log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment:'%s'", b);
+                                        log_error_errno(r, "Invalid routing policy rule destination port range, ignoring assignment: '%s'", b);
                                         continue;
                                 }
 
                                 rule->dport.start = low;
                                 rule->dport.end = high;
+                        } else if (streq(a, "uidrange")) {
+                                uid_t lower, upper;
+
+                                r = parse_uid_range(b, &lower, &upper);
+                                if (r < 0) {
+                                        log_error_errno(r, "Invalid routing policy rule uid range, ignoring assignment: '%s'", b);
+                                        continue;
+                                }
+
+                                rule->uid_range.start = lower;
+                                rule->uid_range.end = upper;
+                        } else if (streq(a, "suppress_prefixlen")) {
+                                r = parse_ip_prefix_length(b, &rule->suppress_prefixlen);
+                                if (r == -ERANGE) {
+                                        log_error_errno(r, "Prefix length outside of valid range 0-128, ignoring: %s", b);
+                                        continue;
+                                }
+                                if (r < 0) {
+                                        log_error_errno(r, "Failed to parse RPDB rule suppress_prefixlen, ignoring: %s", b);
+                                        continue;
+                                }
                         }
                 }
 
index 6b8e3102275e48611f154224a66a872cf977e2c8..21ca0e8021e2cd81ce0d395ec2e085b155383101 100644 (file)
@@ -49,6 +49,9 @@ struct RoutingPolicyRule {
 
         struct fib_rule_port_range sport;
         struct fib_rule_port_range dport;
+        struct fib_rule_uid_range uid_range;
+
+        int suppress_prefixlen;
 
         LIST_FIELDS(RoutingPolicyRule, rules);
 };
@@ -79,3 +82,5 @@ 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);
 CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_family);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_uid_range);
+CONFIG_PARSER_PROTOTYPE(config_parse_routing_policy_rule_suppress_prefixlen);
index 6afc31d824b345a3deecd9456a6caf677c9be7dc..a862355a64598d32737dd8e99b0713ccdfa70a1d 100644 (file)
@@ -906,7 +906,7 @@ static int mount_inaccessible(const char *dest, CustomMount *m) {
 
         r = mount_verbose(m->graceful ? LOG_DEBUG : LOG_ERR, NULL, where, NULL, MS_BIND|MS_RDONLY|MS_REMOUNT, NULL);
         if (r < 0) {
-                umount_verbose(where);
+                (void) umount_verbose(where);
                 return m->graceful ? 0 : r;
         }
 
index ef6d573bb3f6a7fbfcc57fc31e7e7922c0386933..2a63315a4c8803c8cbefaf4495953109ea54d30c 100644 (file)
@@ -3309,10 +3309,12 @@ static int outer_child(
 
                 r = dissected_image_mount(dissected_image, directory, arg_uid_shift,
                                           DISSECT_IMAGE_MOUNT_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|
-                                          (arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0)|
+                                          (arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK)|
                                           (arg_start_mode == START_BOOT ? DISSECT_IMAGE_VALIDATE_OS : 0));
+                if (r == -EUCLEAN)
+                        return log_error_errno(r, "File system check for image failed: %m");
                 if (r < 0)
-                        return r;
+                        return log_error_errno(r, "Failed to mount image root file system: %m");
         }
 
         r = determine_uid_shift(directory);
@@ -3396,9 +3398,11 @@ 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(dissected_image, directory, arg_uid_shift,
-                                          DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : 0));
+                                          DISSECT_IMAGE_MOUNT_NON_ROOT_ONLY|DISSECT_IMAGE_DISCARD_ON_LOOP|(arg_read_only ? DISSECT_IMAGE_READ_ONLY : DISSECT_IMAGE_FSCK));
+                if (r == -EUCLEAN)
+                        return log_error_errno(r, "File system check for image failed: %m");
                 if (r < 0)
-                        return r;
+                        return log_error_errno(r, "Failed to mount image file system: %m");
         }
 
         if (arg_unified_cgroup_hierarchy == CGROUP_UNIFIED_UNKNOWN) {
index ee4907f73f6249a5621baf613254cc820893a299..951989cbb6f818725958454fce39403d226c7ef7 100644 (file)
@@ -1,6 +1,4 @@
-/***
-  SPDX-License-Identifier: LGPL-2.1+
-***/
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include <fcntl.h>
 #include <sys/prctl.h>
diff --git a/src/partition/meson.build b/src/partition/meson.build
new file mode 100644 (file)
index 0000000..d0c111a
--- /dev/null
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: LGPL-2.1+
+
+systemd_repart_sources = files('''
+        repart.c
+'''.split())
diff --git a/src/partition/repart.c b/src/partition/repart.c
new file mode 100644 (file)
index 0000000..58fb9c7
--- /dev/null
@@ -0,0 +1,3026 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#if HAVE_VALGRIND_MEMCHECK_H
+#include <valgrind/memcheck.h>
+#endif
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <libfdisk.h>
+#include <linux/fs.h>
+#include <linux/loop.h>
+#include <sys/file.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#include "sd-id128.h"
+
+#include "alloc-util.h"
+#include "blkid-util.h"
+#include "blockdev-util.h"
+#include "btrfs-util.h"
+#include "conf-files.h"
+#include "conf-parser.h"
+#include "def.h"
+#include "efivars.h"
+#include "errno-util.h"
+#include "fd-util.h"
+#include "format-table.h"
+#include "format-util.h"
+#include "fs-util.h"
+#include "gpt.h"
+#include "id128-util.h"
+#include "list.h"
+#include "locale-util.h"
+#include "main-func.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "proc-cmdline.h"
+#include "sort-util.h"
+#include "stat-util.h"
+#include "stdio-util.h"
+#include "string-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "utf8.h"
+
+/* Note: When growing and placing new partitions we always align to 4K sector size. It's how newer hard disks
+ * are designed, and if everything is aligned to that performance is best. And for older hard disks with 512B
+ * sector size devices were generally assumed to have an even number of sectors, hence at the worst we'll
+ * waste 3K per partition, which is probably fine. */
+
+static enum {
+        EMPTY_REFUSE,   /* refuse empty disks, never create a partition table */
+        EMPTY_ALLOW,    /* allow empty disks, create partition table if necessary */
+        EMPTY_REQUIRE,  /* require an empty disk, create a partition table */
+        EMPTY_FORCE,    /* make disk empty, erase everything, create a partition table always */
+} arg_empty = EMPTY_REFUSE;
+
+static bool arg_dry_run = true;
+static const char *arg_node = NULL;
+static char *arg_root = NULL;
+static char *arg_definitions = NULL;
+static bool arg_discard = true;
+static bool arg_can_factory_reset = false;
+static int arg_factory_reset = -1;
+static sd_id128_t arg_seed = SD_ID128_NULL;
+static bool arg_randomize = false;
+static int arg_pretty = -1;
+
+STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
+
+typedef struct Partition Partition;
+typedef struct FreeArea FreeArea;
+typedef struct Context Context;
+
+struct Partition {
+        char *definition_path;
+
+        sd_id128_t type_uuid;
+        sd_id128_t current_uuid, new_uuid;
+        char *current_label, *new_label;
+
+        bool dropped;
+        bool factory_reset;
+        int32_t priority;
+
+        uint32_t weight, padding_weight;
+
+        uint64_t current_size, new_size;
+        uint64_t size_min, size_max;
+
+        uint64_t current_padding, new_padding;
+        uint64_t padding_min, padding_max;
+
+        uint64_t partno;
+        uint64_t offset;
+
+        struct fdisk_partition *current_partition;
+        struct fdisk_partition *new_partition;
+        FreeArea *padding_area;
+        FreeArea *allocated_to_area;
+
+        LIST_FIELDS(Partition, partitions);
+};
+
+#define PARTITION_IS_FOREIGN(p) (!(p)->definition_path)
+#define PARTITION_EXISTS(p) (!!(p)->current_partition)
+
+struct FreeArea {
+        Partition *after;
+        uint64_t size;
+        uint64_t allocated;
+};
+
+struct Context {
+        LIST_HEAD(Partition, partitions);
+        size_t n_partitions;
+
+        FreeArea **free_areas;
+        size_t n_free_areas, n_allocated_free_areas;
+
+        uint64_t start, end, total;
+
+        struct fdisk_context *fdisk_context;
+
+        sd_id128_t seed;
+};
+
+static uint64_t round_down_size(uint64_t v, uint64_t p) {
+        return (v / p) * p;
+}
+
+static uint64_t round_up_size(uint64_t v, uint64_t p) {
+
+        v = DIV_ROUND_UP(v, p);
+
+        if (v > UINT64_MAX / p)
+                return UINT64_MAX; /* overflow */
+
+        return v * p;
+}
+
+static Partition *partition_new(void) {
+        Partition *p;
+
+        p = new(Partition, 1);
+        if (!p)
+                return NULL;
+
+        *p = (Partition) {
+                .weight = 1000,
+                .padding_weight = 0,
+                .current_size = UINT64_MAX,
+                .new_size = UINT64_MAX,
+                .size_min = UINT64_MAX,
+                .size_max = UINT64_MAX,
+                .current_padding = UINT64_MAX,
+                .new_padding = UINT64_MAX,
+                .padding_min = UINT64_MAX,
+                .padding_max = UINT64_MAX,
+                .partno = UINT64_MAX,
+                .offset = UINT64_MAX,
+        };
+
+        return p;
+}
+
+static Partition* partition_free(Partition *p) {
+        if (!p)
+                return NULL;
+
+        free(p->current_label);
+        free(p->new_label);
+        free(p->definition_path);
+
+        if (p->current_partition)
+                fdisk_unref_partition(p->current_partition);
+        if (p->new_partition)
+                fdisk_unref_partition(p->new_partition);
+
+        return mfree(p);
+}
+
+static Partition* partition_unlink_and_free(Context *context, Partition *p) {
+        if (!p)
+                return NULL;
+
+        LIST_REMOVE(partitions, context->partitions, p);
+
+        assert(context->n_partitions > 0);
+        context->n_partitions--;
+
+        return partition_free(p);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Partition*, partition_free);
+
+static Context *context_new(sd_id128_t seed) {
+        Context *context;
+
+        context = new(Context, 1);
+        if (!context)
+                return NULL;
+
+        *context = (Context) {
+                .start = UINT64_MAX,
+                .end = UINT64_MAX,
+                .total = UINT64_MAX,
+                .seed = seed,
+        };
+
+        return context;
+}
+
+static void context_free_free_areas(Context *context) {
+        assert(context);
+
+        for (size_t i = 0; i < context->n_free_areas; i++)
+                free(context->free_areas[i]);
+
+        context->free_areas = mfree(context->free_areas);
+        context->n_free_areas = 0;
+        context->n_allocated_free_areas = 0;
+}
+
+static Context *context_free(Context *context) {
+        if (!context)
+                return NULL;
+
+        while (context->partitions)
+                partition_unlink_and_free(context, context->partitions);
+        assert(context->n_partitions == 0);
+
+        context_free_free_areas(context);
+
+        if (context->fdisk_context)
+                fdisk_unref_context(context->fdisk_context);
+
+        return mfree(context);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free);
+
+static int context_add_free_area(
+                Context *context,
+                uint64_t size,
+                Partition *after) {
+
+        FreeArea *a;
+
+        assert(context);
+        assert(!after || !after->padding_area);
+
+        if (!GREEDY_REALLOC(context->free_areas, context->n_allocated_free_areas, context->n_free_areas + 1))
+                return -ENOMEM;
+
+        a = new(FreeArea, 1);
+        if (!a)
+                return -ENOMEM;
+
+        *a = (FreeArea) {
+                .size = size,
+                .after = after,
+        };
+
+        context->free_areas[context->n_free_areas++] = a;
+
+        if (after)
+                after->padding_area = a;
+
+        return 0;
+}
+
+static bool context_drop_one_priority(Context *context) {
+        int32_t priority = 0;
+        Partition *p;
+        bool exists = false;
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->dropped)
+                        continue;
+                if (p->priority < priority)
+                        continue;
+                if (p->priority == priority) {
+                        exists = exists || PARTITION_EXISTS(p);
+                        continue;
+                }
+
+                priority = p->priority;
+                exists = PARTITION_EXISTS(p);
+        }
+
+        /* Refuse to drop partitions with 0 or negative priorities or partitions of priorities that have at
+         * least one existing priority */
+        if (priority <= 0 || exists)
+                return false;
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->priority < priority)
+                        continue;
+
+                if (p->dropped)
+                        continue;
+
+                p->dropped = true;
+                log_info("Can't fit partition %s of priority %" PRIi32 ", dropping.", p->definition_path, p->priority);
+        }
+
+        return true;
+}
+
+static uint64_t partition_min_size(const Partition *p) {
+        uint64_t sz;
+
+        /* Calculate the disk space we really need at minimum for this partition. If the partition already
+         * exists the current size is what we really need. If it doesn't exist yet refuse to allocate less
+         * than 4K. */
+
+        if (PARTITION_IS_FOREIGN(p)) {
+                /* Don't allow changing size of partitions not managed by us */
+                assert(p->current_size != UINT64_MAX);
+                return p->current_size;
+        }
+
+        sz = p->current_size != UINT64_MAX ? p->current_size : 4096;
+        if (p->size_min != UINT64_MAX)
+                return MAX(p->size_min, sz);
+
+        return sz;
+}
+
+static uint64_t partition_max_size(const Partition *p) {
+        /* Calculate how large the partition may become at max. This is generally the configured maximum
+         * size, except when it already exists and is larger than that. In that case it's the existing size,
+         * since we never want to shrink partitions. */
+
+        if (PARTITION_IS_FOREIGN(p)) {
+                /* Don't allow changing size of partitions not managed by us */
+                assert(p->current_size != UINT64_MAX);
+                return p->current_size;
+        }
+
+        if (p->current_size != UINT64_MAX)
+                return MAX(p->current_size, p->size_max);
+
+        return p->size_max;
+}
+
+static uint64_t partition_min_size_with_padding(const Partition *p) {
+        uint64_t sz;
+
+        /* Calculate the disk space we need for this partition plus any free space coming after it. This
+         * takes user configured padding into account as well as any additional whitespace needed to align
+         * the next partition to 4K again. */
+
+        sz = partition_min_size(p);
+
+        if (p->padding_min != UINT64_MAX)
+                sz += p->padding_min;
+
+        if (PARTITION_EXISTS(p)) {
+                /* If the partition wasn't aligned, add extra space so that any we might add will be aligned */
+                assert(p->offset != UINT64_MAX);
+                return round_up_size(p->offset + sz, 4096) - p->offset;
+        }
+
+        /* If this is a new partition we'll place it aligned, hence we just need to round up the required size here */
+        return round_up_size(sz, 4096);
+}
+
+static uint64_t free_area_available(const FreeArea *a) {
+        assert(a);
+
+        /* Determines how much of this free area is not allocated yet */
+
+        assert(a->size >= a->allocated);
+        return a->size - a->allocated;
+}
+
+static uint64_t free_area_available_for_new_partitions(const FreeArea *a) {
+        uint64_t avail;
+
+        /* Similar to free_area_available(), but takes into account that the required size and padding of the
+         * preceeding partition is honoured. */
+
+        avail = free_area_available(a);
+        if (a->after) {
+                uint64_t need, space;
+
+                need = partition_min_size_with_padding(a->after);
+
+                assert(a->after->offset != UINT64_MAX);
+                assert(a->after->current_size != UINT64_MAX);
+
+                space = round_up_size(a->after->offset + a->after->current_size, 4096) - a->after->offset + avail;
+                if (need >= space)
+                        return 0;
+
+                return space - need;
+        }
+
+        return avail;
+}
+
+static int free_area_compare(FreeArea *const *a, FreeArea *const*b) {
+        return CMP(free_area_available_for_new_partitions(*a),
+                   free_area_available_for_new_partitions(*b));
+}
+
+static uint64_t charge_size(uint64_t total, uint64_t amount) {
+        uint64_t rounded;
+
+        assert(amount <= total);
+
+        /* Subtract the specified amount from total, rounding up to multiple of 4K if there's room */
+        rounded = round_up_size(amount, 4096);
+        if (rounded >= total)
+                return 0;
+
+        return total - rounded;
+}
+
+static uint64_t charge_weight(uint64_t total, uint64_t amount) {
+        assert(amount <= total);
+        return total - amount;
+}
+
+static bool context_allocate_partitions(Context *context) {
+        Partition *p;
+
+        assert(context);
+
+        /* A simple first-fit algorithm, assuming the array of free areas is sorted by size in decreasing
+         * order. */
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                bool fits = false;
+                uint64_t required;
+                FreeArea *a = NULL;
+
+                /* Skip partitions we already dropped or that already exist */
+                if (p->dropped || PARTITION_EXISTS(p))
+                        continue;
+
+                /* Sort by size */
+                typesafe_qsort(context->free_areas, context->n_free_areas, free_area_compare);
+
+                /* How much do we need to fit? */
+                required = partition_min_size_with_padding(p);
+                assert(required % 4096 == 0);
+
+                for (size_t i = 0; i < context->n_free_areas; i++) {
+                        a = context->free_areas[i];
+
+                        if (free_area_available_for_new_partitions(a) >= required) {
+                                fits = true;
+                                break;
+                        }
+                }
+
+                if (!fits)
+                        return false; /* 😢 Oh no! We can't fit this partition into any free area! */
+
+                /* Assign the partition to this free area */
+                p->allocated_to_area = a;
+
+                /* Budget the minimal partition size */
+                a->allocated += required;
+        }
+
+        return true;
+}
+
+static int context_sum_weights(Context *context, FreeArea *a, uint64_t *ret) {
+        uint64_t weight_sum = 0;
+        Partition *p;
+
+        assert(context);
+        assert(a);
+        assert(ret);
+
+        /* Determine the sum of the weights of all partitions placed in or before the specified free area */
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->padding_area != a && p->allocated_to_area != a)
+                        continue;
+
+                if (p->weight > UINT64_MAX - weight_sum)
+                        goto overflow_sum;
+                weight_sum += p->weight;
+
+                if (p->padding_weight > UINT64_MAX - weight_sum)
+                        goto overflow_sum;
+                weight_sum += p->padding_weight;
+        }
+
+        *ret = weight_sum;
+        return 0;
+
+overflow_sum:
+        return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Combined weight of partition exceeds unsigned 64bit range, refusing.");
+}
+
+static int scale_by_weight(uint64_t value, uint64_t weight, uint64_t weight_sum, uint64_t *ret) {
+        assert(weight_sum >= weight);
+        assert(ret);
+
+        if (weight == 0) {
+                *ret = 0;
+                return 0;
+        }
+
+        if (value > UINT64_MAX / weight)
+                return log_error_errno(SYNTHETIC_ERRNO(EOVERFLOW), "Scaling by weight of partition exceeds unsigned 64bit range, refusing.");
+
+        *ret = value * weight / weight_sum;
+        return 0;
+}
+
+typedef enum GrowPartitionPhase {
+        /* The first phase: we charge partitions which need more (according to constraints) than their weight-based share. */
+        PHASE_OVERCHARGE,
+
+        /* The second phase: we charge partitions which need less (according to constraints) than their weight-based share. */
+        PHASE_UNDERCHARGE,
+
+        /* The third phase: we distribute what remains among the remaining partitions, according to the weights */
+        PHASE_DISTRIBUTE,
+} GrowPartitionPhase;
+
+static int context_grow_partitions_phase(
+                Context *context,
+                FreeArea *a,
+                GrowPartitionPhase phase,
+                uint64_t *span,
+                uint64_t *weight_sum) {
+
+        Partition *p;
+        int r;
+
+        assert(context);
+        assert(a);
+
+        /* Now let's look at the intended weights and adjust them taking the minimum space assignments into
+         * account. i.e. if a partition has a small weight but a high minimum space value set it should not
+         * get any additional room from the left-overs. Similar, if two partitions have the same weight they
+         * should get the same space if possible, even if one has a smaller minimum size than the other. */
+        LIST_FOREACH(partitions, p, context->partitions) {
+
+                /* Look only at partitions associated with this free area, i.e. immediately
+                 * preceeding it, or allocated into it */
+                if (p->allocated_to_area != a && p->padding_area != a)
+                        continue;
+
+                if (p->new_size == UINT64_MAX) {
+                        bool charge = false, try_again = false;
+                        uint64_t share, rsz, xsz;
+
+                        /* Calculate how much this space this partition needs if everyone would get
+                         * the weight based share */
+                        r = scale_by_weight(*span, p->weight, *weight_sum, &share);
+                        if (r < 0)
+                                return r;
+
+                        rsz = partition_min_size(p);
+                        xsz = partition_max_size(p);
+
+                        if (phase == PHASE_OVERCHARGE && rsz > share) {
+                                /* This partition needs more than its calculated share. Let's assign
+                                 * it that, and take this partition out of all calculations and start
+                                 * again. */
+
+                                p->new_size = rsz;
+                                charge = try_again = true;
+
+                        } else if (phase == PHASE_UNDERCHARGE && xsz != UINT64_MAX && xsz < share) {
+                                /* This partition accepts less than its calculated
+                                 * share. Let's assign it that, and take this partition out
+                                 * of all calculations and start again. */
+
+                                p->new_size = xsz;
+                                charge = try_again = true;
+
+                        } else if (phase == PHASE_DISTRIBUTE) {
+                                /* This partition can accept its calculated share. Let's
+                                 * assign it. There's no need to restart things here since
+                                 * assigning this shouldn't impact the shares of the other
+                                 * partitions. */
+
+                                if (PARTITION_IS_FOREIGN(p))
+                                        /* Never change of foreign partitions (i.e. those we don't manage) */
+                                        p->new_size = p->current_size;
+                                else
+                                        p->new_size = MAX(round_down_size(share, 4096), rsz);
+
+                                charge = true;
+                        }
+
+                        if (charge) {
+                                *span = charge_size(*span, p->new_size);
+                                *weight_sum = charge_weight(*weight_sum, p->weight);
+                        }
+
+                        if (try_again)
+                                return 0; /* try again */
+                }
+
+                if (p->new_padding == UINT64_MAX) {
+                        bool charge = false, try_again = false;
+                        uint64_t share;
+
+                        r = scale_by_weight(*span, p->padding_weight, *weight_sum, &share);
+                        if (r < 0)
+                                return r;
+
+                        if (phase == PHASE_OVERCHARGE && p->padding_min != UINT64_MAX && p->padding_min > share) {
+                                p->new_padding = p->padding_min;
+                                charge = try_again = true;
+                        } else if (phase == PHASE_UNDERCHARGE && p->padding_max != UINT64_MAX && p->padding_max < share) {
+                                p->new_padding = p->padding_max;
+                                charge = try_again = true;
+                        } else if (phase == PHASE_DISTRIBUTE) {
+
+                                p->new_padding = round_down_size(share, 4096);
+                                if (p->padding_min != UINT64_MAX && p->new_padding < p->padding_min)
+                                        p->new_padding = p->padding_min;
+
+                                charge = true;
+                        }
+
+                        if (charge) {
+                                *span = charge_size(*span, p->new_padding);
+                                *weight_sum = charge_weight(*weight_sum, p->padding_weight);
+                        }
+
+                        if (try_again)
+                                return 0; /* try again */
+                }
+        }
+
+        return 1; /* done */
+}
+
+static int context_grow_partitions_on_free_area(Context *context, FreeArea *a) {
+        uint64_t weight_sum = 0, span;
+        int r;
+
+        assert(context);
+        assert(a);
+
+        r = context_sum_weights(context, a, &weight_sum);
+        if (r < 0)
+                return r;
+
+        /* Let's calculate the total area covered by this free area and the partition before it */
+        span = a->size;
+        if (a->after) {
+                assert(a->after->offset != UINT64_MAX);
+                assert(a->after->current_size != UINT64_MAX);
+
+                span += round_up_size(a->after->offset + a->after->current_size, 4096) - a->after->offset;
+        }
+
+        GrowPartitionPhase phase = PHASE_OVERCHARGE;
+        for (;;) {
+                r = context_grow_partitions_phase(context, a, phase, &span, &weight_sum);
+                if (r < 0)
+                        return r;
+                if (r == 0) /* not done yet, re-run this phase */
+                        continue;
+
+                if (phase == PHASE_OVERCHARGE)
+                        phase = PHASE_UNDERCHARGE;
+                else if (phase == PHASE_UNDERCHARGE)
+                        phase = PHASE_DISTRIBUTE;
+                else if (phase == PHASE_DISTRIBUTE)
+                        break;
+        }
+
+        /* We still have space left over? Donate to preceeding partition if we have one */
+        if (span > 0 && a->after && !PARTITION_IS_FOREIGN(a->after)) {
+                uint64_t m, xsz;
+
+                assert(a->after->new_size != UINT64_MAX);
+                m = a->after->new_size + span;
+
+                xsz = partition_max_size(a->after);
+                if (xsz != UINT64_MAX && m > xsz)
+                        m = xsz;
+
+                span = charge_size(span, m - a->after->new_size);
+                a->after->new_size = m;
+        }
+
+        /* What? Even still some space left (maybe because there was no preceeding partition, or it had a
+         * size limit), then let's donate it to whoever wants it. */
+        if (span > 0) {
+                Partition *p;
+
+                LIST_FOREACH(partitions, p, context->partitions) {
+                        uint64_t m, xsz;
+
+                        if (p->allocated_to_area != a)
+                                continue;
+
+                        if (PARTITION_IS_FOREIGN(p))
+                                continue;
+
+                        assert(p->new_size != UINT64_MAX);
+                        m = p->new_size + span;
+
+                        xsz = partition_max_size(a->after);
+                        if (xsz != UINT64_MAX && m > xsz)
+                                m = xsz;
+
+                        span = charge_size(span, m - p->new_size);
+                        p->new_size = m;
+
+                        if (span == 0)
+                                break;
+                }
+        }
+
+        /* Yuck, still noone? Then make it padding */
+        if (span > 0 && a->after) {
+                assert(a->after->new_padding != UINT64_MAX);
+                a->after->new_padding += span;
+        }
+
+        return 0;
+}
+
+static int context_grow_partitions(Context *context) {
+        Partition *p;
+        int r;
+
+        assert(context);
+
+        for (size_t i = 0; i < context->n_free_areas; i++) {
+                r = context_grow_partitions_on_free_area(context, context->free_areas[i]);
+                if (r < 0)
+                        return r;
+        }
+
+        /* All existing partitions that have no free space after them can't change size */
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->dropped)
+                        continue;
+
+                if (!PARTITION_EXISTS(p) || p->padding_area) {
+                        /* The algorithm above must have initialized this already */
+                        assert(p->new_size != UINT64_MAX);
+                        continue;
+                }
+
+                assert(p->new_size == UINT64_MAX);
+                p->new_size = p->current_size;
+
+                assert(p->new_padding == UINT64_MAX);
+                p->new_padding = p->current_padding;
+        }
+
+        return 0;
+}
+
+static void context_place_partitions(Context *context) {
+        uint64_t partno = 0;
+        Partition *p;
+
+        assert(context);
+
+        /* Determine next partition number to assign */
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (!PARTITION_EXISTS(p))
+                        continue;
+
+                assert(p->partno != UINT64_MAX);
+                if (p->partno >= partno)
+                        partno = p->partno + 1;
+        }
+
+        for (size_t i = 0; i < context->n_free_areas; i++) {
+                FreeArea *a = context->free_areas[i];
+                uint64_t start, left;
+
+                if (a->after) {
+                        assert(a->after->offset != UINT64_MAX);
+                        assert(a->after->new_size != UINT64_MAX);
+                        assert(a->after->new_padding != UINT64_MAX);
+
+                        start = a->after->offset + a->after->new_size + a->after->new_padding;
+                } else
+                        start = context->start;
+
+                start = round_up_size(start, 4096);
+                left = a->size;
+
+                LIST_FOREACH(partitions, p, context->partitions) {
+                        if (p->allocated_to_area != a)
+                                continue;
+
+                        p->offset = start;
+                        p->partno = partno++;
+
+                        assert(left >= p->new_size);
+                        start += p->new_size;
+                        left -= p->new_size;
+
+                        assert(left >= p->new_padding);
+                        start += p->new_padding;
+                        left -= p->new_padding;
+                }
+        }
+}
+
+static int config_parse_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) {
+
+        sd_id128_t *type_uuid = data;
+        int r;
+
+        assert(rvalue);
+        assert(type_uuid);
+
+        r = gpt_partition_type_uuid_from_string(rvalue, type_uuid);
+        if (r < 0)
+                return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse partition type: %s", rvalue);
+
+        return 0;
+}
+
+static int config_parse_label(
+                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_ char16_t *recoded = NULL;
+        char **label = data;
+        int r;
+
+        assert(rvalue);
+        assert(label);
+
+        if (!utf8_is_valid(rvalue)) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Partition label not valid UTF-8, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        recoded = utf8_to_utf16(rvalue, strlen(rvalue));
+        if (!recoded)
+                return log_oom();
+
+        if (char16_strlen(recoded) > 36) {
+                log_syntax(unit, LOG_WARNING, filename, line, 0,
+                           "Partition label too long for GPT table, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        r = free_and_strdup(label, rvalue);
+        if (r < 0)
+                return log_oom();
+
+        return 0;
+}
+
+static int config_parse_weight(
+                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 *priority = data, v;
+        int r;
+
+        assert(rvalue);
+        assert(priority);
+
+        r = safe_atou32(rvalue, &v);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse weight value, ignoring: %s", rvalue);
+                return 0;
+        }
+
+        if (v > 1000U*1000U) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Weight needs to be in range 0…10000000, ignoring: %" PRIu32, v);
+                return 0;
+        }
+
+        *priority = v;
+        return 0;
+}
+
+static int config_parse_size4096(
+                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) {
+
+        uint64_t *sz = data, parsed;
+        int r;
+
+        assert(rvalue);
+        assert(data);
+
+        r = parse_size(rvalue, 1024, &parsed);
+        if (r < 0)
+                return log_syntax(unit, LOG_WARNING, filename, line, r,
+                                  "Failed to parse size value: %s", rvalue);
+
+        if (ltype > 0)
+                *sz = round_up_size(parsed, 4096);
+        else if (ltype < 0)
+                *sz = round_down_size(parsed, 4096);
+        else
+                *sz = parsed;
+
+        if (*sz != parsed)
+                log_syntax(unit, LOG_NOTICE, filename, line, r, "Rounded %s= size %" PRIu64 " → %" PRIu64 ", a multiple of 4096.", lvalue, parsed, *sz);
+
+        return 0;
+}
+
+static int partition_read_definition(Partition *p, const char *path) {
+
+        ConfigTableItem table[] = {
+                { "Partition", "Type",            config_parse_type,     0,  &p->type_uuid      },
+                { "Partition", "Label",           config_parse_label,    0,  &p->new_label      },
+                { "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  },
+                {}
+        };
+        int r;
+
+        r = config_parse(NULL, path, NULL, "Partition\0", config_item_table_lookup, table, CONFIG_PARSE_WARN, p);
+        if (r < 0)
+                return r;
+
+        if (p->size_min != UINT64_MAX && p->size_max != UINT64_MAX && p->size_min > p->size_max)
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "SizeMinBytes= larger than SizeMaxBytes=, refusing.");
+
+        if (p->padding_min != UINT64_MAX && p->padding_max != UINT64_MAX && p->padding_min > p->padding_max)
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "PaddingMinBytes= larger than PaddingMaxBytes=, refusing.");
+
+        if (sd_id128_is_null(p->type_uuid))
+                return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
+                                  "Type= not defined, refusing.");
+
+        return 0;
+}
+
+static int context_read_definitions(
+                Context *context,
+                const char *directory,
+                const char *root) {
+
+        _cleanup_strv_free_ char **files = NULL;
+        Partition *last = NULL;
+        char **f;
+        int r;
+
+        assert(context);
+
+        if (directory)
+                r = conf_files_list_strv(&files, ".conf", NULL, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) STRV_MAKE(directory));
+        else
+                r = conf_files_list_strv(&files, ".conf", root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char**) CONF_PATHS_STRV("repart.d"));
+        if (r < 0)
+                return log_error_errno(r, "Failed to enumerate *.conf files: %m");
+
+        STRV_FOREACH(f, files) {
+                _cleanup_(partition_freep) Partition *p = NULL;
+
+                p = partition_new();
+                if (!p)
+                        return log_oom();
+
+                p->definition_path = strdup(*f);
+                if (!p->definition_path)
+                        return log_oom();
+
+                r = partition_read_definition(p, *f);
+                if (r < 0)
+                        return r;
+
+                LIST_INSERT_AFTER(partitions, context->partitions, last, p);
+                last = TAKE_PTR(p);
+                context->n_partitions++;
+        }
+
+        return 0;
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_context*, fdisk_unref_context);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_partition*, fdisk_unref_partition);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_parttype*, fdisk_unref_parttype);
+DEFINE_TRIVIAL_CLEANUP_FUNC(struct fdisk_table*, fdisk_unref_table);
+
+static int determine_current_padding(
+                struct fdisk_context *c,
+                struct fdisk_table *t,
+                struct fdisk_partition *p,
+                uint64_t *ret) {
+
+        size_t n_partitions;
+        uint64_t offset, next = UINT64_MAX;
+
+        assert(c);
+        assert(t);
+        assert(p);
+
+        if (!fdisk_partition_has_end(p))
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!");
+
+        offset = fdisk_partition_get_end(p);
+        assert(offset < UINT64_MAX / 512);
+        offset *= 512;
+
+        n_partitions = fdisk_table_get_nents(t);
+        for (size_t i = 0; i < n_partitions; i++)  {
+                struct fdisk_partition *q;
+                uint64_t start;
+
+                q = fdisk_table_get_partition(t, i);
+                if (!q)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
+
+                if (fdisk_partition_is_used(q) <= 0)
+                        continue;
+
+                if (!fdisk_partition_has_start(q))
+                        continue;
+
+                start = fdisk_partition_get_start(q);
+                assert(start < UINT64_MAX / 512);
+                start *= 512;
+
+                if (start >= offset && (next == UINT64_MAX || next > start))
+                        next = start;
+        }
+
+        if (next == UINT64_MAX) {
+                /* No later partition? In that case check the end of the usable area */
+                next = fdisk_get_last_lba(c);
+                assert(next < UINT64_MAX);
+                next++; /* The last LBA is one sector before the end */
+
+                assert(next < UINT64_MAX / 512);
+                next *= 512;
+
+                if (offset > next)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
+        }
+
+        assert(next >= offset);
+        offset = round_up_size(offset, 4096);
+        next = round_down_size(next, 4096);
+
+        if (next >= offset) /* Check again, rounding might have fucked things up */
+                *ret = next - offset;
+        else
+                *ret = 0;
+
+        return 0;
+}
+
+static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *data) {
+        _cleanup_free_ char *ids = NULL;
+        int r;
+
+        if (fdisk_ask_get_type(ask) != FDISK_ASKTYPE_STRING)
+                return -EINVAL;
+
+        ids = new(char, ID128_UUID_STRING_MAX);
+        if (!ids)
+                return -ENOMEM;
+
+        r = fdisk_ask_string_set_result(ask, id128_to_uuid_string(*(sd_id128_t*) data, ids));
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(ids);
+        return 0;
+}
+
+static int fdisk_set_disklabel_id_by_uuid(struct fdisk_context *c, sd_id128_t id) {
+        int r;
+
+        r = fdisk_set_ask(c, fdisk_ask_cb, &id);
+        if (r < 0)
+                return r;
+
+        r = fdisk_set_disklabel_id(c);
+        if (r < 0)
+                return r;
+
+        return fdisk_set_ask(c, NULL, NULL);
+}
+
+#define DISK_UUID_TOKEN "disk-uuid"
+
+static int disk_acquire_uuid(Context *context, sd_id128_t *ret) {
+        union {
+                unsigned char md[SHA256_DIGEST_LENGTH];
+                sd_id128_t id;
+        } result;
+
+        assert(context);
+        assert(ret);
+
+        /* Calculate the HMAC-SHA256 of the string "disk-uuid", keyed off the machine ID. We use the machine
+         * ID as key (and not as cleartext!) since it's the machine ID we don't want to leak. */
+
+        if (!HMAC(EVP_sha256(),
+                  &context->seed, sizeof(context->seed),
+                  (const unsigned char*) DISK_UUID_TOKEN, strlen(DISK_UUID_TOKEN),
+                  result.md, NULL))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "HMAC-SHA256 calculation failed.");
+
+        /* Take the first half, mark it as v4 UUID */
+        assert_cc(sizeof(result.md) == sizeof(result.id) * 2);
+        *ret = id128_make_v4_uuid(result.id);
+        return 0;
+}
+
+static int context_load_partition_table(Context *context, const char *node) {
+        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
+        _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
+        uint64_t left_boundary = UINT64_MAX, first_lba, last_lba, nsectors;
+        _cleanup_free_ char *disk_uuid_string = NULL;
+        bool from_scratch = false;
+        sd_id128_t disk_uuid;
+        size_t n_partitions;
+        int r;
+
+        assert(context);
+        assert(node);
+
+        c = fdisk_new_context();
+        if (!c)
+                return log_oom();
+
+        r = fdisk_assign_device(c, node, arg_dry_run);
+        if (r < 0)
+                return log_error_errno(r, "Failed to open device: %m");
+
+        /* Tell udev not to interfere while we are processing the device */
+        if (flock(fdisk_get_devfd(c), arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+                return log_error_errno(errno, "Failed to lock block device: %m");
+
+        switch (arg_empty) {
+
+        case EMPTY_REFUSE:
+                /* Refuse empty disks, insist on an existing GPT partition table */
+                if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
+                        return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has no GPT disk label, not repartitioning.", node);
+
+                break;
+
+        case EMPTY_REQUIRE:
+                /* Require an empty disk, refuse any existing partition table */
+                r = fdisk_has_label(c);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", node);
+                if (r > 0)
+                        return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s already has a disk label, refusing.", node);
+
+                from_scratch = true;
+                break;
+
+        case EMPTY_ALLOW:
+                /* Allow both an empty disk and an existing partition table, but only GPT */
+                r = fdisk_has_label(c);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to determine whether disk %s has a disk label: %m", node);
+                if (r > 0) {
+                        if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
+                                return log_notice_errno(SYNTHETIC_ERRNO(EHWPOISON), "Disk %s has non-GPT disk label, not repartitioning.", node);
+                } else
+                        from_scratch = true;
+
+                break;
+
+        case EMPTY_FORCE:
+                /* Always reinitiaize the disk, don't consider what there was on the disk before */
+                from_scratch = true;
+                break;
+        }
+
+        if (from_scratch) {
+                r = fdisk_enable_wipe(c, true);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to enable wiping of disk signature: %m");
+
+                r = fdisk_create_disklabel(c, "gpt");
+                if (r < 0)
+                        return log_error_errno(r, "Failed to create GPT disk label: %m");
+
+                r = disk_acquire_uuid(context, &disk_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire disk GPT uuid: %m");
+
+                r = fdisk_set_disklabel_id_by_uuid(c, disk_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set GPT disk label: %m");
+
+                goto add_initial_free_area;
+        }
+
+        r = fdisk_get_disklabel_id(c, &disk_uuid_string);
+        if (r < 0)
+                return log_error_errno(r, "Failed to get current GPT disk label UUID: %m");
+
+        r = sd_id128_from_string(disk_uuid_string, &disk_uuid);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse current GPT disk label UUID: %m");
+
+        if (sd_id128_is_null(disk_uuid)) {
+                r = disk_acquire_uuid(context, &disk_uuid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to acquire disk GPT uuid: %m");
+
+                r = fdisk_set_disklabel_id(c);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to set GPT disk label: %m");
+        }
+
+        r = fdisk_get_partitions(c, &t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire partition table: %m");
+
+        n_partitions = fdisk_table_get_nents(t);
+        for (size_t i = 0; i < n_partitions; i++)  {
+                _cleanup_free_ char *label_copy = NULL;
+                Partition *pp, *last = NULL;
+                struct fdisk_partition *p;
+                struct fdisk_parttype *pt;
+                const char *pts, *ids, *label;
+                uint64_t sz, start;
+                bool found = false;
+                sd_id128_t ptid, id;
+                size_t partno;
+
+                p = fdisk_table_get_partition(t, i);
+                if (!p)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
+
+                if (fdisk_partition_is_used(p) <= 0)
+                        continue;
+
+                if (fdisk_partition_has_start(p) <= 0 ||
+                    fdisk_partition_has_size(p) <= 0 ||
+                    fdisk_partition_has_partno(p) <= 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number.");
+
+                pt = fdisk_partition_get_type(p);
+                if (!pt)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition: %m");
+
+                pts = fdisk_parttype_get_string(pt);
+                if (!pts)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to acquire type of partition as string: %m");
+
+                r = sd_id128_from_string(pts, &ptid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse partition type UUID %s: %m", pts);
+
+                ids = fdisk_partition_get_uuid(p);
+                if (!ids)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a UUID.");
+
+                r = sd_id128_from_string(ids, &id);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse partition UUID %s: %m", ids);
+
+                label = fdisk_partition_get_name(p);
+                if (!isempty(label)) {
+                        label_copy = strdup(label);
+                        if (!label_copy)
+                                return log_oom();
+                }
+
+                sz = fdisk_partition_get_size(p);
+                assert_se(sz <= UINT64_MAX/512);
+                sz *= 512;
+
+                start = fdisk_partition_get_start(p);
+                assert_se(start <= UINT64_MAX/512);
+                start *= 512;
+
+                partno = fdisk_partition_get_partno(p);
+
+                if (left_boundary == UINT64_MAX || left_boundary > start)
+                        left_boundary = start;
+
+                /* Assign this existing partition to the first partition of the right type that doesn't have
+                 * an existing one assigned yet. */
+                LIST_FOREACH(partitions, pp, context->partitions) {
+                        last = pp;
+
+                        if (!sd_id128_equal(pp->type_uuid, ptid))
+                                continue;
+
+                        if (!pp->current_partition) {
+                                pp->current_uuid = id;
+                                pp->current_size = sz;
+                                pp->offset = start;
+                                pp->partno = partno;
+                                pp->current_label = TAKE_PTR(label_copy);
+
+                                pp->current_partition = p;
+                                fdisk_ref_partition(p);
+
+                                r = determine_current_padding(c, t, p, &pp->current_padding);
+                                if (r < 0)
+                                        return r;
+
+                                if (pp->current_padding > 0) {
+                                        r = context_add_free_area(context, pp->current_padding, pp);
+                                        if (r < 0)
+                                                return r;
+                                }
+
+                                found = true;
+                                break;
+                        }
+                }
+
+                /* If we have no matching definition, create a new one. */
+                if (!found) {
+                        _cleanup_(partition_freep) Partition *np = NULL;
+
+                        np = partition_new();
+                        if (!np)
+                                return log_oom();
+
+                        np->current_uuid = id;
+                        np->type_uuid = ptid;
+                        np->current_size = sz;
+                        np->offset = start;
+                        np->partno = partno;
+                        np->current_label = TAKE_PTR(label_copy);
+
+                        np->current_partition = p;
+                        fdisk_ref_partition(p);
+
+                        r = determine_current_padding(c, t, p, &np->current_padding);
+                        if (r < 0)
+                                return r;
+
+                        if (np->current_padding > 0) {
+                                r = context_add_free_area(context, np->current_padding, np);
+                                if (r < 0)
+                                        return r;
+                        }
+
+                        LIST_INSERT_AFTER(partitions, context->partitions, last, TAKE_PTR(np));
+                        context->n_partitions++;
+                }
+        }
+
+add_initial_free_area:
+        nsectors = fdisk_get_nsectors(c);
+        assert(nsectors <= UINT64_MAX/512);
+        nsectors *= 512;
+
+        first_lba = fdisk_get_first_lba(c);
+        assert(first_lba <= UINT64_MAX/512);
+        first_lba *= 512;
+
+        last_lba = fdisk_get_last_lba(c);
+        assert(last_lba < UINT64_MAX);
+        last_lba++;
+        assert(last_lba <= UINT64_MAX/512);
+        last_lba *= 512;
+
+        assert(last_lba >= first_lba);
+
+        if (left_boundary == UINT64_MAX) {
+                /* No partitions at all? Then the whole disk is up for grabs. */
+
+                first_lba = round_up_size(first_lba, 4096);
+                last_lba = round_down_size(last_lba, 4096);
+
+                if (last_lba > first_lba) {
+                        r = context_add_free_area(context, last_lba - first_lba, NULL);
+                        if (r < 0)
+                                return r;
+                }
+        } else {
+                /* Add space left of first partition */
+                assert(left_boundary >= first_lba);
+
+                first_lba = round_up_size(first_lba, 4096);
+                left_boundary = round_down_size(left_boundary, 4096);
+                last_lba = round_down_size(last_lba, 4096);
+
+                if (left_boundary > first_lba) {
+                        r = context_add_free_area(context, left_boundary - first_lba, NULL);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        context->start = first_lba;
+        context->end = last_lba;
+        context->total = nsectors;
+        context->fdisk_context = TAKE_PTR(c);
+
+        return from_scratch;
+}
+
+static void context_unload_partition_table(Context *context) {
+        Partition *p, *next;
+
+        assert(context);
+
+        LIST_FOREACH_SAFE(partitions, p, next, context->partitions) {
+
+                /* Entirely remove partitions that have no configuration */
+                if (PARTITION_IS_FOREIGN(p)) {
+                        partition_unlink_and_free(context, p);
+                        continue;
+                }
+
+                /* Otherwise drop all data we read off the block device and everything we might have
+                 * calculated based on it */
+
+                p->dropped = false;
+                p->current_size = UINT64_MAX;
+                p->new_size = UINT64_MAX;
+                p->current_padding = UINT64_MAX;
+                p->new_padding = UINT64_MAX;
+                p->partno = UINT64_MAX;
+                p->offset = UINT64_MAX;
+
+                if (p->current_partition) {
+                        fdisk_unref_partition(p->current_partition);
+                        p->current_partition = NULL;
+                }
+
+                if (p->new_partition) {
+                        fdisk_unref_partition(p->new_partition);
+                        p->new_partition = NULL;
+                }
+
+                p->padding_area = NULL;
+                p->allocated_to_area = NULL;
+
+                p->current_uuid = p->new_uuid = SD_ID128_NULL;
+        }
+
+        context->start = UINT64_MAX;
+        context->end = UINT64_MAX;
+        context->total = UINT64_MAX;
+
+        if (context->fdisk_context) {
+                fdisk_unref_context(context->fdisk_context);
+                context->fdisk_context = NULL;
+        }
+
+        context_free_free_areas(context);
+}
+
+static int format_size_change(uint64_t from, uint64_t to, char **ret) {
+        char format_buffer1[FORMAT_BYTES_MAX], format_buffer2[FORMAT_BYTES_MAX], *buf;
+
+        if (from != UINT64_MAX)
+                format_bytes(format_buffer1, sizeof(format_buffer1), from);
+        if (to != UINT64_MAX)
+                format_bytes(format_buffer2, sizeof(format_buffer2), to);
+
+        if (from != UINT64_MAX) {
+                if (from == to || to == UINT64_MAX)
+                        buf = strdup(format_buffer1);
+                else
+                        buf = strjoin(format_buffer1, " ", special_glyph(SPECIAL_GLYPH_ARROW), " ", format_buffer2);
+        } else if (to != UINT64_MAX)
+                buf = strjoin(special_glyph(SPECIAL_GLYPH_ARROW), " ", format_buffer2);
+        else {
+                *ret = NULL;
+                return 0;
+        }
+
+        if (!buf)
+                return log_oom();
+
+        *ret = TAKE_PTR(buf);
+        return 1;
+}
+
+static const char *partition_label(const Partition *p) {
+        assert(p);
+
+        if (p->new_label)
+                return p->new_label;
+
+        if (p->current_label)
+                return p->current_label;
+
+        return gpt_partition_type_uuid_to_string(p->type_uuid);
+}
+
+static int context_dump_partitions(Context *context, const char *node) {
+        _cleanup_(table_unrefp) Table *t = NULL;
+        uint64_t sum_padding = 0, sum_size = 0;
+        Partition *p;
+        int r;
+
+        t = table_new("type", "label", "uuid", "file", "node", "offset", "raw size", "size", "raw padding", "padding");
+        if (!t)
+                return log_oom();
+
+        if (!DEBUG_LOGGING)
+                (void) table_set_display(t, 0, 1, 2, 3, 4, 7, 9, (size_t) -1);
+
+        (void) table_set_align_percent(t, table_get_cell(t, 0, 4), 100);
+        (void) table_set_align_percent(t, table_get_cell(t, 0, 5), 100);
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                _cleanup_free_ char *size_change = NULL, *padding_change = NULL, *partname = NULL;
+                char uuid_buffer[ID128_UUID_STRING_MAX];
+                const char *label;
+
+                if (p->dropped)
+                        continue;
+
+                label = partition_label(p);
+                partname = p->partno != UINT64_MAX ? fdisk_partname(node, p->partno+1) : NULL;
+
+                r = format_size_change(p->current_size, p->new_size, &size_change);
+                if (r < 0)
+                        return r;
+
+                r = format_size_change(p->current_padding, p->new_padding, &padding_change);
+                if (r < 0)
+                        return r;
+
+                if (p->new_size != UINT64_MAX)
+                        sum_size += p->new_size;
+                if (p->new_padding != UINT64_MAX)
+                        sum_padding += p->new_padding;
+
+                r = table_add_many(
+                                t,
+                                TABLE_STRING, gpt_partition_type_uuid_to_string_harder(p->type_uuid, uuid_buffer),
+                                TABLE_STRING, label ?: "-", TABLE_SET_COLOR, label ? NULL : ansi_grey(),
+                                TABLE_UUID, sd_id128_is_null(p->new_uuid) ? p->current_uuid : p->new_uuid,
+                                TABLE_STRING, p->definition_path ? basename(p->definition_path) : "-", TABLE_SET_COLOR, p->definition_path ? NULL : ansi_grey(),
+                                TABLE_STRING, partname ?: "no", TABLE_SET_COLOR, partname ? NULL : ansi_highlight(),
+                                TABLE_UINT64, p->offset,
+                                TABLE_UINT64, p->new_size,
+                                TABLE_STRING, size_change, TABLE_SET_COLOR, !p->partitions_next && sum_size > 0 ? ansi_underline() : NULL,
+                                TABLE_UINT64, p->new_padding,
+                                TABLE_STRING, padding_change, TABLE_SET_COLOR, !p->partitions_next && sum_padding > 0 ? ansi_underline() : NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add row to table: %m");
+        }
+
+        if (sum_padding > 0 || sum_size > 0) {
+                char s[FORMAT_BYTES_MAX];
+                const char *a, *b;
+
+                a = strjoina(special_glyph(SPECIAL_GLYPH_SIGMA), " = ", format_bytes(s, sizeof(s), sum_size));
+                b = strjoina(special_glyph(SPECIAL_GLYPH_SIGMA), " = ", format_bytes(s, sizeof(s), sum_padding));
+
+                r = table_add_many(
+                                t,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_EMPTY,
+                                TABLE_STRING, a,
+                                TABLE_EMPTY,
+                                TABLE_STRING, b);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add row to table: %m");
+        }
+
+        r = table_print(t, stdout);
+        if (r < 0)
+                return log_error_errno(r, "Failed to dump table: %m");
+
+        return 0;
+}
+
+static void context_bar_char_process_partition(
+                Context *context,
+                Partition *bar[],
+                size_t n,
+                Partition *p,
+                size_t *ret_start) {
+
+        uint64_t from, to, total;
+        size_t x, y;
+
+        assert(context);
+        assert(bar);
+        assert(n > 0);
+        assert(p);
+
+        if (p->dropped)
+                return;
+
+        assert(p->offset != UINT64_MAX);
+        assert(p->new_size != UINT64_MAX);
+
+        from = p->offset;
+        to = from + p->new_size;
+
+        assert(context->end >= context->start);
+        total = context->end - context->start;
+
+        assert(from >= context->start);
+        assert(from <= context->end);
+        x = (from - context->start) * n / total;
+
+        assert(to >= context->start);
+        assert(to <= context->end);
+        y = (to - context->start) * n / total;
+
+        assert(x <= y);
+        assert(y <= n);
+
+        for (size_t i = x; i < y; i++)
+                bar[i] = p;
+
+        *ret_start = x;
+}
+
+static int partition_hint(const Partition *p, const char *node, char **ret) {
+        _cleanup_free_ char *buf = NULL;
+        char ids[ID128_UUID_STRING_MAX];
+        const char *label;
+        sd_id128_t id;
+
+        /* Tries really hard to find a suitable description for this partition */
+
+        if (p->definition_path) {
+                buf = strdup(basename(p->definition_path));
+                goto done;
+        }
+
+        label = partition_label(p);
+        if (!isempty(label)) {
+                buf = strdup(label);
+                goto done;
+        }
+
+        if (p->partno != UINT64_MAX) {
+                buf = fdisk_partname(node, p->partno+1);
+                goto done;
+        }
+
+        if (!sd_id128_is_null(p->new_uuid))
+                id = p->new_uuid;
+        else if (!sd_id128_is_null(p->current_uuid))
+                id = p->current_uuid;
+        else
+                id = p->type_uuid;
+
+        buf = strdup(id128_to_uuid_string(id, ids));
+
+done:
+        if (!buf)
+                return -ENOMEM;
+
+        *ret = TAKE_PTR(buf);
+        return 0;
+}
+
+static int context_dump_partition_bar(Context *context, const char *node) {
+        _cleanup_free_ Partition **bar = NULL;
+        _cleanup_free_ size_t *start_array = NULL;
+        Partition *p, *last = NULL;
+        bool z = false;
+        size_t c, j = 0;
+
+        assert((c = columns()) >= 2);
+        c -= 2; /* We do not use the leftmost and rightmost character cell */
+
+        bar = new0(Partition*, c);
+        if (!bar)
+                return log_oom();
+
+        start_array = new(size_t, context->n_partitions);
+        if (!start_array)
+                return log_oom();
+
+        LIST_FOREACH(partitions, p, context->partitions)
+                context_bar_char_process_partition(context, bar, c, p, start_array + j++);
+
+        putc(' ', stdout);
+
+        for (size_t i = 0; i < c; i++) {
+                if (bar[i]) {
+                        if (last != bar[i])
+                                z = !z;
+
+                        fputs(z ? ansi_green() : ansi_yellow(), stdout);
+                        fputs(special_glyph(SPECIAL_GLYPH_DARK_SHADE), stdout);
+                } else {
+                        fputs(ansi_normal(), stdout);
+                        fputs(special_glyph(SPECIAL_GLYPH_LIGHT_SHADE), stdout);
+                }
+
+                last = bar[i];
+        }
+
+        fputs(ansi_normal(), stdout);
+        putc('\n', stdout);
+
+        for (size_t i = 0; i < context->n_partitions; i++) {
+                _cleanup_free_ char **line = NULL;
+
+                line = new0(char*, c);
+                if (!line)
+                        return log_oom();
+
+                j = 0;
+                LIST_FOREACH(partitions, p, context->partitions) {
+                        _cleanup_free_ char *d = NULL;
+                        j++;
+
+                        if (i < context->n_partitions - j) {
+
+                                if (line[start_array[j-1]]) {
+                                        const char *e;
+
+                                        /* Upgrade final corner to the right with a branch to the right */
+                                        e = startswith(line[start_array[j-1]], special_glyph(SPECIAL_GLYPH_TREE_RIGHT));
+                                        if (e) {
+                                                d = strjoin(special_glyph(SPECIAL_GLYPH_TREE_BRANCH), e);
+                                                if (!d)
+                                                        return log_oom();
+                                        }
+                                }
+
+                                if (!d) {
+                                        d = strdup(special_glyph(SPECIAL_GLYPH_TREE_VERTICAL));
+                                        if (!d)
+                                                return log_oom();
+                                }
+
+                        } else if (i == context->n_partitions - j) {
+                                _cleanup_free_ char *hint = NULL;
+
+                                (void) partition_hint(p, node, &hint);
+
+                                if (streq_ptr(line[start_array[j-1]], special_glyph(SPECIAL_GLYPH_TREE_VERTICAL)))
+                                        d = strjoin(special_glyph(SPECIAL_GLYPH_TREE_BRANCH), " ", strna(hint));
+                                else
+                                        d = strjoin(special_glyph(SPECIAL_GLYPH_TREE_RIGHT), " ", strna(hint));
+
+                                if (!d)
+                                        return log_oom();
+                        }
+
+                        if (d)
+                                free_and_replace(line[start_array[j-1]], d);
+                }
+
+                putc(' ', stdout);
+
+                j = 0;
+                while (j < c) {
+                        if (line[j]) {
+                                fputs(line[j], stdout);
+                                j += utf8_console_width(line[j]);
+                        } else {
+                                putc(' ', stdout);
+                                j++;
+                        }
+                }
+
+                putc('\n', stdout);
+
+                for (j = 0; j < c; j++)
+                        free(line[j]);
+        }
+
+        return 0;
+}
+
+static bool context_changed(const Context *context) {
+        Partition *p;
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->dropped)
+                        continue;
+
+                if (p->allocated_to_area)
+                        return true;
+
+                if (p->new_size != p->current_size)
+                        return true;
+        }
+
+        return false;
+}
+
+static int context_wipe_partition(Context *context, Partition *p) {
+        _cleanup_(blkid_free_probep) blkid_probe probe = NULL;
+        int r;
+
+        assert(context);
+        assert(p);
+        assert(!PARTITION_EXISTS(p)); /* Safety check: never wipe existing partitions */
+
+        probe = blkid_new_probe();
+        if (!probe)
+                return log_oom();
+
+        assert(p->offset != UINT64_MAX);
+        assert(p->new_size != UINT64_MAX);
+
+        errno = 0;
+        r = blkid_probe_set_device(probe, fdisk_get_devfd(context->fdisk_context), p->offset, p->new_size);
+        if (r < 0)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to allocate device probe for partition %" PRIu64 ".", p->partno);
+
+        errno = 0;
+        if (blkid_probe_enable_superblocks(probe, true) < 0 ||
+            blkid_probe_set_superblocks_flags(probe, BLKID_SUBLKS_MAGIC|BLKID_SUBLKS_BADCSUM) < 0 ||
+            blkid_probe_enable_partitions(probe, true) < 0 ||
+            blkid_probe_set_partitions_flags(probe, BLKID_PARTS_MAGIC) < 0)
+                return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to enable superblock and partition probing for partition %" PRIu64 ".", p->partno);
+
+        for (;;) {
+                errno = 0;
+                r = blkid_do_probe(probe);
+                if (r < 0)
+                        return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe for file systems.");
+                if (r > 0)
+                        break;
+
+                errno = 0;
+                if (blkid_do_wipe(probe, false) < 0)
+                        return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to wipe file system signature.");
+        }
+
+        log_info("Successfully wiped file system signatures from partition %" PRIu64 ".", p->partno);
+        return 0;
+}
+
+static int context_discard_range(Context *context, uint64_t offset, uint64_t size) {
+        struct stat st;
+        int fd;
+
+        assert(context);
+        assert(offset != UINT64_MAX);
+        assert(size != UINT64_MAX);
+
+        if (size <= 0)
+                return 0;
+
+        fd = fdisk_get_devfd(context->fdisk_context);
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (S_ISREG(st.st_mode)) {
+                if (fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, size) < 0) {
+                        if (ERRNO_IS_NOT_SUPPORTED(errno))
+                                return -EOPNOTSUPP;
+
+                        return -errno;
+                }
+
+                return 1;
+        }
+
+        if (S_ISBLK(st.st_mode)) {
+                uint64_t range[2], end;
+
+                range[0] = round_up_size(offset, 512);
+
+                end = offset + size;
+                if (end <= range[0])
+                        return 0;
+
+                range[1] = round_down_size(end - range[0], 512);
+                if (range[1] <= 0)
+                        return 0;
+
+                if (ioctl(fd, BLKDISCARD, range) < 0) {
+                        if (ERRNO_IS_NOT_SUPPORTED(errno))
+                                return -EOPNOTSUPP;
+
+                        return -errno;
+                }
+
+                return 1;
+        }
+
+        return -EOPNOTSUPP;
+}
+
+static int context_discard_partition(Context *context, Partition *p) {
+        int r;
+
+        assert(context);
+        assert(p);
+
+        assert(p->offset != UINT64_MAX);
+        assert(p->new_size != UINT64_MAX);
+        assert(!PARTITION_EXISTS(p)); /* Safety check: never discard existing partitions */
+
+        if (!arg_discard)
+                return 0;
+
+        r = context_discard_range(context, p->offset, p->new_size);
+        if (r == -EOPNOTSUPP) {
+                log_info("Storage does not support discarding, not discarding data in new partition %" PRIu64 ".", p->partno);
+                return 0;
+        }
+        if (r == 0) {
+                log_info("Partition %" PRIu64 " too short for discard, skipping.", p->partno);
+                return 0;
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to discard data for new partition %" PRIu64 ".", p->partno);
+
+        log_info("Successfully discarded data from partition %" PRIu64 ".", p->partno);
+        return 1;
+}
+
+static int context_discard_gap_after(Context *context, Partition *p) {
+        uint64_t gap, next = UINT64_MAX;
+        Partition *q;
+        int r;
+
+        assert(context);
+        assert(!p || (p->offset != UINT64_MAX && p->new_size != UINT64_MAX));
+
+        if (p)
+                gap = p->offset + p->new_size;
+        else
+                gap = context->start;
+
+        LIST_FOREACH(partitions, q, context->partitions) {
+                if (q->dropped)
+                        continue;
+
+                assert(q->offset != UINT64_MAX);
+                assert(q->new_size != UINT64_MAX);
+
+                if (q->offset < gap)
+                        continue;
+
+                if (next == UINT64_MAX || q->offset < next)
+                        next = q->offset;
+        }
+
+        if (next == UINT64_MAX) {
+                next = context->end;
+                if (gap > next)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
+        }
+
+        assert(next >= gap);
+        r = context_discard_range(context, gap, next - gap);
+        if (r == -EOPNOTSUPP) {
+                if (p)
+                        log_info("Storage does not support discarding, not discarding gap after partition %" PRIu64 ".", p->partno);
+                else
+                        log_info("Storage does not support discarding, not discarding gap at beginning of disk.");
+                return 0;
+        }
+        if (r == 0)  /* Too short */
+                return 0;
+        if (r < 0) {
+                if (p)
+                        return log_error_errno(r, "Failed to discard gap after partition %" PRIu64 ".", p->partno);
+                else
+                        return log_error_errno(r, "Failed to discard gap at beginning of disk.");
+        }
+
+        if (p)
+                log_info("Successfully discarded gap after partition %" PRIu64 ".", p->partno);
+        else
+                log_info("Successfully discarded gap at beginning of disk.");
+
+        return 0;
+}
+
+static int context_wipe_and_discard(Context *context, bool from_scratch) {
+        Partition *p;
+        int r;
+
+        assert(context);
+
+        /* Wipe and discard the contents of all partitions we are about to create. We skip the discarding if
+         * we were supposed to start from scratch anyway, as in that case we just discard the whole block
+         * device in one go early on. */
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+
+                if (!p->allocated_to_area)
+                        continue;
+
+                if (!from_scratch) {
+                        r = context_discard_partition(context, p);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = context_wipe_partition(context, p);
+                if (r < 0)
+                        return r;
+
+                if (!from_scratch) {
+                        r = context_discard_gap_after(context, p);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        if (!from_scratch) {
+                r = context_discard_gap_after(context, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int partition_acquire_uuid(Context *context, Partition *p, sd_id128_t *ret) {
+        struct {
+                sd_id128_t type_uuid;
+                uint64_t counter;
+        } _packed_  plaintext = {};
+        union {
+                unsigned char md[SHA256_DIGEST_LENGTH];
+                sd_id128_t id;
+        } result;
+
+        uint64_t k = 0;
+        Partition *q;
+        int r;
+
+        assert(context);
+        assert(p);
+        assert(ret);
+
+        /* Calculate a good UUID for the indicated partition. We want a certain degree of reproducibility,
+         * hence we won't generate the UUIDs randomly. Instead we use a cryptographic hash (precisely:
+         * HMAC-SHA256) to derive them from a single seed. The seed is generally the machine ID of the
+         * installation we are processing, but if random behaviour is desired can be random, too. We use the
+         * seed value as key for the HMAC (since the machine ID is something we generally don't want to leak)
+         * and the partition type as plaintext. The partition type is suffixed with a counter (only for the
+         * second and later partition of the same type) if we have more than one partition of the same
+         * time. Or in other words:
+         *
+         * With:
+         *     SEED := /etc/machine-id
+         *
+         * If first partition instance of type TYPE_UUID:
+         *     PARTITION_UUID := HMAC-SHA256(SEED, TYPE_UUID)
+         *
+         * For all later partition instances of type TYPE_UUID with INSTANCE being the LE64 encoded instance number:
+         *     PARTITION_UUID := HMAC-SHA256(SEED, TYPE_UUID || INSTANCE)
+         */
+
+        LIST_FOREACH(partitions, q, context->partitions) {
+                if (p == q)
+                        break;
+
+                if (!sd_id128_equal(p->type_uuid, q->type_uuid))
+                        continue;
+
+                k++;
+        }
+
+        plaintext.type_uuid = p->type_uuid;
+        plaintext.counter = htole64(k);
+
+        if (!HMAC(EVP_sha256(),
+                  &context->seed, sizeof(context->seed),
+                  (const unsigned char*) &plaintext, k == 0 ? sizeof(sd_id128_t) : sizeof(plaintext),
+                  result.md, NULL))
+                return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SHA256 calculation failed.");
+
+        /* Take the first half, mark it as v4 UUID */
+        assert_cc(sizeof(result.md) == sizeof(result.id) * 2);
+        result.id = id128_make_v4_uuid(result.id);
+
+        /* Ensure this partition UUID is actually unique, and there's no remaining partition from an earlier run? */
+        LIST_FOREACH(partitions, q, context->partitions) {
+                if (p == q)
+                        continue;
+
+                if (sd_id128_equal(q->current_uuid, result.id) ||
+                    sd_id128_equal(q->new_uuid, result.id)) {
+                        log_warning("Partition UUID calculated from seed for partition %" PRIu64 " exists already, reverting to randomized UUID.", p->partno);
+
+                        r = sd_id128_randomize(&result.id);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to generate randomized UUID: %m");
+
+                        break;
+                }
+        }
+
+        *ret = result.id;
+        return 0;
+}
+
+static int partition_acquire_label(Context *context, Partition *p, char **ret) {
+        _cleanup_free_ char *label = NULL;
+        const char *prefix;
+        unsigned k = 1;
+
+        assert(context);
+        assert(p);
+        assert(ret);
+
+        prefix = gpt_partition_type_uuid_to_string(p->type_uuid);
+        if (!prefix)
+                prefix = "linux";
+
+        for (;;) {
+                const char *ll = label ?: prefix;
+                bool retry = false;
+                Partition *q;
+
+                LIST_FOREACH(partitions, q, context->partitions) {
+                        if (p == q)
+                                break;
+
+                        if (streq_ptr(ll, q->current_label) ||
+                            streq_ptr(ll, q->new_label)) {
+                                retry = true;
+                                break;
+                        }
+                }
+
+                if (!retry)
+                        break;
+
+                label = mfree(label);
+
+
+                if (asprintf(&label, "%s-%u", prefix, ++k) < 0)
+                        return log_oom();
+        }
+
+        if (!label) {
+                label = strdup(prefix);
+                if (!label)
+                        return log_oom();
+        }
+
+        *ret = TAKE_PTR(label);
+        return 0;
+}
+
+static int context_acquire_partition_uuids_and_labels(Context *context) {
+        Partition *p;
+        int r;
+
+        assert(context);
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                assert(sd_id128_is_null(p->new_uuid));
+                assert(!p->new_label);
+
+                /* Never touch foreign partitions */
+                if (PARTITION_IS_FOREIGN(p)) {
+                        p->new_uuid = p->current_uuid;
+
+                        if (p->current_label) {
+                                p->new_label = strdup(p->current_label);
+                                if (!p->new_label)
+                                        return log_oom();
+                        }
+
+                        continue;
+                }
+
+                if (!sd_id128_is_null(p->current_uuid))
+                        p->new_uuid = p->current_uuid; /* Never change initialized UUIDs */
+                else {
+                        r = partition_acquire_uuid(context, p, &p->new_uuid);
+                        if (r < 0)
+                                return r;
+                }
+
+                if (!isempty(p->current_label)) {
+                        p->new_label = strdup(p->current_label); /* never change initialized labels */
+                        if (!p->new_label)
+                                return log_oom();
+                } else {
+                        r = partition_acquire_label(context, p, &p->new_label);
+                        if (r < 0)
+                                return r;
+                }
+        }
+
+        return 0;
+}
+
+static int device_kernel_partitions_supported(int fd) {
+        struct loop_info64 info;
+        struct stat st;
+
+        assert(fd >= 0);
+
+        if (fstat(fd, &st) < 0)
+                return log_error_errno(fd, "Failed to fstat() image file: %m");
+        if (!S_ISBLK(st.st_mode))
+                return false;
+
+        if (ioctl(fd, LOOP_GET_STATUS64, &info) < 0) {
+
+                if (ERRNO_IS_NOT_SUPPORTED(errno) || errno == EINVAL)
+                        return true; /* not a loopback device, let's assume partition are supported */
+
+                return log_error_errno(fd, "Failed to issue LOOP_GET_STATUS64 on block device: %m");
+        }
+
+#if HAVE_VALGRIND_MEMCHECK_H
+        /* Valgrind currently doesn't know LOOP_GET_STATUS64. Remove this once it does */
+        VALGRIND_MAKE_MEM_DEFINED(&info, sizeof(info));
+#endif
+
+        return FLAGS_SET(info.lo_flags, LO_FLAGS_PARTSCAN);
+}
+
+static int context_write_partition_table(
+                Context *context,
+                const char *node,
+                bool from_scratch) {
+
+        _cleanup_(fdisk_unref_tablep) struct fdisk_table *original_table = NULL;
+        int capable, r;
+        Partition *p;
+
+        assert(context);
+
+        if (arg_pretty > 0 ||
+            (arg_pretty < 0 && isatty(STDOUT_FILENO) > 0)) {
+
+                if (context->n_partitions == 0)
+                        puts("Empty partition table.");
+                else
+                        (void) context_dump_partitions(context, node);
+
+                putc('\n', stdout);
+
+                (void) context_dump_partition_bar(context, node);
+                putc('\n', stdout);
+                fflush(stdout);
+        }
+
+        if (!from_scratch && !context_changed(context)) {
+                log_info("No changes.");
+                return 0;
+        }
+
+        if (arg_dry_run) {
+                log_notice("Refusing to repartition, please re-run with --dry-run=no.");
+                return 0;
+        }
+
+        log_info("Applying changes.");
+
+        if (from_scratch) {
+                r = context_discard_range(context, 0, context->total);
+                if (r == -EOPNOTSUPP)
+                        log_info("Storage does not support discarding, not discarding entire block device data.");
+                else if (r < 0)
+                        return log_error_errno(r, "Failed to discard entire block device: %m");
+                else if (r > 0)
+                        log_info("Discarded entire block device.");
+        }
+
+        r = fdisk_get_partitions(context->fdisk_context, &original_table);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire partition table: %m");
+
+        /* Wipe fs signatures and discard sectors where the new partitions are going to be placed and in the
+         * gaps between partitions, just to be sure. */
+        r = context_wipe_and_discard(context, from_scratch);
+        if (r < 0)
+                return r;
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+                if (p->dropped)
+                        continue;
+
+                assert(p->new_size != UINT64_MAX);
+                assert(p->offset != UINT64_MAX);
+                assert(p->partno != UINT64_MAX);
+
+                if (PARTITION_EXISTS(p)) {
+                        bool changed = false;
+
+                        assert(p->current_partition);
+
+                        if (p->new_size != p->current_size) {
+                                assert(p->new_size >= p->current_size);
+                                assert(p->new_size % 512 == 0);
+
+                                r = fdisk_partition_size_explicit(p->current_partition, true);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to enable explicit sizing: %m");
+
+                                r = fdisk_partition_set_size(p->current_partition, p->new_size / 512);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to grow partition: %m");
+
+                                log_info("Growing existing partition %" PRIu64 ".", p->partno);
+                                changed = true;
+                        }
+
+                        if (!sd_id128_equal(p->new_uuid, p->current_uuid)) {
+                                char buf[ID128_UUID_STRING_MAX];
+
+                                assert(!sd_id128_is_null(p->new_uuid));
+
+                                r = fdisk_partition_set_uuid(p->current_partition, id128_to_uuid_string(p->new_uuid, buf));
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set partition UUID: %m");
+
+                                log_info("Initializing UUID of existing partition %" PRIu64 ".", p->partno);
+                                changed = true;
+                        }
+
+                        if (!streq_ptr(p->new_label, p->current_label)) {
+                                assert(!isempty(p->new_label));
+
+                                r = fdisk_partition_set_name(p->current_partition, p->new_label);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to set partition label: %m");
+
+                                log_info("Setting partition label of existing partition %" PRIu64 ".", p->partno);
+                                changed = true;
+                        }
+
+                        if (changed) {
+                                assert(!PARTITION_IS_FOREIGN(p)); /* never touch foreign partitions */
+
+                                r = fdisk_set_partition(context->fdisk_context, p->partno, p->current_partition);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to update partition: %m");
+                        }
+                } else {
+                        _cleanup_(fdisk_unref_partitionp) struct fdisk_partition *q = NULL;
+                        _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *t = NULL;
+                        char ids[ID128_UUID_STRING_MAX];
+
+                        assert(!p->new_partition);
+                        assert(p->offset % 512 == 0);
+                        assert(p->new_size % 512 == 0);
+                        assert(!sd_id128_is_null(p->new_uuid));
+                        assert(!isempty(p->new_label));
+
+                        t = fdisk_new_parttype();
+                        if (!t)
+                                return log_oom();
+
+                        r = fdisk_parttype_set_typestr(t, id128_to_uuid_string(p->type_uuid, ids));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to initialize partition type: %m");
+
+                        q = fdisk_new_partition();
+                        if (!q)
+                                return log_oom();
+
+                        r = fdisk_partition_set_type(q, t);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set partition type: %m");
+
+                        r = fdisk_partition_size_explicit(q, true);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to enable explicit sizing: %m");
+
+                        r = fdisk_partition_set_start(q, p->offset / 512);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to position partition: %m");
+
+                        r = fdisk_partition_set_size(q, p->new_size / 512);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to grow partition: %m");
+
+                        r = fdisk_partition_set_partno(q, p->partno);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set partition number: %m");
+
+                        r = fdisk_partition_set_uuid(q, id128_to_uuid_string(p->new_uuid, ids));
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set partition UUID: %m");
+
+                        r = fdisk_partition_set_name(q, p->new_label);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to set partition label: %m");
+
+                        log_info("Creating new partition %" PRIu64 ".", p->partno);
+
+                        r = fdisk_add_partition(context->fdisk_context, q, NULL);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to add partition: %m");
+
+                        assert(!p->new_partition);
+                        p->new_partition = TAKE_PTR(q);
+                }
+        }
+
+        log_info("Writing new partition table.");
+
+        r = fdisk_write_disklabel(context->fdisk_context);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write partition table: %m");
+
+        capable = device_kernel_partitions_supported(fdisk_get_devfd(context->fdisk_context));
+        if (capable < 0)
+                return capable;
+        if (capable > 0) {
+                log_info("Telling kernel to reread partition table.");
+
+                if (from_scratch)
+                        r = fdisk_reread_partition_table(context->fdisk_context);
+                else
+                        r = fdisk_reread_changes(context->fdisk_context, original_table);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to reread partition table: %m");
+        } else
+                log_notice("Not telling kernel to reread partition table, because selected image does not support kernel partition block devices.");
+
+        log_info("All done.");
+
+        return 0;
+}
+
+static int context_read_seed(Context *context, const char *root) {
+        int r;
+
+        assert(context);
+
+        if (!sd_id128_is_null(context->seed))
+                return 0;
+
+        if (!arg_randomize) {
+                _cleanup_close_ int fd = -1;
+
+                fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, NULL);
+                if (fd == -ENOENT)
+                        log_info("No machine ID set, using randomized partition UUIDs.");
+                else if (fd < 0)
+                        return log_error_errno(fd, "Failed to determine machine ID of image: %m");
+                else {
+                        r = id128_read_fd(fd, ID128_PLAIN, &context->seed);
+                        if (r == -ENOMEDIUM)
+                                log_info("No machine ID set, using randomized partition UUIDs.");
+                        else if (r < 0)
+                                return log_error_errno(r, "Failed to parse machine ID of image: %m");
+
+                        return 0;
+                }
+        }
+
+        r = sd_id128_randomize(&context->seed);
+        if (r < 0)
+                return log_error_errno(r, "Failed to generate randomized seed: %m");
+
+        return 0;
+}
+
+static int context_factory_reset(Context *context, bool from_scratch) {
+        Partition *p;
+        size_t n = 0;
+        int r;
+
+        assert(context);
+
+        if (arg_factory_reset <= 0)
+                return 0;
+
+        if (from_scratch) /* Nothing to reset if we start from scratch */
+                return 0;
+
+        if (arg_dry_run) {
+                log_notice("Refusing to factory reset, please re-run with --dry-run=no.");
+                return 0;
+        }
+
+        log_info("Applying factory reset.");
+
+        LIST_FOREACH(partitions, p, context->partitions) {
+
+                if (!p->factory_reset || !PARTITION_EXISTS(p))
+                        continue;
+
+                assert(p->partno != UINT64_MAX);
+
+                log_info("Removing partition %" PRIu64 " for factory reset.", p->partno);
+
+                r = fdisk_delete_partition(context->fdisk_context, p->partno);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to remove partition %" PRIu64 ": %m", p->partno);
+
+                n++;
+        }
+
+        if (n == 0) {
+                log_info("Factory reset requested, but no partitions to delete found.");
+                return 0;
+        }
+
+        r = fdisk_write_disklabel(context->fdisk_context);
+        if (r < 0)
+                return log_error_errno(r, "Failed to write disk label: %m");
+
+        log_info("Successfully deleted %zu partitions.", n);
+        return 1;
+}
+
+static int context_can_factory_reset(Context *context) {
+        Partition *p;
+
+        assert(context);
+
+        LIST_FOREACH(partitions, p, context->partitions)
+                if (p->factory_reset && PARTITION_EXISTS(p))
+                        return true;
+
+        return false;
+}
+
+static int help(void) {
+        _cleanup_free_ char *link = NULL;
+        int r;
+
+        r = terminal_urlify_man("systemd-repart", "1", &link);
+        if (r < 0)
+                return log_oom();
+
+        printf("%s [OPTIONS...] [DEVICE]\n"
+               "\n%sGrow and add partitions to partition table.%s\n\n"
+               "  -h --help               Show this help\n"
+               "     --version            Show package version\n"
+               "     --dry-run=BOOL       Whether to run dry-run operation\n"
+               "     --empty=MODE         One of refuse, allow, require, force; controls how to\n"
+               "                          handle empty disks lacking partition table\n"
+               "     --discard=BOOL       Whether to discard backing blocks for new partitions\n"
+               "     --pretty=BOOL        Whether to show pretty summary before executing operation\n"
+               "     --factory-reset=BOOL Whether to remove data partitions before recreating\n"
+               "                          them\n"
+               "     --can-factory-reset  Test whether factory reset is defined\n"
+               "     --root=PATH          Operate relative to root path\n"
+               "     --definitions=DIR    Find partitions in specified directory\n"
+               "     --seed=UUID          128bit seed UUID to derive all UUIDs from\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_DRY_RUN,
+                ARG_EMPTY,
+                ARG_DISCARD,
+                ARG_FACTORY_RESET,
+                ARG_CAN_FACTORY_RESET,
+                ARG_ROOT,
+                ARG_SEED,
+                ARG_PRETTY,
+                ARG_DEFINITIONS,
+        };
+
+        static const struct option options[] = {
+                { "help",              no_argument,       NULL, 'h'                   },
+                { "version",           no_argument,       NULL, ARG_VERSION           },
+                { "dry-run",           required_argument, NULL, ARG_DRY_RUN           },
+                { "empty",             required_argument, NULL, ARG_EMPTY             },
+                { "discard",           required_argument, NULL, ARG_DISCARD           },
+                { "factory-reset",     required_argument, NULL, ARG_FACTORY_RESET     },
+                { "can-factory-reset", no_argument,       NULL, ARG_CAN_FACTORY_RESET },
+                { "root",              required_argument, NULL, ARG_ROOT              },
+                { "seed",              required_argument, NULL, ARG_SEED              },
+                { "pretty",            required_argument, NULL, ARG_PRETTY            },
+                { "definitions",       required_argument, NULL, ARG_DEFINITIONS       },
+                {}
+        };
+
+        int c, r;
+
+        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_DRY_RUN:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --dry-run= parameter: %s", optarg);
+
+                        arg_dry_run = r;
+                        break;
+
+                case ARG_EMPTY:
+                        if (isempty(optarg) || streq(optarg, "refuse"))
+                                arg_empty = EMPTY_REFUSE;
+                        else if (streq(optarg, "allow"))
+                                arg_empty = EMPTY_ALLOW;
+                        else if (streq(optarg, "require"))
+                                arg_empty = EMPTY_REQUIRE;
+                        else if (streq(optarg, "force"))
+                                arg_empty = EMPTY_FORCE;
+                        else
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                       "Failed to parse --empty= parameter: %s", optarg);
+                        break;
+
+                case ARG_DISCARD:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --discard= parameter: %s", optarg);
+
+                        arg_discard = r;
+                        break;
+
+                case ARG_FACTORY_RESET:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --factory-reset= parameter: %s", optarg);
+
+                        arg_factory_reset = r;
+                        break;
+
+                case ARG_CAN_FACTORY_RESET:
+                        arg_can_factory_reset = true;
+                        break;
+
+                case ARG_ROOT:
+                        r = parse_path_argument_and_warn(optarg, false, &arg_root);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case ARG_SEED:
+                        if (isempty(optarg)) {
+                                arg_seed = SD_ID128_NULL;
+                                arg_randomize = false;
+                        } else if (streq(optarg, "random"))
+                                arg_randomize = true;
+                        else {
+                                r = sd_id128_from_string(optarg, &arg_seed);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse seed: %s", optarg);
+
+                                arg_randomize = false;
+                        }
+
+                        break;
+
+                case ARG_PRETTY:
+                        r = parse_boolean(optarg);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to parse --pretty= parameter: %s", optarg);
+
+                        arg_pretty = r;
+                        break;
+
+                case ARG_DEFINITIONS:
+                        r = parse_path_argument_and_warn(optarg, false, &arg_definitions);
+                        if (r < 0)
+                                return r;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+
+        if (argc - optind > 1)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Expected at most one argument, the path to the block device.");
+
+        if (arg_factory_reset > 0 && IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "Combination of --factory-reset=yes and --empty=force/--empty=require is invalid.");
+
+        if (arg_can_factory_reset)
+                arg_dry_run = true;
+
+        arg_node = argc > optind ? argv[optind] : NULL;
+        return 1;
+}
+
+static int parse_proc_cmdline_factory_reset(void) {
+        bool b;
+        int r;
+
+        if (arg_factory_reset >= 0) /* Never override what is specified on the process command line */
+                return 0;
+
+        if (!in_initrd()) /* Never honour kernel command line factory reset request outside of the initrd */
+                return 0;
+
+        r = proc_cmdline_get_bool("systemd.factory_reset", &b);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse systemd.factory_reset kernel command line argument: %m");
+        if (r > 0) {
+                arg_factory_reset = b;
+
+                if (b)
+                        log_notice("Honouring factory reset requested via kernel command line.");
+        }
+
+        return 0;
+}
+
+static int parse_efi_variable_factory_reset(void) {
+        _cleanup_free_ char *value = NULL;
+        int r;
+
+        if (arg_factory_reset >= 0) /* Never override what is specified on the process command line */
+                return 0;
+
+        if (!in_initrd()) /* Never honour EFI variable factory reset request outside of the initrd */
+                return 0;
+
+        r = efi_get_variable_string(EFI_VENDOR_SYSTEMD, "FactoryReset", &value);
+        if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r))
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to read EFI variable FactoryReset: %m");
+
+        r = parse_boolean(value);
+        if (r < 0)
+                return log_error_errno(r, "Failed to parse EFI variable FactoryReset: %m");
+
+        arg_factory_reset = r;
+        if (r)
+                log_notice("Honouring factory reset requested via EFI variable FactoryReset: %m");
+
+        return 0;
+}
+
+static int remove_efi_variable_factory_reset(void) {
+        int r;
+
+        r = efi_set_variable(EFI_VENDOR_SYSTEMD, "FactoryReset", NULL, 0);
+        if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r))
+                return 0;
+        if (r < 0)
+                return log_error_errno(r, "Failed to remove EFI variable FactoryReset: %m");
+
+        log_info("Successfully unset EFI variable FactoryReset.");
+        return 0;
+}
+
+static int acquire_root_devno(const char *p, int mode, char **ret) {
+        _cleanup_close_ int fd = -1;
+        struct stat st;
+        dev_t devno;
+        int r;
+
+        fd = open(p, mode);
+        if (fd < 0)
+                return -errno;
+
+        if (fstat(fd, &st) < 0)
+                return -errno;
+
+        if (S_ISREG(st.st_mode)) {
+                char *s;
+
+                s = strdup(p);
+                if (!s)
+                        return log_oom();
+
+                *ret = s;
+                return 0;
+        }
+
+        if (S_ISBLK(st.st_mode))
+                devno = st.st_rdev;
+        else if (S_ISDIR(st.st_mode)) {
+
+                devno = st.st_dev;
+
+                if (major(st.st_dev) == 0) {
+                        r = btrfs_get_block_device_fd(fd, &devno);
+                        if (r == -ENOTTY) /* not btrfs */
+                                return -ENODEV;
+                        if (r < 0)
+                                return r;
+                }
+
+        } else
+                return -ENOTBLK;
+
+        /* From dm-crypt to backing partition */
+        r = block_get_originating(devno, &devno);
+        if (r < 0)
+                log_debug_errno(r, "Failed to find underlying block device for '%s', ignoring: %m", p);
+
+        /* From partition to whole disk containing it */
+        r = block_get_whole_disk(devno, &devno);
+        if (r < 0)
+                log_debug_errno(r, "Failed to find whole disk block device for '%s', ingoring: %m", p);
+
+        return device_path_make_canonical(S_IFBLK, devno, ret);
+}
+
+static int find_root(char **ret) {
+        const char *t;
+        int r;
+
+        if (arg_node) {
+                r = acquire_root_devno(arg_node, O_RDONLY|O_CLOEXEC, ret);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to determine backing device of %s: %m", arg_node);
+
+                return 0;
+        }
+
+        /* Let's search for the root device. We look for two cases here: first in /, and then in /usr. The
+         * latter we check for cases where / is a tmpfs and only /usr is an actual persistent block device
+         * (think: volatile setups) */
+
+        FOREACH_STRING(t, "/", "/usr") {
+                _cleanup_free_ char *j = NULL;
+                const char *p;
+
+                if (in_initrd()) {
+                        j = path_join("/sysroot", t);
+                        if (!j)
+                                return log_oom();
+
+                        p = j;
+                } else
+                        p = t;
+
+                r = acquire_root_devno(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC, ret);
+                if (r < 0) {
+                        if (r != -ENODEV)
+                                return log_error_errno(r, "Failed to determine backing device of %s: %m", p);
+                } else
+                        return 0;
+        }
+
+        return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "Failed to discover root block device.");
+}
+
+static int run(int argc, char *argv[]) {
+        _cleanup_(context_freep) Context* context = NULL;
+        _cleanup_free_ char *node = NULL;
+        bool from_scratch;
+        int r;
+
+        log_show_color(true);
+        log_parse_environment();
+        log_open();
+
+        if (in_initrd()) {
+                /* Default to operation on /sysroot when invoked in the initrd! */
+                arg_root = strdup("/sysroot");
+                if (!arg_root)
+                        return log_oom();
+        }
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r;
+
+        r = parse_proc_cmdline_factory_reset();
+        if (r < 0)
+                return r;
+
+        r = parse_efi_variable_factory_reset();
+        if (r < 0)
+                return r;
+
+        r = find_root(&node);
+        if (r < 0)
+                return r;
+
+        context = context_new(arg_seed);
+        if (!context)
+                return log_oom();
+
+        r = context_read_definitions(context, arg_definitions, arg_root);
+        if (r < 0)
+                return r;
+
+        r = context_load_partition_table(context, node);
+        if (r == -EHWPOISON)
+                return 77; /* Special return value which means "Not GPT, so not doing anything". This isn't
+                            * really an error when called at boot. */
+        if (r < 0)
+                return r;
+        from_scratch = r > 0; /* Starting from scratch */
+
+        if (arg_can_factory_reset) {
+                r = context_can_factory_reset(context);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        return EXIT_FAILURE;
+
+                return 0;
+        }
+
+        r = context_factory_reset(context, from_scratch);
+        if (r < 0)
+                return r;
+        if (r > 0) {
+                /* We actually did a factory reset! */
+                r = remove_efi_variable_factory_reset();
+                if (r < 0)
+                        return r;
+
+                /* Reload the reduced partition table */
+                context_unload_partition_table(context);
+                r = context_load_partition_table(context, node);
+                if (r < 0)
+                        return r;
+        }
+
+#if 0
+        (void) context_dump_partitions(context, node);
+        putchar('\n');
+#endif
+
+        r = context_read_seed(context, arg_root);
+        if (r < 0)
+                return r;
+
+        /* First try to fit new partitions in, dropping by priority until it fits */
+        for (;;) {
+                if (context_allocate_partitions(context))
+                        break; /* Success! */
+
+                if (!context_drop_one_priority(context))
+                        return log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
+                                               "Can't fit requested partitions into free space, refusing.");
+        }
+
+        /* Now assign free space according to the weight logic */
+        r = context_grow_partitions(context);
+        if (r < 0)
+                return r;
+
+        /* Now calculate where each partition gets placed */
+        context_place_partitions(context);
+
+        /* Make sure each partition has a unique UUID and unique label */
+        r = context_acquire_partition_uuids_and_labels(context);
+        if (r < 0)
+                return r;
+
+        r = context_write_partition_table(context, node, from_scratch);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
index 89168c3c43bab79bb1c6ec0ae84ec94c088f5d4c..0fa05434efb34094325b808d7f3be1e344f08221 100644 (file)
@@ -3,7 +3,7 @@
 #include "alloc-util.h"
 #include "btrfs-util.h"
 #include "bus-common-errors.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "fd-util.h"
 #include "io-util.h"
 #include "machine-image.h"
index fd2b7c99944a062565bd7add3b0b800e8c7b7c8f..2bd1c495e4e0ee540202780230480a74be27886a 100644 (file)
@@ -8,6 +8,7 @@
 #include "alloc-util.h"
 #include "bus-common-errors.h"
 #include "bus-label.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "fd-util.h"
 #include "fileio.h"
index c74ec429627b721a6b8d85d857437d5eea2cbf2e..75b76926e5565be0e0b4370d17ff6cfcb59e6bb7 100644 (file)
@@ -7,7 +7,7 @@
 #include "sd-daemon.h"
 
 #include "alloc-util.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "def.h"
 #include "main-func.h"
 #include "portabled-bus.h"
index ff6acb2eac1fac0b14d54dde7b35f7a8fad07797..ffe61bdb9f5f85fe1d0645b926c380ebff65462b 100644 (file)
@@ -2,6 +2,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "dns-domain.h"
 #include "memory-util.h"
index 89e48403f090223d128b2b1a77b41322ca1c1548..92842bcf89d43f8646b768f1b3333a8b88db4780 100644 (file)
 
 static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d");
 
-/* The first DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */
-static const uint8_t root_digest1[] =
-        { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60,
-          0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 };
-
 /* The second DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved February 2017 */
 static const uint8_t root_digest2[] =
         { 0xE0, 0x6D, 0x44, 0xB8, 0x0B, 0x8F, 0x1D, 0x39, 0xA9, 0x5C, 0x0B, 0x0D, 0x7C, 0x65, 0xD0, 0x84,
@@ -96,11 +91,7 @@ static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) {
         if (!answer)
                 return -ENOMEM;
 
-        /* Add the two RRs from https://data.iana.org/root-anchors/root-anchors.xml */
-        r = add_root_ksk(answer, key, 19036, DNSSEC_ALGORITHM_RSASHA256, DNSSEC_DIGEST_SHA256, root_digest1, sizeof(root_digest1));
-        if (r < 0)
-                return r;
-
+        /* Add the currently valid RRs from https://data.iana.org/root-anchors/root-anchors.xml */
         r = add_root_ksk(answer, key, 20326, DNSSEC_ALGORITHM_RSASHA256, DNSSEC_DIGEST_SHA256, root_digest2, sizeof(root_digest2));
         if (r < 0)
                 return r;
index 24bb37b35e848eaaf59195267ad887046f94b508..f7dcb3bfa56f8fb4afc035e9ab165d3211129b21 100644 (file)
@@ -1,9 +1,10 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
 
 #include "alloc-util.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "missing_capability.h"
-#include "resolved-dnssd.h"
 #include "resolved-dnssd-bus.h"
+#include "resolved-dnssd.h"
 #include "resolved-link.h"
 #include "strv.h"
 #include "user-util.h"
index a8480f190a6f96b04b15e55f4c85acd4573477a3..2a166c11b04b65102b0a96bb5a97c4efb0870de5 100644 (file)
@@ -6,6 +6,7 @@
 
 #include "alloc-util.h"
 #include "bus-common-errors.h"
+#include "bus-polkit.h"
 #include "bus-util.h"
 #include "parse-util.h"
 #include "resolve-util.h"
index af91a8ec1a5e7d0379b432652c4482c43aa3cf41..4f72077720b1ca8aad518d482759bdf961f7a56d 100644 (file)
@@ -14,7 +14,7 @@
 
 #include "af-list.h"
 #include "alloc-util.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "dirent-util.h"
 #include "dns-domain.h"
 #include "fd-util.h"
index 699b101b3901688f05311decd72c2a7bf2edc6be..13d7b2f16075ae37cfea2d8e82d39d3e04d473eb 100644 (file)
@@ -164,6 +164,7 @@ void boot_config_free(BootConfig *config) {
         free(config->auto_entries);
         free(config->auto_firmware);
         free(config->console_mode);
+        free(config->random_seed_mode);
 
         free(config->entry_oneshot);
         free(config->entry_default);
@@ -229,6 +230,8 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) {
                         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"))
+                        r = free_and_strdup(&config->random_seed_mode, p);
                 else {
                         log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field);
                         continue;
index a825b35bc58c829caabb0036e5ea44e4b98c1a89..b40680b643b3b3ff39419ec763e9cbc016f09f44 100644 (file)
@@ -43,6 +43,7 @@ typedef struct BootConfig {
         char *auto_entries;
         char *auto_firmware;
         char *console_mode;
+        char *random_seed_mode;
 
         char *entry_oneshot;
         char *entry_default;
diff --git a/src/shared/bus-polkit.c b/src/shared/bus-polkit.c
new file mode 100644 (file)
index 0000000..da4aee5
--- /dev/null
@@ -0,0 +1,358 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "bus-internal.h"
+#include "bus-message.h"
+#include "bus-polkit.h"
+#include "strv.h"
+#include "user-util.h"
+
+static int 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;
+
+        assert(m);
+
+        if (good_user == UID_INVALID)
+                return 0;
+
+        r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
+        if (r < 0)
+                return r;
+
+        /* Don't trust augmented credentials for authorization */
+        assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM);
+
+        r = sd_bus_creds_get_euid(creds, &sender_uid);
+        if (r < 0)
+                return r;
+
+        return sender_uid == good_user;
+}
+
+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) {
+
+        int r;
+
+        assert(call);
+        assert(action);
+
+        /* Tests non-interactively! */
+
+        r = check_good_user(call, good_user);
+        if (r != 0)
+                return r;
+
+        r = sd_bus_query_sender_privilege(call, capability);
+        if (r < 0)
+                return r;
+        else if (r > 0)
+                return 1;
+#if ENABLE_POLKIT
+        else {
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL;
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+                int authorized = false, challenge = false;
+                const char *sender, **k, **v;
+
+                sender = sd_bus_message_get_sender(call);
+                if (!sender)
+                        return -EBADMSG;
+
+                r = sd_bus_message_new_method_call(
+                                call->bus,
+                                &request,
+                                "org.freedesktop.PolicyKit1",
+                                "/org/freedesktop/PolicyKit1/Authority",
+                                "org.freedesktop.PolicyKit1.Authority",
+                                "CheckAuthorization");
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append(
+                                request,
+                                "(sa{sv})s",
+                                "system-bus-name", 1, "name", "s", sender,
+                                action);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_open_container(request, 'a', "{ss}");
+                if (r < 0)
+                        return r;
+
+                STRV_FOREACH_PAIR(k, v, details) {
+                        r = sd_bus_message_append(request, "{ss}", *k, *v);
+                        if (r < 0)
+                                return r;
+                }
+
+                r = sd_bus_message_close_container(request);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_append(request, "us", 0, NULL);
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_call(call->bus, request, 0, e, &reply);
+                if (r < 0) {
+                        /* Treat no PK available as access denied */
+                        if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) {
+                                sd_bus_error_free(e);
+                                return -EACCES;
+                        }
+
+                        return r;
+                }
+
+                r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
+                if (r < 0)
+                        return r;
+
+                r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
+                if (r < 0)
+                        return r;
+
+                if (authorized)
+                        return 1;
+
+                if (_challenge) {
+                        *_challenge = challenge;
+                        return 0;
+                }
+        }
+#endif
+
+        return -EACCES;
+}
+
+#if ENABLE_POLKIT
+
+typedef struct AsyncPolkitQuery {
+        sd_bus_message *request, *reply;
+        sd_bus_message_handler_t callback;
+        void *userdata;
+        sd_bus_slot *slot;
+        Hashmap *registry;
+} AsyncPolkitQuery;
+
+static void async_polkit_query_free(AsyncPolkitQuery *q) {
+
+        if (!q)
+                return;
+
+        sd_bus_slot_unref(q->slot);
+
+        if (q->registry && q->request)
+                hashmap_remove(q->registry, q->request);
+
+        sd_bus_message_unref(q->request);
+        sd_bus_message_unref(q->reply);
+
+        free(q);
+}
+
+static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
+        AsyncPolkitQuery *q = userdata;
+        int r;
+
+        assert(reply);
+        assert(q);
+
+        q->slot = sd_bus_slot_unref(q->slot);
+        q->reply = sd_bus_message_ref(reply);
+
+        r = sd_bus_message_rewind(q->request, true);
+        if (r < 0) {
+                r = sd_bus_reply_method_errno(q->request, r, NULL);
+                goto finish;
+        }
+
+        r = q->callback(q->request, q->userdata, &error_buffer);
+        r = bus_maybe_reply_error(q->request, r, &error_buffer);
+
+finish:
+        async_polkit_query_free(q);
+
+        return r;
+}
+
+#endif
+
+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) {
+
+#if ENABLE_POLKIT
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
+        AsyncPolkitQuery *q;
+        const char *sender, **k, **v;
+        sd_bus_message_handler_t callback;
+        void *userdata;
+        int c;
+#endif
+        int r;
+
+        assert(call);
+        assert(action);
+        assert(registry);
+
+        r = check_good_user(call, good_user);
+        if (r != 0)
+                return r;
+
+#if ENABLE_POLKIT
+        q = hashmap_get(*registry, call);
+        if (q) {
+                int authorized, challenge;
+
+                /* This is the second invocation of this function, and
+                 * there's already a response from polkit, let's
+                 * process it */
+                assert(q->reply);
+
+                if (sd_bus_message_is_method_error(q->reply, NULL)) {
+                        const sd_bus_error *e;
+
+                        e = sd_bus_message_get_error(q->reply);
+
+                        /* Treat no PK available as access denied */
+                        if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
+                            sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER))
+                                return -EACCES;
+
+                        /* Copy error from polkit reply */
+                        sd_bus_error_copy(error, e);
+                        return -sd_bus_error_get_errno(e);
+                }
+
+                r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}");
+                if (r >= 0)
+                        r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge);
+                if (r < 0)
+                        return r;
+
+                if (authorized)
+                        return 1;
+
+                if (challenge)
+                        return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
+
+                return -EACCES;
+        }
+#endif
+
+        r = sd_bus_query_sender_privilege(call, capability);
+        if (r < 0)
+                return r;
+        else if (r > 0)
+                return 1;
+
+#if ENABLE_POLKIT
+        if (sd_bus_get_current_message(call->bus) != call)
+                return -EINVAL;
+
+        callback = sd_bus_get_current_handler(call->bus);
+        if (!callback)
+                return -EINVAL;
+
+        userdata = sd_bus_get_current_userdata(call->bus);
+
+        sender = sd_bus_message_get_sender(call);
+        if (!sender)
+                return -EBADMSG;
+
+        c = sd_bus_message_get_allow_interactive_authorization(call);
+        if (c < 0)
+                return c;
+        if (c > 0)
+                interactive = true;
+
+        r = hashmap_ensure_allocated(registry, NULL);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_new_method_call(
+                        call->bus,
+                        &pk,
+                        "org.freedesktop.PolicyKit1",
+                        "/org/freedesktop/PolicyKit1/Authority",
+                        "org.freedesktop.PolicyKit1.Authority",
+                        "CheckAuthorization");
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(
+                        pk,
+                        "(sa{sv})s",
+                        "system-bus-name", 1, "name", "s", sender,
+                        action);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(pk, 'a', "{ss}");
+        if (r < 0)
+                return r;
+
+        STRV_FOREACH_PAIR(k, v, details) {
+                r = sd_bus_message_append(pk, "{ss}", *k, *v);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(pk);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_append(pk, "us", interactive, NULL);
+        if (r < 0)
+                return r;
+
+        q = new0(AsyncPolkitQuery, 1);
+        if (!q)
+                return -ENOMEM;
+
+        q->request = sd_bus_message_ref(call);
+        q->callback = callback;
+        q->userdata = userdata;
+
+        r = hashmap_put(*registry, call, q);
+        if (r < 0) {
+                async_polkit_query_free(q);
+                return r;
+        }
+
+        q->registry = *registry;
+
+        r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0);
+        if (r < 0) {
+                async_polkit_query_free(q);
+                return r;
+        }
+
+        return 0;
+#endif
+
+        return -EACCES;
+}
+
+void bus_verify_polkit_async_registry_free(Hashmap *registry) {
+#if ENABLE_POLKIT
+        hashmap_free_with_destructor(registry, async_polkit_query_free);
+#endif
+}
diff --git a/src/shared/bus-polkit.h b/src/shared/bus-polkit.h
new file mode 100644 (file)
index 0000000..29b3923
--- /dev/null
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+#include "hashmap.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);
+
+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);
+void bus_verify_polkit_async_registry_free(Hashmap *registry);
index 22a15493d7f3b8e6568437f205a45480e4a7bb95..28d85944a8a73b0b3500d98ed90a7b135de13204 100644 (file)
@@ -833,7 +833,8 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "RuntimeDirectoryPreserve",
                               "Personality",
                               "KeyringMode",
-                              "NetworkNamespacePath"))
+                              "NetworkNamespacePath",
+                              "LogNamespace"))
                 return bus_append_string(m, field, eq);
 
         if (STR_IN_SET(field, "IgnoreSIGPIPE",
@@ -854,6 +855,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "ProtectKernelTunables",
                               "ProtectKernelModules",
                               "ProtectKernelLogs",
+                              "ProtectClock",
                               "ProtectControlGroups",
                               "MountAPIVFS",
                               "CPUSchedulingResetOnFork",
index 10c05eba184772a94255bd8d259ac98bec59f989..15bc0ed71bf4099ad7a6de1abb17ce2216f2d4ad 100644 (file)
@@ -9,7 +9,6 @@
 #include <sys/socket.h>
 #include <unistd.h>
 
-#include "sd-bus-protocol.h"
 #include "sd-bus.h"
 #include "sd-daemon.h"
 #include "sd-event.h"
 #include "bus-util.h"
 #include "cap-list.h"
 #include "cgroup-util.h"
-#include "def.h"
-#include "escape.h"
-#include "fd-util.h"
 #include "mountpoint-util.h"
 #include "nsflags.h"
 #include "parse-util.h"
 #include "path-util.h"
-#include "proc-cmdline.h"
 #include "rlimit-util.h"
+#include "socket-util.h"
 #include "stdio-util.h"
 #include "strv.h"
 #include "user-util.h"
@@ -185,357 +181,6 @@ int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) {
         return has_owner;
 }
 
-static int 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;
-
-        assert(m);
-
-        if (good_user == UID_INVALID)
-                return 0;
-
-        r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds);
-        if (r < 0)
-                return r;
-
-        /* Don't trust augmented credentials for authorization */
-        assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM);
-
-        r = sd_bus_creds_get_euid(creds, &sender_uid);
-        if (r < 0)
-                return r;
-
-        return sender_uid == good_user;
-}
-
-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) {
-
-        int r;
-
-        assert(call);
-        assert(action);
-
-        /* Tests non-interactively! */
-
-        r = check_good_user(call, good_user);
-        if (r != 0)
-                return r;
-
-        r = sd_bus_query_sender_privilege(call, capability);
-        if (r < 0)
-                return r;
-        else if (r > 0)
-                return 1;
-#if ENABLE_POLKIT
-        else {
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL;
-                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
-                int authorized = false, challenge = false;
-                const char *sender, **k, **v;
-
-                sender = sd_bus_message_get_sender(call);
-                if (!sender)
-                        return -EBADMSG;
-
-                r = sd_bus_message_new_method_call(
-                                call->bus,
-                                &request,
-                                "org.freedesktop.PolicyKit1",
-                                "/org/freedesktop/PolicyKit1/Authority",
-                                "org.freedesktop.PolicyKit1.Authority",
-                                "CheckAuthorization");
-                if (r < 0)
-                        return r;
-
-                r = sd_bus_message_append(
-                                request,
-                                "(sa{sv})s",
-                                "system-bus-name", 1, "name", "s", sender,
-                                action);
-                if (r < 0)
-                        return r;
-
-                r = sd_bus_message_open_container(request, 'a', "{ss}");
-                if (r < 0)
-                        return r;
-
-                STRV_FOREACH_PAIR(k, v, details) {
-                        r = sd_bus_message_append(request, "{ss}", *k, *v);
-                        if (r < 0)
-                                return r;
-                }
-
-                r = sd_bus_message_close_container(request);
-                if (r < 0)
-                        return r;
-
-                r = sd_bus_message_append(request, "us", 0, NULL);
-                if (r < 0)
-                        return r;
-
-                r = sd_bus_call(call->bus, request, 0, e, &reply);
-                if (r < 0) {
-                        /* Treat no PK available as access denied */
-                        if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) {
-                                sd_bus_error_free(e);
-                                return -EACCES;
-                        }
-
-                        return r;
-                }
-
-                r = sd_bus_message_enter_container(reply, 'r', "bba{ss}");
-                if (r < 0)
-                        return r;
-
-                r = sd_bus_message_read(reply, "bb", &authorized, &challenge);
-                if (r < 0)
-                        return r;
-
-                if (authorized)
-                        return 1;
-
-                if (_challenge) {
-                        *_challenge = challenge;
-                        return 0;
-                }
-        }
-#endif
-
-        return -EACCES;
-}
-
-#if ENABLE_POLKIT
-
-typedef struct AsyncPolkitQuery {
-        sd_bus_message *request, *reply;
-        sd_bus_message_handler_t callback;
-        void *userdata;
-        sd_bus_slot *slot;
-        Hashmap *registry;
-} AsyncPolkitQuery;
-
-static void async_polkit_query_free(AsyncPolkitQuery *q) {
-
-        if (!q)
-                return;
-
-        sd_bus_slot_unref(q->slot);
-
-        if (q->registry && q->request)
-                hashmap_remove(q->registry, q->request);
-
-        sd_bus_message_unref(q->request);
-        sd_bus_message_unref(q->reply);
-
-        free(q);
-}
-
-static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) {
-        _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL;
-        AsyncPolkitQuery *q = userdata;
-        int r;
-
-        assert(reply);
-        assert(q);
-
-        q->slot = sd_bus_slot_unref(q->slot);
-        q->reply = sd_bus_message_ref(reply);
-
-        r = sd_bus_message_rewind(q->request, true);
-        if (r < 0) {
-                r = sd_bus_reply_method_errno(q->request, r, NULL);
-                goto finish;
-        }
-
-        r = q->callback(q->request, q->userdata, &error_buffer);
-        r = bus_maybe_reply_error(q->request, r, &error_buffer);
-
-finish:
-        async_polkit_query_free(q);
-
-        return r;
-}
-
-#endif
-
-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) {
-
-#if ENABLE_POLKIT
-        _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL;
-        AsyncPolkitQuery *q;
-        const char *sender, **k, **v;
-        sd_bus_message_handler_t callback;
-        void *userdata;
-        int c;
-#endif
-        int r;
-
-        assert(call);
-        assert(action);
-        assert(registry);
-
-        r = check_good_user(call, good_user);
-        if (r != 0)
-                return r;
-
-#if ENABLE_POLKIT
-        q = hashmap_get(*registry, call);
-        if (q) {
-                int authorized, challenge;
-
-                /* This is the second invocation of this function, and
-                 * there's already a response from polkit, let's
-                 * process it */
-                assert(q->reply);
-
-                if (sd_bus_message_is_method_error(q->reply, NULL)) {
-                        const sd_bus_error *e;
-
-                        e = sd_bus_message_get_error(q->reply);
-
-                        /* Treat no PK available as access denied */
-                        if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
-                            sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER))
-                                return -EACCES;
-
-                        /* Copy error from polkit reply */
-                        sd_bus_error_copy(error, e);
-                        return -sd_bus_error_get_errno(e);
-                }
-
-                r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}");
-                if (r >= 0)
-                        r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge);
-                if (r < 0)
-                        return r;
-
-                if (authorized)
-                        return 1;
-
-                if (challenge)
-                        return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required.");
-
-                return -EACCES;
-        }
-#endif
-
-        r = sd_bus_query_sender_privilege(call, capability);
-        if (r < 0)
-                return r;
-        else if (r > 0)
-                return 1;
-
-#if ENABLE_POLKIT
-        if (sd_bus_get_current_message(call->bus) != call)
-                return -EINVAL;
-
-        callback = sd_bus_get_current_handler(call->bus);
-        if (!callback)
-                return -EINVAL;
-
-        userdata = sd_bus_get_current_userdata(call->bus);
-
-        sender = sd_bus_message_get_sender(call);
-        if (!sender)
-                return -EBADMSG;
-
-        c = sd_bus_message_get_allow_interactive_authorization(call);
-        if (c < 0)
-                return c;
-        if (c > 0)
-                interactive = true;
-
-        r = hashmap_ensure_allocated(registry, NULL);
-        if (r < 0)
-                return r;
-
-        r = sd_bus_message_new_method_call(
-                        call->bus,
-                        &pk,
-                        "org.freedesktop.PolicyKit1",
-                        "/org/freedesktop/PolicyKit1/Authority",
-                        "org.freedesktop.PolicyKit1.Authority",
-                        "CheckAuthorization");
-        if (r < 0)
-                return r;
-
-        r = sd_bus_message_append(
-                        pk,
-                        "(sa{sv})s",
-                        "system-bus-name", 1, "name", "s", sender,
-                        action);
-        if (r < 0)
-                return r;
-
-        r = sd_bus_message_open_container(pk, 'a', "{ss}");
-        if (r < 0)
-                return r;
-
-        STRV_FOREACH_PAIR(k, v, details) {
-                r = sd_bus_message_append(pk, "{ss}", *k, *v);
-                if (r < 0)
-                        return r;
-        }
-
-        r = sd_bus_message_close_container(pk);
-        if (r < 0)
-                return r;
-
-        r = sd_bus_message_append(pk, "us", interactive, NULL);
-        if (r < 0)
-                return r;
-
-        q = new0(AsyncPolkitQuery, 1);
-        if (!q)
-                return -ENOMEM;
-
-        q->request = sd_bus_message_ref(call);
-        q->callback = callback;
-        q->userdata = userdata;
-
-        r = hashmap_put(*registry, call, q);
-        if (r < 0) {
-                async_polkit_query_free(q);
-                return r;
-        }
-
-        q->registry = *registry;
-
-        r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0);
-        if (r < 0) {
-                async_polkit_query_free(q);
-                return r;
-        }
-
-        return 0;
-#endif
-
-        return -EACCES;
-}
-
-void bus_verify_polkit_async_registry_free(Hashmap *registry) {
-#if ENABLE_POLKIT
-        hashmap_free_with_destructor(registry, async_polkit_query_free);
-#endif
-}
-
 int bus_check_peercred(sd_bus *c) {
         struct ucred ucred;
         int fd, r;
index 1e2f04cc5dffcfc21fdb33bbb9eb8e56d828bc7e..db245a791ea4fa4c21450e0394f41993e5f38dde 100644 (file)
@@ -9,8 +9,8 @@
 #include "sd-bus.h"
 #include "sd-event.h"
 
-#include "hashmap.h"
 #include "macro.h"
+#include "set.h"
 #include "string-util.h"
 #include "time-util.h"
 
@@ -52,11 +52,6 @@ int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error);
 
 int bus_check_peercred(sd_bus *c);
 
-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);
-
-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);
-void bus_verify_polkit_async_registry_free(Hashmap *registry);
-
 int bus_connect_system_systemd(sd_bus **_bus);
 int bus_connect_user_systemd(sd_bus **_bus);
 
index cb20279dda602ad45276dc223fdc1761b0d622c9..318588cc1fd6b93db1527cb3757a340768c72d00 100644 (file)
@@ -515,6 +515,7 @@ DEFINE_PARSER(long, long, safe_atoli);
 DEFINE_PARSER(uint8, uint8_t, safe_atou8);
 DEFINE_PARSER(uint16, uint16_t, safe_atou16);
 DEFINE_PARSER(uint32, uint32_t, safe_atou32);
+DEFINE_PARSER(int32, int32_t, safe_atoi32);
 DEFINE_PARSER(uint64, uint64_t, safe_atou64);
 DEFINE_PARSER(unsigned, unsigned, safe_atou);
 DEFINE_PARSER(double, double, safe_atod);
@@ -555,7 +556,7 @@ int config_parse_iec_size(const char* unit,
         return 0;
 }
 
-int config_parse_si_size(
+int config_parse_si_uint64(
                 const char* unit,
                 const char *filename,
                 unsigned line,
@@ -567,8 +568,7 @@ int config_parse_si_size(
                 void *data,
                 void *userdata) {
 
-        size_t *sz = data;
-        uint64_t v;
+        uint64_t *sz = data;
         int r;
 
         assert(filename);
@@ -576,15 +576,12 @@ int config_parse_si_size(
         assert(rvalue);
         assert(data);
 
-        r = parse_size(rvalue, 1000, &v);
-        if (r >= 0 && (uint64_t) (size_t) v != v)
-                r = -ERANGE;
+        r = parse_size(rvalue, 1000, sz);
         if (r < 0) {
                 log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value '%s', ignoring: %m", rvalue);
                 return 0;
         }
 
-        *sz = (size_t) v;
         return 0;
 }
 
index 287620ad71431a7854e3fa81c5c61f974bb981b6..2f3cb4217d01ebda574db82f7de8cdf3450c3933 100644 (file)
@@ -115,10 +115,11 @@ CONFIG_PARSER_PROTOTYPE(config_parse_long);
 CONFIG_PARSER_PROTOTYPE(config_parse_uint8);
 CONFIG_PARSER_PROTOTYPE(config_parse_uint16);
 CONFIG_PARSER_PROTOTYPE(config_parse_uint32);
+CONFIG_PARSER_PROTOTYPE(config_parse_int32);
 CONFIG_PARSER_PROTOTYPE(config_parse_uint64);
 CONFIG_PARSER_PROTOTYPE(config_parse_double);
 CONFIG_PARSER_PROTOTYPE(config_parse_iec_size);
-CONFIG_PARSER_PROTOTYPE(config_parse_si_size);
+CONFIG_PARSER_PROTOTYPE(config_parse_si_uint64);
 CONFIG_PARSER_PROTOTYPE(config_parse_iec_uint64);
 CONFIG_PARSER_PROTOTYPE(config_parse_bool);
 CONFIG_PARSER_PROTOTYPE(config_parse_tristate);
index 087c3dc3452d28aca9897777fd71fc7734e2225f..1ac6549ba543dbfbb4cfeaaa4bd02187c6940356 100644 (file)
@@ -28,6 +28,7 @@
 #include "fd-util.h"
 #include "fileio.h"
 #include "fs-util.h"
+#include "fsck-util.h"
 #include "gpt.h"
 #include "hexdecoct.h"
 #include "hostname-util.h"
@@ -278,6 +279,29 @@ static int loop_wait_for_partitions_to_appear(
                                N_DEVICE_NODE_LIST_ATTEMPTS);
 }
 
+static void check_partition_flags(
+                const char *node,
+                unsigned long long pflags,
+                unsigned long long supported) {
+
+        assert(node);
+
+        /* Mask away all flags supported by this partition's type and the three flags the UEFI spec defines generically */
+        pflags &= ~(supported | GPT_FLAG_REQUIRED_PARTITION | GPT_FLAG_NO_BLOCK_IO_PROTOCOL | GPT_FLAG_LEGACY_BIOS_BOOTABLE);
+
+        if (pflags == 0)
+                return;
+
+        /* If there are other bits set, then log about it, to make things discoverable */
+        for (unsigned i = 0; i < sizeof(pflags) * 8; i++) {
+                unsigned long long bit = 1ULL << i;
+                if (!FLAGS_SET(pflags, bit))
+                        continue;
+
+                log_debug("Unexpected partition flag %llu set on %s!", bit, node);
+        }
+}
+
 #endif
 
 int dissect_image(
@@ -484,6 +508,8 @@ int dissect_image(
 
                         if (sd_id128_equal(type_id, GPT_HOME)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -491,6 +517,8 @@ int dissect_image(
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                         } else if (sd_id128_equal(type_id, GPT_SRV)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -510,6 +538,8 @@ int dissect_image(
 
                         } else if (sd_id128_equal(type_id, GPT_XBOOTLDR)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -519,6 +549,8 @@ int dissect_image(
 #ifdef GPT_ROOT_NATIVE
                         else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -531,6 +563,8 @@ int dissect_image(
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                         } else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -549,6 +583,8 @@ int dissect_image(
 #ifdef GPT_ROOT_SECONDARY
                         else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -561,6 +597,8 @@ int dissect_image(
                                 rw = !(pflags & GPT_FLAG_READ_ONLY);
                         } else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -578,6 +616,8 @@ int dissect_image(
 #endif
                         else if (sd_id128_equal(type_id, GPT_SWAP)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -585,6 +625,8 @@ int dissect_image(
                                 fstype = "swap";
                         } else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -601,6 +643,8 @@ int dissect_image(
 
                         } else if (sd_id128_equal(type_id, GPT_TMP)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -609,6 +653,8 @@ int dissect_image(
 
                         } else if (sd_id128_equal(type_id, GPT_VAR)) {
 
+                                check_partition_flags(node, pflags, GPT_FLAG_NO_AUTO|GPT_FLAG_READ_ONLY);
+
                                 if (pflags & GPT_FLAG_NO_AUTO)
                                         continue;
 
@@ -851,6 +897,49 @@ static int is_loop_device(const char *path) {
         return true;
 }
 
+static int run_fsck(const char *node, const char *fstype) {
+        int r, exit_status;
+        pid_t pid;
+
+        assert(node);
+        assert(fstype);
+
+        r = fsck_exists(fstype);
+        if (r < 0) {
+                log_debug_errno(r, "Couldn't determine whether fsck for %s exists, proceeding anyway.", fstype);
+                return 0;
+        }
+        if (r == 0) {
+                log_debug("Not checking partition %s, as fsck for %s does not exist.", node, fstype);
+                return 0;
+        }
+
+        r = safe_fork("(fsck)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG|FORK_NULL_STDIO, &pid);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to fork off fsck: %m");
+        if (r == 0) {
+                /* Child */
+                execl("/sbin/fsck", "/sbin/fsck", "-aT", node, NULL);
+                log_debug_errno(errno, "Failed to execl() fsck: %m");
+                _exit(FSCK_OPERATIONAL_ERROR);
+        }
+
+        exit_status = wait_for_terminate_and_check("fsck", pid, 0);
+        if (exit_status < 0)
+                return log_debug_errno(exit_status, "Failed to fork off /sbin/fsck: %m");
+
+        if ((exit_status & ~FSCK_ERROR_CORRECTED) != FSCK_SUCCESS) {
+                log_debug("fsck failed with exit status %i.", exit_status);
+
+                if ((exit_status & (FSCK_SYSTEM_SHOULD_REBOOT|FSCK_ERRORS_LEFT_UNCORRECTED)) != 0)
+                        return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), "File system is corrupted, refusing.");
+
+                log_debug("Ignoring fsck error.");
+        }
+
+        return 0;
+}
+
 static int mount_partition(
                 DissectedPartition *m,
                 const char *where,
@@ -878,6 +967,12 @@ static int mount_partition(
 
         rw = m->rw && !(flags & DISSECT_IMAGE_READ_ONLY);
 
+        if (FLAGS_SET(flags, DISSECT_IMAGE_FSCK) && rw) {
+                r = run_fsck(node, fstype);
+                if (r < 0)
+                        return r;
+        }
+
         if (directory) {
                 r = chase_symlinks(directory, where, CHASE_PREFIX_ROOT, &chased, NULL);
                 if (r < 0)
index 359dc877d5890d75c42f50c00a8035d65289b0c0..6a666ca7c70bec70eaf39666b3705fdc10816040 100644 (file)
@@ -62,6 +62,7 @@ typedef enum DissectImageFlags {
         DISSECT_IMAGE_VALIDATE_OS         = 1 << 8,  /* Refuse mounting images that aren't identifiable as OS images */
         DISSECT_IMAGE_NO_UDEV             = 1 << 9,  /* Don't wait for udev initializing things */
         DISSECT_IMAGE_RELAX_VAR_CHECK     = 1 << 10, /* Don't insist that the UUID of /var is hashed from /etc/machine-id */
+        DISSECT_IMAGE_FSCK                = 1 << 11, /* File system check the partition before mounting (no effect when combined with DISSECT_IMAGE_READ_ONLY) */
 } DissectImageFlags;
 
 struct DissectedImage {
index 108f31d502bb76b41409f3ceb5f2f69aa052ea85..b05dc91ecf71bffb5ef7262eee67f6c12ac6f685 100644 (file)
@@ -63,40 +63,6 @@ struct device_path device_path__contents;
 struct device_path__packed device_path__contents _packed_;
 assert_cc(sizeof(struct device_path) == sizeof(struct device_path__packed));
 
-bool is_efi_boot(void) {
-        if (detect_container() > 0)
-                return false;
-
-        return access("/sys/firmware/efi/", F_OK) >= 0;
-}
-
-static int read_flag(const char *varname) {
-        _cleanup_free_ void *v = NULL;
-        uint8_t b;
-        size_t s;
-        int r;
-
-        if (!is_efi_boot()) /* If this is not an EFI boot, assume the queried flags are zero */
-                return 0;
-
-        r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s);
-        if (r < 0)
-                return r;
-
-        if (s != 1)
-                return -EINVAL;
-
-        b = *(uint8_t *)v;
-        return !!b;
-}
-
-bool is_efi_secure_boot(void) {
-        return read_flag("SecureBoot") > 0;
-}
-
-bool is_efi_secure_boot_setup_mode(void) {
-        return read_flag("SetupMode") > 0;
-}
 
 int efi_reboot_to_firmware_supported(void) {
         _cleanup_free_ void *v = NULL;
index 7d41fbb3593608b2128d0d62171346e689654f97..96208d25bf5ea053687d156e579063cf72164d64 100644 (file)
@@ -5,9 +5,6 @@
 
 #if ENABLE_EFI
 
-bool is_efi_boot(void);
-bool is_efi_secure_boot(void);
-bool is_efi_secure_boot_setup_mode(void);
 int efi_reboot_to_firmware_supported(void);
 int efi_get_reboot_to_firmware(void);
 int efi_set_reboot_to_firmware(bool value);
@@ -28,18 +25,6 @@ int efi_loader_get_features(uint64_t *ret);
 
 #else
 
-static inline bool is_efi_boot(void) {
-        return false;
-}
-
-static inline bool is_efi_secure_boot(void) {
-        return false;
-}
-
-static inline bool is_efi_secure_boot_setup_mode(void) {
-        return false;
-}
-
 static inline int efi_reboot_to_firmware_supported(void) {
         return -EOPNOTSUPP;
 }
index a9059a3ab7a0bd60245f7b06679ed40762defd10..00a71d64a638a6e718f44ec3340d7fa2e8b3b58b 100644 (file)
@@ -50,6 +50,8 @@ DEFINE_STRING_TABLE_LOOKUP(port, NetDevPort);
 DEFINE_CONFIG_PARSE_ENUM(config_parse_port, port, NetDevPort, "Failed to parse Port setting");
 
 static const char* const netdev_feature_table[_NET_DEV_FEAT_MAX] = {
+        [NET_DEV_FEAT_RX]   = "rx-checksum",
+        [NET_DEV_FEAT_TX]   = "tx-checksum-", /* The suffix "-" means any feature beginning with "tx-checksum-" */
         [NET_DEV_FEAT_GSO]  = "tx-generic-segmentation",
         [NET_DEV_FEAT_GRO]  = "rx-gro",
         [NET_DEV_FEAT_LRO]  = "rx-lro",
@@ -177,7 +179,7 @@ int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret) {
 }
 
 int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
-                          int *ret_autonegotiation, size_t *ret_speed,
+                          int *ret_autonegotiation, uint64_t *ret_speed,
                           Duplex *ret_duplex, NetDevPort *ret_port) {
         struct ethtool_cmd ecmd = {
                 .cmd = ETHTOOL_GSET,
@@ -498,22 +500,38 @@ static int get_stringset(int ethtool_fd, struct ifreq *ifr, int stringset_id, st
         return 0;
 }
 
-static int find_feature_index(struct ethtool_gstrings *strings, const char *feature) {
-        unsigned i;
+static int set_features_bit(
+                const struct ethtool_gstrings *strings,
+                const char *feature,
+                bool flag,
+                struct ethtool_sfeatures *sfeatures) {
+        bool found = false;
 
-        for (i = 0; i < strings->len; i++) {
-                if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature))
-                        return i;
-        }
+        assert(strings);
+        assert(feature);
+        assert(sfeatures);
+
+        for (size_t i = 0; i < strings->len; i++)
+                if (streq((char *) &strings->data[i * ETH_GSTRING_LEN], feature) ||
+                    (endswith(feature, "-") && startswith((char *) &strings->data[i * ETH_GSTRING_LEN], feature))) {
+                        size_t block, bit;
 
-        return -ENODATA;
+                        block = i / 32;
+                        bit = i % 32;
+
+                        sfeatures->features[block].valid |= 1 << bit;
+                        SET_FLAG(sfeatures->features[block].requested, 1 << bit, flag);
+                        found = true;
+                }
+
+        return found ? 0 : -ENODATA;
 }
 
 int ethtool_set_features(int *ethtool_fd, const char *ifname, int *features) {
         _cleanup_free_ struct ethtool_gstrings *strings = NULL;
         struct ethtool_sfeatures *sfeatures;
-        int block, bit, i, r;
         struct ifreq ifr = {};
+        int i, r;
 
         if (*ethtool_fd < 0) {
                 r = ethtool_connect_or_warn(ethtool_fd, true);
@@ -531,27 +549,14 @@ int ethtool_set_features(int *ethtool_fd, const char *ifname, int *features) {
         sfeatures->cmd = ETHTOOL_SFEATURES;
         sfeatures->size = DIV_ROUND_UP(strings->len, 32U);
 
-        for (i = 0; i < _NET_DEV_FEAT_MAX; i++) {
-
+        for (i = 0; i < _NET_DEV_FEAT_MAX; i++)
                 if (features[i] != -1) {
-
-                        r = find_feature_index(strings, netdev_feature_table[i]);
+                        r = set_features_bit(strings, netdev_feature_table[i], features[i], sfeatures);
                         if (r < 0) {
-                                log_warning_errno(r, "ethtool: could not find feature: %s", netdev_feature_table[i]);
+                                log_warning_errno(r, "ethtool: could not find feature, ignoring: %s", netdev_feature_table[i]);
                                 continue;
                         }
-
-                        block = r / 32;
-                        bit = r % 32;
-
-                        sfeatures->features[block].valid |= 1 << bit;
-
-                        if (features[i])
-                                sfeatures->features[block].requested |= 1 << bit;
-                        else
-                                sfeatures->features[block].requested &= ~(1 << bit);
                 }
-        }
 
         ifr.ifr_data = (void *) sfeatures;
 
@@ -734,7 +739,7 @@ int ethtool_set_glinksettings(
                 const char *ifname,
                 int autonegotiation,
                 uint32_t advertise[static N_ADVERTISE],
-                size_t speed,
+                uint64_t speed,
                 Duplex duplex,
                 NetDevPort port) {
         _cleanup_free_ struct ethtool_link_usettings *u = NULL;
index 8468a26bc2601937f3006ba6671408db9b221ff3..c1d5d7590ef9a2a777d8fa40d1922a0294a59922 100644 (file)
@@ -32,6 +32,8 @@ typedef enum WakeOnLan {
 } WakeOnLan;
 
 typedef enum NetDevFeature {
+        NET_DEV_FEAT_RX,
+        NET_DEV_FEAT_TX,
         NET_DEV_FEAT_GSO,
         NET_DEV_FEAT_GRO,
         NET_DEV_FEAT_LRO,
@@ -90,7 +92,7 @@ typedef struct netdev_ring_param {
 
 int ethtool_get_driver(int *ethtool_fd, const char *ifname, char **ret);
 int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
-                          int *ret_autonegotiation, size_t *ret_speed,
+                          int *ret_autonegotiation, uint64_t *ret_speed,
                           Duplex *ret_duplex, NetDevPort *ret_port);
 int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct ether_addr *ret);
 int ethtool_set_speed(int *ethtool_fd, const char *ifname, unsigned speed, Duplex duplex);
@@ -99,7 +101,7 @@ int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, netdev_ring
 int ethtool_set_features(int *ethtool_fd, const char *ifname, int *features);
 int ethtool_set_glinksettings(int *ethtool_fd, const char *ifname,
                               int autonegotiation, uint32_t advertise[static N_ADVERTISE],
-                              size_t speed, Duplex duplex, NetDevPort port);
+                              uint64_t speed, Duplex duplex, NetDevPort port);
 int ethtool_set_channels(int *ethtool_fd, const char *ifname, netdev_channels *channels);
 
 const char *duplex_to_string(Duplex d) _const_;
index d7cb976757c76e41c60c01a9e82e414a53abd7bc..4250130464910e521b95f09eb4415b420e8f2867 100644 (file)
@@ -4,12 +4,15 @@
 #include <net/if.h>
 #include <unistd.h>
 
+#include "sd-id128.h"
+
 #include "alloc-util.h"
 #include "fd-util.h"
 #include "fileio.h"
 #include "format-table.h"
 #include "format-util.h"
 #include "gunicode.h"
+#include "id128-util.h"
 #include "in-addr-util.h"
 #include "locale-util.h"
 #include "memory-util.h"
@@ -94,6 +97,7 @@ typedef struct TableData {
                 int percent;        /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
                 int ifindex;
                 union in_addr_union address;
+                sd_id128_t id128;
                 /* … add more here as we start supporting more cell data types … */
         };
 } TableData;
@@ -289,6 +293,10 @@ static size_t table_data_size(TableDataType type, const void *data) {
         case TABLE_IN6_ADDR:
                 return sizeof(struct in6_addr);
 
+        case TABLE_UUID:
+        case TABLE_ID128:
+                return sizeof(sd_id128_t);
+
         default:
                 assert_not_reached("Uh? Unexpected cell type");
         }
@@ -335,7 +343,6 @@ static bool table_data_matches(
 
         k = table_data_size(type, data);
         l = table_data_size(d->type, d->data);
-
         if (k != l)
                 return false;
 
@@ -778,6 +785,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         int ifindex;
                         bool b;
                         union in_addr_union address;
+                        sd_id128_t id128;
                 } buffer;
 
                 switch (type) {
@@ -901,6 +909,12 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         data = &buffer.address.in6;
                         break;
 
+                case TABLE_UUID:
+                case TABLE_ID128:
+                        buffer.id128 = va_arg(ap, sd_id128_t);
+                        data = &buffer.id128;
+                        break;
+
                 case TABLE_SET_MINIMUM_WIDTH: {
                         size_t w = va_arg(ap, size_t);
 
@@ -1137,6 +1151,10 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                 case TABLE_IN6_ADDR:
                         return memcmp(&a->address.in6, &b->address.in6, FAMILY_ADDRESS_SIZE(AF_INET6));
 
+                case TABLE_UUID:
+                case TABLE_ID128:
+                        return memcmp(&a->id128, &b->id128, sizeof(sd_id128_t));
+
                 default:
                         ;
                 }
@@ -1451,6 +1469,28 @@ static const char *table_data_format(Table *t, TableData *d) {
                 break;
         }
 
+        case TABLE_ID128: {
+                char *p;
+
+                p = new(char, SD_ID128_STRING_MAX);
+                if (!p)
+                        return NULL;
+
+                d->formatted = sd_id128_to_string(d->id128, p);
+                break;
+        }
+
+        case TABLE_UUID: {
+                char *p;
+
+                p = new(char, ID128_UUID_STRING_MAX);
+                if (!p)
+                        return NULL;
+
+                d->formatted = id128_to_uuid_string(d->id128, p);
+                break;
+        }
+
         default:
                 assert_not_reached("Unexpected type?");
         }
@@ -2155,6 +2195,16 @@ static int table_data_to_json(TableData *d, JsonVariant **ret) {
         case TABLE_IN6_ADDR:
                 return json_variant_new_array_bytes(ret, &d->address, FAMILY_ADDRESS_SIZE(AF_INET6));
 
+        case TABLE_ID128: {
+                char buf[SD_ID128_STRING_MAX];
+                return json_variant_new_string(ret, sd_id128_to_string(d->id128, buf));
+        }
+
+        case TABLE_UUID: {
+                char buf[ID128_UUID_STRING_MAX];
+                return json_variant_new_string(ret, id128_to_uuid_string(d->id128, buf));
+        }
+
         default:
                 return -EINVAL;
         }
index fa7a2bd6d6a9685db3a137c912650b2aa3e6c2d1..870a29d3856cb5c67fadc84de5824e5d21a099d5 100644 (file)
@@ -35,6 +35,8 @@ typedef enum TableDataType {
         TABLE_IFINDEX,
         TABLE_IN_ADDR,  /* Takes a union in_addr_union (or a struct in_addr) */
         TABLE_IN6_ADDR, /* Takes a union in_addr_union (or a struct in6_addr) */
+        TABLE_ID128,
+        TABLE_UUID,
         _TABLE_DATA_TYPE_MAX,
 
         /* The following are not really data types, but commands for table_add_cell_many() to make changes to
index 06e1ab803125d342fc8db4569930b0e7001fc480..1cf5887a60de1516bac1df3322d3abe8f1b4c5c3 100644 (file)
@@ -493,15 +493,21 @@ int generator_hook_up_growfs(
                 "BindsTo=%%i.mount\n"
                 "Conflicts=shutdown.target\n"
                 "After=%%i.mount\n"
-                "Before=shutdown.target %s\n"
+                "Before=shutdown.target %s\n",
+                program_invocation_short_name,
+                target);
+
+        if (empty_or_root(where)) /* Make sure the root fs is actually writable before we resize it */
+                fprintf(f,
+                        "After=systemd-remount-fs.service\n");
+
+        fprintf(f,
                 "\n"
                 "[Service]\n"
                 "Type=oneshot\n"
                 "RemainAfterExit=yes\n"
                 "ExecStart="SYSTEMD_GROWFS_PATH " %s\n"
                 "TimeoutSec=0\n",
-                program_invocation_short_name,
-                target,
                 escaped);
 
         return generator_add_symlink(dir, where_unit, "wants", unit);
diff --git a/src/shared/gpt.c b/src/shared/gpt.c
new file mode 100644 (file)
index 0000000..e62f21e
--- /dev/null
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include "gpt.h"
+#include "string-util.h"
+
+const GptPartitionType gpt_partition_type_table[] = {
+        { GPT_ROOT_X86,              "root-x86"              },
+        { GPT_ROOT_X86_VERITY,       "root-x86-verity"       },
+        { GPT_ROOT_X86_64,           "root-x86-64"           },
+        { GPT_ROOT_X86_64_VERITY,    "root-x86-64-verity"    },
+        { GPT_ROOT_ARM,              "root-arm"              },
+        { GPT_ROOT_ARM_VERITY,       "root-arm-verity"       },
+        { GPT_ROOT_ARM_64,           "root-arm64"            },
+        { GPT_ROOT_ARM_64_VERITY,    "root-arm64-verity"     },
+        { GPT_ROOT_IA64,             "root-ia64"             },
+        { GPT_ROOT_IA64_VERITY,      "root-ia64-verity"      },
+#ifdef GPT_ROOT_NATIVE
+        { GPT_ROOT_NATIVE,           "root"                  },
+        { GPT_ROOT_NATIVE_VERITY,    "root-verity"           },
+#endif
+#ifdef GPT_ROOT_SECONDARY
+        { GPT_ROOT_SECONDARY,        "root-secondary"        },
+        { GPT_ROOT_SECONDARY_VERITY, "root-secondary-verity" },
+#endif
+        { GPT_ESP,                   "esp"                   },
+        { GPT_XBOOTLDR,              "xbootldr"              },
+        { GPT_SWAP,                  "swap"                  },
+        { GPT_HOME,                  "home"                  },
+        { GPT_SRV,                   "srv"                   },
+        { GPT_VAR,                   "var"                   },
+        { GPT_TMP,                   "tmp"                   },
+        { GPT_LINUX_GENERIC,         "linux-generic",        },
+        {}
+};
+
+const char *gpt_partition_type_uuid_to_string(sd_id128_t id) {
+        for (size_t i = 0; i < ELEMENTSOF(gpt_partition_type_table) - 1; i++)
+                if (sd_id128_equal(id, gpt_partition_type_table[i].uuid))
+                        return gpt_partition_type_table[i].name;
+
+        return NULL;
+}
+
+const char *gpt_partition_type_uuid_to_string_harder(
+                sd_id128_t id,
+                char buffer[static ID128_UUID_STRING_MAX]) {
+
+        const char *s;
+
+        assert(buffer);
+
+        s = gpt_partition_type_uuid_to_string(id);
+        if (s)
+                return s;
+
+        return id128_to_uuid_string(id, buffer);
+}
+
+int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret) {
+        assert(s);
+        assert(ret);
+
+        for (size_t i = 0; i < ELEMENTSOF(gpt_partition_type_table) - 1; i++)
+                if (streq(s, gpt_partition_type_table[i].name)) {
+                        *ret = gpt_partition_type_table[i].uuid;
+                        return 0;
+                }
+
+        return sd_id128_from_string(s, ret);
+}
index 8e9b111857c40b722161bd200855e28a2269756c..26a4e1ea653c5d8bb7cb97f0e190b8804c455111 100644 (file)
@@ -5,6 +5,8 @@
 
 #include "sd-id128.h"
 
+#include "id128-util.h"
+
 /* We only support root disk discovery for x86, x86-64, Itanium and ARM for
  * now, since EFI for anything else doesn't really exist, and we only
  * care for root partitions on the same disk as the EFI ESP. */
@@ -21,6 +23,7 @@
 #define GPT_SRV         SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8)
 #define GPT_VAR         SD_ID128_MAKE(4d,21,b0,16,b5,34,45,c2,a9,fb,5c,16,e0,91,fd,2d)
 #define GPT_TMP         SD_ID128_MAKE(7e,c6,f5,57,3b,c5,4a,ca,b2,93,16,ef,5d,f6,39,d1)
+#define GPT_USER_HOME   SD_ID128_MAKE(77,3f,91,ef,66,d4,49,b5,bd,83,d6,83,bf,40,ad,16)
 
 /* Verity partitions for the root partitions above (we only define them for the root partitions, because only they are
  * are commonly read-only and hence suitable for verity). */
@@ -55,7 +58,9 @@
 #  define GPT_ROOT_NATIVE_VERITY GPT_ROOT_ARM_VERITY
 #endif
 
+#define GPT_FLAG_REQUIRED_PARTITION (1ULL << 0)
 #define GPT_FLAG_NO_BLOCK_IO_PROTOCOL (1ULL << 1)
+#define GPT_FLAG_LEGACY_BIOS_BOOTABLE (1ULL << 2)
 
 /* Flags we recognize on the root, swap, home and srv partitions when
  * doing auto-discovery. These happen to be identical to what
 #define GPT_FLAG_NO_AUTO (1ULL << 63)
 
 #define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4)
+
+const char *gpt_partition_type_uuid_to_string(sd_id128_t id);
+const char *gpt_partition_type_uuid_to_string_harder(
+                sd_id128_t id,
+                char buffer[static ID128_UUID_STRING_MAX]);
+int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret);
+
+typedef struct GptPartitionType {
+        sd_id128_t uuid;
+        const char *name;
+} GptPartitionType;
+
+extern const GptPartitionType gpt_partition_type_table[];
index 356f41050786ce67558db18e618039287f50f8d4..6237424e8243aec252eb4f33e973b22e32b6e08c 100644 (file)
 #include "pretty-print.h"
 #include "terminal-util.h"
 
-int id128_pretty_print(sd_id128_t id, Id128PrettyPrintMode mode) {
-        _cleanup_free_ char *man_link = NULL, *mod_link = NULL;
+int id128_pretty_print_sample(const char *name, sd_id128_t id) {
+       _cleanup_free_ char *man_link = NULL, *mod_link = NULL;
         const char *on, *off;
         unsigned i;
 
-        assert(mode >= 0);
-        assert(mode < _ID128_PRETTY_PRINT_MODE_MAX);
-
-        if (mode == ID128_PRINT_ID128) {
-                printf(SD_ID128_FORMAT_STR "\n",
-                       SD_ID128_FORMAT_VAL(id));
-                return 0;
-        } else if (mode == ID128_PRINT_UUID) {
-                printf(SD_ID128_UUID_FORMAT_STR "\n",
-                       SD_ID128_FORMAT_VAL(id));
-                return 0;
-        }
-
         on = ansi_highlight();
         off = ansi_normal();
 
@@ -42,24 +29,41 @@ int id128_pretty_print(sd_id128_t id, Id128PrettyPrintMode mode) {
                "As UUID:\n"
                "%s" SD_ID128_UUID_FORMAT_STR "%s\n\n"
                "As %s macro:\n"
-               "%s#define XYZ SD_ID128_MAKE(",
+               "%s#define %s SD_ID128_MAKE(",
                on, SD_ID128_FORMAT_VAL(id), off,
                on, SD_ID128_FORMAT_VAL(id), off,
                man_link,
-               on);
+               on, name);
         for (i = 0; i < 16; i++)
                 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
         printf(")%s\n\n", off);
 
         printf("As Python constant:\n"
                ">>> import %s\n"
-               ">>> %sXYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')%s\n",
+               ">>> %s%s = uuid.UUID('" SD_ID128_FORMAT_STR "')%s\n",
                mod_link,
-               on, SD_ID128_FORMAT_VAL(id), off);
+               on, name, SD_ID128_FORMAT_VAL(id), off);
 
         return 0;
 }
 
+
+int id128_pretty_print(sd_id128_t id, Id128PrettyPrintMode mode) {
+        assert(mode >= 0);
+        assert(mode < _ID128_PRETTY_PRINT_MODE_MAX);
+
+        if (mode == ID128_PRINT_ID128) {
+                printf(SD_ID128_FORMAT_STR "\n",
+                       SD_ID128_FORMAT_VAL(id));
+                return 0;
+        } else if (mode == ID128_PRINT_UUID) {
+                printf(SD_ID128_UUID_FORMAT_STR "\n",
+                       SD_ID128_FORMAT_VAL(id));
+                return 0;
+        } else
+                return id128_pretty_print_sample("XYZ", id);
+}
+
 int id128_print_new(Id128PrettyPrintMode mode) {
         sd_id128_t id;
         int r;
index 1dc5b6aae57b00b7d6b91ce3bc15fa425e718822..247558231c9a26c1eaeb382d0df03236817ebb65 100644 (file)
@@ -14,5 +14,6 @@ typedef enum Id128PrettyPrintMode {
         _ID128_PRETTY_PRINT_MODE_INVALID = -1
 } Id128PrettyPrintMode;
 
+int id128_pretty_print_sample(const char *name, sd_id128_t id);
 int id128_pretty_print(sd_id128_t id, Id128PrettyPrintMode mode);
 int id128_print_new(Id128PrettyPrintMode mode);
index f7770e7df50aea23fc799a426304295fea624106..0a57c0899090fd6d1ada682e95a48e2b329e43c1 100644 (file)
@@ -4,6 +4,8 @@
 
 #include "alloc-util.h"
 #include "btrfs-util.h"
+#include "chattr-util.h"
+#include "errno-util.h"
 #include "import-util.h"
 #include "log.h"
 #include "macro.h"
@@ -163,3 +165,15 @@ int import_assign_pool_quota_and_warn(const char *path) {
 
         return 0;
 }
+
+int import_set_nocow_and_log(int fd, const char *path) {
+        int r;
+
+        r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL, NULL);
+        if (r < 0)
+                return log_full_errno(
+                                ERRNO_IS_NOT_SUPPORTED(r) ? LOG_DEBUG : LOG_WARNING,
+                                r, "Failed to set file attributes on %s: %m", path);
+
+        return 0;
+}
index 0f2a5170c85ca9a0ec9011f70cf6bc705a3ce6e3..d85aa565cc1e5bd089f28a158b22eaf9507c187f 100644 (file)
@@ -23,3 +23,5 @@ int tar_strip_suffixes(const char *name, char **ret);
 int raw_strip_suffixes(const char *name, char **ret);
 
 int import_assign_pool_quota_and_warn(const char *path);
+
+int import_set_nocow_and_log(int fd, const char *path);
index 869aa279eeef008a0216272913ea5981327b16d4..e9ae88c7473ed323a0e9cc08a077e42e9a8762ae 100644 (file)
 #include "user-util.h"
 #include "utf8.h"
 
-/* Refuse putting together variants with a larger depth than 4K by default (as a protection against overflowing stacks
+/* Refuse putting together variants with a larger depth than 2K by default (as a protection against overflowing stacks
  * if code processes JSON objects recursively. Note that we store the depth in an uint16_t, hence make sure this
  * remains under 2^16.
- * The value was 16k, but it was discovered to be too high on llvm/x86-64. See also the issue #10738. */
-#define DEPTH_MAX (4U*1024U)
+ *
+ * The value first was 16k, but it was discovered to be too high on llvm/x86-64. See also:
+ * https://github.com/systemd/systemd/issues/10738
+ *
+ * The value then was 4k, but it was discovered to be too high on s390x/aarch64. See also:
+ * https://github.com/systemd/systemd/issues/14396 */
+
+#define DEPTH_MAX (2U*1024U)
 assert_cc(DEPTH_MAX <= UINT16_MAX);
 
 typedef struct JsonSource {
index ce0a4acf9c894532d0373e2fd9ca88a7040b18ed..605412aec5186caa75540d96aa18a9e32ae6319f 100644 (file)
@@ -1453,6 +1453,7 @@ int add_match_this_boot(sd_journal *j, const char *machine) {
 int show_journal_by_unit(
                 FILE *f,
                 const char *unit,
+                const char *log_namespace,
                 OutputMode mode,
                 unsigned n_columns,
                 usec_t not_before,
@@ -1473,7 +1474,7 @@ int show_journal_by_unit(
         if (how_many <= 0)
                 return 0;
 
-        r = sd_journal_open(&j, journal_open_flags);
+        r = sd_journal_open_namespace(&j, log_namespace, journal_open_flags | SD_JOURNAL_INCLUDE_DEFAULT_NAMESPACE);
         if (r < 0)
                 return log_error_errno(r, "Failed to open journal: %m");
 
index 1e0c4ea14646586436f9561eaf33e5fd7b7bdb2f..345efa4b2bbd091697d7bda97328ed5d5e008f4d 100644 (file)
@@ -46,6 +46,7 @@ int add_matches_for_user_unit(
 int show_journal_by_unit(
                 FILE *f,
                 const char *unit,
+                const char *namespace,
                 OutputMode mode,
                 unsigned n_columns,
                 usec_t not_before,
index a7320fc4ed6ec044dac7df01c094a62be0ea0c2a..fa080f8e62d78765b08fe6c34a25ae18d0a0bae3 100644 (file)
@@ -27,6 +27,8 @@ shared_sources = files('''
         bus-unit-util.h
         bus-util.c
         bus-util.h
+        bus-polkit.c
+        bus-polkit.h
         bus-wait-for-jobs.c
         bus-wait-for-jobs.h
         bus-wait-for-units.c
@@ -85,6 +87,7 @@ shared_sources = files('''
         fstab-util.h
         generator.c
         generator.h
+        gpt.c
         gpt.h
         group-record-nss.c
         group-record-nss.h
index 11c085d63d25d23f1ce6b1e043ef10165643324a..0ff6d1711753d189bcac907c199da1846979a5b6 100644 (file)
@@ -6,6 +6,9 @@
 #include "strv.h"
 #include "user-record-nss.h"
 
+#define SET_IF(field, condition, value, fallback)  \
+        field = (condition) ? (value) : (fallback)
+
 int nss_passwd_to_user_record(
                 const struct passwd *pwd,
                 const struct spwd *spwd,
@@ -31,97 +34,66 @@ int nss_passwd_to_user_record(
         if (r < 0)
                 return r;
 
-        if (isempty(pwd->pw_gecos) || streq_ptr(pwd->pw_gecos, hr->user_name))
-                hr->real_name = mfree(hr->real_name);
-        else {
-                r = free_and_strdup(&hr->real_name, pwd->pw_gecos);
-                if (r < 0)
-                        return r;
-        }
+        r = free_and_strdup(&hr->real_name,
+                            streq_ptr(pwd->pw_gecos, hr->user_name) ? NULL : empty_to_null(pwd->pw_gecos));
+        if (r < 0)
+                return r;
 
-        if (isempty(pwd->pw_dir))
-                hr->home_directory = mfree(hr->home_directory);
-        else {
-                r = free_and_strdup(&hr->home_directory, pwd->pw_dir);
-                if (r < 0)
-                        return r;
-        }
+        r = free_and_strdup(&hr->home_directory, empty_to_null(pwd->pw_dir));
+        if (r < 0)
+                return r;
 
-        if (isempty(pwd->pw_shell))
-                hr->shell = mfree(hr->shell);
-        else {
-                r = free_and_strdup(&hr->shell, pwd->pw_shell);
-                if (r < 0)
-                        return r;
-        }
+        r = free_and_strdup(&hr->shell, empty_to_null(pwd->pw_shell));
+        if (r < 0)
+                return r;
 
         hr->uid = pwd->pw_uid;
         hr->gid = pwd->pw_gid;
 
-        if (spwd) {
-                if (hashed_password_valid(spwd->sp_pwdp)) {
-                        strv_free_erase(hr->hashed_password);
-                        hr->hashed_password = strv_new(spwd->sp_pwdp);
-                        if (!hr->hashed_password)
-                                return -ENOMEM;
-                } else
-                        hr->hashed_password = strv_free_erase(hr->hashed_password);
-
-                /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check)
-                 * for locking a whole account, hence check for that. Note that it also defines a way to lock
-                 * just a password instead of the whole account, but that's mostly pointless in times of
-                 * password-less authorization, hence let's not bother. */
-
-                if (spwd->sp_expire >= 0)
-                        hr->locked = spwd->sp_expire <= 1;
-                else
-                        hr->locked = -1;
-
-                if (spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->not_after_usec = spwd->sp_expire * USEC_PER_DAY;
-                else
-                        hr->not_after_usec = UINT64_MAX;
-
-                if (spwd->sp_lstchg >= 0)
-                        hr->password_change_now = spwd->sp_lstchg == 0;
-                else
-                        hr->password_change_now = -1;
-
-                if (spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->last_password_change_usec = spwd->sp_lstchg * USEC_PER_DAY;
-                else
-                        hr->last_password_change_usec = UINT64_MAX;
-
-                if (spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->password_change_min_usec = spwd->sp_min * USEC_PER_DAY;
-                else
-                        hr->password_change_min_usec = UINT64_MAX;
-
-                if (spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->password_change_max_usec = spwd->sp_max * USEC_PER_DAY;
-                else
-                        hr->password_change_max_usec = UINT64_MAX;
-
-                if (spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->password_change_warn_usec = spwd->sp_warn * USEC_PER_DAY;
-                else
-                        hr->password_change_warn_usec = UINT64_MAX;
-
-                if (spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY)
-                        hr->password_change_inactive_usec = spwd->sp_inact * USEC_PER_DAY;
-                else
-                        hr->password_change_inactive_usec = UINT64_MAX;
-        } else {
+        if (spwd && hashed_password_valid(spwd->sp_pwdp)) {
+                strv_free_erase(hr->hashed_password);
+                hr->hashed_password = strv_new(spwd->sp_pwdp);
+                if (!hr->hashed_password)
+                        return -ENOMEM;
+        } else
                 hr->hashed_password = strv_free_erase(hr->hashed_password);
-                hr->locked = -1;
-                hr->not_after_usec = UINT64_MAX;
-                hr->password_change_now = -1,
-                hr->last_password_change_usec = UINT64_MAX;
-                hr->password_change_min_usec = UINT64_MAX;
-                hr->password_change_max_usec = UINT64_MAX;
-                hr->password_change_warn_usec = UINT64_MAX;
-                hr->password_change_inactive_usec = UINT64_MAX;
-        }
+
+        /* shadow-utils suggests using "chage -E 0" (or -E 1, depending on which man page you check)
+         * for locking a whole account, hence check for that. Note that it also defines a way to lock
+         * just a password instead of the whole account, but that's mostly pointless in times of
+         * password-less authorization, hence let's not bother. */
+
+         SET_IF(hr->locked,
+                spwd && spwd->sp_expire >= 0,
+                spwd->sp_expire <= 1, -1);
+
+         SET_IF(hr->not_after_usec,
+                spwd && spwd->sp_expire > 1 && (uint64_t) spwd->sp_expire < (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_expire * USEC_PER_DAY, UINT64_MAX);
+
+         SET_IF(hr->password_change_now,
+                spwd && spwd->sp_lstchg >= 0,
+                spwd->sp_lstchg == 0, -1);
+
+         SET_IF(hr->last_password_change_usec,
+                spwd && spwd->sp_lstchg > 0 && (uint64_t) spwd->sp_lstchg <= (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_lstchg * USEC_PER_DAY, UINT64_MAX);
+
+         SET_IF(hr->password_change_min_usec,
+                spwd && spwd->sp_min > 0 && (uint64_t) spwd->sp_min <= (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_min * USEC_PER_DAY, UINT64_MAX);
+
+         SET_IF(hr->password_change_max_usec,
+                spwd && spwd->sp_max > 0 && (uint64_t) spwd->sp_max <= (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_max * USEC_PER_DAY, UINT64_MAX);
+
+         SET_IF(hr->password_change_warn_usec,
+                spwd && spwd->sp_warn > 0 && (uint64_t) spwd->sp_warn <= (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_warn * USEC_PER_DAY, UINT64_MAX);
+
+         SET_IF(hr->password_change_inactive_usec,
+                spwd && spwd->sp_inact > 0 && (uint64_t) spwd->sp_inact <= (UINT64_MAX-1)/USEC_PER_DAY,
+                spwd->sp_inact * USEC_PER_DAY, UINT64_MAX);
 
         hr->json = json_variant_unref(hr->json);
         r = json_build(&hr->json, JSON_BUILD_OBJECT(
index af2a1cc76fcb9c06523ef1b9271f84c7e1b45700..215e17db9b6b55de4da507568d011ca505b3c22d 100644 (file)
@@ -1246,7 +1246,7 @@ static int userdb_thread_sockaddr(struct sockaddr_un *ret_sa, socklen_t *ret_sal
         assert(ret_sa);
         assert(ret_salen);
 
-        /* This calculates an AF_UNIX socket address in the abstract namespace whose existance works as an
+        /* This calculates an AF_UNIX socket address in the abstract namespace whose existence works as an
          * indicator whether to emulate NSS records for complex user records that are also available via the
          * varlink protocol. The name of the socket is picked in a way so that:
          *
index 77cea00cb9dad68f295bf3e0e92db2d7a69a5076..012ce5308c1c7131637a8becde6ec31283744c48 100644 (file)
@@ -168,6 +168,7 @@ struct VarlinkServer {
 
         Hashmap *methods;
         VarlinkConnect connect_callback;
+        VarlinkDisconnect disconnect_callback;
 
         sd_event *event;
         int64_t event_priority;
@@ -1146,6 +1147,7 @@ int varlink_flush(Varlink *v) {
 }
 
 static void varlink_detach_server(Varlink *v) {
+        VarlinkServer *saved_server;
         assert(v);
 
         if (!v->server)
@@ -1169,8 +1171,15 @@ static void varlink_detach_server(Varlink *v) {
         v->server->n_connections--;
 
         /* If this is a connection associated to a server, then let's disconnect the server and the
-         * connection from each other. This drops the dangling reference that connect_callback() set up. */
-        v->server = varlink_server_unref(v->server);
+         * connection from each other. This drops the dangling reference that connect_callback() set up. But
+         * before we release the references, let's call the disconnection callback if it is defined. */
+
+        saved_server = TAKE_PTR(v->server);
+
+        if (saved_server->disconnect_callback)
+                saved_server->disconnect_callback(saved_server, v, saved_server->userdata);
+
+        varlink_server_unref(saved_server);
         varlink_unref(v);
 }
 
@@ -2413,6 +2422,16 @@ int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect callback) {
         return 0;
 }
 
+int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect callback) {
+        assert_return(s, -EINVAL);
+
+        if (callback && s->disconnect_callback && callback != s->disconnect_callback)
+                return -EBUSY;
+
+        s->disconnect_callback = callback;
+        return 0;
+}
+
 unsigned varlink_server_connections_max(VarlinkServer *s) {
         int dts;
 
@@ -2460,6 +2479,12 @@ int varlink_server_set_connections_max(VarlinkServer *s, unsigned m) {
         return 0;
 }
 
+unsigned varlink_server_current_connections(VarlinkServer *s) {
+        assert_return(s, UINT_MAX);
+
+        return s->n_connections;
+}
+
 int varlink_server_set_description(VarlinkServer *s, const char *description) {
         assert_return(s, -EINVAL);
 
index 0d9617d40352ff7aa6f6d790defb616fc1e9c1ef..7440f2ca44d711588c79138bb27f6a3357db3003 100644 (file)
@@ -51,6 +51,7 @@ typedef enum VarlinkServerFlags {
 typedef int (*VarlinkMethod)(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata);
 typedef int (*VarlinkReply)(Varlink *link, JsonVariant *parameters, const char *error_id, VarlinkReplyFlags flags, void *userdata);
 typedef int (*VarlinkConnect)(VarlinkServer *server, Varlink *link, void *userdata);
+typedef void (*VarlinkDisconnect)(VarlinkServer *server, Varlink *link, void *userdata);
 
 int varlink_connect_address(Varlink **ret, const char *address);
 int varlink_connect_fd(Varlink **ret, int fd);
@@ -134,6 +135,7 @@ int varlink_server_bind_method(VarlinkServer *s, const char *method, VarlinkMeth
 int varlink_server_bind_method_many_internal(VarlinkServer *s, ...);
 #define varlink_server_bind_method_many(s, ...) varlink_server_bind_method_many_internal(s, __VA_ARGS__, NULL)
 int varlink_server_bind_connect(VarlinkServer *s, VarlinkConnect connect);
+int varlink_server_bind_disconnect(VarlinkServer *s, VarlinkDisconnect disconnect);
 
 void* varlink_server_set_userdata(VarlinkServer *s, void *userdata);
 void* varlink_server_get_userdata(VarlinkServer *s);
@@ -150,6 +152,8 @@ unsigned varlink_server_connections_per_uid_max(VarlinkServer *s);
 int varlink_server_set_connections_per_uid_max(VarlinkServer *s, unsigned m);
 int varlink_server_set_connections_max(VarlinkServer *s, unsigned m);
 
+unsigned varlink_server_current_connections(VarlinkServer *s);
+
 int varlink_server_set_description(VarlinkServer *s, const char *description);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(Varlink *, varlink_unref);
index cfd254ad556ed31261768c8098a0fb1808326b1c..fbfddc0262fc161938a4868341ac3d6da4629d1f 100644 (file)
 #include "sd-messages.h"
 
 #include "btrfs-util.h"
+#include "bus-error.h"
 #include "def.h"
 #include "exec-util.h"
 #include "fd-util.h"
-#include "format-util.h"
 #include "fileio.h"
+#include "format-util.h"
 #include "log.h"
 #include "main-func.h"
 #include "parse-util.h"
@@ -125,6 +126,49 @@ static int write_state(FILE **f, char **states) {
         return r;
 }
 
+static int lock_all_homes(void) {
+        _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_flush_close_unrefp) sd_bus *bus = NULL;
+        int r;
+
+        /* Let's synchronously lock all home directories managed by homed that have been marked for it. This
+         * way the key material required to access these volumes is hopefully removed from memory. */
+
+        r = sd_bus_open_system(&bus);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to connect to system bus, ignoring: %m");
+
+        r = sd_bus_message_new_method_call(
+                        bus,
+                        &m,
+                        "org.freedesktop.home1",
+                        "/org/freedesktop/home1",
+                        "org.freedesktop.home1.Manager",
+                        "LockAllHomes");
+        if (r < 0)
+                return bus_log_create_error(r);
+
+        /* If homed is not running it can't have any home directories active either. */
+        r = sd_bus_message_set_auto_start(m, false);
+        if (r < 0)
+                return log_error_errno(r, "Failed to disable auto-start of LockAllHomes() message: %m");
+
+        r = sd_bus_call(bus, m, DEFAULT_TIMEOUT_USEC, &error, NULL);
+        if (r < 0) {
+                if (sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN) ||
+                    sd_bus_error_has_name(&error, SD_BUS_ERROR_NAME_HAS_NO_OWNER)) {
+                        log_debug("systemd-homed is not running, skipping locking of home directories.");
+                        return 0;
+                }
+
+                return log_error_errno(r, "Failed to lock home directories: %s", bus_error_message(&error, r));
+        }
+
+        log_debug("Successfully requested for all home directories to be locked.");
+        return 0;
+}
+
 static int execute(char **modes, char **states) {
         char *arguments[] = {
                 NULL,
@@ -166,6 +210,7 @@ static int execute(char **modes, char **states) {
         }
 
         (void) execute_directories(dirs, DEFAULT_TIMEOUT_USEC, NULL, NULL, arguments, NULL, EXEC_DIR_PARALLEL | EXEC_DIR_IGNORE_ERRORS);
+        (void) lock_all_homes();
 
         log_struct(LOG_INFO,
                    "MESSAGE_ID=" SD_MESSAGE_SLEEP_START_STR,
index 60872bc5373126648d66f66bb913d8f69cfc8319..5547058296ac091be043231374be5d5259cfc5fc 100644 (file)
@@ -4007,6 +4007,8 @@ typedef struct UnitStatusInfo {
 
         int exit_code, exit_status;
 
+        const char *log_namespace;
+
         usec_t condition_timestamp;
         bool condition_result;
         LIST_HEAD(UnitCondition, conditions);
@@ -4545,6 +4547,7 @@ static void print_status_info(
                 show_journal_by_unit(
                                 stdout,
                                 i->id,
+                                i->log_namespace,
                                 arg_output,
                                 0,
                                 i->inactive_exit_timestamp_monotonic,
@@ -5491,6 +5494,7 @@ static int show_one(
                 { "ExecMainExitTimestamp",          "t",               NULL,           offsetof(UnitStatusInfo, exit_timestamp)                    },
                 { "ExecMainCode",                   "i",               NULL,           offsetof(UnitStatusInfo, exit_code)                         },
                 { "ExecMainStatus",                 "i",               NULL,           offsetof(UnitStatusInfo, exit_status)                       },
+                { "LogNamespace",                   "s",               NULL,           offsetof(UnitStatusInfo, log_namespace)                     },
                 { "ConditionTimestamp",             "t",               NULL,           offsetof(UnitStatusInfo, condition_timestamp)               },
                 { "ConditionResult",                "b",               NULL,           offsetof(UnitStatusInfo, condition_result)                  },
                 { "Conditions",                     "a(sbbsi)",        map_conditions, 0                                                           },
index b3ee7bbc24e371a91fd2f530279c83678bdbc778..8158ee733e22e57f9e7c7e6508fbeda129346fa8 100644 (file)
@@ -45,6 +45,18 @@ typedef void (*_sd_destroy_t)(void *userdata);
 #  define _sd_pure_ __attribute__((__pure__))
 #endif
 
+/* Note that strictly speaking __deprecated__ has been available before GCC 6. However, starting with GCC 6
+ * it also works on enum values, which we are interested in. Since this is a developer-facing feature anyway
+ * (as opposed to build engineer-facing), let's hence conditionalize this to gcc 6, given that the developers
+ * are probably going to use something newer anyway. */
+#ifndef _sd_deprecated_
+#  if __GNUC__ >= 6
+#    define _sd_deprecated_ __attribute__((__deprecated__))
+#  else
+#    define _sd_deprecated_
+#  endif
+#endif
+
 #ifndef _SD_STRINGIFY
 #  define _SD_XSTRINGIFY(x) #x
 #  define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x)
index 3c9792e4975e235fc750690f61997316c6907dce..ff2c0e9ef00f645f69ec42fa2d70f8010fb76d8d 100644 (file)
@@ -19,6 +19,7 @@
 
 #include <inttypes.h>
 #include <stdarg.h>
+#include <stdio.h>
 #include <sys/types.h>
 #include <sys/uio.h>
 
@@ -105,6 +106,11 @@ enum {
         SD_BUS_NAME_QUEUE             = 1ULL << 2
 };
 
+enum {
+        SD_BUS_MESSAGE_DUMP_WITH_HEADER  = 1ULL << 0,
+        SD_BUS_MESSAGE_DUMP_SUBTREE_ONLY = 1ULL << 1,
+};
+
 /* Callbacks */
 
 typedef int (*sd_bus_message_handler_t)(sd_bus_message *m, void *userdata, sd_bus_error *ret_error);
@@ -330,6 +336,8 @@ int sd_bus_message_at_end(sd_bus_message *m, int complete);
 int sd_bus_message_rewind(sd_bus_message *m, int complete);
 int sd_bus_message_sensitive(sd_bus_message *m);
 
+int sd_bus_message_dump(sd_bus_message *m, FILE *f, uint64_t flags);
+
 /* Bus management */
 
 int sd_bus_get_unique_name(sd_bus *bus, const char **unique);
index b4bf3b91762f8d316db6d2dbd0396077e43e3c04..29ff40ab2618f5f965039832e76fad1f234ca1bd 100644 (file)
@@ -64,13 +64,15 @@ typedef struct sd_journal sd_journal;
 
 /* Open flags */
 enum {
-        SD_JOURNAL_LOCAL_ONLY   = 1 << 0,
-        SD_JOURNAL_RUNTIME_ONLY = 1 << 1,
-        SD_JOURNAL_SYSTEM       = 1 << 2,
-        SD_JOURNAL_CURRENT_USER = 1 << 3,
-        SD_JOURNAL_OS_ROOT      = 1 << 4,
-
-        SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM /* deprecated name */
+        SD_JOURNAL_LOCAL_ONLY                = 1 << 0,
+        SD_JOURNAL_RUNTIME_ONLY              = 1 << 1,
+        SD_JOURNAL_SYSTEM                    = 1 << 2,
+        SD_JOURNAL_CURRENT_USER              = 1 << 3,
+        SD_JOURNAL_OS_ROOT                   = 1 << 4,
+        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_SYSTEM_ONLY _sd_deprecated_ = SD_JOURNAL_SYSTEM /* deprecated name */
 };
 
 /* Wakeup event types */
@@ -81,11 +83,12 @@ enum {
 };
 
 int sd_journal_open(sd_journal **ret, int flags);
+int sd_journal_open_namespace(sd_journal **ret, const char *name_space, int flags);
 int sd_journal_open_directory(sd_journal **ret, const char *path, int flags);
 int sd_journal_open_directory_fd(sd_journal **ret, int fd, int flags);
 int sd_journal_open_files(sd_journal **ret, const char **paths, int flags);
 int sd_journal_open_files_fd(sd_journal **ret, int fds[], unsigned n_fds, int flags);
-int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); /* deprecated */
+int sd_journal_open_container(sd_journal **ret, const char *machine, int flags) _sd_deprecated_; /* deprecated */
 void sd_journal_close(sd_journal *j);
 
 int sd_journal_previous(sd_journal *j);
index 08a2df707bedf8b813da846385f60c6b294804b8..2771fd959fbfd677dfe0f26b20a53df52996af1b 100644 (file)
@@ -39,6 +39,7 @@ typedef struct Item {
         ItemType type;
 
         char *name;
+        char *group_name;
         char *uid_path;
         char *gid_path;
         char *description;
@@ -1085,18 +1086,15 @@ static int gid_is_ok(gid_t gid) {
         return 1;
 }
 
-static int add_group(Item *i) {
+static int get_gid_by_name(const char *name, gid_t *gid) {
         void *z;
-        int r;
 
-        assert(i);
+        assert(gid);
 
         /* Check the database directly */
-        z = hashmap_get(database_by_groupname, i->name);
+        z = hashmap_get(database_by_groupname, name);
         if (z) {
-                log_debug("Group %s already exists.", i->name);
-                i->gid = PTR_TO_GID(z);
-                i->gid_set = true;
+                *gid = PTR_TO_GID(z);
                 return 0;
         }
 
@@ -1105,15 +1103,30 @@ static int add_group(Item *i) {
                 struct group *g;
 
                 errno = 0;
-                g = getgrnam(i->name);
+                g = getgrnam(name);
                 if (g) {
-                        log_debug("Group %s already exists.", i->name);
-                        i->gid = g->gr_gid;
-                        i->gid_set = true;
+                        *gid = g->gr_gid;
                         return 0;
                 }
                 if (!IN_SET(errno, 0, ENOENT))
-                        return log_error_errno(errno, "Failed to check if group %s already exists: %m", i->name);
+                        return log_error_errno(errno, "Failed to check if group %s already exists: %m", name);
+        }
+
+        return -ENOENT;
+}
+
+static int add_group(Item *i) {
+        int r;
+
+        assert(i);
+
+        r = get_gid_by_name(i->name, &i->gid);
+        if (r != -ENOENT) {
+                if (r < 0)
+                        return r;
+                log_debug("Group %s already exists.", i->name);
+                i->gid_set = true;
+                return 0;
         }
 
         /* Try to use the suggested numeric gid */
@@ -1214,14 +1227,22 @@ static int process_item(Item *i) {
         case ADD_USER: {
                 Item *j;
 
-                j = ordered_hashmap_get(groups, i->name);
+                j = ordered_hashmap_get(groups, i->group_name ?: i->name);
                 if (j && j->todo_group) {
-                        /* When the group with the same name is already in queue,
+                        /* When a group with the target name is already in queue,
                          * use the information about the group and do not create
                          * duplicated group entry. */
                         i->gid_set = j->gid_set;
                         i->gid = j->gid;
                         i->id_set_strict = true;
+                } else if (i->group_name) {
+                        /* When a group name was given instead of a GID and it's
+                         * not in queue, then it must already exist. */
+                        r = get_gid_by_name(i->group_name, &i->gid);
+                        if (r < 0)
+                                return log_error_errno(r, "Group %s not found.", i->group_name);
+                        i->gid_set = true;
+                        i->id_set_strict = true;
                 } else {
                         r = add_group(i);
                         if (r < 0)
@@ -1244,6 +1265,7 @@ static Item* item_free(Item *i) {
                 return NULL;
 
         free(i->name);
+        free(i->group_name);
         free(i->uid_path);
         free(i->gid_path);
         free(i->description);
@@ -1560,10 +1582,15 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
                                 _cleanup_free_ char *uid = NULL, *gid = NULL;
                                 if (split_pair(resolved_id, ":", &uid, &gid) == 0) {
                                         r = parse_gid(gid, &i->gid);
-                                        if (r < 0)
-                                                return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
-                                        i->gid_set = true;
-                                        i->id_set_strict = true;
+                                        if (r < 0) {
+                                                if (valid_user_group_name(gid))
+                                                        i->group_name = TAKE_PTR(gid);
+                                                else
+                                                        return log_error_errno(r, "Failed to parse GID: '%s': %m", id);
+                                        } else {
+                                                i->gid_set = true;
+                                                i->id_set_strict = true;
+                                        }
                                         free_and_replace(resolved_id, uid);
                                 }
                                 if (!streq(resolved_id, "-")) {
index 4d358b8e345f63c80a5cc9c75d5d327bd9a50c93..fdb9e3ecb789600f66ed77c10ddb08058e53010b 100755 (executable)
@@ -6,6 +6,9 @@ for header in sys.argv[2:]:
     print('#include "{}"'.format(header.split('/')[-1]))
 
 print('''
+/* We want to check deprecated symbols too, without complaining */
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+
 const void* symbols[] = {''')
 
 for line in open(sys.argv[1]):
index 18b083d87fcb9d0abccd5d8996c580aa304ac122..510b1de72d104aea2e38af2c6d5117b3098f3208 100644 (file)
@@ -38,11 +38,11 @@ static void test_config_parse_iec_size_one(const char *rvalue, size_t expected)
         assert_se(expected == iec_size);
 }
 
-static void test_config_parse_si_size_one(const char *rvalue, size_t expected) {
-        size_t si_size = 0;
+static void test_config_parse_si_uint64_one(const char *rvalue, uint64_t expected) {
+        uint64_t si_uint64 = 0;
 
-        assert_se(config_parse_si_size("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &si_size, NULL) >= 0);
-        assert_se(expected == si_size);
+        assert_se(config_parse_si_uint64("unit", "filename", 1, "section", 1, "lvalue", 0, rvalue, &si_uint64, NULL) >= 0);
+        assert_se(expected == si_uint64);
 }
 
 static void test_config_parse_int_one(const char *rvalue, int expected) {
@@ -125,17 +125,17 @@ static void test_config_parse_iec_size(void) {
         test_config_parse_iec_size_one("garbage", 0);
 }
 
-static void test_config_parse_si_size(void) {
-        test_config_parse_si_size_one("1024", 1024);
-        test_config_parse_si_size_one("2K", 2000);
-        test_config_parse_si_size_one("10M", 10 * 1000 * 1000);
-        test_config_parse_si_size_one("1G", 1 * 1000 * 1000 * 1000);
-        test_config_parse_si_size_one("0G", 0);
-        test_config_parse_si_size_one("0", 0);
-
-        test_config_parse_si_size_one("-982", 0);
-        test_config_parse_si_size_one("49874444198739873000000G", 0);
-        test_config_parse_si_size_one("garbage", 0);
+static void test_config_parse_si_uint64(void) {
+        test_config_parse_si_uint64_one("1024", 1024);
+        test_config_parse_si_uint64_one("2K", 2000);
+        test_config_parse_si_uint64_one("10M", 10 * 1000 * 1000);
+        test_config_parse_si_uint64_one("1G", 1 * 1000 * 1000 * 1000);
+        test_config_parse_si_uint64_one("0G", 0);
+        test_config_parse_si_uint64_one("0", 0);
+
+        test_config_parse_si_uint64_one("-982", 0);
+        test_config_parse_si_uint64_one("49874444198739873000000G", 0);
+        test_config_parse_si_uint64_one("garbage", 0);
 }
 
 static void test_config_parse_int(void) {
@@ -391,7 +391,7 @@ int main(int argc, char **argv) {
         test_config_parse_log_level();
         test_config_parse_log_facility();
         test_config_parse_iec_size();
-        test_config_parse_si_size();
+        test_config_parse_si_uint64();
         test_config_parse_int();
         test_config_parse_unsigned();
         test_config_parse_strv();
index add17f9547dc6b803143d4cef265f1a8f54167a2..f6aae1eb1828d8432648cd043a1f036bc73127c6 100644 (file)
@@ -96,6 +96,22 @@ static void test_cunescape(void) {
 
         assert_se(cunescape("A=A\\\\x0aB", UNESCAPE_RELAX, &unescaped) >= 0);
         assert_se(streq_ptr(unescaped, "A=A\\x0aB"));
+        unescaped = mfree(unescaped);
+
+        assert_se(cunescape("\\x00\\x00\\x00", UNESCAPE_ACCEPT_NUL, &unescaped) == 3);
+        assert_se(memcmp(unescaped, "\0\0\0", 3) == 0);
+        unescaped = mfree(unescaped);
+
+        assert_se(cunescape("\\u0000\\u0000\\u0000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3);
+        assert_se(memcmp(unescaped, "\0\0\0", 3) == 0);
+        unescaped = mfree(unescaped);
+
+        assert_se(cunescape("\\U00000000\\U00000000\\U00000000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3);
+        assert_se(memcmp(unescaped, "\0\0\0", 3) == 0);
+        unescaped = mfree(unescaped);
+
+        assert_se(cunescape("\\000\\000\\000", UNESCAPE_ACCEPT_NUL, &unescaped) == 3);
+        assert_se(memcmp(unescaped, "\0\0\0", 3) == 0);
 }
 
 static void test_shell_escape_one(const char *s, const char *bad, const char *expected) {
index b3f8cc84345da32dbcb2967ed3902bdc4c3fd825..ff40e0dd43dd4749a239bef701ee216194e81dd0 100644 (file)
@@ -756,6 +756,7 @@ static void test_exec_specifier(Manager *m) {
 static void test_exec_standardinput(Manager *m) {
         test(__func__, m, "exec-standardinput-data.service", 0, CLD_EXITED);
         test(__func__, m, "exec-standardinput-file.service", 0, CLD_EXITED);
+        test(__func__, m, "exec-standardinput-file-cat.service", 0, CLD_EXITED);
 }
 
 static void test_exec_standardoutput(Manager *m) {
@@ -786,6 +787,7 @@ static int run_tests(UnitFileScope scope, const test_entry tests[], char **patte
         assert_se(tests);
 
         r = manager_new(scope, MANAGER_TEST_RUN_BASIC, &m);
+        m->default_std_output = EXEC_OUTPUT_NULL; /* don't rely on host journald */
         if (manager_errno_skip_test(r))
                 return log_tests_skipped_errno(r, "manager_new");
         assert_se(r >= 0);
index ac8b95aece0eaed479a1d419a67fe8965909c97f..d0c6fb82bfda2748ffb0ebfb499845d3e933452f 100644 (file)
@@ -148,6 +148,7 @@ static void test_chase_symlinks(void) {
         r = chase_symlinks(p, NULL, 0, &result, NULL);
         assert_se(r > 0);
         assert_se(path_equal(result, "/usr"));
+        assert_se(streq(result, "/usr")); /* we guarantee that we drop redundant slashes */
         result = mfree(result);
 
         r = chase_symlinks(p, temp, 0, &result, NULL);
@@ -371,6 +372,15 @@ static void test_chase_symlinks(void) {
         assert_se(streq("/usr", result));
         result = mfree(result);
 
+        /* Make sure that symlinks in the "root" path are not resolved, but those below are */
+        p = strjoina("/etc/..", temp, "/self");
+        assert_se(symlink(".", p) >= 0);
+        q = strjoina(p, "/top/dot/dotdota");
+        r = chase_symlinks(q, p, 0, &result, NULL);
+        assert_se(r > 0);
+        assert_se(path_equal(path_startswith(result, p), "usr"));
+        result = mfree(result);
+
  cleanup:
         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
 }
@@ -729,7 +739,7 @@ static void test_rename_noreplace(void) {
                 STRV_FOREACH(b, (char**) table) {
                         _cleanup_free_ char *w = NULL;
 
-                        w = strjoin(w, *b);
+                        w = strjoin(z, *b);
                         assert_se(w);
 
                         if (access(w, F_OK) < 0) {
@@ -737,7 +747,7 @@ static void test_rename_noreplace(void) {
                                 continue;
                         }
 
-                        assert_se(rename_noreplace(AT_FDCWD, w, AT_FDCWD, y) == -EEXIST);
+                        assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, w) == -EEXIST);
                 }
 
                 y = strjoin(z, "/somethingelse");
index 41ea733b7eb2911e4fd414922a5a98523fb391d4..f2bfc6c62bc9c3f5724e725549cb2932a1bee767 100644 (file)
@@ -148,6 +148,7 @@ static void test_protect_kernel_logs(void) {
                                     NULL, 0,
                                     NULL,
                                     NULL,
+                                    NULL,
                                     PROTECT_HOME_NO,
                                     PROTECT_SYSTEM_NO,
                                     0,
index e9233a16437e23268ec15d3a5e41ad30ee60acb0..48a5ff9f2d331738ddffa1ae0293920b013bc3b2 100644 (file)
@@ -72,6 +72,7 @@ int main(int argc, char *argv[]) {
                             &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
                             tmp_dir,
                             var_tmp_dir,
+                            NULL,
                             PROTECT_HOME_NO,
                             PROTECT_SYSTEM_NO,
                             0,
index 4a3f2116588c71049f5cb2d80b7ded2dcebbbb89..d78e0544a7a6236c0f9fcf233e8a4805f4625b4b 100644 (file)
@@ -572,10 +572,8 @@ static void test_pid_to_ptr(void) {
         assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MAX)) == INT16_MAX);
         assert_se(PTR_TO_PID(PID_TO_PTR(INT16_MIN)) == INT16_MIN);
 
-#if SIZEOF_PID_T >= 4
         assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MAX)) == INT32_MAX);
         assert_se(PTR_TO_PID(PID_TO_PTR(INT32_MIN)) == INT32_MIN);
-#endif
 }
 
 static void test_ioprio_class_from_to_string_one(const char *val, int expected) {
index 6bfe7dc7e174109ec8c9058750db983584fe5e9e..084a584876dde73ce36971407174c967281e1d57 100644 (file)
@@ -310,15 +310,19 @@ static void test_gid_lists_ops(void) {
         int nresult;
 
         nresult = merge_gid_lists(l2, ELEMENTSOF(l2), l3, ELEMENTSOF(l3), &res1);
+        assert_se(nresult >= 0);
         assert_se(memcmp_nn(res1, nresult, result1, ELEMENTSOF(result1)) == 0);
 
         nresult = merge_gid_lists(NULL, 0, l2, ELEMENTSOF(l2), &res2);
+        assert_se(nresult >= 0);
         assert_se(memcmp_nn(res2, nresult, l2, ELEMENTSOF(l2)) == 0);
 
         nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l1, ELEMENTSOF(l1), &res3);
+        assert_se(nresult >= 0);
         assert_se(memcmp_nn(l1, ELEMENTSOF(l1), res3, nresult) == 0);
 
         nresult = merge_gid_lists(l1, ELEMENTSOF(l1), l4, ELEMENTSOF(l4), &res4);
+        assert_se(nresult >= 0);
         assert_se(memcmp_nn(result2, ELEMENTSOF(result2), res4, nresult) == 0);
 
         nresult = getgroups_alloc(&gids);
index 4ec3b503592555a3de5e25fd56be4a30e116bf16..5e2fb50d8371b5138f0a2cafc1b1f74d147c1b75 100644 (file)
@@ -12,7 +12,7 @@
 #include "alloc-util.h"
 #include "bus-common-errors.h"
 #include "bus-error.h"
-#include "bus-util.h"
+#include "bus-polkit.h"
 #include "clock-util.h"
 #include "conf-files.h"
 #include "def.h"
index da6b4104767eca24da1ce1dcb1eed37cca215138..7b71e98f566ff2667c6a1d7e7418bb278bb54f27 100644 (file)
@@ -514,7 +514,7 @@ static int manager_receive_response(sd_event_source *source, int fd, uint32_t re
 
         root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + ntp_ts_short_to_d(&ntpmsg.root_dispersion);
         if (root_distance > (double) m->max_root_distance_usec / (double) USEC_PER_SEC) {
-                log_debug("Server has too large root distance. Disconnecting.");
+                log_info("Server has too large root distance. Disconnecting.");
                 return manager_connect(m);
         }
 
index 686ff1bc5ca9b71e876d8f57868bd5f34c2c9b76..43d1c59b9401c80f4be2c49ae94556e6c692b6ed 100644 (file)
@@ -40,11 +40,13 @@ Link.AlternativeName,            config_parse_ifnames,                  1,
 Link.AlternativeNamesPolicy,     config_parse_alternative_names_policy, 0,                             offsetof(link_config, alternative_names_policy)
 Link.Alias,                      config_parse_ifalias,                  0,                             offsetof(link_config, alias)
 Link.MTUBytes,                   config_parse_mtu,                      AF_UNSPEC,                     offsetof(link_config, mtu)
-Link.BitsPerSecond,              config_parse_si_size,                  0,                             offsetof(link_config, speed)
+Link.BitsPerSecond,              config_parse_si_uint64,                0,                             offsetof(link_config, speed)
 Link.Duplex,                     config_parse_duplex,                   0,                             offsetof(link_config, duplex)
 Link.AutoNegotiation,            config_parse_tristate,                 0,                             offsetof(link_config, autonegotiation)
 Link.WakeOnLan,                  config_parse_wol,                      0,                             offsetof(link_config, wol)
 Link.Port,                       config_parse_port,                     0,                             offsetof(link_config, port)
+Link.ReceiveChecksumOffload,     config_parse_tristate,                 0,                             offsetof(link_config, features[NET_DEV_FEAT_RX])
+Link.TransmitChecksumOffload,    config_parse_tristate,                 0,                             offsetof(link_config, features[NET_DEV_FEAT_TX])
 Link.GenericSegmentationOffload, config_parse_tristate,                 0,                             offsetof(link_config, features[NET_DEV_FEAT_GSO])
 Link.TCPSegmentationOffload,     config_parse_tristate,                 0,                             offsetof(link_config, features[NET_DEV_FEAT_TSO])
 Link.TCP6SegmentationOffload,    config_parse_tristate,                 0,                             offsetof(link_config, features[NET_DEV_FEAT_TSO6])
index dfe9b9f31307fbca589326afda89d10ea8993ac2..bcf9be1a0d918e33c19d65ea64f91f03cec2bb79 100644 (file)
@@ -160,9 +160,6 @@ int link_load_one(link_config_ctx *ctx, const char *filename) {
         if (r < 0)
                 return r;
 
-        if (link->speed > UINT_MAX)
-                return -ERANGE;
-
         if (set_isempty(link->match_mac) && set_isempty(link->match_permanent_mac) &&
             strv_isempty(link->match_path) && strv_isempty(link->match_driver) && strv_isempty(link->match_type) &&
             strv_isempty(link->match_name) && strv_isempty(link->match_property) && !link->conditions) {
index 496a8bccb78c38a5928cae7e640d60f6f3ee2e57..a85bd4b46b021bca937c713e2e1497fefbd5dbfc 100644 (file)
@@ -53,7 +53,7 @@ struct link_config {
         char **alternative_names;
         char *alias;
         uint32_t mtu;
-        size_t speed;
+        uint64_t speed;
         Duplex duplex;
         int autonegotiation;
         uint32_t advertise[N_ADVERTISE];
index 7678331897f5e988a66ef2dba903433fae518091..ca65474f27634a880ad4381ddc9ef446ae387877 100644 (file)
@@ -1577,7 +1577,11 @@ static int manager_new(Manager **ret, int fd_ctrl, int fd_uevent, const char *cg
         if (r < 0)
                 return log_error_errno(r, "Failed to initialize device monitor: %m");
 
-        (void) sd_device_monitor_set_receive_buffer_size(manager->monitor, 128 * 1024 * 1024);
+        /* Bump receiver buffer, but only if we are not called via socket activation, as in that
+         * case systemd sets the receive buffer size for us, and the value in the .socket unit
+         * should take full effect. */
+        if (fd_uevent < 0)
+                (void) sd_device_monitor_set_receive_buffer_size(manager->monitor, 128 * 1024 * 1024);
 
         r = device_monitor_enable_receiving(manager->monitor);
         if (r < 0)
index 2836ca0409aeb6adf4d24077409c2eecd93c0544..dec8b289a33e379071f2afa194efb87fa3769c9f 100644 (file)
@@ -77,7 +77,7 @@ static int on_sigusr2(sd_event_source *s, const struct signalfd_siginfo *si, voi
 }
 
 int manager_new(Manager **ret) {
-        Manager *m;
+        _cleanup_(manager_freep) Manager *m = NULL;
         int r;
 
         m = new(Manager, 1);
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-group b/test/TEST-21-SYSUSERS/test-13.expected-group
new file mode 100644 (file)
index 0000000..c78ea54
--- /dev/null
@@ -0,0 +1,5 @@
+hoge:x:300:
+baz:x:302:
+yyy:x:SYSTEM_GID_MAX:
+foo:x:301:
+ccc:x:305:
diff --git a/test/TEST-21-SYSUSERS/test-13.expected-passwd b/test/TEST-21-SYSUSERS/test-13.expected-passwd
new file mode 100644 (file)
index 0000000..ffc20a8
--- /dev/null
@@ -0,0 +1,5 @@
+foo:x:301:301::/:NOLOGIN
+aaa:x:303:302::/:NOLOGIN
+bbb:x:304:302::/:NOLOGIN
+ccc:x:305:305::/:NOLOGIN
+zzz:x:306:SYSTEM_GID_MAX::/:NOLOGIN
diff --git a/test/TEST-21-SYSUSERS/test-13.input b/test/TEST-21-SYSUSERS/test-13.input
new file mode 100644 (file)
index 0000000..bad2f09
--- /dev/null
@@ -0,0 +1,13 @@
+# Ensure that the semantic for the uid:groupname syntax is correct
+#
+#Type Name ID  GECOS HOMEDIR
+g hoge    300     -            -
+u foo     301     -            -
+
+g baz     302     -            -
+u aaa     303:baz -            -
+u bbb     304:baz -            -
+u ccc     305     -            -
+
+g yyy     -
+u zzz     306:yyy
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-group b/test/TEST-21-SYSUSERS/test-14.expected-group
new file mode 100644 (file)
index 0000000..2e619bc
--- /dev/null
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.expected-passwd b/test/TEST-21-SYSUSERS/test-14.expected-passwd
new file mode 100644 (file)
index 0000000..62ed4f5
--- /dev/null
@@ -0,0 +1 @@
+aaa:x:SYSTEM_UID_MAX:987::/:NOLOGIN
diff --git a/test/TEST-21-SYSUSERS/test-14.initial-group b/test/TEST-21-SYSUSERS/test-14.initial-group
new file mode 100644 (file)
index 0000000..2e619bc
--- /dev/null
@@ -0,0 +1 @@
+pre:x:987:
diff --git a/test/TEST-21-SYSUSERS/test-14.input b/test/TEST-21-SYSUSERS/test-14.input
new file mode 100644 (file)
index 0000000..0a11a2e
--- /dev/null
@@ -0,0 +1,4 @@
+# Ensure that a preexisting system group can be used as primary
+#
+#Type Name ID  GECOS HOMEDIR
+u aaa -:pre
index a1a2e62ab1b0c55df2255c712cfacf09ae4a6e6f..aed921e39ec3fbb01d4d10a8ba68b1dc5f1e7f05 100755 (executable)
@@ -23,6 +23,7 @@ preprocess() {
     # get this value from config.h, however the autopkgtest fails with
     # it
     SYSTEM_UID_MAX=$(awk 'BEGIN { uid=999 } /^\s*SYS_UID_MAX\s+/ { uid=$2 } END { print uid }' /etc/login.defs)
+    SYSTEM_GID_MAX=$(awk 'BEGIN { gid=999 } /^\s*SYS_GID_MAX\s+/ { gid=$2 } END { print gid }' /etc/login.defs)
 
     # we can't rely on config.h to get the nologin path, as autopkgtest
     # uses pre-compiled binaries, so extract it from the systemd-sysusers
@@ -30,6 +31,7 @@ preprocess() {
     NOLOGIN=$(strings $(type -p systemd-sysusers) | grep nologin)
 
     sed -e "s/SYSTEM_UID_MAX/${SYSTEM_UID_MAX}/g" \
+        -e "s/SYSTEM_GID_MAX/${SYSTEM_GID_MAX}/g" \
         -e "s#NOLOGIN#${NOLOGIN}#g" "$in"
 }
 
@@ -114,6 +116,7 @@ test_run() {
         prepare_testdir ${f%.input}
         cp $f $TESTDIR/usr/lib/sysusers.d/test.conf
         systemd-sysusers --root=$TESTDIR 2> /dev/null
+        journalctl --sync
         journalctl -t systemd-sysusers -o cat | tail -n1 > $TESTDIR/tmp/err
         if ! diff -u $TESTDIR/tmp/err  ${f%.*}.expected-err; then
             echo "**** Unexpected error output for $f"
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.expected-err b/test/TEST-21-SYSUSERS/unhappy-3.expected-err
new file mode 100644 (file)
index 0000000..d55b366
--- /dev/null
@@ -0,0 +1 @@
+Group g1 not found.
diff --git a/test/TEST-21-SYSUSERS/unhappy-3.input b/test/TEST-21-SYSUSERS/unhappy-3.input
new file mode 100644 (file)
index 0000000..64e60dd
--- /dev/null
@@ -0,0 +1,4 @@
+# Ensure it is not allowed to create groups implicitly in the uid:groupname syntax
+#
+#Type Name ID  GECOS HOMEDIR
+u u1 100:g1 -
diff --git a/test/TEST-44-LOG-NAMESPACE/Makefile b/test/TEST-44-LOG-NAMESPACE/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-44-LOG-NAMESPACE/test.sh b/test/TEST-44-LOG-NAMESPACE/test.sh
new file mode 100755 (executable)
index 0000000..4dfb441
--- /dev/null
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -e
+TEST_DESCRIPTION="test log namespaces"
+
+. $TEST_BASE_DIR/test-functions
+
+test_setup() {
+    create_empty_image_rootdir
+
+    (
+        LOG_LEVEL=5
+        eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
+
+        setup_basic_environment
+
+        mask_supporting_services
+
+        # setup the testsuite service
+        cat >$initdir/etc/systemd/system/testsuite.service <<EOF
+[Unit]
+Description=Testsuite service
+Before=getty-pre.target
+Wants=getty-pre.target
+Wants=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket
+After=systemd-journald@foobar.socket systemd-journald-varlink@foobar.socket
+
+[Service]
+ExecStart=/testsuite.sh
+Type=oneshot
+LogTarget=foobar
+EOF
+        cp testsuite.sh $initdir/
+
+        setup_testsuite
+    )
+    setup_nspawn_root
+}
+
+do_test "$@"
diff --git a/test/TEST-44-LOG-NAMESPACE/testsuite.sh b/test/TEST-44-LOG-NAMESPACE/testsuite.sh
new file mode 100755 (executable)
index 0000000..9be0765
--- /dev/null
@@ -0,0 +1,19 @@
+#!/bin/bash
+set -ex
+
+systemd-analyze log-level debug
+
+systemd-run -p LogNamespace=foobar echo "hello world"
+
+journalctl --namespace=foobar --sync
+journalctl --namespace=foobar > /tmp/hello-world
+journalctl > /tmp/no-hello-world
+
+grep "hello world" /tmp/hello-world
+! grep "hello world" /tmp/no-hello-world
+
+systemd-analyze log-level info
+
+echo OK > /testok
+
+exit 0
diff --git a/test/TEST-45-REPART/Makefile b/test/TEST-45-REPART/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-45-REPART/test.sh b/test/TEST-45-REPART/test.sh
new file mode 100755 (executable)
index 0000000..f1f4660
--- /dev/null
@@ -0,0 +1,37 @@
+#!/bin/bash
+set -e
+TEST_DESCRIPTION="test systemd-repart"
+
+. $TEST_BASE_DIR/test-functions
+
+test_setup() {
+    create_empty_image_rootdir
+
+    (
+        LOG_LEVEL=5
+        eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
+
+        setup_basic_environment
+
+        mask_supporting_services
+        dracut_install truncate sfdisk grep
+
+        # setup the testsuite service
+        cat >$initdir/etc/systemd/system/testsuite.service <<EOF
+[Unit]
+Description=Testsuite service
+Before=getty-pre.target
+Wants=getty-pre.target
+
+[Service]
+ExecStart=/testsuite.sh
+Type=oneshot
+EOF
+        cp testsuite.sh $initdir/
+
+        setup_testsuite
+    )
+    setup_nspawn_root
+}
+
+do_test "$@"
diff --git a/test/TEST-45-REPART/testsuite.sh b/test/TEST-45-REPART/testsuite.sh
new file mode 100755 (executable)
index 0000000..efea390
--- /dev/null
@@ -0,0 +1,120 @@
+#!/bin/bash
+set -ex
+
+# Check if repart is installed, and if it isn't bail out early instead of failing
+if ! test -x /usr/bin/systemd-repart ; then
+    echo OK > /testok
+    exit 0
+fi
+
+systemd-analyze log-level debug
+
+truncate -s 1G /tmp/zzz
+
+SEED=e2a40bf9-73f1-4278-9160-49c031e7aef8
+
+systemd-repart /tmp/zzz --empty=force --dry-run=no --seed=$SEED
+
+sfdisk -d /tmp/zzz | grep -v -e 'sector-size' -e '^$' > /tmp/empty
+
+cmp /tmp/empty - <<EOF
+label: gpt
+label-id: EF7F7EE2-47B3-4251-B1A1-09EA8BF12D5D
+device: /tmp/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+EOF
+
+mkdir /tmp/definitions
+
+cat > /tmp/definitions/root.conf <<EOF
+[Partition]
+Type=root
+EOF
+
+ln -s root.conf /tmp/definitions/root2.conf
+
+cat > /tmp/definitions/home.conf <<EOF
+[Partition]
+Type=home
+EOF
+
+cat > /tmp/definitions/swap.conf <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=64M
+PaddingMinBytes=92M
+EOF
+
+systemd-repart /tmp/zzz --dry-run=no --seed=$SEED --definitions=/tmp/definitions
+
+sfdisk -d /tmp/zzz | grep -v -e 'sector-size' -e '^$' > /tmp/populated
+
+cmp /tmp/populated - <<EOF
+label: gpt
+label-id: EF7F7EE2-47B3-4251-B1A1-09EA8BF12D5D
+device: /tmp/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+/tmp/zzz1 : start=        2048, size=      591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=A6005774-F558-4330-A8E5-D6D2C01C01D6, name="home"
+/tmp/zzz2 : start=      593904, size=      591856, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=CE9C76EB-A8F1-40FF-813C-11DCA6C0A55B, name="root-x86-64"
+/tmp/zzz3 : start=     1185760, size=      591864, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=AC60A837-550C-43BD-B5C4-9CB73B884E79, name="root-x86-64-2"
+/tmp/zzz4 : start=     1777624, size=      131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=2AA78CDB-59C7-4173-AF11-C7453737A5D1, name="swap"
+EOF
+
+cat > /tmp/definitions/swap.conf <<EOF
+[Partition]
+Type=swap
+SizeMaxBytes=64M
+EOF
+
+cat > /tmp/definitions/extra.conf <<EOF
+[Partition]
+Type=linux-generic
+EOF
+
+systemd-repart /tmp/zzz --dry-run=no --seed=$SEED --definitions=/tmp/definitions
+
+sfdisk -d /tmp/zzz | grep -v -e 'sector-size' -e '^$' > /tmp/populated2
+
+cmp /tmp/populated2 - <<EOF
+label: gpt
+label-id: EF7F7EE2-47B3-4251-B1A1-09EA8BF12D5D
+device: /tmp/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+/tmp/zzz1 : start=        2048, size=      591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=A6005774-F558-4330-A8E5-D6D2C01C01D6, name="home"
+/tmp/zzz2 : start=      593904, size=      591856, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=CE9C76EB-A8F1-40FF-813C-11DCA6C0A55B, name="root-x86-64"
+/tmp/zzz3 : start=     1185760, size=      591864, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=AC60A837-550C-43BD-B5C4-9CB73B884E79, name="root-x86-64-2"
+/tmp/zzz4 : start=     1777624, size=      131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=2AA78CDB-59C7-4173-AF11-C7453737A5D1, name="swap"
+/tmp/zzz5 : start=     1908696, size=      188416, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=03477476-06AD-44E8-9EF4-BC2BD7771289, name="linux-generic"
+EOF
+
+truncate -s 2G /tmp/zzz
+
+systemd-repart /tmp/zzz --dry-run=no --seed=$SEED --definitions=/tmp/definitions
+
+sfdisk -d /tmp/zzz | grep -v -e 'sector-size' -e '^$' > /tmp/populated3
+
+cmp /tmp/populated3 - <<EOF
+label: gpt
+label-id: EF7F7EE2-47B3-4251-B1A1-09EA8BF12D5D
+device: /tmp/zzz
+unit: sectors
+first-lba: 2048
+last-lba: 4194270
+/tmp/zzz1 : start=        2048, size=      591856, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=A6005774-F558-4330-A8E5-D6D2C01C01D6, name="home"
+/tmp/zzz2 : start=      593904, size=      591856, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=CE9C76EB-A8F1-40FF-813C-11DCA6C0A55B, name="root-x86-64"
+/tmp/zzz3 : start=     1185760, size=      591864, type=4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709, uuid=AC60A837-550C-43BD-B5C4-9CB73B884E79, name="root-x86-64-2"
+/tmp/zzz4 : start=     1777624, size=      131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=2AA78CDB-59C7-4173-AF11-C7453737A5D1, name="swap"
+/tmp/zzz5 : start=     1908696, size=     2285568, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=03477476-06AD-44E8-9EF4-BC2BD7771289, name="linux-generic"
+EOF
+
+systemd-analyze log-level info
+
+echo OK > /testok
+
+exit 0
diff --git a/test/TEST-46-HOMED/Makefile b/test/TEST-46-HOMED/Makefile
new file mode 120000 (symlink)
index 0000000..e9f93b1
--- /dev/null
@@ -0,0 +1 @@
+../TEST-01-BASIC/Makefile
\ No newline at end of file
diff --git a/test/TEST-46-HOMED/test.sh b/test/TEST-46-HOMED/test.sh
new file mode 100755 (executable)
index 0000000..092136c
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/bash
+set -e
+TEST_DESCRIPTION="testing homed"
+TEST_NO_QEMU=1
+
+. $TEST_BASE_DIR/test-functions
+
+test_setup() {
+    create_empty_image
+    mkdir -p $TESTDIR/root
+    mount ${LOOPDEV}p1 $TESTDIR/root
+
+    (
+        LOG_LEVEL=5
+        eval $(udevadm info --export --query=env --name=${LOOPDEV}p2)
+
+        setup_basic_environment
+        mask_supporting_services
+
+        # setup the testsuite service
+        cat >$initdir/etc/systemd/system/testsuite.service <<EOF
+[Unit]
+Description=Testsuite service
+Before=getty-pre.target
+Wants=getty-pre.target
+
+[Service]
+ExecStart=/bin/bash -x /testsuite.sh
+Type=oneshot
+NotifyAccess=all
+EOF
+        cp testsuite.sh $initdir/
+
+        setup_testsuite
+    ) || return 1
+    setup_nspawn_root
+
+    ddebug "umount $TESTDIR/root"
+    umount $TESTDIR/root
+}
+
+do_test "$@"
diff --git a/test/TEST-46-HOMED/testsuite.sh b/test/TEST-46-HOMED/testsuite.sh
new file mode 100755 (executable)
index 0000000..9c52312
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/bash
+set -ex
+set -o pipefail
+
+# Check if homectl is installed, and if it isn't bail out early instead of failing
+if ! test -x /usr/bin/homectl ; then
+        echo OK > /testok
+        exit 0
+fi
+
+inspect() {
+        homectl inspect $1 | tee /tmp/a
+        userdbctl user $1 | tee /tmp/b
+        cmp /tmp/a /tmp/b
+        rm /tmp/a /tmp/b
+}
+
+systemd-analyze log-level debug
+systemd-analyze log-target console
+
+NEWPASSWORD=xEhErW0ndafV4s homectl create test-user --disk-size=20M
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl authenticate test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Inline test"
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s NEWPASSWORD=yPN4N0fYNKUkOq homectl passwd test-user
+inspect test-user
+
+PASSWORD=yPN4N0fYNKUkOq homectl activate test-user
+inspect test-user
+
+SYSTEMD_LOG_LEVEL=debug PASSWORD=yPN4N0fYNKUkOq NEWPASSWORD=xEhErW0ndafV4s homectl passwd test-user
+inspect test-user
+
+homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl update test-user --real-name="Offline test"
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl activate test-user
+inspect test-user
+
+PASSWORD=xEhErW0ndafV4s homectl deactivate test-user
+inspect test-user
+
+! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- touch /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
+PASSWORD=xEhErW0ndafV4s homectl with test-user -- rm /home/test-user/xyz
+! PASSWORD=xEhErW0ndafV4s homectl with test-user -- test -f /home/test-user/xyz
+
+homectl remove test-user
+
+systemd-analyze log-level info
+
+echo OK > /testok
+
+exit 0
index ba8760f12bab808b87530ab8b4bfebaacc1d81ff..b304ad0ddb141923743e5ec30d01099e641652de 100644 (file)
@@ -26,6 +26,8 @@ Duplex=
 AutoNegotiation=
 WakeOnLan=
 Port=
+ReceiveChecksumOffload=
+TransmitChecksumOffload=
 GenericSegmentationOffload=
 TCPSegmentationOffload=
 TCP6SegmentationOffload=
index 0e3adac5ce1b6c45f61c7d502978d0ca5fc4c9e5..7262ae1ee2ca41c5a0be42148650d064748394bb 100644 (file)
@@ -225,6 +225,8 @@ DestinationPort=
 IPProtocol=
 InvertRule=
 Family=
+SuppressPrefixLength=
+User=
 [IPv6PrefixDelegation]
 RouterPreference=
 DNSLifetimeSec=
index 31a45043daca31d1be54b84a9f1bb43a1f876610..45f8751971b9b199ced97758b187936a4284b5b7 100644 (file)
@@ -538,6 +538,7 @@ STP=
 Scope=
 SendHostname=
 Source=
+SuppressPrefixLength=
 TCP6SegmentationOffload=
 TCPSegmentationOffload=
 TOS=
index 237b4db12c9771271dc720ed34a276e812734574..856e4a434d0365a4320d048d5b06e45f5b67deac 100644 (file)
@@ -138,6 +138,7 @@ test_data_files = '''
         test-execute/exec-specifier@.service
         test-execute/exec-standardinput-data.service
         test-execute/exec-standardinput-file.service
+        test-execute/exec-standardinput-file-cat.service
         test-execute/exec-standardoutput-file.service
         test-execute/exec-standardoutput-append.service
         test-execute/exec-supplementarygroups-multiple-groups-default-group-user.service
diff --git a/test/test-execute/exec-standardinput-file-cat.service b/test/test-execute/exec-standardinput-file-cat.service
new file mode 100644 (file)
index 0000000..a0c786c
--- /dev/null
@@ -0,0 +1,9 @@
+[Unit]
+Description=Test for StandardInput=file:
+
+[Service]
+ExecStart=cat
+Type=oneshot
+StandardInput=file:/etc/os-release
+# We leave StandardOutput= unset here, to verify https://github.com/systemd/systemd/issues/14560 works
+# The "cat" tool is going to write to stdout, which fails if we dup() stdin to stdout
index 5fffa5b9f862a20560b692f2d586776755678ae6..1bba8e429bfc5b9b7452485d2863b0d6c0ff0d55 100644 (file)
@@ -469,9 +469,14 @@ printf "[Service]\nTimeoutSec=180s\n" >/etc/systemd/system/systemd-journal-flush
 # 1
 # Let's workaround this by clearing the previously set LD_PRELOAD env variable,
 # so the libasan library is not loaded for this particular service
-REMOUNTFS_CONF_DIR=/etc/systemd/system/systemd-remount-fs.service.d
-mkdir -p "\$REMOUNTFS_CONF_DIR"
-printf "[Service]\nUnsetEnvironment=LD_PRELOAD\n" >"\$REMOUNTFS_CONF_DIR/env.conf"
+unset_ld_preload() {
+    local _dropin_dir="/etc/systemd/system/\$1.service.d"
+    mkdir -p "\$_dropin_dir"
+    printf "[Service]\nUnsetEnvironment=LD_PRELOAD\n" >"\$_dropin_dir/unset_ld_preload.conf"
+}
+
+unset_ld_preload systemd-remount-fs
+unset_ld_preload testsuite
 
 export ASAN_OPTIONS=\$DEFAULT_ASAN_OPTIONS:log_path=/systemd.asan.log UBSAN_OPTIONS=\$DEFAULT_UBSAN_OPTIONS
 exec  $ROOTLIBDIR/systemd "\$@"
@@ -732,7 +737,6 @@ install_config_files() {
     inst /etc/shells
     inst /etc/nsswitch.conf
     inst /etc/pam.conf || :
-    inst /etc/securetty || :
     inst /etc/os-release
     inst /etc/localtime
     # we want an empty environment
diff --git a/test/test-network/conf/25-fibrule-uidrange.network b/test/test-network/conf/25-fibrule-uidrange.network
new file mode 100644 (file)
index 0000000..f42dfee
--- /dev/null
@@ -0,0 +1,9 @@
+[Match]
+Name=test1
+
+[RoutingPolicyRule]
+TypeOfService=0x08
+Table=7
+From= 192.168.100.18
+Priority=111
+User=100-200
index f45e948b682a1ffbb6eb935c012c20cd42804113..01292d7c3b839cb11f7f97aa12449fe79d423cc5 100755 (executable)
@@ -100,6 +100,23 @@ def expectedFailureIfRoutingPolicyIPProtoIsNotAvailable():
 
     return f
 
+def expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable():
+    def f(func):
+        support = False
+        rc = call('ip rule add from 192.168.100.19 table 7 uidrange 200-300', stderr=subprocess.DEVNULL)
+        if rc == 0:
+            ret = run('ip rule list from 192.168.100.19 table 7', stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+            if ret.returncode == 0 and 'uidrange 200-300' in ret.stdout.rstrip():
+                support = True
+            call('ip rule del from 192.168.100.19 table 7 uidrange 200-300')
+
+        if support:
+            return func
+        else:
+            return unittest.expectedFailure(func)
+
+    return f
+
 def expectedFailureIfLinkFileFieldIsNotSet():
     def f(func):
         support = False
@@ -1572,6 +1589,7 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         '25-bond-active-backup-slave.netdev',
         '25-fibrule-invert.network',
         '25-fibrule-port-range.network',
+        '25-fibrule-uidrange.network',
         '25-gre-tunnel-remote-any.netdev',
         '25-ip6gre-tunnel-remote-any.netdev',
         '25-ipv6-address-label-section.network',
@@ -1776,6 +1794,19 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         self.assertRegex(output, 'tcp')
         self.assertRegex(output, 'lookup 7')
 
+    @expectedFailureIfRoutingPolicyUIDRangeIsNotAvailable()
+    def test_routing_policy_rule_uidrange(self):
+        copy_unit_to_networkd_unit_path('25-fibrule-uidrange.network', '11-dummy.netdev')
+        start_networkd()
+        self.wait_online(['test1:degraded'])
+
+        output = check_output('ip rule')
+        print(output)
+        self.assertRegex(output, '111')
+        self.assertRegex(output, 'from 192.168.100.18')
+        self.assertRegex(output, 'lookup 7')
+        self.assertRegex(output, 'uidrange 100-200')
+
     def test_route_static(self):
         copy_unit_to_networkd_unit_path('25-route-static.network', '12-dummy.netdev')
         start_networkd()
@@ -2211,7 +2242,10 @@ class NetworkdNetworkTests(unittest.TestCase, Utilities):
         output = check_output('tc qdisc show dev dummy98')
         print(output)
         self.assertRegex(output, 'qdisc fq')
-        self.assertRegex(output, 'limit 1000p flow_limit 200p buckets 512 orphan_mask 511 quantum 1500 initial_quantum 13000 maxrate 1Mbit')
+        self.assertRegex(output, 'limit 1000p flow_limit 200p buckets 512 orphan_mask 511')
+        self.assertRegex(output, 'quantum 1500')
+        self.assertRegex(output, 'initial_quantum 13000')
+        self.assertRegex(output, 'maxrate 1Mbit')
         self.assertRegex(output, 'qdisc codel')
         self.assertRegex(output, 'limit 2000p target 10.0ms ce_threshold 100.0ms interval 50.0ms ecn')
 
index 4914e8ec8c6be239e1700d5aa69eeccf73d4f005..11d87d275b743b82387aa1474771a9e9fde7b26d 100644 (file)
@@ -32,17 +32,17 @@ Z /run/log/journal/%m ~2750 root systemd-journal - -
 m4_ifdef(`HAVE_ACL',`m4_dnl
 m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl
 m4_ifdef(`ENABLE_WHEEL_GROUP',``
-a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x
-a+ /run/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x
+a+ /run/log/journal    - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x,group::r-x,group:adm:r-x,group:wheel:r-x
+a+ /run/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-x,group:wheel:r-x
 a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--,group:wheel:r--
 '',``
-a+ /run/log/journal/%m - - - - d:group:adm:r-x
-a+ /run/log/journal/%m - - - - group:adm:r-x
+a+ /run/log/journal    - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x
+a+ /run/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x
 a+ /run/log/journal/%m/*.journal* - - - - group:adm:r--
 '')',`m4_dnl
 m4_ifdef(`ENABLE_WHEEL_GROUP',``
-a+ /run/log/journal/%m - - - - d:group:wheel:r-x
-a+ /run/log/journal/%m - - - - group:wheel:r-x
+a+ /run/log/journal    - - - - d:group::r-x,d:group:wheel:r-x,group::r-x,group:wheel:r-x
+a+ /run/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-x
 a+ /run/log/journal/%m/*.journal* - - - - group:wheel:r--
 '')')')m4_dnl
 
@@ -52,23 +52,17 @@ z /var/log/journal/%m/system.journal 0640 root systemd-journal - -
 m4_ifdef(`HAVE_ACL',`m4_dnl
 m4_ifdef(`ENABLE_ADM_GROUP',`m4_dnl
 m4_ifdef(`ENABLE_WHEEL_GROUP',``
-a+ /var/log/journal    - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x
-a+ /var/log/journal    - - - - group::r-x,group:adm:r-x,group:wheel:r-x
-a+ /var/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x
-a+ /var/log/journal/%m - - - - group:adm:r-x,group:wheel:r-x
+a+ /var/log/journal    - - - - d:group::r-x,d:group:adm:r-x,d:group:wheel:r-x,group::r-x,group:adm:r-x,group:wheel:r-x
+a+ /var/log/journal/%m - - - - d:group:adm:r-x,d:group:wheel:r-x,group:adm:r-x,group:wheel:r-x
 a+ /var/log/journal/%m/system.journal - - - - group:adm:r--,group:wheel:r--
 '', ``
-a+ /var/log/journal    - - - - d:group::r-x,d:group:adm:r-x
-a+ /var/log/journal    - - - - group::r-x,group:adm:r-x
-a+ /var/log/journal/%m - - - - d:group:adm:r-x
-a+ /var/log/journal/%m - - - - group:adm:r-x
+a+ /var/log/journal    - - - - d:group::r-x,d:group:adm:r-x,group::r-x,group:adm:r-x
+a+ /var/log/journal/%m - - - - d:group:adm:r-x,group:adm:r-x
 a+ /var/log/journal/%m/system.journal - - - - group:adm:r--
 '')',`m4_dnl
 m4_ifdef(`ENABLE_WHEEL_GROUP',``
-a+ /var/log/journal    - - - - d:group::r-x,d:group:wheel:r-x
-a+ /var/log/journal    - - - - group::r-x,group:wheel:r-x
-a+ /var/log/journal/%m - - - - d:group:wheel:r-x
-a+ /var/log/journal/%m - - - - group:wheel:r-x
+a+ /var/log/journal    - - - - d:group::r-x,d:group:wheel:r-x,group::r-x,group:wheel:r-x
+a+ /var/log/journal/%m - - - - d:group:wheel:r-x,group:wheel:r-x
 a+ /var/log/journal/%m/system.journal - - - - group:wheel:r--
 '')')')m4_dnl
 
index 6884048d83e28a5703749ce9510220113e9f351a..5e48a7e0c69b03ef566b9742401d7c54c31dd83f 100755 (executable)
@@ -32,10 +32,6 @@ if [ -z "$FUZZING_ENGINE" ]; then
     fuzzflag="llvm-fuzz=true"
 fi
 
-# FIXME: temporarily pin the meson version as 0.53 doesn't work with older python 3.5
-# See: https://github.com/mesonbuild/meson/issues/6427
-pip3 install meson==0.52.1
-
 meson $build -D$fuzzflag -Db_lundef=false
 ninja -v -C $build fuzzers
 
index 6a6923fbc86a138d7623220f19c8c1724d17dfe2..ac86e6274e0c86f2643a9abd3c44f758971d3a0f 100755 (executable)
@@ -18,7 +18,11 @@ REPO_ROOT="${REPO_ROOT:-$PWD}"
 ADDITIONAL_DEPS=(python3-libevdev
                  python3-pyparsing
                  clang
-                 perl)
+                 perl
+                 libpwquality-dev
+                 libfdisk-dev
+                 libp11-kit-dev
+                 libssl-dev)
 
 function info() {
     echo -e "\033[33;1m$1\033[0m"
index e07b4938dfd47a5eabcda2d3cf46f7f522b34d4d..b0f431aac9e15191a526280b69321a779f905572 100755 (executable)
@@ -23,7 +23,11 @@ ADDITIONAL_DEPS=(dnf-plugins-core
                  libubsan
                  clang
                  llvm
-                 perl)
+                 perl
+                 libfdisk-devel
+                 libpwquality-devel
+                 openssl-devel
+                 p11-kit-devel)
 
 function info() {
     echo -e "\033[33;1m$1\033[0m"
index 1541b466526681ec65eb99f243c71613db606631..b89cfba11a064595efb233a8b820ae5a1ad6c8e4 100755 (executable)
@@ -10,9 +10,9 @@ sudo bash -c "echo 'deb-src http://archive.ubuntu.com/ubuntu/ xenial main restri
 sudo apt-get update -y
 sudo apt-get build-dep systemd -y
 sudo apt-get install -y ninja-build python3-pip python3-setuptools quota
-# FIXME: temporarily pin the meson version as 0.53 doesn't work with older python 3.5
-# See: https://github.com/mesonbuild/meson/issues/6427
-pip3 install meson==0.52.1
+# The following should be dropped when debian packaging has been updated to include them
+sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
+pip3 install meson
 
 cd $REPO_ROOT
 export PATH="$HOME/.local/bin/:$PATH"
index 376761e20ccfa7a7da1fe90bfaf0d69ca4be00ed..c3d76134fec4a5f97dbb0d4acf9cda9b26fd4cc3 100755 (executable)
@@ -14,9 +14,9 @@ sudo bash -c "echo 'deb-src http://archive.ubuntu.com/ubuntu/ xenial main restri
 sudo apt-get update -y
 sudo apt-get build-dep systemd -y
 sudo apt-get install -y ninja-build python3-pip python3-setuptools
-# FIXME: temporarily pin the meson version as 0.53 doesn't work with older python 3.5
-# See: https://github.com/mesonbuild/meson/issues/6427
-pip3 install meson==0.52.1
+# The following should be dropped when debian packaging has been updated to include them
+sudo apt-get install -y libfdisk-dev libp11-kit-dev libssl-dev libpwquality-dev
+pip3 install meson
 
 cd $REPO_ROOT
 export PATH="$HOME/.local/bin/:$PATH"
similarity index 88%
rename from units/initrd-cleanup.service.in
rename to units/initrd-cleanup.service
index 9775540c947b0cb18e5787221c22801300a4920a..b04607671503fcf043c11ba32c9daffe971de330 100644 (file)
@@ -17,4 +17,4 @@ After=initrd-root-fs.target initrd-fs.target initrd.target
 
 [Service]
 Type=oneshot
-ExecStart=@rootbindir@/systemctl --no-block isolate initrd-switch-root.target
+ExecStart=systemctl --no-block isolate initrd-switch-root.target
similarity index 77%
rename from units/initrd-parse-etc.service.in
rename to units/initrd-parse-etc.service
index 2b3cd61cd6014f4cebc637fb237b34dc18a8e2f4..b0e38368cfbd3782d861675c8b97a707c68c3850 100644 (file)
@@ -18,7 +18,7 @@ ConditionPathExists=/etc/initrd-release
 
 [Service]
 Type=oneshot
-ExecStartPre=-@rootbindir@/systemctl daemon-reload
+ExecStartPre=-systemctl daemon-reload
 # we have to retrigger initrd-fs.target after daemon-reload
-ExecStart=-@rootbindir@/systemctl --no-block start initrd-fs.target
-ExecStart=@rootbindir@/systemctl --no-block start initrd-cleanup.service
+ExecStart=-systemctl --no-block start initrd-fs.target
+ExecStart=systemctl --no-block start initrd-cleanup.service
similarity index 89%
rename from units/initrd-switch-root.service.in
rename to units/initrd-switch-root.service
index 6ce468e872c83dfadd595f419c0d2daf3c6fc9aa..c1a37928880535435318a74e982ec76f8e385bd6 100644 (file)
@@ -17,4 +17,4 @@ AllowIsolate=yes
 
 [Service]
 Type=oneshot
-ExecStart=@rootbindir@/systemctl --no-block switch-root /sysroot
+ExecStart=systemctl --no-block switch-root /sysroot
similarity index 93%
rename from units/initrd-udevadm-cleanup-db.service.in
rename to units/initrd-udevadm-cleanup-db.service
index 09af690986c9b5b929b1ee26624d67dcb926eba7..ad2f2a5b35b74d277ed0f1a2666705f7ba4f38b5 100644 (file)
@@ -17,4 +17,4 @@ Before=initrd-switch-root.target
 
 [Service]
 Type=oneshot
-ExecStart=-@rootbindir@/udevadm info --cleanup-db
+ExecStart=-udevadm info --cleanup-db
index 82c399bee89fd4e938eb1401d39b36fb7356bbc6..0dc85ac64a3c88cb28a19429b610d3af89bd130c 100644 (file)
@@ -23,10 +23,14 @@ units = [
         ['hibernate.target',                    'ENABLE_HIBERNATE'],
         ['hybrid-sleep.target',                 'ENABLE_HIBERNATE'],
         ['suspend-then-hibernate.target',       'ENABLE_HIBERNATE'],
+        ['initrd-cleanup.service',              ''],
         ['initrd-fs.target',                    ''],
+        ['initrd-parse-etc.service',            ''],
         ['initrd-root-device.target',           ''],
         ['initrd-root-fs.target',               ''],
+        ['initrd-switch-root.service',          ''],
         ['initrd-switch-root.target',           ''],
+        ['initrd-udevadm-cleanup-db.service',   ''],
         ['initrd.target',                       ''],
         ['kexec.target',                        ''],
         ['ldconfig.service',                    'ENABLE_LDCONFIG',
@@ -81,13 +85,24 @@ units = [
         ['system-update-cleanup.service',       ''],
         ['systemd-ask-password-console.path',   '',
          'sysinit.target.wants/'],
+        ['systemd-ask-password-console.service', ''],
         ['systemd-ask-password-wall.path',      '',
          'multi-user.target.wants/'],
+        ['systemd-ask-password-wall.service',   ''],
+        ['systemd-boot-system-token.service',   'ENABLE_EFI',
+         'sysinit.target.wants/'],
         ['systemd-coredump.socket',             'ENABLE_COREDUMP',
          'sockets.target.wants/'],
-        ['systemd-exit.service',                 ''],
+        ['systemd-exit.service',                ''],
+        ['systemd-firstboot.service',           'ENABLE_FIRSTBOOT',
+         'sysinit.target.wants/'],
+        ['systemd-halt.service',                ''],
         ['systemd-initctl.socket',              '',
          'sockets.target.wants/'],
+        ['systemd-journal-catalog-update.service', '',
+         'sysinit.target.wants/'],
+        ['systemd-journal-flush.service',       '',
+         'sysinit.target.wants/'],
         ['systemd-journal-gatewayd.socket',     'ENABLE_REMOTE HAVE_MICROHTTPD'],
         ['systemd-journal-remote.socket',       'ENABLE_REMOTE HAVE_MICROHTTPD'],
         ['systemd-journald-audit.socket',       '',
@@ -96,18 +111,33 @@ units = [
          'sockets.target.wants/'],
         ['systemd-journald.socket',             '',
          'sockets.target.wants/'],
-        ['systemd-userdbd.socket',              'ENABLE_USERDB',
-         'sockets.target.wants/'],
+        ['systemd-kexec.service',               ''],
+        ['systemd-machine-id-commit.service',   '',
+         'sysinit.target.wants/'],
+        ['systemd-journald@.socket',            ''],
+        ['systemd-journald-varlink@.socket',    ''],
         ['systemd-networkd.socket',             'ENABLE_NETWORKD'],
-        ['systemd-poweroff.service',             ''],
-        ['systemd-reboot.service',               ''],
+        ['systemd-poweroff.service',            ''],
+        ['systemd-reboot.service',              ''],
         ['systemd-rfkill.socket',               'ENABLE_RFKILL'],
+        ['systemd-sysusers.service',            'ENABLE_SYSUSERS',
+         'sysinit.target.wants/'],
+        ['systemd-tmpfiles-clean.service',      'ENABLE_TMPFILES'],
         ['systemd-tmpfiles-clean.timer',        'ENABLE_TMPFILES',
          'timers.target.wants/'],
+        ['systemd-tmpfiles-setup-dev.service',  'ENABLE_TMPFILES',
+         'sysinit.target.wants/'],
+        ['systemd-tmpfiles-setup.service',      'ENABLE_TMPFILES',
+         'sysinit.target.wants/'],
         ['systemd-udevd-control.socket',        '',
          'sockets.target.wants/'],
+        ['systemd-udev-settle.service',         ''],
+        ['systemd-udev-trigger.service',        '',
+         'sysinit.target.wants/'],
         ['systemd-udevd-kernel.socket',         '',
          'sockets.target.wants/'],
+        ['systemd-userdbd.socket',              'ENABLE_USERDB',
+         'sockets.target.wants/'],
         ['time-set.target',                     ''],
         ['time-sync.target',                    ''],
         ['timers.target',                       ''],
@@ -122,31 +152,20 @@ units = [
 in_units = [
         ['debug-shell.service',                  ''],
         ['emergency.service',                    ''],
-        ['initrd-cleanup.service',               ''],
-        ['initrd-parse-etc.service',             ''],
-        ['initrd-switch-root.service',           ''],
-        ['initrd-udevadm-cleanup-db.service',    ''],
         ['kmod-static-nodes.service',            'HAVE_KMOD ENABLE_TMPFILES',
          'sysinit.target.wants/'],
         ['quotaon.service',                      'ENABLE_QUOTACHECK'],
         ['rc-local.service',                     'HAVE_SYSV_COMPAT'],
         ['rescue.service',                       ''],
-        ['systemd-ask-password-console.service', ''],
-        ['systemd-ask-password-wall.service',    ''],
         ['systemd-backlight@.service',           'ENABLE_BACKLIGHT'],
         ['systemd-binfmt.service',               'ENABLE_BINFMT',
          'sysinit.target.wants/'],
         ['systemd-bless-boot.service',           'ENABLE_EFI HAVE_BLKID'],
         ['systemd-boot-check-no-failures.service', ''],
-        ['systemd-boot-system-token.service',    'ENABLE_EFI',
-         'sysinit.target.wants/'],
         ['systemd-coredump@.service',            'ENABLE_COREDUMP'],
         ['systemd-pstore.service',               'ENABLE_PSTORE'],
-        ['systemd-firstboot.service',            'ENABLE_FIRSTBOOT',
-         'sysinit.target.wants/'],
         ['systemd-fsck-root.service',            ''],
         ['systemd-fsck@.service',                ''],
-        ['systemd-halt.service',                 ''],
         ['systemd-hibernate-resume@.service',    'ENABLE_HIBERNATE'],
         ['systemd-hibernate.service',            'ENABLE_HIBERNATE'],
         ['systemd-hybrid-sleep.service',         'ENABLE_HIBERNATE'],
@@ -158,22 +177,16 @@ in_units = [
         ['systemd-importd.service',              'ENABLE_IMPORTD',
          'dbus-org.freedesktop.import1.service'],
         ['systemd-initctl.service',               ''],
-        ['systemd-journal-catalog-update.service', '',
-         'sysinit.target.wants/'],
-        ['systemd-journal-flush.service',         '',
-         'sysinit.target.wants/'],
         ['systemd-journal-gatewayd.service',     'ENABLE_REMOTE HAVE_MICROHTTPD'],
         ['systemd-journal-remote.service',       'ENABLE_REMOTE HAVE_MICROHTTPD'],
         ['systemd-journal-upload.service',       'ENABLE_REMOTE HAVE_LIBCURL'],
         ['systemd-journald.service',             '',
          'sysinit.target.wants/'],
-        ['systemd-kexec.service',                ''],
+        ['systemd-journald@.service',            ''],
         ['systemd-localed.service',              'ENABLE_LOCALED',
          'dbus-org.freedesktop.locale1.service'],
         ['systemd-logind.service',               'ENABLE_LOGIND',
          'multi-user.target.wants/ dbus-org.freedesktop.login1.service'],
-        ['systemd-machine-id-commit.service',    '',
-         'sysinit.target.wants/'],
         ['systemd-machined.service',             'ENABLE_MACHINED',
          'dbus-org.freedesktop.machine1.service'],
         ['systemd-modules-load.service',         'HAVE_KMOD',
@@ -185,6 +198,8 @@ in_units = [
         ['systemd-portabled.service',            'ENABLE_PORTABLED',
          'dbus-org.freedesktop.portable1.service'],
         ['systemd-userdbd.service',              'ENABLE_USERDB'],
+        ['systemd-homed.service',                'ENABLE_HOMED',
+         'multi-user.target.wants/ dbus-org.freedesktop.home1.service'],
         ['systemd-quotacheck.service',           'ENABLE_QUOTACHECK'],
         ['systemd-random-seed.service',          'ENABLE_RANDOMSEED',
          'sysinit.target.wants/'],
@@ -194,20 +209,10 @@ in_units = [
         ['systemd-suspend.service',              ''],
         ['systemd-sysctl.service',               '',
          'sysinit.target.wants/'],
-        ['systemd-sysusers.service',             'ENABLE_SYSUSERS',
-         'sysinit.target.wants/'],
         ['systemd-timedated.service',            'ENABLE_TIMEDATED',
          'dbus-org.freedesktop.timedate1.service'],
         ['systemd-timesyncd.service',            'ENABLE_TIMESYNCD'],
         ['systemd-time-wait-sync.service',       'ENABLE_TIMESYNCD'],
-        ['systemd-tmpfiles-clean.service',       'ENABLE_TMPFILES'],
-        ['systemd-tmpfiles-setup-dev.service',   'ENABLE_TMPFILES',
-         'sysinit.target.wants/'],
-        ['systemd-tmpfiles-setup.service',       'ENABLE_TMPFILES',
-         'sysinit.target.wants/'],
-        ['systemd-udev-settle.service',          ''],
-        ['systemd-udev-trigger.service',         '',
-         'sysinit.target.wants/'],
         ['systemd-udevd.service',                '',
          'sysinit.target.wants/'],
         ['systemd-update-done.service',          '',
@@ -220,6 +225,8 @@ in_units = [
          'multi-user.target.wants/'],
         ['systemd-vconsole-setup.service',       'ENABLE_VCONSOLE'],
         ['systemd-volatile-root.service',        ''],
+        ['systemd-repart.service',               'ENABLE_REPART',
+         'sysinit.target.wants/ initrd-root-fs.target.wants/'],
         ['user-runtime-dir@.service',            ''],
         ['user@.service',                        ''],
 ]
similarity index 90%
rename from units/systemd-ask-password-console.service.in
rename to units/systemd-ask-password-console.service
index 60fa7c320009cae82d764499150d3156d37fee91..6ee4c253a39dafcfe4c294072e7db97598b4f704 100644 (file)
@@ -17,5 +17,5 @@ Before=shutdown.target
 ConditionPathExists=!/run/plymouth/pid
 
 [Service]
-ExecStart=@rootbindir@/systemd-tty-ask-password-agent --watch --console
+ExecStart=systemd-tty-ask-password-agent --watch --console
 SystemCallArchitectures=native
similarity index 68%
rename from units/systemd-ask-password-wall.service.in
rename to units/systemd-ask-password-wall.service
index 1e4808b6d57fadd8a6a82a4b92a5c3e6e6bb79af..52a3037cd48ff454b2f56d07b99327778e9929ac 100644 (file)
@@ -13,6 +13,6 @@ Documentation=man:systemd-ask-password-console.service(8)
 After=systemd-user-sessions.service
 
 [Service]
-ExecStartPre=-@SYSTEMCTL@ stop systemd-ask-password-console.path systemd-ask-password-console.service systemd-ask-password-plymouth.path systemd-ask-password-plymouth.service
-ExecStart=@rootbindir@/systemd-tty-ask-password-agent --wall
+ExecStartPre=-systemctl stop systemd-ask-password-console.path systemd-ask-password-console.service systemd-ask-password-plymouth.path systemd-ask-password-plymouth.service
+ExecStart=systemd-tty-ask-password-agent --wall
 SystemCallArchitectures=native
similarity index 96%
rename from units/systemd-boot-system-token.service.in
rename to units/systemd-boot-system-token.service
index e9b742c5c7015b610c8d816e57905659fd4730a4..8aead02417824ee78e2bea41023a2a38e29344d1 100644 (file)
@@ -31,4 +31,4 @@ ConditionPathExists=|!/sys/firmware/efi/efivars/LoaderRandomSeed-4a67b082-0a4c-4
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@bindir@/bootctl random-seed --graceful
+ExecStart=bootctl random-seed --graceful
similarity index 87%
rename from units/systemd-firstboot.service.in
rename to units/systemd-firstboot.service
index d4deba90b7445c081827d62d227bd122a3d4d552..9f5c7101cd50d846e0978a895245b74599383231 100644 (file)
@@ -20,7 +20,7 @@ ConditionFirstBoot=yes
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-firstboot --prompt-locale --prompt-timezone --prompt-root-password
+ExecStart=systemd-firstboot --prompt-locale --prompt-timezone --prompt-root-password
 StandardOutput=tty
 StandardInput=tty
 StandardError=tty
similarity index 93%
rename from units/systemd-halt.service.in
rename to units/systemd-halt.service
index 09c10058298de140c09380182b33c824e6cd38bd..cd16d1de202a827e0f380d94f287b6ebcfc83a58 100644 (file)
@@ -16,4 +16,4 @@ After=shutdown.target umount.target final.target
 
 [Service]
 Type=oneshot
-ExecStart=@SYSTEMCTL@ --force halt
+ExecStart=systemctl --force halt
diff --git a/units/systemd-homed.service.in b/units/systemd-homed.service.in
new file mode 100644 (file)
index 0000000..512804c
--- /dev/null
@@ -0,0 +1,36 @@
+#  SPDX-License-Identifier: LGPL-2.1+
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Home Manager
+Documentation=man:systemd-homed.service(8)
+RequiresMountsFor=/home
+
+[Service]
+BusName=org.freedesktop.home1
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_SETGID CAP_SETUID
+DeviceAllow=/dev/loop-control rw
+DeviceAllow=/dev/mapper/control rw
+DeviceAllow=block-* rw
+ExecStart=@rootlibexecdir@/systemd-homed
+IPAddressDeny=any
+KillMode=mixed
+LimitNOFILE=@HIGH_RLIMIT_NOFILE@
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+PrivateNetwork=yes
+RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_ALG
+RestrictNamespaces=mnt
+RestrictRealtime=yes
+StateDirectory=systemd/home
+SystemCallArchitectures=native
+SystemCallErrorNumber=EPERM
+SystemCallFilter=@system-service @mount
+@SERVICE_WATCHDOG@
index 259fe0de8b01e39c2c708cc02b34ffaaf9620d24..9887018a1f23631a3ca7e2cfd0f9f8b935054fee 100644 (file)
@@ -22,5 +22,5 @@ ConditionDirectoryNotEmpty=|/etc/udev/hwdb.d/
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-hwdb update
+ExecStart=systemd-hwdb update
 TimeoutSec=90s
similarity index 93%
rename from units/systemd-journal-catalog-update.service.in
rename to units/systemd-journal-catalog-update.service
index 18b2739ffab0bfe066937cd72c1b69d8fb205df6..6db55a5490ff89825e0321dc8d28b5321f5b1d10 100644 (file)
@@ -19,5 +19,5 @@ ConditionNeedsUpdate=/var
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/journalctl --update-catalog
+ExecStart=journalctl --update-catalog
 TimeoutSec=90s
similarity index 87%
rename from units/systemd-journal-flush.service.in
rename to units/systemd-journal-flush.service
index 29b006cba5775ac2fb42859e4d4505d923dac776..0f45743fa0a76e9e54c41b6b46a000ab4eda9af0 100644 (file)
@@ -17,8 +17,8 @@ Before=systemd-tmpfiles-setup.service
 RequiresMountsFor=/var/log/journal
 
 [Service]
-ExecStart=@rootbindir@/journalctl --flush
-ExecStop=@rootbindir@/journalctl --smart-relinquish-var
+ExecStart=journalctl --flush
+ExecStop=journalctl --smart-relinquish-var
 Type=oneshot
 RemainAfterExit=yes
 TimeoutSec=90s
diff --git a/units/systemd-journald-varlink@.socket b/units/systemd-journald-varlink@.socket
new file mode 100644 (file)
index 0000000..b6730c2
--- /dev/null
@@ -0,0 +1,18 @@
+#  SPDX-License-Identifier: LGPL-2.1+
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Varlink Socket for Namespace %i
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+StopWhenUnneeded=yes
+
+[Socket]
+Service=systemd-journald@%i.service
+ListenStream=/run/systemd/journal.%i/io.systemd.journal
+SocketMode=0600
index 303d5a4826c11ebb6d8188018c9911ed5adea137..5144868bcb711912e5b640f27b6eca8bcca3c767 100644 (file)
@@ -16,7 +16,6 @@ After=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-a
 Before=sysinit.target
 
 [Service]
-OOMScoreAdjust=-250
 CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_SYSLOG CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
 DeviceAllow=char-* rw
 ExecStart=@rootlibexecdir@/systemd-journald
@@ -25,12 +24,15 @@ IPAddressDeny=any
 LockPersonality=yes
 MemoryDenyWriteExecute=yes
 NoNewPrivileges=yes
+OOMScoreAdjust=-250
 Restart=always
 RestartSec=0
 RestrictAddressFamilies=AF_UNIX AF_NETLINK
 RestrictNamespaces=yes
 RestrictRealtime=yes
 RestrictSUIDSGID=yes
+RuntimeDirectory=systemd/journal
+RuntimeDirectoryPreserve=yes
 Sockets=systemd-journald.socket systemd-journald-dev-log.socket systemd-journald-audit.socket
 StandardOutput=null
 SystemCallArchitectures=native
diff --git a/units/systemd-journald@.service.in b/units/systemd-journald@.service.in
new file mode 100644 (file)
index 0000000..e7ea919
--- /dev/null
@@ -0,0 +1,44 @@
+#  SPDX-License-Identifier: LGPL-2.1+
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Service for Namespace %i
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+Requires=systemd-journald@%i.socket systemd-journald-varlink@%i.socket
+After=systemd-journald@%i.socket systemd-journald-varlink@%i.socket
+
+[Service]
+CapabilityBoundingSet=CAP_SYS_ADMIN CAP_DAC_OVERRIDE CAP_SYS_PTRACE CAP_CHOWN CAP_DAC_READ_SEARCH CAP_FOWNER CAP_SETUID CAP_SETGID CAP_MAC_OVERRIDE
+DevicePolicy=closed
+ExecStart=@rootlibexecdir@/systemd-journald %i
+FileDescriptorStoreMax=4224
+Group=systemd-journal
+IPAddressDeny=any
+LockPersonality=yes
+LogsDirectory=journal/%m.%i
+LogsDirectoryMode=02755
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictAddressFamilies=AF_UNIX AF_NETLINK
+RestrictNamespaces=yes
+RestrictRealtime=yes
+RestrictSUIDSGID=yes
+RuntimeDirectory=systemd/journal.%i
+RuntimeDirectoryPreserve=yes
+Sockets=systemd-journald@%i.socket
+StandardOutput=null
+SystemCallArchitectures=native
+SystemCallErrorNumber=EPERM
+SystemCallFilter=@system-service
+Type=notify
+@SERVICE_WATCHDOG@
+
+# If there are many split up journal files we need a lot of fds to access them
+# all in parallel.
+LimitNOFILE=@HIGH_RLIMIT_NOFILE@
diff --git a/units/systemd-journald@.socket b/units/systemd-journald@.socket
new file mode 100644 (file)
index 0000000..3badd78
--- /dev/null
@@ -0,0 +1,24 @@
+#  SPDX-License-Identifier: LGPL-2.1+
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Journal Socket for Namespace %i
+Documentation=man:systemd-journald.service(8) man:journald.conf(5)
+StopWhenUnneeded=yes
+
+[Socket]
+Service=systemd-journald@%i.service
+ListenStream=/run/systemd/journal.%i/stdout
+ListenDatagram=/run/systemd/journal.%i/socket
+ListenDatagram=/run/systemd/journal.%i/dev-log
+SocketMode=0666
+PassCredentials=yes
+PassSecurity=yes
+ReceiveBuffer=8M
+SendBuffer=8M
similarity index 93%
rename from units/systemd-kexec.service.in
rename to units/systemd-kexec.service
index 1201b232895b810df5ea8f9ff54b3ebb8716b2b9..7413e1d6b1a5881c518257cc3fa7f32ce5bd80ba 100644 (file)
@@ -16,4 +16,4 @@ After=shutdown.target umount.target final.target
 
 [Service]
 Type=oneshot
-ExecStart=@SYSTEMCTL@ --force kexec
+ExecStart=systemctl --force kexec
similarity index 92%
rename from units/systemd-machine-id-commit.service.in
rename to units/systemd-machine-id-commit.service
index 4f348730eed0cca43ba85587e6f2e61f7213da34..e3acb0f3260bc30ae28d6556296c561fe145c7ec 100644 (file)
@@ -20,5 +20,5 @@ ConditionPathIsMountPoint=/etc/machine-id
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-machine-id-setup --commit
+ExecStart=systemd-machine-id-setup --commit
 TimeoutSec=30s
index 01931665a494bca3796c7e4215c73b74942da092..1b69677496d9e44bc5429fe4b087f087dc1bc337 100644 (file)
@@ -33,7 +33,7 @@ ProtectKernelLogs=yes
 ProtectSystem=strict
 Restart=on-failure
 RestartSec=0
-RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 AF_PACKET
+RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 AF_PACKET AF_ALG
 RestrictNamespaces=yes
 RestrictRealtime=yes
 RestrictSUIDSGID=yes
index 5367ee44105b3bf7dddd21e2773f5473bdee7a64..51634472ac35c4dfff6ced1ae531b7706898b3f0 100644 (file)
@@ -18,7 +18,7 @@ RequiresMountsFor=/var/lib/machines
 
 [Service]
 # Make sure the DeviceAllow= lines below can properly resolve the 'block-loop' expression (and others)
-ExecStart=@bindir@/systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth -U --settings=override --machine=%i
+ExecStart=systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --network-veth -U --settings=override --machine=%i
 KillMode=mixed
 Type=notify
 RestartForceExitStatus=133
diff --git a/units/systemd-repart.service.in b/units/systemd-repart.service.in
new file mode 100644 (file)
index 0000000..7ce6aef
--- /dev/null
@@ -0,0 +1,25 @@
+#  SPDX-License-Identifier: LGPL-2.1+
+#
+#  This file is part of systemd.
+#
+#  systemd is free software; you can redistribute it and/or modify it
+#  under the terms of the GNU Lesser General Public License as published by
+#  the Free Software Foundation; either version 2.1 of the License, or
+#  (at your option) any later version.
+
+[Unit]
+Description=Repartition Root Disk
+Documentation=man:systemd-repart.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=sysroot.mount
+Before=initrd-root-fs.target shutdown.target
+ConditionVirtualization=!container
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=@rootbindir@/systemd-repart --dry-run=no
+
+# The tool returns 77 if there's no GPT partition table pre-existing
+SuccessExitStatus=77
similarity index 94%
rename from units/systemd-sysusers.service.in
rename to units/systemd-sysusers.service
index 4d11bbb76266363151dbe329a92813df5633afcd..da05e0eb1bbe9cb07c0968086ad91c4fda9f9729 100644 (file)
@@ -19,5 +19,5 @@ ConditionNeedsUpdate=/etc
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-sysusers
+ExecStart=systemd-sysusers
 TimeoutSec=90s
similarity index 92%
rename from units/systemd-tmpfiles-clean.service.in
rename to units/systemd-tmpfiles-clean.service
index 5d70aafb29f7cf411906b7c0925788dae00e7386..f20bb143ef7a99dded415e8cb24437fbb25fcfc9 100644 (file)
@@ -17,6 +17,6 @@ Before=shutdown.target
 
 [Service]
 Type=oneshot
-ExecStart=@rootbindir@/systemd-tmpfiles --clean
+ExecStart=systemd-tmpfiles --clean
 SuccessExitStatus=DATAERR
 IOSchedulingClass=idle
similarity index 90%
rename from units/systemd-tmpfiles-setup-dev.service.in
rename to units/systemd-tmpfiles-setup-dev.service
index ed52db4953a6c7a5a4f309520a387414d779a60d..1027823859e5974167959cf01d95b71567d6286d 100644 (file)
@@ -18,5 +18,5 @@ Before=sysinit.target local-fs-pre.target systemd-udevd.service shutdown.target
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-tmpfiles --prefix=/dev --create --boot
+ExecStart=systemd-tmpfiles --prefix=/dev --create --boot
 SuccessExitStatus=DATAERR CANTCREAT
similarity index 89%
rename from units/systemd-tmpfiles-setup.service.in
rename to units/systemd-tmpfiles-setup.service
index 32a475d71531a2121ebecf89388014733228c1e1..29799ee81caa3cee7542f0027835a3f2fa626263 100644 (file)
@@ -19,5 +19,5 @@ RefuseManualStop=yes
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
+ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev
 SuccessExitStatus=DATAERR CANTCREAT
similarity index 95%
rename from units/systemd-udev-settle.service.in
rename to units/systemd-udev-settle.service
index 22ebf08c5196d2044998f657062946b14a5c3548..ed6a68b864f2850b4134269f1a09cca9f2c412c1 100644 (file)
@@ -24,4 +24,4 @@ ConditionPathIsReadWrite=/sys
 Type=oneshot
 TimeoutSec=180
 RemainAfterExit=yes
-ExecStart=@rootbindir@/udevadm settle
+ExecStart=udevadm settle
similarity index 82%
rename from units/systemd-udev-trigger.service.in
rename to units/systemd-udev-trigger.service
index b60204eccc0fc7abcfa199d18bf927e495cffd8d..8a625b630599286874e658cb34409bb6bf2210ee 100644 (file)
@@ -19,5 +19,5 @@ ConditionPathIsReadWrite=/sys
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/udevadm trigger --type=subsystems --action=add
-ExecStart=@rootbindir@/udevadm trigger --type=devices --action=add
+ExecStart=udevadm trigger --type=subsystems --action=add
+ExecStart=udevadm trigger --type=devices --action=add
index 8b1dd0efc73fa91877f167dd910fd29f67dd8c8e..5eee69933bde944c9afc7db3779997891ecb3a06 100644 (file)
@@ -23,7 +23,7 @@ Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket
 Restart=always
 RestartSec=0
 ExecStart=@rootlibexecdir@/systemd-udevd
-ExecReload=@rootbindir@/udevadm control --reload --timeout 0
+ExecReload=udevadm control --reload --timeout 0
 KillMode=mixed
 TasksMax=infinity
 PrivateMounts=yes
index 36341a42f5abf48a22fadd7b075a18fb49a761ed..cb8f630b8cde79b606c7ec37f8f06ff7db5024e6 100644 (file)
@@ -13,26 +13,14 @@ units = [
         'smartcard.target',
         'sockets.target',
         'sound.target',
-        'timers.target',
         'systemd-exit.service',
+        'systemd-tmpfiles-clean.service',
         'systemd-tmpfiles-clean.timer',
+        'systemd-tmpfiles-setup.service',
+        'timers.target',
 ]
 
 foreach file : units
         install_data(file,
                      install_dir : userunitdir)
 endforeach
-
-in_units = [
-        'systemd-tmpfiles-clean.service',
-        'systemd-tmpfiles-setup.service',
-]
-
-foreach file : in_units
-        gen = configure_file(
-                input : file + '.in',
-                output : file,
-                configuration : substs)
-        install_data(gen,
-                     install_dir : userunitdir)
-endforeach
similarity index 91%
rename from units/user/systemd-tmpfiles-clean.service.in
rename to units/user/systemd-tmpfiles-clean.service
index 306b064e895330e443b43af8e14407eb5cc1d691..3be0de5f7ddfcd1b4b80ef8de8c67a41e5dc4b0e 100644 (file)
@@ -16,6 +16,6 @@ Before=basic.target shutdown.target
 
 [Service]
 Type=oneshot
-ExecStart=@rootbindir@/systemd-tmpfiles --user --clean
+ExecStart=systemd-tmpfiles --user --clean
 SuccessExitStatus=DATAERR
 IOSchedulingClass=idle
similarity index 90%
rename from units/user/systemd-tmpfiles-setup.service.in
rename to units/user/systemd-tmpfiles-setup.service
index a852ef5748038647bccd69874590414bb532377f..c4b29cbe8ce381cd331e62b2ba5d19c5df47ad9b 100644 (file)
@@ -18,7 +18,7 @@ RefuseManualStop=yes
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStart=@rootbindir@/systemd-tmpfiles --user --create --remove --boot
+ExecStart=systemd-tmpfiles --user --create --remove --boot
 SuccessExitStatus=DATAERR
 
 [Install]