From: Lennart Poettering Date: Thu, 20 Feb 2025 22:19:01 +0000 (+0100) Subject: factory-reset: revamp infrastructure X-Git-Tag: v258-rc1~1176^2~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=41d9ed93d9089f0d18da4bbade8986ba6a35349a;p=thirdparty%2Fsystemd.git factory-reset: revamp infrastructure This introduces a bunch of facilities: 1. The factory-reset.target unit that requests a factory reset is now complemented by factory-reset-now.target that executes it at next boot. 2. This latter is added to the initial transaction via the new trivial systemd-factory-reset-generator. 3. A tool systemd-factory-reset has been added to query, request, cancel, complete factory reset operations (via EFI variables). Two of these are wrapped into units that are plugged into factory-reset.target and factory-reset-now.target respectively. The tool also provides a simple Varlink API. This should make things a lot cleaner, and both be useful as explicit implementation on UEFI, and as template + hookpoints for alternative implementations on non-UEFI. --- diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 590bd425d82..8e21ecda39a 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -761,6 +761,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 19beebfc432..7edbbf7fad9 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', diff --git a/man/systemd-factory-reset-generator.xml b/man/systemd-factory-reset-generator.xml new file mode 100644 index 00000000000..314a66c7d21 --- /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 00000000000..1cd55c9accd --- /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.special.xml b/man/systemd.special.xml index a4ae3f8983f..0195bcb7fb3 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 fdad63fd298..1cdce6a868f 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/src/factory-reset/factory-reset-generator.c b/src/factory-reset/factory-reset-generator.c new file mode 100644 index 00000000000..d3c961a2a93 --- /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 00000000000..8bf57346011 --- /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 00000000000..1afac3ad64e --- /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/shared/meson.build b/src/shared/meson.build index 17b689553f9..ccbdc84974d 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -184,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 00000000000..4f68889f2e6 --- /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 00000000000..2590a38bdb5 --- /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/test/test-varlink-idl.c b/src/test/test-varlink-idl.c index 9de50641e61..d3e4a6a116c 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/units/factory-reset-now.target b/units/factory-reset-now.target new file mode 100644 index 00000000000..6415cc1232e --- /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 d2c35ee031f..68d505c877b 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 bd7f5a0724c..551c9f77c9f 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'], diff --git a/units/systemd-factory-reset-complete.service.in b/units/systemd-factory-reset-complete.service.in new file mode 100644 index 00000000000..337b99d3d42 --- /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 00000000000..6928cafe834 --- /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 00000000000..bd12e625db7 --- /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 00000000000..d833ffdb995 --- /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 00000000000..923ea04251e --- /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 1f7e2a612a7..85a2a9b8718 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