---
.../nbdkit-partitioning-plugin.pod | 29 ++--
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 +++++-
tests/Makefile.am | 3 +-
tests/test-partitioning5.sh | 96 +++++++++++++
8 files changed, 286 insertions(+), 66 deletions(-)
diff --git a/plugins/partitioning/nbdkit-partitioning-plugin.pod
b/plugins/partitioning/nbdkit-partitioning-plugin.pod
index 57a1133..49436d9 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<partition-type> parameter you can choose either an MBR or
+a GPT partition table. If this parameter is I<not> present then:
=over 4
-=item C<partition-type=mbr> parameter on the command line
-
-⇒ MBR is selected
-
-=item C<partition-type=gpt> parameter on the command line
-
-⇒ GPT is selected
-
-=item else, number of files E<gt> 4
+=item number of files E<gt> 4
⇒ GPT
@@ -131,7 +120,13 @@ C<./> (absolute paths do not need modification).
=item B<partition-type=mbr>
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<gt> 4 partitions then the first three files are mapped
+to primary partitions, an extended partition
+(L<https://en.wikipedia.org/wiki/Extended_boot_record>) is created as
+partition 4, and the files starting from the 4th will appear as
+partition 5 and upwards.
=item B<partition-type=gpt>
@@ -163,10 +158,6 @@ a Linux filesystem.
=head1 LIMITS
-This plugin only supports B<primary> 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 <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -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..6b256d1 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, 0xf,
+ &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 <stdio.h>
#include <stdlib.h>
+#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>
#include <string.h>
@@ -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;
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 420cb45..b6a44f1 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -456,7 +456,8 @@ TESTS += \
if HAVE_GUESTFISH
TESTS += \
test-partitioning2.sh \
- test-partitioning3.sh
+ test-partitioning3.sh \
+ test-partitioning5.sh
endif HAVE_GUESTFISH
# pattern plugin test.
diff --git a/tests/test-partitioning5.sh b/tests/test-partitioning5.sh
new file mode 100755
index 0000000..b4cb6bf
--- /dev/null
+++ b/tests/test-partitioning5.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018-2019 Red Hat Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+# Test the partitioning plugin.
+#
+# Test 5: Create a filesystem and embed it in an MBR logical
+# partition. libguestfs uses virtio-scsi so the practical limit here
+# is about 15 partitions.
+
+source ./functions.sh
+set -e
+set -x
+
+files="partitioning5.pid partitioning5.sock
+ partitioning5.fs
+ partitioning5.p1 partitioning5.p2 partitioning5.p3 partitioning5.p5
partitioning5.p6 partitioning5.p7 partitioning5.p8 partitioning5.p9 partitioning5.p10
partitioning5.p11 partitioning5.p13"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Test that mke2fs works
+if ! mke2fs -V; then
+ echo "$0: missing or broken mke2fs"
+ exit 77
+fi
+
+# Create partitions before and after.
+truncate -s 1 partitioning5.p1
+truncate -s 10M partitioning5.p2
+truncate -s 512 partitioning5.p3
+# partition 4 = extended partition
+truncate -s 1 partitioning5.p5
+truncate -s 512 partitioning5.p6
+truncate -s 1 partitioning5.p7
+truncate -s 1 partitioning5.p8
+truncate -s 10M partitioning5.p9
+truncate -s 512 partitioning5.p10
+truncate -s 1 partitioning5.p11
+# partition 12 = naked filesystem
+truncate -s 10M partitioning5.p13
+
+# Create the naked filesystem.
+truncate -s 20M partitioning5.fs
+mke2fs -F -t ext2 partitioning5.fs
+
+# Run nbdkit.
+start_nbdkit -P partitioning5.pid -U partitioning5.sock \
+ partitioning \
+ partitioning5.p1 partitioning5.p2 \
+ partitioning5.p3 \
+ partitioning5.p5 partitioning5.p6 \
+ partitioning5.p7 partitioning5.p8 \
+ partitioning5.p9 partitioning5.p10 \
+ partitioning5.p11 partitioning5.fs \
+ partitioning5.p13 \
+ partition-type=mbr
+
+# Connect with guestfish and read/write stuff to partition 12.
+guestfish --format=raw -a "nbd://?socket=$PWD/partitioning5.sock"
<<'EOF'
+ run
+ mount /dev/sda12 /
+ touch /hello
+ fill-pattern "abc" 10000 /pattern
+ ll /
+ umount /dev/sda12
+ sync
+EOF
--
2.20.1