extra-tests: Add an extra suppression for OCaml 3.11.2 in RHEL 6.
[libguestfs.git] / inspector / virt-inspector.c
1 /* virt-inspector
2  * Copyright (C) 2010-2011 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 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <getopt.h>
27 #include <locale.h>
28 #include <assert.h>
29 #include <libintl.h>
30
31 #include <libxml/xmlIO.h>
32 #include <libxml/xmlwriter.h>
33 #include <libxml/xpath.h>
34 #include <libxml/parser.h>
35 #include <libxml/tree.h>
36 #include <libxml/xmlsave.h>
37
38 #include "progname.h"
39 #include "c-ctype.h"
40
41 #include "guestfs.h"
42 #include "options.h"
43
44 /* Currently open libguestfs handle. */
45 guestfs_h *g;
46
47 int read_only = 1;
48 int live = 0;
49 int verbose = 0;
50 int keys_from_stdin = 0;
51 int echo_keys = 0;
52 const char *libvirt_uri = NULL;
53 int inspector = 1;
54 static const char *xpath = NULL;
55
56 static void output (char **roots);
57 static void output_roots (xmlTextWriterPtr xo, char **roots);
58 static void output_root (xmlTextWriterPtr xo, char *root);
59 static void output_mountpoints (xmlTextWriterPtr xo, char *root);
60 static void output_filesystems (xmlTextWriterPtr xo, char *root);
61 static void output_drive_mappings (xmlTextWriterPtr xo, char *root);
62 static void output_applications (xmlTextWriterPtr xo, char *root);
63 static void canonicalize (char *dev);
64 static void free_strings (char **argv);
65 static int count_strings (char *const*argv);
66 static void do_xpath (const char *query);
67
68 static inline char *
69 bad_cast (char const *s)
70 {
71   return (char *) s;
72 }
73
74 static void __attribute__((noreturn))
75 usage (int status)
76 {
77   if (status != EXIT_SUCCESS)
78     fprintf (stderr, _("Try `%s --help' for more information.\n"),
79              program_name);
80   else {
81     fprintf (stdout,
82            _("%s: display information about a virtual machine\n"
83              "Copyright (C) 2010 Red Hat Inc.\n"
84              "Usage:\n"
85              "  %s [--options] -d domname file [file ...]\n"
86              "  %s [--options] -a disk.img [-a disk.img ...] file [file ...]\n"
87              "Options:\n"
88              "  -a|--add image       Add image\n"
89              "  -c|--connect uri     Specify libvirt URI for -d option\n"
90              "  -d|--domain guest    Add disks from libvirt guest\n"
91              "  --echo-keys          Don't turn off echo for passphrases\n"
92              "  --format[=raw|..]    Force disk format for -a option\n"
93              "  --help               Display brief help\n"
94              "  --keys-from-stdin    Read passphrases from stdin\n"
95              "  -v|--verbose         Verbose messages\n"
96              "  -V|--version         Display version and exit\n"
97              "  -x                   Trace libguestfs API calls\n"
98              "  --xpath query        Perform an XPath query\n"
99              "For more information, see the manpage %s(1).\n"),
100              program_name, program_name, program_name,
101              program_name);
102   }
103   exit (status);
104 }
105
106 int
107 main (int argc, char *argv[])
108 {
109   /* Set global program name that is not polluted with libtool artifacts.  */
110   set_program_name (argv[0]);
111
112   setlocale (LC_ALL, "");
113   bindtextdomain (PACKAGE, LOCALEBASEDIR);
114   textdomain (PACKAGE);
115
116   enum { HELP_OPTION = CHAR_MAX + 1 };
117
118   static const char *options = "a:c:d:vVx";
119   static const struct option long_options[] = {
120     { "add", 1, 0, 'a' },
121     { "connect", 1, 0, 'c' },
122     { "domain", 1, 0, 'd' },
123     { "echo-keys", 0, 0, 0 },
124     { "format", 2, 0, 0 },
125     { "help", 0, 0, HELP_OPTION },
126     { "keys-from-stdin", 0, 0, 0 },
127     { "verbose", 0, 0, 'v' },
128     { "version", 0, 0, 'V' },
129     { "xpath", 1, 0, 0 },
130     { 0, 0, 0, 0 }
131   };
132   struct drv *drvs = NULL;
133   struct drv *drv;
134   const char *format = NULL;
135   int c;
136   int option_index;
137
138   g = guestfs_create ();
139   if (g == NULL) {
140     fprintf (stderr, _("guestfs_create: failed to create handle\n"));
141     exit (EXIT_FAILURE);
142   }
143
144   argv[0] = bad_cast (program_name);
145
146   for (;;) {
147     c = getopt_long (argc, argv, options, long_options, &option_index);
148     if (c == -1) break;
149
150     switch (c) {
151     case 0:                     /* options which are long only */
152       if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
153         keys_from_stdin = 1;
154       } else if (STREQ (long_options[option_index].name, "echo-keys")) {
155         echo_keys = 1;
156       } else if (STREQ (long_options[option_index].name, "format")) {
157         if (!optarg || STREQ (optarg, ""))
158           format = NULL;
159         else
160           format = optarg;
161       } else if (STREQ (long_options[option_index].name, "xpath")) {
162         xpath = optarg;
163       } else {
164         fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
165                  program_name, long_options[option_index].name, option_index);
166         exit (EXIT_FAILURE);
167       }
168       break;
169
170     case 'a':
171       OPTION_a;
172       break;
173
174     case 'c':
175       OPTION_c;
176       break;
177
178     case 'd':
179       OPTION_d;
180       break;
181
182     case 'h':
183       usage (EXIT_SUCCESS);
184
185     case 'v':
186       OPTION_v;
187       break;
188
189     case 'V':
190       OPTION_V;
191       break;
192
193     case 'x':
194       OPTION_x;
195       break;
196
197     case HELP_OPTION:
198       usage (EXIT_SUCCESS);
199
200     default:
201       usage (EXIT_FAILURE);
202     }
203   }
204
205   /* Old-style syntax?  There were no -a or -d options in the old
206    * virt-inspector which is how we detect this.
207    */
208   if (drvs == NULL) {
209     while (optind < argc) {
210       if (strchr (argv[optind], '/') ||
211           access (argv[optind], F_OK) == 0) { /* simulate -a option */
212         drv = malloc (sizeof (struct drv));
213         if (!drv) {
214           perror ("malloc");
215           exit (EXIT_FAILURE);
216         }
217         drv->type = drv_a;
218         drv->a.filename = argv[optind];
219         drv->a.format = NULL;
220         drv->next = drvs;
221         drvs = drv;
222       } else {                  /* simulate -d option */
223         drv = malloc (sizeof (struct drv));
224         if (!drv) {
225           perror ("malloc");
226           exit (EXIT_FAILURE);
227         }
228         drv->type = drv_d;
229         drv->d.guest = argv[optind];
230         drv->next = drvs;
231         drvs = drv;
232       }
233
234       optind++;
235     }
236   }
237
238   /* These are really constants, but they have to be variables for the
239    * options parsing code.  Assert here that they have known-good
240    * values.
241    */
242   assert (read_only == 1);
243   assert (inspector == 1);
244   assert (live == 0);
245
246   /* Must be no extra arguments on the command line. */
247   if (optind != argc)
248     usage (EXIT_FAILURE);
249
250   /* XPath is modal: no drives should be specified.  There must be
251    * one extra parameter on the command line.
252    */
253   if (xpath) {
254     if (drvs != NULL) {
255       fprintf (stderr, _("%s: cannot use --xpath together with other options.\n"),
256                program_name);
257       exit (EXIT_FAILURE);
258     }
259
260     do_xpath (xpath);
261
262     exit (EXIT_SUCCESS);
263   }
264
265   /* User must have specified some drives. */
266   if (drvs == NULL)
267     usage (EXIT_FAILURE);
268
269   /* Add drives, inspect and mount.  Note that inspector is always true,
270    * and there is no -m option.
271    */
272   add_drives (drvs, 'a');
273
274   if (guestfs_launch (g) == -1)
275     exit (EXIT_FAILURE);
276
277   /* Free up data structures, no longer needed after this point. */
278   free_drives (drvs);
279
280   /* NB. Can't call inspect_mount () here (ie. normal processing of
281    * the -i option) because it can only handle a single root.  So we
282    * use low-level APIs.
283    */
284   inspect_do_decrypt ();
285
286   char **roots = guestfs_inspect_os (g);
287   if (roots == NULL) {
288     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"),
289              program_name);
290     exit (EXIT_FAILURE);
291   }
292
293   output (roots);
294
295   free_strings (roots);
296
297   guestfs_close (g);
298
299   exit (EXIT_SUCCESS);
300 }
301
302 #define DISABLE_GUESTFS_ERRORS_FOR(stmt) do {                           \
303     guestfs_error_handler_cb old_error_cb;                              \
304     void *old_error_data;                                               \
305     old_error_cb = guestfs_get_error_handler (g, &old_error_data);      \
306     guestfs_set_error_handler (g, NULL, NULL);                          \
307     stmt;                                                               \
308     guestfs_set_error_handler (g, old_error_cb, old_error_data);        \
309   } while (0)
310
311 #define XMLERROR(code,e) do {                                           \
312     if ((e) == (code)) {                                                \
313       fprintf (stderr, _("%s: XML write error at \"%s\": %m\n"),        \
314                #e, program_name);                                       \
315       exit (EXIT_FAILURE);                                              \
316     }                                                                   \
317   } while (0)
318
319 static void
320 output (char **roots)
321 {
322   xmlOutputBufferPtr ob = xmlOutputBufferCreateFd (1, NULL);
323   if (ob == NULL) {
324     fprintf (stderr,
325              _("%s: xmlOutputBufferCreateFd: failed to open stdout\n"),
326              program_name);
327     exit (EXIT_FAILURE);
328   }
329
330   xmlTextWriterPtr xo = xmlNewTextWriter (ob);
331   if (xo == NULL) {
332     fprintf (stderr,
333              _("%s: xmlNewTextWriter: failed to create libxml2 writer\n"),
334              program_name);
335     exit (EXIT_FAILURE);
336   }
337
338   /* Pretty-print the output. */
339   XMLERROR (-1, xmlTextWriterSetIndent (xo, 1));
340   XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST "  "));
341
342   XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL));
343   output_roots (xo, roots);
344   XMLERROR (-1, xmlTextWriterEndDocument (xo));
345
346   /* 'ob' is freed by this too. */
347   xmlFreeTextWriter (xo);
348 }
349
350 static void
351 output_roots (xmlTextWriterPtr xo, char **roots)
352 {
353   size_t i;
354
355   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystems"));
356   for (i = 0; roots[i] != NULL; ++i)
357     output_root (xo, roots[i]);
358   XMLERROR (-1, xmlTextWriterEndElement (xo));
359 }
360
361 static void
362 output_root (xmlTextWriterPtr xo, char *root)
363 {
364   char *str;
365   int i, r;
366   char buf[32];
367   char canonical_root[strlen (root) + 1];
368   size_t size;
369
370   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystem"));
371
372   strcpy (canonical_root, root);
373   canonicalize (canonical_root);
374   XMLERROR (-1,
375     xmlTextWriterWriteElement (xo, BAD_CAST "root", BAD_CAST canonical_root));
376
377   str = guestfs_inspect_get_type (g, root);
378   if (!str) exit (EXIT_FAILURE);
379   if (STRNEQ (str, "unknown"))
380     XMLERROR (-1,
381       xmlTextWriterWriteElement (xo, BAD_CAST "name", BAD_CAST str));
382   free (str);
383
384   str = guestfs_inspect_get_arch (g, root);
385   if (!str) exit (EXIT_FAILURE);
386   if (STRNEQ (str, "unknown"))
387     XMLERROR (-1,
388       xmlTextWriterWriteElement (xo, BAD_CAST "arch", BAD_CAST str));
389   free (str);
390
391   str = guestfs_inspect_get_distro (g, root);
392   if (!str) exit (EXIT_FAILURE);
393   if (STRNEQ (str, "unknown"))
394     XMLERROR (-1,
395       xmlTextWriterWriteElement (xo, BAD_CAST "distro", BAD_CAST str));
396   free (str);
397
398   str = guestfs_inspect_get_product_name (g, root);
399   if (!str) exit (EXIT_FAILURE);
400   if (STRNEQ (str, "unknown"))
401     XMLERROR (-1,
402       xmlTextWriterWriteElement (xo, BAD_CAST "product_name", BAD_CAST str));
403   free (str);
404
405   str = guestfs_inspect_get_product_variant (g, root);
406   if (!str) exit (EXIT_FAILURE);
407   if (STRNEQ (str, "unknown"))
408     XMLERROR (-1,
409       xmlTextWriterWriteElement (xo, BAD_CAST "product_variant", BAD_CAST str));
410   free (str);
411
412   i = guestfs_inspect_get_major_version (g, root);
413   snprintf (buf, sizeof buf, "%d", i);
414   XMLERROR (-1,
415     xmlTextWriterWriteElement (xo, BAD_CAST "major_version", BAD_CAST buf));
416   i = guestfs_inspect_get_minor_version (g, root);
417   snprintf (buf, sizeof buf, "%d", i);
418   XMLERROR (-1,
419     xmlTextWriterWriteElement (xo, BAD_CAST "minor_version", BAD_CAST buf));
420
421   str = guestfs_inspect_get_package_format (g, root);
422   if (!str) exit (EXIT_FAILURE);
423   if (STRNEQ (str, "unknown"))
424     XMLERROR (-1,
425       xmlTextWriterWriteElement (xo, BAD_CAST "package_format", BAD_CAST str));
426   free (str);
427
428   str = guestfs_inspect_get_package_management (g, root);
429   if (!str) exit (EXIT_FAILURE);
430   if (STRNEQ (str, "unknown"))
431     XMLERROR (-1,
432       xmlTextWriterWriteElement (xo, BAD_CAST "package_management",
433                                  BAD_CAST str));
434   free (str);
435
436   /* inspect-get-windows-systemroot will fail with non-windows guests,
437    * or if the systemroot could not be determined for a windows guest.
438    * Disable error output around this call.
439    */
440   DISABLE_GUESTFS_ERRORS_FOR (
441     str = guestfs_inspect_get_windows_systemroot (g, root);
442     if (str)
443       XMLERROR (-1,
444                 xmlTextWriterWriteElement (xo, BAD_CAST "windows_systemroot",
445                                            BAD_CAST str));
446     free (str);
447   );
448   DISABLE_GUESTFS_ERRORS_FOR (
449     str = guestfs_inspect_get_windows_current_control_set (g, root);
450     if (str)
451       XMLERROR (-1,
452                 xmlTextWriterWriteElement (xo, BAD_CAST "windows_current_control_set",
453                                            BAD_CAST str));
454     free (str);
455   );
456
457   str = guestfs_inspect_get_hostname (g, root);
458   if (!str) exit (EXIT_FAILURE);
459   if (STRNEQ (str, "unknown"))
460     XMLERROR (-1,
461       xmlTextWriterWriteElement (xo, BAD_CAST "hostname",
462                                  BAD_CAST str));
463   free (str);
464
465   str = guestfs_inspect_get_format (g, root);
466   if (!str) exit (EXIT_FAILURE);
467   if (STRNEQ (str, "unknown"))
468     XMLERROR (-1,
469       xmlTextWriterWriteElement (xo, BAD_CAST "format",
470                                  BAD_CAST str));
471   free (str);
472
473   r = guestfs_inspect_is_live (g, root);
474   if (r > 0) {
475     XMLERROR (-1,
476               xmlTextWriterStartElement (xo, BAD_CAST "live"));
477     XMLERROR (-1, xmlTextWriterEndElement (xo));
478   }
479
480   r = guestfs_inspect_is_netinst (g, root);
481   if (r > 0) {
482     XMLERROR (-1,
483               xmlTextWriterStartElement (xo, BAD_CAST "netinst"));
484     XMLERROR (-1, xmlTextWriterEndElement (xo));
485   }
486
487   r = guestfs_inspect_is_multipart (g, root);
488   if (r > 0) {
489     XMLERROR (-1,
490               xmlTextWriterStartElement (xo, BAD_CAST "multipart"));
491     XMLERROR (-1, xmlTextWriterEndElement (xo));
492   }
493
494   output_mountpoints (xo, root);
495
496   output_filesystems (xo, root);
497
498   output_drive_mappings (xo, root);
499
500   /* We need to mount everything up in order to read out the list of
501    * applications and the icon, ie. everything below this point.
502    */
503   inspect_mount_root (root);
504
505   output_applications (xo, root);
506
507   /* Don't return favicon.  XXX Should we? */
508   str = guestfs_inspect_get_icon (g, root, &size,
509                                   GUESTFS_INSPECT_GET_ICON_FAVICON, 0,
510                                   -1);
511   if (!str) exit (EXIT_FAILURE);
512   if (size > 0) {
513     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "icon"));
514     XMLERROR (-1, xmlTextWriterWriteBase64 (xo, str, 0, size));
515     XMLERROR (-1, xmlTextWriterEndElement (xo));
516   }
517   /* Note we must free (str) even if size == 0, because that indicates
518    * there was no icon.
519    */
520   free (str);
521
522   /* Unmount (see inspect_mount_root above). */
523   if (guestfs_umount_all (g) == -1)
524     exit (EXIT_FAILURE);
525
526   XMLERROR (-1, xmlTextWriterEndElement (xo));
527 }
528
529 static int
530 compare_keys (const void *p1, const void *p2)
531 {
532   const char *key1 = * (char * const *) p1;
533   const char *key2 = * (char * const *) p2;
534
535   return strcmp (key1, key2);
536 }
537
538 static int
539 compare_keys_nocase (const void *p1, const void *p2)
540 {
541   const char *key1 = * (char * const *) p1;
542   const char *key2 = * (char * const *) p2;
543
544   return strcasecmp (key1, key2);
545 }
546
547 static int
548 compare_keys_len (const void *p1, const void *p2)
549 {
550   const char *key1 = * (char * const *) p1;
551   const char *key2 = * (char * const *) p2;
552   int c;
553
554   c = strlen (key1) - strlen (key2);
555   if (c != 0)
556     return c;
557
558   return compare_keys (p1, p2);
559 }
560
561 static void
562 output_mountpoints (xmlTextWriterPtr xo, char *root)
563 {
564   char **mountpoints;
565   size_t i;
566
567   mountpoints = guestfs_inspect_get_mountpoints (g, root);
568   if (mountpoints == NULL)
569     exit (EXIT_FAILURE);
570
571   /* Sort by key length, shortest key first, and then name, so the
572    * output is stable.
573    */
574   qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
575          compare_keys_len);
576
577   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "mountpoints"));
578
579   for (i = 0; mountpoints[i] != NULL; i += 2) {
580     canonicalize (mountpoints[i+1]);
581
582     XMLERROR (-1,
583               xmlTextWriterStartElement (xo, BAD_CAST "mountpoint"));
584     XMLERROR (-1,
585               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
586                                            BAD_CAST mountpoints[i+1]));
587     XMLERROR (-1,
588               xmlTextWriterWriteString (xo, BAD_CAST mountpoints[i]));
589     XMLERROR (-1, xmlTextWriterEndElement (xo));
590   }
591
592   XMLERROR (-1, xmlTextWriterEndElement (xo));
593
594   free_strings (mountpoints);
595 }
596
597 static void
598 output_filesystems (xmlTextWriterPtr xo, char *root)
599 {
600   char **filesystems;
601   char *str;
602   size_t i;
603
604   filesystems = guestfs_inspect_get_filesystems (g, root);
605   if (filesystems == NULL)
606     exit (EXIT_FAILURE);
607
608   /* Sort by name so the output is stable. */
609   qsort (filesystems, count_strings (filesystems), sizeof (char *),
610          compare_keys);
611
612   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystems"));
613
614   for (i = 0; filesystems[i] != NULL; ++i) {
615     canonicalize (filesystems[i]);
616
617     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystem"));
618     XMLERROR (-1,
619               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
620                                            BAD_CAST filesystems[i]));
621
622     DISABLE_GUESTFS_ERRORS_FOR (
623       str = guestfs_vfs_type (g, filesystems[i]);
624       if (str && str[0])
625         XMLERROR (-1,
626                   xmlTextWriterWriteElement (xo, BAD_CAST "type",
627                                              BAD_CAST str));
628       free (str);
629     );
630
631     DISABLE_GUESTFS_ERRORS_FOR (
632       str = guestfs_vfs_label (g, filesystems[i]);
633       if (str && str[0])
634         XMLERROR (-1,
635                   xmlTextWriterWriteElement (xo, BAD_CAST "label",
636                                              BAD_CAST str));
637       free (str);
638     );
639
640     DISABLE_GUESTFS_ERRORS_FOR (
641       str = guestfs_vfs_uuid (g, filesystems[i]);
642       if (str && str[0])
643         XMLERROR (-1,
644                   xmlTextWriterWriteElement (xo, BAD_CAST "uuid",
645                                              BAD_CAST str));
646       free (str);
647     );
648
649     XMLERROR (-1, xmlTextWriterEndElement (xo));
650   }
651
652   XMLERROR (-1, xmlTextWriterEndElement (xo));
653
654   free_strings (filesystems);
655 }
656
657 static void
658 output_drive_mappings (xmlTextWriterPtr xo, char *root)
659 {
660   char **drive_mappings = NULL;
661   size_t i;
662
663   DISABLE_GUESTFS_ERRORS_FOR (
664     drive_mappings = guestfs_inspect_get_drive_mappings (g, root);
665   );
666   if (drive_mappings == NULL)
667     return;
668
669   if (drive_mappings[0] == NULL) {
670     free_strings (drive_mappings);
671     return;
672   }
673
674   /* Sort by key. */
675   qsort (drive_mappings,
676          count_strings (drive_mappings) / 2, 2 * sizeof (char *),
677          compare_keys_nocase);
678
679   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "drive_mappings"));
680
681   for (i = 0; drive_mappings[i] != NULL; i += 2) {
682     canonicalize (drive_mappings[i+1]);
683
684     XMLERROR (-1,
685               xmlTextWriterStartElement (xo, BAD_CAST "drive_mapping"));
686     XMLERROR (-1,
687               xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
688                                            BAD_CAST drive_mappings[i]));
689     XMLERROR (-1,
690               xmlTextWriterWriteString (xo, BAD_CAST drive_mappings[i+1]));
691     XMLERROR (-1, xmlTextWriterEndElement (xo));
692   }
693
694   XMLERROR (-1, xmlTextWriterEndElement (xo));
695
696   free_strings (drive_mappings);
697 }
698
699 static void
700 output_applications (xmlTextWriterPtr xo, char *root)
701 {
702   struct guestfs_application_list *apps;
703   size_t i;
704
705   /* This returns an empty list if we simply couldn't determine the
706    * applications, so if it returns NULL then it's a real error.
707    */
708   apps = guestfs_inspect_list_applications (g, root);
709   if (apps == NULL)
710     exit (EXIT_FAILURE);
711
712   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));
713
714   for (i = 0; i < apps->len; ++i) {
715     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "application"));
716
717     assert (apps->val[i].app_name && apps->val[i].app_name[0]);
718     XMLERROR (-1,
719               xmlTextWriterWriteElement (xo, BAD_CAST "name",
720                                          BAD_CAST apps->val[i].app_name));
721
722     if (apps->val[i].app_display_name && apps->val[i].app_display_name[0])
723       XMLERROR (-1,
724         xmlTextWriterWriteElement (xo, BAD_CAST "display_name",
725                                    BAD_CAST apps->val[i].app_display_name));
726
727     if (apps->val[i].app_epoch != 0) {
728       char buf[32];
729
730       snprintf (buf, sizeof buf, "%d", apps->val[i].app_epoch);
731
732       XMLERROR (-1,
733         xmlTextWriterWriteElement (xo, BAD_CAST "epoch", BAD_CAST buf));
734     }
735
736     if (apps->val[i].app_version && apps->val[i].app_version[0])
737       XMLERROR (-1,
738         xmlTextWriterWriteElement (xo, BAD_CAST "version",
739                                    BAD_CAST apps->val[i].app_version));
740     if (apps->val[i].app_release && apps->val[i].app_release[0])
741       XMLERROR (-1,
742         xmlTextWriterWriteElement (xo, BAD_CAST "release",
743                                    BAD_CAST apps->val[i].app_release));
744     if (apps->val[i].app_install_path && apps->val[i].app_install_path[0])
745       XMLERROR (-1,
746         xmlTextWriterWriteElement (xo, BAD_CAST "install_path",
747                                    BAD_CAST apps->val[i].app_install_path));
748     if (apps->val[i].app_publisher && apps->val[i].app_publisher[0])
749       XMLERROR (-1,
750         xmlTextWriterWriteElement (xo, BAD_CAST "publisher",
751                                    BAD_CAST apps->val[i].app_publisher));
752     if (apps->val[i].app_url && apps->val[i].app_url[0])
753       XMLERROR (-1,
754         xmlTextWriterWriteElement (xo, BAD_CAST "url",
755                                    BAD_CAST apps->val[i].app_url));
756     if (apps->val[i].app_source_package && apps->val[i].app_source_package[0])
757       XMLERROR (-1,
758         xmlTextWriterWriteElement (xo, BAD_CAST "source_package",
759                                    BAD_CAST apps->val[i].app_source_package));
760     if (apps->val[i].app_summary && apps->val[i].app_summary[0])
761       XMLERROR (-1,
762         xmlTextWriterWriteElement (xo, BAD_CAST "summary",
763                                    BAD_CAST apps->val[i].app_summary));
764     if (apps->val[i].app_description && apps->val[i].app_description[0])
765       XMLERROR (-1,
766         xmlTextWriterWriteElement (xo, BAD_CAST "description",
767                                    BAD_CAST apps->val[i].app_description));
768
769     XMLERROR (-1, xmlTextWriterEndElement (xo));
770   }
771
772   XMLERROR (-1, xmlTextWriterEndElement (xo));
773
774   guestfs_free_application_list (apps);
775 }
776
777 /* "/dev/vda1" -> "/dev/sda1"
778  * See BLOCK DEVICE NAMING in guestfs(3).
779  */
780 static void
781 canonicalize (char *dev)
782 {
783   if (STRPREFIX (dev, "/dev/") &&
784       (dev[5] == 'h' || dev[5] == 'v') &&
785       dev[6] == 'd' &&
786       c_isalpha (dev[7]) &&
787       (c_isdigit (dev[8]) || dev[8] == '\0'))
788     dev[5] = 's';
789 }
790
791 static void
792 free_strings (char **argv)
793 {
794   int argc;
795
796   for (argc = 0; argv[argc] != NULL; ++argc)
797     free (argv[argc]);
798   free (argv);
799 }
800
801 static int
802 count_strings (char *const *argv)
803 {
804   int c;
805
806   for (c = 0; argv[c]; ++c)
807     ;
808   return c;
809 }
810
811 /* Run an XPath query on XML on stdin, print results to stdout. */
812 static void
813 do_xpath (const char *query)
814 {
815   xmlDocPtr doc;
816   xmlXPathContextPtr xpathCtx;
817   xmlXPathObjectPtr xpathObj;
818   xmlNodeSetPtr nodes;
819   char *r;
820   size_t i;
821   xmlSaveCtxtPtr saveCtx;
822   xmlDocPtr wrdoc;
823   xmlNodePtr wrnode;
824
825   doc = xmlReadFd (STDIN_FILENO, NULL, "utf8", 0);
826   if (doc == NULL) {
827     fprintf (stderr, _("%s: unable to parse XML from stdin\n"), program_name);
828     exit (EXIT_FAILURE);
829   }
830
831   xpathCtx = xmlXPathNewContext (doc);
832   if (xpathCtx == NULL) {
833     fprintf (stderr, _("%s: unable to create new XPath context\n"),
834              program_name);
835     exit (EXIT_FAILURE);
836   }
837
838   xpathObj = xmlXPathEvalExpression (BAD_CAST query, xpathCtx);
839   if (xpathObj == NULL) {
840     fprintf (stderr, _("%s: unable to evaluate XPath expression\n"),
841              program_name);
842     exit (EXIT_FAILURE);
843   }
844
845   switch (xpathObj->type) {
846   case XPATH_NODESET:
847     nodes = xpathObj->nodesetval;
848
849     saveCtx = xmlSaveToFd (STDOUT_FILENO, NULL, XML_SAVE_NO_DECL);
850     if (saveCtx == NULL) {
851       fprintf (stderr, _("%s: xmlSaveToFd failed\n"), program_name);
852       exit (EXIT_FAILURE);
853     }
854
855     for (i = 0; i < (size_t) nodes->nodeNr; ++i) {
856       wrdoc = xmlNewDoc (BAD_CAST "1.0");
857       if (wrdoc == NULL) {
858         fprintf (stderr, _("%s: xmlNewDoc failed\n"), program_name);
859         exit (EXIT_FAILURE);
860       }
861       wrnode = xmlCopyNode (nodes->nodeTab[i], 1);
862       if (wrnode == NULL) {
863         fprintf (stderr, _("%s: xmlCopyNode failed\n"), program_name);
864         exit (EXIT_FAILURE);
865       }
866
867       xmlDocSetRootElement (wrdoc, wrnode);
868
869       if (xmlSaveDoc (saveCtx, wrdoc) == -1) {
870         fprintf (stderr, _("%s: xmlSaveDoc failed\n"), program_name);
871         exit (EXIT_FAILURE);
872       }
873
874       xmlFreeDoc (wrdoc);
875     }
876
877     xmlSaveClose (saveCtx);
878
879     break;
880
881   case XPATH_STRING:
882     r = (char *) xpathObj->stringval;
883     printf ("%s", r);
884     i = strlen (r);
885     if (i > 0 && r[i-1] != '\n')
886       printf ("\n");
887     break;
888
889   case XPATH_UNDEFINED: /* grrrrr ... switch-enum is a useless warning */
890   case XPATH_BOOLEAN:
891   case XPATH_NUMBER:
892   case XPATH_POINT:
893   case XPATH_RANGE:
894   case XPATH_LOCATIONSET:
895   case XPATH_USERS:
896   case XPATH_XSLT_TREE:
897   default:
898     r = (char *) xmlXPathCastToString (xpathObj);
899     printf ("%s\n", r);
900     free (r);
901   }
902
903   xmlXPathFreeObject (xpathObj);
904   xmlXPathFreeContext (xpathCtx);
905   xmlFreeDoc (doc);
906
907   exit (EXIT_SUCCESS);
908 }