grub2/grub2-grubenv-in-btrfs-header.patch
2024-05-02 09:06:09 +00:00

512 lines
12 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

GRUB cannot write Btrfs file systems from the bootloader, so it cannot
modify values set from userspace (e.g. "next_entry" set by grub2-once).
As a workaround use the Btrfs header to store known data of the GRUB environment
block.
v2: export env_block and make sure to use the device of grubenv
v3:
* Use xcalloc for overflow check and return NULL when it would
occur.
v4:
* Fix gcc error with CFLAGS=-Og
../util/grub-editenv.c: In function read_envblk_fs:
../util/grub-editenv.c:172:14: error: sz may be used uninitialized [-Werror=maybe-uninitialized]
172 | sz <<= GRUB_DISK_SECTOR_BITS;
../util/grub-editenv.c:155:16: note: sz was declared here
155 | int off, sz;
| ^~
cc1: all warnings being treated as errors
---
--- a/grub-core/kern/fs.c
+++ b/grub-core/kern/fs.c
@@ -27,6 +27,7 @@
#include <grub/mm.h>
#include <grub/term.h>
#include <grub/i18n.h>
+#include <grub/partition.h>
grub_fs_t grub_fs_list = 0;
@@ -236,6 +237,13 @@
size, buf) != GRUB_ERR_NONE)
return -1;
+ if (file->read_hook)
+ {
+ grub_disk_addr_t part_start;
+
+ part_start = grub_partition_get_start (file->device->disk->partition);
+ file->read_hook (p->offset + sector + part_start, (unsigned)offset, (unsigned)size, NULL, file->read_hook_data);
+ }
ret += size;
len -= size;
sector -= ((size + offset) >> GRUB_DISK_SECTOR_BITS);
--- a/util/grub-editenv.c
+++ b/util/grub-editenv.c
@@ -23,8 +23,11 @@
#include <grub/util/misc.h>
#include <grub/lib/envblk.h>
#include <grub/i18n.h>
-#include <grub/emu/hostfile.h>
+#include <grub/emu/hostdisk.h>
#include <grub/util/install.h>
+#include <grub/emu/getroot.h>
+#include <grub/fs.h>
+#include <grub/crypto.h>
#include <stdio.h>
#include <unistd.h>
@@ -120,6 +123,142 @@
NULL, help_filter, NULL
};
+struct fs_envblk_spec {
+ const char *fs_name;
+ int offset;
+ int size;
+} fs_envblk_spec[] = {
+ { "btrfs", 256 * 1024, GRUB_DISK_SECTOR_SIZE },
+ { NULL, 0, 0 }
+};
+
+struct fs_envblk {
+ struct fs_envblk_spec *spec;
+ const char *dev;
+};
+
+typedef struct fs_envblk_spec *fs_envblk_spec_t;
+typedef struct fs_envblk *fs_envblk_t;
+
+fs_envblk_t fs_envblk = NULL;
+
+static int
+read_envblk_fs (const char *varname, const char *value, void *hook_data)
+{
+ grub_envblk_t *p_envblk = (grub_envblk_t *)hook_data;
+
+ if (!p_envblk || !fs_envblk)
+ return 0;
+
+ if (strcmp (varname, "env_block") == 0)
+ {
+ int off, sz;
+ char *p;
+
+ off = strtol (value, &p, 10);
+ if (*p == '+')
+ sz = strtol (p+1, &p, 10);
+ else
+ return 0;
+
+ if (*p == '\0')
+ {
+ FILE *fp;
+ char *buf;
+
+ off <<= GRUB_DISK_SECTOR_BITS;
+ sz <<= GRUB_DISK_SECTOR_BITS;
+
+ fp = grub_util_fopen (fs_envblk->dev, "rb");
+ if (! fp)
+ grub_util_error (_("cannot open `%s': %s"), fs_envblk->dev,
+ strerror (errno));
+
+
+ if (fseek (fp, off, SEEK_SET) < 0)
+ grub_util_error (_("cannot seek `%s': %s"), fs_envblk->dev,
+ strerror (errno));
+
+ buf = xmalloc (sz);
+ if ((fread (buf, 1, sz, fp)) != sz)
+ grub_util_error (_("cannot read `%s': %s"), fs_envblk->dev,
+ strerror (errno));
+
+ fclose (fp);
+
+ *p_envblk = grub_envblk_open (buf, sz);
+ }
+ }
+
+ return 0;
+}
+
+static void
+create_envblk_fs (void)
+{
+ FILE *fp;
+ char *buf;
+ const char *device;
+ int offset, size;
+
+ if (!fs_envblk)
+ return;
+
+ device = fs_envblk->dev;
+ offset = fs_envblk->spec->offset;
+ size = fs_envblk->spec->size;
+
+ fp = grub_util_fopen (device, "r+b");
+ if (! fp)
+ grub_util_error (_("cannot open `%s': %s"), device, strerror (errno));
+
+ buf = xmalloc (size);
+ memcpy (buf, GRUB_ENVBLK_SIGNATURE, sizeof (GRUB_ENVBLK_SIGNATURE) - 1);
+ memset (buf + sizeof (GRUB_ENVBLK_SIGNATURE) - 1, '#', size - sizeof (GRUB_ENVBLK_SIGNATURE) + 1);
+
+ if (fseek (fp, offset, SEEK_SET) < 0)
+ grub_util_error (_("cannot seek `%s': %s"), device, strerror (errno));
+
+ if (fwrite (buf, 1, size, fp) != size)
+ grub_util_error (_("cannot write to `%s': %s"), device, strerror (errno));
+
+ grub_util_file_sync (fp);
+ free (buf);
+ fclose (fp);
+}
+
+static grub_envblk_t
+open_envblk_fs (grub_envblk_t envblk)
+{
+ grub_envblk_t envblk_fs = NULL;
+ char *val;
+ int offset, size;
+
+ if (!fs_envblk)
+ return NULL;
+
+ offset = fs_envblk->spec->offset;
+ size = fs_envblk->spec->size;
+
+ grub_envblk_iterate (envblk, &envblk_fs, read_envblk_fs);
+
+ if (envblk_fs && grub_envblk_size (envblk_fs) == size)
+ return envblk_fs;
+
+ create_envblk_fs ();
+
+ offset = offset >> GRUB_DISK_SECTOR_BITS;
+ size = (size + GRUB_DISK_SECTOR_SIZE - 1) >> GRUB_DISK_SECTOR_BITS;
+
+ val = xasprintf ("%d+%d", offset, size);
+ if (! grub_envblk_set (envblk, "env_block", val))
+ grub_util_error ("%s", _("environment block too small"));
+ grub_envblk_iterate (envblk, &envblk_fs, read_envblk_fs);
+ free (val);
+
+ return envblk_fs;
+}
+
static grub_envblk_t
open_envblk_file (const char *name)
{
@@ -182,10 +321,17 @@
list_variables (const char *name)
{
grub_envblk_t envblk;
+ grub_envblk_t envblk_fs = NULL;
envblk = open_envblk_file (name);
+ grub_envblk_iterate (envblk, &envblk_fs, read_envblk_fs);
grub_envblk_iterate (envblk, NULL, print_var);
grub_envblk_close (envblk);
+ if (envblk_fs)
+ {
+ grub_envblk_iterate (envblk_fs, NULL, print_var);
+ grub_envblk_close (envblk_fs);
+ }
}
static void
@@ -209,6 +355,38 @@
}
static void
+write_envblk_fs (grub_envblk_t envblk)
+{
+ FILE *fp;
+ const char *device;
+ int offset, size;
+
+ if (!fs_envblk)
+ return;
+
+ device = fs_envblk->dev;
+ offset = fs_envblk->spec->offset;
+ size = fs_envblk->spec->size;
+
+ if (grub_envblk_size (envblk) > size)
+ grub_util_error ("%s", _("environment block too small"));
+
+ fp = grub_util_fopen (device, "r+b");
+
+ if (! fp)
+ grub_util_error (_("cannot open `%s': %s"), device, strerror (errno));
+
+ if (fseek (fp, offset, SEEK_SET) < 0)
+ grub_util_error (_("cannot seek `%s': %s"), device, strerror (errno));
+
+ if (fwrite (grub_envblk_buffer (envblk), 1, grub_envblk_size (envblk), fp) != grub_envblk_size (envblk))
+ grub_util_error (_("cannot write to `%s': %s"), device, strerror (errno));
+
+ grub_util_file_sync (fp);
+ fclose (fp);
+}
+
+static void
set_variables (const char *name, int argc, char *argv[])
{
grub_envblk_t envblk;
@@ -224,8 +402,27 @@
*(p++) = 0;
- if (! grub_envblk_set (envblk, argv[0], p))
- grub_util_error ("%s", _("environment block too small"));
+ if ((strcmp (argv[0], "next_entry") == 0 ||
+ strcmp (argv[0], "health_checker_flag") == 0) && fs_envblk)
+ {
+ grub_envblk_t envblk_fs;
+ envblk_fs = open_envblk_fs (envblk);
+ if (!envblk_fs)
+ grub_util_error ("%s", _("can't open fs environment block"));
+ if (! grub_envblk_set (envblk_fs, argv[0], p))
+ grub_util_error ("%s", _("environment block too small"));
+ write_envblk_fs (envblk_fs);
+ grub_envblk_close (envblk_fs);
+ }
+ else if (strcmp (argv[0], "env_block") == 0)
+ {
+ grub_util_warn ("can't set env_block as it's read-only");
+ }
+ else
+ {
+ if (! grub_envblk_set (envblk, argv[0], p))
+ grub_util_error ("%s", _("environment block too small"));
+ }
argc--;
argv++;
@@ -233,26 +430,158 @@
write_envblk (name, envblk);
grub_envblk_close (envblk);
+
}
static void
unset_variables (const char *name, int argc, char *argv[])
{
grub_envblk_t envblk;
+ grub_envblk_t envblk_fs;
envblk = open_envblk_file (name);
+
+ envblk_fs = NULL;
+ if (fs_envblk)
+ envblk_fs = open_envblk_fs (envblk);
+
while (argc)
{
grub_envblk_delete (envblk, argv[0]);
+ if (envblk_fs)
+ grub_envblk_delete (envblk_fs, argv[0]);
+
argc--;
argv++;
}
write_envblk (name, envblk);
grub_envblk_close (envblk);
+
+ if (envblk_fs)
+ {
+ write_envblk_fs (envblk_fs);
+ grub_envblk_close (envblk_fs);
+ }
}
+int have_abstraction = 0;
+static void
+probe_abstraction (grub_disk_t disk)
+{
+ if (disk->partition == NULL)
+ grub_util_info ("no partition map found for %s", disk->name);
+
+ if (disk->dev->id == GRUB_DISK_DEVICE_DISKFILTER_ID ||
+ disk->dev->id == GRUB_DISK_DEVICE_CRYPTODISK_ID)
+ {
+ have_abstraction = 1;
+ }
+}
+
+static fs_envblk_t
+probe_fs_envblk (fs_envblk_spec_t spec)
+{
+ char **grub_devices;
+ char **curdev, **curdrive;
+ size_t ndev = 0;
+ char **grub_drives;
+ grub_device_t grub_dev = NULL;
+ grub_fs_t grub_fs;
+ const char *fs_envblk_device;
+
+#ifdef __s390x__
+ return NULL;
+#endif
+
+ grub_util_biosdisk_init (DEFAULT_DEVICE_MAP);
+ grub_init_all ();
+ grub_gcry_init_all ();
+
+ grub_lvm_fini ();
+ grub_mdraid09_fini ();
+ grub_mdraid1x_fini ();
+ grub_diskfilter_fini ();
+ grub_diskfilter_init ();
+ grub_mdraid09_init ();
+ grub_mdraid1x_init ();
+ grub_lvm_init ();
+
+ grub_devices = grub_guess_root_devices (DEFAULT_DIRECTORY);
+
+ if (!grub_devices || !grub_devices[0])
+ grub_util_error (_("cannot find a device for %s (is /dev mounted?)"), DEFAULT_DIRECTORY);
+
+ fs_envblk_device = grub_devices[0];
+
+ for (curdev = grub_devices; *curdev; curdev++)
+ {
+ grub_util_pull_device (*curdev);
+ ndev++;
+ }
+
+ grub_drives = xcalloc ((ndev + 1), sizeof (grub_drives[0]));
+
+ for (curdev = grub_devices, curdrive = grub_drives; *curdev; curdev++,
+ curdrive++)
+ {
+ *curdrive = grub_util_get_grub_dev (*curdev);
+ if (! *curdrive)
+ grub_util_error (_("cannot find a GRUB drive for %s. Check your device.map"),
+ *curdev);
+ }
+ *curdrive = 0;
+
+ grub_dev = grub_device_open (grub_drives[0]);
+ if (! grub_dev)
+ grub_util_error ("%s", grub_errmsg);
+
+ grub_fs = grub_fs_probe (grub_dev);
+ if (! grub_fs)
+ grub_util_error ("%s", grub_errmsg);
+
+ if (grub_dev->disk)
+ {
+ probe_abstraction (grub_dev->disk);
+ }
+ for (curdrive = grub_drives + 1; *curdrive; curdrive++)
+ {
+ grub_device_t dev = grub_device_open (*curdrive);
+ if (!dev)
+ continue;
+ if (dev->disk)
+ probe_abstraction (dev->disk);
+ grub_device_close (dev);
+ }
+
+ free (grub_drives);
+ grub_device_close (grub_dev);
+ grub_gcry_fini_all ();
+ grub_fini_all ();
+ grub_util_biosdisk_fini ();
+
+ fs_envblk_spec_t p;
+
+ for (p = spec; p->fs_name; p++)
+ {
+ if (strcmp (grub_fs->name, p->fs_name) == 0 && !have_abstraction)
+ {
+ if (p->offset % GRUB_DISK_SECTOR_SIZE == 0 &&
+ p->size % GRUB_DISK_SECTOR_SIZE == 0)
+ {
+ fs_envblk = xmalloc (sizeof (fs_envblk_t));
+ fs_envblk->spec = p;
+ fs_envblk->dev = strdup(fs_envblk_device);
+ return fs_envblk;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
int
main (int argc, char *argv[])
{
@@ -284,6 +613,9 @@
command = argv[curindex++];
}
+ if (strcmp (filename, DEFAULT_ENVBLK_PATH) == 0)
+ fs_envblk = probe_fs_envblk (fs_envblk_spec);
+
if (strcmp (command, "create") == 0)
grub_util_create_envblk_file (filename);
else if (strcmp (command, "list") == 0)
--- a/util/grub.d/00_header.in
+++ b/util/grub.d/00_header.in
@@ -46,6 +46,13 @@
if [ -s \$prefix/grubenv ]; then
load_env
fi
+
+if [ "\${env_block}" ] ; then
+ set env_block="(\${root})\${env_block}"
+ export env_block
+ load_env -f "\${env_block}"
+fi
+
EOF
if [ "x$GRUB_BUTTON_CMOS_ADDRESS" != "x" ]; then
cat <<EOF
@@ -55,6 +62,9 @@
set default="\${next_entry}"
set next_entry=
save_env next_entry
+ if [ "\${env_block}" ] ; then
+ save_env -f "\${env_block}" next_entry
+ fi
set boot_once=true
else
set default="${GRUB_DEFAULT}"
@@ -66,6 +76,9 @@
set default="\${next_entry}"
set next_entry=
save_env next_entry
+ if [ "\${env_block}" ] ; then
+ save_env -f "\${env_block}" next_entry
+ fi
set boot_once=true
else
set default="${GRUB_DEFAULT}"
@@ -93,7 +106,12 @@
function savedefault {
if [ -z "\${boot_once}" ]; then
saved_entry="\${chosen}"
- save_env saved_entry
+ if [ "\${env_block}" ] ; then
+ save_env -f "\${env_block}" saved_entry
+ else
+ save_env saved_entry
+ fi
+
fi
}