diff --git a/NEWS b/NEWS
index 1d5578e86b..804c33e339 100644
--- a/NEWS
+++ b/NEWS
@@ -76,6 +76,12 @@ CHANGES WITH 258 in spe:
*now* to include a native systemd unit file instead of a legacy
System V script to retain compatibility with future systemd releases.
+ * Support for systemd-repart's FactoryReset EFI variable has been
+ deprecated and support for it will be removed in v260. Use the newer,
+ more generic FactoryResetRequest variable instead, which can be
+ managed by "systemd-factory-reset request" and "systemd-factory-reset
+ complete".
+
* To work around limitations of X11's keyboard handling systemd's
keyboard mapping hardware database (hwdb.d/60-keyboard.hwdb) so far
mapped the microphone mute and touchpad on/off/toggle keys to the
diff --git a/TODO b/TODO
index a8a4a3cbc6..132dd47bef 100644
--- a/TODO
+++ b/TODO
@@ -125,6 +125,9 @@ Deprecations and removals:
* Once baseline is 4.13, remove support for INTERFACE_OLD= checks in "udevadm
trigger"'s waiting logic, since we can then rely on uuid-tagged uevents
+* In v260: remove support for deprecated FactoryReset EFI variable in
+ systemd-repart, replaced by FactoryResetRequest.
+
Features:
* Maybe rename pkcs7 and public verbs of systemd-keyutil to be more verb like.
@@ -830,10 +833,6 @@ Features:
AllowPeerGroup= that installs additional user/group ACL entries on AF_UNIX
sockets.
-* systemd-tpm2-setup should probably have a factory reset logic, i.e. when some
- kernel command line option is set we reset the TPM (equivalent of tpm2_clear
- -c owner? or rather echo 5 >/sys/class/tpm/tpm0/ppi/request?).
-
* systemd-tpm2-setup should support a mode where we refuse booting if the SRK
changed. (Must be opt-in, to not break systems which are supposed to be
migratable between PCs)
diff --git a/catalog/systemd.catalog.in b/catalog/systemd.catalog.in
index 801bda0939..610b6f5fab 100644
--- a/catalog/systemd.catalog.in
+++ b/catalog/systemd.catalog.in
@@ -819,3 +819,18 @@ systemd-networkd, has been changed by another, unrelated process
and will likely result in problems later on.
Value changed to "@NEWVALUE@", which should be "@OURVALUE@".
+
+-- 438188861e0b427a9d638a90487a0ca6
+Subject: TPM clear requested
+Defined-By: systemd
+Support: %SUPPORT_URL%
+Documentation: man:systemd-tpm2-clear.service(8)
+
+A request to clear the TPM security chip has been issued to the firmware. This
+is typically done as part of a comprehensive factory reset operation, and
+ensures that on the next boot process the firmware will reset the TPM, after
+interactively requesting confirmation by the user.
+
+Clearing the TPM has the effect of invalidating all keys locked to the TPM,
+including full disk encryption keys. Because of that care should be taken that
+access to relevant resources is retained via other means.
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 0976623be9..4b2289e646 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -767,3 +767,10 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as
`process`, `session`, `user`, `user-session`, or `group`. Controls the kernel
keyring in which `systemd-ask-password` caches the queried password. Defaults
to `user`.
+
+`systemd-tpm2-clear`:
+
+* `SYSTEMD_TPM2_ALLOW_CLEAR` – takes a boolean. Overrides the effect of the
+ `systemd.factory_reset=` kernel command line option: if set to false,
+ requesting a TPM clearing is skipped, and the command immediately exits
+ successfully.
diff --git a/docs/FACTORY_RESET.md b/docs/FACTORY_RESET.md
new file mode 100644
index 0000000000..bb0e3063b1
--- /dev/null
+++ b/docs/FACTORY_RESET.md
@@ -0,0 +1,137 @@
+---
+title: Factory Reset
+category: Booting
+layout: default
+SPDX-License-Identifier: LGPL-2.1-or-later
+---
+
+# Factory Reset
+
+In various scenarios it is important to be able to reset operating systems back
+into a "factory state", i.e. where all state, user data and configuration is
+reset so that it resembles the system state when it was originally shipped.
+
+systemd natively supports a concept of factory reset, that can both act as a
+specific implementation for UEFI based systems, as well as a series of hook
+points and a template for implementations on other systems.
+
+Factory reset always takes place during early boot, i.e. from a well-defined
+"clean" state. Factory reset operations may be requested from one boot to be
+executed on the next.
+
+Specifically, the following concepts are available:
+
+* The `factory-reset.target` unit may be used to request a factory reset
+ operation and trigger a reboot in order to execute it. It by default executes
+ three services: `systemd-factory-reset-request.service`,
+ `systemd-tpm2-clear.service` and `systemd-factory-reset-reboot.service`.
+
+* The
+ [`systemd-factory-reset-request.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-request.service.html)
+ unit is typically invoked via `factory-reset.target`. It requests a factory
+ reset operation for the next boot by setting the `FactoryResetRequest` EFI
+ variable. The EFI variable contains information about the requesting OS, so
+ that multi-boot scenarios are somewhat covered.
+
+* The
+ [`systemd-tpm2-clear.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-tpm2-clear.service.html)
+ unit can request a TPM2 clear operation from the firmware on the next
+ boot. It is also invoked via `factory-reset.target`. UEFI firmwares that
+ support TPMs will ask the user for confirmation and then reset the TPM,
+ invalidating all prior keys associated with the security chip and generating
+ a new seed key.
+
+* The
+ [`systemd-factory-reset-reboot.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-reboot.service.html)
+ unit automatically reboots the system as part of `factory-reset.target`. It
+ is ordered after `systemd-tpm2-clear.service` and
+ `systemd-factory-reset-request.service` in order to initiate the reboot that
+ is supposed to execute the factory reset operations.
+
+* The `factory-reset-now.target` unit is started at boot whenever a factory
+ reset is requested for the boot. A factory reset may be requested via a
+ kernel command line option (`systemd.factory_reset=1`) or via the UEFI
+ variable `FactoryResetRequest` (see above). The
+ `systemd-factory-reset-generator` unit generator checks both these conditions
+ and adds `factory-reset-now.target` to the boot transaction, already in the
+ initial RAM disk (initrd).
+
+* The
+ [`systemd-factory-reset-complete.service`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset-complete.service.html)
+ unit is invoked after `factory-reset-now.target` and marks the factory reset
+ operation as complete. The boot process then may continue.
+
+* The
+ [`systemd-repart`](https://www.freedesktop.org/software/systemd/man/latest/systemd-repart.html)
+ tool can take the factory reset logic into account. Either on explicit
+ request via the `--factory-reset=` logic, or automatically derived from the
+ aforementioned kernel command line switch and EFI variable. When invoked for
+ factory reset it will securely erase all partitions marked for that via the
+ `FactoryReset=` setting in its partition definition files. Once that is
+ complete it will execute the usual setup operation, i.e. format new
+ partitions again.
+
+* The
+ [`systemd-logind.service(8)`](https://www.freedesktop.org/software/systemd/man/latest/systemd-logind.service.html)
+ unit supports automatically binding factory reset to special keypresses
+ (typically long presses), see the
+ [`logind.conf(5)`](https://www.freedesktop.org/software/systemd/man/latest/logind.conf.html)
+ man page.
+
+* The
+ [`systemd-factory-reset`](https://www.freedesktop.org/software/systemd/man/latest/systemd-factory-reset.html)
+ tool can be used to query the current state of the factory request mechanism,
+ i.e. whether a factory reset is currently being executed, or if one has been
+ requested for the next boot.
+
+* The `/run/systemd/io.systemd.FactoryReset` Varlink service provides two IPC
+ APIs for working with factory reset: it permits querying whether the local
+ system supports requesting a factory reset by starting
+ `factory-reset.target`. This may be used by UIs to hide or show in the UI an
+ interface to request a factory reset. The Varlink IPC service also reports
+ the current factory reset state, much like the `systemd-factory-reset` tool
+ mentioned above. This may be used by various early boot services that
+ potentially intent to reset system state during a factory reset operation.
+
+## Exposure in the UI
+
+If a graphical UI shall expose a factory reset operation it should first check
+if requesting a factory reset is supported at all via the Varlink service
+mentioned above. Once a factory reset shall be executed it shall ask for
+activation of the `factory-reset.target` unit.
+
+Alternatively, `systemd-logind.service`'s hotkey support may be used, for
+example to request factory reset if the reboot button is pressed for a long
+time.
+
+## Support for non-UEFI Systems
+
+The above is a relatively bespoke solution for EFI systems. It uses EFI
+variables as stateful memory to request the factory reset on the next boot.
+
+On non-EFI systems, a different mechanism should be devised. A service
+requesting the factory request can then be plugged into
+`factory-reset.target`. At boot the request should then be fed back to the
+booted kernel via the `systemd.factory_reset=1` kernel command line option, in
+order to execute the reset operation.
+
+## Support for Resetting other Resources than Partitions + TPM
+
+By default a factory reset implemented with systemd's tools can reset/erase
+partitions (via `systemd-repart`, see above) and reset the TPM (via
+`systemd-tpm2-clear.service`, see above).
+
+In some cases other resources shall be reset/erased too. To support that,
+define your own service and plug it into `factory-reset-now.target`, ensuring
+it is ordered before that.
+
+## Factory Reset via Boot Menu
+
+Factory reset can also be requested via the boot menu. A simple factory reset
+(that does not touch the TPM) at boot can be requested via a boot menu item
+containing the `systemd.factory_reset=1` kernel command line option. A more
+comprehensive factory reset operation (that also erases the TPM) can be
+requested by booting with `rd.systemd.unit=factory-reset.target`. Note that the
+latter will require one reboot (required since that's how TPM resets work),
+while the former will reset state and continue running without an additional
+reboot.
diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml
index 590bd425d8..3a4619edee 100644
--- a/man/kernel-command-line.xml
+++ b/man/kernel-command-line.xml
@@ -752,6 +752,15 @@
+
+ systemd.tpm2_allow_clear=
+
+ Controls whether to allow clearing of the TPM chip, implemented by
+ systemd-tpm2-clear8.
+
+
+
+
systemd.tpm2_wait=
@@ -761,6 +770,17 @@
+
+ systemd.factory_reset=
+
+ Controls whether to to boot into factory reset mode, implemented by
+ systemd-factory-reset-generator8,
+ systemd-repart8,
+ and other tools.
+
+
+
+
diff --git a/man/rules/meson.build b/man/rules/meson.build
index 19beebfc43..2b06442d62 100644
--- a/man/rules/meson.build
+++ b/man/rules/meson.build
@@ -942,6 +942,14 @@ manpages = [
['30-systemd-environment-d-generator'],
'ENABLE_ENVIRONMENT_D'],
['systemd-escape', '1', [], ''],
+ ['systemd-factory-reset-generator', '8', [], ''],
+ ['systemd-factory-reset',
+ '8',
+ ['systemd-factory-reset-complete.service',
+ 'systemd-factory-reset-request.service',
+ 'systemd-factory-reset.socket',
+ 'systemd-factory-reset@.service'],
+ ''],
['systemd-firstboot', '1', ['systemd-firstboot.service'], 'ENABLE_FIRSTBOOT'],
['systemd-fsck@.service',
'8',
@@ -1134,6 +1142,7 @@ manpages = [
'systemd-tmpfiles-setup-dev.service',
'systemd-tmpfiles-setup.service'],
''],
+ ['systemd-tpm2-clear.service', '8', [], 'ENABLE_BOOTLOADER'],
['systemd-tpm2-generator', '8', [], ''],
['systemd-tpm2-setup.service',
'8',
diff --git a/man/systemd-factory-reset-generator.xml b/man/systemd-factory-reset-generator.xml
new file mode 100644
index 0000000000..314a66c7d2
--- /dev/null
+++ b/man/systemd-factory-reset-generator.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+ systemd-factory-reset-generator
+ systemd
+
+
+
+ systemd-factory-reset-generator
+ 8
+
+
+
+ systemd-factory-reset-generator
+ Pull factory-reset-now.target into the initial boot transaction when factory reset has been requested
+
+
+
+ /usr/lib/systemd/system-generators/systemd-factory-reset-generator
+
+
+
+ Description
+
+ systemd-factory-reset-generator is a generator that pulls
+ factory-reset-now.target into the initial boot transaction when the factory reset
+ operation has been requested, either via the systemd.factory_reset= kernel command
+ line option or via the FactoryResetRequest EFI variable.
+
+ systemd-factory-reset-generator implements
+ systemd.generator7.
+
+
+
+ See Also
+
+ systemd1
+ systemd-factory-reset8
+ Factory Reset
+
+
+
+
diff --git a/man/systemd-factory-reset.xml b/man/systemd-factory-reset.xml
new file mode 100644
index 0000000000..1cd55c9acc
--- /dev/null
+++ b/man/systemd-factory-reset.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+ systemd-factory-reset
+ systemd
+
+
+
+ systemd-factory-reset
+ 8
+
+
+
+ systemd-factory-reset
+ systemd-factory-reset-request.service
+ systemd-factory-reset-complete.service
+ systemd-factory-reset.socket
+ systemd-factory-reset@.service
+ Request or complete a factory reset operation, or query current factory reset mode
+
+
+
+ /usr/lib/systemd/systemd-factory-reset
+ systemd-factory-reset-request.service
+ systemd-factory-reset-complete.service
+ systemd-factory-reset.socket
+ systemd-factory-reset@.service
+
+
+
+ Description
+
+ systemd-factory-reset is a tool that can query the current factory reset
+ state, request factory request operations or complete them.
+
+ Some of the functionality is also available via the
+ /run/systemd/io.systemd.FactoryReset Varlink service (implemented via the
+ systemd-factory-reset.socket/systemd-factory-reset@.service
+ units.
+
+ See Factory Reset for an overview of the
+ factory reset logic.
+
+
+
+ Commands
+
+ The /usr/lib/systemd/systemd-factory-reset executable may also be invoked from the
+ command line, taking one of the following command arguments:
+
+
+
+
+
+ Report current factory reset state. Reports one of unsupported (if
+ the OS does not support a factory reset logic), unspecified (if no factory reset
+ was requested, but it wasn't turned off explicitly either), off (if the factory
+ reset logic was explicitly turned off via the kernel command line option), on (if
+ the factory reset is currently enabled and executed), complete (if the factory
+ reset logic ran during the current boot but is complete now), pending (if a
+ factory reset has been requested for the next boot).
+
+ Returns with an exit status of 0 if the factory reset mechanism is currently not in effect, 10
+ if a factory reset is currently being executed, or 11 if it is pending for the next boot.
+
+
+
+
+
+
+
+ Request a factory reset operation to be executed on next boot.
+
+ Note that this is a relatively low-level operation. The primary interface for requesting a
+ factory reset operation is by starting the factory-reset.target
+ unit.
+
+ This sets the FactoryResetRequested EFI variable, see below.
+
+ This operation is executed when the systemd-factory-reset-request.service
+ unit is started (which is typically one of the services hooked into
+ and ordered before factory-reset.target).
+
+
+
+
+
+
+
+ Cancel any previously requested (but not yet executed) factory reset
+ operation.
+
+
+
+
+
+
+
+ Mark an ongoing factory reset operation as complete.
+
+ This operation is executed when the systemd-factory-reset-complete.service
+ unit is started (which is typically one of the services hooked into and ordered after
+ factory-reset-now.target).
+
+
+
+
+
+
+
+ Options
+
+ The following options are understood:
+
+
+
+
+
+ When used with the complete command retriggers all block devices,
+ which might result in auto-discovered devices being usable that previously weren't because the factory
+ reset logic was in place.
+
+
+
+
+
+
+
+
+ Suppresses the state output of status, but still sets the exit
+ status as documented.
+
+
+
+
+
+
+
+
+
+
+ EFI Variables
+
+ The following EFI variable is set and read by systemd-factory-reset, under the
+ vendor UUID 8cf2644b-4b0b-428f-9387-6d876050dc67, for communication between this boot
+ and the next.
+
+
+
+ FactoryResetRequest
+
+ Set whenever a factory reset is requested from the next boot, deleted once the
+ factory reset is complete. Contains JSON data describing the requesting OS, in order to avoid
+ confusion in multi-boot systems.
+
+
+
+
+
+
+
+ See Also
+
+ systemd1
+ systemd-factory-reset-generator8
+ systemd.special7
+ Factory Reset
+
+
+
diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml
index 69788baab4..d3724e7e9d 100644
--- a/man/systemd-gpt-auto-generator.xml
+++ b/man/systemd-gpt-auto-generator.xml
@@ -322,16 +322,20 @@
rootflags=When root= is used with the special value
- gpt-auto, full automatic discovery of the root
- partition based on the GPT partition type is enabled. Any other value disables this
- logic.
+ gpt-auto, full automatic discovery of the root partition based on the GPT
+ partition type is enabled. Use of the root partition is delayed until factory reset mode is left, in
+ case it is enabled during the current boot. See Factory
+ Reset for more information on that. If gpt-auto-force is specified
+ automatic discovery of the root partition is enabled, ignoring any factory reset mode. Any other
+ value disables this logic.
If root= is not specified at all on the kernel command line automatic
- discovery of the root partition via the boot loader reported ESP is also enabled, however in this
- case discovery based on the loopback block device .lo_name field is not enabled.
+ discovery of the root partition via the ESP reported by the boot loader is also enabled (taking
+ factory reset state into account), however in this case discovery based on the loopback block device
+ .lo_name field is not enabled.
- The rootfstype= and rootflags= are used to select the
- file system type and options when the root file system is automatically discovered.
+ The rootfstype= and rootflags= options are used to select
+ the file system type and options when the root file system is automatically discovered.
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml
index f4823cd0e2..40376bc773 100644
--- a/man/systemd-repart.xml
+++ b/man/systemd-repart.xml
@@ -130,9 +130,9 @@
systemd-repart may be used to erase existing partitions to reset an installation back
to vendor defaults. This mode of operation is used when either the
switch is passed on the tool's command line, or the option is
- specified on the kernel command line, or the FactoryReset EFI variable (vendor UUID
- 8cf2644b-4b0b-428f-9387-6d876050dc67) is set to "yes". It alters the algorithm above
- slightly: between the 3rd and the 4th step above any partition marked explicitly via the
+ specified on the kernel command line, or the FactoryResetRequest EFI variable (vendor
+ UUID 8cf2644b-4b0b-428f-9387-6d876050dc67) is set to "yes". It alters the algorithm
+ above slightly: between the 3rd and the 4th step above any partition marked explicitly via the
FactoryReset= boolean is deleted, and the algorithm restarted, thus immediately
re-creating these partitions anew empty.
diff --git a/man/systemd-tpm2-clear.service.xml b/man/systemd-tpm2-clear.service.xml
new file mode 100644
index 0000000000..e400e74e34
--- /dev/null
+++ b/man/systemd-tpm2-clear.service.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+ systemd-tpm2-clear.service
+ systemd
+
+
+
+ systemd-tpm2-clear.service
+ 8
+
+
+
+ systemd-tpm2-clear.service
+ Request that the TPM security chip is cleared on next boot
+
+
+
+ systemd-tpm2-clear.service
+ /usr/lib/systemd/systemd-tpm2-clear
+
+
+
+ Description
+
+ systemd-tpm2-clear.service is a service that requests that the TPM is reset by
+ the PC firmware on the next boot. It makes use of the TPM Physical Presence Interface (PPI). Note that
+ this service does not immediately execute the clear operation, but simply asks the PC firmware to execute
+ it at next boot, where the user will be asked for confirmation before the operation is done.
+
+ systemd-tpm2-clear.service is typically hooked into the
+ factory-reset.target unit in order to request the TPM request before an immediate
+ reboot. See Factory Reset for more
+ information.
+
+
+
+ Options
+
+ The following options are understood:
+
+
+
+
+
+ Exit cleanly and execute no operation if the system does not possess a TPM
+ chip.
+
+
+
+
+
+
+
+
+
+
+ Kernel Command Line
+
+ systemd-tpm2-clear understands the following kernel command line
+ parameters:
+
+
+
+ systemd.tpm2_allow_clear=
+
+ Takes a boolean argument. If false the service will succeed, but instead of requesting
+ the TPM clear operation from the PC firmware it will not execute any operation. If not specified
+ defaults to true.
+
+
+
+
+
+
+
+ See Also
+
+ systemd1
+ systemd-tpm2-setup.service8
+ systemd-factory-reset-request.service8
+
+
+
diff --git a/man/systemd.special.xml b/man/systemd.special.xml
index a4ae3f8983..0195bcb7fb 100644
--- a/man/systemd.special.xml
+++ b/man/systemd.special.xml
@@ -34,6 +34,7 @@
emergency.target,
exit.target,
factory-reset.target,
+ factory-reset-now.target,
final.target,
first-boot-complete.target,
getty.target,
@@ -304,11 +305,30 @@
factory-reset.target
- A special target to trigger a factory reset.
+ A special target to request a factory reset operation. This will typically persistently
+ store a request flag for the next boot and then reboot in order to reset the system to factory
+ state.
+
+ See Factory Reset for more
+ information.
+
+ factory-reset-now.target
+
+ A special target that is started on boots that shall execute a factory reset. It may be
+ used to pull in additional services that shall be invoked during a factory reset operation. It
+ also acts as ordering barrier: once the target is reached the factory reset state is marked as
+ "completed".
+
+ See Factory Reset for more
+ information.
+
+
+
+ final.target
diff --git a/meson.build b/meson.build
index fdad63fd29..1cdce6a868 100644
--- a/meson.build
+++ b/meson.build
@@ -2299,6 +2299,7 @@ subdir('src/detect-virt')
subdir('src/dissect')
subdir('src/environment-d-generator')
subdir('src/escape')
+subdir('src/factory-reset')
subdir('src/firstboot')
subdir('src/fsck')
subdir('src/fstab-generator')
diff --git a/presets/90-systemd.preset b/presets/90-systemd.preset
index a12be9eba7..9c13e9c3de 100644
--- a/presets/90-systemd.preset
+++ b/presets/90-systemd.preset
@@ -33,6 +33,7 @@ enable systemd-pstore.service
enable systemd-resolved.service
enable systemd-sysext.service
enable systemd-timesyncd.service
+enable systemd-tpm2-clear.service
enable systemd-userdbd.socket
disable console-getty.service
diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in
index ae05c75bb5..3fd3ea3ecd 100644
--- a/rules.d/99-systemd.rules.in
+++ b/rules.d/99-systemd.rules.in
@@ -34,10 +34,25 @@ SUBSYSTEM=="block", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", ENV{SYSTEMD_READ
# we are probably still calling mke2fs or mkswap on it.
SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0"
-# add symlink to GPT root disk
-SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="gpt-auto-root"
-SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="gpt-auto-root-luks"
-SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", SYMLINK+="gpt-auto-root"
+# Add symlink to GPT root disk – in two flavours: one which takes the factory
+# reset state into account, and one which does not. The former is useful for
+# wipe-rootfs-on-factory-reset scenarios where we should not be tempted to use
+# the root fs before factory reset is complete. The latter is useful for
+# wipe-only-/var-on-factory-reset where we should use it (because that's where
+# repart.d/ definitions are placed which tell us what to wipe).
+SUBSYSTEM!="block", GOTO="gpt_auto_root_end"
+ENV{ID_PART_GPT_AUTO_ROOT}!="1", GOTO="gpt_auto_root_end"
+IMPORT{builtin}="factory_reset status"
+ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_FACTORY_RESET}!="on", SYMLINK+="gpt-auto-root"
+ENV{ID_FS_TYPE}!="crypto_LUKS", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="gpt-auto-root-ignore-factory-reset"
+ENV{ID_FS_TYPE}=="crypto_LUKS", ENV{ID_FACTORY_RESET}!="on", SYMLINK+="gpt-auto-root-luks"
+ENV{ID_FS_TYPE}=="crypto_LUKS", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="gpt-auto-root-luks-ignore-factory-reset"
+LABEL="gpt_auto_root_end"
+# Note we don't need to condition the gpt-auto-root LUKS symlink for
+# auto-discovered LUKS devices, because it's sufficient if we do this for the
+# underlying partition block device, which is covered by the above.
+SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", SYMLINK+="gpt-auto-root", IMPORT{builtin}="factory_reset status"
+SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", ENV{ID_FACTORY_RESET}=="on|complete", SYMLINK+="gpt-auto-root-ignore-factory-reset"
# Ignore raid devices that are not yet assembled and started
SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", TEST!="md/array_state", ENV{SYSTEMD_READY}="0"
diff --git a/src/factory-reset/factory-reset-generator.c b/src/factory-reset/factory-reset-generator.c
new file mode 100644
index 0000000000..d3c961a2a9
--- /dev/null
+++ b/src/factory-reset/factory-reset-generator.c
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "factory-reset.h"
+#include "generator.h"
+#include "special.h"
+
+/* This generator pulls factory-reset-now.target into the initial transaction the kernel command line's
+ * systemd.factor_reset= variable, or the FactoryResetRequest EFI variable say so. */
+
+static int run(const char *dest, const char *dest_early, const char *dest_late) {
+ assert(dest_early);
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine factory reset mode: %m");
+ if (f != FACTORY_RESET_ON) {
+ log_debug("Not in factory reset mode, skipping.");
+ return EXIT_SUCCESS;
+ }
+
+ log_debug("Detected factory reset mode, pulling in factory-reset-now.target.");
+
+ /* We pull this in from basic.target so that it ends up in all "regular" boot ups, but not in
+ * rescue.target or even emergency.target. */
+ return generator_add_symlink(dest_early, SPECIAL_BASIC_TARGET, "wants", "factory-reset-now.target");
+}
+
+DEFINE_MAIN_GENERATOR_FUNCTION(run);
diff --git a/src/factory-reset/factory-reset-tool.c b/src/factory-reset/factory-reset-tool.c
new file mode 100644
index 0000000000..8bf5734601
--- /dev/null
+++ b/src/factory-reset/factory-reset-tool.c
@@ -0,0 +1,385 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-json.h"
+#include "sd-varlink.h"
+
+#include "ansi-color.h"
+#include "build.h"
+#include "device-util.h"
+#include "efivars.h"
+#include "factory-reset.h"
+#include "fs-util.h"
+#include "json-util.h"
+#include "main-func.h"
+#include "os-util.h"
+#include "pretty-print.h"
+#include "udev-util.h"
+#include "varlink-io.systemd.FactoryReset.h"
+#include "varlink-util.h"
+#include "verbs.h"
+
+static bool arg_retrigger = false;
+static bool arg_quiet = false;
+static bool arg_varlink = false;
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-factory-reset", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...] COMMAND\n"
+ "\n%5$sQuery, request, cancel factory reset operation.%6$s\n"
+ "\n%3$sCommands:%4$s\n"
+ " status Report current factory reset status\n"
+ " request Request a factory reset on next boot\n"
+ " cancel Cancel a prior factory reset request for next boot\n"
+ " complete Mark a factory reset as complete\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Print version\n"
+ " --retrigger Retrigger block devices\n"
+ " -q --quiet Suppress output\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_RETRIGGER,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "retrigger", no_argument, NULL, ARG_RETRIGGER },
+ { "quiet", no_argument, NULL, 'q' },
+ {}
+ };
+
+ int r, c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_RETRIGGER:
+ arg_retrigger = true;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
+ if (r > 0)
+ arg_varlink = true;
+
+ return 1;
+}
+
+static int verb_status(int argc, char *argv[], void *userdata) {
+ static const int exit_status_table[_FACTORY_RESET_MODE_MAX] = {
+ /* Report current mode also as via exit status, but only return a subset of states */
+ [FACTORY_RESET_UNSUPPORTED] = EXIT_SUCCESS,
+ [FACTORY_RESET_UNSPECIFIED] = EXIT_SUCCESS,
+ [FACTORY_RESET_OFF] = EXIT_SUCCESS,
+ [FACTORY_RESET_ON] = 10,
+ [FACTORY_RESET_COMPLETE] = EXIT_SUCCESS,
+ [FACTORY_RESET_PENDING] = 11,
+ };
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine factory reset mode: %m");
+
+ if (!arg_quiet)
+ puts(factory_reset_mode_to_string(f));
+
+ return exit_status_table[f];
+}
+
+static int verb_request(int argc, char *argv[], void *userdata) {
+ int r;
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine current factory reset mode: %m");
+ if (f == FACTORY_RESET_ON)
+ return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "System is currently in factory reset mode, refusing to request another one.");
+ if (f == FACTORY_RESET_PENDING) {
+ if (!arg_quiet)
+ log_info("Factory reset already requested, skipping.");
+ return 0;
+ }
+
+ if (!is_efi_boot())
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Not an EFI boot, requesting factory reset via EFI variable not supported.");
+
+ _cleanup_free_ char *id = NULL, *image_id = NULL, *version_id = NULL, *image_version = NULL;
+ r = parse_os_release(
+ /* root= */ NULL,
+ "ID", &id,
+ "IMAGE_ID", &image_id,
+ "VERSION_ID", &version_id,
+ "IMAGE_VERSION", &image_version);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse os-release: %m");
+
+ if (!id)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADR), "os-release data lacks ID= field, refusing.");
+
+ sd_id128_t boot_id;
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get boot ID: %m");
+
+ /* NB: we don't really use the version fields for anything on the parsing side, because we want to
+ * allow some flexbility between OS/image versions that request the factory reset and that execute
+ * it. However, we include it nonetheless to make things more clearly debuggable. */
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ r = sd_json_buildo(
+ &v,
+ SD_JSON_BUILD_PAIR_STRING("osReleaseId", id),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("osReleaseVersionId", version_id),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("osReleaseImageId", image_id),
+ JSON_BUILD_PAIR_STRING_NON_EMPTY("osReleaseImageVersion", image_version),
+ SD_JSON_BUILD_PAIR_ID128("bootId", boot_id));
+ if (r < 0)
+ return log_error_errno(r, "Failed to build JSON object: %m");
+
+ _cleanup_free_ char *formatted = NULL;
+ r = sd_json_variant_format(v, /* flags= */ 0, &formatted);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format JSON object: %m");
+
+ r = efi_set_variable_string(EFI_SYSTEMD_VARIABLE_STR("FactoryResetRequest"), formatted);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set EFI variable FactoryResetRequest: %m");
+
+ log_debug("Set EFI variable FactoryResetRequest to '%s'.", formatted);
+
+ if (!arg_quiet)
+ log_info("Factory reset requested.");
+
+ return 0;
+}
+
+static int verb_cancel(int argc, char *argv[], void *userdata) {
+ int r;
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine current factory reset mode: %m");
+ if (f == FACTORY_RESET_ON)
+ return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "System already executing factory reset, cannot cancel.");
+ if (f != FACTORY_RESET_PENDING) {
+ if (!arg_quiet)
+ log_info("No factory reset has been requested, cannot cancel, skipping.");
+ return 0;
+ }
+
+ if (!is_efi_boot()) {
+ if (!arg_quiet)
+ log_info("Not an EFI boot, cannot remove FactoryResetMode EFI variable, not cancelling.");
+
+ return 0;
+ }
+
+ r = efi_set_variable(EFI_SYSTEMD_VARIABLE_STR("FactoryResetRequest"), /* value= */ NULL, /* size= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to remove FactoryResetRequest EFI variable: %m");
+
+ if (!arg_quiet)
+ log_info("Factory reset cancelled.");
+
+ return 0;
+}
+
+static int retrigger_block_devices(void) {
+ int r;
+
+ /* Let's retrigger block devices after factory reset is complete: it's quite likely that some
+ * partitions went away or got recreated, and will only be considered relevant once factory reset
+ * mode is left. For example, /dev/disk/gpt-auto-root is like that: it is only created once factory
+ * reset mode is complete. */
+
+ if (!udev_available()) {
+ if (!arg_quiet)
+ log_info("Skipping triggering of block devices, as udev is not available.");
+ return 0;
+ }
+
+ _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL;
+ r = sd_device_enumerator_new(&e);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate device enumerator: %m");
+
+ r = sd_device_enumerator_allow_uninitialized(e);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable enumeration of uninitialized devices: %m");
+
+ r = sd_device_enumerator_add_match_subsystem(e, "block", /* match = */ true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to filter device enumeration by 'block' subsystem: %m");
+
+ if (!arg_quiet)
+ log_info("Retriggering block devices.");
+
+ FOREACH_DEVICE(e, d) {
+ r = sd_device_trigger(d, SD_DEVICE_CHANGE);
+ if (r < 0)
+ /* Devices can appear anytime, let's not loudly log about that. */
+ log_device_full_errno(
+ d,
+ ERRNO_IS_DEVICE_ABSENT(r) ? LOG_DEBUG : LOG_WARNING,
+ r,
+ "Failed to trigger block device, ignoring: %m");
+ }
+
+ return 0;
+}
+
+static int verb_complete(int argc, char *argv[], void *userdata) {
+ int r;
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to dermine factory reset mode: %m");
+ log_debug("Current factory reset mode is: %s", factory_reset_mode_to_string(f));
+ if (f != FACTORY_RESET_ON) {
+ if (!arg_quiet)
+ log_info("Attempted to leave factory reset mode, even though we are not in factory reset mode. Ignoring.");
+ return 0;
+ }
+
+ if (is_efi_boot()) {
+ r = efi_set_variable(EFI_SYSTEMD_VARIABLE_STR("FactoryResetRequest"), /* value= */ NULL, /* size= */ 0);
+ if (r < 0)
+ log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r,
+ "Failed to remove FactoryResetRequest EFI variable: %m");
+ }
+
+ r = touch("/run/systemd/factory-reset-complete");
+ if (r < 0)
+ return log_error_errno(r, "Failed to create /run/systemd/factory-reset-complete file: %m");
+
+ if (!arg_quiet)
+ log_info("Successfully left factory reset mode.");
+
+ if (arg_retrigger)
+ (void) retrigger_block_devices();
+
+ return 0;
+}
+
+static int vl_method_get_factory_reset_mode(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ int r;
+
+ assert(parameters);
+
+ r = sd_varlink_dispatch(link, parameters, /* table= */ NULL, /* userdata= */ NULL);
+ if (r != 0)
+ return r;
+
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return f;
+
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("mode", factory_reset_mode_to_string(f)));
+}
+
+static int vl_method_can_request_factory_reset(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
+ int r;
+
+ assert(parameters);
+
+ r = sd_varlink_dispatch(link, parameters, /* table= */ NULL, /* userdata= */ NULL);
+ if (r != 0)
+ return r;
+
+ return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_BOOLEAN("supported", is_efi_boot()));
+}
+
+static int varlink_service(void) {
+ int r;
+
+ /* Invocation as Varlink service */
+
+ _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
+ r = varlink_server_new(&varlink_server, /* flags= */ 0, /* userdata= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate Varlink server: %m");
+
+ r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_FactoryReset);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add Varlink interface: %m");
+
+ r = sd_varlink_server_bind_method_many(
+ varlink_server,
+ "io.systemd.FactoryReset.GetFactoryResetMode", vl_method_get_factory_reset_mode,
+ "io.systemd.FactoryReset.CanRequestFactoryReset", vl_method_can_request_factory_reset);
+ if (r < 0)
+ return log_error_errno(r, "Failed to bind Varlink methods: %m");
+
+ r = sd_varlink_server_loop_auto(varlink_server);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run Varlink event loop: %m");
+
+ return EXIT_SUCCESS;
+}
+
+static int run(int argc, char *argv[]) {
+ static const Verb verbs[] = {
+ { "status", VERB_ANY, 1, VERB_DEFAULT, verb_status },
+ { "request", VERB_ANY, 1, 0, verb_request },
+ { "cancel", VERB_ANY, 1, 0, verb_cancel },
+ { "complete", VERB_ANY, 1, 0, verb_complete },
+ {}
+ };
+
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ if (arg_varlink)
+ return varlink_service();
+
+ return dispatch_verb(argc, argv, verbs, /* userdata= */ NULL);
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/factory-reset/meson.build b/src/factory-reset/meson.build
new file mode 100644
index 0000000000..1afac3ad64
--- /dev/null
+++ b/src/factory-reset/meson.build
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+
+executables += [
+ libexec_template + {
+ 'name' : 'systemd-factory-reset',
+ 'sources' : files('factory-reset-tool.c'),
+ },
+ generator_template + {
+ 'name' : 'systemd-factory-reset-generator',
+ 'sources' : files('factory-reset-generator.c'),
+ },
+]
diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c
index 6693333c4b..7e84be499d 100644
--- a/src/fstab-generator/fstab-generator.c
+++ b/src/fstab-generator/fstab-generator.c
@@ -1119,7 +1119,7 @@ static bool validate_root_or_usr_mount_source(const char *what, const char *swit
return false;
}
- if (streq(what, "gpt-auto")) {
+ if (parse_gpt_auto_root(what) > 0) {
/* This is handled by gpt-auto-generator */
log_debug("Skipping %s directory handling, as gpt-auto was requested.", switch_name);
return false;
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
index 1b59fecf9e..c6727523f0 100644
--- a/src/gpt-auto-generator/gpt-auto-generator.c
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
@@ -17,6 +17,7 @@
#include "dissect-image.h"
#include "dropin.h"
#include "efi-loader.h"
+#include "factory-reset.h"
#include "fd-util.h"
#include "fileio.h"
#include "fs-util.h"
@@ -39,7 +40,7 @@
static const char *arg_dest = NULL;
static bool arg_enabled = true;
-static int arg_root_enabled = -1; /* tristate */
+static GptAutoRoot arg_auto_root = _GPT_AUTO_ROOT_INVALID;
static bool arg_swap_enabled = true;
static char *arg_root_fstype = NULL;
static char *arg_root_options = NULL;
@@ -47,7 +48,6 @@ static int arg_root_rw = -1;
static ImagePolicy *arg_image_policy = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
-
STATIC_DESTRUCTOR_REGISTER(arg_root_fstype, freep);
STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep);
@@ -661,7 +661,19 @@ static int add_root_cryptsetup(void) {
/* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which
* sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */
- return add_cryptsetup("root", "/dev/gpt-auto-root-luks", arg_root_options, /* rw= */ true, /* require= */ false, /* measure= */ true, NULL);
+ const char *bdev = "/dev/gpt-auto-root-luks";
+
+ if (arg_auto_root == GPT_AUTO_ROOT_FORCE) {
+ /* Similar logic as in add_root_mount(), see below */
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ log_warning_errno(f, "Failed to determine whether we are in factory reset mode, assuming not: %m");
+
+ if (IN_SET(f, FACTORY_RESET_ON, FACTORY_RESET_COMPLETE))
+ bdev = "/dev/gpt-auto-root-luks-ignore-factory-reset";
+ }
+
+ return add_cryptsetup("root", bdev, arg_root_options, /* rw= */ true, /* require= */ false, /* measure= */ true, NULL);
#else
return 0;
#endif
@@ -674,11 +686,11 @@ static int add_root_mount(void) {
int r;
/* Explicitly disabled? Then exit immediately */
- if (arg_root_enabled == 0)
+ if (arg_auto_root == GPT_AUTO_ROOT_OFF)
return 0;
/* Neither explicitly enabled nor disabled? Then decide based on the EFI partition variables to be set */
- if (arg_root_enabled < 0) {
+ if (arg_auto_root < 0) {
if (!is_efi_boot()) {
log_debug("Not an EFI boot, not creating root mount.");
return 0;
@@ -695,10 +707,27 @@ static int add_root_mount(void) {
}
/* OK, we shall look for a root device, so let's wait for a root device to show up. A udev rule will
- * create the link for us under the right name. */
+ * create the link for us under the right name.
+ *
+ * There are two distinct names: the /dev/gpt-auto-root-ignore-factory-reset symlink is created for
+ * the root partition if factory reset mode is enabled or complete, and the /dev/gpt-auto-root
+ * symlink is only created if factory reset mode is off or already complete (thus taking factory
+ * reset state into account). In scenarios where the root disk is partially reformatted during
+ * factory reset the latter is the link to use, otherwise the former (so that we don't accidentally
+ * mount a root partition too early that is about to be wiped and replaced by another one). */
+
+ const char *bdev = "/dev/gpt-auto-root";
+ if (arg_auto_root == GPT_AUTO_ROOT_FORCE) {
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ log_warning_errno(f, "Failed to determine whether we are in factory reset mode, assuming not: %m");
+
+ if (IN_SET(f, FACTORY_RESET_ON, FACTORY_RESET_COMPLETE))
+ bdev = "/dev/gpt-auto-root-ignore-factory-reset";
+ }
if (in_initrd()) {
- r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root");
+ r = generator_write_initrd_root_device_deps(arg_dest, bdev);
if (r < 0)
return 0;
@@ -727,7 +756,7 @@ static int add_root_mount(void) {
return add_mount(
"root",
- "/dev/gpt-auto-root",
+ bdev,
in_initrd() ? "/sysroot" : "/",
arg_root_fstype,
/* rw= */ arg_root_rw > 0,
@@ -909,16 +938,11 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
if (proc_cmdline_value_missing(key, value))
return 0;
- /* Disable root disk logic if there's a root= value
- * specified (unless it happens to be "gpt-auto") */
+ /* Disable root disk logic if there's a root= value specified (unless it happens to be
+ * "gpt-auto" or "gpt-auto-force") */
- if (streq(value, "gpt-auto")) {
- arg_root_enabled = true;
- log_debug("Enabling root partition auto-detection, root= is explicitly set to 'gpt_auto'.");
- } else {
- arg_root_enabled = false;
- log_debug("Disabling root partition auto-detection, root= is neither unset, nor set to 'gpt-auto'.");
- }
+ arg_auto_root = parse_gpt_auto_root(value);
+ assert(arg_auto_root >= 0);
} else if (streq(key, "roothash")) {
@@ -927,7 +951,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat
/* Disable root disk logic if there's roothash= defined (i.e. verity enabled) */
- arg_root_enabled = false;
+ arg_auto_root = GPT_AUTO_ROOT_OFF;
log_debug("Disabling root partition auto-detection, roothash= is set.");
} else if (streq(key, "rootfstype")) {
diff --git a/src/repart/repart.c b/src/repart/repart.c
index c8ff37124c..9827d032b5 100644
--- a/src/repart/repart.c
+++ b/src/repart/repart.c
@@ -33,6 +33,7 @@
#include "dirent-util.h"
#include "efivars.h"
#include "errno-util.h"
+#include "factory-reset.h"
#include "fd-util.h"
#include "fdisk-util.h"
#include "fileio.h"
@@ -8712,23 +8713,20 @@ static int parse_argv(int argc, char *argv[], X509 **ret_certificate, EVP_PKEY *
}
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", /* flags = */ 0, &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;
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0)
+ return log_error_errno(f, "Failed to determine factory reset status: %m");
+ if (f != FACTORY_RESET_UNSPECIFIED) {
+ arg_factory_reset = f == FACTORY_RESET_ON;
- if (b)
- log_notice("Honouring factory reset requested via kernel command line.");
+ if (arg_factory_reset)
+ log_notice("Honouring factory reset requested via kernel command line or EFI variable.");
}
return 0;
@@ -8738,6 +8736,10 @@ static int parse_efi_variable_factory_reset(void) {
_cleanup_free_ char *value = NULL;
int r;
+ /* NB: This is legacy, people should move to the newer FactoryResetRequest variable! */
+
+ // FIXME: Remove this in v260
+
if (arg_factory_reset >= 0) /* Never override what is specified on the process command line */
return 0;
@@ -8751,6 +8753,8 @@ static int parse_efi_variable_factory_reset(void) {
return log_error_errno(r, "Failed to read EFI variable FactoryReset: %m");
}
+ log_warning("Warning, EFI variable FactoryReset is in use, please migrate to use FactoryResetRequest instead, support will be removed in v260!");
+
r = parse_boolean(value);
if (r < 0)
return log_error_errno(r, "Failed to parse EFI variable FactoryReset: %m");
@@ -8765,6 +8769,8 @@ static int parse_efi_variable_factory_reset(void) {
static int remove_efi_variable_factory_reset(void) {
int r;
+ // FIXME: Remove this in v260, see above
+
r = efi_set_variable(EFI_SYSTEMD_VARIABLE_STR("FactoryReset"), NULL, 0);
if (r < 0) {
if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r))
diff --git a/src/shared/factory-reset.c b/src/shared/factory-reset.c
new file mode 100644
index 0000000000..39f4f2578a
--- /dev/null
+++ b/src/shared/factory-reset.c
@@ -0,0 +1,125 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "sd-json.h"
+
+#include "efivars.h"
+#include "env-util.h"
+#include "factory-reset.h"
+#include "os-util.h"
+#include "proc-cmdline.h"
+#include "string-table.h"
+
+static bool factory_reset_supported(void) {
+ int r;
+
+ r = secure_getenv_bool("SYSTEMD_FACTORY_RESET_SUPPORTED");
+ if (r >= 0)
+ return r;
+ if (r != -ENXIO)
+ log_debug_errno(r, "Unable to parse $SYSTEMD_FACTORY_RESET_SUPPORTED, ignoring: %m");
+
+ return true;
+}
+
+static FactoryResetMode factory_reset_mode_efi_variable(void) {
+ int r;
+
+ if (!is_efi_boot()) {
+ log_debug("Not booted in EFI mode, not checking FactoryResetRequest variable.");
+ return FACTORY_RESET_UNSPECIFIED;
+ }
+
+ _cleanup_free_ char *req_str = NULL;
+ r = efi_get_variable_string(EFI_SYSTEMD_VARIABLE_STR("FactoryResetRequest"), &req_str);
+ if (r == -ENOENT) {
+ log_debug_errno(r, "EFI variable FactoryResetRequest is not set, skipping.");
+ return FACTORY_RESET_UNSPECIFIED;
+ }
+ if (r < 0)
+ return log_debug_errno(r, "Failed to get EFI variable FactoryResetRequest: %m");
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
+ r = sd_json_parse(req_str, /* flags= */ 0, &v, /* ret_line= */ NULL, /* ret_column= */ NULL);
+ if (r < 0) {
+ log_debug_errno(r, "EFI variable FactoryResetRequest set to invalid JSON, ignoring: %m");
+ return FACTORY_RESET_UNSPECIFIED;
+ }
+
+ struct {
+ const char *id;
+ const char *image_id;
+ sd_id128_t boot_id;
+ } req = {};
+ static const sd_json_dispatch_field dispatch_table[] = {
+ { "osReleaseId", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(req, id), SD_JSON_MANDATORY },
+ { "osReleaseImageId", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(req, image_id), 0 },
+ { "bootId", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, voffsetof(req, boot_id), SD_JSON_MANDATORY },
+ {},
+ };
+
+ r = sd_json_dispatch(v, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &req);
+ if (r < 0) {
+ log_debug_errno(r, "Unable to dispatch EFI variable FactoryResetRequest, ignoring: %m");
+ return FACTORY_RESET_UNSPECIFIED;
+ }
+
+ _cleanup_free_ char *id = NULL, *image_id = NULL;
+ r = parse_os_release(
+ /* root= */ NULL,
+ "ID", &id,
+ "IMAGE_ID", &image_id);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse os-release: %m");
+
+ if (!streq_ptr(req.id, id) || !streq_ptr(req.image_id, image_id)) {
+ log_debug("FactoryResetRequest EFI variable set, but not for us, ignoring.");
+ return FACTORY_RESET_UNSPECIFIED;
+ }
+
+ sd_id128_t boot_id;
+ r = sd_id128_get_boot(&boot_id);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to query boot ID: %m");
+
+ if (sd_id128_equal(req.boot_id, boot_id)) {
+ /* NB: if the boot ID in the EFI variable matches our *current* one, then the request is not
+ * intended for us, but for the *next* boot. */
+ log_debug("EFI variable FactoryResetRequest set for next boot.");
+ return FACTORY_RESET_PENDING;
+ }
+
+ return FACTORY_RESET_ON;
+}
+
+FactoryResetMode factory_reset_mode(void) {
+ int r;
+
+ if (!factory_reset_supported())
+ return FACTORY_RESET_UNSUPPORTED;
+
+ /* First check if we already completed a factory reset in this boot */
+ if (access("/run/systemd/factory-reset-complete", F_OK) >= 0)
+ return FACTORY_RESET_COMPLETE;
+ if (errno != ENOENT)
+ return log_debug_errno(errno, "Can't determine if /run/systemd/factory-reset-complete exists: %m");
+
+ bool b;
+ r = proc_cmdline_get_bool("systemd.factory_reset", /* flags= */ 0, &b);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse systemd.factory_reset kernel command line argument: %m");
+ if (r == 0) /* Check EFI variable in case kernel cmdline switch is not specified */
+ return factory_reset_mode_efi_variable();
+
+ return b ? FACTORY_RESET_ON : FACTORY_RESET_OFF; /* Honour if explicitly turned off or on via kernel cmdline */
+}
+
+static const char* const factory_reset_mode_table[_FACTORY_RESET_MODE_MAX] = {
+ [FACTORY_RESET_UNSUPPORTED] = "unsupported",
+ [FACTORY_RESET_UNSPECIFIED] = "unspecified",
+ [FACTORY_RESET_OFF] = "off",
+ [FACTORY_RESET_ON] = "on",
+ [FACTORY_RESET_COMPLETE] = "complete",
+ [FACTORY_RESET_PENDING] = "pending",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(factory_reset_mode, FactoryResetMode);
diff --git a/src/shared/factory-reset.h b/src/shared/factory-reset.h
new file mode 100644
index 0000000000..49ae7007b0
--- /dev/null
+++ b/src/shared/factory-reset.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "errno-list.h"
+#include "macro.h"
+
+typedef enum FactoryResetMode {
+ FACTORY_RESET_UNSUPPORTED, /* feature not available on this OS */
+ FACTORY_RESET_UNSPECIFIED, /* not specified on the kernel cmdline, nor via EFI variable */
+ FACTORY_RESET_OFF, /* explicitly turned off on kernel cmdline */
+ FACTORY_RESET_ON, /* turned on via kernel cmdline or EFI variable */
+ FACTORY_RESET_COMPLETE, /* turned on via kernel cmdline or EFI variable, but marked as complete now */
+ FACTORY_RESET_PENDING, /* marked for next boot via EFI variable, but not in effect on this boot */
+ _FACTORY_RESET_MODE_MAX,
+ _FACTORY_RESET_MODE_INVALID = -EINVAL,
+ _FACTORY_RESET_MODE_ERRNO_MAX = -ERRNO_MAX,
+} FactoryResetMode;
+
+FactoryResetMode factory_reset_mode(void);
+
+const char* factory_reset_mode_to_string(FactoryResetMode) _const_;
+FactoryResetMode factory_reset_mode_from_string(const char *s) _pure_;
diff --git a/src/shared/generator.c b/src/shared/generator.c
index 9d7246fe02..f2997c1b16 100644
--- a/src/shared/generator.c
+++ b/src/shared/generator.c
@@ -1030,3 +1030,22 @@ bool generator_soft_rebooted(void) {
return (cached = (u > 0));
}
+
+GptAutoRoot parse_gpt_auto_root(const char *value) {
+ assert(value);
+
+ /* Parses the 'gpt-auto'/'gpt-auto-root' parameters to root= */
+
+ if (streq(value, "gpt-auto")) {
+ log_debug("Enabling root partition auto-detection (respecting factory reset mode), root= is explicitly set to 'gpt-auto'.");
+ return GPT_AUTO_ROOT_ON;
+ }
+
+ if (streq(value, "gpt-auto-force")) {
+ log_debug("Enabling root partition auto-detection (ignoring factory reset mode), root= is explicitly set to 'gpt-auto-force'.");
+ return GPT_AUTO_ROOT_FORCE;
+ }
+
+ log_debug("Disabling root partition auto-detection, root= is neither unset, nor set to 'gpt-auto' or 'gpt-auto-force'.");
+ return GPT_AUTO_ROOT_OFF;
+}
diff --git a/src/shared/generator.h b/src/shared/generator.h
index 5879e34ea2..4738fe90af 100644
--- a/src/shared/generator.h
+++ b/src/shared/generator.h
@@ -3,6 +3,7 @@
#include
+#include "errno-list.h"
#include "macro.h"
#include "main-func.h"
@@ -114,3 +115,13 @@ bool generator_soft_rebooted(void);
argv[argc == 4 ? 3 : 1]), \
exit_failure_if_negative, \
exit_failure_if_negative)
+
+typedef enum GptAutoRoot {
+ GPT_AUTO_ROOT_OFF = 0, /* root= set to something else */
+ GPT_AUTO_ROOT_ON, /* root= set explicitly to "gpt-auto" */
+ GPT_AUTO_ROOT_FORCE, /* root= set explicitly to "gpt-auto-force" → ignores factory reset mode */
+ _GPT_AUTO_ROOT_MAX,
+ _GPT_AUTO_ROOT_INVALID = -EINVAL,
+} GptAutoRoot;
+
+GptAutoRoot parse_gpt_auto_root(const char *value);
diff --git a/src/shared/meson.build b/src/shared/meson.build
index 62c6bcf511..ccbdc84974 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -70,6 +70,7 @@ shared_sources = files(
'exec-util.c',
'exit-status.c',
'extension-util.c',
+ 'factory-reset.c',
'fdset.c',
'fido2-util.c',
'find-esp.c',
@@ -183,6 +184,7 @@ shared_sources = files(
'varlink-io.systemd.AskPassword.c',
'varlink-io.systemd.BootControl.c',
'varlink-io.systemd.Credentials.c',
+ 'varlink-io.systemd.FactoryReset.c',
'varlink-io.systemd.Hostname.c',
'varlink-io.systemd.Import.c',
'varlink-io.systemd.Journal.c',
diff --git a/src/shared/varlink-io.systemd.FactoryReset.c b/src/shared/varlink-io.systemd.FactoryReset.c
new file mode 100644
index 0000000000..4f68889f2e
--- /dev/null
+++ b/src/shared/varlink-io.systemd.FactoryReset.c
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "varlink-io.systemd.FactoryReset.h"
+#include "sd-varlink-idl.h"
+
+static SD_VARLINK_DEFINE_ENUM_TYPE(
+ FactoryResetMode,
+ SD_VARLINK_FIELD_COMMENT("Factory reset is not supported on this OS."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(unsupported),
+ SD_VARLINK_FIELD_COMMENT("Factory reset not requested."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(unspecified),
+ SD_VARLINK_FIELD_COMMENT("Factory reset explicitly turned off."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(off),
+ SD_VARLINK_FIELD_COMMENT("Factory reset is currently being executed."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(on),
+ SD_VARLINK_FIELD_COMMENT("Factory reset has been completed during the current boot."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(complete),
+ SD_VARLINK_FIELD_COMMENT("Factory reset has been requested for the next boot."),
+ SD_VARLINK_DEFINE_ENUM_VALUE(pending));
+
+static SD_VARLINK_DEFINE_METHOD(
+ GetFactoryResetMode,
+ SD_VARLINK_FIELD_COMMENT("The current factory reset mode"),
+ SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(mode, FactoryResetMode, 0));
+
+static SD_VARLINK_DEFINE_METHOD(
+ CanRequestFactoryReset,
+ SD_VARLINK_DEFINE_OUTPUT(supported, SD_VARLINK_BOOL, 0));
+
+SD_VARLINK_DEFINE_INTERFACE(
+ io_systemd_FactoryReset,
+ "io.systemd.FactoryReset",
+ SD_VARLINK_INTERFACE_COMMENT("APIs to query factory reset status"),
+ SD_VARLINK_SYMBOL_COMMENT("Encodes the current factory reset status"),
+ &vl_type_FactoryResetMode,
+ SD_VARLINK_SYMBOL_COMMENT("Report the current factory reset status"),
+ &vl_method_GetFactoryResetMode,
+ SD_VARLINK_SYMBOL_COMMENT("Returns whether requesting a factory reset is available (by invoking the factory-reset.target unit)."),
+ &vl_method_CanRequestFactoryReset);
diff --git a/src/shared/varlink-io.systemd.FactoryReset.h b/src/shared/varlink-io.systemd.FactoryReset.h
new file mode 100644
index 0000000000..2590a38bdb
--- /dev/null
+++ b/src/shared/varlink-io.systemd.FactoryReset.h
@@ -0,0 +1,6 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include "sd-varlink-idl.h"
+
+extern const sd_varlink_interface vl_interface_io_systemd_FactoryReset;
diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h
index cc30add200..60b28087f6 100644
--- a/src/systemd/sd-messages.h
+++ b/src/systemd/sd-messages.h
@@ -278,6 +278,8 @@ _SD_BEGIN_DECLARATIONS;
#define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION SD_ID128_MAKE(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a)
#define SD_MESSAGE_SRK_ENROLLMENT_NEEDS_AUTHORIZATION_STR SD_ID128_MAKE_STR(ad,70,89,f9,28,ac,4f,7e,a0,0c,07,45,7d,47,ba,8a)
+#define SD_MESSAGE_TPM2_CLEAR_REQUESTED SD_ID128_MAKE(43,81,88,86,1e,0b,42,7a,9d,63,8a,90,48,7a,0c,a6)
+#define SD_MESSAGE_TPM2_CLEAR_REQUESTED_STR SD_ID128_MAKE_STR(43,81,88,86,1e,0b,42,7a,9d,63,8a,90,48,7a,0c,a6)
#define SD_MESSAGE_SYSCTL_CHANGED SD_ID128_MAKE(9c,f5,6b,8b,af,95,46,cf,94,78,78,3a,8d,e4,21,13)
#define SD_MESSAGE_SYSCTL_CHANGED_STR SD_ID128_MAKE_STR(9c,f5,6b,8b,af,95,46,cf,94,78,78,3a,8d,e4,21,13)
diff --git a/src/test/test-varlink-idl.c b/src/test/test-varlink-idl.c
index 9de50641e6..d3e4a6a116 100644
--- a/src/test/test-varlink-idl.c
+++ b/src/test/test-varlink-idl.c
@@ -10,9 +10,10 @@
#include "tests.h"
#include "varlink-idl-util.h"
#include "varlink-io.systemd.h"
-#include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.AskPassword.h"
+#include "varlink-io.systemd.BootControl.h"
#include "varlink-io.systemd.Credentials.h"
+#include "varlink-io.systemd.FactoryReset.h"
#include "varlink-io.systemd.Import.h"
#include "varlink-io.systemd.Journal.h"
#include "varlink-io.systemd.Login.h"
@@ -203,6 +204,8 @@ TEST(parse_format) {
print_separator();
test_parse_format_one(&vl_interface_io_systemd_Login);
print_separator();
+ test_parse_format_one(&vl_interface_io_systemd_FactoryReset);
+ print_separator();
test_parse_format_one(&vl_interface_xyz_test);
}
diff --git a/src/tpm2-setup/meson.build b/src/tpm2-setup/meson.build
index 11427d5e3a..6cfafe7cc5 100644
--- a/src/tpm2-setup/meson.build
+++ b/src/tpm2-setup/meson.build
@@ -13,6 +13,13 @@ executables += [
libopenssl,
],
},
+ libexec_template + {
+ 'name' : 'systemd-tpm2-clear',
+ 'sources' : files('tpm2-clear.c'),
+ 'conditions' : [
+ 'HAVE_TPM2',
+ ],
+ },
generator_template + {
'name' : 'systemd-tpm2-generator',
'sources' : files('tpm2-generator.c'),
diff --git a/src/tpm2-setup/tpm2-clear.c b/src/tpm2-setup/tpm2-clear.c
new file mode 100644
index 0000000000..330d5cbd59
--- /dev/null
+++ b/src/tpm2-setup/tpm2-clear.c
@@ -0,0 +1,140 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include
+
+#include "sd-messages.h"
+
+#include "build.h"
+#include "env-util.h"
+#include "fileio.h"
+#include "main-func.h"
+#include "pretty-print.h"
+#include "proc-cmdline.h"
+#include "tpm2-util.h"
+
+static bool arg_graceful = false;
+
+static int help(void) {
+ _cleanup_free_ char *link = NULL;
+ int r;
+
+ r = terminal_urlify_man("systemd-tpm2-clear", "8", &link);
+ if (r < 0)
+ return log_oom();
+
+ printf("%1$s [OPTIONS...]\n"
+ "\n%5$sRequest clearing of the TPM2 from PC firmware.%6$s\n"
+ "\n%3$sOptions:%4$s\n"
+ " -h --help Show this help\n"
+ " --version Show package version\n"
+ " --graceful Exit gracefully if no TPM2 device is found\n"
+ "\nSee the %2$s for details.\n",
+ program_invocation_short_name,
+ link,
+ ansi_underline(),
+ ansi_normal(),
+ ansi_highlight(),
+ ansi_normal());
+
+ return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+ enum {
+ ARG_VERSION = 0x100,
+ ARG_GRACEFUL,
+ };
+
+ static const struct option options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, ARG_VERSION },
+ { "graceful", no_argument, NULL, ARG_GRACEFUL },
+ {}
+ };
+
+ int c;
+
+ assert(argc >= 0);
+ assert(argv);
+
+ while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
+ switch (c) {
+
+ case 'h':
+ return help();
+
+ case ARG_VERSION:
+ return version();
+
+ case ARG_GRACEFUL:
+ arg_graceful = true;
+ break;
+
+ case '?':
+ return -EINVAL;
+
+ default:
+ assert_not_reached();
+ }
+
+ if (optind != argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program expects no arguments.");
+
+ return 1;
+}
+
+static int request_tpm2_clear(void) {
+ int r, clear = -1;
+
+ r = secure_getenv_bool("SYSTEMD_TPM2_ALLOW_CLEAR");
+ if (r < 0 && r != -ENXIO)
+ log_warning_errno(r, "Failed to parse $SYSTEMD_TPM2_ALLOW_CLEAR, ignoring: %m");
+ if (r >= 0)
+ clear = r;
+
+ if (clear < 0) {
+ bool b;
+ r = proc_cmdline_get_bool("systemd.tpm2_allow_clear", /* flags= */ 0, &b);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to parse systemd.tpm2_allow_clear kernel command line argument: %m");
+ if (r > 0)
+ clear = b;
+ }
+
+ if (clear == 0) {
+ log_info("Clearing TPM2 disabled, exiting early.");
+ return EXIT_SUCCESS;
+ }
+
+ /* Now issue PPI request */
+ r = write_string_file("/sys/class/tpm/tpm0/ppi/request", "5", /* flags= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to request TPM2 cleaing via PPI, unable to write to /sys/class/tpm/tpm0/ppi/request: %m");
+
+ log_struct(LOG_NOTICE,
+ "MESSAGE_ID=" SD_MESSAGE_TPM2_CLEAR_REQUESTED_STR,
+ LOG_MESSAGE("Requested TPM2 clearing via PPI. Firmware will verify with user and clear TPM on reboot."));
+ return 0;
+}
+
+static int run(int argc, char *argv[]) {
+ int r;
+
+ log_setup();
+
+ r = parse_argv(argc, argv);
+ if (r <= 0)
+ return r;
+
+ /* If we don't fully support the TPM we are unlikely able to reinitialize it after boot, hence don't
+ * be tempted to reset it in graceful mode. Otherwise we might destroy something without being able
+ * to rebuild it. */
+ if (arg_graceful && !tpm2_is_fully_supported()) {
+ log_notice("No complete TPM2 support detected, exiting gracefully.");
+ return EXIT_SUCCESS;
+ }
+
+ return request_tpm2_clear();
+}
+
+DEFINE_MAIN_FUNCTION_WITH_POSITIVE_FAILURE(run);
diff --git a/src/udev/meson.build b/src/udev/meson.build
index b3f27e0df2..b24234bba2 100644
--- a/src/udev/meson.build
+++ b/src/udev/meson.build
@@ -20,7 +20,9 @@ udevadm_sources = files(
libudevd_core_sources = files(
'net/link-config.c',
+ 'udev-builtin.c',
'udev-builtin-btrfs.c',
+ 'udev-builtin-factory_reset.c',
'udev-builtin-hwdb.c',
'udev-builtin-input_id.c',
'udev-builtin-keyboard.c',
@@ -29,7 +31,6 @@ libudevd_core_sources = files(
'udev-builtin-net_setup_link.c',
'udev-builtin-path_id.c',
'udev-builtin-usb_id.c',
- 'udev-builtin.c',
'udev-config.c',
'udev-ctrl.c',
'udev-dump.c',
diff --git a/src/udev/udev-builtin-factory_reset.c b/src/udev/udev-builtin-factory_reset.c
new file mode 100644
index 0000000000..ffa0674772
--- /dev/null
+++ b/src/udev/udev-builtin-factory_reset.c
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "device-util.h"
+#include "factory-reset.h"
+#include "udev-builtin.h"
+
+/* Sometimes it is relevant in udev rules to know whether factory reset is currently in effect or not. Report
+ * the current state at moment of probing as a udev property. This can be used to create certain device node
+ * symlinks only once factory reset is complete, or even mark whole devices as SYSTEMD_READY=0 as long as
+ * factory reset is still ongoing. */
+
+static int builtin_factory_reset(UdevEvent *event, int argc, char *argv[]) {
+ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
+
+ if (argc != 2 || !streq(argv[1], "status"))
+ return log_device_warning_errno(
+ dev, SYNTHETIC_ERRNO(EINVAL), "%s: expected: status", argv[0]);
+
+ /* Report factory reset mode at the moment of probing a device. */
+ FactoryResetMode f = factory_reset_mode();
+ if (f < 0) {
+ log_device_debug_errno(dev, f, "Unable to detect factory reset mode, ignoring: %m");
+ return 0;
+ }
+
+ return udev_builtin_add_property(event, "ID_FACTORY_RESET", factory_reset_mode_to_string(f));
+}
+
+const UdevBuiltin udev_builtin_factory_reset = {
+ .name = "factory_reset",
+ .cmd = builtin_factory_reset,
+ .help = "Factory Reset Mode",
+ .run_once = true,
+};
diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c
index 749589463d..fd4b23c1cc 100644
--- a/src/udev/udev-builtin.c
+++ b/src/udev/udev-builtin.c
@@ -15,6 +15,7 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = {
[UDEV_BUILTIN_BLKID] = &udev_builtin_blkid,
#endif
[UDEV_BUILTIN_BTRFS] = &udev_builtin_btrfs,
+ [UDEV_BUILTIN_FACTORY_RESET] = &udev_builtin_factory_reset,
[UDEV_BUILTIN_HWDB] = &udev_builtin_hwdb,
[UDEV_BUILTIN_INPUT_ID] = &udev_builtin_input_id,
[UDEV_BUILTIN_KEYBOARD] = &udev_builtin_keyboard,
diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h
index 826308c970..f38f8bd93e 100644
--- a/src/udev/udev-builtin.h
+++ b/src/udev/udev-builtin.h
@@ -37,6 +37,7 @@ typedef struct UdevBuiltin {
extern const UdevBuiltin udev_builtin_blkid;
#endif
extern const UdevBuiltin udev_builtin_btrfs;
+extern const UdevBuiltin udev_builtin_factory_reset;
extern const UdevBuiltin udev_builtin_hwdb;
extern const UdevBuiltin udev_builtin_input_id;
extern const UdevBuiltin udev_builtin_keyboard;
diff --git a/src/udev/udev-def.h b/src/udev/udev-def.h
index c157c487cf..4064b25f57 100644
--- a/src/udev/udev-def.h
+++ b/src/udev/udev-def.h
@@ -40,6 +40,7 @@ typedef enum UdevBuiltinCommand {
UDEV_BUILTIN_BLKID,
#endif
UDEV_BUILTIN_BTRFS,
+ UDEV_BUILTIN_FACTORY_RESET,
UDEV_BUILTIN_HWDB,
UDEV_BUILTIN_INPUT_ID,
UDEV_BUILTIN_KEYBOARD,
diff --git a/units/factory-reset-now.target b/units/factory-reset-now.target
new file mode 100644
index 0000000000..6415cc1232
--- /dev/null
+++ b/units/factory-reset-now.target
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Factory Reset Execution
+Documentation=man:systemd.special(7)
+Wants=systemd-factory-reset-complete.service
diff --git a/units/factory-reset.target b/units/factory-reset.target
index d2c35ee031..68d505c877 100644
--- a/units/factory-reset.target
+++ b/units/factory-reset.target
@@ -8,5 +8,7 @@
# (at your option) any later version.
[Unit]
-Description=Factory Reset
+Description=Factory Reset Initiation
Documentation=man:systemd.special(7)
+Wants=systemd-factory-reset-reboot.service
+Before=systemd-factory-reset-reboot.service
diff --git a/units/meson.build b/units/meson.build
index bd7f5a0724..ae13f85ade 100644
--- a/units/meson.build
+++ b/units/meson.build
@@ -37,6 +37,7 @@ units = [
{ 'file' : 'emergency.target' },
{ 'file' : 'exit.target' },
{ 'file' : 'factory-reset.target' },
+ { 'file' : 'factory-reset-now.target' },
{ 'file' : 'final.target' },
{ 'file' : 'first-boot-complete.target' },
{ 'file' : 'getty-pre.target' },
@@ -322,6 +323,19 @@ units = [
},
{ 'file' : 'systemd-creds@.service' },
{ 'file' : 'systemd-exit.service' },
+ {
+ 'file' : 'systemd-factory-reset@.service.in',
+ },
+ {
+ 'file' : 'systemd-factory-reset.socket',
+ 'symlinks' : ['sockets.target.wants/'],
+ },
+ { 'file' : 'systemd-factory-reset-complete.service.in' },
+ { 'file' : 'systemd-factory-reset-reboot.service' },
+ {
+ 'file' : 'systemd-factory-reset-request.service.in',
+ 'symlinks' : ['factory-reset.target.wants/'],
+ },
{
'file' : 'systemd-firstboot.service',
'conditions' : ['ENABLE_FIRSTBOOT'],
@@ -557,6 +571,10 @@ units = [
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
'symlinks' : ['sysinit.target.wants/'],
},
+ {
+ 'file' : 'systemd-tpm2-clear.service.in',
+ 'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
+ },
{
'file' : 'systemd-tpm2-setup.service.in',
'conditions' : ['ENABLE_BOOTLOADER', 'HAVE_OPENSSL', 'HAVE_TPM2'],
diff --git a/units/systemd-factory-reset-complete.service.in b/units/systemd-factory-reset-complete.service.in
new file mode 100644
index 0000000000..337b99d3d4
--- /dev/null
+++ b/units/systemd-factory-reset-complete.service.in
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Mark the Factory Reset as Completed
+Documentation=man:systemd-factory-reset-complete.service(8)
+DefaultDependencies=no
+Requires=factory-reset-now.target
+After=factory-reset-now.target
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-factory-reset complete --retrigger
diff --git a/units/systemd-factory-reset-reboot.service b/units/systemd-factory-reset-reboot.service
new file mode 100644
index 0000000000..6928cafe83
--- /dev/null
+++ b/units/systemd-factory-reset-reboot.service
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Reboot to Execute Factory Reset
+Documentation=man:systemd.special(7)
+DefaultDependencies=no
+After=factory-reset.target
+Conflicts=shutdown.target
+Before=shutdown.target
+SuccessAction=reboot
diff --git a/units/systemd-factory-reset-request.service.in b/units/systemd-factory-reset-request.service.in
new file mode 100644
index 0000000000..bd12e625db
--- /dev/null
+++ b/units/systemd-factory-reset-request.service.in
@@ -0,0 +1,22 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Request Factory Reset on Next Boot
+Documentation=man:systemd-factory-reset-request.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=systemd-pcrphase-factory-reset.service
+Before=factory-reset.target shutdown.target
+ConditionFirmware=uefi
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-factory-reset request
diff --git a/units/systemd-factory-reset.socket b/units/systemd-factory-reset.socket
new file mode 100644
index 0000000000..d833ffdb99
--- /dev/null
+++ b/units/systemd-factory-reset.socket
@@ -0,0 +1,24 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Factory Reset Management
+Documentation=man:systemd-factory-reset.service(8)
+DefaultDependencies=no
+Before=sockets.target
+
+[Socket]
+ListenStream=/run/systemd/io.systemd.FactoryReset
+FileDescriptorName=varlink
+SocketMode=0666
+Accept=yes
+MaxConnectionsPerSource=16
+
+[Install]
+WantedBy=sockets.target
diff --git a/units/systemd-factory-reset@.service.in b/units/systemd-factory-reset@.service.in
new file mode 100644
index 0000000000..923ea04251
--- /dev/null
+++ b/units/systemd-factory-reset@.service.in
@@ -0,0 +1,18 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Factory Reset Management (Varlink)
+Documentation=man:systemd-factory-reset@.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+Before=shutdown.target
+
+[Service]
+ExecStart=-{{LIBEXECDIR}}/systemd-factory-reset
diff --git a/units/systemd-repart.service b/units/systemd-repart.service
index 1f7e2a612a..85a2a9b871 100644
--- a/units/systemd-repart.service
+++ b/units/systemd-repart.service
@@ -22,7 +22,7 @@ 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 systemd-tpm2-setup-early.service
-Before=initrd-root-fs.target
+Before=initrd-root-fs.target factory-reset-now.target
Conflicts=shutdown.target initrd-switch-root.target
Before=shutdown.target initrd-switch-root.target
diff --git a/units/systemd-tpm2-clear.service.in b/units/systemd-tpm2-clear.service.in
new file mode 100644
index 0000000000..a47d99ac8e
--- /dev/null
+++ b/units/systemd-tpm2-clear.service.in
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: LGPL-2.1-or-later
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Issue TPM Clear Request
+Documentation=man:systemd-tpm2-clear.service(8)
+DefaultDependencies=no
+Conflicts=shutdown.target
+After=tpm2.target systemd-pcrphase-factory-reset.service
+Before=factory-reset.target shutdown.target
+
+# Note all systems that have a TPM implement the "Physical Presence Interface" (PPI)
+ConditionPathExists=/sys/class/tpm/tpm0/ppi/request
+
+# Only do this if we can be reasonably sure people accept our TPM use, which we
+# derive here from the fact that UKIs are used. Because if they do they are OK
+# with our SRK initialization and our PCR measurements, and hence should also
+# be OK with our TPM resets.
+ConditionSecurity=measured-uki
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart={{LIBEXECDIR}}/systemd-tpm2-clear --graceful
+
+[Install]
+WantedBy=factory-reset.target
diff --git a/units/tpm2.target b/units/tpm2.target
index ba51d5740e..86f596fef8 100644
--- a/units/tpm2.target
+++ b/units/tpm2.target
@@ -12,5 +12,5 @@ Description=Trusted Platform Module
Documentation=man:systemd.special(7)
# Make this a synchronization point on the first TPM device found
-After=dev-tpmrm0.device
-Wants=dev-tpmrm0.device
+After=dev-tpmrm0.device dev-tpm0.device
+Wants=dev-tpmrm0.device dev-tpm0.device