gpt-auto-generator: automatically find the root disk of the system

When run in an initrd and no root= argument is set (or is set to
root=gpt-auto) we will automatically look for the root partition on the
same disk the EFI ESP is located on.

Since we look for swap, /home and /srv on the disk the root partition is
located on, we hence have a fully discoverable chain:

    Firmware discovers the EFI ESP partition → the initrd discovers the
    root partition → the host OS discovers swap, /home, and /srv.

Note that this requires an EFI boot loader that sets the
LoaderDevicePartUUID EFI variable, such as Gummiboot.
This commit is contained in:
Lennart Poettering 2014-03-07 04:31:26 +01:00
parent 329f7803ee
commit 73b80ec2d9
4 changed files with 224 additions and 93 deletions

View File

@ -43,7 +43,8 @@
<refnamediv>
<refname>systemd-gpt-auto-generator</refname>
<refpurpose>Generator for automatically discovering
and mounting <filename>/home</filename> and <filename>/srv</filename>, as well as
and mounting root, <filename>/home</filename> and
<filename>/srv</filename> partitions, as well as
discovering and enabling swap partitions, based on GPT
partition type GUIDs.</refpurpose>
</refnamediv>
@ -56,7 +57,7 @@
<title>Description</title>
<para><filename>systemd-gpt-auto-generator</filename>
is a unit generator that automatically discovers
is a unit generator that automatically discovers root,
<filename>/home</filename>, <filename>/srv</filename>
and swap partitions and creates mount and swap units
for them, based on the the partition type GUIDs of
@ -67,11 +68,14 @@
<citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>),
or where the mount points are non-empty.</para>
<para>This generator will only look for partitions on
the same physical disk the root file system is stored
on. This generator has no effect on systems where the
root file system is distributed on multiple disks, for
example via btrfs RAID.</para>
<para>This generator will only look for root
partitions on the same physical disk the EFI System
Partition (ESP) is located on. It will only look for
the other partitions on the same physical disk the
root file system is located on. These partitions will
not be search on systems where the root file system is
distributed on multiple disks, for example via btrfs
RAID.</para>
<para><filename>systemd-gpt-auto-generator</filename>
is useful for centralizing file system configuration
@ -87,30 +91,40 @@
<title>Partition Type GUIDs</title>
<tgroup cols='3' align='left' colsep='1' rowsep='1'>
<colspec colname="guid" />
<colspec colname="location" />
<colspec colname="name" />
<colspec colname="explanation" />
<thead>
<row>
<entry>Partition Type GUID</entry>
<entry>Location</entry>
<entry>Name</entry>
<entry>Explanation</entry>
</row>
</thead>
<tbody>
<row>
<entry>0657fd6d-a4ab-43c4-84e50933c84b4f4f</entry>
<entry>Swap</entry>
<entry>All swap partitions are enabled.</entry>
<entry>44479540-f297-41b2-9af7d131d5f0458a</entry>
<entry><filename>Root Partition (x86)</filename></entry>
<entry>On 32bit x86 systems the first x86 root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
</row>
<row>
<entry>4f68bce3-e8cd-4db1-96e7fbcaf984b709</entry>
<entry><filename>Root Partition (x86-64)</filename></entry>
<entry>On 64bit x86 systems the first x86-64 root partition on the disk the EFI ESP is located on is mounted to the root directory <filename>/</filename>.</entry>
</row>
<row>
<entry>933ac7e1-2eb4-4f13-b8440e14e2aef915</entry>
<entry><filename>/home</filename></entry>
<entry>The first home partition on the disk is mounted to <filename>/home</filename>.</entry>
<entry>Home Partition</entry>
<entry>The first home partition on the disk the root partition is located on is mounted to <filename>/home</filename>.</entry>
</row>
<row>
<entry>3b8f8425-20e0-4f3b-907f1a25a76f98e8</entry>
<entry><filename>/srv</filename></entry>
<entry>The first server data partition on the disk is mounted to <filename>/srv</filename>.</entry>
<entry>Server Data Partition</entry>
<entry>The first server data partition on the disk the root partition is located on is mounted to <filename>/srv</filename>.</entry>
</row>
<row>
<entry>0657fd6d-a4ab-43c4-84e50933c84b4f4f</entry>
<entry>Swap</entry>
<entry>All swap partitions located on the disk the root partition is located on are enabled.</entry>
</row>
</tbody>
</tgroup>
@ -128,8 +142,13 @@
<para>Also note that
<citerefentry><refentrytitle>systemd-efi-boot-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>
will mount the EFI System Partition to
<filename>/boot</filename> is not otherwise mounted.</para>
will mount the EFI System Partition (ESP) to
<filename>/boot</filename> if not otherwise mounted.</para>
<para>When using this generator in conjunction with
btrfs file systems make sure to set the correct
default subvolumes on them, using <command>btrfs
subvolume set-default</command>.</para>
<para><filename>systemd-gpt-auto-generator</filename>
implements the <ulink
@ -147,7 +166,8 @@
<citerefentry><refentrytitle>systemd-efi-boot-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>systemd-cryptsetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>cryptsetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
<citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>
<citerefentry><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<citerefentry><refentrytitle>btrfs</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>

