Skip to content

Commit

Permalink
image-hd: add forced-primary flag for higher MBR layout flexibility
Browse files Browse the repository at this point in the history
The current limitation of Genimage is that it is not able to create
MBR images that have primary partitions that start after a logical
partition. This can be useful for images that can be later resized based
on the actual device size - for this operation the partition must be at
the end of the device, and if it is present in a logical partition, it
must be resized first, making it a two-step process.

This commit adds the "forced-primary" flag which can be used to indicate
that the partition should be put into the disk's MBR instead of creating
another logical partition. Validation ensures that this syntax allows to
create such partitions only after an existing logical partition, and
that the maximum number of MBR entries woudn't be exceeded by doing so.

Test cases for valid and invalid configuiration has been added. Also
added few more details in the debug print to make it more obvious how
the MBR/EBR layout looks like.

Signed-off-by: Jan Čermák <[email protected]>
  • Loading branch information
sairon committed May 28, 2024
1 parent 0d9f207 commit c570141
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 11 deletions.
5 changes: 5 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ Partition options:
:bootable: Boolean specifying whether to set the bootable flag.
:in-partition-table: Boolean specifying whether to include this partition in
the partition table. Defaults to true.
:forced-primary: Force this partition to be a primary partition in the
MBR partition table, useful when the extended partition should be
followed by primary partitions. If there are more partitions
defined after the first forced-primary, they must be also defined
as forced-primary. Defaults to false.
:partition-uuid: UUID string used by GPT partition tables to specify the partition
id. Defaults to a random value.
:partition-type-uuid: String used by GPT partition tables to specify the partition type.
Expand Down
2 changes: 2 additions & 0 deletions genimage.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ static cfg_opt_t partition_opts[] = {
CFG_STR("align", NULL, CFGF_NONE),
CFG_INT("partition-type", 0, CFGF_NONE),
CFG_BOOL("bootable", cfg_false, CFGF_NONE),
CFG_BOOL("forced-primary", cfg_false, CFGF_NONE),
CFG_BOOL("read-only", cfg_false, CFGF_NONE),
CFG_BOOL("hidden", cfg_false, CFGF_NONE),
CFG_BOOL("no-automount", cfg_false, CFGF_NONE),
Expand Down Expand Up @@ -396,6 +397,7 @@ static int parse_partitions(struct image *image, cfg_t *imagesec)
part->align = cfg_getint_suffix(partsec, "align");
part->partition_type = cfg_getint(partsec, "partition-type");
part->bootable = cfg_getbool(partsec, "bootable");
part->forced_primary = cfg_getbool(partsec, "forced-primary");
part->read_only = cfg_getbool(partsec, "read-only");
part->hidden = cfg_getbool(partsec, "hidden");
part->no_automount = cfg_getbool(partsec, "no-automount");
Expand Down
1 change: 1 addition & 0 deletions genimage.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ struct partition {
unsigned char partition_type;
cfg_bool_t bootable;
cfg_bool_t extended;
cfg_bool_t forced_primary;
cfg_bool_t read_only;
cfg_bool_t hidden;
cfg_bool_t no_automount;
Expand Down
63 changes: 53 additions & 10 deletions image-hd.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ struct hdimage {
unsigned int extended_partition;
unsigned long long align;
unsigned long long extended_lba;
unsigned long long extended_size;
uint32_t disksig;
const char *disk_uuid;
int table_type;
Expand Down Expand Up @@ -137,7 +138,7 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions)
struct hdimage *hd = image->handler_priv;
struct mbr_tail mbr;
struct partition *part;
int ret, i = 0;
int ret, i = 0, extended_written = 0;

if (hd->table_type == TYPE_HYBRID) {
image_info(image, "writing hybrid MBR\n");
Expand All @@ -160,6 +161,9 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions)
if (hd->table_type == TYPE_HYBRID && part->extended)
continue;

if (part->extended && extended_written)
continue;

entry = &mbr.part_entry[i];

entry->boot = part->bootable ? 0x80 : 0x00;
Expand All @@ -171,12 +175,21 @@ static int hdimage_insert_mbr(struct image *image, struct list_head *partitions)
else {
entry->partition_type = 0x0F;
entry->relative_sectors = (hd->extended_lba)/512;
entry->total_sectors = (image->size - hd->extended_lba)/512;
entry->total_sectors = hd->extended_size/512;
}
hdimage_setup_chs(entry);

if (part->extended)
break;
image_debug(image, "[MBR entry %d]: type=%x start=%d size=%d\n",
i, entry->partition_type,
entry->relative_sectors, entry->total_sectors);

if (part->extended) {
if (!extended_written) {
extended_written = 1;
i++;
}
continue;
}
i++;
}

Expand Down Expand Up @@ -215,8 +228,9 @@ static int hdimage_insert_ebr(struct image *image, struct partition *part)
struct mbr_partition_entry *entry;
char ebr[4*sizeof(struct mbr_partition_entry)+2], *part_table;
int ret;
unsigned long long ebr_offset = part->offset - hd->align + 446;

image_info(image, "writing EBR\n");
image_debug(image, "writing EBR to sector %llu\n", ebr_offset / 512);

memset(ebr, 0, sizeof(ebr));
part_table = ebr;
Expand Down Expand Up @@ -245,7 +259,7 @@ static int hdimage_insert_ebr(struct image *image, struct partition *part)
part_table[1] = 0xaa;

ret = insert_data(image, ebr, imageoutfile(image), sizeof(ebr),
part->offset - hd->align + 446);
ebr_offset);
if (ret) {
image_error(image, "failed to write EBR\n");
return ret;
Expand Down Expand Up @@ -756,6 +770,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg)
struct partition *autoresize_part = NULL;
int has_extended;
unsigned int partition_table_entries = 0, hybrid_entries = 0;
unsigned int mbr_entries = 0, forced_primary_entries = 0;
unsigned long long now = 0;
const char *disk_signature, *table_type;
struct hdimage *hd = xzalloc(sizeof(*hd));
Expand Down Expand Up @@ -821,11 +836,38 @@ static int hdimage_setup(struct image *image, cfg_t *cfg)
"multiple of 1 sector (512 bytes)\n", hd->align);
return -EINVAL;
}
if (hd->table_type == TYPE_MBR && hd->extended_partition)
mbr_entries = hd->extended_partition;
list_for_each_entry(part, &image->partitions, list) {
if (hd->table_type == TYPE_NONE)
part->in_partition_table = false;
if (part->in_partition_table)
++partition_table_entries;
if (hd->table_type == TYPE_MBR && part->in_partition_table) {
if (!hd->extended_partition && partition_table_entries > 4) {
hd->extended_partition = mbr_entries = 4;
}
if (part->forced_primary) {
++forced_primary_entries;
++mbr_entries;
if (partition_table_entries <= hd->extended_partition) {
image_error(image, "partition %s: forced-primary can only be used for "
"partitions following the extended partition\n",
part->name);
return -EINVAL;
}
} else if (forced_primary_entries > 0) {
image_error(image,
"cannot create non-primary partition %s after forced-primary partition\n",
part->name);
return -EINVAL;
}
if (mbr_entries > 4) {
image_error(image, "too many primary partitions\n");
return -EINVAL;
}
image_debug(image, "mbr_entries %d\n", mbr_entries);
}
if (!part->align)
part->align = (part->in_partition_table || hd->table_type == TYPE_NONE) ? hd->align : 1;
if (part->in_partition_table && part->align % hd->align) {
Expand All @@ -834,9 +876,6 @@ static int hdimage_setup(struct image *image, cfg_t *cfg)
part->align, part->name, hd->align);
}
}
if (hd->table_type == TYPE_MBR && !hd->extended_partition &&
partition_table_entries > 4)
hd->extended_partition = 4;
has_extended = hd->extended_partition > 0;

