]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared: add generic factory reset state apis
authorLennart Poettering <lennart@poettering.net>
Thu, 20 Feb 2025 22:18:12 +0000 (23:18 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 5 Mar 2025 11:37:03 +0000 (12:37 +0100)
Let's provide a generic implementation of the systemd.factory_reset
kernel cmdline checking repart implements. Moreover add support for
leaving the factory reset state again.

This only establishes the basic APIs, it does not hook them up with
anything.

src/shared/factory-reset.c [new file with mode: 0644]
src/shared/factory-reset.h [new file with mode: 0644]
src/shared/meson.build

diff --git a/src/shared/factory-reset.c b/src/shared/factory-reset.c
new file mode 100644 (file)
index 0000000..39f4f25
--- /dev/null
@@ -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 (file)
index 0000000..49ae700
--- /dev/null
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include <errno.h>
+
+#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_;
index 62c6bcf511127f59b2ff5d7cc7188ea48075b425..17b689553f903d5afdcd6c53e5f10930a6f4a30d 100644 (file)
@@ -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',