shared: add generic factory reset state apis

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.
This commit is contained in:
Lennart Poettering
2025-02-20 23:18:12 +01:00
parent 7eb1cd2f0c
commit 45623d4ad6
3 changed files with 150 additions and 0 deletions

125
src/shared/factory-reset.c Normal file
View File

@@ -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);

View File

@@ -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_;

View 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',