Merge pull request #27687 from keszybz/boot-efi-choices

gpt-auto-generator: rework/simplify logic for picking /efi or /boot
This commit is contained in:
Mike Yuan
2023-05-30 15:21:42 +08:00
committed by GitHub
3 changed files with 191 additions and 127 deletions
+34 -32
View File
@@ -30,25 +30,28 @@
<title>Description</title>
<para><filename>systemd-gpt-auto-generator</filename> is a unit generator that automatically discovers
root, <filename>/home/</filename>, <filename>/srv/</filename>, <filename>/var/</filename>,
<filename>/var/tmp/</filename>, the EFI System Partition, the Extended Boot Loader Partition and swap
the root partition, <filename>/home/</filename>, <filename>/srv/</filename>, <filename>/var/</filename>,
<filename>/var/tmp/</filename>, the EFI System Partition, the Extended Boot Loader Partition, and swap
partitions and creates mount and swap units for them, based on the partition type GUIDs of GUID partition
tables (GPT), see <ulink url="https://uefi.org/specifications">UEFI Specification</ulink>, chapter 5. It
implements the <ulink url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable Partitions
Specification</ulink>. Note that this generator has no effect on non-GPT systems, and on specific mount
points that are directories already containing files. Also, on systems where the units are explicitly
configured (for example, listed in <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>), the
units this generator creates are overridden, but additional implicit dependencies might be
created.</para>
tables (GPT). See <ulink url="https://uefi.org/specifications">UEFI Specification</ulink>, chapter 5 for
more details. It implements the <ulink
url="https://uapi-group.org/specifications/specs/discoverable_partitions_specification">Discoverable
Partitions Specification</ulink>.</para>
<para>Note that this generator has no effect on non-GPT systems. It will also not create mount point
configuration for directories which already contain files or if the mount point is explicitly configured
in <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>. If
the units this generator creates are overridden, for example by units in directories with higher
precedence, drop-ins and additional dependencies created by this generator might still be used.</para>
<para>This generator will only look for the root partition on the same physical disk where the EFI System
Partition (ESP) is located. Note that support from the boot loader is required: the EFI variable
<varname>LoaderDevicePartUUID</varname> of the <constant>4a67b082-0a4c-41cf-b6c7-440b29bb8c4f</constant>
vendor UUID is used to determine from which partition, and hence the disk from which the system was
booted. If the boot loader does not set this variable, this generator will not be able to autodetect the
root partition. See the <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader
Interface</ulink> for details.</para>
vendor UUID is used to determine from which partition, and hence the disk, from which the system was
booted. If the boot loader does not set this variable, this generator will not be able to detect the root
partition. See the <ulink url="https://systemd.io/BOOT_LOADER_INTERFACE">Boot Loader Interface</ulink>
for details.</para>
<para>Similarly, this generator will only look for the other partitions on the same physical disk as the
root partition. In this case, boot loader support is not required. These partitions will not be searched
@@ -190,9 +193,10 @@
listed in <filename>/etc/crypttab</filename> with a different device mapper device name.</para>
<para>When systemd is running in the initrd the <filename>/</filename> partition may be encrypted in LUKS
format as well. In this case, a device mapper device is set up under the name <filename>/dev/mapper/root</filename>,
and a <filename>sysroot.mount</filename> is set up that mounts the device under <filename>/sysroot</filename>.
For more information, see <citerefentry><refentrytitle>bootup</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
format as well. In this case, a device mapper device is set up under the name
<filename>/dev/mapper/root</filename>, and a <filename>sysroot.mount</filename> is set up that mounts the
device under <filename>/sysroot</filename>. For more information, see
<citerefentry><refentrytitle>bootup</refentrytitle><manvolnum>7</manvolnum></citerefentry>.
</para>
<para>The root partition can be specified by symlinking <filename>/run/systemd/volatile-root</filename>
@@ -200,22 +204,20 @@
replaced by some form of volatile file system (overlayfs).
</para>
<para>Mount and automount units for the EFI System Partition (ESP) are generated on EFI systems. The ESP
is mounted to <filename>/boot/</filename> (except if an Extended Boot Loader partition exists, see
below), unless a mount point directory <filename>/efi/</filename> exists, in which case it is mounted
there. Since this generator creates an automount unit, the mount will only be activated on-demand, when
accessed. On systems where <filename>/boot/</filename> (or <filename>/efi/</filename> if it exists) is an
explicitly configured mount (for example, listed in <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>) or where
the <filename>/boot/</filename> (or <filename>/efi/</filename>) mount point is non-empty, no mount units
are generated.</para>
<para>Mount and automount units for the EFI System Partition (ESP) and Extended Boot Loader Partition
(XBOOTLDR) are generated on EFI systems. If the disk contains an XBOOTLDR partition, as defined in the
<ulink url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader
Specification</ulink>, it is made available at <filename>/boot/</filename>. This generator creates an
automount unit; the mount will only be activated on-demand when accessed. The mount point will be created
if necessary.</para>
<para>If the disk contains an Extended Boot Loader partition, as defined in the <ulink
url="https://uapi-group.org/specifications/specs/boot_loader_specification">Boot Loader Specification</ulink>, it is made
available at <filename>/boot/</filename> (by means of an automount point, similar to the ESP, see
above). If both an EFI System Partition and an Extended Boot Loader partition exist the latter is
preferably mounted to <filename>/boot/</filename>. Make sure to create both <filename>/efi/</filename>
and <filename>/boot/</filename> to ensure both partitions are mounted.</para>
<para>The ESP is mounted to <filename>/boot/</filename> if that directory exists and is not used for
XBOOTLDR, and otherwise to <filename>/efi/</filename>. Same as for <filename>/boot/</filename>, an
automount unit is used. The mount point will be created if necessary.</para>
<para>No configuration is created for mount points that are configured in <citerefentry
project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry> or when
the target directory contains files.</para>
<para>When using this generator in conjunction with btrfs file
systems, make sure to set the correct default subvolumes on them,
+107 -63
View File
@@ -25,7 +25,6 @@
#include "gpt.h"
#include "image-policy.h"
#include "initrd-util.h"
#include "mkdir.h"
#include "mountpoint-util.h"
#include "parse-util.h"
#include "path-util.h"
@@ -109,7 +108,7 @@ static int add_cryptsetup(
r = efi_stub_measured(LOG_WARNING);
if (r == 0)
log_debug("Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id);
log_debug("Will not measure volume key of volume '%s', not booted via systemd-stub with measurements enabled.", id);
else if (r > 0) {
if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes"))
return log_oom();
@@ -128,8 +127,7 @@ static int add_cryptsetup(
if (r < 0)
return r;
const char *dmname;
dmname = strjoina("dev-mapper-", e, ".device");
const char *dmname = strjoina("dev-mapper-", e, ".device");
if (require) {
r = generator_add_symlink(arg_dest, "cryptsetup.target", "requires", n);
@@ -160,7 +158,8 @@ static int add_cryptsetup(
return 0;
#else
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Partition is encrypted, but the project was compiled without libcryptsetup support");
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Partition is encrypted, but systemd-gpt-auto-generator was compiled without libcryptsetup support");
#endif
}
@@ -280,12 +279,13 @@ static int add_mount(
static int path_is_busy(const char *where) {
int r;
assert(where);
/* already a mountpoint; generators run during reload */
r = path_is_mount_point(where, NULL, AT_SYMLINK_FOLLOW);
if (r > 0)
return false;
/* the directory might not exist on a stateless system */
/* The directory will be created by the mount or automount unit when it is started. */
if (r == -ENOENT)
return false;
@@ -294,13 +294,17 @@ static int path_is_busy(const char *where) {
/* not a mountpoint but it contains files */
r = dir_is_empty(where, /* ignore_hidden_or_backup= */ false);
if (r < 0)
if (r == -ENOTDIR) {
log_debug("\"%s\" is not a directory, ignoring.", where);
return true;
} else if (r < 0)
return log_warning_errno(r, "Cannot check if \"%s\" is empty: %m", where);
if (r > 0)
return false;
else if (r == 0) {
log_debug("\"%s\" already populated, ignoring.", where);
return true;
}
log_debug("\"%s\" already populated, ignoring.", where);
return true;
return false;
}
static int add_partition_mount(
@@ -471,6 +475,45 @@ static int add_automount(
return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit);
}
static int slash_boot_in_fstab(void) {
static int cache = -1;
if (cache >= 0)
return cache;
cache = fstab_is_mount_point("/boot");
if (cache < 0)
return log_error_errno(cache, "Failed to parse fstab: %m");
return cache;
}
static int slash_efi_in_fstab(void) {
static int cache = -1;
if (cache >= 0)
return cache;
cache = fstab_is_mount_point("/efi");
if (cache < 0)
return log_error_errno(cache, "Failed to parse fstab: %m");
return cache;
}
static bool slash_boot_exists(void) {
static int cache = -1;
if (cache >= 0)
return cache;
if (access("/boot", F_OK) >= 0)
return (cache = true);
if (errno != ENOENT)
log_error_errno(errno, "Failed to determine whether /boot/ exists, assuming no: %m");
else
log_debug_errno(errno, "/boot/: %m");
return (cache = false);
}
static int add_partition_xbootldr(DissectedPartition *p) {
_cleanup_free_ char *options = NULL;
int r;
@@ -482,11 +525,11 @@ static int add_partition_xbootldr(DissectedPartition *p) {
return 0;
}
r = fstab_is_mount_point("/boot");
r = slash_boot_in_fstab();
if (r < 0)
return log_error_errno(r, "Failed to parse fstab: %m");
return r;
if (r > 0) {
log_debug("/boot specified in fstab, ignoring XBOOTLDR partition.");
log_debug("/boot/ specified in fstab, ignoring XBOOTLDR partition.");
return 0;
}
@@ -504,17 +547,18 @@ static int add_partition_xbootldr(DissectedPartition *p) {
&options,
/* ret_ms_flags= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to determine default mount options for Boot Loader Partition: %m");
return log_error_errno(r, "Failed to determine default mount options for /boot/: %m");
return add_automount("boot",
p->node,
"/boot",
p->fstype,
/* rw= */ true,
/* growfs= */ false,
options,
"Boot Loader Partition",
120 * USEC_PER_SEC);
return add_automount(
"boot",
p->node,
"/boot",
p->fstype,
/* rw= */ true,
/* growfs= */ false,
options,
"Boot Loader Partition",
120 * USEC_PER_SEC);
}
#if ENABLE_EFI
@@ -530,43 +574,42 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) {
return 0;
}
/* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice, but
* only if there's no explicit XBOOTLDR partition around. */
if (access("/efi", F_OK) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to determine whether /efi exists: %m");
/* Use /boot as fallback, but only if there's no XBOOTLDR partition and /boot exists */
if (!has_xbootldr) {
if (access("/boot", F_OK) < 0) {
if (errno != ENOENT)
return log_error_errno(errno, "Failed to determine whether /boot exists: %m");
} else {
/* If /boot/ is present, unused, and empty, we'll take that.
* Otherwise, if /efi/ is unused and empty (or missing), we'll take that.
* Otherwise, we do nothing.
*/
if (!has_xbootldr && slash_boot_exists()) {
r = slash_boot_in_fstab();
if (r < 0)
return r;
if (r == 0) {
r = path_is_busy("/boot");
if (r < 0)
return r;
if (r == 0) {
esp_path = "/boot";
id = "boot";
}
}
}
if (!esp_path)
if (!esp_path) {
r = slash_efi_in_fstab();
if (r < 0)
return r;
if (r > 0)
return 0;
r = path_is_busy("/efi");
if (r < 0)
return r;
if (r > 0)
return 0;
esp_path = "/efi";
if (!id)
id = "efi";
/* We create an .automount which is not overridden by the .mount from the fstab generator. */
r = fstab_is_mount_point(esp_path);
if (r < 0)
return log_error_errno(r, "Failed to parse fstab: %m");
if (r > 0) {
log_debug("%s specified in fstab, ignoring.", esp_path);
return 0;
}
r = path_is_busy(esp_path);
if (r < 0)
return r;
if (r > 0)
return 0;
if (is_efi_boot()) {
sd_id128_t loader_uuid;
@@ -595,17 +638,18 @@ static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) {
&options,
/* ret_ms_flags= */ NULL);
if (r < 0)
return log_error_errno(r, "Failed to determine default mount options for EFI System Partition: %m");
return log_error_errno(r, "Failed to determine default mount options for %s: %m", esp_path);
return add_automount(id,
p->node,
esp_path,
p->fstype,
/* rw= */ true,
/* growfs= */ false,
options,
"EFI System Partition Automount",
120 * USEC_PER_SEC);
return add_automount(
id,
p->node,
esp_path,
p->fstype,
/* rw= */ true,
/* growfs= */ false,
options,
"EFI System Partition Automount",
120 * USEC_PER_SEC);
}
#else
static int add_partition_esp(DissectedPartition *p, bool has_xbootldr) {
+50 -32
View File
@@ -2019,6 +2019,27 @@ static int mount_root_tmpfs(const char *where, uid_t uid_shift, DissectImageFlag
return 1;
}
static int mount_point_is_available(const char *where, const char *path, bool missing_ok) {
_cleanup_free_ char *p = NULL;
int r;
/* Check whether <path> is suitable as a mountpoint, i.e. is an empty directory
* or does not exist at all (when missing_ok). */
r = chase(path, where, CHASE_PREFIX_ROOT, &p, NULL);
if (r == -ENOENT)
return missing_ok;
if (r < 0)
return log_debug_errno(r, "Failed to chase \"%s\": %m", path);
r = dir_is_empty(p, /* ignore_hidden_or_backup= */ false);
if (r == -ENOTDIR)
return false;
if (r < 0)
return log_debug_errno(r, "Failed to check directory \"%s\": %m", p);
return true;
}
int dissected_image_mount(
DissectedImage *m,
const char *where,
@@ -2026,7 +2047,7 @@ int dissected_image_mount(
uid_t uid_range,
DissectImageFlags flags) {
int r, xbootldr_mounted;
int r;
assert(m);
assert(where);
@@ -2110,45 +2131,42 @@ int dissected_image_mount(
if (r < 0)
return r;
xbootldr_mounted = mount_partition(PARTITION_XBOOTLDR, m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, uid_range, flags);
if (xbootldr_mounted < 0)
return xbootldr_mounted;
int slash_boot_is_available;
r = slash_boot_is_available = mount_point_is_available(where, "/boot", /* missing_ok = */ true);
if (r < 0)
return r;
if (r > 0) {
r = mount_partition(PARTITION_XBOOTLDR, m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, uid_range, flags);
if (r < 0)
return r;
slash_boot_is_available = !r;
}
if (m->partitions[PARTITION_ESP].found) {
int esp_done = false;
const char *esp_path = NULL;
/* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it
* exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */
/* Mount the ESP to /boot/ if it exists and is empty and we didn't already mount the XBOOTLDR
* partition into it. Otherwise, use /efi instead, but only if it exists and is empty. */
r = chase("/efi", where, CHASE_PREFIX_ROOT, NULL, NULL);
if (r < 0) {
if (r != -ENOENT)
if (slash_boot_is_available) {
r = mount_point_is_available(where, "/boot", /* missing_ok = */ false);
if (r < 0)
return r;
/* /efi doesn't exist. Let's see if /boot is suitable then */
if (!xbootldr_mounted) {
_cleanup_free_ char *p = NULL;
r = chase("/boot", where, CHASE_PREFIX_ROOT, &p, NULL);
if (r < 0) {
if (r != -ENOENT)
return r;
} else if (dir_is_empty(p, /* ignore_hidden_or_backup= */ false) > 0) {
/* It exists and is an empty directory. Let's mount the ESP there. */
r = mount_partition(PARTITION_ESP, m->partitions + PARTITION_ESP, where, "/boot", uid_shift, uid_range, flags);
if (r < 0)
return r;
esp_done = true;
}
}
if (r > 0)
esp_path = "/boot";
}
if (!esp_done) {
/* OK, let's mount the ESP now to /efi (possibly creating the dir if missing) */
if (!esp_path) {
r = mount_point_is_available(where, "/efi", /* missing_ok = */ true);
if (r < 0)
return r;
if (r > 0)
esp_path = "/efi";
}
r = mount_partition(PARTITION_ESP, m->partitions + PARTITION_ESP, where, "/efi", uid_shift, uid_range, flags);
if (esp_path) {
/* OK, let's mount the ESP now (possibly creating the dir if missing) */
r = mount_partition(PARTITION_ESP, m->partitions + PARTITION_ESP, where, esp_path, uid_shift, uid_range, flags);
if (r < 0)
return r;
}