Create a partitioned virtual drive from a list of one or more files
each containing single partitions. The plugin concatenates the files
together and generates a virtual partition table so that NBD clients
see a single partitioned disk.
For example:
nbdkit partitioning boot.img swap.img root.img
creates a virtual disk with 3 partitions.
---
configure.ac | 2 +
filters/partition/nbdkit-partition-filter.pod | 4 +
plugins/file/nbdkit-file-plugin.pod | 3 +-
plugins/partitioning/Makefile.am | 64 ++
plugins/partitioning/crc32.c | 140 +++
plugins/partitioning/crc32.h | 41 +
.../nbdkit-partitioning-plugin.pod | 118 +++
plugins/partitioning/partitioning.c | 837 ++++++++++++++++++
plugins/split/nbdkit-split-plugin.pod | 6 +-
tests/Makefile.am | 10 +
tests/test-partitioning1.sh | 83 ++
tests/test-partitioning2.sh | 75 ++
12 files changed, 1381 insertions(+), 2 deletions(-)
diff --git a/configure.ac b/configure.ac
index 2a4879a..3f99459 100644
--- a/configure.ac
+++ b/configure.ac
@@ -573,6 +573,7 @@ non_lang_plugins="\
memory \
nbd \
null \
+ partitioning \
pattern \
random \
split \
@@ -630,6 +631,7 @@ AC_CONFIG_FILES([Makefile
plugins/nbd/Makefile
plugins/null/Makefile
plugins/ocaml/Makefile
+ plugins/partitioning/Makefile
plugins/pattern/Makefile
plugins/perl/Makefile
plugins/python/Makefile
diff --git a/filters/partition/nbdkit-partition-filter.pod
b/filters/partition/nbdkit-partition-filter.pod
index ae72f3f..4a615b6 100644
--- a/filters/partition/nbdkit-partition-filter.pod
+++ b/filters/partition/nbdkit-partition-filter.pod
@@ -16,6 +16,9 @@ parameter, and count from 1.
This works like the C<qemu-nbd -P> option.
+The opposite of this filter is L<nbdkit-partitioning-plugin(1)> which
+adds a virtual partition table to a file or files.
+
=head1 NOTE
Only MBR primary partitions and GPT partition tables are supported.
@@ -46,6 +49,7 @@ L<nbdkit(1)>,
L<nbdkit-file-plugin(1)>,
L<nbdkit-filter(3)>,
L<nbdkit-offset-filter(1)>,
+L<nbdkit-partitioning-plugin(1)>,
L<nbdkit-truncate-filter(1)>,
L<parted(8)>.
diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod
index 7d5c71b..cdd9d55 100644
--- a/plugins/file/nbdkit-file-plugin.pod
+++ b/plugins/file/nbdkit-file-plugin.pod
@@ -89,7 +89,8 @@ or block device efficiently or not.
L<nbdkit(1)>,
L<nbdkit-plugin(3)>,
-L<nbdkit-split-plugin(1)>.
+L<nbdkit-split-plugin(1)>,
+L<nbdkit-partitioning-plugin(1)>.
=head1 AUTHORS
diff --git a/plugins/partitioning/Makefile.am b/plugins/partitioning/Makefile.am
new file mode 100644
index 0000000..e517a63
--- /dev/null
+++ b/plugins/partitioning/Makefile.am
@@ -0,0 +1,64 @@
+# nbdkit
+# Copyright (C) 2018 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-partitioning-plugin.pod
+
+plugin_LTLIBRARIES = nbdkit-partitioning-plugin.la
+
+nbdkit_partitioning_plugin_la_SOURCES = \
+ crc32.c \
+ crc32.h \
+ partitioning.c \
+ $(top_srcdir)/include/nbdkit-plugin.h
+
+nbdkit_partitioning_plugin_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include \
+ -I.
+nbdkit_partitioning_plugin_la_CFLAGS = \
+ $(WARNINGS_CFLAGS)
+nbdkit_partitioning_plugin_la_LDFLAGS = \
+ -module -avoid-version -shared
+
+if HAVE_POD
+
+man_MANS = nbdkit-partitioning-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-partitioning-plugin.1: nbdkit-partitioning-plugin.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/plugins/partitioning/crc32.c b/plugins/partitioning/crc32.c
new file mode 100644
index 0000000..e707a50
--- /dev/null
+++ b/plugins/partitioning/crc32.c
@@ -0,0 +1,140 @@
+/* This code was taken from parted and indirectly from other sources
+ * as you can see from the messages below. The license is compatible
+ * with the permissive license used in nbdkit. - RWMJ 2018-09-16
+ */
+
+/*
+ * Dec 5, 2000 Matt Domsch <Matt_Domsch(a)dell.com>
+ * - Copied crc32.c from the linux/drivers/net/cipe directory.
+ * - Now pass seed as an arg
+ * - changed unsigned long to uint32_t, added #include<stdint.h>
+ * - changed len to be an unsigned long
+ * - changed crc32val to be a register
+ * - License remains unchanged! It's still GPL-compatable!
+ */
+
+ /* ============================================================= */
+ /* COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or */
+ /* code or tables extracted from it, as desired without restriction. */
+ /* */
+ /* First, the polynomial itself and its table of feedback terms. The */
+ /* polynomial is */
+ /* X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 */
+ /* */
+ /* Note that we take it "backwards" and put the highest-order term in */
+ /* the lowest-order bit. The X^32 term is "implied"; the LSB is the */
+ /* X^31 term, etc. The X^0 term (usually shown as "+1") results in */
+ /* the MSB being 1. */
+ /* */
+ /* Note that the usual hardware shift register implementation, which */
+ /* is what we're using (we're merely optimizing it by doing eight-bit */
+ /* chunks at a time) shifts bits into the lowest-order term. In our */
+ /* implementation, that means shifting towards the right. Why do we */
+ /* do it this way? Because the calculated CRC must be transmitted in */
+ /* order from highest-order term to lowest-order term. UARTs transmit */
+ /* characters in order from LSB to MSB. By storing the CRC this way, */
+ /* we hand it to the UART in the order low-byte to high-byte; the UART */
+ /* sends each low-bit to hight-bit; and the result is transmission bit */
+ /* by bit from highest- to lowest-order term without requiring any bit */
+ /* shuffling on our part. Reception works similarly. */
+ /* */
+ /* The feedback terms table consists of 256, 32-bit entries. Notes: */
+ /* */
+ /* The table can be generated at runtime if desired; code to do so */
+ /* is shown later. It might not be obvious, but the feedback */
+ /* terms simply represent the results of eight shift/xor opera- */
+ /* tions for all combinations of data and CRC register values. */
+ /* */
+ /* The values must be right-shifted by eight bits by the "updcrc" */
+ /* logic; the shift must be unsigned (bring in zeroes). On some */
+ /* hardware you could probably optimize the shift in assembler by */
+ /* using byte-swap instructions. */
+ /* polynomial $edb88320 */
+ /* */
+ /* -------------------------------------------------------------------- */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include "crc32.h"
+
+static const uint32_t crc32_tab[] = {
+ 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L,
+ 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L,
+ 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L,
+ 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL,
+ 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L,
+ 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L,
+ 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L,
+ 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL,
+ 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L,
+ 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL,
+ 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L,
+ 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L,
+ 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L,
+ 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL,
+ 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL,
+ 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L,
+ 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL,
+ 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L,
+ 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L,
+ 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L,
+ 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL,
+ 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L,
+ 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L,
+ 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL,
+ 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L,
+ 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L,
+ 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L,
+ 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L,
+ 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L,
+ 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL,
+ 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL,
+ 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L,
+ 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L,
+ 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL,
+ 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL,
+ 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L,
+ 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL,
+ 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L,
+ 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL,
+ 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L,
+ 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL,
+ 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L,
+ 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L,
+ 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL,
+ 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L,
+ 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L,
+ 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L,
+ 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L,
+ 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L,
+ 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L,
+ 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL,
+ 0x2d02ef8dL
+ };
+
+/* Return a 32-bit CRC of the contents of the buffer. */
+
+static uint32_t
+efi_crc32 (const void *buf, size_t len, uint32_t seed)
+{
+ size_t i;
+ uint32_t crc32val;
+ const unsigned char *s = buf;
+
+ crc32val = seed;
+ for (i = 0; i < len; i++) {
+ crc32val =
+ crc32_tab[(crc32val ^ s[i]) & 0xff] ^
+ (crc32val >> 8);
+ }
+ return crc32val;
+}
+
+uint32_t
+crc32 (const void *buf, size_t len)
+{
+ return efi_crc32 (buf, len, ~0L) ^ ~0L;
+}
diff --git a/plugins/partitioning/crc32.h b/plugins/partitioning/crc32.h
new file mode 100644
index 0000000..6bd5d2d
--- /dev/null
+++ b/plugins/partitioning/crc32.h
@@ -0,0 +1,41 @@
+/* nbdkit
+ * Copyright (C) 2018 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.
+ */
+
+#ifndef NBDKIT_CRC32_H
+#define NBDKIT_CRC32_H
+
+#include <stdint.h>
+
+extern uint32_t crc32 (const void *buf, size_t len);
+
+#endif /* NBDKIT_CRC32_H */
diff --git a/plugins/partitioning/nbdkit-partitioning-plugin.pod
b/plugins/partitioning/nbdkit-partitioning-plugin.pod
new file mode 100644
index 0000000..07780e9
--- /dev/null
+++ b/plugins/partitioning/nbdkit-partitioning-plugin.pod
@@ -0,0 +1,118 @@
+=head1 NAME
+
+nbdkit-partitioning-plugin - create virtual disk from partitions
+
+=head1 SYNOPSIS
+
+ nbdkit partitioning [file=]part1 [[file=]part2 [file=]part3 ...]
+ [partition-type=mbr|gpt]
+
+=head1 DESCRIPTION
+
+C<nbdkit-partitioning-plugin> is a plugin for L<nbdkit(1)> which
+creates a partitioned virtual drive from a list of one or more files
+each containing single partitions. The plugin concatenates the files
+together and generates a virtual partition table so that NBD clients
+see a single partitioned disk.
+
+If you just want to concatenate files together (without adding a
+partition table) use L<nbdkit-split-plugin(1)>. If you want to select
+a single partition from an existing disk, use
+L<nbdkit-partition-filter(1)>.
+
+The plugin supports read/write access. To limit clients to read-only
+access use the I<-r> flag.
+
+=head2 Partition type
+
+You can choose either MBR (limited to 4 partitions) or GPT (limited to
+128 partitions) partition table type.
+
+If the C<partition-type> parameter is not supplied then the default is
+chosen as follows: If the number of files is E<gt> 4 then GPT is used.
+If the total file size is larger than supported by MBR (approximately
+2 TB), then GPT is used. Otherwise MBR is used for maximum
+compatibility.
+
+=head2 Padding and alignment
+
+Partition sizes are automatically rounded up to a multiple of the 512
+byte sector size. Padding may be added between partitions to provide
+the best alignment. NBD clients may write to partitions, but will get
+an I/O error if they try to change the virtual partition table or any
+padding areas of the disk.
+
+=head1 EXAMPLES
+
+Create a virtual disk containing boot, swap and root partitions (note
+this will not be bootable since the virtual partition table does not
+contain a boot sector or boot loader):
+
+ nbdkit partitioning boot.img swap.img root.img
+
+From a bare ext4 filesystem, create a virtual partitioned disk:
+
+ nbdkit partitioning ext4fs.img
+
+If the underlying file is a multiple of 512 bytes then this is a
+no-op:
+
+ nbdkit --filter=partition partitioning file --partition=1
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<file=>FILENAME
+
+One or more files containing partition data.
+
+This parameter is required.
+
+C<file=> may be omitted. To ensure that the filename does not end up
+being parsed accidentally as C<key=value>, prefix relative paths with
+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 clients, but only supports up to 4 partitions.
+
+=item B<partition-type=gpt>
+
+Add a GPT partition table. This plugin supports up to 128 GPT
+partitions. Note that as well as the virtual primary partition table
+added at the beginning of the disk, a virtual secondary partition
+table is added at the end, as required by GPT.
+
+=back
+
+=head1 LIMITS
+
+This plugin only supports primary MBR partitions, hence the limit of 4
+partitions with MBR. This might be increased in future if we
+implement support for logical/extended partitions.
+
+This plugin only supports 128 GPT partitions. The GPT format can
+support more, but this plugin does not implement that.
+
+Zero length partitions are not allowed and will cause nbdkit to exit
+with an error.
+
+The sector size is fixed at 512 bytes.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-partition-filter(1)>,
+L<nbdkit-split-plugin(1)>,
+L<nbdkit-plugin(3)>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+=head1 COPYRIGHT
+
+Copyright (C) 2018 Red Hat Inc.
diff --git a/plugins/partitioning/partitioning.c b/plugins/partitioning/partitioning.c
new file mode 100644
index 0000000..c2f52a5
--- /dev/null
+++ b/plugins/partitioning/partitioning.c
@@ -0,0 +1,837 @@
+/* nbdkit
+ * Copyright (C) 2018 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <nbdkit-plugin.h>
+
+#include "byte-swapping.h"
+#include "isaligned.h"
+#include "iszero.h"
+#include "rounding.h"
+
+#include "crc32.h"
+
+/* Debug flag: -D partitioning.regions=1: Print the regions table. */
+int partitioning_debug_regions;
+
+#define SECTOR_SIZE UINT64_C(512)
+
+/* Maximum size of MBR disks. This is an approximation based on the
+ * known limit (2^32 sectors) and an estimate based on the amount of
+ * padding between partitions.
+ */
+#define MAX_MBR_DISK_SIZE (UINT32_MAX * SECTOR_SIZE - 5 * ALIGNMENT)
+
+#define GPT_PT_ENTRY_SIZE 128
+
+/* XXX Make these configurable in future? */
+#define ALIGNMENT (2048 * SECTOR_SIZE)
+#define PARTITION_ID 0x83
+#define PARTITION_GUID
"\xaf\x3d\xc6\x0f\x83\x84\x72\x47\x8e\x79\x3d\x69\xd8\x47\x7d\xe4"
+
+/* Files supplied on the command line. */
+struct file {
+ const char *filename; /* file= supplied on the command line */
+ int fd;
+ struct stat statbuf;
+ char guid[16]; /* random GUID used for GPT */
+};
+
+static struct file *files = NULL;
+static size_t nr_files = 0;
+
+/* partition-type parameter. */
+#define PARTTYPE_UNSET 0
+#define PARTTYPE_MBR 1
+#define PARTTYPE_GPT 2
+static int parttype = PARTTYPE_UNSET;
+
+/* Virtual disk regions (contiguous). */
+enum region_type {
+ region_file, /* contents of the i'th file */
+ region_data, /* pointer to data (used for partition table) */
+ region_zero, /* padding */
+};
+
+struct region {
+ uint64_t start, len, end; /* byte offsets; end = start + len - 1 */
+ enum region_type type;
+ union {
+ size_t i; /* region_file: i'th file */
+ const unsigned char *data; /* region_data: data (partition table) */
+ } u;
+};
+
+static struct region *regions = NULL;
+static size_t nr_regions = 0;
+
+/* Primary and secondary partition tables (secondary is only used for GPT). */
+static unsigned char *primary = NULL, *secondary = NULL;
+
+static void
+partitioning_load (void)
+{
+ srandom (time (NULL));
+}
+
+static void
+partitioning_unload (void)
+{
+ size_t i;
+
+ for (i = 0; i < nr_files; ++i)
+ close (files[i].fd);
+ free (files);
+
+ /* We don't need to free regions[].u.data because it points to
+ * either primary or secondary which we free here.
+ */
+ free (regions);
+ free (primary);
+ free (secondary);
+}
+
+/* Find the region corresponding to the given offset. Use region->end
+ * to find the end of the region.
+ */
+static int
+compare_offset (const void *offsetp, const void *regionp)
+{
+ const uint64_t offset = *(uint64_t *)offsetp;
+ const struct region *region = (struct region *)regionp;
+
+ if (offset < region->start) return -1;
+ if (offset > region->end) return 1;
+ return 0;
+}
+
+static struct region *
+get_region (uint64_t offset)
+{
+ return bsearch (&offset, regions, nr_regions, sizeof (struct region),
+ compare_offset);
+}
+
+/* Helper function to expand an array of objects. */
+static int
+expand (void **objects, size_t size, size_t *nr_objects)
+{
+ void *p;
+
+ p = realloc (*objects, (*nr_objects+1) * size);
+ if (p == NULL) {
+ nbdkit_error ("realloc: %m");
+ return -1;
+ }
+ *objects = p;
+ (*nr_objects)++;
+ return 0;
+}
+
+/* Called once we have the list of filenames and have selected a
+ * partition type. This creates the virtual disk layout as a list of
+ * regions.
+ */
+static int create_partition_table (void);
+
+static int
+create_virtual_disk_layout (void)
+{
+ struct region region;
+ size_t i;
+
+ assert (nr_regions == 0);
+ assert (nr_files > 0);
+ assert (primary == NULL);
+ assert (secondary == NULL);
+
+ /* Allocate the virtual partition table. */
+ if (parttype == PARTTYPE_MBR) {
+ primary = calloc (1, SECTOR_SIZE);
+ if (primary == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ }
+ else /* PARTTYPE_GPT */ {
+ primary = calloc (34, SECTOR_SIZE);
+ if (primary == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ secondary = calloc (33, SECTOR_SIZE);
+ if (secondary == NULL) {
+ nbdkit_error ("malloc: %m");
+ return -1;
+ }
+ }
+
+ /* Virtual primary partition table region at the start of the disk. */
+ if (parttype == PARTTYPE_MBR) {
+ region.start = 0;
+ region.len = SECTOR_SIZE;
+ region.end = region.start + region.len - 1;
+ region.type = region_data;
+ region.u.data = primary;
+ if (expand ((void *) ®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+ }
+ else /* PARTTYPE_GPT */ {
+ region.start = 0;
+ region.len = 34 * SECTOR_SIZE;
+ region.end = region.start + region.len - 1;
+ region.type = region_data;
+ region.u.data = primary;
+ if (expand ((void *)®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+ }
+
+ /* The partitions. */
+ for (i = 0; i < nr_files; ++i) {
+ uint64_t offset;
+
+ offset = regions[nr_regions-1].end + 1;
+ /* Because we add padding after each partition, this invariant
+ * must always be true.
+ */
+ assert (IS_ALIGNED (offset, SECTOR_SIZE));
+
+ /* Make sure each partition is aligned for best performance. */
+ if (!IS_ALIGNED (offset, ALIGNMENT)) {
+ region.start = offset;
+ region.end = (offset & ~(ALIGNMENT-1)) + ALIGNMENT - 1;
+ region.len = region.end - region.start + 1;
+ region.type = region_zero;
+ if (expand ((void *)®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+ }
+
+ offset = regions[nr_regions-1].end + 1;
+ assert (IS_ALIGNED (offset, ALIGNMENT));
+
+ /* Create the partition region for this file. */
+ region.start = offset;
+ region.len = files[i].statbuf.st_size;
+ region.end = region.start + region.len - 1;
+ region.type = region_file;
+ region.u.i = i;
+ if (expand ((void *)®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+
+ /* If the file size is not a multiple of SECTOR_SIZE then
+ * add a padding region at the end to round it up.
+ */
+ if (!IS_ALIGNED (files[i].statbuf.st_size, SECTOR_SIZE)) {
+ region.start = regions[nr_regions-1].end + 1;
+ region.len = SECTOR_SIZE - (files[i].statbuf.st_size & (SECTOR_SIZE-1));
+ region.end = region.start + region.len - 1;
+ region.type = region_zero;
+ if (expand ((void *)®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+ }
+ }
+
+ /* For GPT add the virtual secondary/backup partition table. */
+ if (parttype == PARTTYPE_GPT) {
+ region.start = regions[nr_regions-1].end + 1;
+ region.len = 33 * SECTOR_SIZE;
+ region.end = region.start + region.len - 1;
+ region.type = region_data;
+ region.u.data = secondary;
+ if (expand ((void *)®ions, sizeof (struct region), &nr_regions) == -1)
+ return -1;
+ regions[nr_regions-1] = region;
+ }
+
+ if (partitioning_debug_regions) {
+ for (i = 0; i < nr_regions; ++i) {
+ nbdkit_debug ("region[%zu]: %" PRIx64 "-%" PRIx64 "
type=%s",
+ i, regions[i].start, regions[i].end,
+ regions[i].type == region_file ?
+ files[regions[i].u.i].filename :
+ regions[i].type == region_data ?
+ "data" : "zero");
+ }
+ }
+
+ /* Assert that the regions table looks sane. */
+ assert (nr_regions > 0);
+ assert (regions[0].start == 0);
+ for (i = 0; i < nr_regions; ++i) {
+ assert (regions[i].len > 0);
+ assert (regions[i].end >= regions[i].start);
+ assert (regions[i].len == regions[i].end - regions[i].start + 1);
+ if (i+1 < nr_regions) {
+ assert (regions[i].end + 1 == regions[i+1].start);
+ }
+ }
+
+ return create_partition_table ();
+}
+
+/* Create the partition table (and for GPT the secondary/backup). */
+static void create_mbr_partition_table (unsigned char *out);
+static void create_mbr_partition_table_entry (const struct region *, int bootable, int
partition_id, unsigned char *);
+static void create_gpt_partition_header (const void *pt, int is_primary, unsigned char
*out);
+static void create_gpt_partition_table (unsigned char *out);
+static void create_gpt_partition_table_entry (const struct region *region, int bootable,
char partition_type_guid[16], unsigned char *out);
+static void create_gpt_protective_mbr (unsigned char *out);
+
+static int
+create_partition_table (void)
+{
+ /* The caller has already create the disk layout and allocated space
+ * in memory for the partition table.
+ */
+ assert (nr_regions > 0);
+ assert (primary != NULL);
+ if (parttype == PARTTYPE_GPT)
+ assert (secondary != NULL);
+
+ if (parttype == PARTTYPE_MBR) {
+ assert (nr_files <= 4);
+ create_mbr_partition_table (primary);
+ }
+ else /* parttype == PARTTYPE_GPT */ {
+ void *pt;
+
+ assert (nr_files <= 128);
+
+ /* Protective MBR. LBA 0 */
+ create_gpt_protective_mbr (primary);
+
+ /* Primary partition table. LBA 2-33 */
+ pt = &primary[2*SECTOR_SIZE];
+ create_gpt_partition_table (pt);
+
+ /* Partition table header. LBA 1 */
+ create_gpt_partition_header (pt, 1, &primary[SECTOR_SIZE]);
+
+ /* Backup partition table. LBA -33 */
+ pt = secondary;
+ create_gpt_partition_table (pt);
+
+ /* Backup partition table header. LBA -1 */
+ create_gpt_partition_header (pt, 0, &secondary[32*SECTOR_SIZE]);
+ }
+
+ return 0;
+}
+
+static void
+create_mbr_partition_table (unsigned char *out)
+{
+ size_t i, j;
+
+ for (j = 0; j < nr_regions; ++j) {
+ if (regions[j].type == region_file) {
+ i = regions[j].u.i;
+ assert (i < 4);
+ create_mbr_partition_table_entry (®ions[j], i == 0, PARTITION_ID,
+ &out[0x1be + 16*i]);
+ }
+ }
+
+ /* Boot signature. */
+ out[0x1fe] = 0x55;
+ out[0x1ff] = 0xaa;
+}
+
+static void
+chs_too_large (unsigned char *out)
+{
+ const int c = 1023, h = 254, s = 63;
+
+ out[0] = h;
+ out[1] = (c & 0x300) >> 2 | s;
+ out[2] = c & 0xff;
+}
+
+static void
+create_mbr_partition_table_entry (const struct region *region,
+ int bootable, int partition_id,
+ unsigned char *out)
+{
+ uint64_t start_sector, nr_sectors;
+ uint32_t u32;
+
+ assert (IS_ALIGNED (region->start, SECTOR_SIZE));
+
+ start_sector = region->start / SECTOR_SIZE;
+ nr_sectors = DIV_ROUND_UP (region->len, SECTOR_SIZE);
+
+ /* The total_size test in partitioning_config_complete should catch
+ * this earlier.
+ */
+ assert (start_sector <= UINT32_MAX);
+ assert (nr_sectors <= UINT32_MAX);
+
+ out[0] = bootable ? 0x80 : 0;
+ chs_too_large (&out[1]);
+ out[4] = partition_id;
+ chs_too_large (&out[5]);
+ u32 = htole32 (start_sector);
+ memcpy (&out[8], &u32, 4);
+ u32 = htole32 (nr_sectors);
+ memcpy (&out[12], &u32, 4);
+}
+
+static void
+create_gpt_partition_header (const void *pt, int is_primary,
+ unsigned char *out)
+{
+ uint64_t nr_lbas;
+ struct gpt_header {
+ char signature[8];
+ char revision[4];
+ uint32_t header_size;
+ uint32_t crc;
+ uint32_t reserved;
+ uint64_t current_lba;
+ uint64_t backup_lba;
+ uint64_t first_usable_lba;
+ uint64_t last_usable_lba;
+ char guid[16];
+ uint64_t partition_entries_lba;
+ uint32_t nr_partition_entries;
+ uint32_t size_partition_entry;
+ uint32_t crc_partitions;
+ } *header = (struct gpt_header *) out;
+
+ nr_lbas = (regions[nr_regions-1].end + 1) / SECTOR_SIZE;
+
+ memset (header, 0, sizeof *header);
+ memcpy (header->signature, "EFI PART", 8);
+ memcpy (header->revision, "\0\0\1\0", 4); /* revision 1.0 */
+ header->header_size = htole32 (sizeof *header);
+ if (is_primary) {
+ header->current_lba = htole64 (1);
+ header->backup_lba = htole64 (nr_lbas - 1);
+ }
+ else {
+ header->current_lba = htole64 (nr_lbas - 1);
+ header->backup_lba = htole64 (1);
+ }
+ header->first_usable_lba = htole64 (34);
+ header->last_usable_lba = htole64 (nr_lbas - 34);
+ if (is_primary)
+ header->partition_entries_lba = htole64 (2);
+ else
+ header->partition_entries_lba = htole64 (nr_lbas - 33);
+ header->nr_partition_entries = htole32 (128);
+ header->size_partition_entry = htole32 (GPT_PT_ENTRY_SIZE);
+ header->crc_partitions = htole32 (crc32 (pt, GPT_PT_ENTRY_SIZE * 128));
+
+ /* Must be computed last. */
+ header->crc = htole32 (crc32 (header, sizeof *header));
+}
+
+static void
+create_gpt_partition_table (unsigned char *out)
+{
+ size_t i, j;
+
+ for (j = 0; j < nr_regions; ++j) {
+ if (regions[j].type == region_file) {
+ i = regions[j].u.i;
+ assert (i < 128);
+ create_gpt_partition_table_entry (®ions[j], i == 0, PARTITION_GUID,
+ out);
+ out += GPT_PT_ENTRY_SIZE;
+ }
+ }
+}
+
+static void
+create_gpt_partition_table_entry (const struct region *region,
+ int bootable, char partition_type_guid[16],
+ unsigned char *out)
+{
+ size_t i, len;
+ const char *filename;
+ struct gpt_entry {
+ char partition_type_guid[16];
+ char unique_guid[16];
+ uint64_t first_lba;
+ uint64_t last_lba;
+ uint64_t attributes;
+ char name[72]; /* UTF-16LE */
+ } *entry = (struct gpt_entry *) out;
+
+ assert (sizeof (struct gpt_entry) == GPT_PT_ENTRY_SIZE);
+
+ memcpy (entry->partition_type_guid, partition_type_guid, 16);
+
+ memcpy (entry->unique_guid, files[region->u.i].guid, 16);
+
+ entry->first_lba = htole64 (region->start / SECTOR_SIZE);
+ entry->last_lba = htole64 (region->end / SECTOR_SIZE);
+ entry->attributes = htole64 (bootable ? 4 : 0);
+
+ /* If the filename is 7 bit ASCII then this will reproduce it as a
+ * UTF-16LE string.
+ *
+ * Is this a security risk? It reveals something about paths on the
+ * server to clients. XXX
+ */
+ filename = files[region->u.i].filename;
+ len = strlen (filename);
+ if (len < 36) {
+ for (i = 0; i < len; ++i)
+ if (filename[i] > 127)
+ goto out;
+
+ for (i = 0; i < len; ++i) {
+ entry->name[2*i] = filename[i];
+ entry->name[2*i+1] = 0;
+ }
+ }
+ out: ;
+}
+
+static void
+create_gpt_protective_mbr (unsigned char *out)
+{
+ struct region region;
+ uint64_t end;
+
+ /* Protective MBR creates a partition with partition ID 0xee which
+ * covers the whole of the disk, or as much of the disk as
+ * expressible with MBR.
+ */
+ region.start = 512;
+ end = regions[nr_regions-1].end;
+ if (end > UINT32_MAX * SECTOR_SIZE)
+ end = UINT32_MAX * SECTOR_SIZE;
+ region.end = end;
+ region.len = region.end - region.start + 1;
+
+ create_mbr_partition_table_entry (®ion, 0, 0xee, &out[0x1be]);
+
+ /* Boot signature. */
+ out[0x1fe] = 0x55;
+ out[0x1ff] = 0xaa;
+}
+
+static int
+partitioning_config (const char *key, const char *value)
+{
+ struct file file;
+ size_t i;
+ int err;
+
+ if (strcmp (key, "file") == 0) {
+ file.filename = value;
+ file.fd = open (file.filename, O_RDWR);
+ if (file.fd == -1) {
+ nbdkit_error ("%s: %m", file.filename);
+ return -1;
+ }
+ if (fstat (file.fd, &file.statbuf) == -1) {
+ err = errno;
+ close (file.fd);
+ errno = err;
+ nbdkit_error ("%s: stat: %m", file.filename);
+ return -1;
+ }
+
+ if (file.statbuf.st_size == 0) {
+ nbdkit_error ("%s: zero length partitions are not allowed",
+ file.filename);
+ return -1;
+ }
+
+ /* Create a random GUID used as "Unique partition GUID". However
+ * this doesn't follow GUID conventions so in theory could make an
+ * invalid value. This is only used by GPT, and we store it in
+ * the file structure because it must be the same across primary
+ * and secondary PT entries.
+ */
+ for (i = 0; i < 16; ++i)
+ file.guid[i] = random () & 0xff;
+
+ if (expand ((void *)&files, sizeof (struct file), &nr_files) == -1) {
+ err = errno;
+ close (file.fd);
+ errno = err;
+ return -1;
+ }
+ files[nr_files-1] = file;
+ }
+ else if (strcmp (key, "partition-type") == 0) {
+ if (strcasecmp (value, "mbr") == 0 || strcasecmp (value, "dos")
== 0)
+ parttype = PARTTYPE_MBR;
+ else if (strcasecmp (value, "gpt") == 0)
+ parttype = PARTTYPE_GPT;
+ else {
+ nbdkit_error ("unknown partition-type: %s", value);
+ return -1;
+ }
+ }
+ else {
+ nbdkit_error ("unknown parameter '%s'", key);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+partitioning_config_complete (void)
+{
+ size_t i;
+ uint64_t total_size;
+ int needs_gpt;
+
+ /* Not enough / too many files? */
+ if (nr_files == 0) {
+ nbdkit_error ("at least one file= parameter must be supplied");
+ return -1;
+ }
+ if (nr_files > 128) {
+ nbdkit_error ("too many files, the plugin supports a maximum of 128
files");
+ return -1;
+ }
+
+ 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;
+
+ /* Choose default parttype if not set. */
+ if (parttype == PARTTYPE_UNSET) {
+ if (needs_gpt) {
+ parttype = PARTTYPE_GPT;
+ nbdkit_debug ("picking partition type GPT");
+ }
+ else {
+ parttype = PARTTYPE_MBR;
+ nbdkit_debug ("picking partition type MBR");
+ }
+ }
+ 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, but you requested %zu partition(s) and a total
size of %" PRIu64 " bytes (> %" PRIu64 "). Try using:
partition-type=gpt",
+ nr_files, total_size, (uint64_t) MAX_MBR_DISK_SIZE);
+ return -1;
+ }
+
+ return create_virtual_disk_layout ();
+}
+
+#define partitioning_config_help \
+ "file=<FILENAME> (required) File(s) containing partitions\n" \
+ "partition-type=mbr|gpt Partition type"
+
+/* Create the per-connection handle. */
+static void *
+partitioning_open (int readonly)
+{
+ /* We don't need a handle. This is a non-NULL pointer we can return. */
+ static int h;
+
+ return &h;
+}
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
+
+/* Get the disk size. */
+static int64_t
+partitioning_get_size (void *handle)
+{
+ assert (nr_regions > 0);
+ return regions[nr_regions-1].end + 1;
+}
+
+/* Read data. */
+static int
+partitioning_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+ while (count > 0) {
+ const struct region *region = get_region (offset);
+ size_t i, len;
+ ssize_t r;
+
+ /* Length to end of region. */
+ len = region->end - offset + 1;
+ if (len > count)
+ len = count;
+
+ switch (region->type) {
+ case region_file:
+ i = region->u.i;
+ assert (i < nr_files);
+ r = pread (files[i].fd, buf, len, offset - region->start);
+ if (r == -1) {
+ nbdkit_error ("pread: %s: %m", files[i].filename);
+ return -1;
+ }
+ if (r == 0) {
+ nbdkit_error ("pread: %s: unexpected end of file", files[i].filename);
+ return -1;
+ }
+ len = r;
+ break;
+
+ case region_data:
+ memcpy (buf, ®ion->u.data[offset - region->start], len);
+ break;
+
+ case region_zero:
+ memset (buf, 0, len);
+ break;
+ }
+
+ count -= len;
+ buf += len;
+ offset += len;
+ }
+
+ return 0;
+}
+
+/* Write data. */
+static int
+partitioning_pwrite (void *handle,
+ const void *buf, uint32_t count, uint64_t offset)
+{
+ while (count > 0) {
+ const struct region *region = get_region (offset);
+ size_t i, len;
+ ssize_t r;
+
+ /* Length to end of region. */
+ len = region->end - offset + 1;
+ if (len > count)
+ len = count;
+
+ switch (region->type) {
+ case region_file:
+ i = region->u.i;
+ assert (i < nr_files);
+ r = pwrite (files[i].fd, buf, len, offset - region->start);
+ if (r == -1) {
+ nbdkit_error ("pwrite: %s: %m", files[i].filename);
+ return -1;
+ }
+ len = r;
+ break;
+
+ case region_data:
+ /* You can only write same data as already present. */
+ if (memcmp (®ion->u.data[offset - region->start], buf, len) != 0) {
+ nbdkit_error ("attempt to change partition table of virtual disk");
+ errno = EIO;
+ return -1;
+ }
+ break;
+
+ case region_zero:
+ /* You can only write zeros. */
+ if (!is_zero (buf, len)) {
+ nbdkit_error ("write non-zeros to padding region");
+ errno = EIO;
+ return -1;
+ }
+ break;
+ }
+
+ count -= len;
+ buf += len;
+ offset += len;
+ }
+
+ return 0;
+}
+
+/* Flush. */
+static int
+partitioning_flush (void *handle)
+{
+ size_t i;
+
+ for (i = 0; i < nr_files; ++i) {
+ if (fdatasync (files[i].fd) == -1) {
+ nbdkit_error ("fdatasync: %m");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static struct nbdkit_plugin plugin = {
+ .name = "partitioning",
+ .version = PACKAGE_VERSION,
+ .load = partitioning_load,
+ .unload = partitioning_unload,
+ .config = partitioning_config,
+ .config_complete = partitioning_config_complete,
+ .config_help = partitioning_config_help,
+ .magic_config_key = "file",
+ .open = partitioning_open,
+ .get_size = partitioning_get_size,
+ .pread = partitioning_pread,
+ .pwrite = partitioning_pwrite,
+ .flush = partitioning_flush,
+ /* In this plugin, errno is preserved properly along error return
+ * paths from failed system calls.
+ */
+ .errno_is_preserved = 1,
+};
+
+NBDKIT_REGISTER_PLUGIN(plugin)
diff --git a/plugins/split/nbdkit-split-plugin.pod
b/plugins/split/nbdkit-split-plugin.pod
index 6f62eb3..c0b7272 100644
--- a/plugins/split/nbdkit-split-plugin.pod
+++ b/plugins/split/nbdkit-split-plugin.pod
@@ -12,6 +12,9 @@ C<nbdkit-split-plugin> is a file plugin for L<nbdkit(1)>.
One or more
filenames may be given using the C<FILENAME> parameter. These
files are logically concatenated into a single disk image.
+If you want to add a virtual partition table, see
+L<nbdkit-partitioning-plugin(1)>.
+
=head2 Differences from nbdkit-file-plugin
Normally to serve a single file you should use
@@ -73,7 +76,8 @@ modification).
L<nbdkit(1)>,
L<nbdkit-plugin(3)>,
-L<nbdkit-file-plugin(1)>.
+L<nbdkit-file-plugin(1)>,
+L<nbdkit-partitioning-plugin(1)>.
=head1 AUTHORS
diff --git a/tests/Makefile.am b/tests/Makefile.am
index d04a7d6..b5b7d4c 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -70,6 +70,8 @@ EXTRA_DIST = \
test-offset2.sh \
test-parallel-file.sh \
test-parallel-nbd.sh \
+ test-partitioning1.sh \
+ test-partitioning2.sh \
test-pattern.sh \
test-pattern-largest.sh \
test-pattern-largest-for-qemu.sh \
@@ -356,6 +358,14 @@ test_memory_SOURCES = test-memory.c test.h
test_memory_CFLAGS = $(WARNINGS_CFLAGS) $(LIBGUESTFS_CFLAGS)
test_memory_LDADD = libtest.la $(LIBGUESTFS_LIBS)
+# partitioning plugin test.
+TESTS += \
+ test-partitioning1.sh
+if HAVE_GUESTFISH
+TESTS += \
+ test-partitioning2.sh
+endif HAVE_GUESTFISH
+
# pattern plugin test.
TESTS += \
test-pattern.sh \
diff --git a/tests/test-partitioning1.sh b/tests/test-partitioning1.sh
new file mode 100755
index 0000000..8dd2af7
--- /dev/null
+++ b/tests/test-partitioning1.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018 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 1: check that partitioning + partition filter = identity
+
+source ./functions.sh
+set -e
+set -x
+
+files="partitioning1.out partitioning1-p1 partitioning1-p2 partitioning1-p3
partitioning1-p4 partitioning1-p5 partitioning1-p6"
+rm -f $files
+cleanup_fn rm -f $files
+
+# Test that qemu-img works
+if ! qemu-img --help >/dev/null; then
+ echo "$0: missing or broken qemu-img"
+ exit 77
+fi
+
+# Create some odd-sized partitions. These exist to test alignment and
+# padding.
+truncate -s 1 partitioning1-p1
+truncate -s 511 partitioning1-p2
+truncate -s 10M partitioning1-p3
+truncate -s 1023 partitioning1-p4
+truncate -s 1 partitioning1-p5
+truncate -s 511 partitioning1-p6
+
+# Run nbdkit with partitioning plugin and partition filter.
+nbdkit -f -v -D partitioning.regions=1 -U - \
+ --filter=partition \
+ partitioning \
+ partitioning1-p1 partitioning1-p2 file-data partitioning1-p3 \
+ partition-type=mbr \
+ partition=3 \
+ --run 'qemu-img convert $nbd partitioning1.out'
+
+# Contents of partitioning1.out should be identical to file-data.
+cmp file-data partitioning1.out
+
+# Same test with GPT and more partitions.
+nbdkit -f -v -D partitioning.regions=1 -U - \
+ --filter=partition \
+ partitioning \
+ partitioning1-p1 partitioning1-p2 partitioning1-p3 \
+ partitioning1-p4 file-data partitioning1-p5 partitioning1-p6 \
+ partition-type=gpt \
+ partition=5 \
+ --run 'qemu-img convert $nbd partitioning1.out'
+
+cmp file-data partitioning1.out
diff --git a/tests/test-partitioning2.sh b/tests/test-partitioning2.sh
new file mode 100755
index 0000000..411f392
--- /dev/null
+++ b/tests/test-partitioning2.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# nbdkit
+# Copyright (C) 2018 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 2: Create a naked filesystem, embed in a partition, and try to
+# read/write it with guestfish.
+
+source ./functions.sh
+set -e
+set -x
+
+files="partitioning2.pid partitioning2.sock partitioning2.fs partitioning2.p1
partitioning2.p3"
+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 partitioning2.p1
+truncate -s 10M partitioning2.p3
+
+# Create the naked filesystem.
+truncate -s 20M partitioning2.fs
+mke2fs -F -t ext2 partitioning2.fs
+
+# Run nbdkit.
+start_nbdkit -P partitioning2.pid -U partitioning2.sock \
+ partitioning partitioning2.p1 partitioning2.fs partitioning2.p3 \
+ partition-type=gpt
+
+# Connect with guestfish and read/write stuff to partition 2.
+guestfish --format=raw -a "nbd://?socket=$PWD/partitioning2.sock"
<<'EOF'
+ run
+ mount /dev/sda2 /
+ touch /hello
+ fill-pattern "abc" 10000 /pattern
+ ll /
+ umount /dev/sda2
+ sync
+EOF
--
2.19.0.rc0