607e5ae09ff24994df58c39803b6e2ce691631dc
[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
344   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "operatingsystem"));
345
346   strcpy (canonical_root, root);
347   canonicalize (canonical_root);
348   XMLERROR (-1,
349     xmlTextWriterWriteElement (xo, BAD_CAST "root", BAD_CAST canonical_root));
350
351   str = guestfs_inspect_get_type (g, root);
352   if (!str) exit (EXIT_FAILURE);
353   if (STRNEQ (str, "unknown"))
354     XMLERROR (-1,
355       xmlTextWriterWriteElement (xo, BAD_CAST "name", BAD_CAST str));
356   free (str);
357
358   str = guestfs_inspect_get_arch (g, root);
359   if (!str) exit (EXIT_FAILURE);
360   if (STRNEQ (str, "unknown"))
361     XMLERROR (-1,
362       xmlTextWriterWriteElement (xo, BAD_CAST "arch", BAD_CAST str));
363   free (str);
364
365   str = guestfs_inspect_get_distro (g, root);
366   if (!str) exit (EXIT_FAILURE);
367   if (STRNEQ (str, "unknown"))
368     XMLERROR (-1,
369       xmlTextWriterWriteElement (xo, BAD_CAST "distro", BAD_CAST str));
370   free (str);
371
372   str = guestfs_inspect_get_product_name (g, root);
373   if (!str) exit (EXIT_FAILURE);
374   if (STRNEQ (str, "unknown"))
375     XMLERROR (-1,
376       xmlTextWriterWriteElement (xo, BAD_CAST "product_name", BAD_CAST str));
377   free (str);
378
379   str = guestfs_inspect_get_product_variant (g, root);
380   if (!str) exit (EXIT_FAILURE);
381   if (STRNEQ (str, "unknown"))
382     XMLERROR (-1,
383       xmlTextWriterWriteElement (xo, BAD_CAST "product_variant", BAD_CAST str));
384   free (str);
385
386   i = guestfs_inspect_get_major_version (g, root);
387   snprintf (buf, sizeof buf, "%d", i);
388   XMLERROR (-1,
389     xmlTextWriterWriteElement (xo, BAD_CAST "major_version", BAD_CAST buf));
390   i = guestfs_inspect_get_minor_version (g, root);
391   snprintf (buf, sizeof buf, "%d", i);
392   XMLERROR (-1,
393     xmlTextWriterWriteElement (xo, BAD_CAST "minor_version", BAD_CAST buf));
394
395   str = guestfs_inspect_get_package_format (g, root);
396   if (!str) exit (EXIT_FAILURE);
397   if (STRNEQ (str, "unknown"))
398     XMLERROR (-1,
399       xmlTextWriterWriteElement (xo, BAD_CAST "package_format", BAD_CAST str));
400   free (str);
401
402   str = guestfs_inspect_get_package_management (g, root);
403   if (!str) exit (EXIT_FAILURE);
404   if (STRNEQ (str, "unknown"))
405     XMLERROR (-1,
406       xmlTextWriterWriteElement (xo, BAD_CAST "package_management",
407                                  BAD_CAST str));
408   free (str);
409
410   /* inspect-get-windows-systemroot will fail with non-windows guests,
411    * or if the systemroot could not be determined for a windows guest.
412    * Disable error output around this call.
413    */
414   DISABLE_GUESTFS_ERRORS_FOR (
415     str = guestfs_inspect_get_windows_systemroot (g, root);
416     if (str)
417       XMLERROR (-1,
418                 xmlTextWriterWriteElement (xo, BAD_CAST "windows_systemroot",
419                                            BAD_CAST str));
420     free (str);
421   );
422   DISABLE_GUESTFS_ERRORS_FOR (
423     str = guestfs_inspect_get_windows_current_control_set (g, root);
424     if (str)
425       XMLERROR (-1,
426                 xmlTextWriterWriteElement (xo, BAD_CAST "windows_current_control_set",
427                                            BAD_CAST str));
428     free (str);
429   );
430
431   str = guestfs_inspect_get_hostname (g, root);
432   if (!str) exit (EXIT_FAILURE);
433   if (STRNEQ (str, "unknown"))
434     XMLERROR (-1,
435       xmlTextWriterWriteElement (xo, BAD_CAST "hostname",
436                                  BAD_CAST str));
437   free (str);
438
439   str = guestfs_inspect_get_format (g, root);
440   if (!str) exit (EXIT_FAILURE);
441   if (STRNEQ (str, "unknown"))
442     XMLERROR (-1,
443       xmlTextWriterWriteElement (xo, BAD_CAST "format",
444                                  BAD_CAST str));
445   free (str);
446
447   r = guestfs_inspect_is_live (g, root);
448   if (r > 0) {
449     XMLERROR (-1,
450               xmlTextWriterStartElement (xo, BAD_CAST "live"));
451     XMLERROR (-1, xmlTextWriterEndElement (xo));
452   }
453
454   r = guestfs_inspect_is_netinst (g, root);
455   if (r > 0) {
456     XMLERROR (-1,
457               xmlTextWriterStartElement (xo, BAD_CAST "netinst"));
458     XMLERROR (-1, xmlTextWriterEndElement (xo));
459   }
460
461   r = guestfs_inspect_is_multipart (g, root);
462   if (r > 0) {
463     XMLERROR (-1,
464               xmlTextWriterStartElement (xo, BAD_CAST "multipart"));
465     XMLERROR (-1, xmlTextWriterEndElement (xo));
466   }
467
468   output_mountpoints (xo, root);
469
470   output_filesystems (xo, root);
471
472   output_drive_mappings (xo, root);
473
474   output_applications (xo, root);
475
476   XMLERROR (-1, xmlTextWriterEndElement (xo));
477 }
478
479 static int
480 compare_keys (const void *p1, const void *p2)
481 {
482   const char *key1 = * (char * const *) p1;
483   const char *key2 = * (char * const *) p2;
484
485   return strcmp (key1, key2);
486 }
487
488 static int
489 compare_keys_nocase (const void *p1, const void *p2)
490 {
491   const char *key1 = * (char * const *) p1;
492   const char *key2 = * (char * const *) p2;
493
494   return strcasecmp (key1, key2);
495 }
496
497 static int
498 compare_keys_len (const void *p1, const void *p2)
499 {
500   const char *key1 = * (char * const *) p1;
501   const char *key2 = * (char * const *) p2;
502   int c;
503
504   c = strlen (key1) - strlen (key2);
505   if (c != 0)
506     return c;
507
508   return compare_keys (p1, p2);
509 }
510
511 static void
512 output_mountpoints (xmlTextWriterPtr xo, char *root)
513 {
514   char **mountpoints;
515   size_t i;
516
517   mountpoints = guestfs_inspect_get_mountpoints (g, root);
518   if (mountpoints == NULL)
519     exit (EXIT_FAILURE);
520
521   /* Sort by key length, shortest key first, and then name, so the
522    * output is stable.
523    */
524   qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
525          compare_keys_len);
526
527   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "mountpoints"));
528
529   for (i = 0; mountpoints[i] != NULL; i += 2) {
530     canonicalize (mountpoints[i+1]);
531
532     XMLERROR (-1,
533               xmlTextWriterStartElement (xo, BAD_CAST "mountpoint"));
534     XMLERROR (-1,
535               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
536                                            BAD_CAST mountpoints[i+1]));
537     XMLERROR (-1,
538               xmlTextWriterWriteString (xo, BAD_CAST mountpoints[i]));
539     XMLERROR (-1, xmlTextWriterEndElement (xo));
540   }
541
542   XMLERROR (-1, xmlTextWriterEndElement (xo));
543
544   free_strings (mountpoints);
545 }
546
547 static void
548 output_filesystems (xmlTextWriterPtr xo, char *root)
549 {
550   char **filesystems;
551   char *str;
552   size_t i;
553
554   filesystems = guestfs_inspect_get_filesystems (g, root);
555   if (filesystems == NULL)
556     exit (EXIT_FAILURE);
557
558   /* Sort by name so the output is stable. */
559   qsort (filesystems, count_strings (filesystems), sizeof (char *),
560          compare_keys);
561
562   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystems"));
563
564   for (i = 0; filesystems[i] != NULL; ++i) {
565     canonicalize (filesystems[i]);
566
567     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystem"));
568     XMLERROR (-1,
569               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
570                                            BAD_CAST filesystems[i]));
571
572     DISABLE_GUESTFS_ERRORS_FOR (
573       str = guestfs_vfs_type (g, filesystems[i]);
574       if (str && str[0])
575         XMLERROR (-1,
576                   xmlTextWriterWriteElement (xo, BAD_CAST "type",
577                                              BAD_CAST str));
578       free (str);
579     );
580
581     DISABLE_GUESTFS_ERRORS_FOR (
582       str = guestfs_vfs_label (g, filesystems[i]);
583       if (str && str[0])
584         XMLERROR (-1,
585                   xmlTextWriterWriteElement (xo, BAD_CAST "label",
586                                              BAD_CAST str));
587       free (str);
588     );
589
590     DISABLE_GUESTFS_ERRORS_FOR (
591       str = guestfs_vfs_uuid (g, filesystems[i]);
592       if (str && str[0])
593         XMLERROR (-1,
594                   xmlTextWriterWriteElement (xo, BAD_CAST "uuid",
595                                              BAD_CAST str));
596       free (str);
597     );
598
599     XMLERROR (-1, xmlTextWriterEndElement (xo));
600   }
601
602   XMLERROR (-1, xmlTextWriterEndElement (xo));
603
604   free_strings (filesystems);
605 }
606
607 static void
608 output_drive_mappings (xmlTextWriterPtr xo, char *root)
609 {
610   char **drive_mappings = NULL;
611   size_t i;
612
613   DISABLE_GUESTFS_ERRORS_FOR (
614     drive_mappings = guestfs_inspect_get_drive_mappings (g, root);
615   );
616   if (drive_mappings == NULL)
617     return;
618
619   if (drive_mappings[0] == NULL) {
620     free_strings (drive_mappings);
621     return;
622   }
623
624   /* Sort by key. */
625   qsort (drive_mappings,
626          count_strings (drive_mappings) / 2, 2 * sizeof (char *),
627          compare_keys_nocase);
628
629   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "drive_mappings"));
630
631   for (i = 0; drive_mappings[i] != NULL; i += 2) {
632     canonicalize (drive_mappings[i+1]);
633
634     XMLERROR (-1,
635               xmlTextWriterStartElement (xo, BAD_CAST "drive_mapping"));
636     XMLERROR (-1,
637               xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
638                                            BAD_CAST drive_mappings[i]));
639     XMLERROR (-1,
640               xmlTextWriterWriteString (xo, BAD_CAST drive_mappings[i+1]));
641     XMLERROR (-1, xmlTextWriterEndElement (xo));
642   }
643
644   XMLERROR (-1, xmlTextWriterEndElement (xo));
645
646   free_strings (drive_mappings);
647 }
648
649 static void
650 output_applications (xmlTextWriterPtr xo, char *root)
651 {
652   struct guestfs_application_list *apps;
653   size_t i;
654
655   /* We need to mount everything up in order to read out the list of
656    * applications.
657    */
658   inspect_mount_root (root);
659
660   /* This returns an empty list if we simply couldn't determine the
661    * applications, so if it returns NULL then it's a real error.
662    */
663   apps = guestfs_inspect_list_applications (g, root);
664   if (apps == NULL)
665     exit (EXIT_FAILURE);
666   if (guestfs_umount_all (g) == -1)
667     exit (EXIT_FAILURE);
668
669   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));
670
671   for (i = 0; i < apps->len; ++i) {
672     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "application"));
673
674     assert (apps->val[i].app_name && apps->val[i].app_name[0]);
675     XMLERROR (-1,
676               xmlTextWriterWriteElement (xo, BAD_CAST "name",
677                                          BAD_CAST apps->val[i].app_name));
678
679     if (apps->val[i].app_display_name && apps->val[i].app_display_name[0])
680       XMLERROR (-1,
681         xmlTextWriterWriteElement (xo, BAD_CAST "display_name",
682                                    BAD_CAST apps->val[i].app_display_name));
683
684     if (apps->val[i].app_epoch != 0) {
685       char buf[32];
686
687       snprintf (buf, sizeof buf, "%d", apps->val[i].app_epoch);
688
689       XMLERROR (-1,
690         xmlTextWriterWriteElement (xo, BAD_CAST "epoch", BAD_CAST buf));
691     }
692
693     if (apps->val[i].app_version && apps->val[i].app_version[0])
694       XMLERROR (-1,
695         xmlTextWriterWriteElement (xo, BAD_CAST "version",
696                                    BAD_CAST apps->val[i].app_version));
697     if (apps->val[i].app_release && apps->val[i].app_release[0])
698       XMLERROR (-1,
699         xmlTextWriterWriteElement (xo, BAD_CAST "release",
700                                    BAD_CAST apps->val[i].app_release));
701     if (apps->val[i].app_install_path && apps->val[i].app_install_path[0])
702       XMLERROR (-1,
703         xmlTextWriterWriteElement (xo, BAD_CAST "install_path",
704                                    BAD_CAST apps->val[i].app_install_path));
705     if (apps->val[i].app_publisher && apps->val[i].app_publisher[0])
706       XMLERROR (-1,
707         xmlTextWriterWriteElement (xo, BAD_CAST "publisher",
708                                    BAD_CAST apps->val[i].app_publisher));
709     if (apps->val[i].app_url && apps->val[i].app_url[0])
710       XMLERROR (-1,
711         xmlTextWriterWriteElement (xo, BAD_CAST "url",
712                                    BAD_CAST apps->val[i].app_url));
713     if (apps->val[i].app_source_package && apps->val[i].app_source_package[0])
714       XMLERROR (-1,
715         xmlTextWriterWriteElement (xo, BAD_CAST "source_package",
716                                    BAD_CAST apps->val[i].app_source_package));
717     if (apps->val[i].app_summary && apps->val[i].app_summary[0])
718       XMLERROR (-1,
719         xmlTextWriterWriteElement (xo, BAD_CAST "summary",
720                                    BAD_CAST apps->val[i].app_summary));
721     if (apps->val[i].app_description && apps->val[i].app_description[0])
722       XMLERROR (-1,
723         xmlTextWriterWriteElement (xo, BAD_CAST "description",
724                                    BAD_CAST apps->val[i].app_description));
725
726     XMLERROR (-1, xmlTextWriterEndElement (xo));
727   }
728
729   XMLERROR (-1, xmlTextWriterEndElement (xo));
730
731   guestfs_free_application_list (apps);
732 }
733
734 /* "/dev/vda1" -> "/dev/sda1"
735  * See BLOCK DEVICE NAMING in guestfs(3).
736  */
737 static void
738 canonicalize (char *dev)
739 {
740   if (STRPREFIX (dev, "/dev/") &&
741       (dev[5] == 'h' || dev[5] == 'v') &&
742       dev[6] == 'd' &&
743       c_isalpha (dev[7]) &&
744       (c_isdigit (dev[8]) || dev[8] == '\0'))
745     dev[5] = 's';
746 }
747
748 static void
749 free_strings (char **argv)
750 {
751   int argc;
752
753   for (argc = 0; argv[argc] != NULL; ++argc)
754     free (argv[argc]);
755   free (argv);
756 }
757
758 static int
759 count_strings (char *const *argv)
760 {
761   int c;
762
763   for (c = 0; argv[c]; ++c)
764     ;
765   return c;
766 }