Include string.h and libintl.h, as needed.
[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_format (g, root);
432   if (!str) exit (EXIT_FAILURE);
433   if (STRNEQ (str, "unknown"))
434     XMLERROR (-1,
435       xmlTextWriterWriteElement (xo, BAD_CAST "format",
436                                  BAD_CAST str));
437   free (str);
438
439   r = guestfs_inspect_is_live (g, root);
440   if (r > 0) {
441     XMLERROR (-1,
442               xmlTextWriterStartElement (xo, BAD_CAST "live"));
443     XMLERROR (-1, xmlTextWriterEndElement (xo));
444   }
445
446   r = guestfs_inspect_is_netinst (g, root);
447   if (r > 0) {
448     XMLERROR (-1,
449               xmlTextWriterStartElement (xo, BAD_CAST "netinst"));
450     XMLERROR (-1, xmlTextWriterEndElement (xo));
451   }
452
453   r = guestfs_inspect_is_multipart (g, root);
454   if (r > 0) {
455     XMLERROR (-1,
456               xmlTextWriterStartElement (xo, BAD_CAST "multipart"));
457     XMLERROR (-1, xmlTextWriterEndElement (xo));
458   }
459
460   output_mountpoints (xo, root);
461
462   output_filesystems (xo, root);
463
464   output_drive_mappings (xo, root);
465
466   output_applications (xo, root);
467
468   XMLERROR (-1, xmlTextWriterEndElement (xo));
469 }
470
471 static int
472 compare_keys (const void *p1, const void *p2)
473 {
474   const char *key1 = * (char * const *) p1;
475   const char *key2 = * (char * const *) p2;
476
477   return strcmp (key1, key2);
478 }
479
480 static int
481 compare_keys_nocase (const void *p1, const void *p2)
482 {
483   const char *key1 = * (char * const *) p1;
484   const char *key2 = * (char * const *) p2;
485
486   return strcasecmp (key1, key2);
487 }
488
489 static int
490 compare_keys_len (const void *p1, const void *p2)
491 {
492   const char *key1 = * (char * const *) p1;
493   const char *key2 = * (char * const *) p2;
494   int c;
495
496   c = strlen (key1) - strlen (key2);
497   if (c != 0)
498     return c;
499
500   return compare_keys (p1, p2);
501 }
502
503 static void
504 output_mountpoints (xmlTextWriterPtr xo, char *root)
505 {
506   char **mountpoints;
507   size_t i;
508
509   mountpoints = guestfs_inspect_get_mountpoints (g, root);
510   if (mountpoints == NULL)
511     exit (EXIT_FAILURE);
512
513   /* Sort by key length, shortest key first, and then name, so the
514    * output is stable.
515    */
516   qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *),
517          compare_keys_len);
518
519   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "mountpoints"));
520
521   for (i = 0; mountpoints[i] != NULL; i += 2) {
522     canonicalize (mountpoints[i+1]);
523
524     XMLERROR (-1,
525               xmlTextWriterStartElement (xo, BAD_CAST "mountpoint"));
526     XMLERROR (-1,
527               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
528                                            BAD_CAST mountpoints[i+1]));
529     XMLERROR (-1,
530               xmlTextWriterWriteString (xo, BAD_CAST mountpoints[i]));
531     XMLERROR (-1, xmlTextWriterEndElement (xo));
532   }
533
534   XMLERROR (-1, xmlTextWriterEndElement (xo));
535
536   free_strings (mountpoints);
537 }
538
539 static void
540 output_filesystems (xmlTextWriterPtr xo, char *root)
541 {
542   char **filesystems;
543   char *str;
544   size_t i;
545
546   filesystems = guestfs_inspect_get_filesystems (g, root);
547   if (filesystems == NULL)
548     exit (EXIT_FAILURE);
549
550   /* Sort by name so the output is stable. */
551   qsort (filesystems, count_strings (filesystems), sizeof (char *),
552          compare_keys);
553
554   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystems"));
555
556   for (i = 0; filesystems[i] != NULL; ++i) {
557     canonicalize (filesystems[i]);
558
559     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "filesystem"));
560     XMLERROR (-1,
561               xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
562                                            BAD_CAST filesystems[i]));
563
564     DISABLE_GUESTFS_ERRORS_FOR (
565       str = guestfs_vfs_type (g, filesystems[i]);
566       if (str && str[0])
567         XMLERROR (-1,
568                   xmlTextWriterWriteElement (xo, BAD_CAST "type",
569                                              BAD_CAST str));
570       free (str);
571     );
572
573     DISABLE_GUESTFS_ERRORS_FOR (
574       str = guestfs_vfs_label (g, filesystems[i]);
575       if (str && str[0])
576         XMLERROR (-1,
577                   xmlTextWriterWriteElement (xo, BAD_CAST "label",
578                                              BAD_CAST str));
579       free (str);
580     );
581
582     DISABLE_GUESTFS_ERRORS_FOR (
583       str = guestfs_vfs_uuid (g, filesystems[i]);
584       if (str && str[0])
585         XMLERROR (-1,
586                   xmlTextWriterWriteElement (xo, BAD_CAST "uuid",
587                                              BAD_CAST str));
588       free (str);
589     );
590
591     XMLERROR (-1, xmlTextWriterEndElement (xo));
592   }
593
594   XMLERROR (-1, xmlTextWriterEndElement (xo));
595
596   free_strings (filesystems);
597 }
598
599 static void
600 output_drive_mappings (xmlTextWriterPtr xo, char *root)
601 {
602   char **drive_mappings = NULL;
603   size_t i;
604
605   DISABLE_GUESTFS_ERRORS_FOR (
606     drive_mappings = guestfs_inspect_get_drive_mappings (g, root);
607   );
608   if (drive_mappings == NULL)
609     return;
610
611   if (drive_mappings[0] == NULL) {
612     free_strings (drive_mappings);
613     return;
614   }
615
616   /* Sort by key. */
617   qsort (drive_mappings,
618          count_strings (drive_mappings) / 2, 2 * sizeof (char *),
619          compare_keys_nocase);
620
621   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "drive_mappings"));
622
623   for (i = 0; drive_mappings[i] != NULL; i += 2) {
624     canonicalize (drive_mappings[i+1]);
625
626     XMLERROR (-1,
627               xmlTextWriterStartElement (xo, BAD_CAST "drive_mapping"));
628     XMLERROR (-1,
629               xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
630                                            BAD_CAST drive_mappings[i]));
631     XMLERROR (-1,
632               xmlTextWriterWriteString (xo, BAD_CAST drive_mappings[i+1]));
633     XMLERROR (-1, xmlTextWriterEndElement (xo));
634   }
635
636   XMLERROR (-1, xmlTextWriterEndElement (xo));
637
638   free_strings (drive_mappings);
639 }
640
641 static void
642 output_applications (xmlTextWriterPtr xo, char *root)
643 {
644   struct guestfs_application_list *apps;
645   size_t i;
646
647   /* We need to mount everything up in order to read out the list of
648    * applications.
649    */
650   inspect_mount_root (root);
651
652   /* This returns an empty list if we simply couldn't determine the
653    * applications, so if it returns NULL then it's a real error.
654    */
655   apps = guestfs_inspect_list_applications (g, root);
656   if (apps == NULL)
657     exit (EXIT_FAILURE);
658   if (guestfs_umount_all (g) == -1)
659     exit (EXIT_FAILURE);
660
661   XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "applications"));
662
663   for (i = 0; i < apps->len; ++i) {
664     XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "application"));
665
666     assert (apps->val[i].app_name && apps->val[i].app_name[0]);
667     XMLERROR (-1,
668               xmlTextWriterWriteElement (xo, BAD_CAST "name",
669                                          BAD_CAST apps->val[i].app_name));
670
671     if (apps->val[i].app_display_name && apps->val[i].app_display_name[0])
672       XMLERROR (-1,
673         xmlTextWriterWriteElement (xo, BAD_CAST "display_name",
674                                    BAD_CAST apps->val[i].app_display_name));
675
676     if (apps->val[i].app_epoch != 0) {
677       char buf[32];
678
679       snprintf (buf, sizeof buf, "%d", apps->val[i].app_epoch);
680
681       XMLERROR (-1,
682         xmlTextWriterWriteElement (xo, BAD_CAST "epoch", BAD_CAST buf));
683     }
684
685     if (apps->val[i].app_version && apps->val[i].app_version[0])
686       XMLERROR (-1,
687         xmlTextWriterWriteElement (xo, BAD_CAST "version",
688                                    BAD_CAST apps->val[i].app_version));
689     if (apps->val[i].app_release && apps->val[i].app_release[0])
690       XMLERROR (-1,
691         xmlTextWriterWriteElement (xo, BAD_CAST "release",
692                                    BAD_CAST apps->val[i].app_release));
693     if (apps->val[i].app_install_path && apps->val[i].app_install_path[0])
694       XMLERROR (-1,
695         xmlTextWriterWriteElement (xo, BAD_CAST "install_path",
696                                    BAD_CAST apps->val[i].app_install_path));
697     if (apps->val[i].app_publisher && apps->val[i].app_publisher[0])
698       XMLERROR (-1,
699         xmlTextWriterWriteElement (xo, BAD_CAST "publisher",
700                                    BAD_CAST apps->val[i].app_publisher));
701     if (apps->val[i].app_url && apps->val[i].app_url[0])
702       XMLERROR (-1,
703         xmlTextWriterWriteElement (xo, BAD_CAST "url",
704                                    BAD_CAST apps->val[i].app_url));
705     if (apps->val[i].app_source_package && apps->val[i].app_source_package[0])
706       XMLERROR (-1,
707         xmlTextWriterWriteElement (xo, BAD_CAST "source_package",
708                                    BAD_CAST apps->val[i].app_source_package));
709     if (apps->val[i].app_summary && apps->val[i].app_summary[0])
710       XMLERROR (-1,
711         xmlTextWriterWriteElement (xo, BAD_CAST "summary",
712                                    BAD_CAST apps->val[i].app_summary));
713     if (apps->val[i].app_description && apps->val[i].app_description[0])
714       XMLERROR (-1,
715         xmlTextWriterWriteElement (xo, BAD_CAST "description",
716                                    BAD_CAST apps->val[i].app_description));
717
718     XMLERROR (-1, xmlTextWriterEndElement (xo));
719   }
720
721   XMLERROR (-1, xmlTextWriterEndElement (xo));
722
723   guestfs_free_application_list (apps);
724 }
725
726 /* "/dev/vda1" -> "/dev/sda1"
727  * See BLOCK DEVICE NAMING in guestfs(3).
728  */
729 static void
730 canonicalize (char *dev)
731 {
732   if (STRPREFIX (dev, "/dev/") &&
733       (dev[5] == 'h' || dev[5] == 'v') &&
734       dev[6] == 'd' &&
735       c_isalpha (dev[7]) &&
736       (c_isdigit (dev[8]) || dev[8] == '\0'))
737     dev[5] = 's';
738 }
739
740 static void
741 free_strings (char **argv)
742 {
743   int argc;
744
745   for (argc = 0; argv[argc] != NULL; ++argc)
746     free (argv[argc]);
747   free (argv);
748 }
749
750 static int
751 count_strings (char *const *argv)
752 {
753   int c;
754
755   for (c = 0; argv[c]; ++c)
756     ;
757   return c;
758 }