Update Red Hat Copyright Notices
[nbdkit.git] / plugins / ondemand / ondemand.c
blob5f94daf00e25484fc42f77e5768324b8d157eef0
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 <dirent.h>
44 #include <limits.h>
45 #include <errno.h>
46 #include <assert.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
50 #ifdef HAVE_SYS_WAIT_H
51 #include <sys/wait.h>
52 #endif
54 #include <pthread.h>
56 #define NBDKIT_API_VERSION 2
57 #include <nbdkit-plugin.h>
59 #include "cleanup.h"
60 #include "fdatasync.h"
61 #include "utils.h"
63 static char *dir; /* dir parameter */
64 static DIR *exportsdir; /* opened exports dir */
65 static int64_t requested_size = -1; /* size parameter on the command line */
66 static int waitlock; /* wait if locked */
68 /* Shell variables. */
69 static struct var {
70 struct var *next;
71 const char *key, *value;
72 } *vars, *last_var;
74 /* This comes from default-command.c which is generated from
75 * default-command.sh.in.
77 extern const char *command;
79 static void
80 ondemand_unload (void)
82 struct var *v, *v_next;
84 for (v = vars; v != NULL; v = v_next) {
85 v_next = v->next;
86 free (v);
89 if (exportsdir)
90 closedir (exportsdir);
91 free (dir);
94 static int
95 ondemand_config (const char *key, const char *value)
97 if (strcmp (key, "command") == 0) {
98 command = value;
100 else if (strcmp (key, "size") == 0) {
101 requested_size = nbdkit_parse_size (value);
102 if (requested_size == -1)
103 return -1;
105 else if (strcmp (key, "dir") == 0) {
106 dir = nbdkit_realpath (value);
107 if (dir == NULL)
108 return -1;
111 else if (strcmp (key, "wait") == 0) {
112 waitlock = nbdkit_parse_bool (value);
113 if (waitlock == -1)
114 return -1;
117 /* This parameter cannot be set on the command line since it is used
118 * to pass the disk name to the command.
120 else if (strcmp (key, "disk") == 0) {
121 nbdkit_error ("'disk' parameter cannot be set on the command line");
122 return -1;
125 /* Any other parameter will be forwarded to a shell variable. */
126 else {
127 struct var *new_var;
129 new_var = malloc (sizeof *new_var);
130 if (new_var == NULL) {
131 perror ("malloc");
132 exit (EXIT_FAILURE);
135 new_var->next = NULL;
136 new_var->key = key;
137 new_var->value = value;
139 /* Append it to the linked list. */
140 if (vars == NULL) {
141 assert (last_var == NULL);
142 vars = last_var = new_var;
144 else {
145 assert (last_var != NULL);
146 last_var->next = new_var;
147 last_var = new_var;
151 return 0;
154 static int
155 ondemand_config_complete (void)
157 if (dir == NULL || requested_size == -1) {
158 nbdkit_error ("dir and size parameters are required");
159 return -1;
162 return 0;
165 static int
166 ondemand_get_ready (void)
168 exportsdir = opendir (dir);
169 if (exportsdir == NULL) {
170 nbdkit_error ("opendir: %s: %m", dir);
171 return -1;
174 return 0;
177 #define ondemand_config_help \
178 "dir=<EXPORTSDIR> (required) Directory containing filesystems.\n" \
179 "size=<SIZE> (required) Virtual filesystem size.\n" \
180 "label=<LABEL> The filesystem label.\n" \
181 "type=ext4|... The filesystem type.\n" \
182 "wait=true Wait instead of rejecting second client.\n" \
183 "command=<COMMAND> Alternate command instead of mkfs."
185 /* Because we rewind the exportsdir handle, we need a lock to protect
186 * list_exports from being called in parallel.
188 static pthread_mutex_t exports_lock = PTHREAD_MUTEX_INITIALIZER;
190 static int
191 ondemand_list_exports (int readonly, int default_only,
192 struct nbdkit_exports *exports)
194 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&exports_lock);
195 struct dirent *d;
197 /* First entry should be the default export. XXX Should we check if
198 * the "default" file was created? I don't think we need to.
200 if (nbdkit_add_export (exports, "", NULL) == -1)
201 return -1;
202 if (default_only) return 0;
204 /* Read the rest of the exports. */
205 rewinddir (exportsdir);
207 /* XXX Output is not sorted. Does it matter? */
208 while (errno = 0, (d = readdir (exportsdir)) != NULL) {
209 /* Skip any file containing non-permitted characters '.' and ':'.
210 * As a side effect this skips all dot-files. Commands can use
211 * dot-files to "hide" files in the export dir (eg. if needing to
212 * keep state).
214 if (strchr (d->d_name, '.') || strchr (d->d_name, ':'))
215 continue;
217 /* Skip the "default" filename which refers to the "" export. */
218 if (strcmp (d->d_name, "default") == 0)
219 continue;
221 if (nbdkit_add_export (exports, d->d_name, NULL) == -1)
222 return -1;
225 /* Did readdir fail? */
226 if (errno != 0) {
227 nbdkit_error ("readdir: %s: %m", dir);
228 return -1;
231 return 0;
234 static const char *
235 ondemand_default_export (int readonly, int is_tls)
237 /* We always accept "" as an export name; canonicalize it to "default". */
238 return "default";
241 struct handle {
242 int fd;
243 int64_t size;
244 const char *exportname;
245 bool can_punch_hole;
248 /* In theory clients that want multi-conn should all pass the same
249 * export name, and that would be safe. However our locking
250 * implementation (see ondemand_open) does not allow this. It seems
251 * to work around this we will need to implement client UUID in the
252 * protocol. (https://lists.debian.org/nbd/2020/08/msg00001.html)
254 static int
255 ondemand_can_multi_conn (void *handle)
257 return 0;
260 static int
261 ondemand_can_trim (void *handle)
263 #ifdef FALLOC_FL_PUNCH_HOLE
264 return 1;
265 #else
266 return 0;
267 #endif
270 static int
271 ondemand_can_fua (void *handle)
273 return NBDKIT_FUA_NATIVE;
276 /* This creates and runs the full "mkfs" (or whatever) command. */
277 static int
278 run_command (const char *disk)
280 FILE *fp;
281 CLEANUP_FREE char *cmd = NULL;
282 size_t len = 0;
283 int r;
284 struct var *v;
286 fp = open_memstream (&cmd, &len);
287 if (fp == NULL) {
288 nbdkit_error ("open_memstream: %m");
289 return -1;
292 /* Avoid stdin/stdout leaking (because of nbdkit -s). */
293 fprintf (fp, "exec </dev/null >/dev/null\n");
295 /* Set the standard shell variables. */
296 fprintf (fp, "disk=");
297 shell_quote (disk, fp);
298 putc ('\n', fp);
299 fprintf (fp, "size=%" PRIi64 "\n", requested_size);
300 putc ('\n', fp);
302 /* The other parameters/shell variables. */
303 for (v = vars; v != NULL; v = v->next) {
304 /* Keys probably can never contain shell-unsafe chars (because of
305 * nbdkit's own restrictions), but quoting it makes it safe.
307 shell_quote (v->key, fp);
308 putc ('=', fp);
309 shell_quote (v->value, fp);
310 putc ('\n', fp);
312 putc ('\n', fp);
314 /* The command. */
315 fprintf (fp, "%s", command);
317 if (fclose (fp) == EOF) {
318 nbdkit_error ("memstream failed");
319 return -1;
322 r = system (cmd);
323 if (r == -1) {
324 nbdkit_error ("failed to execute command: %m");
325 return -1;
327 if (WIFEXITED (r) && WEXITSTATUS (r) != 0) {
328 nbdkit_error ("command exited with code %d", WEXITSTATUS (r));
329 return -1;
331 else if (WIFSIGNALED (r)) {
332 nbdkit_error ("command killed by signal %d", WTERMSIG (r));
333 return -1;
335 else if (WIFSTOPPED (r)) {
336 nbdkit_error ("command stopped by signal %d", WSTOPSIG (r));
337 return -1;
340 return 0;
343 /* For block devices, stat->st_size is not the true size. */
344 static int64_t
345 block_device_size (int fd)
347 off_t size;
349 size = lseek (fd, 0, SEEK_END);
350 if (size == -1) {
351 nbdkit_error ("lseek: %m");
352 return -1;
355 return size;
358 static void *
359 ondemand_open (int readonly)
361 struct handle *h;
362 CLEANUP_FREE char *disk = NULL;
363 int flags, err;
364 struct stat statbuf;
365 #ifdef F_OFD_SETLK
366 struct flock lock;
367 int cmd;
368 #endif
370 h = malloc (sizeof *h);
371 if (h == NULL) {
372 nbdkit_error ("malloc: %m");
373 goto error;
375 h->fd = -1;
376 h->size = -1;
377 h->can_punch_hole = true;
379 /* This is safe since we're only storing it in the handle, so only
380 * for the lifetime of this connection.
382 h->exportname = nbdkit_export_name ();
383 if (!h->exportname) {
384 nbdkit_error ("internal error: expected nbdkit_export_name () != NULL");
385 goto error;
387 assert (strcmp (h->exportname, "") != 0); /* see .default_export */
389 /* Verify that the export name is valid. */
390 if (strlen (h->exportname) > NAME_MAX ||
391 strchr (h->exportname, '.') ||
392 strchr (h->exportname, '/') ||
393 strchr (h->exportname, ':')) {
394 nbdkit_error ("invalid exportname ‘%s’ rejected", h->exportname);
395 goto error;
398 /* Try to open the filesystem. */
399 if (readonly)
400 flags = O_RDONLY | O_CLOEXEC;
401 else
402 flags = O_RDWR | O_CLOEXEC;
403 h->fd = openat (dirfd (exportsdir), h->exportname, flags);
404 if (h->fd == -1) {
405 if (errno != ENOENT) {
406 nbdkit_error ("open: %s/%s: %m", dir, h->exportname);
407 goto error;
410 /* Create the filesystem. */
411 if (asprintf (&disk, "%s/%s", dir, h->exportname) == -1) {
412 nbdkit_error ("asprintf: %m");
413 goto error;
416 /* Now run the mkfs command. */
417 if (run_command (disk) == -1)
418 goto error;
420 h->fd = openat (dirfd (exportsdir), h->exportname, flags);
421 if (h->fd == -1) {
422 nbdkit_error ("open: %s/%s: %m", dir, h->exportname);
423 goto error;
427 /* Lock the file to prevent filesystem corruption.
429 * This uses a currently Linux-specific extension. It requires
430 * Linux >= 3.15 (released in 2014, later backported to RHEL 7).
431 * There is no sensible way to do this in pure POSIX.
433 #ifdef F_OFD_SETLK
434 memset (&lock, 0, sizeof lock);
435 /* While we do check the readonly flag here, it's not very useful
436 * because NBD clients cannot specify that they want to open a
437 * connection readonly, and using the -r command line flag is not
438 * very useful with this plugin.
440 if (readonly)
441 lock.l_type = F_RDLCK;
442 else
443 lock.l_type = F_WRLCK;
444 lock.l_whence = SEEK_SET;
445 lock.l_start = 0;
446 lock.l_len = 0;
447 again:
448 cmd = waitlock ? F_OFD_SETLKW : F_OFD_SETLK;
449 if (fcntl (h->fd, cmd, &lock) == -1) {
450 if (errno == EINTR && cmd == F_OFD_SETLKW)
451 goto again;
452 if (errno == EACCES || errno == EAGAIN) {
453 nbdkit_error ("%s: filesystem is locked by another client",
454 h->exportname);
455 /* XXX Would be nice if NBD protocol supported some kind of "is
456 * locked" indication. If it did we could use it here.
458 errno = EINVAL;
459 goto error;
461 else {
462 nbdkit_error ("fcntl: %s/%s: %m", dir, h->exportname);
463 goto error;
466 #endif
468 /* Find the size of the disk. */
469 if (fstat (h->fd, &statbuf) == -1) {
470 nbdkit_error ("fstat: %s: %m", disk);
471 goto error;
474 /* The command could set $disk to a regular file or a block device
475 * (or a symlink to either), so we must check that here.
477 if (S_ISBLK (statbuf.st_mode)) {
478 h->size = block_device_size (h->fd);
479 if (h->size == -1)
480 goto error;
482 else /* Regular file. */
483 h->size = statbuf.st_size;
484 nbdkit_debug ("ondemand: requested_size = %" PRIi64 ", size = %" PRIi64,
485 requested_size, h->size);
487 /* Return the handle. */
488 return h;
490 error:
491 err = errno;
492 if (h) {
493 if (h->fd >= 0)
494 close (h->fd);
495 free (h);
497 errno = err;
498 return NULL;
501 static void
502 ondemand_close (void *handle)
504 struct handle *h = handle;
506 close (h->fd);
507 free (h);
510 static int64_t
511 ondemand_get_size (void *handle)
513 struct handle *h = handle;
515 return h->size;
518 /* Read data from the file. */
519 static int
520 ondemand_pread (void *handle, void *buf,
521 uint32_t count, uint64_t offset,
522 uint32_t flags)
524 struct handle *h = handle;
526 while (count > 0) {
527 ssize_t r = pread (h->fd, buf, count, offset);
528 if (r == -1) {
529 nbdkit_error ("pread: %m");
530 return -1;
532 if (r == 0) {
533 nbdkit_error ("pread: unexpected end of file");
534 return -1;
536 buf += r;
537 count -= r;
538 offset += r;
541 return 0;
544 /* Flush the file to disk. */
545 static int
546 ondemand_flush (void *handle, uint32_t flags)
548 struct handle *h = handle;
550 if (fdatasync (h->fd) == -1) {
551 nbdkit_error ("fdatasync: %m");
552 return -1;
555 return 0;
558 /* Write data to the file. */
559 static int
560 ondemand_pwrite (void *handle, const void *buf,
561 uint32_t count, uint64_t offset,
562 uint32_t flags)
564 struct handle *h = handle;
566 while (count > 0) {
567 ssize_t r = pwrite (h->fd, buf, count, offset);
568 if (r == -1) {
569 nbdkit_error ("pwrite: %m");
570 return -1;
572 buf += r;
573 count -= r;
574 offset += r;
577 if ((flags & NBDKIT_FLAG_FUA) && ondemand_flush (handle, 0) == -1)
578 return -1;
580 return 0;
583 #if defined (FALLOC_FL_PUNCH_HOLE)
584 static int
585 do_fallocate (int fd, int mode, off_t offset, off_t len)
587 int r = fallocate (fd, mode, offset, len);
588 if (r == -1 && errno == ENODEV) {
589 /* kernel 3.10 fails with ENODEV for block device. Kernel >= 4.9 fails
590 * with EOPNOTSUPP in this case. Normalize errno to simplify callers.
592 errno = EOPNOTSUPP;
594 return r;
597 static bool
598 is_enotsup (int err)
600 return err == ENOTSUP || err == EOPNOTSUPP;
602 #endif
604 /* Punch a hole in the file. */
605 static int
606 ondemand_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
608 #ifdef FALLOC_FL_PUNCH_HOLE
609 struct handle *h = handle;
610 int r;
612 if (h->can_punch_hole) {
613 r = do_fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
614 offset, count);
615 if (r == -1) {
616 /* Trim is advisory; we don't care if it fails for anything other
617 * than EIO or EPERM.
619 if (errno == EPERM || errno == EIO) {
620 nbdkit_error ("fallocate: %m");
621 return -1;
624 if (is_enotsup (EOPNOTSUPP))
625 h->can_punch_hole = false;
627 nbdkit_debug ("ignoring failed fallocate during trim: %m");
630 #endif
632 if ((flags & NBDKIT_FLAG_FUA) && ondemand_flush (handle, 0) == -1)
633 return -1;
635 return 0;
638 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
640 static struct nbdkit_plugin plugin = {
641 .name = "ondemand",
642 .version = PACKAGE_VERSION,
644 .unload = ondemand_unload,
645 .config = ondemand_config,
646 .config_complete = ondemand_config_complete,
647 .config_help = ondemand_config_help,
648 .magic_config_key = "size",
649 .get_ready = ondemand_get_ready,
651 .list_exports = ondemand_list_exports,
652 .default_export = ondemand_default_export,
654 .can_multi_conn = ondemand_can_multi_conn,
655 .can_trim = ondemand_can_trim,
656 .can_fua = ondemand_can_fua,
657 .get_size = ondemand_get_size,
659 .open = ondemand_open,
660 .close = ondemand_close,
661 .pread = ondemand_pread,
662 .pwrite = ondemand_pwrite,
663 .flush = ondemand_flush,
664 .trim = ondemand_trim,
666 .errno_is_preserved = 1,
669 NBDKIT_REGISTER_PLUGIN (plugin)