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_devices (guestfs_h *g);
298 static int examine_partitions (guestfs_h *g);
299 static int examine_lvs (guestfs_h *g);
300 static int examine_filesystems (guestfs_h *g);
301 static int examine_filesystem (guestfs_h *g, const char *dev, const char *type);
302 static int visit_fn (const char *dir, const char *name, const struct guestfs_statns *stat, const struct guestfs_xattr_list *xattrs, void *opaque);
303 static int ranges_to_output (void);
306 start_thread (void *infov)
308 struct thread_info *info = infov;
309 guestfs_h *g = info->g;
311 CLEANUP_FREE char *server = NULL;
312 const char *servers[2];
316 /* Wait for the socket to appear, ie. for nbdkit to start up. */
317 for (i = 0; i < 10; ++i) {
318 if (access (socket, R_OK) == 0)
323 fprintf (stderr, "timed out waiting for nbdkit server to start up\n");
327 if (asprintf (&server, "unix:%s", socket) == -1) {
335 if (guestfs_add_drive_opts (g, "" /* export name */,
336 GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
337 GUESTFS_ADD_DRIVE_OPTS_FORMAT, format,
338 GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "nbd",
339 GUESTFS_ADD_DRIVE_OPTS_SERVER, servers,
343 if (guestfs_launch (g) == -1)
346 /* Examine non-filesystem objects. */
347 if (examine_devices (g) == -1)
349 if (examine_partitions (g) == -1)
351 if (examine_lvs (g) == -1)
354 /* Examine filesystems. */
355 if (examine_filesystems (g) == -1)
358 /* Convert ranges to final output file. */
359 printf ("virt-bmap: writing %s\n", output);
360 if (ranges_to_output () == -1)
364 printf ("virt-bmap: successfully examined %d partitions, %d logical volumes,\n"
365 " %d filesystems, %d directories, %d files\n"
366 "virt-bmap: output written to %s\n",
367 count_partitions, count_lvs,
368 count_filesystems, count_directory, count_regular,
374 /* Kill the nbdkit process so it exits. The nbdkit process is us,
375 * so we're killing ourself here.
377 kill (getpid (), SIGTERM);
383 examine_devices (guestfs_h *g)
385 CLEANUP_FREE_STRING_LIST char **devices = NULL;
388 /* Get list of devices. */
389 devices = guestfs_list_devices (g);
393 for (i = 0; devices[i] != NULL; ++i) {
394 CLEANUP_FREE char *object = NULL;
397 printf ("virt-bmap: examining %s ...\n", devices[i]);
399 if (asprintf (&object, "v %s", devices[i]) == -1)
402 /* We don't actually bother to examine the device, which would be
403 * slow and pointless. We just mark it in the map.
405 size = guestfs_blockdev_getsize64 (g, devices[i]);
409 pthread_mutex_lock (¤t_object_mutex);
410 insert_range (ranges, 0, size, object);
411 pthread_mutex_unlock (¤t_object_mutex);
418 examine_partitions (guestfs_h *g)
420 CLEANUP_FREE_STRING_LIST char **parts = NULL;
424 /* Get partitions. */
425 parts = guestfs_list_partitions (g);
429 for (i = 0; parts[i] != NULL; ++i) {
430 CLEANUP_FREE char *object = NULL;
432 printf ("virt-bmap: examining %s ...\n", parts[i]);
435 if (asprintf (&object, "p %s", parts[i]) == -1)
438 if (guestfs_bmap_device (g, parts[i]) == -1)
441 r = guestfs_bmap (g);
443 if (r == -1) return -1;
450 examine_lvs (guestfs_h *g)
452 CLEANUP_FREE_STRING_LIST char **lvs = NULL;
457 lvs = guestfs_lvs (g);
461 for (i = 0; lvs[i] != NULL; ++i) {
462 CLEANUP_FREE char *object = NULL;
464 printf ("virt-bmap: examining %s ...\n", lvs[i]);
467 if (asprintf (&object, "l %s", lvs[i]) == -1)
470 if (guestfs_bmap_device (g, lvs[i]) == -1)
473 r = guestfs_bmap (g);
475 if (r == -1) return -1;
482 examine_filesystems (guestfs_h *g)
484 CLEANUP_FREE_STRING_LIST char **filesystems = NULL;
487 /* Get the filesystems in the disk image. */
488 filesystems = guestfs_list_filesystems (g);
489 if (filesystems == NULL)
492 for (i = 0; filesystems[i] != NULL; i += 2) {
493 if (examine_filesystem (g, filesystems[i], filesystems[i+1]) == -1)
501 count_strings (char *const *argv)
505 for (r = 0; argv[r]; ++r)
511 struct visit_context {
513 const char *dev; /* filesystem */
514 size_t nr_files; /* used for progress bar */
515 size_t files_processed;
519 examine_filesystem (guestfs_h *g, const char *dev, const char *type)
523 /* Try to mount it. */
524 guestfs_push_error_handler (g, NULL, NULL);
525 r = guestfs_mount_ro (g, dev, "/");
526 guestfs_pop_error_handler (g);
528 struct visit_context context;
529 CLEANUP_FREE_STRING_LIST char **files = NULL;
531 /* Mountable, so examine the filesystem. */
532 printf ("virt-bmap: examining filesystem on %s (%s) ...\n", dev, type);
535 /* Read how many files/directories there are so we can estimate
538 files = guestfs_find (g, "/");
540 /* Set filesystem readahead to 0, but ignore error if not possible. */
541 guestfs_push_error_handler (g, NULL, NULL);
542 guestfs_blockdev_setra (g, dev, 0);
543 guestfs_pop_error_handler (g);
547 context.nr_files = count_strings (files);
548 context.files_processed = 0;
549 if (visit (g, "/", visit_fn, &context) == -1)
553 guestfs_umount_all (g);
559 visit_fn (const char *dir, const char *name,
560 const struct guestfs_statns *stat,
561 const struct guestfs_xattr_list *xattrs,
564 struct visit_context *context = contextv;
565 guestfs_h *g = context->g;
567 CLEANUP_FREE char *path = NULL, *object = NULL;
570 context->files_processed++;
571 if ((context->files_processed & 255) == 0) {
572 printf ("%zu/%zu (%.1f%%) \r",
573 context->files_processed, context->nr_files,
574 100.0 * context->files_processed / context->nr_files);
578 path = full_path (dir, name);
582 if (is_reg (stat->st_mode))
584 else if (is_dir (stat->st_mode))
587 /* Note that the object string is structured (space-separated
588 * fields), and this is copied directly to the output file.
590 if (asprintf (&object, "%c %s %s", type, context->dev, path) == -1)
593 if (type == 'f') { /* regular file */
596 if (guestfs_bmap_file (g, path) == -1)
599 r = guestfs_bmap (g);
601 if (r == -1) return -1;
603 else if (type == 'd') { /* directory */
611 /* Convert ranges to output file format. */
612 static void print_range (uint64_t start, uint64_t end, const char *object, void *opaque);
615 ranges_to_output (void)
619 pthread_mutex_lock (¤t_object_mutex);
621 /* Write out the ranges to 'output'. */
622 fp = fopen (output, "w");
628 iter_range (ranges, print_range, fp);
632 pthread_mutex_unlock (¤t_object_mutex);
638 print_range (uint64_t start, uint64_t end, const char *object, void *opaque)
642 /* Note that the initial '1' is meant to signify the first disk.
643 * Currently we can only map a single disk, but in future we
644 * should be able to handle multiple disks.
646 fprintf (fp, "1 %" PRIx64 " %" PRIx64 " %s\n", start, end, object);
649 /* Register the nbdkit plugin. */
651 static struct nbdkit_plugin plugin = {
652 .name = "virtbmapexaminer",
653 .version = PACKAGE_VERSION,
654 .config = bmap_config,
655 .config_help = bmap_config_help,
656 .config_complete = bmap_config_complete,
659 .get_size = bmap_get_size,
661 .unload = bmap_unload,
664 NBDKIT_REGISTER_PLUGIN(plugin)