4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46 #include <sys/types.h>
49 #include <nbdkit-plugin.h>
51 #include "ascii-string.h"
52 #include "byte-swapping.h"
53 #include "fdatasync.h"
54 #include "isaligned.h"
63 #include "virtual-disk.h"
65 /* Debug flag: -D partitioning.regions=1: Print the regions table. */
66 NBDKIT_DLL_PUBLIC
int partitioning_debug_regions
;
68 /* alignment, mbr_id, type_guid set on the command line for
69 * following partitions.
71 unsigned long alignment
= DEFAULT_ALIGNMENT
;
72 uint8_t mbr_id
= DEFAULT_MBR_ID
;
73 char type_guid
[16]; /* initialized by partitioning_load function below */
75 /* partition-type parameter. */
76 int parttype
= PARTTYPE_UNSET
;
78 /* Files supplied on the command line. */
81 /* Virtual disk layout. */
84 /* Primary and secondary partition tables and extended boot records.
85 * Secondary PT is only used for GPT. EBR array of sectors is only
86 * used for MBR with > 4 partitions and has length equal to
89 unsigned char *primary
= NULL
, *secondary
= NULL
, **ebr
= NULL
;
91 /* Used to generate random unique partition GUIDs for GPT. */
92 static struct random_state random_state
;
95 partitioning_load (void)
97 init_regions (&the_regions
);
98 parse_guid (DEFAULT_TYPE_GUID
, type_guid
);
99 xsrandom (time (NULL
), &random_state
);
103 partitioning_unload (void)
107 for (i
= 0; i
< the_files
.len
; ++i
)
108 close (the_files
.ptr
[i
].fd
);
109 free (the_files
.ptr
);
111 /* We don't need to free regions.regions[].u.data because it points
112 * to primary, secondary or ebr which we free here.
114 free_regions (&the_regions
);
119 for (i
= 0; i
< the_files
.len
-3; ++i
)
126 partitioning_config (const char *key
, const char *value
)
132 if (strcmp (key
, "file") == 0) {
133 file
.filename
= value
;
134 file
.alignment
= alignment
;
135 file
.mbr_id
= mbr_id
;
136 memcpy (file
.type_guid
, type_guid
, sizeof type_guid
);
138 file
.fd
= open (file
.filename
, O_RDWR
);
140 nbdkit_error ("%s: %m", file
.filename
);
143 if (fstat (file
.fd
, &file
.statbuf
) == -1) {
147 nbdkit_error ("%s: stat: %m", file
.filename
);
151 if (file
.statbuf
.st_size
== 0) {
152 nbdkit_error ("%s: zero length partitions are not allowed",
157 /* Create a random GUID used as "Unique partition GUID". However
158 * this doesn't follow GUID conventions so in theory could make an
159 * invalid value. This is only used by GPT, and we store it in
160 * the file structure because it must be the same across primary
161 * and secondary PT entries.
163 for (i
= 0; i
< 16; ++i
)
164 file
.guid
[i
] = xrandom (&random_state
) & 0xff;
166 if (files_append (&the_files
, file
) == -1) {
170 nbdkit_error ("realloc: %m");
174 else if (strcmp (key
, "partition-type") == 0) {
175 if (ascii_strcasecmp (value
, "mbr") == 0 ||
176 ascii_strcasecmp (value
, "dos") == 0)
177 parttype
= PARTTYPE_MBR
;
178 else if (ascii_strcasecmp (value
, "gpt") == 0)
179 parttype
= PARTTYPE_GPT
;
181 nbdkit_error ("unknown partition-type: %s", value
);
185 else if (strcmp (key
, "alignment") == 0) {
188 r
= nbdkit_parse_size (value
);
192 if (!(r
>= SECTOR_SIZE
&& r
<= MAX_ALIGNMENT
)) {
193 nbdkit_error ("partition alignment %" PRIi64
" should be "
194 ">= sector size %" PRIu64
" and "
195 "<= maximum alignment %" PRIu64
,
196 r
, SECTOR_SIZE
, MAX_ALIGNMENT
);
199 if (!IS_ALIGNED (r
, SECTOR_SIZE
)) {
200 nbdkit_error ("partition alignment %" PRIi64
" should be "
201 "a multiple of sector size %" PRIu64
,
208 else if (strcmp (key
, "mbr-id") == 0) {
209 if (ascii_strcasecmp (value
, "default") == 0)
210 mbr_id
= DEFAULT_MBR_ID
;
211 else if (nbdkit_parse_uint8_t ("mbr-id", value
, &mbr_id
) == -1)
214 else if (strcmp (key
, "type-guid") == 0) {
215 if (ascii_strcasecmp (value
, "default") == 0)
216 parse_guid (DEFAULT_TYPE_GUID
, type_guid
);
217 else if (parse_guid (value
, type_guid
) == -1) {
218 nbdkit_error ("could not validate GUID: %s", value
);
223 nbdkit_error ("unknown parameter '%s'", key
);
231 partitioning_config_complete (void)
237 /* Not enough / too many files? */
238 if (the_files
.len
== 0) {
239 nbdkit_error ("at least one file= parameter must be supplied");
244 for (i
= 0; i
< the_files
.len
; ++i
)
245 total_size
+= the_files
.ptr
[i
].statbuf
.st_size
;
246 needs_gpt
= total_size
> MAX_MBR_DISK_SIZE
;
248 /* Choose default parttype if not set. */
249 if (parttype
== PARTTYPE_UNSET
) {
250 if (needs_gpt
|| the_files
.len
> 4) {
251 parttype
= PARTTYPE_GPT
;
252 nbdkit_debug ("picking partition type GPT");
255 parttype
= PARTTYPE_MBR
;
256 nbdkit_debug ("picking partition type MBR");
259 else if (parttype
== PARTTYPE_MBR
&& needs_gpt
) {
260 nbdkit_error ("MBR partition table type supports "
261 "a maximum virtual disk size of about 2 TB, "
262 "but you requested %zu partition(s) "
263 "and a total size of %" PRIu64
" bytes (> %" PRIu64
"). "
264 "Try using: partition-type=gpt",
265 the_files
.len
, total_size
, (uint64_t) MAX_MBR_DISK_SIZE
);
272 #define partitioning_config_help \
273 "file=<FILENAME> (required) File(s) containing partitions\n" \
274 "partition-type=mbr|gpt Partition type\n" \
275 "alignment=<N> Set the alignment (bytes)\n" \
276 "mbr-id=default|0xN Set the MBR type byte\n" \
277 "type-guid=default|GUID Set the GPT partition type GUID"
280 partitioning_get_ready (void)
282 return create_virtual_disk_layout ();
285 /* Create the per-connection handle. */
287 partitioning_open (int readonly
)
289 return NBDKIT_HANDLE_NOT_NEEDED
;
292 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
294 /* Get the disk size. */
296 partitioning_get_size (void *handle
)
298 return virtual_size (&the_regions
);
301 /* Serves the same data over multiple connections. */
303 partitioning_can_multi_conn (void *handle
)
310 partitioning_can_cache (void *handle
)
312 /* Let nbdkit call pread to populate the file system cache. */
313 return NBDKIT_CACHE_EMULATE
;
318 partitioning_pread (void *handle
, void *buf
, uint32_t count
, uint64_t offset
)
321 const struct region
*region
= find_region (&the_regions
, offset
);
325 /* Length to end of region. */
326 len
= region
->end
- offset
+ 1;
330 switch (region
->type
) {
333 assert (i
< the_files
.len
);
334 r
= pread (the_files
.ptr
[i
].fd
, buf
, len
, offset
- region
->start
);
336 nbdkit_error ("pread: %s: %m", the_files
.ptr
[i
].filename
);
340 nbdkit_error ("pread: %s: unexpected end of file",
341 the_files
.ptr
[i
].filename
);
348 memcpy (buf
, ®ion
->u
.data
[offset
- region
->start
], len
);
352 memset (buf
, 0, len
);
366 partitioning_pwrite (void *handle
,
367 const void *buf
, uint32_t count
, uint64_t offset
)
370 const struct region
*region
= find_region (&the_regions
, offset
);
374 /* Length to end of region. */
375 len
= region
->end
- offset
+ 1;
379 switch (region
->type
) {
382 assert (i
< the_files
.len
);
383 r
= pwrite (the_files
.ptr
[i
].fd
, buf
, len
, offset
- region
->start
);
385 nbdkit_error ("pwrite: %s: %m", the_files
.ptr
[i
].filename
);
392 /* You can only write same data as already present. */
393 if (memcmp (®ion
->u
.data
[offset
- region
->start
], buf
, len
) != 0) {
394 nbdkit_error ("attempt to change partition table of virtual disk");
401 /* You can only write zeroes. */
402 if (!is_zero (buf
, len
)) {
403 nbdkit_error ("write non-zeroes to padding region");
420 partitioning_flush (void *handle
)
424 for (i
= 0; i
< the_files
.len
; ++i
) {
425 if (fdatasync (the_files
.ptr
[i
].fd
) == -1) {
426 nbdkit_error ("fdatasync: %m");
434 static struct nbdkit_plugin plugin
= {
435 .name
= "partitioning",
436 .version
= PACKAGE_VERSION
,
437 .load
= partitioning_load
,
438 .unload
= partitioning_unload
,
439 .config
= partitioning_config
,
440 .config_complete
= partitioning_config_complete
,
441 .config_help
= partitioning_config_help
,
442 .magic_config_key
= "file",
443 .get_ready
= partitioning_get_ready
,
444 .open
= partitioning_open
,
445 .get_size
= partitioning_get_size
,
446 .can_multi_conn
= partitioning_can_multi_conn
,
447 .can_cache
= partitioning_can_cache
,
448 .pread
= partitioning_pread
,
449 .pwrite
= partitioning_pwrite
,
450 .flush
= partitioning_flush
,
451 /* In this plugin, errno is preserved properly along error return
452 * paths from failed system calls.
454 .errno_is_preserved
= 1,
457 NBDKIT_REGISTER_PLUGIN (plugin
)