if (hd->disk_uuid) {
Expand Down Expand Up @@ -961,7 +1000,7 @@ static int hdimage_setup(struct image *image, cfg_t *cfg)
/* reserve space for extended boot record if necessary */
if (part->in_partition_table)
++partition_table_entries;
part->extended = has_extended && part->in_partition_table &&
part->extended = !part->forced_primary && has_extended && part->in_partition_table &&
(partition_table_entries >= hd->extended_partition);
if (part->extended) {
now += hd->align;
Expand Down Expand Up @@ -1053,6 +1092,10 @@ static int hdimage_setup(struct image *image, cfg_t *cfg)
}
else if (part->extended)
hd->file_size = part->offset - hd->align + 512;

if (part->extended) {
hd->extended_size = part->offset + part->size - hd->extended_lba;
}
}

if (hybrid_entries > 3) {
Expand Down
33 changes: 33 additions & 0 deletions test/hdimage-fail10.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
image test.hdimage {
hdimage {
align = 1M
extended-partition = 3
}
partition primary1 {
image = "part1.img"
partition-type = 0x83
}
partition primary2 {
image = "part1.img"
partition-type = 0x83
}
partition extended1 {
image = "part1.img"
partition-type = 0x83
}
partition extended2 {
image = "part1.img"
partition-type = 0x83
}
partition primary3 {
image = "part1.img"
partition-type = 0x83
forced-primary = "yes"
}
partition primary4 {
image = "part1.img"
partition-type = 0x83
/* would be 5th primary partition */
forced-primary = "yes"
}
}
32 changes: 32 additions & 0 deletions test/hdimage-fail11.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
image test.hdimage {
hdimage {
align = 1M
extended-partition = 1
}
partition extended1 {
image = "part1.img"
partition-type = 0x83
}
partition extended2 {
image = "part1.img"
partition-type = 0x83
}
partition extended3 {
image = "part1.img"
partition-type = 0x83
}
partition extended4 {
image = "part1.img"
partition-type = 0x83
}
partition primary2 {
image = "part1.img"
partition-type = 0x83
forced-primary = "yes"
}
partition extended5 {
image = "part1.img"
partition-type = 0x83
/* extended partition would overlap the forced-primary one */
}
}
28 changes: 28 additions & 0 deletions test/hdimage-fail8.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
image test.hdimage {
hdimage {
align = 1M
extended-partition = 1
}
partition part1 {
image = "part1.img"
partition-type = 0x83
forced-primary = "yes"
/* forced-primary can be only used for partitions defined after the extended partition */
}
partition part2 {
image = "part1.img"
partition-type = 0x83
}
partition part3 {
image = "part1.img"
partition-type = 0x83
}
partition part4 {
image = "part1.img"
partition-type = 0x83
}
partition part5 {
image = "part1.img"
partition-type = 0x83
}
}
27 changes: 27 additions & 0 deletions test/hdimage-fail9.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
image test.hdimage {
hdimage {
align = 1M
}
partition primary1 {
image = "part1.img"
partition-type = 0x83
}
partition primary2 {
image = "part1.img"
partition-type = 0x83
}
partition primary3 {
image = "part1.img"
partition-type = 0x83
}
partition primary4 {
image = "part1.img"
partition-type = 0x83
}
partition primary5 {
image = "part1.img"
partition-type = 0x83
/* part4 is implicitly extended -> too many primary entries */
forced-primary = "yes"
}
}
47 changes: 47 additions & 0 deletions test/hdimage-forced-primary.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
image test.hdimage {
hdimage {
align = 1M
disk-signature = 0x12345678
extended-partition = 2
}
partition part1 {
image = "part1.img"
partition-type = 0xc
bootable = "yes"
}
/*
* partition 2 will be the extended partition entry
* partitions 3-4 will be primary partitions at the end
* partition 5 is first logical partition of the extended partition
*/
partition part5-logical {
image = "part1.img"
partition-type = 0x83
}
partition part6-logical {
image = "part2.img"
partition-type = 0x83
}
partition part7-logical {
image = "part1.img"
partition-type = 0x83
}
partition part8-logical {
image = "part2.img"
partition-type = 0x83
}
partition part9-logical {
image = "part1.img"
partition-type = 0x83
}
partition part3 {
image = "part1.img"
partition-type = 0x83
forced-primary = "yes"
}
partition part4 {
image = "part2.img"
partition-type = 0x82
forced-primary = "yes"
}
}
10 changes: 10 additions & 0 deletions test/hdimage-forced-primary.fdisk
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Disk identifier: 0x12345678
images/test.hdimage1:start=2048,size=2048,type=c,bootable
images/test.hdimage2:start=4096,size=20480,type=f
images/test.hdimage3:start=24576,size=2048,type=83
images/test.hdimage4:start=26624,size=2048,type=82
images/test.hdimage5:start=6144,size=2048,type=83
images/test.hdimage6:start=10240,size=2048,type=83
images/test.hdimage7:start=14336,size=2048,type=83
images/test.hdimage8:start=18432,size=2048,type=83
images/test.hdimage9:start=22528,size=2048,type=83
14 changes: 13 additions & 1 deletion test/hdimage.test
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,11 @@ test_expect_success "hdimage syntax" "
test_must_fail run_genimage hdimage-fail4.config &&
test_must_fail run_genimage hdimage-fail5.config &&
test_must_fail run_genimage hdimage-fail6.config &&
test_must_fail run_genimage hdimage-fail7.config
test_must_fail run_genimage hdimage-fail7.config &&
test_must_fail run_genimage hdimage-fail8.config &&
test_must_fail run_genimage hdimage-fail9.config &&
test_must_fail run_genimage hdimage-fail10.config &&
test_must_fail run_genimage hdimage-fail11.config
"

setup_gpt_files() {
Expand Down Expand Up @@ -163,6 +167,14 @@ test_expect_success "hdimage no-partition" "
test_cmp 'hdimage-nopart.hexdump' '${testdir}/hdimage-nopart.hexdump'
"

test_expect_success "hdimage forced-primary" "
setup_test_images &&
run_genimage hdimage-forced-primary.config &&
sfdisk_validate images/test.hdimage &&
sanitized_fdisk_sfdisk images/test.hdimage > hdimage.fdisk &&
test_cmp '${testdir}/hdimage-forced-primary.fdisk' hdimage.fdisk
"

test_done

# vim: syntax=sh

0 comments on commit c570141

Please sign in to comment.