From 4e66876dfc9ab1b9b7063988d7f3689568c76cfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 15 Sep 2022 15:54:18 +0200 Subject: [PATCH 01/15] units: do more reordering of ordering config No functional change, just a cleanup to make the subsequent changes easier to see. This is a continuation of 9810e419425263bde86787bc21251f1ad3c35628 > The block is reordered and split to have: > 1. description + documentation > 2. (optionally) conditions > 3. all the dependencies The dependencies for shutdown.target are listed separately because they are the other deps are for startup, and shutdown.target only matter much later. --- units/local-fs.target | 3 ++- units/proc-sys-fs-binfmt_misc.automount | 8 +++++--- units/quotaon.service.in | 7 +++++-- units/systemd-boot-update.service | 3 ++- units/systemd-firstboot.service | 13 ++++++++----- units/systemd-growfs-root.service.in | 3 ++- units/systemd-growfs@.service.in | 3 ++- units/systemd-hwdb-update.service.in | 11 +++++++---- units/systemd-journal-catalog-update.service | 11 +++++++---- units/systemd-journal-flush.service | 13 ++++++++----- units/systemd-quotacheck.service.in | 7 +++++-- units/systemd-random-seed.service.in | 15 ++++++++------- units/systemd-remount-fs.service.in | 6 ++++-- units/systemd-repart.service.in | 13 ++++++++----- units/systemd-rfkill.socket | 3 ++- units/systemd-sysusers.service | 6 +++--- units/systemd-update-utmp.service.in | 8 +++++--- units/systemd-vconsole-setup.service.in | 4 +++- 18 files changed, 86 insertions(+), 51 deletions(-) diff --git a/units/local-fs.target b/units/local-fs.target index 02797953a5..fe175a7af9 100644 --- a/units/local-fs.target +++ b/units/local-fs.target @@ -10,8 +10,9 @@ [Unit] Description=Local File Systems Documentation=man:systemd.special(7) + DefaultDependencies=no -Conflicts=shutdown.target After=local-fs-pre.target +Conflicts=shutdown.target OnFailure=emergency.target OnFailureJobMode=replace-irreversibly diff --git a/units/proc-sys-fs-binfmt_misc.automount b/units/proc-sys-fs-binfmt_misc.automount index 6b1bbdc91e..41396f9299 100644 --- a/units/proc-sys-fs-binfmt_misc.automount +++ b/units/proc-sys-fs-binfmt_misc.automount @@ -11,11 +11,13 @@ Description=Arbitrary Executable File Formats File System Automount Point Documentation=https://docs.kernel.org/admin-guide/binfmt-misc.html Documentation=https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems -DefaultDependencies=no -Before=sysinit.target -Conflicts=shutdown.target + ConditionPathExists=/proc/sys/fs/binfmt_misc/ ConditionPathIsReadWrite=/proc/sys/ +DefaultDependencies=no +Before=sysinit.target +Conflicts=shutdown.target + [Automount] Where=/proc/sys/fs/binfmt_misc diff --git a/units/quotaon.service.in b/units/quotaon.service.in index ffabebfafe..7fa7061eea 100644 --- a/units/quotaon.service.in +++ b/units/quotaon.service.in @@ -10,10 +10,13 @@ [Unit] Description=Enable File System Quotas Documentation=man:quotaon(8) + +ConditionPathExists={{QUOTAON}} + DefaultDependencies=no After=systemd-quotacheck.service -Before=remote-fs.target shutdown.target -ConditionPathExists={{QUOTAON}} +Before=remote-fs.target +Before=shutdown.target [Service] Type=oneshot diff --git a/units/systemd-boot-update.service b/units/systemd-boot-update.service index fe63fde35a..ce9187bbfe 100644 --- a/units/systemd-boot-update.service +++ b/units/systemd-boot-update.service @@ -13,8 +13,9 @@ Documentation=man:bootctl(1) DefaultDependencies=no After=local-fs.target +Before=sysinit.target systemd-update-done.service Conflicts=shutdown.target -Before=sysinit.target shutdown.target systemd-update-done.service +Before=shutdown.target [Service] Type=oneshot diff --git a/units/systemd-firstboot.service b/units/systemd-firstboot.service index 2e57b064c1..58f3eeb679 100644 --- a/units/systemd-firstboot.service +++ b/units/systemd-firstboot.service @@ -10,14 +10,17 @@ [Unit] Description=First Boot Wizard Documentation=man:systemd-firstboot(1) -DefaultDependencies=no -Conflicts=shutdown.target -After=systemd-remount-fs.service -Before=systemd-sysusers.service systemd-vconsole-setup.service sysinit.target first-boot-complete.target shutdown.target -Wants=first-boot-complete.target + ConditionPathIsReadWrite=/etc ConditionFirstBoot=yes +DefaultDependencies=no +After=systemd-remount-fs.service +Before=systemd-sysusers.service systemd-vconsole-setup.service sysinit.target first-boot-complete.target +Wants=first-boot-complete.target +Conflicts=shutdown.target +Before=shutdown.target + [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-growfs-root.service.in b/units/systemd-growfs-root.service.in index 295bafd5af..61b825672d 100644 --- a/units/systemd-growfs-root.service.in +++ b/units/systemd-growfs-root.service.in @@ -10,9 +10,10 @@ [Unit] Description=Grow Root File System Documentation=man:systemd-growfs-root.service(8) + DefaultDependencies=no -Conflicts=shutdown.target After=systemd-repart.service systemd-remount-fs.service +Conflicts=shutdown.target Before=shutdown.target [Service] diff --git a/units/systemd-growfs@.service.in b/units/systemd-growfs@.service.in index 7154e4ca76..d7c90e96fc 100644 --- a/units/systemd-growfs@.service.in +++ b/units/systemd-growfs@.service.in @@ -10,10 +10,11 @@ [Unit] Description=Grow File System on %f Documentation=man:systemd-growfs@.service(8) + DefaultDependencies=no BindsTo=%i.mount -Conflicts=shutdown.target After=systemd-repart.service %i.mount +Conflicts=shutdown.target Before=shutdown.target [Service] diff --git a/units/systemd-hwdb-update.service.in b/units/systemd-hwdb-update.service.in index a09fe8aa52..4ba36d1fc6 100644 --- a/units/systemd-hwdb-update.service.in +++ b/units/systemd-hwdb-update.service.in @@ -10,15 +10,18 @@ [Unit] Description=Rebuild Hardware Database Documentation=man:hwdb(7) man:systemd-hwdb(8) -DefaultDependencies=no -Conflicts=shutdown.target -After=systemd-remount-fs.service -Before=sysinit.target shutdown.target systemd-update-done.service + ConditionNeedsUpdate=/etc ConditionPathExists=|!{{UDEVLIBEXECDIR}}/hwdb.bin ConditionPathExists=|/etc/udev/hwdb.bin ConditionDirectoryNotEmpty=|/etc/udev/hwdb.d/ +DefaultDependencies=no +After=systemd-remount-fs.service +Before=sysinit.target systemd-update-done.service +Conflicts=shutdown.target +Before=shutdown.target + [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-journal-catalog-update.service b/units/systemd-journal-catalog-update.service index 477925685a..691e03fe62 100644 --- a/units/systemd-journal-catalog-update.service +++ b/units/systemd-journal-catalog-update.service @@ -10,12 +10,15 @@ [Unit] Description=Rebuild Journal Catalog Documentation=man:systemd-journald.service(8) man:journald.conf(5) -DefaultDependencies=no -Conflicts=shutdown.target -After=local-fs.target systemd-tmpfiles-setup.service -Before=sysinit.target shutdown.target systemd-update-done.service + ConditionNeedsUpdate=/var +DefaultDependencies=no +After=local-fs.target systemd-tmpfiles-setup.service +Before=sysinit.target systemd-update-done.service +Conflicts=shutdown.target +Before=shutdown.target + [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-journal-flush.service b/units/systemd-journal-flush.service index 5d0b811ae3..8c01587cad 100644 --- a/units/systemd-journal-flush.service +++ b/units/systemd-journal-flush.service @@ -10,13 +10,16 @@ [Unit] Description=Flush Journal to Persistent Storage Documentation=man:systemd-journald.service(8) man:journald.conf(5) -DefaultDependencies=no -Wants=systemd-journald.service -After=systemd-journald.service systemd-remount-fs.service -Before=systemd-tmpfiles-setup.service -RequiresMountsFor=/var/log/journal + ConditionPathExists=!/etc/initrd-release +DefaultDependencies=no +After=systemd-remount-fs.service +Before=systemd-tmpfiles-setup.service +Wants=systemd-journald.service +After=systemd-journald.service +RequiresMountsFor=/var/log/journal + [Service] ExecStart=journalctl --flush ExecStop=journalctl --smart-relinquish-var diff --git a/units/systemd-quotacheck.service.in b/units/systemd-quotacheck.service.in index c3e936d220..05c14ff8b6 100644 --- a/units/systemd-quotacheck.service.in +++ b/units/systemd-quotacheck.service.in @@ -10,10 +10,13 @@ [Unit] Description=File System Quota Check Documentation=man:systemd-quotacheck.service(8) + +ConditionPathExists={{QUOTACHECK}} + DefaultDependencies=no After=systemd-remount-fs.service -Before=remote-fs.target shutdown.target -ConditionPathExists={{QUOTACHECK}} +Before=remote-fs.target +Before=shutdown.target [Service] Type=oneshot diff --git a/units/systemd-random-seed.service.in b/units/systemd-random-seed.service.in index d57b2d1269..820fdd8536 100644 --- a/units/systemd-random-seed.service.in +++ b/units/systemd-random-seed.service.in @@ -11,16 +11,17 @@ Description=Load/Save OS Random Seed Documentation=man:systemd-random-seed.service(8) man:random(4) -DefaultDependencies=no -RequiresMountsFor={{RANDOM_SEED}} -Conflicts=shutdown.target -After=systemd-remount-fs.service -Before=first-boot-complete.target shutdown.target -Wants=first-boot-complete.target - ConditionVirtualization=!container ConditionPathExists=!/etc/initrd-release +DefaultDependencies=no +After=systemd-remount-fs.service +Before=first-boot-complete.target +RequiresMountsFor={{RANDOM_SEED}} +Wants=first-boot-complete.target +Conflicts=shutdown.target +Before=shutdown.target + [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-remount-fs.service.in b/units/systemd-remount-fs.service.in index 2abed1d0a9..be1dfd6199 100644 --- a/units/systemd-remount-fs.service.in +++ b/units/systemd-remount-fs.service.in @@ -11,11 +11,13 @@ Description=Remount Root and Kernel File Systems Documentation=man:systemd-remount-fs.service(8) Documentation=https://www.freedesktop.org/wiki/Software/systemd/APIFileSystems + DefaultDependencies=no -Conflicts=shutdown.target After=systemd-fsck-root.service -Before=local-fs-pre.target local-fs.target shutdown.target +Before=local-fs-pre.target local-fs.target Wants=local-fs-pre.target +Conflicts=shutdown.target +Before=shutdown.target [Service] Type=oneshot diff --git a/units/systemd-repart.service.in b/units/systemd-repart.service.in index 105be680cd..5c04a21834 100644 --- a/units/systemd-repart.service.in +++ b/units/systemd-repart.service.in @@ -10,11 +10,7 @@ [Unit] Description=Repartition Root Disk Documentation=man:systemd-repart.service(8) -DefaultDependencies=no -Conflicts=shutdown.target -Wants=modprobe@loop.service modprobe@dm_mod.service -After=initrd-usr-fs.target modprobe@loop.service modprobe@dm_mod.service -Before=initrd-root-fs.target shutdown.target + ConditionVirtualization=!container ConditionDirectoryNotEmpty=|/usr/lib/repart.d ConditionDirectoryNotEmpty=|/usr/local/lib/repart.d @@ -26,6 +22,13 @@ ConditionDirectoryNotEmpty=|/sysroot/etc/repart.d ConditionDirectoryNotEmpty=|/sysusr/usr/lib/repart.d ConditionDirectoryNotEmpty=|/sysusr/usr/local/lib/repart.d +DefaultDependencies=no +Wants=modprobe@loop.service modprobe@dm_mod.service +After=initrd-usr-fs.target modprobe@loop.service modprobe@dm_mod.service +Before=initrd-root-fs.target +Conflicts=shutdown.target +Before=shutdown.target + [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-rfkill.socket b/units/systemd-rfkill.socket index 4ca8d6fc04..81c0e8c9c6 100644 --- a/units/systemd-rfkill.socket +++ b/units/systemd-rfkill.socket @@ -10,12 +10,13 @@ [Unit] Description=Load/Save RF Kill Switch Status /dev/rfkill Watch Documentation=man:systemd-rfkill.socket(8) + DefaultDependencies=no BindsTo=sys-devices-virtual-misc-rfkill.device After=sys-devices-virtual-misc-rfkill.device systemd-remount-fs.service -RequiresMountsFor=/var/lib/systemd/rfkill Conflicts=shutdown.target Before=shutdown.target +RequiresMountsFor=/var/lib/systemd/rfkill [Socket] ListenSpecial=/dev/rfkill diff --git a/units/systemd-sysusers.service b/units/systemd-sysusers.service index 0eb40294b2..2ec2766841 100644 --- a/units/systemd-sysusers.service +++ b/units/systemd-sysusers.service @@ -11,15 +11,15 @@ Description=Create System Users Documentation=man:sysusers.d(5) man:systemd-sysusers.service(8) +ConditionNeedsUpdate=|/etc +ConditionCredential=|sysusers.extra + DefaultDependencies=no After=systemd-remount-fs.service Before=sysinit.target systemd-update-done.service Conflicts=shutdown.target initrd-switch-root.target Before=shutdown.target initrd-switch-root.target -ConditionNeedsUpdate=|/etc -ConditionCredential=|sysusers.extra - [Service] Type=oneshot RemainAfterExit=yes diff --git a/units/systemd-update-utmp.service.in b/units/systemd-update-utmp.service.in index cedefa8e08..f1278fae32 100644 --- a/units/systemd-update-utmp.service.in +++ b/units/systemd-update-utmp.service.in @@ -10,11 +10,13 @@ [Unit] Description=Record System Boot/Shutdown in UTMP Documentation=man:systemd-update-utmp.service(8) man:utmp(5) + DefaultDependencies=no -RequiresMountsFor=/var/log/wtmp -Conflicts=shutdown.target After=systemd-remount-fs.service systemd-tmpfiles-setup.service auditd.service -Before=sysinit.target shutdown.target +Before=sysinit.target +Conflicts=shutdown.target +Before=shutdown.target +RequiresMountsFor=/var/log/wtmp [Service] Type=oneshot diff --git a/units/systemd-vconsole-setup.service.in b/units/systemd-vconsole-setup.service.in index 0009528307..41d738a63d 100644 --- a/units/systemd-vconsole-setup.service.in +++ b/units/systemd-vconsole-setup.service.in @@ -10,10 +10,12 @@ [Unit] Description=Setup Virtual Console Documentation=man:systemd-vconsole-setup.service(8) man:vconsole.conf(5) + +ConditionPathExists=/dev/tty0 + DefaultDependencies=no Before=sysinit.target Before=initrd-switch-root.target shutdown.target -ConditionPathExists=/dev/tty0 [Service] Type=oneshot From ce37fb0d92ca8af31215c81b573ebaac81ed6fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 28 Sep 2022 13:38:56 +0200 Subject: [PATCH 02/15] shared/generator: apply similar config reordering of generated units --- src/shared/generator.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/shared/generator.c b/src/shared/generator.c index b16d0a0ef2..49c5fe5c05 100644 --- a/src/shared/generator.c +++ b/src/shared/generator.c @@ -224,6 +224,7 @@ static int write_fsck_sysroot_service( "[Unit]\n" "Description=File System Check on %2$s\n" "Documentation=man:%3$s(8)\n" + "\n" "DefaultDependencies=no\n" "BindsTo=%4$s\n" "Conflicts=shutdown.target\n" @@ -517,11 +518,13 @@ int generator_hook_up_mkswap( "[Unit]\n" "Description=Make Swap on %%f\n" "Documentation=man:systemd-mkswap@.service(8)\n" + "\n" "DefaultDependencies=no\n" "BindsTo=%%i.device\n" - "Conflicts=shutdown.target\n" "After=%%i.device\n" - "Before=shutdown.target %s\n" + "Before=%s\n" + "Conflicts=shutdown.target\n" + "Before=shutdown.target\n" "\n" "[Service]\n" "Type=oneshot\n" @@ -602,13 +605,15 @@ int generator_hook_up_mkfs( "[Unit]\n" "Description=Make File System on %%f\n" "Documentation=man:systemd-makefs@.service(8)\n" + "\n" "DefaultDependencies=no\n" "BindsTo=%%i.device\n" - "Conflicts=shutdown.target\n" "After=%%i.device\n" /* fsck might or might not be used, so let's be safe and order * ourselves before both systemd-fsck@.service and the mount unit. */ - "Before=shutdown.target %s %s\n" + "Before=%s %s\n" + "Conflicts=shutdown.target\n" + "Before=shutdown.target\n" "\n" "[Service]\n" "Type=oneshot\n" @@ -748,11 +753,12 @@ int generator_write_cryptsetup_unit_section( fprintf(f, "SourcePath=%s\n", source); fprintf(f, + "\n" "DefaultDependencies=no\n" - "IgnoreOnIsolate=true\n" "After=cryptsetup-pre.target systemd-udevd-kernel.socket\n" "Before=blockdev@dev-mapper-%%i.target\n" - "Wants=blockdev@dev-mapper-%%i.target\n"); + "Wants=blockdev@dev-mapper-%%i.target\n" + "IgnoreOnIsolate=true\n"); return 0; } From d6f6846464d081f7a0c6ead2a6a2d8f2c2a01aa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 15 Sep 2022 16:12:05 +0200 Subject: [PATCH 03/15] units/systemd-repart: stop pretending that root config is executed in the initrd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I have a system with /usr/lib/repart.d/50-root.conf with GrowFileSystem=yes. The partition wouldn't be resized in the initrd, because ConditionDirectoryNotEmpty=|/sysusr/usr/lib/repart.d was evaluated very early, before /sysroot was mounted. There was no ordering dependency between systemd-repart.service and sysroot.mount. (There was After=initrd-usr-fs.target, but it seems to be only referred to by systemd-fstab-generator, which in my case doesn't even run, because there's no fstab.) But in fact, we neeed to run systemd-repart in the initrd only in limited circumstances: when we need to create the root device based on config under sysusr.mount. If there is config on the root device, it can be executed in the host system, early during boot. Thus, let's remove the condition on /sysroot/…. Without an ordering dependency on sysroot.mount, it was subject to a race condition anyway. (A race condition with a low probability of "winning", because systemd-repart.service has no dependencies, but sysroot.mount requires a device to be detected and the mount to happen.) The other problem was that systemd-repart.service didn't have the ordering wrt. initrd-switch-root.target, so it was subject to the same race condition that was fixed for other units in 7c0e2b555968d70ac563a37e32a6931ee90961a6. (If the systemd-repart.service/stop job is slow, we could end up not restarting systemd-repart.service in the host system.) With the changes here, I see systemd-repart.service/start running twice: in the initrd it is skipped because the conditions fail, and then in the host system it runs normally. Note: support for /sysroot is retained in systemd-repart code. I don't see a strong reason to remove it, since it may still be useful to people invoking repart in the initrd in other circumstances. --- units/systemd-repart.service.in | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/units/systemd-repart.service.in b/units/systemd-repart.service.in index 5c04a21834..35c4ca8ca5 100644 --- a/units/systemd-repart.service.in +++ b/units/systemd-repart.service.in @@ -16,9 +16,6 @@ ConditionDirectoryNotEmpty=|/usr/lib/repart.d ConditionDirectoryNotEmpty=|/usr/local/lib/repart.d ConditionDirectoryNotEmpty=|/etc/repart.d ConditionDirectoryNotEmpty=|/run/repart.d -ConditionDirectoryNotEmpty=|/sysroot/usr/lib/repart.d -ConditionDirectoryNotEmpty=|/sysroot/usr/local/lib/repart.d -ConditionDirectoryNotEmpty=|/sysroot/etc/repart.d ConditionDirectoryNotEmpty=|/sysusr/usr/lib/repart.d ConditionDirectoryNotEmpty=|/sysusr/usr/local/lib/repart.d @@ -26,8 +23,8 @@ DefaultDependencies=no Wants=modprobe@loop.service modprobe@dm_mod.service After=initrd-usr-fs.target modprobe@loop.service modprobe@dm_mod.service Before=initrd-root-fs.target -Conflicts=shutdown.target -Before=shutdown.target +Conflicts=shutdown.target initrd-switch-root.target +Before=shutdown.target initrd-switch-root.target [Service] Type=oneshot From b93562a1a13af0d7fa6c03e384a00eaab647791b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 30 Sep 2022 16:05:54 +0200 Subject: [PATCH 04/15] units: make sure proc-sys-binfmt_misc.automount is actually stopped As with other units, stopping of the automount requires actual work, and without the ordering dependency systemd might not execute the stop job before shutdown.target is reached and units ordered after that are executed. --- units/proc-sys-fs-binfmt_misc.automount | 1 + 1 file changed, 1 insertion(+) diff --git a/units/proc-sys-fs-binfmt_misc.automount b/units/proc-sys-fs-binfmt_misc.automount index 41396f9299..5d212015a5 100644 --- a/units/proc-sys-fs-binfmt_misc.automount +++ b/units/proc-sys-fs-binfmt_misc.automount @@ -18,6 +18,7 @@ ConditionPathIsReadWrite=/proc/sys/ DefaultDependencies=no Before=sysinit.target Conflicts=shutdown.target +Before=shutdown.target [Automount] Where=/proc/sys/fs/binfmt_misc From fd6ee7ed4214f2110697987d75ad59b015780470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 3 Oct 2022 11:04:24 +0200 Subject: [PATCH 05/15] firstboot: clarify that machine-id options are only offline, add missing docs Let's flat out refuse to configure machine-id on a running system with systemd-firstboot. It wouldn't work anyway, because by the time firstboot is started, pid1 has created /etc/machine-id, possibly with "unitialized", so firstboot wouldn't touch the file. (If --force is specified, it works. So let's allow that in case people want to do crazy things.) While at it, add missing descriptions of various things that were added over time, and group descriptions of similar options together. --- man/systemd-firstboot.xml | 63 +++++++++++++++++++++------------------ src/firstboot/firstboot.c | 31 +++++++++++-------- 2 files changed, 52 insertions(+), 42 deletions(-) diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 42666c96f8..9984683967 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -34,18 +34,18 @@ Description - systemd-firstboot initializes the most - basic system settings interactively on the first boot, or - optionally non-interactively when a system image is created. - The service is started if ConditionFirstBoot=yes - is satisfied. This essentially means that /etc/ + systemd-firstboot initializes basic system settings interactively during the + first boot, or non-interactively on an offline system image. The service is started during boot if + ConditionFirstBoot=yes is met, which essentially means that /etc/ is empty, see - systemd.unit5 - for details. + systemd.unit5 for + details. - The following settings may be set up: + The following settings may be configured: + The machine ID of the system + The system locale, more specifically the two locale variables LANG= and LC_MESSAGES @@ -56,9 +56,9 @@ The system hostname - The machine ID of the system + The kernel command line used when installing kernel images - The root user's password + The root user's password and shell Each of the fields may either be queried interactively by @@ -79,7 +79,7 @@ This allows systemd-firstboot to operate on mounted but not booted disk images and in early boot. It is not recommended to use systemd-firstboot on the - running system while it is up. + running system after it has been set up. @@ -150,13 +150,25 @@ configuration file. + + + + Initialize the system's machine ID to a random ID. This controls the + machine-id5 file. + + + This option only works in combination with or + . On a running system, machine-id is written by the + manager with help from + systemd-machine-id-commit.service8. + + + - Sets the system's machine ID. This controls - the - machine-id5 - file. + Set the system's machine ID to the specified value. The same restrictions apply + as to . @@ -230,8 +242,8 @@ Copy a specific basic setting from the host. - This only works in combination with - (see above). + This only works in combination with or . + @@ -247,21 +259,14 @@ - - - - Initialize the system's machine ID to a random - ID. This only works in combination with - . - - - systemd-firstboot doesn't modify existing files unless - is specified. For modifications to /etc/passwd and - /etc/shadow, systemd-firstboot only modifies the entry of the - root user instead of overwriting the entire file. + Write configuration even if the relevant files already exist. Without this option, + systemd-firstboot doesn't modify or replace existing files. Note that when + configuring the root account, even with this option, systemd-firstboot only + modifies the entry of the root user, leaving other entries in + /etc/passwd and /etc/shadow intact. diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 6d50054baf..19d5568854 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -1172,7 +1172,8 @@ static int help(void) { " --keymap=KEYMAP Set keymap\n" " --timezone=TIMEZONE Set timezone\n" " --hostname=NAME Set hostname\n" - " --machine-ID=ID Set machine ID\n" + " --setup-machine-id Set a random machine ID\n" + " --machine-ID=ID Set specified machine ID\n" " --root-password=PASSWORD Set root password from plaintext password\n" " --root-password-file=FILE Set root password from file\n" " --root-password-hashed=HASH Set root password from hashed password\n" @@ -1190,7 +1191,6 @@ static int help(void) { " --copy-root-password Copy root password from host\n" " --copy-root-shell Copy root shell from host\n" " --copy Copy locale, keymap, timezone, root password\n" - " --setup-machine-id Generate a new random machine ID\n" " --force Overwrite existing files\n" " --delete-root-password Delete root password\n" " --welcome=no Disable the welcome text\n" @@ -1214,6 +1214,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_KEYMAP, ARG_TIMEZONE, ARG_HOSTNAME, + ARG_SETUP_MACHINE_ID, ARG_MACHINE_ID, ARG_ROOT_PASSWORD, ARG_ROOT_PASSWORD_FILE, @@ -1233,7 +1234,6 @@ static int parse_argv(int argc, char *argv[]) { ARG_COPY_TIMEZONE, ARG_COPY_ROOT_PASSWORD, ARG_COPY_ROOT_SHELL, - ARG_SETUP_MACHINE_ID, ARG_FORCE, ARG_DELETE_ROOT_PASSWORD, ARG_WELCOME, @@ -1251,6 +1251,7 @@ static int parse_argv(int argc, char *argv[]) { { "keymap", required_argument, NULL, ARG_KEYMAP }, { "timezone", required_argument, NULL, ARG_TIMEZONE }, { "hostname", required_argument, NULL, ARG_HOSTNAME }, + { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, { "root-password", required_argument, NULL, ARG_ROOT_PASSWORD }, { "root-password-file", required_argument, NULL, ARG_ROOT_PASSWORD_FILE }, @@ -1270,7 +1271,6 @@ static int parse_argv(int argc, char *argv[]) { { "copy-timezone", no_argument, NULL, ARG_COPY_TIMEZONE }, { "copy-root-password", no_argument, NULL, ARG_COPY_ROOT_PASSWORD }, { "copy-root-shell", no_argument, NULL, ARG_COPY_ROOT_SHELL }, - { "setup-machine-id", no_argument, NULL, ARG_SETUP_MACHINE_ID }, { "force", no_argument, NULL, ARG_FORCE }, { "delete-root-password", no_argument, NULL, ARG_DELETE_ROOT_PASSWORD }, { "welcome", required_argument, NULL, ARG_WELCOME }, @@ -1392,6 +1392,13 @@ static int parse_argv(int argc, char *argv[]) { hostname_cleanup(arg_hostname); break; + case ARG_SETUP_MACHINE_ID: + r = sd_id128_randomize(&arg_machine_id); + if (r < 0) + return log_error_errno(r, "Failed to generate randomized machine ID: %m"); + + break; + case ARG_MACHINE_ID: r = sd_id128_from_string(optarg, &arg_machine_id); if (r < 0) @@ -1460,13 +1467,6 @@ static int parse_argv(int argc, char *argv[]) { arg_copy_root_shell = true; break; - case ARG_SETUP_MACHINE_ID: - r = sd_id128_randomize(&arg_machine_id); - if (r < 0) - return log_error_errno(r, "Failed to generate randomized machine ID: %m"); - - break; - case ARG_FORCE: arg_force = true; break; @@ -1496,10 +1496,15 @@ static int parse_argv(int argc, char *argv[]) { if (arg_delete_root_password && (arg_copy_root_password || arg_root_password || arg_prompt_root_password)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "--delete-root-password cannot be combined with other root password options"); + "--delete-root-password cannot be combined with other root password options."); if (arg_image && arg_root) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--root= and --image= cannot be used together."); + + if (!sd_id128_is_null(arg_machine_id) && !(arg_image || arg_root) && !arg_force) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "--machine-id=/--setup-machine-id only works with --root= or --image=."); return 1; } From 2223a0258032938f95c85bd9dde9ae5e1cb7396b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 9 Oct 2022 18:36:43 +0200 Subject: [PATCH 06/15] tmpfiles: align table --- src/tmpfiles/tmpfiles.c | 61 ++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index be04b25653..5232f17de7 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -86,35 +86,35 @@ typedef enum OperationMask { typedef enum ItemType { /* These ones take file names */ - CREATE_FILE = 'f', - TRUNCATE_FILE = 'F', /* deprecated: use f+ */ - CREATE_DIRECTORY = 'd', - TRUNCATE_DIRECTORY = 'D', - CREATE_SUBVOLUME = 'v', + CREATE_FILE = 'f', + TRUNCATE_FILE = 'F', /* deprecated: use f+ */ + CREATE_DIRECTORY = 'd', + TRUNCATE_DIRECTORY = 'D', + CREATE_SUBVOLUME = 'v', CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', - CREATE_SUBVOLUME_NEW_QUOTA = 'Q', - CREATE_FIFO = 'p', - CREATE_SYMLINK = 'L', - CREATE_CHAR_DEVICE = 'c', - CREATE_BLOCK_DEVICE = 'b', - COPY_FILES = 'C', + CREATE_SUBVOLUME_NEW_QUOTA = 'Q', + CREATE_FIFO = 'p', + CREATE_SYMLINK = 'L', + CREATE_CHAR_DEVICE = 'c', + CREATE_BLOCK_DEVICE = 'b', + COPY_FILES = 'C', /* These ones take globs */ - WRITE_FILE = 'w', - EMPTY_DIRECTORY = 'e', - SET_XATTR = 't', - RECURSIVE_SET_XATTR = 'T', - SET_ACL = 'a', - RECURSIVE_SET_ACL = 'A', - SET_ATTRIBUTE = 'h', - RECURSIVE_SET_ATTRIBUTE = 'H', - IGNORE_PATH = 'x', - IGNORE_DIRECTORY_PATH = 'X', - REMOVE_PATH = 'r', - RECURSIVE_REMOVE_PATH = 'R', - RELABEL_PATH = 'z', - RECURSIVE_RELABEL_PATH = 'Z', - ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ + WRITE_FILE = 'w', + EMPTY_DIRECTORY = 'e', + SET_XATTR = 't', + RECURSIVE_SET_XATTR = 'T', + SET_ACL = 'a', + RECURSIVE_SET_ACL = 'A', + SET_ATTRIBUTE = 'h', + RECURSIVE_SET_ATTRIBUTE = 'H', + IGNORE_PATH = 'x', + IGNORE_DIRECTORY_PATH = 'X', + REMOVE_PATH = 'r', + RECURSIVE_REMOVE_PATH = 'R', + RELABEL_PATH = 'z', + RECURSIVE_RELABEL_PATH = 'Z', + ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ } ItemType; typedef enum AgeBy { @@ -125,7 +125,7 @@ typedef enum AgeBy { /* All file timestamp types are checked by default. */ AGE_BY_DEFAULT_FILE = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_CTIME | AGE_BY_MTIME, - AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME + AGE_BY_DEFAULT_DIR = AGE_BY_ATIME | AGE_BY_BTIME | AGE_BY_MTIME, } AgeBy; typedef struct Item { @@ -3717,7 +3717,12 @@ static int parse_line( i.mode = m; i.mode_set = true; } else - i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; + i.mode = IN_SET(i.type, + CREATE_DIRECTORY, + TRUNCATE_DIRECTORY, + CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; if (!empty_or_dash(age)) { const char *a = age; From c42bac6a60ed40aeaa1e605d37990e20f52f6c26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 23 May 2023 12:07:13 +0200 Subject: [PATCH 07/15] basic/user-util: return -ESRCH if passwd/group are missing --- src/basic/user-util.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/basic/user-util.c b/src/basic/user-util.c index dd2642d214..3325aabe4d 100644 --- a/src/basic/user-util.c +++ b/src/basic/user-util.c @@ -287,7 +287,9 @@ int get_user_creds( p = getpwnam(*username); } if (!p) { - r = errno_or_else(ESRCH); + /* getpwnam() may fail with ENOENT if /etc/passwd is missing. + * For us that is equivalent to the name not being defined. */ + r = IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; /* If the user requested that we only synthesize as fallback, do so now */ if (FLAGS_SET(flags, USER_CREDS_PREFER_NSS)) { @@ -381,7 +383,9 @@ int get_group_creds(const char **groupname, gid_t *gid, UserCredsFlags flags) { } if (!g) - return errno_or_else(ESRCH); + /* getgrnam() may fail with ENOENT if /etc/group is missing. + * For us that is equivalent to the name not being defined. */ + return IN_SET(errno, 0, ENOENT) ? -ESRCH : -errno; if (gid) { if (!gid_is_valid(g->gr_gid)) From e0ea6af3b3a1651b4d8e4b26ecbc23d1c8b2c3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 9 Oct 2022 18:57:25 +0200 Subject: [PATCH 08/15] tmpfiles: add --graceful See the man page diff for description. --- man/systemd-tmpfiles.xml | 6 ++++++ src/tmpfiles/tmpfiles.c | 46 ++++++++++++++++++++++++++++++---------- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index 5612b4803d..a6246844cb 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -148,6 +148,12 @@ + + + Ignore configuration lines pertaining to unknown users or groups. This option is + intended to be used in early boot before all users or groups have been created. + + Only apply rules with paths that start with diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 5232f17de7..518d8012da 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -200,6 +200,7 @@ static bool arg_cat_config = false; static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; static OperationMask arg_operation = 0; static bool arg_boot = false; +static bool arg_graceful = false; static PagerFlags arg_pager_flags = 0; static char **arg_include_prefixes = NULL; @@ -1131,7 +1132,8 @@ static int parse_acls_from_arg(Item *item) { r = parse_acl(item->argument, &item->acl_access, &item->acl_access_exec, &item->acl_default, !item->append_or_force); if (r < 0) - log_warning_errno(r, "Failed to parse ACL \"%s\", ignoring: %m", item->argument); + log_full_errno(arg_graceful && IN_SET(r, -ENOENT, -ESRCH) ? LOG_DEBUG : LOG_WARNING, + r, "Failed to parse ACL \"%s\", ignoring: %m", item->argument); #else log_warning("ACLs are not supported, ignoring."); #endif @@ -3322,7 +3324,8 @@ static int parse_line( ItemArray *existing; OrderedHashmap *h; int r, pos; - bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, unbase64 = false, from_cred = false; + bool append_or_force = false, boot = false, allow_failure = false, try_replace = false, + unbase64 = false, from_cred = false, missing_user_or_group = false; assert(fname); assert(line >= 1); @@ -3669,12 +3672,15 @@ static int parse_line( u = user; r = find_uid(u, &i.uid, uid_cache); - if (r < 0) { + if (r == -ESRCH && arg_graceful) { + log_syntax(NULL, LOG_DEBUG, fname, line, r, + "%s: user '%s' not found, not adjusting ownership.", i.path, u); + missing_user_or_group = true; + } else if (r < 0) { *invalid_config = true; return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve user '%s': %m", u); - } - - i.uid_set = true; + } else + i.uid_set = true; } if (!empty_or_dash(group)) { @@ -3687,12 +3693,15 @@ static int parse_line( g = group; r = find_gid(g, &i.gid, gid_cache); - if (r < 0) { + if (r == -ESRCH && arg_graceful) { + log_syntax(NULL, LOG_DEBUG, fname, line, r, + "%s: group '%s' not found, not adjusting ownership.", i.path, g); + missing_user_or_group = true; + } else if (r < 0) { *invalid_config = true; - return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s'.", g); - } - - i.gid_set = true; + return log_syntax(NULL, LOG_ERR, fname, line, r, "Failed to resolve group '%s': %m", g); + } else + i.gid_set = true; } if (!empty_or_dash(mode)) { @@ -3724,6 +3733,14 @@ static int parse_line( CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; + if (missing_user_or_group && (i.mode & ~0777) != 0) { + /* Refuse any special bits for nodes where we couldn't resolve the ownership properly. */ + mode_t adjusted = i.mode & 0777; + log_syntax(NULL, LOG_INFO, fname, line, 0, + "Changing mode 0%o to 0%o because of changed ownership.", i.mode, adjusted); + i.mode = adjusted; + } + if (!empty_or_dash(age)) { const char *a = age; _cleanup_free_ char *seconds = NULL, *age_by = NULL; @@ -3844,6 +3861,7 @@ static int help(void) { " --clean Clean up marked directories\n" " --remove Remove marked files/directories\n" " --boot Execute actions only safe at boot\n" + " --graceful Quitely ignore unknown users or groups\n" " --prefix=PATH Only apply rules with the specified prefix\n" " --exclude-prefix=PATH Ignore rules with the specified prefix\n" " -E Ignore rules prefixed with /dev, /proc, /run, /sys\n" @@ -3871,6 +3889,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_CLEAN, ARG_REMOVE, ARG_BOOT, + ARG_GRACEFUL, ARG_PREFIX, ARG_EXCLUDE_PREFIX, ARG_ROOT, @@ -3889,6 +3908,7 @@ static int parse_argv(int argc, char *argv[]) { { "clean", no_argument, NULL, ARG_CLEAN }, { "remove", no_argument, NULL, ARG_REMOVE }, { "boot", no_argument, NULL, ARG_BOOT }, + { "graceful", no_argument, NULL, ARG_GRACEFUL }, { "prefix", required_argument, NULL, ARG_PREFIX }, { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, { "root", required_argument, NULL, ARG_ROOT }, @@ -3938,6 +3958,10 @@ static int parse_argv(int argc, char *argv[]) { arg_boot = true; break; + case ARG_GRACEFUL: + arg_graceful = true; + break; + case ARG_PREFIX: if (strv_push(&arg_include_prefixes, optarg) < 0) return log_oom(); From c264ec5f536c9629a24d770694ce50520ac8420b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 23 May 2023 12:28:55 +0200 Subject: [PATCH 09/15] repart: do not require /var/tmp if not used MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If systemd-repart is running sufficiently early, /var/tmp might not be in place yet. But if there is nothing to minimize, we won't even use it. Let's move the check right before the first use. systemd-repart[441]: Device '/' has no dm-crypt/dm-verity device, no need to look for… systemd-repart[441]: Device /dev/sda opened and locked. systemd-repart[441]: Sector size of device is 512 bytes. Using grain size of 4096. systemd-repart[441]: Could not determine temporary directory: No such file or directory systemd[1]: systemd-repart.service: Child 441 belongs to systemd-repart.service. systemd[1]: systemd-repart.service: Main process exited, code=exited, status=1/FAILURE systemd[1]: systemd-repart.service: Failed with result 'exit-code'. --- src/partition/repart.c | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/partition/repart.c b/src/partition/repart.c index d5d5e2353f..5a2f992e2d 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -5515,15 +5515,11 @@ static int fd_apparent_size(int fd, uint64_t *ret) { } static int context_minimize(Context *context) { - const char *vt; + const char *vt = NULL; int r; assert(context); - r = var_tmp_dir(&vt); - if (r < 0) - return log_error_errno(r, "Could not determine temporary directory: %m"); - LIST_FOREACH(partitions, p, context->partitions) { _cleanup_(rm_rf_physical_and_freep) char *root = NULL; _cleanup_(unlink_and_freep) char *temp = NULL; @@ -5556,6 +5552,12 @@ static int context_minimize(Context *context) { log_info("Pre-populating %s filesystem of partition %s twice to calculate minimal partition size", p->format, strna(hint)); + if (!vt) { + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Could not determine temporary directory: %m"); + } + r = tempfn_random_child(vt, "repart", &temp); if (r < 0) return log_error_errno(r, "Failed to generate temporary file path: %m"); @@ -5713,6 +5715,12 @@ static int context_minimize(Context *context) { log_info("Pre-populating verity hash data of partition %s to calculate minimal partition size", strna(hint)); + if (!vt) { + r = var_tmp_dir(&vt); + if (r < 0) + return log_error_errno(r, "Could not determine temporary directory: %m"); + } + r = tempfn_random_child(vt, "repart", &temp); if (r < 0) return log_error_errno(r, "Failed to generate temporary file path: %m"); From a3256ea8f4684ff92622ca9df1dc18dc517a4c6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 9 Oct 2022 18:58:01 +0200 Subject: [PATCH 10/15] man: extend description of --boot --- man/systemd-tmpfiles.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/man/systemd-tmpfiles.xml b/man/systemd-tmpfiles.xml index a6246844cb..735f59cc11 100644 --- a/man/systemd-tmpfiles.xml +++ b/man/systemd-tmpfiles.xml @@ -144,8 +144,10 @@ - Also execute lines with an exclamation mark. - + Also execute lines with an exclamation mark. Lines that are not safe to be executed + on a running system may be marked in this way. systemd-tmpfiles is executed in + early boot with specified and will execute those lines. When invoked again + later, it should be called without . From b42482af904ae0b94a6e4501ec595448f0ba1c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 9 Oct 2022 19:02:27 +0200 Subject: [PATCH 11/15] units: create /dev with --graceful first, allow sysusers to run later We want to call systemd-tmpfiles-setup-dev.service to create /dev/fuse and other device nodes so that module probing will work. But it is possible that when we're in first boot, some users or groups need to be created by systemd-sysusers first. But it is also possible that systemd-sysusers cannot actually execute configuration because the root partition is not fully writable yet. So let systemd-tmpfiles-setup-dev.service run earlier, possibly without all users and groups in place. Since systemd-tmpfiles-setup-dev.service writes to /dev only, it doesn't care how the root partition is mounted. In this early run, some some nodes might be created with default permissions (i.e. not accessible to non-root users or groups). This should be OK for the early boot phase. Afterwards, we let systemd-tmpfiles-setup.service execute full configuration. We will configure any files in /dev twice, but considering that there's only a few of them and that the second run should only adjust ownership and permissions, this should be OK. This way, we avoid the dependency loop. --- units/systemd-tmpfiles-setup-dev.service | 3 +-- units/systemd-tmpfiles-setup.service | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/units/systemd-tmpfiles-setup-dev.service b/units/systemd-tmpfiles-setup-dev.service index c65539aa79..d9cb3de554 100644 --- a/units/systemd-tmpfiles-setup-dev.service +++ b/units/systemd-tmpfiles-setup-dev.service @@ -12,7 +12,6 @@ Description=Create Static Device Nodes in /dev Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8) DefaultDependencies=no -After=systemd-sysusers.service Before=sysinit.target local-fs-pre.target systemd-udevd.service Conflicts=shutdown.target initrd-switch-root.target Before=shutdown.target initrd-switch-root.target @@ -20,6 +19,6 @@ Before=shutdown.target initrd-switch-root.target [Service] Type=oneshot RemainAfterExit=yes -ExecStart=systemd-tmpfiles --prefix=/dev --create --boot +ExecStart=systemd-tmpfiles --prefix=/dev --create --boot --graceful SuccessExitStatus=DATAERR CANTCREAT LoadCredential=tmpfiles.extra diff --git a/units/systemd-tmpfiles-setup.service b/units/systemd-tmpfiles-setup.service index a420465534..506f53eaa2 100644 --- a/units/systemd-tmpfiles-setup.service +++ b/units/systemd-tmpfiles-setup.service @@ -21,7 +21,7 @@ RefuseManualStop=yes [Service] Type=oneshot RemainAfterExit=yes -ExecStart=systemd-tmpfiles --create --remove --boot --exclude-prefix=/dev +ExecStart=systemd-tmpfiles --create --remove --boot SuccessExitStatus=DATAERR CANTCREAT LoadCredential=tmpfiles.extra LoadCredential=login.motd From 9740e677be4d74508ff2a788011856b24a1b88c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 13 Oct 2022 13:39:17 +0200 Subject: [PATCH 12/15] sysusers: add usual "ret_" prefix, fix messages We had 'make backup x'. 'make backup of x' would be correct, but 'backup x' is shorter and to the point. --- src/sysusers/sysusers.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index aa1f1356dc..c16cff35a2 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -415,7 +415,7 @@ static const char* pick_shell(const Item *i) { return NOLOGIN; } -static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char **tmpfile_path) { +static int write_temporary_passwd(const char *passwd_path, FILE **ret_tmpfile, char **ret_tmpfile_path) { _cleanup_fclose_ FILE *original = NULL, *passwd = NULL; _cleanup_(unlink_and_freep) char *passwd_tmp = NULL; struct passwd *pw = NULL; @@ -532,8 +532,8 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char if (r < 0) return log_debug_errno(r, "Failed to flush %s: %m", passwd_tmp); - *tmpfile = TAKE_PTR(passwd); - *tmpfile_path = TAKE_PTR(passwd_tmp); + *ret_tmpfile = TAKE_PTR(passwd); + *ret_tmpfile_path = TAKE_PTR(passwd_tmp); return 0; } @@ -550,7 +550,7 @@ static usec_t epoch_or_now(void) { return now(CLOCK_REALTIME); } -static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char **tmpfile_path) { +static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, char **ret_tmpfile_path) { _cleanup_fclose_ FILE *original = NULL, *shadow = NULL; _cleanup_(unlink_and_freep) char *shadow_tmp = NULL; struct spwd *sp = NULL; @@ -668,13 +668,13 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char if (r < 0) return log_debug_errno(r, "Failed to flush %s: %m", shadow_tmp); - *tmpfile = TAKE_PTR(shadow); - *tmpfile_path = TAKE_PTR(shadow_tmp); + *ret_tmpfile = TAKE_PTR(shadow); + *ret_tmpfile_path = TAKE_PTR(shadow_tmp); return 0; } -static int write_temporary_group(const char *group_path, FILE **tmpfile, char **tmpfile_path) { +static int write_temporary_group(const char *group_path, FILE **ret_tmpfile, char **ret_tmpfile_path) { _cleanup_fclose_ FILE *original = NULL, *group = NULL; _cleanup_(unlink_and_freep) char *group_tmp = NULL; bool group_changed = false; @@ -774,13 +774,13 @@ static int write_temporary_group(const char *group_path, FILE **tmpfile, char ** return log_error_errno(r, "Failed to flush %s: %m", group_tmp); if (group_changed) { - *tmpfile = TAKE_PTR(group); - *tmpfile_path = TAKE_PTR(group_tmp); + *ret_tmpfile = TAKE_PTR(group); + *ret_tmpfile_path = TAKE_PTR(group_tmp); } return 0; } -static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, char **tmpfile_path) { +static int write_temporary_gshadow(const char * gshadow_path, FILE **ret_tmpfile, char **ret_tmpfile_path) { #if ENABLE_GSHADOW _cleanup_fclose_ FILE *original = NULL, *gshadow = NULL; _cleanup_(unlink_and_freep) char *gshadow_tmp = NULL; @@ -853,8 +853,8 @@ static int write_temporary_gshadow(const char * gshadow_path, FILE **tmpfile, ch return log_error_errno(r, "Failed to flush %s: %m", gshadow_tmp); if (group_changed) { - *tmpfile = TAKE_PTR(gshadow); - *tmpfile_path = TAKE_PTR(gshadow_tmp); + *ret_tmpfile = TAKE_PTR(gshadow); + *ret_tmpfile_path = TAKE_PTR(gshadow_tmp); } #endif return 0; @@ -891,23 +891,23 @@ static int write_files(void) { if (group) { r = make_backup("/etc/group", group_path); if (r < 0) - return log_error_errno(r, "Failed to make backup %s: %m", group_path); + return log_error_errno(r, "Failed to backup %s: %m", group_path); } if (gshadow) { r = make_backup("/etc/gshadow", gshadow_path); if (r < 0) - return log_error_errno(r, "Failed to make backup %s: %m", gshadow_path); + return log_error_errno(r, "Failed to backup %s: %m", gshadow_path); } if (passwd) { r = make_backup("/etc/passwd", passwd_path); if (r < 0) - return log_error_errno(r, "Failed to make backup %s: %m", passwd_path); + return log_error_errno(r, "Failed to backup %s: %m", passwd_path); } if (shadow) { r = make_backup("/etc/shadow", shadow_path); if (r < 0) - return log_error_errno(r, "Failed to make backup %s: %m", shadow_path); + return log_error_errno(r, "Failed to backup %s: %m", shadow_path); } /* And make the new files count */ From 7e4c61491a720d7a28186cf9e7e144049885a926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 13 Oct 2022 14:51:28 +0200 Subject: [PATCH 13/15] sysusers: fix argument confusion in error message Bug introduced in 335f6ab4f13abcd8073fe84f2a3c70c67271126e. pw/sp are totally wrong in this context, most likely NULL. --- src/sysusers/sysusers.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index c16cff35a2..5aaaf609b2 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -511,7 +511,7 @@ static int write_temporary_passwd(const char *passwd_path, FILE **ret_tmpfile, c r = putpwent_sane(&n, passwd); if (r < 0) return log_debug_errno(r, "Failed to add new user \"%s\" to temporary passwd file: %m", - pw->pw_name); + i->name); } /* Append the remaining NIS entries if any */ @@ -645,7 +645,7 @@ static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, c r = putspent_sane(&n, shadow); if (r < 0) return log_debug_errno(r, "Failed to add new user \"%s\" to temporary shadow file: %m", - sp->sp_namp); + i->name); } /* Append the remaining NIS entries if any */ From bd3beda2834a1400faa1ad83e0eb27ce2a1d0cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 13 Oct 2022 14:05:01 +0200 Subject: [PATCH 14/15] shared/condition: add envvar override for the check for first-boot Before 7cd43e34c5a302ff323c013f437092d2ff5ccbbf, it was possible to use SYSTEMD_PROC_CMDLINE=systemd.condition-first-boot to override autodetection. But now this doesn't work anymore, and it's useful to be able to do that for testing. --- docs/ENVIRONMENT.md | 3 +++ src/shared/condition.c | 33 +++++++++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 2ba222d3a6..f1b9fd7715 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -35,6 +35,9 @@ All tools: as `start` into no-ops. If that's what's explicitly desired, you might consider setting `$SYSTEMD_OFFLINE=1`. +* `$SYSTEMD_FIRST_BOOT=0|1` — if set, assume "first boot" condition to be false + or true, instead of checking the flag file created by PID 1. + * `$SD_EVENT_PROFILE_DELAYS=1` — if set, the sd-event event loop implementation will print latency information at runtime. diff --git a/src/shared/condition.c b/src/shared/condition.c index e5a80757e0..fce3fc2afb 100644 --- a/src/shared/condition.c +++ b/src/shared/condition.c @@ -821,22 +821,43 @@ static int condition_test_needs_update(Condition *c, char **env) { return timespec_load_nsec(&usr.st_mtim) > timestamp; } +static bool in_first_boot(void) { + static int first_boot = -1; + int r; + + if (first_boot >= 0) + return first_boot; + + const char *e = secure_getenv("SYSTEMD_FIRST_BOOT"); + if (e) { + r = parse_boolean(e); + if (r < 0) + log_debug_errno(r, "Failed to parse $SYSTEMD_FIRST_BOOT, ignoring: %m"); + else + return (first_boot = r); + } + + r = RET_NERRNO(access("/run/systemd/first-boot", F_OK)); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to check if /run/systemd/first-boot exists, assuming no: %m"); + return r >= 0; +} + static int condition_test_first_boot(Condition *c, char **env) { - int r, q; + int r; assert(c); assert(c->parameter); assert(c->type == CONDITION_FIRST_BOOT); + // TODO: Parse c->parameter immediately when reading the config. + // Apply negation when parsing too. + r = parse_boolean(c->parameter); if (r < 0) return r; - q = access("/run/systemd/first-boot", F_OK); - if (q < 0 && errno != ENOENT) - log_debug_errno(errno, "Failed to check if /run/systemd/first-boot exists, assuming no: %m"); - - return (q >= 0) == r; + return in_first_boot() == r; } static int condition_test_environment(Condition *c, char **env) { From a777a59243ab3a8496642e330c377fff0769fdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Mon, 3 Oct 2022 12:12:15 +0200 Subject: [PATCH 15/15] firstboot: process the root account after sysusers created it We would create root account from sysusers or from firstboot, depending on which one ran earlier. Since firstboot offers more options, in particular can set the root password, we needed to order it earlier. This created an ugly ordering requirement: systemd-sysusers.service > systemd-firstboot.service > ... > systemd-remount-fs.service > systemd-tmpfiles-setup-dev.service > systemd-sysusers.service We want sysusers.service to create basic users, so we can create nodes in dev, so we can operate on block devices and such, so that we can resize and remount things. But at the same time, systemd-firstboot.service can only work if it is run early, before systemd-sysusers.service has created /etc/passwd. We can't have it both ways: the units that want to have a fully writable root file system cannot be ordered before units which are required to do file system preparation. Instead of trying to order firstboot very early, let's let it do its thing even if it is started later. Instead of refusing to create to the root account if /etc/passwd and /etc/shadow exist, actually check if the account is configured. Now sysusers writes root account with password PASSWORD_UNPROVISIONED ("!unprovisioned"), and then firstboot checks for this, and will configure root in this case. This allows sysusers to be executed earlier (or accounts to be set up earlier in another way). This effectively reverts b825ab1a99b69956057c79838faaf7b44afee474. --- src/basic/user-util.h | 5 +++ src/firstboot/firstboot.c | 63 ++++++++++++++++++++++++++++++--- src/sysusers/sysusers.c | 6 +++- units/systemd-firstboot.service | 2 +- 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/basic/user-util.h b/src/basic/user-util.h index 5aca6307e0..c82941dd81 100644 --- a/src/basic/user-util.h +++ b/src/basic/user-util.h @@ -150,3 +150,8 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) { /* A password indicating "hey, no password required for login" */ #define PASSWORD_NONE "" + +/* Used by sysusers to indicate that the password should be filled in by firstboot. + * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325. + */ +#define PASSWORD_UNPROVISIONED "!unprovisioned" diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 19d5568854..6b42e64043 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -234,17 +234,72 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i } static int should_configure(int dir_fd, const char *filename) { + _cleanup_fclose_ FILE *passwd = NULL, *shadow = NULL; + int r; + assert(dir_fd >= 0); assert(filename); - if (faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno != ENOENT) - return log_error_errno(errno, "Failed to access %s: %m", filename); + if (streq(filename, "passwd") && !arg_force) + /* We may need to do additional checks, so open the file. */ + r = xfopenat(dir_fd, filename, "re", O_NOFOLLOW, &passwd); + else + r = RET_NERRNO(faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW)); + if (r == -ENOENT) return true; /* missing */ + if (r < 0) + return log_error_errno(r, "Failed to access %s: %m", filename); + if (arg_force) + return true; /* exists, but if --force was given we should still configure the file. */ + + if (!passwd) + return false; + + /* In case of /etc/passwd, do an additional check for the root password field. + * We first check that passwd redirects to shadow, and then we check shadow. + */ + struct passwd *i; + while ((r = fgetpwent_sane(passwd, &i)) > 0) { + if (!streq(i->pw_name, "root")) + continue; + + if (streq_ptr(i->pw_passwd, PASSWORD_SEE_SHADOW)) + break; + log_debug("passwd: root account with non-shadow password found, treating root as configured"); + return false; + } + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", filename); + if (r == 0) { + log_debug("No root account found in %s, assuming root is not configured.", filename); + return true; } - return arg_force; /* exists, but if --force was given we should still configure the file. */ + r = xfopenat(dir_fd, "shadow", "re", O_NOFOLLOW, &shadow); + if (r == -ENOENT) { + log_debug("No shadow file found, assuming root is not configured."); + return true; /* missing */ + } + if (r < 0) + return log_error_errno(r, "Failed to access shadow: %m"); + + struct spwd *j; + while ((r = fgetspent_sane(shadow, &j)) > 0) { + if (!streq(j->sp_namp, "root")) + continue; + + bool unprovisioned = streq_ptr(j->sp_pwdp, PASSWORD_UNPROVISIONED); + log_debug("Root account found, %s.", + unprovisioned ? "with unprovisioned password, treating root as not configured" : + "treating root as configured"); + return unprovisioned; + } + if (r < 0) + return log_error_errno(r, "Failed to read shadow: %m"); + assert(r == 0); + log_debug("No root account found in shadow, assuming root is not configured."); + return true; } static bool locale_is_installed_bool(const char *name) { diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 5aaaf609b2..12adad516e 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -618,7 +618,6 @@ static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, c struct spwd n = { .sp_namp = i->name, - .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID, .sp_lstchg = lstchg, .sp_min = -1, .sp_max = -1, @@ -641,6 +640,11 @@ static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, c if (creds_password) n.sp_pwdp = creds_password; + else if (streq(i->name, "root")) + /* Let firstboot set the password later */ + n.sp_pwdp = (char*) PASSWORD_UNPROVISIONED; + else + n.sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID; r = putspent_sane(&n, shadow); if (r < 0) diff --git a/units/systemd-firstboot.service b/units/systemd-firstboot.service index 58f3eeb679..5fee85a287 100644 --- a/units/systemd-firstboot.service +++ b/units/systemd-firstboot.service @@ -16,7 +16,7 @@ ConditionFirstBoot=yes DefaultDependencies=no After=systemd-remount-fs.service -Before=systemd-sysusers.service systemd-vconsole-setup.service sysinit.target first-boot-complete.target +Before=systemd-vconsole-setup.service sysinit.target first-boot-complete.target Wants=first-boot-complete.target Conflicts=shutdown.target Before=shutdown.target