Update Red Hat Copyright Notices
[nbdkit.git] / plugins / partitioning / partitioning.c
blob4593601dcf023365e7ccbf807ca27507d35923eb
1 /* nbdkit
2 * Copyright Red Hat
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
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
30 * SUCH DAMAGE.
33 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <stdbool.h>
38 #include <stdint.h>
39 #include <inttypes.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <fcntl.h>
43 #include <assert.h>
44 #include <errno.h>
45 #include <time.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
49 #include <nbdkit-plugin.h>
51 #include "ascii-string.h"
52 #include "byte-swapping.h"
53 #include "fdatasync.h"
54 #include "isaligned.h"
55 #include "iszero.h"
56 #include "pread.h"
57 #include "pwrite.h"
58 #include "rounding.h"
59 #include "vector.h"
61 #include "random.h"
62 #include "regions.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. */
79 files the_files;
81 /* Virtual disk layout. */
82 regions the_regions;
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
87 * the_files.size-3.
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;
94 static void
95 partitioning_load (void)
97 init_regions (&the_regions);
98 parse_guid (DEFAULT_TYPE_GUID, type_guid);
99 xsrandom (time (NULL), &random_state);
102 static void
103 partitioning_unload (void)
105 size_t i;
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);
116 free (primary);
117 free (secondary);
118 if (ebr) {
119 for (i = 0; i < the_files.len-3; ++i)
120 free (ebr[i]);
121 free (ebr);
125 static int
126 partitioning_config (const char *key, const char *value)
128 struct file file;
129 size_t i;
130 int err;
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);
139 if (file.fd == -1) {
140 nbdkit_error ("%s: %m", file.filename);
141 return -1;
143 if (fstat (file.fd, &file.statbuf) == -1) {
144 err = errno;
145 close (file.fd);
146 errno = err;
147 nbdkit_error ("%s: stat: %m", file.filename);
148 return -1;
151 if (file.statbuf.st_size == 0) {
152 nbdkit_error ("%s: zero length partitions are not allowed",
153 file.filename);
154 return -1;
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) {
167 err = errno;
168 close (file.fd);
169 errno = err;
170 nbdkit_error ("realloc: %m");
171 return -1;
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;
180 else {
181 nbdkit_error ("unknown partition-type: %s", value);
182 return -1;
185 else if (strcmp (key, "alignment") == 0) {
186 int64_t r;
188 r = nbdkit_parse_size (value);
189 if (r == -1)
190 return -1;
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);
197 return -1;
199 if (!IS_ALIGNED (r, SECTOR_SIZE)) {
200 nbdkit_error ("partition alignment %" PRIi64 " should be "
201 "a multiple of sector size %" PRIu64,
202 r, SECTOR_SIZE);
203 return -1;
206 alignment = r;
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)
212 return -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);
219 return -1;
222 else {
223 nbdkit_error ("unknown parameter '%s'", key);
224 return -1;
227 return 0;
230 static int
231 partitioning_config_complete (void)
233 size_t i;
234 uint64_t total_size;
235 bool needs_gpt;
237 /* Not enough / too many files? */
238 if (the_files.len == 0) {
239 nbdkit_error ("at least one file= parameter must be supplied");
240 return -1;
243 total_size = 0;
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");
254 else {
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);
266 return -1;
269 return 0;
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"
279 static int
280 partitioning_get_ready (void)
282 return create_virtual_disk_layout ();
285 /* Create the per-connection handle. */
286 static void *
287 partitioning_open (int readonly)
289 return NBDKIT_HANDLE_NOT_NEEDED;
292 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
294 /* Get the disk size. */
295 static int64_t
296 partitioning_get_size (void *handle)
298 return virtual_size (&the_regions);
301 /* Serves the same data over multiple connections. */
302 static int
303 partitioning_can_multi_conn (void *handle)
305 return 1;
308 /* Cache. */
309 static int
310 partitioning_can_cache (void *handle)
312 /* Let nbdkit call pread to populate the file system cache. */
313 return NBDKIT_CACHE_EMULATE;
316 /* Read data. */
317 static int
318 partitioning_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
320 while (count > 0) {
321 const struct region *region = find_region (&the_regions, offset);
322 size_t i, len;
323 ssize_t r;
325 /* Length to end of region. */
326 len = region->end - offset + 1;
327 if (len > count)
328 len = count;
330 switch (region->type) {
331 case region_file:
332 i = region->u.i;
333 assert (i < the_files.len);
334 r = pread (the_files.ptr[i].fd, buf, len, offset - region->start);
335 if (r == -1) {
336 nbdkit_error ("pread: %s: %m", the_files.ptr[i].filename);
337 return -1;
339 if (r == 0) {
340 nbdkit_error ("pread: %s: unexpected end of file",
341 the_files.ptr[i].filename);
342 return -1;
344 len = r;
345 break;
347 case region_data:
348 memcpy (buf, &region->u.data[offset - region->start], len);
349 break;
351 case region_zero:
352 memset (buf, 0, len);
353 break;
356 count -= len;
357 buf += len;
358 offset += len;
361 return 0;
364 /* Write data. */
365 static int
366 partitioning_pwrite (void *handle,
367 const void *buf, uint32_t count, uint64_t offset)
369 while (count > 0) {
370 const struct region *region = find_region (&the_regions, offset);
371 size_t i, len;
372 ssize_t r;
374 /* Length to end of region. */
375 len = region->end - offset + 1;
376 if (len > count)
377 len = count;
379 switch (region->type) {
380 case region_file:
381 i = region->u.i;
382 assert (i < the_files.len);
383 r = pwrite (the_files.ptr[i].fd, buf, len, offset - region->start);
384 if (r == -1) {
385 nbdkit_error ("pwrite: %s: %m", the_files.ptr[i].filename);
386 return -1;
388 len = r;
389 break;
391 case region_data:
392 /* You can only write same data as already present. */
393 if (memcmp (&region->u.data[offset - region->start], buf, len) != 0) {
394 nbdkit_error ("attempt to change partition table of virtual disk");
395 errno = EIO;
396 return -1;
398 break;
400 case region_zero:
401 /* You can only write zeroes. */
402 if (!is_zero (buf, len)) {
403 nbdkit_error ("write non-zeroes to padding region");
404 errno = EIO;
405 return -1;
407 break;
410 count -= len;
411 buf += len;
412 offset += len;
415 return 0;
418 /* Flush. */
419 static int
420 partitioning_flush (void *handle)
422 size_t i;
424 for (i = 0; i < the_files.len; ++i) {
425 if (fdatasync (the_files.ptr[i].fd) == -1) {
426 nbdkit_error ("fdatasync: %m");
427 return -1;
431 return 0;
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)