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;
425 /* Get partitions. */
426 parts = guestfs_list_partitions (g);
430 for (i = 0; parts[i] != NULL; ++i) {
431 CLEANUP_FREE char *object = NULL;
432 printf ("virt-bmap: examining %s ...\n", parts[i]);
435 if (asprintf (&object, "p %s", parts[i]) == -1)
440 r = guestfs_debug (g, "bmap_device", (char **) argv);
446 r = guestfs_debug (g, "bmap", (char **) argv);
457 examine_lvs (guestfs_h *g)
459 CLEANUP_FREE_STRING_LIST char **lvs = NULL;
465 lvs = guestfs_lvs (g);
469 for (i = 0; lvs[i] != NULL; ++i) {
470 CLEANUP_FREE char *object = NULL;
472 printf ("virt-bmap: examining %s ...\n", lvs[i]);
475 if (asprintf (&object, "l %s", lvs[i]) == -1)
480 r = guestfs_debug (g, "bmap_device", (char **) argv);
486 r = guestfs_debug (g, "bmap", (char **) argv);
497 examine_filesystems (guestfs_h *g)
499 CLEANUP_FREE_STRING_LIST char **filesystems = NULL;
502 /* Get the filesystems in the disk image. */
503 filesystems = guestfs_list_filesystems (g);
504 if (filesystems == NULL)
507 for (i = 0; filesystems[i] != NULL; i += 2) {
508 if (examine_filesystem (g, filesystems[i], filesystems[i+1]) == -1)
516 count_strings (char *const *argv)
520 for (r = 0; argv[r]; ++r)
526 struct visit_context {
528 const char *dev; /* filesystem */
529 size_t nr_files; /* used for progress bar */
530 size_t files_processed;
534 examine_filesystem (guestfs_h *g, const char *dev, const char *type)
538 /* Try to mount it. */
539 guestfs_push_error_handler (g, NULL, NULL);
540 r = guestfs_mount_ro (g, dev, "/");
541 guestfs_pop_error_handler (g);
543 struct visit_context context;
544 CLEANUP_FREE_STRING_LIST char **files = NULL;
546 /* Mountable, so examine the filesystem. */
547 printf ("virt-bmap: examining filesystem on %s (%s) ...\n", dev, type);
550 /* Read how many files/directories there are so we can estimate
553 files = guestfs_find (g, "/");
555 /* Set filesystem readahead to 0, but ignore error if not possible. */
556 guestfs_push_error_handler (g, NULL, NULL);
557 guestfs_blockdev_setra (g, dev, 0);
558 guestfs_pop_error_handler (g);
562 context.nr_files = count_strings (files);
563 context.files_processed = 0;
564 if (visit (g, "/", visit_fn, &context) == -1)
568 guestfs_umount_all (g);
574 visit_fn (const char *dir, const char *name,
575 const struct guestfs_statns *stat,
576 const struct guestfs_xattr_list *xattrs,
579 struct visit_context *context = contextv;
580 guestfs_h *g = context->g;
582 CLEANUP_FREE char *path = NULL, *object = NULL;
586 context->files_processed++;
587 if ((context->files_processed & 255) == 0) {
588 printf ("%zu/%zu (%.1f%%) \r",
589 context->files_processed, context->nr_files,
590 100.0 * context->files_processed / context->nr_files);
594 path = full_path (dir, name);
598 if (is_reg (stat->st_mode))
600 else if (is_dir (stat->st_mode))
603 /* Note that the object string is structured (space-separated
604 * fields), and this is copied directly to the output file.
606 if (asprintf (&object, "%c %s %s", type, context->dev, path) == -1)
609 if (type == 'f') { /* regular file */
614 r = guestfs_debug (g, "bmap_file", (char **) argv);
620 r = guestfs_debug (g, "bmap", (char **) argv);
626 else if (type == 'd') { /* directory */
634 /* Convert ranges to output file format. */
635 static void print_range (uint64_t start, uint64_t end, const char *object, void *opaque);
638 ranges_to_output (void)
642 pthread_mutex_lock (¤t_object_mutex);
644 /* Write out the ranges to 'output'. */
645 fp = fopen (output, "w");
651 iter_range (ranges, print_range, fp);
655 pthread_mutex_unlock (¤t_object_mutex);
661 print_range (uint64_t start, uint64_t end, const char *object, void *opaque)
665 /* Note that the initial '1' is meant to signify the first disk.
666 * Currently we can only map a single disk, but in future we
667 * should be able to handle multiple disks.
669 fprintf (fp, "1 %" PRIx64 " %" PRIx64 " %s\n", start, end, object);
672 /* Register the nbdkit plugin. */
674 static struct nbdkit_plugin plugin = {
675 .name = "virtbmapexaminer",
676 .version = PACKAGE_VERSION,
677 .config = bmap_config,
678 .config_help = bmap_config_help,
679 .config_complete = bmap_config_complete,
682 .get_size = bmap_get_size,
684 .unload = bmap_unload,
687 NBDKIT_REGISTER_PLUGIN(plugin)