diff --git a/grub-core/fs/btrfs.c b/grub-core/fs/btrfs.c index ba3ea96a1..937153306 100644 --- a/grub-core/fs/btrfs.c +++ b/grub-core/fs/btrfs.c @@ -2189,6 +2189,33 @@ grub_btrfs_label (grub_device_t device, char **label) } #ifdef GRUB_UTIL + +struct embed_region { + unsigned int start; + unsigned int secs; +}; + +/* + * https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs(5)#BOOTLOADER_SUPPORT + * The first 1 MiB on each device is unused with the exception of primary + * superblock that is on the offset 64 KiB and spans 4 KiB. + */ + +static const struct { + struct embed_region available; + struct embed_region used[6]; +} btrfs_head = { + .available = {0, GRUB_DISK_KiB_TO_SECTORS (1024)}, /* The first 1 MiB. */ + .used = { + {0, 1}, /* boot.S. */ + {GRUB_DISK_KiB_TO_SECTORS (64) - 1, 1}, /* Overflow guard. */ + {GRUB_DISK_KiB_TO_SECTORS (64), GRUB_DISK_KiB_TO_SECTORS (4)}, /* 4 KiB superblock. */ + {GRUB_DISK_KiB_TO_SECTORS (68), 1}, /* Overflow guard. */ + {GRUB_DISK_KiB_TO_SECTORS (1024) - 1, 1}, /* Overflow guard. */ + {0, 0} /* Array terminator. */ + } +}; + static grub_err_t grub_btrfs_embed (grub_device_t device __attribute__ ((unused)), unsigned int *nsectors, @@ -2196,25 +2223,62 @@ grub_btrfs_embed (grub_device_t device __attribute__ ((unused)), grub_embed_type_t embed_type, grub_disk_addr_t **sectors) { - unsigned i; + unsigned int i, j, n = 0; + const struct embed_region *u; + grub_disk_addr_t *map; if (embed_type != GRUB_EMBED_PCBIOS) return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "BtrFS currently supports only PC-BIOS embedding"); - if (64 * 2 - 1 < *nsectors) - return grub_error (GRUB_ERR_OUT_OF_RANGE, - N_("your core.img is unusually large. " - "It won't fit in the embedding area")); - - *nsectors = 64 * 2 - 1; - if (*nsectors > max_nsectors) - *nsectors = max_nsectors; - *sectors = grub_calloc (*nsectors, sizeof (**sectors)); - if (!*sectors) + map = grub_calloc (btrfs_head.available.secs, sizeof (*map)); + if (map == NULL) return grub_errno; - for (i = 0; i < *nsectors; i++) - (*sectors)[i] = i + 1; + + /* + * Populating the map array so that it can be used to index if a disk + * address is available to embed: + * - 0: available, + * - 1: unavailable. + */ + for (u = btrfs_head.used; u->secs; ++u) + { + unsigned int end = u->start + u->secs; + + if (end > btrfs_head.available.secs) + end = btrfs_head.available.secs; + for (i = u->start; i < end; ++i) + map[i] = 1; + } + + /* Adding up n until it matches total size of available embedding area. */ + for (i = 0; i < btrfs_head.available.secs; ++i) + if (map[i] == 0) + n++; + + if (n < *nsectors) + { + grub_free (map); + return grub_error (GRUB_ERR_OUT_OF_RANGE, + N_("your core.img is unusually large. " + "It won't fit in the embedding area")); + } + + if (n > max_nsectors) + n = max_nsectors; + + /* + * Populating the array so that it can used to index disk block address for + * an image file's offset to be embedded on disk (the unit is in sectors): + * - i: The disk block address relative to btrfs_head.available.start, + * - j: The offset in image file. + */ + for (i = 0, j = 0; i < btrfs_head.available.secs && j < n; ++i) + if (map[i] == 0) + map[j++] = btrfs_head.available.start + i; + + *nsectors = n; + *sectors = map; return GRUB_ERR_NONE; } diff --git a/include/grub/disk.h b/include/grub/disk.h index f95aca929..06210a704 100644 --- a/include/grub/disk.h +++ b/include/grub/disk.h @@ -182,6 +182,8 @@ typedef struct grub_disk_memberlist *grub_disk_memberlist_t; /* Return value of grub_disk_native_sectors() in case disk size is unknown. */ #define GRUB_DISK_SIZE_UNKNOWN 0xffffffffffffffffULL +#define GRUB_DISK_KiB_TO_SECTORS(x) ((x) << (10 - GRUB_DISK_SECTOR_BITS)) + /* Convert sector number from one sector size to another. */ static inline grub_disk_addr_t grub_convert_sector (grub_disk_addr_t sector,