Add section "Notes on disk format" to the manual.
[virt-bmap.git] / examiner.c
1 /* virt-bmap examiner plugin
2  * Copyright (C) 2014 Red Hat Inc.
3  *
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.
8  *
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.
13  *
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.
17  */
18
19 /* This is an nbdkit plugin which implements the guts of the 'virt-bmap'
20  * command.
21  *
22  * IMPORTANT NOTES:
23  *
24  * Do not try to use this plugin directly!  Use the 'virt-bmap'
25  * wrapper script which sets up the arguments correctly and calls
26  * nbdkit.
27  *
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.
32  */
33
34 #include <config.h>
35
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <inttypes.h>
40 #include <string.h>
41 #include <signal.h>
42 #include <fcntl.h>
43 #include <unistd.h>
44 #include <sys/mman.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <assert.h>
48
49 #include <pthread.h>
50
51 #include <nbdkit-plugin.h>
52
53 #include <guestfs.h>
54
55 #include "cleanups.h"
56 #include "ranges.h"
57 #include "visit.h"
58
59 static char *output = NULL;
60 static char *disk = NULL;
61 static const char *socket = NULL;
62 static const char *format = "raw";
63 static int fd = -1;
64 static int64_t size = -1;
65 static int thread_running = 0;
66 static pthread_t thread;
67
68 static struct thread_info {
69   guestfs_h *g;
70   int ret;
71 } thread_info;
72
73 /* Current object being mapped. */
74 static pthread_mutex_t current_object_mutex = PTHREAD_MUTEX_INITIALIZER;
75 static char *current_object = NULL;
76
77 /* Ranges.  NB: acquire current_object_mutex before accessing. */
78 static void *ranges = NULL;
79
80 static void *start_thread (void *);
81
82 static int
83 bmap_config (const char *key, const char *value)
84 {
85   if (strcmp (key, "disk") == 0) {
86     free (disk);
87     disk = nbdkit_absolute_path (value);
88     if (disk == NULL)
89       return -1;
90   }
91   else if (strcmp (key, "format") == 0) {
92     format = value;
93   }
94   else if (strcmp (key, "output") == 0) {
95     free (output);
96     output = nbdkit_absolute_path (value);
97     if (output == NULL)
98       return -1;
99   }
100   else if (strcmp (key, "socket") == 0) {
101     socket = value;
102   }
103   else {
104     nbdkit_error ("unknown parameter '%s'", key);
105     return -1;
106   }
107
108   return 0;
109 }
110
111 static int
112 bmap_config_complete (void)
113 {
114   struct stat statbuf;
115   guestfs_h *g;
116   int err;
117
118   if (!output || !disk || !socket) {
119     nbdkit_error ("missing parameters: are you using the 'virt-bmap' wrapper?");
120     return -1;
121   }
122
123   ranges = new_ranges ();
124
125   fd = open (disk, O_RDONLY);
126   if (fd == -1) {
127     nbdkit_error ("%s: %m", disk);
128     return -1;
129   }
130
131   if (fstat (fd, &statbuf) == -1) {
132     nbdkit_error ("fstat: %m");
133     return -1;
134   }
135   size = statbuf.st_size;
136
137   /* Open the guestfs handle synchronously so we can print errors. */
138   g = guestfs_create ();
139   if (!g) {
140     nbdkit_error ("guestfs_create: %m");
141     return -1;
142   }
143
144   /* Start the guestfs thread. */
145   memset (&thread_info, 0, sizeof thread_info);
146   thread_info.g = g;
147   err = pthread_create (&thread, NULL, start_thread, &thread_info);
148   if (err != 0) {
149     nbdkit_error ("cannot start guestfs thread: %s", strerror (err));
150     return -1;
151   }
152   thread_running = 1;
153
154   return 0;
155 }
156
157 static void
158 bmap_unload (void)
159 {
160   int err;
161   void *retv;
162
163   free_ranges (ranges);
164
165   if (thread_running) {
166     err = pthread_join (thread, &retv);
167     if (err != 0)
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 */
172   }
173   if (fd >= 0)
174     close (fd);
175   free (output);
176   free (disk);
177 }
178
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"
183
184 /* The per-connection handle. */
185 struct bmap_handle {
186   /* empty */
187 };
188
189 /* Create the per-connection handle. */
190 static void *
191 bmap_open (int readonly)
192 {
193   struct bmap_handle *h;
194
195   if (!readonly) {
196     nbdkit_error ("handle must be opened readonly");
197     return NULL;
198   }
199
200   h = malloc (sizeof *h);
201   if (h == NULL) {
202     nbdkit_error ("malloc: %m");
203     return NULL;
204   }
205
206   return h;
207 }
208
209 /* Free up the per-connection handle. */
210 static void
211 bmap_close (void *handle)
212 {
213   struct bmap_handle *h = handle;
214
215   free (h);
216 }
217
218 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_ALL_REQUESTS
219
220 /* Get the file size. */
221 static int64_t
222 bmap_get_size (void *handle)
223 {
224   return size;
225 }
226
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.
229  */
230 static void
231 mark_start (const char *object)
232 {
233   pthread_mutex_lock (&current_object_mutex);
234   free (current_object);
235   current_object = strdup (object);
236   if (current_object == NULL)
237     abort ();
238   pthread_mutex_unlock (&current_object_mutex);
239 }
240
241 static void
242 mark_end (void)
243 {
244   pthread_mutex_lock (&current_object_mutex);
245   free (current_object);
246   current_object = NULL;
247   pthread_mutex_unlock (&current_object_mutex);
248 }
249
250 static void
251 add_range (uint64_t offset, uint32_t count)
252 {
253   pthread_mutex_lock (&current_object_mutex);
254   if (current_object)
255     insert_range (ranges, offset, offset+count, current_object);
256   pthread_mutex_unlock (&current_object_mutex);
257 }
258
259 /* Read data from the file. */
260 static int
261 bmap_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
262 {
263   ssize_t r;
264
265   add_range (offset, count);
266
267   if (lseek (fd, offset, SEEK_SET) == -1) {
268     nbdkit_error ("lseek: %m");
269     return -1;
270   }
271
272   while (count > 0) {
273     r = read (fd, buf, count);
274     if (r == -1) {
275       nbdkit_error ("read: %m");
276       return -1;
277     }
278     if (r == 0) {
279       nbdkit_error ("read: unexpected end of file");
280       return -1;
281     }
282     count -= r;
283     buf += r;
284   }
285
286   return 0;
287 }
288
289 /* This is the guestfs thread which calls back into nbdkit. */
290
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;
296
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);
304
305 static void *
306 start_thread (void *infov)
307 {
308   struct thread_info *info = infov;
309   guestfs_h *g = info->g;
310   size_t i;
311   CLEANUP_FREE char *server = NULL;
312   const char *servers[2];
313
314   info->ret = -1;
315
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)
319       goto nbdkit_started;
320     sleep (1);
321   }
322
323   fprintf (stderr, "timed out waiting for nbdkit server to start up\n");
324   goto error;
325
326  nbdkit_started:
327   if (asprintf (&server, "unix:%s", socket) == -1) {
328     perror ("asprintf");
329     goto error;
330   }
331
332   servers[0] = server;
333   servers[1] = NULL;
334
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,
340                               -1) == -1)
341     goto error;
342
343   if (guestfs_launch (g) == -1)
344     goto error;
345
346   /* Examine non-filesystem objects. */
347   if (examine_devices (g) == -1)
348     goto error;
349   if (examine_partitions (g) == -1)
350     goto error;
351   if (examine_lvs (g) == -1)
352     goto error;
353
354   /* Examine filesystems. */
355   if (examine_filesystems (g) == -1)
356     goto error;
357
358   /* Convert ranges to final output file. */
359   printf ("virt-bmap: writing %s\n", output);
360   if (ranges_to_output () == -1)
361     goto error;
362
363   /* Print summary. */
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,
369           output);
370   info->ret = 0;
371  error:
372   guestfs_close (g);
373
374   /* Kill the nbdkit process so it exits.  The nbdkit process is us,
375    * so we're killing ourself here.
376    */
377   kill (getpid (), SIGTERM);
378
379   return &info->ret;
380 }
381
382 static int
383 examine_devices (guestfs_h *g)
384 {
385   CLEANUP_FREE_STRING_LIST char **devices = NULL;
386   size_t i;
387
388   /* Get list of devices. */
389   devices = guestfs_list_devices (g);
390   if (devices == NULL)
391     return -1;
392
393   for (i = 0; devices[i] != NULL; ++i) {
394     CLEANUP_FREE char *object = NULL;
395     int64_t size;
396
397     printf ("virt-bmap: examining %s ...\n", devices[i]);
398
399     if (asprintf (&object, "v %s", devices[i]) == -1)
400       return -1;
401
402     /* We don't actually bother to examine the device, which would be
403      * slow and pointless.  We just mark it in the map.
404      */
405     size = guestfs_blockdev_getsize64 (g, devices[i]);
406     if (size == -1)
407       return -1;
408
409     pthread_mutex_lock (&current_object_mutex);
410     insert_range (ranges, 0, size, object);
411     pthread_mutex_unlock (&current_object_mutex);
412   }
413
414   return 0;
415 }
416
417 static int
418 examine_partitions (guestfs_h *g)
419 {
420   CLEANUP_FREE_STRING_LIST char **parts = NULL;
421   size_t i;
422   const char *argv[2];
423   char *r;
424
425   /* Get partitions. */
426   parts = guestfs_list_partitions (g);
427   if (parts == NULL)
428     return -1;
429
430   for (i = 0; parts[i] != NULL; ++i) {
431     CLEANUP_FREE char *object = NULL;
432     printf ("virt-bmap: examining %s ...\n", parts[i]);
433     count_partitions++;
434
435     if (asprintf (&object, "p %s", parts[i]) == -1)
436       return -1;
437
438     argv[0] = parts[i];
439     argv[1] = NULL;
440     r = guestfs_debug (g, "bmap_device", (char **) argv);
441     if (r == NULL)
442       return -1;
443     free (r);
444     mark_start (object);
445     argv[0] = NULL;
446     r = guestfs_debug (g, "bmap", (char **) argv);
447     mark_end ();
448     if (r == NULL)
449       return -1;
450     free (r);
451   }
452
453   return 0;
454 }
455
456 static int
457 examine_lvs (guestfs_h *g)
458 {
459   CLEANUP_FREE_STRING_LIST char **lvs = NULL;
460   size_t i;
461   const char *argv[2];
462   char *r;
463
464   /* Get LVs. */
465   lvs = guestfs_lvs (g);
466   if (lvs == NULL)
467     return -1;
468
469   for (i = 0; lvs[i] != NULL; ++i) {
470     CLEANUP_FREE char *object = NULL;
471
472     printf ("virt-bmap: examining %s ...\n", lvs[i]);
473     count_lvs++;
474
475     if (asprintf (&object, "l %s", lvs[i]) == -1)
476       return -1;
477
478     argv[0] = lvs[i];
479     argv[1] = NULL;
480     r = guestfs_debug (g, "bmap_device", (char **) argv);
481     if (r == NULL)
482       return -1;
483     free (r);
484     mark_start (object);
485     argv[0] = NULL;
486     r = guestfs_debug (g, "bmap", (char **) argv);
487     mark_end ();
488     if (r == NULL)
489       return -1;
490     free (r);
491   }
492
493   return 0;
494 }
495
496 static int
497 examine_filesystems (guestfs_h *g)
498 {
499   CLEANUP_FREE_STRING_LIST char **filesystems = NULL;
500   size_t i;
501
502   /* Get the filesystems in the disk image. */
503   filesystems = guestfs_list_filesystems (g);
504   if (filesystems == NULL)
505     return -1;
506
507   for (i = 0; filesystems[i] != NULL; i += 2) {
508     if (examine_filesystem (g, filesystems[i], filesystems[i+1]) == -1)
509       return -1;
510   }
511
512   return 0;
513 }
514
515 static size_t
516 count_strings (char *const *argv)
517 {
518   size_t r;
519
520   for (r = 0; argv[r]; ++r)
521     ;
522
523   return r;
524 }
525
526 struct visit_context {
527   guestfs_h *g;
528   const char *dev;              /* filesystem */
529   size_t nr_files;              /* used for progress bar */
530   size_t files_processed;
531 };
532
533 static int
534 examine_filesystem (guestfs_h *g, const char *dev, const char *type)
535 {
536   int r;
537
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);
542   if (r == 0) {
543     struct visit_context context;
544     CLEANUP_FREE_STRING_LIST char **files = NULL;
545
546     /* Mountable, so examine the filesystem. */
547     printf ("virt-bmap: examining filesystem on %s (%s) ...\n", dev, type);
548     count_filesystems++;
549
550     /* Read how many files/directories there are so we can estimate
551      * progress.
552      */
553     files = guestfs_find (g, "/");
554
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);
559
560     context.g = g;
561     context.dev = dev;
562     context.nr_files = count_strings (files);
563     context.files_processed = 0;
564     if (visit (g, "/", visit_fn, &context) == -1)
565       return -1;
566   }
567
568   guestfs_umount_all (g);
569
570   return 0;
571 }
572
573 static int
574 visit_fn (const char *dir, const char *name,
575           const struct guestfs_statns *stat,
576           const struct guestfs_xattr_list *xattrs,
577           void *contextv)
578 {
579   struct visit_context *context = contextv;
580   guestfs_h *g = context->g;
581   char type = '?';
582   CLEANUP_FREE char *path = NULL, *object = NULL;
583   const char *argv[2];
584   char *r;
585
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);
591     fflush (stdout);
592   }
593
594   path = full_path (dir, name);
595   if (path == NULL)
596     return -1;
597
598   if (is_reg (stat->st_mode))
599     type = 'f';
600   else if (is_dir (stat->st_mode))
601     type = 'd';
602
603   /* Note that the object string is structured (space-separated
604    * fields), and this is copied directly to the output file.
605    */
606   if (asprintf (&object, "%c %s %s", type, context->dev, path) == -1)
607     return -1;
608
609   if (type == 'f') {            /* regular file */
610     count_regular++;
611   bmap_file:
612     argv[0] = path;
613     argv[1] = NULL;
614     r = guestfs_debug (g, "bmap_file", (char **) argv);
615     if (r == NULL)
616       return -1;
617     free (r);
618     mark_start (object);
619     argv[0] = NULL;
620     r = guestfs_debug (g, "bmap", (char **) argv);
621     mark_end ();
622     if (r == NULL)
623       return -1;
624     free (r);
625   }
626   else if (type == 'd') {       /* directory */
627     count_directory++;
628     goto bmap_file;
629   }
630
631   return 0;
632 }
633
634 /* Convert ranges to output file format. */
635 static void print_range (uint64_t start, uint64_t end, const char *object, void *opaque);
636
637 static int
638 ranges_to_output (void)
639 {
640   FILE *fp;
641
642   pthread_mutex_lock (&current_object_mutex);
643
644   /* Write out the ranges to 'output'. */
645   fp = fopen (output, "w");
646   if (fp == NULL) {
647     perror (output);
648     return -1;
649   }
650
651   iter_range (ranges, print_range, fp);
652
653   fclose (fp);
654
655   pthread_mutex_unlock (&current_object_mutex);
656
657   return 0;
658 }
659
660 static void
661 print_range (uint64_t start, uint64_t end, const char *object, void *opaque)
662 {
663   FILE *fp = opaque;
664
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.
668    */
669   fprintf (fp, "1 %" PRIx64 " %" PRIx64 " %s\n", start, end, object);
670 }
671
672 /* Register the nbdkit plugin. */
673
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,
680   .open              = bmap_open,
681   .close             = bmap_close,
682   .get_size          = bmap_get_size,
683   .pread             = bmap_pread,
684   .unload            = bmap_unload,
685 };
686
687 NBDKIT_REGISTER_PLUGIN(plugin)