This patch extends the ext2 driver to allow embedding core.img into the filesystem. This allows the long needed installation of GRUB into a partition without the need to use (unsafe) block lists.
It is realized using the ioctl(EXT4_IOC_SWAP_BOOT) introduced into Linux 3.10. This ioctl basically swaps the data blocks of the associated file with the EXT2_BOOT_LOADER_INO inode. After using the ioctl the code of core.img is stored in the BOOT_LOADER incode of the filesystem and it is not accessible to file system tools anymore. This ensures, that the boot code is safe and installation into a partition is possible. The patchs needs 0001-Refactor-grub_read_mountinfo-out-of-grub_find_root_d.patch to work correctly. Signed-off-by: Dr. Tilmann Bubeck <tilm...@bubecks.de> --- grub-core/fs/ext2.c | 212 ++++++++++++++++++++++++++++++++++++++++++++++++++-- util/setup.c | 11 ++- 2 files changed, 215 insertions(+), 8 deletions(-) diff --git a/grub-core/fs/ext2.c b/grub-core/fs/ext2.c index 5f7a2b9..643969e 100644 --- a/grub-core/fs/ext2.c +++ b/grub-core/fs/ext2.c @@ -133,6 +133,14 @@ GRUB_MOD_LICENSE ("GPLv3+"); #define EXT4_EXTENTS_FLAG 0x80000 +/* + * Special inodes numbers + */ +#define EXT2_ROOT_INO 2 /* Root inode */ +#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */ + +#define EXT4_IOC_SWAP_BOOT _IO('f', 17) + /* The ext2 superblock. */ struct grub_ext2_sblock { @@ -393,10 +401,10 @@ grub_ext4_find_leaf (struct grub_ext2_data *data, } static grub_disk_addr_t -grub_ext2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +grub_ext2_read_block2 (struct grub_ext2_data *data, + struct grub_ext2_inode *inode, + grub_disk_addr_t fileblock) { - struct grub_ext2_data *data = node->data; - struct grub_ext2_inode *inode = &node->inode; unsigned int blksz = EXT2_BLOCK_SIZE (data); grub_disk_addr_t blksz_quarter = blksz / 4; int log2_blksz = LOG2_EXT2_BLOCK_SIZE (data); @@ -497,6 +505,12 @@ indirect: return grub_le_to_cpu32 (indir); } +static grub_disk_addr_t +grub_ext2_read_block (grub_fshelp_node_t node, grub_disk_addr_t fileblock) +{ + return grub_ext2_read_block2(node->data, &node->inode, fileblock); +} + /* Read LEN bytes from the file described by DATA starting with byte POS. Return the amount of read bytes in READ. */ static grub_ssize_t @@ -607,12 +621,12 @@ grub_ext2_mount (grub_disk_t disk) data->disk = disk; data->diropen.data = data; - data->diropen.ino = 2; + data->diropen.ino = EXT2_ROOT_INO; data->diropen.inode_read = 1; data->inode = &data->diropen.inode; - grub_ext2_read_inode (data, 2, data->inode); + grub_ext2_read_inode (data, EXT2_ROOT_INO, data->inode); if (grub_errno) goto fail; @@ -977,6 +991,193 @@ grub_ext2_mtime (grub_device_t device, grub_int32_t *tm) } +#ifdef GRUB_UTIL + +#include <stdlib.h> +#include <sys/ioctl.h> +#include <include/grub/emu/misc.h> +#include <include/grub/emu/getroot.h> +#include <include/grub/emu/hostfile.h> + +/* Read the addresses of all sectors occupied by the file with the + given inode from the filesystem. Return the number of sectors in + "nsector" and the addresses in "sectors". "sectors" is allocated + in this function and must be freed by the caller after usage. + A sector in sectors has size GRUB_DISK_SECTOR_SIZE. */ +static grub_err_t +grub_ext2_read_sectorlist(grub_device_t device, + int ino, + unsigned int *nsectors, + grub_disk_addr_t **sectors) +{ + struct grub_ext2_data *data; + struct grub_ext2_inode inode; + grub_off_t size; + grub_err_t err; + grub_disk_addr_t fileblock; + int i; + grub_disk_addr_t fileblock_count; + int sectors_per_block; + + data = grub_ext2_mount (device->disk); + if (! data) { + return grub_error (grub_errno, N_("Unable to mount device")); + } + + err = grub_ext2_read_inode (data, ino, &inode); + if (err) + return grub_error (err, N_("Unable to read inode #%d"), ino); + + size = grub_le_to_cpu32 (inode.size); + size |= ((grub_off_t) grub_le_to_cpu32 (inode.size_high)) << 32; + + fileblock_count = size / EXT2_BLOCK_SIZE(data); + if ( size % EXT2_BLOCK_SIZE(data) != 0 ) fileblock_count++; + + sectors_per_block = EXT2_BLOCK_SIZE(data) / GRUB_DISK_SECTOR_SIZE; + *sectors = grub_malloc (fileblock_count * sectors_per_block + * sizeof (**sectors)); + *nsectors = 0; + for ( fileblock = 0; fileblock < fileblock_count; fileblock++ ) { + (*sectors)[*nsectors] = grub_ext2_read_block2 (data, &inode, fileblock) + * sectors_per_block; + for ( i = 1; i < sectors_per_block; i++) { + (*sectors)[(*nsectors) + i] = (*sectors)[*nsectors] + i; + } + (*nsectors) += sectors_per_block; + } + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_ext2_embed (grub_device_t device, + unsigned int *nsectors, + unsigned int max_nsectors, + grub_embed_type_t embed_type, + grub_disk_addr_t **sectors) +{ + unsigned i; + grub_err_t err; + char *mountpoint; + struct mountinfo_entry *entries; + grub_util_fd_t out; + char *core_name; + char diskbuffer[GRUB_DISK_SECTOR_SIZE]; + unsigned int nsectors_wanted; + char *device_name; + int ioctl_err; + + if (embed_type != GRUB_EMBED_PCBIOS) + return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, + N_("ext2 currently supports only PC-BIOS embedding")); + + if ( device->disk->partition) + device_name = grub_xasprintf ("%s,%s", device->disk->name, + grub_partition_get_name(device->disk->partition)); + else + device_name = grub_xasprintf ("%s", device->disk->name); + + nsectors_wanted = *nsectors; + + grub_util_info (N_("Embedding %d/%d sectors into ext2 filesystem of %s"), + nsectors_wanted, max_nsectors, device_name); + + /* [1] Check, if the existing boot loader inode exists and has the + wanted size: */ + err = grub_ext2_read_sectorlist (device, EXT2_BOOT_LOADER_INO, + nsectors, sectors); + if (!err && *nsectors >= nsectors_wanted && *nsectors <= max_nsectors) { + grub_util_info(N_("Reusing existing boot loader inode" + " offering %d sectors"), + *nsectors); + grub_free (device_name); + return GRUB_ERR_NONE; /* YES! Everything is fine. */ + } + + /* [2] We have to create a new boot loader inode. */ + + /* [2.1] Check if device is mounted and get mountpoint: */ + mountpoint = NULL; + entries = grub_read_mountinfo (); + if ( entries ) { + for ( i = 0; entries[i].enc_root[0] != 0; i++) { + char *grub_dev_of_mount; + grub_errno = GRUB_ERR_NONE; /* Clear errno set previously */ + grub_dev_of_mount = grub_util_get_grub_dev (entries[i].device); + if ( grub_dev_of_mount ) { + if ( grub_strcmp (grub_dev_of_mount, device_name) == 0 ) { + mountpoint = grub_strdup (entries[i].enc_path); + break; + } + } + } + free (entries); + } + + grub_errno = GRUB_ERR_NONE; /* Clear errno set previously */ + + if (!mountpoint) { + /* We were unable to find the mountpoint for the device of the + filesystem. Maybe it is not mounted? */ + return grub_error (GRUB_ERR_UNKNOWN_DEVICE, + N_("We are unable to find the mountpoint of %s"), + device_name); + + /** ToDo: Try to mount the filesystem to a temporary location. */ + } + + grub_free (device_name); + + /* [2.2] Create a new (temporary) file, which then gets the boot loader: */ + core_name = grub_util_path_concat (2, mountpoint, ".core.img"); + free (mountpoint); + out = grub_util_fd_open (core_name, GRUB_UTIL_FD_O_WRONLY + | GRUB_UTIL_FD_O_CREATTRUNC); + if (!GRUB_UTIL_FD_IS_VALID (out)) { + return grub_error (GRUB_ERR_BAD_FS, + N_("cannot create `%s': %s"), core_name, + grub_util_fd_strerror ()); + } + for ( i = 0; i < max_nsectors; i++ ) { + grub_util_fd_write (out, diskbuffer, GRUB_DISK_SECTOR_SIZE); + } + grub_util_fd_sync (out); + + /* [2.3] Make that file to the new boot loader inode by swapping the + file of "out" with the boot loader inode: */ + ioctl_err = ioctl (out, EXT4_IOC_SWAP_BOOT); + if ( ioctl_err ) { + err = grub_error (GRUB_ERR_BAD_FS, + N_("Error in ioctl(EXT4_IOC_SWAP_BOOT);" + "you need Linux >= 3.10: %s"), + grub_util_fd_strerror ()); + grub_util_fd_close (out); + grub_util_unlink (core_name); + return err; + } + grub_util_fd_close (out); + + /* [2.4] Unlink the core file, now containing the previous boot loader. */ + grub_util_unlink (core_name); + + /* [2.5] Invalidate disk cache and read block list again: */ + grub_disk_cache_invalidate_all (); + + err = grub_ext2_read_sectorlist (device, EXT2_BOOT_LOADER_INO, + nsectors, sectors); + if ( err ) { + return grub_error (GRUB_ERR_BAD_FS, + N_("unable to read boot loader inode")); + } + + grub_util_info (N_("Created new boot loader inode offering %d sectors"), + *nsectors); + + return GRUB_ERR_NONE; +} +#endif + static struct grub_fs grub_ext2_fs = @@ -990,6 +1191,7 @@ static struct grub_fs grub_ext2_fs = .uuid = grub_ext2_uuid, .mtime = grub_ext2_mtime, #ifdef GRUB_UTIL + .embed = grub_ext2_embed, .reserved_first_sector = 1, .blocklist_install = 1, #endif diff --git a/util/setup.c b/util/setup.c index 9fb91a8..3f4a007 100644 --- a/util/setup.c +++ b/util/setup.c @@ -509,8 +509,10 @@ SETUP (const char *dir, if (!err && nsec < core_sectors) { err = grub_error (GRUB_ERR_OUT_OF_RANGE, - N_("Your embedding area is unusually small. " - "core.img won't fit in it.")); + N_("Your embedding area is unusually small " + "(only %d sectors). " + "core.img won't fit in it (needs %d sectors)."), + nsec, core_sectors); } if (err) @@ -583,10 +585,13 @@ SETUP (const char *dir, } /* Write the core image onto the disk. */ - for (i = 0; i < nsec; i++) + for (i = 0; i < nsec; i++) { + grub_util_info ("writing core.img/%d to sector %" PRIuGRUB_UINT64_T, i, + sectors[i]); grub_disk_write (dest_dev->disk, sectors[i], 0, GRUB_DISK_SECTOR_SIZE, core_img + i * GRUB_DISK_SECTOR_SIZE); + } grub_free (sectors); -- 1.8.1.4 _______________________________________________ Grub-devel mailing list Grub-devel@gnu.org https://lists.gnu.org/mailman/listinfo/grub-devel