View File

@ -374,6 +374,7 @@ static int parse_proc_cmdline_item(const char *key, const char *value) {
log_warning("Environment variable name '%s' is not valid. Ignoring.", value);
} else if (!streq(key, "systemd.restore_state") &&
!streq(key, "systemd.gpt_auto") &&
(startswith(key, "systemd.") || startswith(key, "rd.systemd."))) {
const char *c;

View File

@ -43,8 +43,12 @@
#include "generator.h"
#include "gpt.h"
#include "fileio.h"
#include "efivars.h"
static const char *arg_dest = "/tmp";
static bool arg_enabled = true;
static bool arg_root_enabled = true;
static bool arg_root_rw = false;
DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe);
#define _cleanup_blkid_freep_probe_ _cleanup_(blkid_free_probep)
@ -287,7 +291,15 @@ static int add_cryptsetup(const char *id, const char *what, char **device) {
return 0;
}
static int add_mount(const char *id, const char *what, const char *where, const char *fstype, const char *description) {
static int add_mount(
const char *id,
const char *what,
const char *where,
const char *fstype,
const char *options,
const char *description,
const char *post) {
_cleanup_free_ char *unit = NULL, *lnk = NULL, *crypto_what = NULL, *p = NULL;
_cleanup_fclose_ FILE *f = NULL;
int r;
@ -295,7 +307,6 @@ static int add_mount(const char *id, const char *what, const char *where, const
assert(id);
assert(what);
assert(where);
assert(fstype);
assert(description);
if (path_is_mount_point(where, true) <= 0 &&
@ -304,9 +315,9 @@ static int add_mount(const char *id, const char *what, const char *where, const
return 0;
}
log_debug("Adding %s: %s %s", where, what, fstype);
log_debug("Adding %s: %s %s", where, what, strna(fstype));
if (streq(fstype, "crypto_LUKS")) {
if (streq_ptr(fstype, "crypto_LUKS")) {
r = add_cryptsetup(id, what, &crypto_what);
if (r < 0)
@ -337,6 +348,9 @@ static int add_mount(const char *id, const char *what, const char *where, const
"Documentation=man:systemd-gpt-auto-generator(8)\n",
description);
if (post)
fprintf(f, "Before=%s\n", post);
r = generator_write_fsck_deps(f, arg_dest, what, where, fstype);
if (r < 0)
return r;
@ -348,26 +362,28 @@ static int add_mount(const char *id, const char *what, const char *where, const
"Where=%s\n",
what, where);
if (fstype) {
fprintf(f,
"Type=%s\n",
fstype);
}
if (fstype)
fprintf(f, "Type=%s\n", fstype);
if (options)
fprintf(f, "Options=%s\n", options);
fflush(f);
if (ferror(f)) {
log_error("Failed to write unit file %s: %m", unit);
log_error("Failed to write unit file %s: %m", p);
return -errno;
}
lnk = strjoin(arg_dest, "/" SPECIAL_LOCAL_FS_TARGET ".requires/", unit, NULL);
if (!lnk)
return log_oom();
if (post) {
lnk = strjoin(arg_dest, "/", post, ".requires/", unit, NULL);
if (!lnk)
return log_oom();
mkdir_parents_label(lnk, 0755);
if (symlink(unit, lnk) < 0) {
log_error("Failed to create symlink %s: %m", lnk);
return -errno;
mkdir_parents_label(lnk, 0755);
if (symlink(p, lnk) < 0) {
log_error("Failed to create symlink %s: %m", lnk);
return -errno;
}
}
return 0;
@ -380,7 +396,7 @@ static int enumerate_partitions(struct udev *udev, dev_t dev) {
unsigned home_nr = (unsigned) -1, srv_nr = (unsigned )-1;
struct udev_list_entry *first, *item;
struct udev_device *parent = NULL;
int r;
int r, k;
e = udev_enumerate_new(udev);
if (!e)
@ -408,6 +424,8 @@ static int enumerate_partitions(struct udev *udev, dev_t dev) {
return r;
}
r = 0;
first = udev_enumerate_get_list_entry(e);
udev_list_entry_foreach(item, first) {
_cleanup_udev_device_unref_ struct udev_device *q;
@ -430,21 +448,26 @@ static int enumerate_partitions(struct udev *udev, dev_t dev) {
if (!node)
return log_oom();
r = verify_gpt_partition(node, &type_id, &nr, &fstype);
if (r < 0) {
k = verify_gpt_partition(node, &type_id, &nr, &fstype);
if (k < 0) {
/* skip child devices which are not detected properly */
if (r == -EBADSLT)
if (k == -EBADSLT)
continue;
log_error("Failed to verify GPT partition %s: %s", node, strerror(-r));
return r;
log_error("Failed to verify GPT partition %s: %s", node, strerror(-k));
r = k;
continue;
}
if (r == 0)
if (k == 0)
continue;
if (sd_id128_equal(type_id, GPT_SWAP))
add_swap(node, fstype);
if (sd_id128_equal(type_id, GPT_SWAP)) {
else if (sd_id128_equal(type_id, GPT_HOME)) {
k = add_swap(node, fstype);
if (k < 0)
r = k;
} else if (sd_id128_equal(type_id, GPT_HOME)) {
/* We only care for the first /home partition */
if (home && nr >= home_nr)
@ -480,11 +503,17 @@ static int enumerate_partitions(struct udev *udev, dev_t dev) {
}
}
if (home && home_fstype)
add_mount("home", home, "/home", home_fstype, "Home Partition");
if (home && home_fstype) {
k = add_mount("home", home, "/home", home_fstype, NULL, "Home Partition", SPECIAL_LOCAL_FS_TARGET);
if (k < 0)
r = k;
}
if (srv && srv_fstype)
add_mount("srv", srv, "/srv", srv_fstype, "Server Data Partition");
if (srv && srv_fstype) {
k = add_mount("srv", srv, "/srv", srv_fstype, NULL, "Server Data Partition", SPECIAL_LOCAL_FS_TARGET);
if (k < 0)
r = k;
}
return r;
}
@ -582,10 +611,113 @@ static int devno_to_devnode(struct udev *udev, dev_t devno, char **ret) {
return 0;
}
int main(int argc, char *argv[]) {
static int parse_proc_cmdline_item(const char *key, const char *value) {
int r;
assert(key);
if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto") && value) {
r = parse_boolean(value);
if (r < 0)
log_warning("Failed to parse gpt-auto switch %s. Ignoring.", value);
arg_enabled = r;
} else if (streq(key, "root") && value) {
/* Disable root disk logic if there's a root= value
* specified (unless it happens to be "gpt-auto") */
arg_root_enabled = streq(value, "gpt-auto");
} else if (streq(key, "rw") && !value)
arg_root_rw = true;
else if (streq(key, "ro") && !value)
arg_root_rw = false;
else if (startswith(key, "systemd.gpt-auto.") || startswith(key, "rd.systemd.gpt-auto."))
log_warning("Unknown kernel switch %s. Ignoring.", key);
return 0;
}
static int add_root_mount(void) {
#ifdef ENABLE_EFI
int r;
if (!is_efi_boot()) {
log_debug("Not a EFI boot, not creating root mount.");
return 0;
}
r = efi_loader_get_device_part_uuid(NULL);
if (r == -ENOENT) {
log_debug("EFI loader partition unknown, exiting.");
return 0;
} else if (r < 0) {
log_error("Failed to read ESP partition UUID: %s", strerror(-r));
return r;
}
/* OK, we have an ESP partition, this is fantastic, so let's
* wait for a root device to show up. A udev rule will create
* the link for us under the right name. */
return add_mount(
"root",
"/dev/disk/by-id/gpt-auto-root",
in_initrd() ? "/sysroot" : "/",
NULL,
arg_root_rw ? "rw" : "ro",
"Root Partition",
in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET);
#else
return 0;
#endif
}
static int add_mounts(void) {
_cleanup_udev_unref_ struct udev *udev = NULL;
_cleanup_free_ char *node = NULL;
dev_t devno;
int r;
r = get_block_device("/", &devno);
if (r < 0) {
log_error("Failed to determine block device of root file system: %s", strerror(-r));
return r;
} else if (r == 0) {
log_debug("Root file system not on a (single) block device.");
return 0;
}
udev = udev_new();
if (!udev)
return log_oom();
r = devno_to_devnode(udev, devno, &node);
if (r < 0) {
log_error("Failed to determine block device node from major/minor: %s", strerror(-r));
return r;
}
log_debug("Root device %s.", node);
r = verify_gpt_partition(node, NULL, NULL, NULL);
if (r < 0) {
log_error("Failed to verify GPT partition %s: %s", node, strerror(-r));
return r;
}
if (r == 0) {
log_debug("Not a GPT disk, exiting.");
return 0;
}
return enumerate_partitions(udev, devno);
}
int main(int argc, char *argv[]) {
int r = 0;
if (argc > 1 && argc != 4) {
@ -602,52 +734,29 @@ int main(int argc, char *argv[]) {
umask(0022);
if (in_initrd()) {
log_debug("In initrd, exiting.");
return EXIT_SUCCESS;
}
if (detect_container(NULL) > 0) {
log_debug("In a container, exiting.");
return EXIT_SUCCESS;
}
r = get_block_device("/", &devno);
if (r < 0) {
log_error("Failed to determine block device of root file system: %s", strerror(-r));
if (parse_proc_cmdline(parse_proc_cmdline_item) < 0)
return EXIT_FAILURE;
} else if (r == 0) {
log_debug("Root file system not on a (single) block device.");
if (!arg_enabled) {
log_debug("Disabled, exiting.");
return EXIT_SUCCESS;
}
udev = udev_new();
if (!udev) {
log_oom();
return EXIT_FAILURE;
if (arg_root_enabled)
r = add_root_mount();
if (!in_initrd()) {
int k;
k = add_mounts();
if (k < 0)
r = k;
}
r = devno_to_devnode(udev, devno, &node);
if (r < 0) {
log_error("Failed to determine block device node from major/minor: %s", strerror(-r));
return EXIT_FAILURE;
}
log_debug("Root device %s.", node);
r = verify_gpt_partition(node, NULL, NULL, NULL);
if (r < 0) {
log_error("Failed to verify GPT partition %s: %s", node, strerror(-r));
return EXIT_FAILURE;
}
if (r == 0) {
log_debug("Not a GPT disk, exiting.");
return EXIT_SUCCESS;
}
r = enumerate_partitions(udev, devno);
if (r < 0)
return EXIT_FAILURE;
return EXIT_SUCCESS;
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}

View File

@ -440,9 +440,6 @@ int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) {
int efi_loader_get_device_part_uuid(sd_id128_t *u) {
_cleanup_free_ char *p = NULL;
int r, parsed[16];
unsigned i;
assert(u);
r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p);
if (r < 0)
@ -455,8 +452,12 @@ int efi_loader_get_device_part_uuid(sd_id128_t *u) {
&parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16)
return -EIO;
for (i = 0; i < ELEMENTSOF(parsed); i++)
u->bytes[i] = parsed[i];
if (u) {
unsigned i;
for (i = 0; i < ELEMENTSOF(parsed); i++)
u->bytes[i] = parsed[i];
}
return 0;
}