From 0c89abee6d1142b6b20ba2539eceece2cbd3b9eb Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Fri, 17 Oct 2014 11:46:52 -0400 Subject: [PATCH] Add (non-atomic) support for GRUB2 + UEFI We need basic support for UEFI - many newer servers don't support BIOS compatibility mode anymore. However, this patch only implements non-atomic because UEFI is FAT, and we can't do the previous design for OSTree of atomic swap of /boot/loader. The Fedora/RHEL UEFI layout has the kernels on a "real" /boot partition, and /boot/efi/EFI/$vendor just holds the grub2 UEFI binary and grub.cfg. Following this, /boot/loader is still on the OS boot partition, and we still atomically swap it. This potentially paves the way to atomic upgrades in the future. https://bugzilla.gnome.org/show_bug.cgi?id=724246 --- src/libostree/ostree-bootloader-grub2.c | 140 +++++++++++++++++++-- src/libostree/ostree-bootloader-grub2.h | 2 +- src/libostree/ostree-bootloader-syslinux.c | 8 +- src/libostree/ostree-bootloader-uboot.c | 8 +- src/libostree/ostree-bootloader.c | 7 +- src/libostree/ostree-bootloader.h | 10 +- src/libostree/ostree-cmdprivate.c | 4 +- src/libostree/ostree-sysroot-deploy.c | 5 +- src/libostree/ostree-sysroot-private.h | 5 +- src/libostree/ostree-sysroot.c | 55 +++++--- 10 files changed, 202 insertions(+), 42 deletions(-) diff --git a/src/libostree/ostree-bootloader-grub2.c b/src/libostree/ostree-bootloader-grub2.c index 8f4dcae9..19d04924 100644 --- a/src/libostree/ostree-bootloader-grub2.c +++ b/src/libostree/ostree-bootloader-grub2.c @@ -35,6 +35,8 @@ struct _OstreeBootloaderGrub2 OstreeSysroot *sysroot; GFile *config_path_bios; + GFile *config_path_efi; + gboolean is_efi; }; typedef GObjectClass OstreeBootloaderGrub2Class; @@ -44,11 +46,79 @@ G_DEFINE_TYPE_WITH_CODE (OstreeBootloaderGrub2, _ostree_bootloader_grub2, G_TYPE G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BOOTLOADER, _ostree_bootloader_grub2_bootloader_iface_init)); static gboolean -_ostree_bootloader_grub2_query (OstreeBootloader *bootloader) +_ostree_bootloader_grub2_query (OstreeBootloader *bootloader, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error) { + gboolean ret = FALSE; OstreeBootloaderGrub2 *self = OSTREE_BOOTLOADER_GRUB2 (bootloader); + gs_unref_object GFile* efi_basedir = NULL; + gs_unref_object GFileInfo *file_info = NULL; - return g_file_query_exists (self->config_path_bios, NULL); + if (g_file_query_exists (self->config_path_bios, NULL)) + { + *out_is_active = TRUE; + ret = TRUE; + goto out; + } + + efi_basedir = g_file_resolve_relative_path (self->sysroot->path, "boot/efi/EFI"); + + g_clear_object (&self->config_path_efi); + + if (g_file_query_exists (efi_basedir, NULL)) + { + gs_unref_object GFileEnumerator *direnum = NULL; + + direnum = g_file_enumerate_children (efi_basedir, OSTREE_GIO_FAST_QUERYINFO, + G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, + cancellable, error); + if (!direnum) + goto out; + + while (TRUE) + { + GFileInfo *file_info; + const char *fname; + gs_free char *subdir_grub_cfg = NULL; + + if (!gs_file_enumerator_iterate (direnum, &file_info, NULL, + cancellable, error)) + goto out; + if (file_info == NULL) + break; + + fname = g_file_info_get_name (file_info); + if (strcmp (fname, "BOOT") == 0) + continue; + + if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY) + continue; + + subdir_grub_cfg = g_build_filename (gs_file_get_path_cached (efi_basedir), fname, "grub.cfg", NULL); + + if (g_file_test (subdir_grub_cfg, G_FILE_TEST_EXISTS)) + { + self->config_path_efi = g_file_new_for_path (subdir_grub_cfg); + break; + } + } + + if (self->config_path_efi) + { + self->is_efi = TRUE; + *out_is_active = TRUE; + ret = TRUE; + goto out; + } + } + else + *out_is_active = FALSE; + + ret = TRUE; + out: + return ret; } static const char * @@ -58,7 +128,7 @@ _ostree_bootloader_grub2_get_name (OstreeBootloader *bootloader) } gboolean -_ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, +_ostree_bootloader_grub2_generate_config (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, @@ -70,6 +140,7 @@ _ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, gs_unref_ptrarray GPtrArray *loader_configs = NULL; guint i; gsize bytes_written; + gboolean is_efi; /* So... yeah. Just going to hardcode these. */ static const char hardcoded_video[] = "load_video\n" "set gfxpayload=keep\n"; @@ -83,9 +154,12 @@ _ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, g_assert (grub2_boot_device_id != NULL); g_assert (grub2_prepare_root_cache != NULL); + /* Passed from the parent */ + is_efi = g_getenv ("_OSTREE_GRUB2_IS_EFI") != NULL; + out_stream = g_unix_output_stream_new (target_fd, FALSE); - if (!_ostree_sysroot_read_boot_loader_configs (self->sysroot, bootversion, + if (!_ostree_sysroot_read_boot_loader_configs (sysroot, bootversion, &loader_configs, cancellable, error)) goto out; @@ -127,7 +201,10 @@ _ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, "No \"linux\" key in bootloader config"); goto out; } - g_string_append (output, "linux16 "); + if (is_efi) + g_string_append (output, "linuxefi "); + else + g_string_append (output, "linux16 "); g_string_append (output, kernel); options = ostree_bootconfig_parser_get (config, "options"); @@ -141,7 +218,10 @@ _ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, initrd = ostree_bootconfig_parser_get (config, "initrd"); if (initrd) { - g_string_append (output, "initrd16 "); + if (is_efi) + g_string_append (output, "initrdefi "); + else + g_string_append (output, "initrd16 "); g_string_append (output, initrd); g_string_append_c (output, '\n'); } @@ -168,19 +248,36 @@ _ostree_bootloader_grub2_write_config (OstreeBootloader *bootloader, { OstreeBootloaderGrub2 *self = OSTREE_BOOTLOADER_GRUB2 (bootloader); gboolean ret = FALSE; + gs_unref_object GFile *efi_new_config_temp = NULL; + gs_unref_object GFile *efi_orig_config = NULL; gs_unref_object GFile *new_config_path = NULL; gs_unref_object GSSubprocessContext *procctx = NULL; gs_unref_object GSSubprocess *proc = NULL; gs_strfreev char **child_env = g_get_environ (); gs_free char *bootversion_str = g_strdup_printf ("%u", (guint)bootversion); + gs_unref_object GFile *config_path_efi_dir = NULL; - new_config_path = ot_gfile_resolve_path_printf (self->sysroot->path, "boot/loader.%d/grub.cfg", - bootversion); + if (self->is_efi) + { + config_path_efi_dir = g_file_get_parent (self->config_path_efi); + new_config_path = g_file_get_child (config_path_efi_dir, "grub.cfg.new"); + /* We use grub2-mkconfig to write to a temporary file first */ + if (!ot_gfile_ensure_unlinked (new_config_path, cancellable, error)) + goto out; + } + else + { + new_config_path = ot_gfile_resolve_path_printf (self->sysroot->path, "boot/loader.%d/grub.cfg", + bootversion); + } procctx = gs_subprocess_context_newv ("grub2-mkconfig", "-o", gs_file_get_path_cached (new_config_path), NULL); child_env = g_environ_setenv (child_env, "_OSTREE_GRUB2_BOOTVERSION", bootversion_str, TRUE); + /* We have to pass our state to the child */ + if (self->is_efi) + child_env = g_environ_setenv (child_env, "_OSTREE_GRUB2_IS_EFI", "1", TRUE); gs_subprocess_context_set_environment (procctx, child_env); gs_subprocess_context_set_stdout_disposition (procctx, GS_SUBPROCESS_STREAM_DISPOSITION_NULL); if (g_getenv ("OSTREE_DEBUG_GRUB2")) @@ -206,6 +303,29 @@ rm -f ${grub_cfg}.new /* Now let's fdatasync() for the new file */ if (!gs_file_sync_data (new_config_path, cancellable, error)) goto out; + + if (self->is_efi) + { + gs_unref_object GFile *config_path_efi_old = g_file_get_child (config_path_efi_dir, "grub.cfg.old"); + + /* copy current to old */ + if (!ot_gfile_ensure_unlinked (config_path_efi_old, cancellable, error)) + goto out; + if (!g_file_copy (self->config_path_efi, config_path_efi_old, + G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error)) + goto out; + if (!ot_gfile_ensure_unlinked (config_path_efi_old, cancellable, error)) + goto out; + + /* NOTE: NON-ATOMIC REPLACEMENT; WE can't do anything else on FAT; + * see https://bugzilla.gnome.org/show_bug.cgi?id=724246 + */ + if (!ot_gfile_ensure_unlinked (new_config_path, cancellable, error)) + goto out; + if (!gs_file_rename (new_config_path, self->config_path_efi, + cancellable, error)) + goto out; + } ret = TRUE; out: @@ -215,7 +335,8 @@ rm -f ${grub_cfg}.new static gboolean _ostree_bootloader_grub2_is_atomic (OstreeBootloader *bootloader) { - return TRUE; + OstreeBootloaderGrub2 *self = OSTREE_BOOTLOADER_GRUB2 (bootloader); + return !self->is_efi; } static void @@ -225,6 +346,7 @@ _ostree_bootloader_grub2_finalize (GObject *object) g_clear_object (&self->sysroot); g_clear_object (&self->config_path_bios); + g_clear_object (&self->config_path_efi); G_OBJECT_CLASS (_ostree_bootloader_grub2_parent_class)->finalize (object); } diff --git a/src/libostree/ostree-bootloader-grub2.h b/src/libostree/ostree-bootloader-grub2.h index 334ad49b..dfea1837 100644 --- a/src/libostree/ostree-bootloader-grub2.h +++ b/src/libostree/ostree-bootloader-grub2.h @@ -34,7 +34,7 @@ GType _ostree_bootloader_grub2_get_type (void) G_GNUC_CONST; OstreeBootloaderGrub2 * _ostree_bootloader_grub2_new (OstreeSysroot *sysroot); -gboolean _ostree_bootloader_grub2_generate_config (OstreeBootloaderGrub2 *self, int bootversion, int target_fd, GCancellable *cancellable, GError **error); +gboolean _ostree_bootloader_grub2_generate_config (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, GError **error); G_END_DECLS diff --git a/src/libostree/ostree-bootloader-syslinux.c b/src/libostree/ostree-bootloader-syslinux.c index 0934bb81..86b66fa2 100644 --- a/src/libostree/ostree-bootloader-syslinux.c +++ b/src/libostree/ostree-bootloader-syslinux.c @@ -42,11 +42,15 @@ G_DEFINE_TYPE_WITH_CODE (OstreeBootloaderSyslinux, _ostree_bootloader_syslinux, G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BOOTLOADER, _ostree_bootloader_syslinux_bootloader_iface_init)); static gboolean -_ostree_bootloader_syslinux_query (OstreeBootloader *bootloader) +_ostree_bootloader_syslinux_query (OstreeBootloader *bootloader, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error) { OstreeBootloaderSyslinux *self = OSTREE_BOOTLOADER_SYSLINUX (bootloader); - return g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK; + *out_is_active = g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK; + return TRUE; } static const char * diff --git a/src/libostree/ostree-bootloader-uboot.c b/src/libostree/ostree-bootloader-uboot.c index cd25e56f..84c5cddc 100644 --- a/src/libostree/ostree-bootloader-uboot.c +++ b/src/libostree/ostree-bootloader-uboot.c @@ -46,11 +46,15 @@ G_DEFINE_TYPE_WITH_CODE (OstreeBootloaderUboot, _ostree_bootloader_uboot, G_TYPE G_IMPLEMENT_INTERFACE (OSTREE_TYPE_BOOTLOADER, _ostree_bootloader_uboot_bootloader_iface_init)); static gboolean -_ostree_bootloader_uboot_query (OstreeBootloader *bootloader) +_ostree_bootloader_uboot_query (OstreeBootloader *bootloader, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error) { OstreeBootloaderUboot *self = OSTREE_BOOTLOADER_UBOOT (bootloader); - return g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK; + *out_is_active = g_file_query_file_type (self->config_path, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL) == G_FILE_TYPE_SYMBOLIC_LINK; + return TRUE; } static const char * diff --git a/src/libostree/ostree-bootloader.c b/src/libostree/ostree-bootloader.c index 37219999..c95674c8 100644 --- a/src/libostree/ostree-bootloader.c +++ b/src/libostree/ostree-bootloader.c @@ -30,11 +30,14 @@ _ostree_bootloader_default_init (OstreeBootloaderInterface *iface) } gboolean -_ostree_bootloader_query (OstreeBootloader *self) +_ostree_bootloader_query (OstreeBootloader *self, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error) { g_return_val_if_fail (OSTREE_IS_BOOTLOADER (self), FALSE); - return OSTREE_BOOTLOADER_GET_IFACE (self)->query (self); + return OSTREE_BOOTLOADER_GET_IFACE (self)->query (self, out_is_active, cancellable, error); } /** diff --git a/src/libostree/ostree-bootloader.h b/src/libostree/ostree-bootloader.h index a84ce6ba..cbb25aac 100644 --- a/src/libostree/ostree-bootloader.h +++ b/src/libostree/ostree-bootloader.h @@ -37,7 +37,10 @@ struct _OstreeBootloaderInterface GTypeInterface g_iface; /* virtual functions */ - gboolean (* query) (OstreeBootloader *self); + gboolean (* query) (OstreeBootloader *bootloader, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error); const char * (* get_name) (OstreeBootloader *self); gboolean (* write_config) (OstreeBootloader *self, int bootversion, @@ -48,7 +51,10 @@ struct _OstreeBootloaderInterface GType _ostree_bootloader_get_type (void) G_GNUC_CONST; -gboolean _ostree_bootloader_query (OstreeBootloader *self); +gboolean _ostree_bootloader_query (OstreeBootloader *bootloader, + gboolean *out_is_active, + GCancellable *cancellable, + GError **error); const char *_ostree_bootloader_get_name (OstreeBootloader *self); diff --git a/src/libostree/ostree-cmdprivate.c b/src/libostree/ostree-cmdprivate.c index 2bbfe332..9c2ac3bf 100644 --- a/src/libostree/ostree-cmdprivate.c +++ b/src/libostree/ostree-cmdprivate.c @@ -29,9 +29,7 @@ static gboolean impl_ostree_generate_grub2_config (OstreeSysroot *sysroot, int bootversion, int target_fd, GCancellable *cancellable, GError **error) { - gs_unref_object OstreeBootloaderGrub2 *grub2 = _ostree_bootloader_grub2_new (sysroot); - - return _ostree_bootloader_grub2_generate_config (grub2, bootversion, target_fd, cancellable, error); + return _ostree_bootloader_grub2_generate_config (sysroot, bootversion, target_fd, cancellable, error); } /** diff --git a/src/libostree/ostree-sysroot-deploy.c b/src/libostree/ostree-sysroot-deploy.c index 9934e0f8..8d9fe0c7 100644 --- a/src/libostree/ostree-sysroot-deploy.c +++ b/src/libostree/ostree-sysroot-deploy.c @@ -1598,9 +1598,12 @@ ostree_sysroot_write_deployments (OstreeSysroot *self, else { int new_bootversion = self->bootversion ? 0 : 1; - gs_unref_object OstreeBootloader *bootloader = _ostree_sysroot_query_bootloader (self); + gs_unref_object OstreeBootloader *bootloader = NULL; gs_unref_object GFile *new_loader_entries_dir = NULL; + if (!_ostree_sysroot_query_bootloader (self, &bootloader, cancellable, error)) + goto out; + new_loader_entries_dir = ot_gfile_resolve_path_printf (self->path, "boot/loader.%d/entries", new_bootversion); if (!gs_shutil_rm_rf (new_loader_entries_dir, cancellable, error)) diff --git a/src/libostree/ostree-sysroot-private.h b/src/libostree/ostree-sysroot-private.h index 0857e4cc..28b0feb7 100644 --- a/src/libostree/ostree-sysroot-private.h +++ b/src/libostree/ostree-sysroot-private.h @@ -75,7 +75,10 @@ _ostree_sysroot_get_devino (GFile *path, char *_ostree_sysroot_join_lines (GPtrArray *lines); -OstreeBootloader *_ostree_sysroot_query_bootloader (OstreeSysroot *sysroot); +gboolean _ostree_sysroot_query_bootloader (OstreeSysroot *sysroot, + OstreeBootloader **out_bootloader, + GCancellable *cancellable, + GError **error); G_END_DECLS diff --git a/src/libostree/ostree-sysroot.c b/src/libostree/ostree-sysroot.c index 160fc078..64a5e5bb 100644 --- a/src/libostree/ostree-sysroot.c +++ b/src/libostree/ostree-sysroot.c @@ -846,29 +846,46 @@ ostree_sysroot_get_repo (OstreeSysroot *self, /** * ostree_sysroot_query_bootloader: * @sysroot: Sysroot - * - * Returns: (transfer full): Currently active bootloader in @sysroot + * @out_bootloader: (out) (transfer full) (allow-none): Return location for bootloader, may be %NULL + * @cancellable: Cancellable + * @error: Error */ -OstreeBootloader * -_ostree_sysroot_query_bootloader (OstreeSysroot *self) +gboolean +_ostree_sysroot_query_bootloader (OstreeSysroot *sysroot, + OstreeBootloader **out_bootloader, + GCancellable *cancellable, + GError **error) { - OstreeBootloaderSyslinux *syslinux; - OstreeBootloaderUboot *uboot; - OstreeBootloaderGrub2 *grub2; + gboolean ret = FALSE; + gboolean is_active; + gs_unref_object OstreeBootloader *ret_loader = NULL; - syslinux = _ostree_bootloader_syslinux_new (self); - if (_ostree_bootloader_query ((OstreeBootloader*)syslinux)) - return (OstreeBootloader*) (syslinux); + ret_loader = (OstreeBootloader*)_ostree_bootloader_syslinux_new (sysroot); + if (!_ostree_bootloader_query (ret_loader, &is_active, + cancellable, error)) + goto out; + if (!is_active) + { + g_object_unref (ret_loader); + ret_loader = (OstreeBootloader*)_ostree_bootloader_grub2_new (sysroot); + if (!_ostree_bootloader_query (ret_loader, &is_active, + cancellable, error)) + goto out; + } + if (!is_active) + { + g_object_unref (ret_loader); + ret_loader = (OstreeBootloader*)_ostree_bootloader_uboot_new (sysroot); + if (!_ostree_bootloader_query (ret_loader, &is_active, cancellable, error)) + goto out; + } + if (!is_active) + g_clear_object (&ret_loader); - grub2 = _ostree_bootloader_grub2_new (self); - if (_ostree_bootloader_query ((OstreeBootloader*)grub2)) - return (OstreeBootloader*) (grub2); - - uboot = _ostree_bootloader_uboot_new (self); - if (_ostree_bootloader_query ((OstreeBootloader*)uboot)) - return (OstreeBootloader*) (uboot); - - return NULL; + ret = TRUE; + gs_transfer_out_value (out_bootloader, &ret_loader); + out: + return ret; } char *