examiner: Fix plugin name.
[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_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);
303
304 static void *
305 start_thread (void *infov)
306 {
307   struct thread_info *info = infov;
308   guestfs_h *g = info->g;
309   size_t i;
310   CLEANUP_FREE char *server = NULL;
311   const char *servers[2];
312
313   info->ret = -1;
314
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)
318       goto nbdkit_started;
319     sleep (1);
320   }
321
322   fprintf (stderr, "timed out waiting for nbdkit server to start up\n");
323   goto error;
324
325  nbdkit_started:
326   if (asprintf (&server, "unix:%s", socket) == -1) {
327     perror ("asprintf");
328     goto error;
329   }
330
331   servers[0] = server;
332   servers[1] = NULL;
333
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,
339                               -1) == -1)
340     goto error;
341
342   if (guestfs_launch (g) == -1)
343     goto error;
344
345   /* Examine non-filesystem objects. */
346   if (examine_partitions (g) == -1)
347     goto error;
348   if (examine_lvs (g) == -1)
349     goto error;
350
351   /* Examine filesystems. */
352   if (examine_filesystems (g) == -1)
353     goto error;
354
355   /* Convert ranges to final output file. */
356   printf ("virt-bmap: writing %s\n", output);
357   if (ranges_to_output () == -1)
358     goto error;
359
360   /* Print summary. */
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,
366           output);
367   info->ret = 0;
368  error:
369   guestfs_close (g);
370
371   /* Kill the nbdkit process so it exits.  The nbdkit process is us,
372    * so we're killing ourself here.
373    */
374   kill (getpid (), SIGTERM);
375
376   return &info->ret;
377 }
378
379 static int
380 examine_partitions (guestfs_h *g)
381 {
382   CLEANUP_FREE_STRING_LIST char **parts = NULL;
383   size_t i;
384   int r;
385
386   /* Get partitions. */
387   parts = guestfs_list_partitions (g);
388   if (parts == NULL)
389     return -1;
390
391   for (i = 0; parts[i] != NULL; ++i) {
392     CLEANUP_FREE char *object = NULL;
393
394     printf ("virt-bmap: examining %s ...\n", parts[i]);
395     count_partitions++;
396
397     if (asprintf (&object, "p %s", parts[i]) == -1)
398       return -1;
399
400     if (guestfs_bmap_device (g, parts[i]) == -1)
401       return -1;
402     mark_start (object);
403     r = guestfs_bmap (g);
404     mark_end ();
405     if (r == -1) return -1;
406   }
407
408   return 0;
409
410 }
411
412 static int
413 examine_lvs (guestfs_h *g)
414 {
415   CLEANUP_FREE_STRING_LIST char **lvs = NULL;
416   size_t i;
417   int r;
418
419   /* Get LVs. */
420   lvs = guestfs_lvs (g);
421   if (lvs == NULL)
422     return -1;
423
424   for (i = 0; lvs[i] != NULL; ++i) {
425     CLEANUP_FREE char *object = NULL;
426
427     printf ("virt-bmap: examining %s ...\n", lvs[i]);
428     count_lvs++;
429
430     if (asprintf (&object, "lvm_lv %s", lvs[i]) == -1)
431       return -1;
432
433     if (guestfs_bmap_device (g, lvs[i]) == -1)
434       return -1;
435     mark_start (object);
436     r = guestfs_bmap (g);
437     mark_end ();
438     if (r == -1) return -1;
439   }
440
441   return 0;
442 }
443
444 static int
445 examine_filesystems (guestfs_h *g)
446 {
447   CLEANUP_FREE_STRING_LIST char **filesystems = NULL;
448   size_t i;
449
450   /* Get the filesystems in the disk image. */
451   filesystems = guestfs_list_filesystems (g);
452   if (filesystems == NULL)
453     return -1;
454
455   for (i = 0; filesystems[i] != NULL; i += 2) {
456     if (examine_filesystem (g, filesystems[i], filesystems[i+1]) == -1)
457       return -1;
458   }
459
460   return 0;
461 }
462
463 static size_t
464 count_strings (char *const *argv)
465 {
466   size_t r;
467
468   for (r = 0; argv[r]; ++r)
469     ;
470
471   return r;
472 }
473
474 struct visit_context {
475   guestfs_h *g;
476   const char *dev;              /* filesystem */
477   size_t nr_files;              /* used for progress bar */
478   size_t files_processed;
479 };
480
481 static int
482 examine_filesystem (guestfs_h *g, const char *dev, const char *type)
483 {
484   int r;
485
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);
490   if (r == 0) {
491     struct visit_context context;
492     CLEANUP_FREE_STRING_LIST char **files = NULL;
493
494     /* Mountable, so examine the filesystem. */
495     printf ("virt-bmap: examining filesystem on %s (%s) ...\n", dev, type);
496     count_filesystems++;
497
498     /* Read how many files/directories there are so we can estimate
499      * progress.
500      */
501     files = guestfs_find (g, "/");
502
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);
507
508     context.g = g;
509     context.dev = dev;
510     context.nr_files = count_strings (files);
511     context.files_processed = 0;
512     if (visit (g, "/", visit_fn, &context) == -1)
513       return -1;
514   }
515
516   guestfs_umount_all (g);
517
518   return 0;
519 }
520
521 static int
522 visit_fn (const char *dir, const char *name,
523           const struct guestfs_statns *stat,
524           const struct guestfs_xattr_list *xattrs,
525           void *contextv)
526 {
527   struct visit_context *context = contextv;
528   guestfs_h *g = context->g;
529   char type = '?';
530   CLEANUP_FREE char *path = NULL, *object = NULL;
531   int r;
532
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);
538     fflush (stdout);
539   }
540
541   path = full_path (dir, name);
542   if (path == NULL)
543     return -1;
544
545   if (is_reg (stat->st_mode))
546     type = 'f';
547   else if (is_dir (stat->st_mode))
548     type = 'd';
549
550   /* Note that the object string is structured (space-separated
551    * fields), and this is copied directly to the output file.
552    */
553   if (asprintf (&object, "%c %s %s", type, context->dev, path) == -1)
554     return -1;
555
556   if (type == 'f') {            /* regular file */
557     count_regular++;
558   bmap_file:
559     if (guestfs_bmap_file (g, path) == -1)
560       return -1;
561     mark_start (object);
562     r = guestfs_bmap (g);
563     mark_end ();
564     if (r == -1) return -1;
565   }
566   else if (type == 'd') {       /* directory */
567     count_directory++;
568     goto bmap_file;
569   }
570
571   return 0;
572 }
573
574 /* Convert ranges to output file format. */
575 static void print_range (uint64_t start, uint64_t end, const char *object, void *opaque);
576
577 static int
578 ranges_to_output (void)
579 {
580   FILE *fp;
581
582   pthread_mutex_lock (&current_object_mutex);
583
584   /* Write out the ranges to 'output'. */
585   fp = fopen (output, "w");
586   if (fp == NULL) {
587     perror (output);
588     return -1;
589   }
590
591   iter_range (ranges, print_range, fp);
592
593   fclose (fp);
594
595   pthread_mutex_unlock (&current_object_mutex);
596
597   return 0;
598 }
599
600 static void
601 print_range (uint64_t start, uint64_t end, const char *object, void *opaque)
602 {
603   FILE *fp = opaque;
604
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.
608    */
609   fprintf (fp, "1 %" PRIx64 " %" PRIx64 " %s\n", start, end, object);
610 }
611
612 /* Register the nbdkit plugin. */
613
614 static struct nbdkit_plugin plugin = {
615   .name              = "virtbmapexaminer",
616   .version           = PACKAGE_VERSION,
617   .config            = bmap_config,
618   .config_help       = bmap_config_help,
619   .config_complete   = bmap_config_complete,
620   .open              = bmap_open,
621   .close             = bmap_close,
622   .get_size          = bmap_get_size,
623   .pread             = bmap_pread,
624   .unload            = bmap_unload,
625 };
626
627 NBDKIT_REGISTER_PLUGIN(plugin)