limine-install: Add more sanity checks and give the choice to install stage 2 to a partition with GPT
diff --git a/README.md b/README.md
index 0b66e108..af989339 100644
--- a/README.md
+++ b/README.md
@@ -87,18 +87,21 @@ limine-install <bootloader image> <path to device/image>
Where `<bootloader image>` is the path to a `limine.bin` file.
### GPT
-If using a GPT formatted device, it will be necessary to create an extra partition
-(of at least 32K in size) to store stage 2 code. Then it will be necessary to tell
-`limine-install` where this partition is located by specifying the start sector
-number (in decimal).
+If using a GPT formatted device, there are 2 options one can follow for installation:
+* Specifying a dedicated stage 2 partition.
+* Letting `limine-install` attempt to embed stage 2 within GPT structures.
+
+In case one wants to specify a stage 2 partition, create a partition on the GPT
+device of at least 32KiB in size, and pass the 1-based number of the partition
+to `limine-install` as a third argument; such as:
```bash
-fdisk <device> # Create bootloader partition using your favourite method
-limine-install <bootloader image> <path to device/image> <start sector of boot partition> <sector size>
+limine-install <bootloader image> <path to device/image> <partition 1-based number>
```
-The `<sector size>` argument is optional. Use it to specify the sector size in bytes
-if it is not Limine's expected default of 512 bytes.
+In case one wants to let `limine-install` embed stage 2 within GPT's structures,
+simply omit the partition number, and invoke `limine-install` the same as one would
+do for an MBR partitioned device.
### Configuration
Then make sure the device/image contains at least 1 partition formatted in
@@ -125,7 +128,7 @@ echfs-utils -m -p0 test.img import path/to/limine.cfg limine.cfg
echfs-utils -m -p0 test.img import path/to/kernel.elf kernel.elf
echfs-utils -m -p0 test.img import <path to file> <path in image>
...
-limine-install test.img
+./limine-install limine.bin test.img
```
One can get `echfs-utils` by installing https://github.com/qword-os/echfs.
diff --git a/limine-install.c b/limine-install.c
index e45c1c0a..40af6690 100644
--- a/limine-install.c
+++ b/limine-install.c
@@ -30,6 +30,19 @@ struct gpt_table_header {
uint32_t partition_entry_array_crc32;
} __attribute__((packed));
+struct gpt_entry {
+ uint64_t partition_type_guid[2];
+
+ uint64_t unique_partition_guid[2];
+
+ uint64_t starting_lba;
+ uint64_t ending_lba;
+
+ uint64_t attributes;
+
+ uint16_t partition_name[36];
+} __attribute__((packed));
+
// This table from https://web.mit.edu/freebsd/head/sys/libkern/crc32.c
const uint32_t crc32_table[] = {
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
@@ -95,7 +108,7 @@ int main(int argc, char *argv[]) {
uint8_t orig_mbr[70], timestamp[6];
if (argc < 3) {
- printf("Usage: %s <bootloader image> <device>\n", argv[0]);
+ printf("Usage: %s <bootloader image> <device> [GPT partition index]\n", argv[0]);
return 1;
}
@@ -165,54 +178,105 @@ int main(int argc, char *argv[]) {
// Default split of stage2 for MBR (consecutive in post MBR gap)
uint64_t stage2_loc_a = 512;
uint64_t stage2_loc_b = stage2_loc_a + stage2_size_a;
- if (stage2_loc_b & 512)
+ if (stage2_loc_b & (512 - 1))
stage2_loc_b = (stage2_loc_b + 512) & ~(512 - 1);
if (gpt) {
- stage2_loc_a = (gpt_header.partition_entry_lba + 32) * lb_size;
- stage2_loc_a -= stage2_size_a;
- stage2_loc_a &= ~(lb_size - 1);
- stage2_loc_b = (secondary_gpt_header.partition_entry_lba + 32) * lb_size;
- stage2_loc_b -= stage2_size_b;
- stage2_loc_b &= ~(lb_size - 1);
-
- size_t partition_entries_per_lb =
- lb_size / gpt_header.size_of_partition_entry;
- size_t new_partition_array_lba_size =
- stage2_loc_a / lb_size - gpt_header.partition_entry_lba;
- size_t new_partition_entry_count =
- new_partition_array_lba_size * partition_entries_per_lb;
-
- uint8_t *partition_array =
- malloc(new_partition_entry_count * gpt_header.size_of_partition_entry);
- assert(partition_array);
-
- fseek(device, gpt_header.partition_entry_lba * lb_size, SEEK_SET);
- fread(partition_array,
- new_partition_entry_count * gpt_header.size_of_partition_entry,
- 1, device);
-
- uint32_t crc32_partition_array =
- crc32(partition_array,
- new_partition_entry_count * gpt_header.size_of_partition_entry);
-
- free(partition_array);
-
- gpt_header.partition_entry_array_crc32 = crc32_partition_array;
- gpt_header.number_of_partition_entries = new_partition_entry_count;
- gpt_header.crc32 = 0;
- gpt_header.crc32 = crc32(&gpt_header, sizeof(struct gpt_table_header));
- fseek(device, lb_size, SEEK_SET);
- fwrite(&gpt_header, sizeof(struct gpt_table_header), 1, device);
-
- secondary_gpt_header.partition_entry_array_crc32 = crc32_partition_array;
- secondary_gpt_header.number_of_partition_entries =
- new_partition_entry_count;
- secondary_gpt_header.crc32 = 0;
- secondary_gpt_header.crc32 =
- crc32(&secondary_gpt_header, sizeof(struct gpt_table_header));
- fseek(device, lb_size * gpt_header.alternate_lba, SEEK_SET);
- fwrite(&secondary_gpt_header, sizeof(struct gpt_table_header), 1, device);
+ if (argc > 3) {
+ uint32_t partition_num;
+ sscanf(argv[3], "%" SCNu32, &partition_num);
+ partition_num--;
+ if (partition_num > gpt_header.number_of_partition_entries) {
+ fprintf(stderr, "error: Partition number is too large.\n");
+ abort();
+ }
+
+ struct gpt_entry gpt_entry;
+ fseek(device, (gpt_header.partition_entry_lba * lb_size)
+ + (partition_num * sizeof(struct gpt_entry)), SEEK_SET);
+ fread(&gpt_entry, sizeof(struct gpt_entry), 1, device);
+
+ if (gpt_entry.unique_partition_guid[0] == 0 &&
+ gpt_entry.unique_partition_guid[1] == 0) {
+ fprintf(stderr, "error: No such partition.\n");
+ abort();
+ }
+
+ fprintf(stderr, "GPT partition specified. Installing there instead of embedding.\n");
+
+ stage2_loc_a = gpt_entry.starting_lba * lb_size;
+ stage2_loc_b = stage2_loc_a + stage2_size_a;
+ if (stage2_loc_b & (lb_size - 1))
+ stage2_loc_b = (stage2_loc_b + lb_size) & ~(lb_size - 1);
+ } else {
+ fprintf(stderr, "GPT partition NOT specified. Attempting GPT embedding.\n");
+
+ ssize_t max_partition_entry_used = -1;
+ for (ssize_t i = 0; i < gpt_header.number_of_partition_entries; i++) {
+ struct gpt_entry gpt_entry;
+ fseek(device, (gpt_header.partition_entry_lba * lb_size)
+ + (i * sizeof(struct gpt_entry)), SEEK_SET);
+ fread(&gpt_entry, sizeof(struct gpt_entry), 1, device);
+
+ if (gpt_entry.unique_partition_guid[0] != 0 ||
+ gpt_entry.unique_partition_guid[1] != 0) {
+ if (i > max_partition_entry_used)
+ max_partition_entry_used = i;
+ }
+ }
+
+ stage2_loc_a = (gpt_header.partition_entry_lba + 32) * lb_size;
+ stage2_loc_a -= stage2_size_a;
+ stage2_loc_a &= ~(lb_size - 1);
+ stage2_loc_b = (secondary_gpt_header.partition_entry_lba + 32) * lb_size;
+ stage2_loc_b -= stage2_size_b;
+ stage2_loc_b &= ~(lb_size - 1);
+
+ size_t partition_entries_per_lb =
+ lb_size / gpt_header.size_of_partition_entry;
+ size_t new_partition_array_lba_size =
+ stage2_loc_a / lb_size - gpt_header.partition_entry_lba;
+ size_t new_partition_entry_count =
+ new_partition_array_lba_size * partition_entries_per_lb;
+
+ if ((ssize_t)new_partition_array_lba_size <= max_partition_entry_used) {
+ fprintf(stderr, "error: Cannot embed because there are too many used partition entries.\n");
+ abort();
+ }
+
+ uint8_t *partition_array =
+ malloc(new_partition_entry_count * gpt_header.size_of_partition_entry);
+ assert(partition_array);
+
+ fseek(device, gpt_header.partition_entry_lba * lb_size, SEEK_SET);
+ fread(partition_array,
+ new_partition_entry_count * gpt_header.size_of_partition_entry,
+ 1, device);
+
+ uint32_t crc32_partition_array =
+ crc32(partition_array,
+ new_partition_entry_count * gpt_header.size_of_partition_entry);
+
+ free(partition_array);
+
+ gpt_header.partition_entry_array_crc32 = crc32_partition_array;
+ gpt_header.number_of_partition_entries = new_partition_entry_count;
+ gpt_header.crc32 = 0;
+ gpt_header.crc32 = crc32(&gpt_header, sizeof(struct gpt_table_header));
+ fseek(device, lb_size, SEEK_SET);
+ fwrite(&gpt_header, sizeof(struct gpt_table_header), 1, device);
+
+ secondary_gpt_header.partition_entry_array_crc32 = crc32_partition_array;
+ secondary_gpt_header.number_of_partition_entries =
+ new_partition_entry_count;
+ secondary_gpt_header.crc32 = 0;
+ secondary_gpt_header.crc32 =
+ crc32(&secondary_gpt_header, sizeof(struct gpt_table_header));
+ fseek(device, lb_size * gpt_header.alternate_lba, SEEK_SET);
+ fwrite(&secondary_gpt_header, sizeof(struct gpt_table_header), 1, device);
+ }
+ } else {
+ fprintf(stderr, "Installing to MBR.\n");
}
fprintf(stderr, "Stage 2 to be located at 0x%" PRIx64 " and 0x%" PRIx64 ".\n",
