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