>From 8d6dd4288064e9b880eccb1a6b1b7a6b03f2ba96 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones"
Date: Sat, 19 Jan 2019 10:30:28 +0000
Subject: [PATCH] partitioning: Support MBR logical partitions.
XXX NEEDS TESTS
---
.../nbdkit-partitioning-plugin.pod | 25 +---
plugins/partitioning/virtual-disk.h | 15 +-
plugins/partitioning/partition-gpt.c | 2 +-
plugins/partitioning/partition-mbr.c | 134 +++++++++++++++---
plugins/partitioning/partitioning.c | 31 ++--
plugins/partitioning/virtual-disk.c | 42 +++++-
6 files changed, 184 insertions(+), 65 deletions(-)
diff --git a/plugins/partitioning/nbdkit-partitioning-plugin.pod b/plugins/partitioning/nbdkit-partitioning-plugin.pod
index 57a1133..edb0776 100644
--- a/plugins/partitioning/nbdkit-partitioning-plugin.pod
+++ b/plugins/partitioning/nbdkit-partitioning-plugin.pod
@@ -27,23 +27,12 @@ access use the I<-r> flag.
=head2 Partition table type
-You can choose either an MBR partition table, which is limited to 4
-partitions, or a GPT partition table. In theory GPT supports an
-unlimited number of partitions.
-
-The rule for selecting the partition table type is:
+Using the C parameter you can choose either an MBR or
+a GPT partition table. If this parameter is I present then:
=over 4
-=item C parameter on the command line
-
-⇒ MBR is selected
-
-=item C parameter on the command line
-
-⇒ GPT is selected
-
-=item else, number of files E 4
+=item number of files E 4
⇒ GPT
@@ -131,7 +120,9 @@ C<./> (absolute paths do not need modification).
=item B
Add an MBR (DOS-style) partition table. The MBR format is maximally
-compatible with all clients, but only supports up to 4 partitions.
+compatible with all clients. If there are E 4 partitions then an
+extended partition is created
+(L).
=item B
@@ -163,10 +154,6 @@ a Linux filesystem.
=head1 LIMITS
-This plugin only supports B MBR partitions, hence the limit
-of 4 partitions with MBR. This might be increased in future if we
-implement support for logical/extended MBR partitions.
-
Although this plugin can create GPT partition tables containing more
than 128 GPT partitions (in fact, unlimited numbers of partitions),
some clients will not be able to handle this.
diff --git a/plugins/partitioning/virtual-disk.h b/plugins/partitioning/virtual-disk.h
index 3860f46..f59df70 100644
--- a/plugins/partitioning/virtual-disk.h
+++ b/plugins/partitioning/virtual-disk.h
@@ -34,6 +34,7 @@
#ifndef NBDKIT_VIRTUAL_DISK_H
#define NBDKIT_VIRTUAL_DISK_H
+#include
#include
#include
@@ -103,7 +104,7 @@ extern struct file *files;
extern size_t nr_files;
extern struct regions regions;
-extern unsigned char *primary, *secondary;
+extern unsigned char *primary, *secondary, **ebr;
/* Main entry point called after files array has been populated. */
extern int create_virtual_disk_layout (void);
@@ -114,16 +115,16 @@ extern int create_virtual_disk_layout (void);
extern int parse_guid (const char *str, char *out)
__attribute__((__nonnull__ (1, 2)));
-/* Internal functions for creating MBR and GPT layouts. These are
- * published here because the GPT code calls into the MBR code, but
- * are not meant to be called from the main plugin.
+/* Internal function for creating a single MBR PTE. The GPT code
+ * calls this for creating the protective MBR.
*/
-extern void create_mbr_partition_table (unsigned char *out)
- __attribute__((__nonnull__ (1)));
extern void create_mbr_partition_table_entry (const struct region *,
- int bootable, int partition_id,
+ bool bootable, int partition_id,
unsigned char *)
__attribute__((__nonnull__ (1, 4)));
+
+/* Create MBR or GPT layout. */
+extern void create_mbr_layout (void);
extern void create_gpt_layout (void);
#endif /* NBDKIT_VIRTUAL_DISK_H */
diff --git a/plugins/partitioning/partition-gpt.c b/plugins/partitioning/partition-gpt.c
index 5fb7602..be52e72 100644
--- a/plugins/partitioning/partition-gpt.c
+++ b/plugins/partitioning/partition-gpt.c
@@ -210,7 +210,7 @@ create_gpt_protective_mbr (unsigned char *out)
region.end = end;
region.len = region.end - region.start + 1;
- create_mbr_partition_table_entry (®ion, 0, 0xee, &out[0x1be]);
+ create_mbr_partition_table_entry (®ion, false, 0xee, &out[0x1be]);
/* Boot signature. */
out[0x1fe] = 0x55;
diff --git a/plugins/partitioning/partition-mbr.c b/plugins/partitioning/partition-mbr.c
index d3a0d78..6f76432 100644
--- a/plugins/partitioning/partition-mbr.c
+++ b/plugins/partitioning/partition-mbr.c
@@ -49,27 +49,125 @@
#include "regions.h"
#include "virtual-disk.h"
-/* Create the partition table. */
+static const struct region *find_file_region (size_t i, size_t *j);
+static const struct region *find_ebr_region (size_t i, size_t *j);
+
+/* Create the MBR and optionally EBRs. */
void
-create_mbr_partition_table (unsigned char *out)
+create_mbr_layout (void)
{
- size_t i, j;
-
- for (j = 0; j < nr_regions (®ions); ++j) {
- const struct region *region = get_region (®ions, j);
-
- if (region->type == region_file) {
- i = region->u.i;
- assert (i < 4);
- create_mbr_partition_table_entry (region, i == 0,
- files[i].mbr_id,
- &out[0x1be + 16*i]);
- }
- }
+ size_t i, j = 0;
/* Boot signature. */
- out[0x1fe] = 0x55;
- out[0x1ff] = 0xaa;
+ primary[0x1fe] = 0x55;
+ primary[0x1ff] = 0xaa;
+
+ if (nr_files <= 4) {
+ /* Basic MBR with no extended partition. */
+ for (i = 0; i < nr_files; ++i) {
+ const struct region *region = find_file_region (i, &j);
+
+ create_mbr_partition_table_entry (region, i == 0, files[i].mbr_id,
+ &primary[0x1be + 16*i]);
+ }
+ }
+ else {
+ struct region region;
+ const struct region *rptr, *eptr0, *eptr;
+
+ /* The first three primary partitions correspond to the first
+ * three files.
+ */
+ for (i = 0; i < 3; ++i) {
+ rptr = find_file_region (i, &j);
+ create_mbr_partition_table_entry (rptr, i == 0, files[i].mbr_id,
+ &primary[0x1be + 16*i]);
+ }
+
+ /* The fourth partition is an extended PTE and does not correspond
+ * to any file. This partition starts with the first EBR, so find
+ * it. The partition extends to the end of the disk.
+ */
+ eptr0 = find_ebr_region (3, &j);
+ region.start = eptr0->start;
+ region.end = virtual_size (®ions) - 1; /* to end of disk */
+ region.len = region.end - region.start + 1;
+ create_mbr_partition_table_entry (®ion, false, 0xf, &primary[0x1ee]);
+
+ /* The remaining files are mapped to logical partitions living in
+ * the fourth extended partition.
+ */
+ for (i = 3; i < nr_files; ++i) {
+ if (i == 3)
+ eptr = eptr0;
+ else
+ eptr = find_ebr_region (i, &j);
+ rptr = find_file_region (i, &j);
+
+ /* Signature. */
+ ebr[i-3][0x1fe] = 0x55;
+ ebr[i-3][0x1ff] = 0xaa;
+
+ /* First entry in EBR contains:
+ * offset from EBR sector to the first sector of the logical partition
+ * total count of sectors in the logical partition
+ */
+ region.start = rptr->start - eptr->start;
+ region.len = rptr->len;
+ create_mbr_partition_table_entry (®ion, false, files[i].mbr_id,
+ &ebr[i-3][0x1be]);
+
+ if (i < nr_files-1) {
+ size_t j2 = j;
+ const struct region *enext = find_ebr_region (i+1, &j2);
+ const struct region *rnext = find_file_region (i+1, &j2);
+
+ /* Second entry in the EBR contains:
+ * address of next EBR relative to extended partition
+ * total count of sectors in the next logical partition including
+ * next EBR
+ */
+ region.start = enext->start - eptr0->start;
+ region.len = rnext->end - enext->start + 1;
+ create_mbr_partition_table_entry (®ion, false, files[i+1].mbr_id,
+ &ebr[i-3][0x1ce]);
+ }
+ }
+ }
+}
+
+/* Find the region corresponding to file[i].
+ * j is a scratch register ensuring we only do a linear scan.
+ */
+static const struct region *
+find_file_region (size_t i, size_t *j)
+{
+ const struct region *region;
+
+ for (; *j < nr_regions (®ions); ++(*j)) {
+ region = get_region (®ions, *j);
+ if (region->type == region_file && region->u.i == i)
+ return region;
+ }
+ abort ();
+}
+
+/* Find the region corresponding to EBR of file[i] (i >= 3).
+ * j is a scratch register ensuring we only do a linear scan.
+ */
+static const struct region *
+find_ebr_region (size_t i, size_t *j)
+{
+ const struct region *region;
+
+ assert (i >= 3);
+
+ for (; *j < nr_regions (®ions); ++(*j)) {
+ region = get_region (®ions, *j);
+ if (region->type == region_data && region->u.data == ebr[i-3])
+ return region;
+ }
+ abort ();
}
static void
@@ -84,7 +182,7 @@ chs_too_large (unsigned char *out)
void
create_mbr_partition_table_entry (const struct region *region,
- int bootable, int partition_id,
+ bool bootable, int partition_id,
unsigned char *out)
{
uint64_t start_sector, nr_sectors;
diff --git a/plugins/partitioning/partitioning.c b/plugins/partitioning/partitioning.c
index 205c059..689cb24 100644
--- a/plugins/partitioning/partitioning.c
+++ b/plugins/partitioning/partitioning.c
@@ -35,6 +35,7 @@
#include
#include
+#include
#include
#include
#include
@@ -81,8 +82,11 @@ size_t nr_files = 0;
/* Virtual disk layout. */
struct regions regions;
-/* Primary and secondary partition tables (secondary is only used for GPT). */
-unsigned char *primary = NULL, *secondary = NULL;
+/* Primary and secondary partition tables and extended boot records.
+ * Secondary PT is only used for GPT. EBR array of sectors is only
+ * used for MBR with > 4 partitions and has length equal to nr_files-3.
+ */
+unsigned char *primary = NULL, *secondary = NULL, **ebr = NULL;
/* Used to generate random unique partition GUIDs for GPT. */
static struct random_state random_state;
@@ -105,12 +109,17 @@ partitioning_unload (void)
free (files);
/* We don't need to free regions.regions[].u.data because it points
- * to either primary or secondary which we free here.
+ * to primary, secondary or ebr which we free here.
*/
free_regions (®ions);
free (primary);
free (secondary);
+ if (ebr) {
+ for (i = 0; i < nr_files-3; ++i)
+ free (ebr[i]);
+ free (ebr);
+ }
}
static int
@@ -225,7 +234,7 @@ partitioning_config_complete (void)
{
size_t i;
uint64_t total_size;
- int needs_gpt;
+ bool needs_gpt;
/* Not enough / too many files? */
if (nr_files == 0) {
@@ -236,17 +245,11 @@ partitioning_config_complete (void)
total_size = 0;
for (i = 0; i < nr_files; ++i)
total_size += files[i].statbuf.st_size;
-
- if (nr_files > 4)
- needs_gpt = 1;
- else if (total_size > MAX_MBR_DISK_SIZE)
- needs_gpt = 1;
- else
- needs_gpt = 0;
+ needs_gpt = total_size > MAX_MBR_DISK_SIZE;
/* Choose default parttype if not set. */
if (parttype == PARTTYPE_UNSET) {
- if (needs_gpt) {
+ if (needs_gpt || nr_files > 4) {
parttype = PARTTYPE_GPT;
nbdkit_debug ("picking partition type GPT");
}
@@ -256,8 +259,8 @@ partitioning_config_complete (void)
}
}
else if (parttype == PARTTYPE_MBR && needs_gpt) {
- nbdkit_error ("MBR partition table type supports a maximum of 4 partitions "
- "and a maximum virtual disk size of about 2 TB, "
+ nbdkit_error ("MBR partition table type supports "
+ "a maximum virtual disk size of about 2 TB, "
"but you requested %zu partition(s) "
"and a total size of %" PRIu64 " bytes (> %" PRIu64 "). "
"Try using: partition-type=gpt",
diff --git a/plugins/partitioning/virtual-disk.c b/plugins/partitioning/virtual-disk.c
index e2d72bc..4fe186e 100644
--- a/plugins/partitioning/virtual-disk.c
+++ b/plugins/partitioning/virtual-disk.c
@@ -69,6 +69,25 @@ create_virtual_disk_layout (void)
nbdkit_error ("malloc: %m");
return -1;
}
+
+ if (nr_files > 4) {
+ /* The first 3 primary partitions will be real partitions, the
+ * 4th will be an extended partition, and so we need to store
+ * EBRs for nr_files-3 logical partitions.
+ */
+ ebr = malloc (sizeof (unsigned char *) * (nr_files-3));
+ if (ebr == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ for (i = 0; i < nr_files-3; ++i) {
+ ebr[i] = calloc (1, SECTOR_SIZE);
+ if (ebr[i] == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ }
+ }
}
else /* PARTTYPE_GPT */ {
/* Protective MBR + PT header + PTA = 2 + GPT_PTA_LBAs */
@@ -117,6 +136,20 @@ create_virtual_disk_layout (void)
*/
assert (IS_ALIGNED (offset, SECTOR_SIZE));
+ /* Logical partitions are preceeded by an EBR. */
+ if (parttype == PARTTYPE_MBR && nr_files > 4 && i >= 3) {
+ region.start = offset;
+ region.len = SECTOR_SIZE;
+ region.end = region.start + region.len - 1;
+ region.type = region_data;
+ region.u.data = ebr[i-3];
+ region.description = "EBR";
+ if (append_region (®ions, region) == -1)
+ return -1;
+
+ offset = virtual_size (®ions);
+ }
+
/* Make sure each partition is aligned for best performance. */
if (!IS_ALIGNED (offset, files[i].alignment)) {
region.start = offset;
@@ -207,13 +240,10 @@ create_partition_table (void)
if (parttype == PARTTYPE_GPT)
assert (secondary != NULL);
- if (parttype == PARTTYPE_MBR) {
- assert (nr_files <= 4);
- create_mbr_partition_table (primary);
- }
- else /* parttype == PARTTYPE_GPT */ {
+ if (parttype == PARTTYPE_MBR)
+ create_mbr_layout ();
+ else /* parttype == PARTTYPE_GPT */
create_gpt_layout ();
- }
return 0;
}
--
2.20.1