Update Red Hat Copyright Notices
[nbdkit.git] / plugins / tmpdisk / tmpdisk.c
blob45b58427b0fd5cf3b737bda0bff8a27640fc06d9
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 <errno.h>
44 #include <assert.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <sys/wait.h>
49 #define NBDKIT_API_VERSION 2
50 #include <nbdkit-plugin.h>
52 #include "cleanup.h"
53 #include "utils.h"
55 static const char *tmpdir = LARGE_TMPDIR;
56 static int64_t requested_size = -1; /* size parameter on the command line */
58 /* Shell variables. */
59 static struct var {
60 struct var *next;
61 const char *key, *value;
62 } *vars, *last_var;
64 /* This comes from default-command.c which is generated from
65 * default-command.sh.in.
67 extern const char *command;
69 static void
70 tmpdisk_load (void)
72 const char *s;
74 s = getenv ("TMPDIR");
75 if (s)
76 tmpdir = s;
79 static void
80 tmpdisk_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);
90 static int
91 tmpdisk_config (const char *key, const char *value)
93 if (strcmp (key, "command") == 0) {
94 command = value;
96 else if (strcmp (key, "size") == 0) {
97 requested_size = nbdkit_parse_size (value);
98 if (requested_size == -1)
99 return -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");
107 return -1;
110 /* Any other parameter will be forwarded to a shell variable. */
111 else {
112 struct var *new_var;
114 new_var = malloc (sizeof *new_var);
115 if (new_var == NULL) {
116 perror ("malloc");
117 exit (EXIT_FAILURE);
120 new_var->next = NULL;
121 new_var->key = key;
122 new_var->value = value;
124 /* Append it to the linked list. */
125 if (vars == NULL) {
126 assert (last_var == NULL);
127 vars = last_var = new_var;
129 else {
130 assert (last_var != NULL);
131 last_var->next = new_var;
132 last_var = new_var;
136 return 0;
139 static int
140 tmpdisk_config_complete (void)
142 if (requested_size == -1) {
143 nbdkit_error ("size parameter is required");
144 return -1;
147 return 0;
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."
156 struct handle {
157 int fd;
158 int64_t size;
159 bool can_punch_hole;
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.
166 static int
167 tmpdisk_can_multi_conn (void *handle)
169 return 0;
172 static int
173 tmpdisk_can_trim (void *handle)
175 #ifdef FALLOC_FL_PUNCH_HOLE
176 return 1;
177 #else
178 return 0;
179 #endif
182 /* Pretend we have native FUA support, but actually because all disks
183 * are temporary we will deliberately ignore flush/FUA operations.
185 static int
186 tmpdisk_can_fua (void *handle)
188 return NBDKIT_FUA_NATIVE;
191 static int64_t
192 tmpdisk_get_size (void *handle)
194 struct handle *h = handle;
196 return h->size;
199 /* This creates and runs the full "mkfs" (or whatever) command. */
200 static int
201 run_command (const char *disk)
203 FILE *fp;
204 CLEANUP_FREE char *cmd = NULL;
205 size_t len = 0;
206 int r;
207 struct var *v;
209 fp = open_memstream (&cmd, &len);
210 if (fp == NULL) {
211 nbdkit_error ("open_memstream: %m");
212 return -1;
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);
221 putc ('\n', fp);
222 fprintf (fp, "size=%" PRIi64 "\n", requested_size);
223 putc ('\n', fp);
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);
231 putc ('=', fp);
232 shell_quote (v->value, fp);
233 putc ('\n', fp);
235 putc ('\n', fp);
237 /* The command. */
238 fprintf (fp, "%s", command);
240 if (fclose (fp) == EOF) {
241 nbdkit_error ("memstream failed");
242 return -1;
245 r = system (cmd);
246 if (r == -1) {
247 nbdkit_error ("failed to execute command: %m");
248 return -1;
250 if (WIFEXITED (r) && WEXITSTATUS (r) != 0) {
251 nbdkit_error ("command exited with code %d", WEXITSTATUS (r));
252 return -1;
254 else if (WIFSIGNALED (r)) {
255 nbdkit_error ("command killed by signal %d", WTERMSIG (r));
256 return -1;
258 else if (WIFSTOPPED (r)) {
259 nbdkit_error ("command stopped by signal %d", WSTOPSIG (r));
260 return -1;
263 return 0;
266 /* For block devices, stat->st_size is not the true size. */
267 static int64_t
268 block_device_size (int fd)
270 off_t size;
272 size = lseek (fd, 0, SEEK_END);
273 if (size == -1) {
274 nbdkit_error ("lseek: %m");
275 return -1;
278 return size;
281 static void *
282 tmpdisk_open (int readonly)
284 struct handle *h;
285 CLEANUP_FREE char *dir = NULL, *disk = NULL;
286 int flags;
287 struct stat statbuf;
289 h = malloc (sizeof *h);
290 if (h == NULL) {
291 nbdkit_error ("malloc: %m");
292 goto error;
294 h->fd = -1;
295 h->size = -1;
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");
306 goto error;
308 if (mkdtemp (dir) == NULL) {
309 nbdkit_error ("%s: %m", dir);
310 goto error;
312 if (asprintf (&disk, "%s/disk", dir) == -1) {
313 nbdkit_error ("asprintf: %m");
314 goto error;
317 /* Now run the mkfs command. */
318 if (run_command (disk) == -1)
319 goto error;
321 /* The external command must have created the disk, and then we must
322 * find the true size.
324 if (readonly)
325 flags = O_RDONLY | O_CLOEXEC;
326 else
327 flags = O_RDWR | O_CLOEXEC;
328 h->fd = open (disk, flags);
329 if (h->fd == -1) {
330 nbdkit_error ("open: %s: %m", disk);
331 goto error;
334 if (fstat (h->fd, &statbuf) == -1) {
335 nbdkit_error ("fstat: %s: %m", disk);
336 goto error;
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);
344 if (h->size == -1)
345 goto error;
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.
356 unlink (disk);
357 rmdir (dir);
359 /* Return the handle. */
360 return h;
362 error:
363 if (h) {
364 if (h->fd >= 0) {
365 close (h->fd);
366 unlink (disk);
368 free (h);
370 return NULL;
373 static void
374 tmpdisk_close (void *handle)
376 struct handle *h = handle;
378 close (h->fd);
379 free (h);
382 /* Read data from the file. */
383 static int
384 tmpdisk_pread (void *handle, void *buf,
385 uint32_t count, uint64_t offset,
386 uint32_t flags)
388 struct handle *h = handle;
390 while (count > 0) {
391 ssize_t r = pread (h->fd, buf, count, offset);
392 if (r == -1) {
393 nbdkit_error ("pread: %m");
394 return -1;
396 if (r == 0) {
397 nbdkit_error ("pread: unexpected end of file");
398 return -1;
400 buf += r;
401 count -= r;
402 offset += r;
405 return 0;
408 /* Write data to the file. */
409 static int
410 tmpdisk_pwrite (void *handle, const void *buf,
411 uint32_t count, uint64_t offset,
412 uint32_t flags)
414 struct handle *h = handle;
416 while (count > 0) {
417 ssize_t r = pwrite (h->fd, buf, count, offset);
418 if (r == -1) {
419 nbdkit_error ("pwrite: %m");
420 return -1;
422 buf += r;
423 count -= r;
424 offset += r;
427 /* Deliberately ignore FUA if present in flags. */
429 return 0;
432 /* This plugin deliberately provides a null flush operation, because
433 * all of the disks created are temporary.
435 static int
436 tmpdisk_flush (void *handle, uint32_t flags)
438 return 0;
441 #if defined (FALLOC_FL_PUNCH_HOLE)
442 static int
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.
450 errno = EOPNOTSUPP;
452 return r;
455 static bool
456 is_enotsup (int err)
458 return err == ENOTSUP || err == EOPNOTSUPP;
460 #endif
462 /* Punch a hole in the file. */
463 static int
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;
468 int r;
470 if (h->can_punch_hole) {
471 r = do_fallocate (h->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
472 offset, count);
473 if (r == -1) {
474 /* Trim is advisory; we don't care if it fails for anything other
475 * than EIO or EPERM.
477 if (errno == EPERM || errno == EIO) {
478 nbdkit_error ("fallocate: %m");
479 return -1;
482 if (is_enotsup (EOPNOTSUPP))
483 h->can_punch_hole = false;
485 nbdkit_debug ("ignoring failed fallocate during trim: %m");
488 #endif
490 /* Deliberately ignore FUA if present in flags. */
492 return 0;
495 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
497 static struct nbdkit_plugin plugin = {
498 .name = "tmpdisk",
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)