1 /* virt-bmap examiner plugin
2 * Copyright (C) 2014 Red Hat Inc.
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 /* This is an nbdkit plugin which implements the guts of the 'virt-bmap'
24 * Do not try to use this plugin directly! Use the 'virt-bmap'
25 * wrapper script which sets up the arguments correctly and calls
28 * This is a bit of a weird nbdkit plugin, because it calls itself.
29 * Do not use it as an example of how to write plugins! See the
30 * nbdkit 'example*.c' files supplied in the nbdkit source, and also
31 * read the nbdkit-plugin(3) documentation.
45 #include <sys/types.h>
51 #include <nbdkit-plugin.h>
59 static char *output = NULL;
60 static char *disk = NULL;
61 static const char *socket = NULL;
62 static const char *format = "raw";
64 static int64_t size = -1;
65 static int thread_running = 0;
66 static pthread_t thread;
68 static struct thread_info {
73 /* Current object being mapped. */
74 static pthread_mutex_t current_object_mutex = PTHREAD_MUTEX_INITIALIZER;
75 static char *current_object = NULL;
77 /* Ranges. NB: acquire current_object_mutex before accessing. */
78 static void *ranges = NULL;
80 static void *start_thread (void *);
83 bmap_config (const char *key, const char *value)
85 if (strcmp (key, "disk") == 0) {
87 disk = nbdkit_absolute_path (value);
91 else if (strcmp (key, "format") == 0) {
94 else if (strcmp (key, "output") == 0) {
96 output = nbdkit_absolute_path (value);
100 else if (strcmp (key, "socket") == 0) {
104 nbdkit_error ("unknown parameter '%s'", key);
112 bmap_config_complete (void)
118 if (!output || !disk || !socket) {
119 nbdkit_error ("missing parameters: are you using the 'virt-bmap' wrapper?");
123 ranges = new_ranges ();
125 fd = open (disk, O_RDONLY);
127 nbdkit_error ("%s: %m", disk);
131 if (fstat (fd, &statbuf) == -1) {
132 nbdkit_error ("fstat: %m");
135 size = statbuf.st_size;
137 /* Open the guestfs handle synchronously so we can print errors. */
138 g = guestfs_create ();
140 nbdkit_error ("guestfs_create: %m");
144 /* Start the guestfs thread. */
145 memset (&thread_info, 0, sizeof thread_info);
147 err = pthread_create (&thread, NULL, start_thread, &thread_info);
149 nbdkit_error ("cannot start guestfs thread: %s", strerror (err));
163 free_ranges (ranges);
165 if (thread_running) {
166 err = pthread_join (thread, &retv);
168 fprintf (stderr, "cannot join guestfs thread: %s\n", strerror (err));
169 if (* (int *) retv != 0)
170 fprintf (stderr, "ERROR: failed to construct block map, see earlier errors\n");
171 /* unfortunately we can't return the correct exit code here XXX */
179 #define bmap_config_help \
180 "output=<OUTPUT> Output filename (block map)\n" \
181 "disk=<DISK> Input disk filename" \
182 "format=raw|qcow2|.. Format of input disk (default: raw)\n"
184 /* The per-connection handle. */
189 /* Create the per-connection handle. */
191 bmap_open (int readonly)
193 struct bmap_handle *h;
196 nbdkit_error ("handle must be opened readonly");
200 h = malloc (sizeof *h);
202 nbdkit_error ("malloc: %m");
209 /* Free up the per-connection handle. */
211 bmap_close (void *handle)
213 struct bmap_handle *h = handle;
218 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
220 /* Get the file size. */
222 bmap_get_size (void *handle)
227 /* Mark the start and end of guestfs bmap operations. This is called
228 * from the guestfs thread, so we must lock any shared data structures.
231 mark_start (const char *object)
233 pthread_mutex_lock (¤t_object_mutex);
234 free (current_object);
235 current_object = strdup (object);
236 if (current_object == NULL)
238 pthread_mutex_unlock (¤t_object_mutex);
244 pthread_mutex_lock (¤t_object_mutex);
245 free (current_object);
246 current_object = NULL;
247 pthread_mutex_unlock (¤t_object_mutex);
251 add_range (uint64_t offset, uint32_t count)
253 pthread_mutex_lock (¤t_object_mutex);
255 insert_range (ranges, offset, offset+count, current_object);
256 pthread_mutex_unlock (¤t_object_mutex);
259 /* Read data from the file. */
261 bmap_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
265 add_range (offset, count);
267 if (lseek (fd, offset, SEEK_SET) == -1) {
268 nbdkit_error ("lseek: %m");
273 r = read (fd, buf, count);
275 nbdkit_error ("read: %m");
279 nbdkit_error ("read: unexpected end of file");
289 /* This is the guestfs thread which calls back into nbdkit. */
291 static int count_partitions = 0;
292 static int count_lvs = 0;
293 static int count_filesystems = 0;
294 static int count_regular = 0;
295 static int count_directory = 0;
297 static int examine_partitions (guestfs_h *g);
298 static int examine_lvs (guestfs_h *g);
299 static int examine_filesystems (guestfs_h *g);
300 static int examine_filesystem (guestfs_h *g, const char *dev, const char *type);
301 static int visit_fn (const char *dir, const char *name, const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *opaque);
302 static int ranges_to_output (void);
305 start_thread (void *infov)
307 struct thread_info *info = infov;
308 guestfs_h *g = info->g;
310 CLEANUP_FREE char *server = NULL;
311 const char *servers[2];
315 /* Wait for the socket to appear, ie. for nbdkit to start up. */
316 for (i = 0; i < 10; ++i) {
317 if (access (socket, R_OK) == 0)
322 fprintf (stderr, "timed out waiting for nbdkit server to start up\n");
326 if (asprintf (&server, "unix:%s", socket) == -1) {
334 if (guestfs_add_drive_opts (g, "" /* export name */,
335 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
336 GUESTFS_ADD_DRIVE_OPTS_FORMAT, format,
337 GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
338 GUESTFS_ADD_DRIVE_OPTS_SERVER, servers,
342 if (guestfs_launch (g) == -1)
345 /* Examine non-filesystem objects. */
346 if (examine_partitions (g) == -1)
348 if (examine_lvs (g) == -1)
351 /* Examine filesystems. */
352 if (examine_filesystems (g) == -1)
355 /* Convert ranges to final output file. */
356 printf ("virt-bmap: writing %s\n", output);
357 if (ranges_to_output () == -1)
361 printf ("virt-bmap: successfully examined %d partitions, %d logical volumes,\n"
362 " %d filesystems, %d directories, %d files\n"
363 "virt-bmap: output written to %s\n",
364 count_partitions, count_lvs,
365 count_filesystems, count_directory, count_regular,
371 /* Kill the nbdkit process so it exits. The nbdkit process is us,
372 * so we're killing ourself here.
374 kill (getpid (), SIGTERM);
380 examine_partitions (guestfs_h *g)
382 CLEANUP_FREE_STRING_LIST char **parts = NULL;
386 /* Get partitions. */
387 parts = guestfs_list_partitions (g);
391 for (i = 0; parts[i] != NULL; ++i) {
392 CLEANUP_FREE char *object = NULL;
394 printf ("virt-bmap: examining %s ...\n", parts[i]);
397 if (asprintf (&object, "p %s", parts[i]) == -1)
400 if (guestfs_bmap_device (g, parts[i]) == -1)
403 r = guestfs_bmap (g);
405 if (r == -1) return -1;
413 examine_lvs (guestfs_h *g)
415 CLEANUP_FREE_STRING_LIST char **lvs = NULL;
420 lvs = guestfs_lvs (g);
424 for (i = 0; lvs[i] != NULL; ++i) {
425 CLEANUP_FREE char *object = NULL;
427 printf ("virt-bmap: examining %s ...\n", lvs[i]);
430 if (asprintf (&object, "lvm_lv %s", lvs[i]) == -1)
433 if (guestfs_bmap_device (g, lvs[i]) == -1)
436 r = guestfs_bmap (g);
438 if (r == -1) return -1;
445 examine_filesystems (guestfs_h *g)
447 CLEANUP_FREE_STRING_LIST char **filesystems = NULL;
450 /* Get the filesystems in the disk image. */
451 filesystems = guestfs_list_filesystems (g);
452 if (filesystems == NULL)
455 for (i = 0; filesystems[i] != NULL; i += 2) {
456 if (examine_filesystem (g, filesystems[i], filesystems[i+1]) == -1)
464 count_strings (char *const *argv)
468 for (r = 0; argv[r]; ++r)
474 struct visit_context {
476 const char *dev; /* filesystem */
477 size_t nr_files; /* used for progress bar */
478 size_t files_processed;
482 examine_filesystem (guestfs_h *g, const char *dev, const char *type)
486 /* Try to mount it. */
487 guestfs_push_error_handler (g, NULL, NULL);
488 r = guestfs_mount_ro (g, dev, "/");
489 guestfs_pop_error_handler (g);
491 struct visit_context context;
492 CLEANUP_FREE_STRING_LIST char **files = NULL;
494 /* Mountable, so examine the filesystem. */
495 printf ("virt-bmap: examining filesystem on %s (%s) ...\n", dev, type);
498 /* Read how many files/directories there are so we can estimate
501 files = guestfs_find (g, "/");
503 /* Set filesystem readahead to 0, but ignore error if not possible. */
504 guestfs_push_error_handler (g, NULL, NULL);
505 guestfs_blockdev_setra (g, dev, 0);
506 guestfs_pop_error_handler (g);
510 context.nr_files = count_strings (files);
511 context.files_processed = 0;
512 if (visit (g, "/", visit_fn, &context) == -1)
516 guestfs_umount_all (g);
522 visit_fn (const char *dir, const char *name,
523 const struct guestfs_statns *stat,
524 const struct guestfs_xattr_list *xattrs,
527 struct visit_context *context = contextv;
528 guestfs_h *g = context->g;
530 CLEANUP_FREE char *path = NULL, *object = NULL;
533 context->files_processed++;
534 if ((context->files_processed & 255) == 0) {
535 printf ("%zu/%zu (%.1f%%) \r",
536 context->files_processed, context->nr_files,
537 100.0 * context->files_processed / context->nr_files);
541 path = full_path (dir, name);
545 if (is_reg (stat->st_mode))
547 else if (is_dir (stat->st_mode))
550 /* Note that the object string is structured (space-separated
551 * fields), and this is copied directly to the output file.
553 if (asprintf (&object, "%c %s %s", type, context->dev, path) == -1)
556 if (type == 'f') { /* regular file */
559 if (guestfs_bmap_file (g, path) == -1)
562 r = guestfs_bmap (g);
564 if (r == -1) return -1;
566 else if (type == 'd') { /* directory */
574 /* Convert ranges to output file format. */
575 static void print_range (uint64_t start, uint64_t end, const char *object, void *opaque);
578 ranges_to_output (void)
582 pthread_mutex_lock (¤t_object_mutex);
584 /* Write out the ranges to 'output'. */
585 fp = fopen (output, "w");
591 iter_range (ranges, print_range, fp);
595 pthread_mutex_unlock (¤t_object_mutex);
601 print_range (uint64_t start, uint64_t end, const char *object, void *opaque)
605 /* Note that the initial '1' is meant to signify the first disk.
606 * Currently we can only map a single disk, but in future we
607 * should be able to handle multiple disks.
609 fprintf (fp, "1 %" PRIx64 " %" PRIx64 " %s\n", start, end, object);
612 /* Register the nbdkit plugin. */
614 static struct nbdkit_plugin plugin = {
616 .version = PACKAGE_VERSION,
617 .config = bmap_config,
618 .config_help = bmap_config_help,
619 .config_complete = bmap_config_complete,
622 .get_size = bmap_get_size,
624 .unload = bmap_unload,
627 NBDKIT_REGISTER_PLUGIN(plugin)