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
47 #include <sys/types.h>
50 #ifdef HAVE_SYS_WAIT_H
56 #define NBDKIT_API_VERSION 2
57 #include <nbdkit-plugin.h>
60 #include "fdatasync.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. */
71 const char *key
, *value
;
74 /* This comes from default-command.c which is generated from
75 * default-command.sh.in.
77 extern const char *command
;
80 ondemand_unload (void)
82 struct var
*v
, *v_next
;
84 for (v
= vars
; v
!= NULL
; v
= v_next
) {
90 closedir (exportsdir
);
95 ondemand_config (const char *key
, const char *value
)
97 if (strcmp (key
, "command") == 0) {
100 else if (strcmp (key
, "size") == 0) {
101 requested_size
= nbdkit_parse_size (value
);
102 if (requested_size
== -1)
105 else if (strcmp (key
, "dir") == 0) {
106 dir
= nbdkit_realpath (value
);
111 else if (strcmp (key
, "wait") == 0) {
112 waitlock
= nbdkit_parse_bool (value
);
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");
125 /* Any other parameter will be forwarded to a shell variable. */
129 new_var
= malloc (sizeof *new_var
);
130 if (new_var
== NULL
) {
135 new_var
->next
= NULL
;
137 new_var
->value
= value
;
139 /* Append it to the linked list. */
141 assert (last_var
== NULL
);
142 vars
= last_var
= new_var
;
145 assert (last_var
!= NULL
);
146 last_var
->next
= new_var
;
155 ondemand_config_complete (void)
157 if (dir
== NULL
|| requested_size
== -1) {
158 nbdkit_error ("dir and size parameters are required");
166 ondemand_get_ready (void)
168 exportsdir
= opendir (dir
);
169 if (exportsdir
== NULL
) {
170 nbdkit_error ("opendir: %s: %m", dir
);
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
;
191 ondemand_list_exports (int readonly
, int default_only
,
192 struct nbdkit_exports
*exports
)
194 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&exports_lock
);
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)
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
214 if (strchr (d
->d_name
, '.') || strchr (d
->d_name
, ':'))
217 /* Skip the "default" filename which refers to the "" export. */
218 if (strcmp (d
->d_name
, "default") == 0)
221 if (nbdkit_add_export (exports
, d
->d_name
, NULL
) == -1)
225 /* Did readdir fail? */
227 nbdkit_error ("readdir: %s: %m", dir
);
235 ondemand_default_export (int readonly
, int is_tls
)
237 /* We always accept "" as an export name; canonicalize it to "default". */
244 const char *exportname
;
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)
255 ondemand_can_multi_conn (void *handle
)
261 ondemand_can_trim (void *handle
)
263 #ifdef FALLOC_FL_PUNCH_HOLE
271 ondemand_can_fua (void *handle
)
273 return NBDKIT_FUA_NATIVE
;
276 /* This creates and runs the full "mkfs" (or whatever) command. */
278 run_command (const char *disk
)
281 CLEANUP_FREE
char *cmd
= NULL
;
286 fp
= open_memstream (&cmd
, &len
);
288 nbdkit_error ("open_memstream: %m");
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
);
299 fprintf (fp
, "size=%" PRIi64
"\n", requested_size
);
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
);
309 shell_quote (v
->value
, fp
);
315 fprintf (fp
, "%s", command
);
317 if (fclose (fp
) == EOF
) {
318 nbdkit_error ("memstream failed");
324 nbdkit_error ("failed to execute command: %m");
327 if (WIFEXITED (r
) && WEXITSTATUS (r
) != 0) {
328 nbdkit_error ("command exited with code %d", WEXITSTATUS (r
));
331 else if (WIFSIGNALED (r
)) {
332 nbdkit_error ("command killed by signal %d", WTERMSIG (r
));
335 else if (WIFSTOPPED (r
)) {
336 nbdkit_error ("command stopped by signal %d", WSTOPSIG (r
));
343 /* For block devices, stat->st_size is not the true size. */
345 block_device_size (int fd
)
349 size
= lseek (fd
, 0, SEEK_END
);
351 nbdkit_error ("lseek: %m");
359 ondemand_open (int readonly
)
362 CLEANUP_FREE
char *disk
= NULL
;
370 h
= malloc (sizeof *h
);
372 nbdkit_error ("malloc: %m");
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");
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
);
398 /* Try to open the filesystem. */
400 flags
= O_RDONLY
| O_CLOEXEC
;
402 flags
= O_RDWR
| O_CLOEXEC
;
403 h
->fd
= openat (dirfd (exportsdir
), h
->exportname
, flags
);
405 if (errno
!= ENOENT
) {
406 nbdkit_error ("open: %s/%s: %m", dir
, h
->exportname
);
410 /* Create the filesystem. */
411 if (asprintf (&disk
, "%s/%s", dir
, h
->exportname
) == -1) {
412 nbdkit_error ("asprintf: %m");
416 /* Now run the mkfs command. */
417 if (run_command (disk
) == -1)
420 h
->fd
= openat (dirfd (exportsdir
), h
->exportname
, flags
);
422 nbdkit_error ("open: %s/%s: %m", dir
, h
->exportname
);
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.
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.
441 lock
.l_type
= F_RDLCK
;
443 lock
.l_type
= F_WRLCK
;
444 lock
.l_whence
= SEEK_SET
;
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
)
452 if (errno
== EACCES
|| errno
== EAGAIN
) {
453 nbdkit_error ("%s: filesystem is locked by another client",
455 /* XXX Would be nice if NBD protocol supported some kind of "is
456 * locked" indication. If it did we could use it here.
462 nbdkit_error ("fcntl: %s/%s: %m", dir
, h
->exportname
);
468 /* Find the size of the disk. */
469 if (fstat (h
->fd
, &statbuf
) == -1) {
470 nbdkit_error ("fstat: %s: %m", disk
);
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
);
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. */
502 ondemand_close (void *handle
)
504 struct handle
*h
= handle
;
511 ondemand_get_size (void *handle
)
513 struct handle
*h
= handle
;
518 /* Read data from the file. */
520 ondemand_pread (void *handle
, void *buf
,
521 uint32_t count
, uint64_t offset
,
524 struct handle
*h
= handle
;
527 ssize_t r
= pread (h
->fd
, buf
, count
, offset
);
529 nbdkit_error ("pread: %m");
533 nbdkit_error ("pread: unexpected end of file");
544 /* Flush the file to disk. */
546 ondemand_flush (void *handle
, uint32_t flags
)
548 struct handle
*h
= handle
;
550 if (fdatasync (h
->fd
) == -1) {
551 nbdkit_error ("fdatasync: %m");
558 /* Write data to the file. */
560 ondemand_pwrite (void *handle
, const void *buf
,
561 uint32_t count
, uint64_t offset
,
564 struct handle
*h
= handle
;
567 ssize_t r
= pwrite (h
->fd
, buf
, count
, offset
);
569 nbdkit_error ("pwrite: %m");
577 if ((flags
& NBDKIT_FLAG_FUA
) && ondemand_flush (handle
, 0) == -1)
583 #if defined (FALLOC_FL_PUNCH_HOLE)
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.
600 return err
== ENOTSUP
|| err
== EOPNOTSUPP
;
604 /* Punch a hole in the file. */
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
;
612 if (h
->can_punch_hole
) {
613 r
= do_fallocate (h
->fd
, FALLOC_FL_PUNCH_HOLE
| FALLOC_FL_KEEP_SIZE
,
616 /* Trim is advisory; we don't care if it fails for anything other
619 if (errno
== EPERM
|| errno
== EIO
) {
620 nbdkit_error ("fallocate: %m");
624 if (is_enotsup (EOPNOTSUPP
))
625 h
->can_punch_hole
= false;
627 nbdkit_debug ("ignoring failed fallocate during trim: %m");
632 if ((flags
& NBDKIT_FLAG_FUA
) && ondemand_flush (handle
, 0) == -1)
638 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
640 static struct nbdkit_plugin plugin
= {
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
)