[systemd-devel] [PATCH v3] Add reboot to EFI support
Jan Janssen
medhefgo at web.de
Fri Apr 3 03:40:45 PDT 2015
---
Hi,
Changes in v3:
- call the feature reboot to firmware everywhere
- make the login interface a property and methods to change it and
don't couple it with a reboot action
- changed/added policykit action defaulting to auth_admin_keep. Please
change this if something else is desired.
The policy kit behavior feels weird to me: if I call bus_verify_polkit()
with interactive = false, I still get a password prompt for CanRebootToFirmware.
Is this a bug or am I doing something wrong here? Do I need to make a separate
get policy (with no auth_admin_keep) for this to work?
Jan
man/systemctl.xml | 10 ++++
shell-completion/bash/systemctl.in | 2 +-
shell-completion/zsh/_systemctl.in | 1 +
src/login/logind-dbus.c | 96 ++++++++++++++++++++++++++++++
src/login/org.freedesktop.login1.conf | 8 +++
src/login/org.freedesktop.login1.policy.in | 10 ++++
src/shared/efivars.c | 73 +++++++++++++++++++++++
src/shared/efivars.h | 3 +
src/systemctl/systemctl.c | 18 ++++++
9 files changed, 220 insertions(+), 1 deletion(-)
diff --git a/man/systemctl.xml b/man/systemctl.xml
index 50e6bc9..b77f4ab 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -527,6 +527,16 @@
</varlistentry>
<varlistentry>
+ <term><option>--firmware-setup</option></term>
+
+ <listitem>
+ <para>Indicate to the firmware to boot into setup mode. Note
+ that this is currently only supported on some EFI systems and
+ only if it was booted in EFI mode.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><option>--plain</option></term>
<listitem>
diff --git a/shell-completion/bash/systemctl.in b/shell-completion/bash/systemctl.in
index 8063316..773a59d 100644
--- a/shell-completion/bash/systemctl.in
+++ b/shell-completion/bash/systemctl.in
@@ -92,7 +92,7 @@ _systemctl () {
local -A OPTS=(
[STANDALONE]='--all -a --reverse --after --before --defaults --fail --ignore-dependencies --failed --force -f --full -l --global
--help -h --no-ask-password --no-block --no-legend --no-pager --no-reload --no-wall
- --quiet -q --privileged -P --system --user --version --runtime --recursive -r'
+ --quiet -q --privileged -P --system --user --version --runtime --recursive -r --firmware-setup'
[ARG]='--host -H --kill-who --property -p --signal -s --type -t --state --root'
)
diff --git a/shell-completion/zsh/_systemctl.in b/shell-completion/zsh/_systemctl.in
index 7f2d5ac..3bbfb6f 100644
--- a/shell-completion/zsh/_systemctl.in
+++ b/shell-completion/zsh/_systemctl.in
@@ -384,5 +384,6 @@ _arguments -s \
{-P,--privileged}'[Acquire privileges before execution]' \
{-n+,--lines=}'[Journal entries to show]:number of entries' \
{-o+,--output=}'[Change journal output mode]:modes:_sd_outputmodes' \
+ '--firmware-setup[Tell the firmware to show the setup menu on next boot]' \
'--plain[When used with list-dependencies, print output as a list]' \
'*::systemctl command:_systemctl_command'
diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c
index a3d49ef..cea99fc 100644
--- a/src/login/logind-dbus.c
+++ b/src/login/logind-dbus.c
@@ -38,6 +38,7 @@
#include "bus-common-errors.h"
#include "udev-util.h"
#include "selinux-util.h"
+#include "efivars.h"
#include "logind.h"
int manager_get_session_from_creds(Manager *m, sd_bus_message *message, const char *name, sd_bus_error *error, Session **ret) {
@@ -1850,6 +1851,98 @@ static int method_can_hybrid_sleep(sd_bus *bus, sd_bus_message *message, void *u
error);
}
+static int property_get_reboot_to_firmware(
+ sd_bus *bus,
+ const char *path,
+ const char *interface,
+ const char *property,
+ sd_bus_message *reply,
+ void *userdata,
+ sd_bus_error *error) {
+ int r;
+
+ assert(bus);
+ assert(reply);
+ assert(userdata);
+
+ r = efi_get_reboot_to_fw();
+ if (r < 0 && r != -EOPNOTSUPP)
+ return r;
+
+ return sd_bus_message_append(reply, "b", r > 0);
+}
+
+static int method_set_reboot_to_firmware(sd_bus *bus,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+ int b, r;
+ int interactive;
+ Manager *m = userdata;
+
+ assert(bus);
+ assert(message);
+ assert(m);
+
+ r = sd_bus_message_read(message, "bb", &b, &interactive);
+ if (r < 0)
+ return r;
+
+ r = bus_verify_polkit_async(message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-firmware",
+ interactive,
+ UID_INVALID,
+ &m->polkit_registry,
+ error);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */
+
+ r = efi_set_reboot_to_fw(b);
+ if (r < 0)
+ return r;
+
+ return sd_bus_reply_method_return(message, NULL);
+}
+
+static int method_can_reboot_to_firmware(sd_bus *bus,
+ sd_bus_message *message,
+ void *userdata,
+ sd_bus_error *error) {
+ int r;
+ bool challenge;
+ const char *result;
+ Manager *m = userdata;
+
+ assert(bus);
+ assert(message);
+ assert(m);
+
+ if (!is_efi_reboot_to_fw_supported())
+ return sd_bus_reply_method_return(message, "s", "na");
+
+ r = bus_verify_polkit(message,
+ CAP_SYS_ADMIN,
+ "org.freedesktop.login1.set-reboot-to-firmware",
+ false,
+ UID_INVALID,
+ &challenge,
+ error);
+ if (r < 0)
+ return r;
+
+ if (r > 0)
+ result = "yes";
+ else if (challenge)
+ result = "challenge";
+ else
+ result = "no";
+
+ return sd_bus_reply_method_return(message, "s", result);
+}
+
static int method_inhibit(sd_bus *bus, sd_bus_message *message, void *userdata, sd_bus_error *error) {
_cleanup_bus_creds_unref_ sd_bus_creds *creds = NULL;
const char *who, *why, *what, *mode;
@@ -1970,6 +2063,7 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_PROPERTY("KillOnlyUsers", "as", NULL, offsetof(Manager, kill_only_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KillExcludeUsers", "as", NULL, offsetof(Manager, kill_exclude_users), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KillUserProcesses", "b", NULL, offsetof(Manager, kill_user_processes), SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RebootToFirmware", "b", property_get_reboot_to_firmware, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("IdleHint", "b", property_get_idle_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IdleSinceHint", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
SD_BUS_PROPERTY("IdleSinceHintMonotonic", "t", property_get_idle_since_hint, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
@@ -2023,6 +2117,8 @@ const sd_bus_vtable manager_vtable[] = {
SD_BUS_METHOD("CanHibernate", NULL, "s", method_can_hibernate, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("CanHybridSleep", NULL, "s", method_can_hybrid_sleep, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_METHOD("Inhibit", "ssss", "h", method_inhibit, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("SetRebootToFirmware", "bb", NULL, method_set_reboot_to_firmware, SD_BUS_VTABLE_UNPRIVILEGED),
+ SD_BUS_METHOD("CanRebootToFirmware", NULL, "s", method_can_reboot_to_firmware, SD_BUS_VTABLE_UNPRIVILEGED),
SD_BUS_SIGNAL("SessionNew", "so", 0),
SD_BUS_SIGNAL("SessionRemoved", "so", 0),
diff --git a/src/login/org.freedesktop.login1.conf b/src/login/org.freedesktop.login1.conf
index 1318328..589cca6 100644
--- a/src/login/org.freedesktop.login1.conf
+++ b/src/login/org.freedesktop.login1.conf
@@ -130,6 +130,14 @@
<allow send_destination="org.freedesktop.login1"
send_interface="org.freedesktop.login1.Manager"
+ send_member="CanRebootToFirmware"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
+ send_member="SetRebootToFirmware"/>
+
+ <allow send_destination="org.freedesktop.login1"
+ send_interface="org.freedesktop.login1.Manager"
send_member="AttachDevice"/>
<allow send_destination="org.freedesktop.login1"
diff --git a/src/login/org.freedesktop.login1.policy.in b/src/login/org.freedesktop.login1.policy.in
index c45491b..b2efab1 100644
--- a/src/login/org.freedesktop.login1.policy.in
+++ b/src/login/org.freedesktop.login1.policy.in
@@ -290,4 +290,14 @@
</defaults>
</action>
+ <action id="org.freedesktop.login1.set-reboot-to-firmware">
+ <_description>Allow indication to the firmware to boot to setup interface</_description>
+ <_message>Authentication is required to indicate to the firmware to boot to setup interface.</_message>
+ <defaults>
+ <allow_any>auth_admin_keep</allow_any>
+ <allow_inactive>auth_admin_keep</allow_inactive>
+ <allow_active>auth_admin_keep</allow_active>
+ </defaults>
+ </action>
+
</policyconfig>
diff --git a/src/shared/efivars.c b/src/shared/efivars.c
index 20067c6..5879116 100644
--- a/src/shared/efivars.c
+++ b/src/shared/efivars.c
@@ -25,6 +25,7 @@
#include "util.h"
#include "utf8.h"
+#include "virt.h"
#include "efivars.h"
#ifdef ENABLE_EFI
@@ -37,6 +38,7 @@
#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02
#define END_DEVICE_PATH_TYPE 0x7f
#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff
+#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001
struct boot_option {
uint32_t attr;
@@ -93,6 +95,77 @@ int is_efi_secure_boot_setup_mode(void) {
return read_flag("SetupMode");
}
+static int get_os_indications_supported(uint64_t *b) {
+ int r;
+ size_t s;
+ _cleanup_free_ void *v = NULL;
+
+ if (!is_efi_boot() || detect_container(NULL) > 0)
+ return -EOPNOTSUPP;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s);
+ if (r < 0)
+ return r;
+ else if (s != sizeof(uint64_t))
+ return -EINVAL;
+
+ *b = *(uint64_t *)v;
+ if (!(*b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI))
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int get_os_indications(uint64_t *b) {
+ int r;
+ size_t s;
+ _cleanup_free_ void *v = NULL;
+
+ r = get_os_indications_supported(b);
+ if (r < 0)
+ return r;
+
+ r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s);
+ if (r < 0)
+ return r;
+ else if (s != sizeof(uint64_t))
+ return -EINVAL;
+
+ *b = *(uint64_t *)v;
+ return 0;
+}
+
+bool is_efi_reboot_to_fw_supported(void) {
+ int r;
+ uint64_t b;
+
+ r = get_os_indications_supported(&b);
+ return r >= 0;
+}
+
+int efi_get_reboot_to_fw(void) {
+ int r;
+ uint64_t b;
+
+ r = get_os_indications(&b);
+ return r < 0 ? r : !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI);
+}
+
+int efi_set_reboot_to_fw(bool value) {
+ int r;
+ uint64_t b;
+
+ r = get_os_indications(&b);
+ if (r < 0)
+ return r;
+
+ if (value)
+ b |= EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+ else
+ b &= ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI;
+ return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b, sizeof(uint64_t));
+}
+
int efi_get_variable(
sd_id128_t vendor,
const char *name,
diff --git a/src/shared/efivars.h b/src/shared/efivars.h
index 2492893..e57934f 100644
--- a/src/shared/efivars.h
+++ b/src/shared/efivars.h
@@ -35,6 +35,9 @@
bool is_efi_boot(void);
int is_efi_secure_boot(void);
int is_efi_secure_boot_setup_mode(void);
+bool is_efi_reboot_to_fw_supported(void);
+int efi_get_reboot_to_fw(void);
+int efi_set_reboot_to_fw(bool value);
int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size);
int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size);
diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c
index 3158a38..c82618d 100644
--- a/src/systemctl/systemctl.c
+++ b/src/systemctl/systemctl.c
@@ -68,6 +68,7 @@
#include "bus-common-errors.h"
#include "mkdir.h"
#include "dropin.h"
+#include "efivars.h"
static char **arg_types = NULL;
static char **arg_states = NULL;
@@ -132,6 +133,7 @@ static char *arg_host = NULL;
static unsigned arg_lines = 10;
static OutputMode arg_output = OUTPUT_SHORT;
static bool arg_plain = false;
+static bool arg_firmware_setup = false;
static bool original_stdout_is_tty;
@@ -2928,6 +2930,15 @@ static int start_special(sd_bus *bus, char **args) {
return -EPERM;
}
+ if (arg_firmware_setup) {
+ if (!is_efi_reboot_to_fw_supported())
+ return log_error_errno(EOPNOTSUPP, "Boot to EFI setup not supported.");
+
+ r = efi_set_reboot_to_fw(true);
+ if (r < 0)
+ return log_error_errno(r, "Cannot indicate to boot to EFI setup: %m");
+ }
+
if (a == ACTION_REBOOT && args[1]) {
r = update_reboot_param_file(args[1]);
if (r < 0)
@@ -5971,6 +5982,7 @@ static void systemctl_help(void) {
" -o --output=STRING Change journal output mode (short, short-iso,\n"
" short-precise, short-monotonic, verbose,\n"
" export, json, json-pretty, json-sse, cat)\n"
+ " --firmware-setup Tell the firmware to show the setup menu on next boot\n"
" --plain Print unit dependencies as a list instead of a tree\n\n"
"Unit Commands:\n"
" list-units [PATTERN...] List loaded units\n"
@@ -6150,6 +6162,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
ARG_STATE,
ARG_JOB_MODE,
ARG_PRESET_MODE,
+ ARG_FIRMWARE_SETUP,
};
static const struct option options[] = {
@@ -6192,6 +6205,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "state", required_argument, NULL, ARG_STATE },
{ "recursive", no_argument, NULL, 'r' },
{ "preset-mode", required_argument, NULL, ARG_PRESET_MODE },
+ { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP },
{}
};
@@ -6432,6 +6446,10 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_plain = true;
break;
+ case ARG_FIRMWARE_SETUP:
+ arg_firmware_setup = true;
+ break;
+
case ARG_STATE: {
const char *word, *state;
size_t size;
--
2.3.5
More information about the systemd-devel
mailing list