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