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