ee7c9c38010ac35f88038e5fe5f1d8c6b15aa3a0
[libguestfs.git] / inspector / virt-inspector.c
1 /* virt-inspector
2  * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <inttypes.h>
24 #include <unistd.h>
25 #include <getopt.h>
26 #include <assert.h>
27
28 #include <libxml/xmlIO.h>
29 #include <libxml/xmlwriter.h>
30
31 #include "progname.h"
32 #include "c-ctype.h"
33
34 #include "guestfs.h"
35 #include "options.h"
36
37 /* Currently open libguestfs handle. */
38 guestfs_h *g;
39
40 int read_only = 1;
41 int verbose = 0;
42 int keys_from_stdin = 0;
43 int echo_keys = 0;
44 const char *libvirt_uri = NULL;
45 int inspector = 1;
46
47 static void output (char **roots);
48 static void output_roots (xmlTextWriterPtr xo, char **roots);
49 static void output_root (xmlTextWriterPtr xo, char *root);
50 static void output_mountpoints (xmlTextWriterPtr xo, char *root);
51 static void output_filesystems (xmlTextWriterPtr xo, char *root);
52 static void output_applications (xmlTextWriterPtr xo, char *root);
53 static void canonicalize (char *dev);
54 static void free_strings (char **argv);
55 static int count_strings (char *const*argv);
56
57 static inline char *
58 bad_cast (char const *s)
59 {
60   return (char *) s;
61 }
62
63 static void __attribute__((noreturn))
64 usage (int status)
65 {
66   if (status != EXIT_SUCCESS)
67     fprintf (stderr, _("Try `%s --help' for more information.\n"),
68              program_name);
69   else {
70     fprintf (stdout,
71            _("%s: display information about a virtual machine\n"
72              "Copyright (C) 2010 Red Hat Inc.\n"
73              "Usage:\n"
74              "  %s [--options] -d domname file [file ...]\n"
75              "  %s [--options] -a disk.img [-a disk.img ...] file [file ...]\n"
76              "Options:\n"
77              "  -a|--add image       Add image\n"
78              "  -c|--connect uri     Specify libvirt URI for -d option\n"
79              "  -d|--domain guest    Add disks from libvirt guest\n"
80              "  --echo-keys          Don't turn off echo for passphrases\n"
81              "  --format[=raw|..]    Force disk format for -a option\n"
82              "  --help               Display brief help\n"
83              "  --keys-from-stdin    Read passphrases from stdin\n"
84              "  -v|--verbose         Verbose messages\n"
85              "  -V|--version         Display version and exit\n"
86              "  -x                   Trace libguestfs API calls\n"
87              "For more information, see the manpage %s(1).\n"),
88              program_name, program_name, program_name,
89              program_name);
90   }
91   exit (status);
92 }
93
94 int
95 main (int argc, char *argv[])
96 {
97   /* Set global program name that is not polluted with libtool artifacts.  */
98   set_program_name (argv[0]);
99
100   setlocale (LC_ALL, "");
101   bindtextdomain (PACKAGE, LOCALEBASEDIR);
102   textdomain (PACKAGE);
103
104   enum { HELP_OPTION = CHAR_MAX + 1 };
105
106   static const char *options = "a:c:d:vVx";
107   static const struct option long_options[] = {
108     { "add", 1, 0, 'a' },
109     { "connect", 1, 0, 'c' },
110     { "domain", 1, 0, 'd' },
111     { "echo-keys", 0, 0, 0 },
112     { "format", 2, 0, 0 },
113     { "help", 0, 0, HELP_OPTION },
114     { "keys-from-stdin", 0, 0, 0 },
115     { "verbose", 0, 0, 'v' },
116     { "version", 0, 0, 'V' },
117     { 0, 0, 0, 0 }
118   };
119   struct drv *drvs = NULL;
120   struct drv *drv;
121   char *p, *file = NULL;
122   const char *format = NULL;
123   int c;
124   int option_index;
125   int next_prepared_drive = 1;
126
127   g = guestfs_create ();
128   if (g == NULL) {
129     fprintf (stderr, _("guestfs_create: failed to create handle\n"));
130     exit (EXIT_FAILURE);
131   }
132
133   argv[0] = bad_cast (program_name);
134
135   for (;;) {
136     c = getopt_long (argc, argv, options, long_options, &option_index);
137     if (c == -1) break;
138
139     switch (c) {
140     case 0:                     /* options which are long only */
141       if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
142         keys_from_stdin = 1;
143       } else if (STREQ (long_options[option_index].name, "echo-keys")) {
144         echo_keys = 1;
145       } else if (STREQ (long_options[option_index].name, "format")) {
146         if (!optarg || STREQ (optarg, ""))
147           format = NULL;
148         else
149           format = optarg;
150       } else {
151         fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
152                  program_name, long_options[option_index].name, option_index);
153         exit (EXIT_FAILURE);
154       }
155       break;
156
157     case 'a':
158       OPTION_a;
159       break;
160
161     case 'c':
162       OPTION_c;
163       break;
164
165     case 'd':
166       OPTION_d;
167       break;
168
169     case 'h':
170       usage (EXIT_SUCCESS);
171
172     case 'v':
173       OPTION_v;
174       break;
175
176     case 'V':
177       OPTION_V;
178       break;
179
180     case 'x':
181       OPTION_x;
182       break;
183
184     case HELP_OPTION:
185       usage (EXIT_SUCCESS);
186
187     default:
188       usage (EXIT_FAILURE);
189     }
190   }
191
192   /* Old-style syntax?  There were no -a or -d options in the old
193    * virt-inspector which is how we detect this.
194    */
195   if (drvs == NULL) {
196     while (optind < argc) {
197       if (strchr (argv[optind], '/') ||
198           access (argv[optind], F_OK) == 0) { /* simulate -a option */
199         drv = malloc (sizeof (struct drv));
200         if (!drv) {
201           perror ("malloc");
202           exit (EXIT_FAILURE);
203         }
204         drv->type = drv_a;
205         drv->a.filename = argv[optind];
206         drv->a.format = NULL;
207         drv->next = drvs;
208         drvs = drv;
209       } else {                  /* simulate -d option */
210         drv = malloc (sizeof (struct drv));
211         if (!drv) {
212           perror ("malloc");
213           exit (EXIT_FAILURE);
214         }
215         drv->type = drv_d;
216         drv->d.guest = argv[optind];
217         drv->next = drvs;
218         drvs = drv;
219       }
220
221       optind++;
222     }
223   }
224
225   /* These are really constants, but they have to be variables for the
226    * options parsing code.  Assert here that they have known-good
227    * values.
228    */
229   assert (read_only == 1);
230   assert (inspector == 1);
231
232   /* Must be no extra arguments on the command line. */
233   if (optind != argc)
234     usage (EXIT_FAILURE);
235
236   /* User must have specified some drives. */
237   if (drvs == NULL)
238     usage (EXIT_FAILURE);
239
240   /* Add drives, inspect and mount.  Note that inspector is always true,
241    * and there is no -m option.
242    */
243   add_drives (drvs, 'a');
244
245   if (guestfs_launch (g) == -1)
246     exit (EXIT_FAILURE);
247
248   /* Free up data structures, no longer needed after this point. */
249   free_drives (drvs);
250
251   /* NB. Can't call inspect_mount () here (ie. normal processing of
252    * the -i option) because it can only handle a single root.  So we
253    * use low-level APIs.
254    */
255   inspect_do_decrypt ();
256
257   char **roots = guestfs_inspect_os (g);
258   if (roots == NULL) {
259     fprintf (stderr, _("%s: no operating system could be detected inside this disk image.\n\nThis may be because the file is not a disk image, or is not a virtual machine\nimage, or because the OS type is not understood by libguestfs.\n\nNOTE for Red Hat Enterprise Linux 6 users: for Windows guest support you must\ninstall the separate libguestfs-winsupport package.\n\nIf you feel this is an error, please file a bug report including as much\ninformation about the disk image as possible.\n"),
260              program_name);
261     exit (EXIT_FAILURE);
262   }
263
264   output (roots);
265
266   free_strings (roots);
267
268   guestfs_close (g);
269
270   exit (EXIT_SUCCESS);
271 }
272
273 #define DISABLE_GUESTFS_ERRORS_FOR(stmt) do {                           \
274     guestfs_error_handler_cb old_error_cb;                              \
275     void *old_error_data;                                               \
276     old_error_cb = guestfs_get_error_handler (g, &old_error_data);      \
277     guestfs_set_error_handler (g, NULL, NULL);                          \
278     stmt;                                                               \
279     guestfs_set_error_handler (g, old_error_cb, old_error_data);        \
280   } while (0)
281
282 #define XMLERROR(code,e) do {                                           \
283     if ((e) == (code)) {                                                \
284       fprintf (stderr, _("%s: XML write error at \"%s\": %m\n"),        \
285                #e, program_name);                                       \
286       exit (EXIT_FAILURE);                                              \
287     }                                                                   \
288   } while (0)
289
290 static void
291 output (char **roots)
292 {
293   xmlOutputBufferPtr ob = xmlOutputBufferCreateFd (1, NULL);
294   if (ob == NULL) {
295     fprintf (stderr,
296              _("%s: xmlOutputBufferCreateFd: failed to open stdout\n"),
297              program_name);
298     exit (EXIT_FAILURE);
299   }
300
301   xmlTextWriterPtr xo = xmlNewTextWriter (ob);
302   if (xo == NULL) {
303     fprintf (stderr,
304              _("%s: xmlNewTextWriter: failed to create libxml2 writer\n"),
305              program_name);
306     exit (EXIT_FAILURE);
307   }
308
309   /* Pretty-print the output. */
310   XMLERROR (-1, xmlTextWriterSetIndent (xo, 1));
311   XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST "  "));
312
313   XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL));
314   output_roots (xo, roots);
315   XMLERROR (-1, xmlTextWriterEndDocument (xo));
316
317   /* 'ob' is freed by this too. */
318   xmlFreeTextWriter (xo);
319 }
320
321 static void
322 output_roots (xmlTextWriterPtr xo, char **roots)
323 {
324   size_t i;
325
326   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystems"));
327   for (i = 0; roots[i] != NULL; ++i)
328     output_root (xo, roots[i]);
329   XMLERROR (-1, xmlTextWriterEndElement (xo));
330 }
331
332 static void
333 output_root (xmlTextWriterPtr xo, char *root)
334 {
335   char *str;
336   int i;
337   char buf[32];
338   char canonical_root[strlen (root) + 1];
339
340   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystem"));
341
342   strcpy (canonical_root, root);
343   canonicalize (canonical_root);
344   XMLERROR (-1,
345     xmlTextWriterWriteElement (xo, BAD_CAST "root", BAD_CAST canonical_root));
346
347   str = guestfs_inspect_get_type (g, root);
348   if (!str) exit (EXIT_FAILURE);
349   if (STRNEQ (str, "unknown"))
350     XMLERROR (-1,
351       xmlTextWriterWriteElement (xo, BAD_CAST "name", BAD_CAST str));
352   free (str);
353
354   str = guestfs_inspect_get_arch (g, root);
355   if (!str) exit (EXIT_FAILURE);
356   if (STRNEQ (str, "unknown"))
357     XMLERROR (-1,
358       xmlTextWriterWriteElement (xo, BAD_CAST "arch", BAD_CAST str));
359   free (str);
360
361   str = guestfs_inspect_get_distro (g, root);
362   if (!str) exit (EXIT_FAILURE);
363   if (STRNEQ (str, "unknown"))
364     XMLERROR (-1,
365       xmlTextWriterWriteElement (xo, BAD_CAST "distro", BAD_CAST str));
366   free (str);
367
368   str = guestfs_inspect_get_product_name (g, root);
369   if (!str) exit (EXIT_FAILURE);
370   if (STRNEQ (str, "unknown"))
371     XMLERROR (-1,
372       xmlTextWriterWriteElement (xo, BAD_CAST "product_name", BAD_CAST str));
373   free (str);
374
375   i = guestfs_inspect_get_major_version (g, root);
376   snprintf (buf, sizeof buf, "%d", i);
377   XMLERROR (-1,
378     xmlTextWriterWriteElement (xo, BAD_CAST "major_version", BAD_CAST buf));
379   i = guestfs_inspect_get_minor_version (g, root);
380   snprintf (buf, sizeof buf, "%d", i);
381   XMLERROR (-1,
382     xmlTextWriterWriteElement (xo, BAD_CAST "minor_version", BAD_CAST buf));
383
384   str = guestfs_inspect_get_package_format (g, root);
385   if (!str) exit (EXIT_FAILURE);
386   if (STRNEQ (str, "unknown"))
387     XMLERROR (-1,
388       xmlTextWriterWriteElement (xo, BAD_CAST "package_format", BAD_CAST str));
389   free (str);
390
391   str = guestfs_inspect_get_package_management (g, root);
392   if (!str) exit (EXIT_FAILURE);
393   if (STRNEQ (str, "unknown"))
394     XMLERROR (-1,
395       xmlTextWriterWriteElement (xo, BAD_CAST "package_management",
396                                  BAD_CAST str));
397   free (str);
398
399   /* inspect-get-windows-systemroot will fail with non-windows guests,
400    * or if the systemroot could not be determined for a windows guest.
401    * Disable error output around this call.
402    */
403   DISABLE_GUESTFS_ERRORS_FOR (
404     str = guestfs_inspect_get_windows_systemroot (g, root);
405     if (str)
406       XMLERROR (-1,
407                 xmlTextWriterWriteElement (xo, BAD_CAST "windows_systemroot",
408                                            BAD_CAST str));
409     free (str);
410   );
411
412   output_mountpoints (xo, root);
413
414   output_filesystems (xo, root);
415
416   output_applications (xo, root);
417
418   XMLERROR (-1, xmlTextWriterEndElement (xo));
419 }
420
421 static int
422 compare_keys (const void *p1, const void *p2)
423 {
424   const char *key1 = * (char * const *) p1;
425   const char *key2 = * (char * const *) p2;
426
427   return strcmp (key1, key2);
428 }
429
430 static int
431 compare_keys_len (const void *p1, const void *p2)
432 {
433   const char *key1 = * (char * const *) p1;
434   const char *key2 = * (char * const *) p2;
435   int c;
436
437   c = strlen (key1) - strlen (key2);
438   if (c != 0)
439     return c;
440
441   return compare_keys (p1, p2);
442 }
443
444 static void
445 output_mountpoints (xmlTextWriterPtr xo, char *root)
446 {
447   char **mountpoints;
448   size_t i;
449
450   mountpoints = guestfs_inspect_get_mountpoints (g, root);
451   if (mountpoints == NULL)
452     exit (EXIT_FAILURE);
453
454   /* Sort by key length, shortest key first, and then name, so the
455    * output is stable.
456    */
457   qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
458          compare_keys_len);
459
460   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "mountpoints"));
461
462   for (i = 0; mountpoints[i] != NULL; i += 2) {
463     canonicalize (mountpoints[i+1]);
464
465     XMLERROR (-1,
466               xmlTextWriterStartElement (xo, BAD_CAST "mountpoint"));
467     XMLERROR (-1,
468               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
469                                            BAD_CAST mountpoints[i+1]));
470     XMLERROR (-1,
471               xmlTextWriterWriteString (xo, BAD_CAST mountpoints[i]));
472     XMLERROR (-1, xmlTextWriterEndElement (xo));
473   }
474
475   XMLERROR (-1, xmlTextWriterEndElement (xo));
476
477   free_strings (mountpoints);
478 }
479
480 static void
481 output_filesystems (xmlTextWriterPtr xo, char *root)
482 {
483   char **filesystems;
484   char *str;
485   size_t i;
486
487   filesystems = guestfs_inspect_get_filesystems (g, root);
488   if (filesystems == NULL)
489     exit (EXIT_FAILURE);
490
491   /* Sort by name so the output is stable. */
492   qsort (filesystems, count_strings (filesystems), sizeof (char *),
493          compare_keys);
494
495   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystems"));
496
497   for (i = 0; filesystems[i] != NULL; ++i) {
498     canonicalize (filesystems[i]);
499
500     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystem"));
501     XMLERROR (-1,
502               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
503                                            BAD_CAST filesystems[i]));
504
505     DISABLE_GUESTFS_ERRORS_FOR (
506       str = guestfs_vfs_type (g, filesystems[i]);
507       if (str && str[0])
508         XMLERROR (-1,
509                   xmlTextWriterWriteElement (xo, BAD_CAST "type",
510                                              BAD_CAST str));
511       free (str);
512     );
513
514     DISABLE_GUESTFS_ERRORS_FOR (
515       str = guestfs_vfs_label (g, filesystems[i]);
516       if (str && str[0])
517         XMLERROR (-1,
518                   xmlTextWriterWriteElement (xo, BAD_CAST "label",
519                                              BAD_CAST str));
520       free (str);
521     );
522
523     DISABLE_GUESTFS_ERRORS_FOR (
524       str = guestfs_vfs_uuid (g, filesystems[i]);
525       if (str && str[0])
526         XMLERROR (-1,
527                   xmlTextWriterWriteElement (xo, BAD_CAST "uuid",
528                                              BAD_CAST str));
529       free (str);
530     );
531
532     XMLERROR (-1, xmlTextWriterEndElement (xo));
533   }
534
535   XMLERROR (-1, xmlTextWriterEndElement (xo));
536
537   free_strings (filesystems);
538 }
539
540 static void
541 output_applications (xmlTextWriterPtr xo, char *root)
542 {
543   struct guestfs_application_list *apps;
544   size_t i;
545
546   /* We need to mount everything up in order to read out the list of
547    * applications.
548    */
549   inspect_mount_root (root);
550
551   /* This returns an empty list if we simply couldn't determine the
552    * applications, so if it returns NULL then it's a real error.
553    */
554   apps = guestfs_inspect_list_applications (g, root);
555   if (apps == NULL)
556     exit (EXIT_FAILURE);
557   if (guestfs_umount_all (g) == -1)
558     exit (EXIT_FAILURE);
559
560   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));
561
562   for (i = 0; i < apps->len; ++i) {
563     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "application"));
564
565     assert (apps->val[i].app_name && apps->val[i].app_name[0]);
566     XMLERROR (-1,
567               xmlTextWriterWriteElement (xo, BAD_CAST "name",
568                                          BAD_CAST apps->val[i].app_name));
569
570     if (apps->val[i].app_display_name && apps->val[i].app_display_name[0])
571       XMLERROR (-1,
572         xmlTextWriterWriteElement (xo, BAD_CAST "display_name",
573                                    BAD_CAST apps->val[i].app_display_name));
574
575     if (apps->val[i].app_epoch != 0) {
576       char buf[32];
577
578       snprintf (buf, sizeof buf, "%d", apps->val[i].app_epoch);
579
580       XMLERROR (-1,
581         xmlTextWriterWriteElement (xo, BAD_CAST "epoch", BAD_CAST buf));
582     }
583
584     if (apps->val[i].app_version && apps->val[i].app_version[0])
585       XMLERROR (-1,
586         xmlTextWriterWriteElement (xo, BAD_CAST "version",
587                                    BAD_CAST apps->val[i].app_version));
588     if (apps->val[i].app_release && apps->val[i].app_release[0])
589       XMLERROR (-1,
590         xmlTextWriterWriteElement (xo, BAD_CAST "release",
591                                    BAD_CAST apps->val[i].app_release));
592     if (apps->val[i].app_install_path && apps->val[i].app_install_path[0])
593       XMLERROR (-1,
594         xmlTextWriterWriteElement (xo, BAD_CAST "install_path",
595                                    BAD_CAST apps->val[i].app_install_path));
596     if (apps->val[i].app_publisher && apps->val[i].app_publisher[0])
597       XMLERROR (-1,
598         xmlTextWriterWriteElement (xo, BAD_CAST "publisher",
599                                    BAD_CAST apps->val[i].app_publisher));
600     if (apps->val[i].app_url && apps->val[i].app_url[0])
601       XMLERROR (-1,
602         xmlTextWriterWriteElement (xo, BAD_CAST "url",
603                                    BAD_CAST apps->val[i].app_url));
604     if (apps->val[i].app_source_package && apps->val[i].app_source_package[0])
605       XMLERROR (-1,
606         xmlTextWriterWriteElement (xo, BAD_CAST "source_package",
607                                    BAD_CAST apps->val[i].app_source_package));
608     if (apps->val[i].app_summary && apps->val[i].app_summary[0])
609       XMLERROR (-1,
610         xmlTextWriterWriteElement (xo, BAD_CAST "summary",
611                                    BAD_CAST apps->val[i].app_summary));
612     if (apps->val[i].app_description && apps->val[i].app_description[0])
613       XMLERROR (-1,
614         xmlTextWriterWriteElement (xo, BAD_CAST "description",
615                                    BAD_CAST apps->val[i].app_description));
616
617     XMLERROR (-1, xmlTextWriterEndElement (xo));
618   }
619
620   XMLERROR (-1, xmlTextWriterEndElement (xo));
621
622   guestfs_free_application_list (apps);
623 }
624
625 /* "/dev/vda1" -> "/dev/sda1"
626  * See BLOCK DEVICE NAMING in guestfs(3).
627  */
628 static void
629 canonicalize (char *dev)
630 {
631   if (STRPREFIX (dev, "/dev/") &&
632       (dev[5] == 'h' || dev[5] == 'v') &&
633       dev[6] == 'd' &&
634       c_isalpha (dev[7]) &&
635       (c_isdigit (dev[8]) || dev[8] == '\0'))
636     dev[5] = 's';
637 }
638
639 static void
640 free_strings (char **argv)
641 {
642   int argc;
643
644   for (argc = 0; argv[argc] != NULL; ++argc)
645     free (argv[argc]);
646   free (argv);
647 }
648
649 static int
650 count_strings (char *const *argv)
651 {
652   int c;
653
654   for (c = 0; argv[c]; ++c)
655     ;
656   return c;
657 }