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
45 #include <sys/types.h>
49 #define NBDKIT_API_VERSION 2
50 #include <nbdkit-plugin.h>
55 static const char *tmpdir
= LARGE_TMPDIR
;
56 static int64_t requested_size
= -1; /* size parameter on the command line */
58 /* Shell variables. */
61 const char *key
, *value
;
64 /* This comes from default-command.c which is generated from
65 * default-command.sh.in.
67 extern const char *command
;
74 s
= getenv ("TMPDIR");
82 struct var
*v
, *v_next
;
84 for (v
= vars
; v
!= NULL
; v
= v_next
) {
91 tmpdisk_config (const char *key
, const char *value
)
93 if (strcmp (key
, "command") == 0) {
96 else if (strcmp (key
, "size") == 0) {
97 requested_size
= nbdkit_parse_size (value
);
98 if (requested_size
== -1)
102 /* This parameter cannot be set on the command line since it is used
103 * to pass the disk name to the command.
105 else if (strcmp (key
, "disk") == 0) {
106 nbdkit_error ("'disk' parameter cannot be set on the command line");
110 /* Any other parameter will be forwarded to a shell variable. */
114 new_var
= malloc (sizeof *new_var
);
115 if (new_var
== NULL
) {
120 new_var
->next
= NULL
;
122 new_var
->value
= value
;
124 /* Append it to the linked list. */
126 assert (last_var
== NULL
);
127 vars
= last_var
= new_var
;
130 assert (last_var
!= NULL
);
131 last_var
->next
= new_var
;
140 tmpdisk_config_complete (void)
142 if (requested_size
== -1) {
143 nbdkit_error ("size parameter is required");
150 #define tmpdisk_config_help \
151 "size=<SIZE> (required) Virtual filesystem size.\n" \
152 "label=<LABEL> The filesystem label.\n" \
153 "type=ext4|... The filesystem type.\n" \
154 "command=<COMMAND> Alternate command instead of mkfs."
162 /* Multi-conn is absolutely unsafe! In this callback it is simply
163 * returning the default value (no multi-conn), that's to make it
164 * clear for future authors.
167 tmpdisk_can_multi_conn (void *handle
)
173 tmpdisk_can_trim (void *handle
)
175 #ifdef FALLOC_FL_PUNCH_HOLE
182 /* Pretend we have native FUA support, but actually because all disks
183 * are temporary we will deliberately ignore flush/FUA operations.
186 tmpdisk_can_fua (void *handle
)
188 return NBDKIT_FUA_NATIVE
;
192 tmpdisk_get_size (void *handle
)
194 struct handle
*h
= handle
;
199 /* This creates and runs the full "mkfs" (or whatever) command. */
201 run_command (const char *disk
)
204 CLEANUP_FREE
char *cmd
= NULL
;
209 fp
= open_memstream (&cmd
, &len
);
211 nbdkit_error ("open_memstream: %m");
215 /* Avoid stdin/stdout leaking (because of nbdkit -s). */
216 fprintf (fp
, "exec </dev/null >/dev/null\n");
218 /* Set the standard shell variables. */
219 fprintf (fp
, "disk=");
220 shell_quote (disk
, fp
);
222 fprintf (fp
, "size=%" PRIi64
"\n", requested_size
);
225 /* The other parameters/shell variables. */
226 for (v
= vars
; v
!= NULL
; v
= v
->next
) {
227 /* Keys probably can never contain shell-unsafe chars (because of
228 * nbdkit's own restrictions), but quoting it makes it safe.
230 shell_quote (v
->key
, fp
);
232 shell_quote (v
->value
, fp
);
238 fprintf (fp
, "%s", command
);
240 if (fclose (fp
) == EOF
) {
241 nbdkit_error ("memstream failed");
247 nbdkit_error ("failed to execute command: %m");
250 if (WIFEXITED (r
) && WEXITSTATUS (r
) != 0) {
251 nbdkit_error ("command exited with code %d", WEXITSTATUS (r
));
254 else if (WIFSIGNALED (r
)) {
255 nbdkit_error ("command killed by signal %d", WTERMSIG (r
));
258 else if (WIFSTOPPED (r
)) {
259 nbdkit_error ("command stopped by signal %d", WSTOPSIG (r
));
266 /* For block devices, stat->st_size is not the true size. */
268 block_device_size (int fd
)
272 size
= lseek (fd
, 0, SEEK_END
);
274 nbdkit_error ("lseek: %m");
282 tmpdisk_open (int readonly
)
285 CLEANUP_FREE
char *dir
= NULL
, *disk
= NULL
;
289 h
= malloc (sizeof *h
);
291 nbdkit_error ("malloc: %m");
296 h
->can_punch_hole
= true;
298 /* For security reasons we have to create a temporary directory
299 * under tmpdir that only the current user can access. If we
300 * created it in a shared directory then another user might be able
301 * to see the temporary file being created and interfere with it
302 * before we reopen it in the plugin below.
304 if (asprintf (&dir
, "%s/tmpdiskXXXXXX", tmpdir
) == -1) {
305 nbdkit_error ("asprintf: %m");
308 if (mkdtemp (dir
) == NULL
) {
309 nbdkit_error ("%s: %m", dir
);
312 if (asprintf (&disk
, "%s/disk", dir
) == -1) {
313 nbdkit_error ("asprintf: %m");
317 /* Now run the mkfs command. */
318 if (run_command (disk
) == -1)
321 /* The external command must have created the disk, and then we must
322 * find the true size.
325 flags
= O_RDONLY
| O_CLOEXEC
;
327 flags
= O_RDWR
| O_CLOEXEC
;
328 h
->fd
= open (disk
, flags
);
330 nbdkit_error ("open: %s: %m", disk
);
334 if (fstat (h
->fd
, &statbuf
) == -1) {
335 nbdkit_error ("fstat: %s: %m", disk
);
339 /* The command could set $disk to a regular file or a block device
340 * (or a symlink to either), so we must check that here.
342 if (S_ISBLK (statbuf
.st_mode
)) {
343 h
->size
= block_device_size (h
->fd
);
347 else /* Regular file. */
348 h
->size
= statbuf
.st_size
;
349 nbdkit_debug ("tmpdisk: requested_size = %" PRIi64
", size = %" PRIi64
,
350 requested_size
, h
->size
);
352 /* We don't need the disk to appear in the filesystem since we hold
353 * a file descriptor and access it through that, so unlink the disk.
354 * This also ensures it is always cleaned up.
359 /* Return the handle. */
374 tmpdisk_close (void *handle
)
376 struct handle
*h
= handle
;
382 /* Read data from the file. */
384 tmpdisk_pread (void *handle
, void *buf
,
385 uint32_t count
, uint64_t offset
,
388 struct handle
*h
= handle
;
391 ssize_t r
= pread (h
->fd
, buf
, count
, offset
);
393 nbdkit_error ("pread: %m");
397 nbdkit_error ("pread: unexpected end of file");
408 /* Write data to the file. */
410 tmpdisk_pwrite (void *handle
, const void *buf
,
411 uint32_t count
, uint64_t offset
,
414 struct handle
*h
= handle
;
417 ssize_t r
= pwrite (h
->fd
, buf
, count
, offset
);
419 nbdkit_error ("pwrite: %m");
427 /* Deliberately ignore FUA if present in flags. */
432 /* This plugin deliberately provides a null flush operation, because
433 * all of the disks created are temporary.
436 tmpdisk_flush (void *handle
, uint32_t flags
)
441 #if defined (FALLOC_FL_PUNCH_HOLE)
443 do_fallocate (int fd
, int mode
, off_t offset
, off_t len
)
445 int r
= fallocate (fd
, mode
, offset
, len
);
446 if (r
== -1 && errno
== ENODEV
) {
447 /* kernel 3.10 fails with ENODEV for block device. Kernel >= 4.9 fails
448 * with EOPNOTSUPP in this case. Normalize errno to simplify callers.
458 return err
== ENOTSUP
|| err
== EOPNOTSUPP
;
462 /* Punch a hole in the file. */
464 tmpdisk_trim (void *handle
, uint32_t count
, uint64_t offset
, uint32_t flags
)
466 #ifdef FALLOC_FL_PUNCH_HOLE
467 struct handle
*h
= handle
;
470 if (h
->can_punch_hole
) {
471 r
= do_fallocate (h
->fd
, FALLOC_FL_PUNCH_HOLE
| FALLOC_FL_KEEP_SIZE
,
474 /* Trim is advisory; we don't care if it fails for anything other
477 if (errno
== EPERM
|| errno
== EIO
) {
478 nbdkit_error ("fallocate: %m");
482 if (is_enotsup (EOPNOTSUPP
))
483 h
->can_punch_hole
= false;
485 nbdkit_debug ("ignoring failed fallocate during trim: %m");
490 /* Deliberately ignore FUA if present in flags. */
495 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
497 static struct nbdkit_plugin plugin
= {
499 .version
= PACKAGE_VERSION
,
501 .load
= tmpdisk_load
,
502 .unload
= tmpdisk_unload
,
503 .config
= tmpdisk_config
,
504 .config_complete
= tmpdisk_config_complete
,
505 .config_help
= tmpdisk_config_help
,
506 .magic_config_key
= "size",
508 .can_multi_conn
= tmpdisk_can_multi_conn
,
509 .can_trim
= tmpdisk_can_trim
,
510 .can_fua
= tmpdisk_can_fua
,
511 .get_size
= tmpdisk_get_size
,
513 .open
= tmpdisk_open
,
514 .close
= tmpdisk_close
,
515 .pread
= tmpdisk_pread
,
516 .pwrite
= tmpdisk_pwrite
,
517 .flush
= tmpdisk_flush
,
518 .trim
= tmpdisk_trim
,
520 .errno_is_preserved
= 1,
523 NBDKIT_REGISTER_PLUGIN (plugin
)