inspect: Optimize root filesystem check.
[libguestfs.git] / src / inspect.c
1 /* libguestfs
2  * Copyright (C) 2010 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library 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 GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18
19 #include <config.h>
20
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <stdint.h>
24 #include <inttypes.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <sys/stat.h>
28
29 #ifdef HAVE_PCRE
30 #include <pcre.h>
31 #endif
32
33 #ifdef HAVE_HIVEX
34 #include <hivex.h>
35 #endif
36
37 #include "c-ctype.h"
38 #include "ignore-value.h"
39 #include "xstrtol.h"
40
41 #include "guestfs.h"
42 #include "guestfs-internal.h"
43 #include "guestfs-internal-actions.h"
44 #include "guestfs_protocol.h"
45
46 #if defined(HAVE_PCRE) && defined(HAVE_HIVEX)
47
48 /* Compile all the regular expressions once when the shared library is
49  * loaded.  PCRE is thread safe so we're supposedly OK here if
50  * multiple threads call into the libguestfs API functions below
51  * simultaneously.
52  */
53 static pcre *re_fedora;
54 static pcre *re_rhel_old;
55 static pcre *re_rhel;
56 static pcre *re_rhel_no_minor;
57 static pcre *re_major_minor;
58 static pcre *re_aug_seq;
59 static pcre *re_xdev;
60 static pcre *re_windows_version;
61
62 static void compile_regexps (void) __attribute__((constructor));
63 static void free_regexps (void) __attribute__((destructor));
64
65 static void
66 compile_regexps (void)
67 {
68   const char *err;
69   int offset;
70
71 #define COMPILE(re,pattern,options)                                     \
72   do {                                                                  \
73     re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
74     if (re == NULL) {                                                   \
75       ignore_value (write (2, err, strlen (err)));                      \
76       abort ();                                                         \
77     }                                                                   \
78   } while (0)
79
80   COMPILE (re_fedora, "Fedora release (\\d+)", 0);
81   COMPILE (re_rhel_old,
82            "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+).*Update (\\d+)", 0);
83   COMPILE (re_rhel,
84            "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0);
85   COMPILE (re_rhel_no_minor,
86            "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)", 0);
87   COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0);
88   COMPILE (re_aug_seq, "/\\d+$", 0);
89   COMPILE (re_xdev, "^/dev/(?:h|s|v|xv)d([a-z]\\d*)$", 0);
90   COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0);
91 }
92
93 static void
94 free_regexps (void)
95 {
96   pcre_free (re_fedora);
97   pcre_free (re_rhel_old);
98   pcre_free (re_rhel);
99   pcre_free (re_rhel_no_minor);
100   pcre_free (re_major_minor);
101   pcre_free (re_aug_seq);
102   pcre_free (re_xdev);
103   pcre_free (re_windows_version);
104 }
105
106 /* The main inspection code. */
107 static int check_for_filesystem_on (guestfs_h *g, const char *device);
108
109 char **
110 guestfs__inspect_os (guestfs_h *g)
111 {
112   /* Remove any information previously stored in the handle. */
113   guestfs___free_inspect_info (g);
114
115   if (guestfs_umount_all (g) == -1)
116     return NULL;
117
118   /* Iterate over all possible devices.  Try to mount each
119    * (read-only).  Examine ones which contain filesystems and add that
120    * information to the handle.
121    */
122   /* Look to see if any devices directly contain filesystems (RHBZ#590167). */
123   char **devices;
124   devices = guestfs_list_devices (g);
125   if (devices == NULL)
126     return NULL;
127
128   size_t i;
129   for (i = 0; devices[i] != NULL; ++i) {
130     if (check_for_filesystem_on (g, devices[i]) == -1) {
131       guestfs___free_string_list (devices);
132       guestfs___free_inspect_info (g);
133       return NULL;
134     }
135   }
136   guestfs___free_string_list (devices);
137
138   /* Look at all partitions. */
139   char **partitions;
140   partitions = guestfs_list_partitions (g);
141   if (partitions == NULL) {
142     guestfs___free_inspect_info (g);
143     return NULL;
144   }
145
146   for (i = 0; partitions[i] != NULL; ++i) {
147     if (check_for_filesystem_on (g, partitions[i]) == -1) {
148       guestfs___free_string_list (partitions);
149       guestfs___free_inspect_info (g);
150       return NULL;
151     }
152   }
153   guestfs___free_string_list (partitions);
154
155   /* Look at all LVs. */
156   if (guestfs___feature_available (g, "lvm2")) {
157     char **lvs;
158     lvs = guestfs_lvs (g);
159     if (lvs == NULL) {
160       guestfs___free_inspect_info (g);
161       return NULL;
162     }
163
164     for (i = 0; lvs[i] != NULL; ++i) {
165       if (check_for_filesystem_on (g, lvs[i]) == -1) {
166         guestfs___free_string_list (lvs);
167         guestfs___free_inspect_info (g);
168         return NULL;
169       }
170     }
171     guestfs___free_string_list (lvs);
172   }
173
174   /* At this point we have, in the handle, a list of all filesystems
175    * found and data about each one.  Now we assemble the list of
176    * filesystems which are root devices and return that to the user.
177    * Fall through to guestfs__inspect_get_roots to do that.
178    */
179   char **ret = guestfs__inspect_get_roots (g);
180   if (ret == NULL)
181     guestfs___free_inspect_info (g);
182   return ret;
183 }
184
185 /* Find out if 'device' contains a filesystem.  If it does, add
186  * another entry in g->fses.
187  */
188 static int check_filesystem (guestfs_h *g, const char *device);
189 static int check_linux_root (guestfs_h *g, struct inspect_fs *fs);
190 static int check_fstab (guestfs_h *g, struct inspect_fs *fs);
191 static int check_windows_root (guestfs_h *g, struct inspect_fs *fs);
192 static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs);
193 static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs);
194 static char *resolve_windows_path_silently (guestfs_h *g, const char *);
195 static int extend_fses (guestfs_h *g);
196 static int parse_unsigned_int (guestfs_h *g, const char *str);
197 static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
198                             const char *spec, const char *mp);
199 static char *resolve_fstab_device (guestfs_h *g, const char *spec);
200 static void check_package_format (guestfs_h *g, struct inspect_fs *fs);
201 static void check_package_management (guestfs_h *g, struct inspect_fs *fs);
202
203 static int
204 check_for_filesystem_on (guestfs_h *g, const char *device)
205 {
206   /* Get vfs-type in order to check if it's a Linux(?) swap device.
207    * If there's an error we should ignore it, so to do that we have to
208    * temporarily replace the error handler with a null one.
209    */
210   guestfs_error_handler_cb old_error_cb = g->error_cb;
211   g->error_cb = NULL;
212   char *vfs_type = guestfs_vfs_type (g, device);
213   g->error_cb = old_error_cb;
214
215   int is_swap = vfs_type && STREQ (vfs_type, "swap");
216
217   if (g->verbose)
218     fprintf (stderr, "check_for_filesystem_on: %s (%s)\n",
219              device, vfs_type ? vfs_type : "failed to get vfs type");
220
221   if (is_swap) {
222     free (vfs_type);
223     if (extend_fses (g) == -1)
224       return -1;
225     g->fses[g->nr_fses-1].is_swap = 1;
226     return 0;
227   }
228
229   /* Try mounting the device.  As above, ignore errors. */
230   g->error_cb = NULL;
231   int r = guestfs_mount_ro (g, device, "/");
232   if (r == -1 && vfs_type && STREQ (vfs_type, "ufs")) /* Hack for the *BSDs. */
233     r = guestfs_mount_vfs (g, "ro,ufstype=ufs2", "ufs", device, "/");
234   free (vfs_type);
235   g->error_cb = old_error_cb;
236   if (r == -1)
237     return 0;
238
239   /* Do the rest of the checks. */
240   r = check_filesystem (g, device);
241
242   /* Unmount the filesystem. */
243   if (guestfs_umount_all (g) == -1)
244     return -1;
245
246   return r;
247 }
248
249 static int
250 check_filesystem (guestfs_h *g, const char *device)
251 {
252   if (extend_fses (g) == -1)
253     return -1;
254
255   struct inspect_fs *fs = &g->fses[g->nr_fses-1];
256
257   fs->device = safe_strdup (g, device);
258   fs->is_mountable = 1;
259
260   /* Optimize some of the tests by avoiding multiple tests of the same thing. */
261   int is_dir_etc = guestfs_is_dir (g, "/etc") > 0;
262   int is_dir_bin = guestfs_is_dir (g, "/bin") > 0;
263   int is_dir_share = guestfs_is_dir (g, "/share") > 0;
264
265   /* Grub /boot? */
266   if (guestfs_is_file (g, "/grub/menu.lst") > 0 ||
267       guestfs_is_file (g, "/grub/grub.conf") > 0)
268     fs->content = FS_CONTENT_LINUX_BOOT;
269   /* Linux root? */
270   else if (is_dir_etc &&
271            is_dir_bin &&
272            guestfs_is_file (g, "/etc/fstab") > 0) {
273     fs->is_root = 1;
274     fs->content = FS_CONTENT_LINUX_ROOT;
275     if (check_linux_root (g, fs) == -1)
276       return -1;
277   }
278   /* Linux /usr/local? */
279   else if (is_dir_etc &&
280            is_dir_bin &&
281            is_dir_share &&
282            guestfs_exists (g, "/local") == 0 &&
283            guestfs_is_file (g, "/etc/fstab") == 0)
284     fs->content = FS_CONTENT_LINUX_USR_LOCAL;
285   /* Linux /usr? */
286   else if (is_dir_etc &&
287            is_dir_bin &&
288            is_dir_share &&
289            guestfs_exists (g, "/local") > 0 &&
290            guestfs_is_file (g, "/etc/fstab") == 0)
291     fs->content = FS_CONTENT_LINUX_USR;
292   /* Linux /var? */
293   else if (guestfs_is_dir (g, "/log") > 0 &&
294            guestfs_is_dir (g, "/run") > 0 &&
295            guestfs_is_dir (g, "/spool") > 0)
296     fs->content = FS_CONTENT_LINUX_VAR;
297   /* Windows root? */
298   else if (guestfs_is_file (g, "/AUTOEXEC.BAT") > 0 ||
299            guestfs_is_file (g, "/autoexec.bat") > 0 ||
300            guestfs_is_dir (g, "/Program Files") > 0 ||
301            guestfs_is_dir (g, "/WINDOWS") > 0 ||
302            guestfs_is_dir (g, "/Windows") > 0 ||
303            guestfs_is_dir (g, "/windows") > 0 ||
304            guestfs_is_dir (g, "/WIN32") > 0 ||
305            guestfs_is_dir (g, "/Win32") > 0 ||
306            guestfs_is_dir (g, "/WINNT") > 0 ||
307            guestfs_is_file (g, "/boot.ini") > 0 ||
308            guestfs_is_file (g, "/ntldr") > 0) {
309     fs->is_root = 1;
310     fs->content = FS_CONTENT_WINDOWS_ROOT;
311     if (check_windows_root (g, fs) == -1)
312       return -1;
313   }
314
315   return 0;
316 }
317
318 /* Set fs->product_name to the first line of the release file. */
319 static int
320 parse_release_file (guestfs_h *g, struct inspect_fs *fs,
321                     const char *release_filename)
322 {
323   char **product_name = guestfs_head_n (g, 1, release_filename);
324   if (product_name == NULL)
325     return -1;
326   if (product_name[0] == NULL) {
327     error (g, "%s: file is empty", release_filename);
328     guestfs___free_string_list (product_name);
329     return -1;
330   }
331
332   /* Note that this string becomes owned by the handle and will
333    * be freed by guestfs___free_inspect_info.
334    */
335   fs->product_name = product_name[0];
336   free (product_name);
337
338   return 0;
339 }
340
341 /* Parse generic MAJOR.MINOR from the fs->product_name string. */
342 static int
343 parse_major_minor (guestfs_h *g, struct inspect_fs *fs)
344 {
345   char *major, *minor;
346
347   if (match2 (g, fs->product_name, re_major_minor, &major, &minor)) {
348     fs->major_version = parse_unsigned_int (g, major);
349     free (major);
350     if (fs->major_version == -1) {
351       free (minor);
352       return -1;
353     }
354     fs->minor_version = parse_unsigned_int (g, minor);
355     free (minor);
356     if (fs->minor_version == -1)
357       return -1;
358   }
359   return 0;
360 }
361
362 /* Ubuntu has /etc/lsb-release containing:
363  *   DISTRIB_ID=Ubuntu                                # Distro
364  *   DISTRIB_RELEASE=10.04                            # Version
365  *   DISTRIB_CODENAME=lucid
366  *   DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS"         # Product name
367  * In theory other distros could have this LSB file, but none do.
368  */
369 static int
370 parse_lsb_release (guestfs_h *g, struct inspect_fs *fs)
371 {
372   char **lines;
373   size_t i;
374   int r = 0;
375
376   lines = guestfs_head_n (g, 10, "/etc/lsb-release");
377   if (lines == NULL)
378     return -1;
379
380   for (i = 0; lines[i] != NULL; ++i) {
381     if (fs->distro == 0 &&
382         STREQ (lines[i], "DISTRIB_ID=Ubuntu")) {
383       fs->distro = OS_DISTRO_UBUNTU;
384       r = 1;
385     }
386     else if (STRPREFIX (lines[i], "DISTRIB_RELEASE=")) {
387       char *major, *minor;
388       if (match2 (g, &lines[i][16], re_major_minor, &major, &minor)) {
389         fs->major_version = parse_unsigned_int (g, major);
390         free (major);
391         if (fs->major_version == -1) {
392           free (minor);
393           guestfs___free_string_list (lines);
394           return -1;
395         }
396         fs->minor_version = parse_unsigned_int (g, minor);
397         free (minor);
398         if (fs->minor_version == -1) {
399           guestfs___free_string_list (lines);
400           return -1;
401         }
402       }
403     }
404     else if (fs->product_name == NULL &&
405              (STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=\"") ||
406               STRPREFIX (lines[i], "DISTRIB_DESCRIPTION='"))) {
407       size_t len = strlen (lines[i]) - 21 - 1;
408       fs->product_name = safe_strndup (g, &lines[i][21], len);
409       r = 1;
410     }
411     else if (fs->product_name == NULL &&
412              STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=")) {
413       size_t len = strlen (lines[i]) - 20;
414       fs->product_name = safe_strndup (g, &lines[i][20], len);
415       r = 1;
416     }
417   }
418
419   guestfs___free_string_list (lines);
420   return r;
421 }
422
423 /* The currently mounted device is known to be a Linux root.  Try to
424  * determine from this the distro, version, etc.  Also parse
425  * /etc/fstab to determine the arrangement of mountpoints and
426  * associated devices.
427  */
428 static int
429 check_linux_root (guestfs_h *g, struct inspect_fs *fs)
430 {
431   int r;
432
433   fs->type = OS_TYPE_LINUX;
434
435   if (guestfs_exists (g, "/etc/lsb-release") > 0) {
436     r = parse_lsb_release (g, fs);
437     if (r == -1)        /* error */
438       return -1;
439     if (r == 1)         /* ok - detected the release from this file */
440       goto skip_release_checks;
441   }
442
443   if (guestfs_exists (g, "/etc/redhat-release") > 0) {
444     fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */
445
446     if (parse_release_file (g, fs, "/etc/redhat-release") == -1)
447       return -1;
448
449     char *major, *minor;
450     if ((major = match1 (g, fs->product_name, re_fedora)) != NULL) {
451       fs->distro = OS_DISTRO_FEDORA;
452       fs->major_version = parse_unsigned_int (g, major);
453       free (major);
454       if (fs->major_version == -1)
455         return -1;
456     }
457     else if (match2 (g, fs->product_name, re_rhel_old, &major, &minor) ||
458              match2 (g, fs->product_name, re_rhel, &major, &minor)) {
459       fs->distro = OS_DISTRO_RHEL;
460       fs->major_version = parse_unsigned_int (g, major);
461       free (major);
462       if (fs->major_version == -1) {
463         free (minor);
464         return -1;
465       }
466       fs->minor_version = parse_unsigned_int (g, minor);
467       free (minor);
468       if (fs->minor_version == -1)
469         return -1;
470     }
471     else if ((major = match1 (g, fs->product_name, re_rhel_no_minor)) != NULL) {
472       fs->distro = OS_DISTRO_RHEL;
473       fs->major_version = parse_unsigned_int (g, major);
474       free (major);
475       if (fs->major_version == -1)
476         return -1;
477       fs->minor_version = 0;
478     }
479   }
480   else if (guestfs_exists (g, "/etc/debian_version") > 0) {
481     fs->distro = OS_DISTRO_DEBIAN;
482
483     if (parse_release_file (g, fs, "/etc/debian_version") == -1)
484       return -1;
485
486     if (parse_major_minor (g, fs) == -1)
487       return -1;
488   }
489   else if (guestfs_exists (g, "/etc/pardus-release") > 0) {
490     fs->distro = OS_DISTRO_PARDUS;
491
492     if (parse_release_file (g, fs, "/etc/pardus-release") == -1)
493       return -1;
494
495     if (parse_major_minor (g, fs) == -1)
496       return -1;
497   }
498   else if (guestfs_exists (g, "/etc/arch-release") > 0) {
499     fs->distro = OS_DISTRO_ARCHLINUX;
500
501     /* /etc/arch-release file is empty and I can't see a way to
502      * determine the actual release or product string.
503      */
504   }
505   else if (guestfs_exists (g, "/etc/gentoo-release") > 0) {
506     fs->distro = OS_DISTRO_GENTOO;
507
508     if (parse_release_file (g, fs, "/etc/gentoo-release") == -1)
509       return -1;
510
511     if (parse_major_minor (g, fs) == -1)
512       return -1;
513   }
514   else if (guestfs_exists (g, "/etc/meego-release") > 0) {
515     fs->distro = OS_DISTRO_MEEGO;
516
517     if (parse_release_file (g, fs, "/etc/meego-release") == -1)
518       return -1;
519
520     if (parse_major_minor (g, fs) == -1)
521       return -1;
522   }
523
524  skip_release_checks:;
525
526   /* If distro test above was successful, work out the package format. */
527   check_package_format (g, fs);
528   check_package_management (g, fs);
529
530   /* Determine the architecture. */
531   const char *binaries[] =
532     { "/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh" };
533   size_t i;
534   for (i = 0; i < sizeof binaries / sizeof binaries[0]; ++i) {
535     if (guestfs_is_file (g, binaries[i]) > 0) {
536       /* Ignore errors from file_architecture call. */
537       guestfs_error_handler_cb old_error_cb = g->error_cb;
538       g->error_cb = NULL;
539       char *arch = guestfs_file_architecture (g, binaries[i]);
540       g->error_cb = old_error_cb;
541
542       if (arch) {
543         /* String will be owned by handle, freed by
544          * guestfs___free_inspect_info.
545          */
546         fs->arch = arch;
547         break;
548       }
549     }
550   }
551
552   /* We already know /etc/fstab exists because it's part of the test
553    * for Linux root above.  We must now parse this file to determine
554    * which filesystems are used by the operating system and how they
555    * are mounted.
556    * XXX What if !feature_available (g, "augeas")?
557    */
558   if (guestfs_aug_init (g, "/", 16|32) == -1)
559     return -1;
560
561   /* Tell Augeas to only load /etc/fstab (thanks RaphaĆ«l Pinson). */
562   guestfs_aug_rm (g, "/augeas/load//incl[. != \"/etc/fstab\"]");
563   guestfs_aug_load (g);
564
565   r = check_fstab (g, fs);
566   guestfs_aug_close (g);
567   if (r == -1)
568     return -1;
569
570   return 0;
571 }
572
573 static int
574 check_fstab (guestfs_h *g, struct inspect_fs *fs)
575 {
576   char **lines = guestfs_aug_ls (g, "/files/etc/fstab");
577   if (lines == NULL)
578     return -1;
579
580   if (lines[0] == NULL) {
581     error (g, "could not parse /etc/fstab or empty file");
582     guestfs___free_string_list (lines);
583     return -1;
584   }
585
586   size_t i;
587   char augpath[256];
588   for (i = 0; lines[i] != NULL; ++i) {
589     /* Ignore comments.  Only care about sequence lines which
590      * match m{/\d+$}.
591      */
592     if (match (g, lines[i], re_aug_seq)) {
593       snprintf (augpath, sizeof augpath, "%s/spec", lines[i]);
594       char *spec = guestfs_aug_get (g, augpath);
595       if (spec == NULL) {
596         guestfs___free_string_list (lines);
597         return -1;
598       }
599
600       snprintf (augpath, sizeof augpath, "%s/file", lines[i]);
601       char *mp = guestfs_aug_get (g, augpath);
602       if (mp == NULL) {
603         guestfs___free_string_list (lines);
604         free (spec);
605         return -1;
606       }
607
608       int r = add_fstab_entry (g, fs, spec, mp);
609       free (spec);
610       free (mp);
611
612       if (r == -1) {
613         guestfs___free_string_list (lines);
614         return -1;
615       }
616     }
617   }
618
619   guestfs___free_string_list (lines);
620   return 0;
621 }
622
623 /* Add a filesystem and possibly a mountpoint entry for
624  * the root filesystem 'fs'.
625  *
626  * 'spec' is the fstab spec field, which might be a device name or a
627  * pseudodevice or 'UUID=...' or 'LABEL=...'.
628  *
629  * 'mp' is the mount point, which could also be 'swap' or 'none'.
630  */
631 static int
632 add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
633                  const char *spec, const char *mp)
634 {
635   /* Ignore certain mountpoints. */
636   if (STRPREFIX (mp, "/dev/") ||
637       STREQ (mp, "/dev") ||
638       STRPREFIX (mp, "/media/") ||
639       STRPREFIX (mp, "/proc/") ||
640       STREQ (mp, "/proc") ||
641       STRPREFIX (mp, "/selinux/") ||
642       STREQ (mp, "/selinux") ||
643       STRPREFIX (mp, "/sys/") ||
644       STREQ (mp, "/sys"))
645     return 0;
646
647   /* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives. */
648   if ((STRPREFIX (spec, "/dev/fd") && c_isdigit (spec[7])) ||
649       STREQ (spec, "/dev/floppy") ||
650       STREQ (spec, "/dev/cdrom"))
651     return 0;
652
653   /* Resolve UUID= and LABEL= to the actual device. */
654   char *device = NULL;
655   if (STRPREFIX (spec, "UUID="))
656     device = guestfs_findfs_uuid (g, &spec[5]);
657   else if (STRPREFIX (spec, "LABEL="))
658     device = guestfs_findfs_label (g, &spec[6]);
659   /* Ignore "/.swap" (Pardus) and pseudo-devices like "tmpfs". */
660   else if (STRPREFIX (spec, "/dev/"))
661     /* Resolve guest block device names. */
662     device = resolve_fstab_device (g, spec);
663
664   /* If we haven't resolved the device successfully by this point,
665    * we don't care, just ignore it.
666    */
667   if (device == NULL)
668     return 0;
669
670   char *mountpoint = safe_strdup (g, mp);
671
672   /* Add this to the fstab entry in 'fs'.
673    * Note these are further filtered by guestfs_inspect_get_mountpoints
674    * and guestfs_inspect_get_filesystems.
675    */
676   size_t n = fs->nr_fstab + 1;
677   struct inspect_fstab_entry *p;
678
679   p = realloc (fs->fstab, n * sizeof (struct inspect_fstab_entry));
680   if (p == NULL) {
681     perrorf (g, "realloc");
682     free (device);
683     free (mountpoint);
684     return -1;
685   }
686
687   fs->fstab = p;
688   fs->nr_fstab = n;
689
690   /* These are owned by the handle and freed by guestfs___free_inspect_info. */
691   fs->fstab[n-1].device = device;
692   fs->fstab[n-1].mountpoint = mountpoint;
693
694   if (g->verbose)
695     fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint);
696
697   return 0;
698 }
699
700 /* Resolve block device name to the libguestfs device name, eg.
701  * /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV.  This
702  * assumes that disks were added in the same order as they appear to
703  * the real VM, which is a reasonable assumption to make.  Return
704  * anything we don't recognize unchanged.
705  */
706 static char *
707 resolve_fstab_device (guestfs_h *g, const char *spec)
708 {
709   char *a1;
710   char *device = NULL;
711
712   if (STRPREFIX (spec, "/dev/mapper/")) {
713     /* LVM2 does some strange munging on /dev/mapper paths for VGs and
714      * LVs which contain '-' character:
715      *
716      * ><fs> lvcreate LV--test VG--test 32
717      * ><fs> debug ls /dev/mapper
718      * VG----test-LV----test
719      *
720      * This makes it impossible to reverse those paths directly, so
721      * we have implemented lvm_canonical_lv_name in the daemon.
722      */
723     device = guestfs_lvm_canonical_lv_name (g, spec);
724   }
725   else if ((a1 = match1 (g, spec, re_xdev)) != NULL) {
726     char **devices = guestfs_list_devices (g);
727     if (devices == NULL)
728       return NULL;
729
730     size_t count;
731     for (count = 0; devices[count] != NULL; count++)
732       ;
733
734     size_t i = a1[0] - 'a'; /* a1[0] is always [a-z] because of regex. */
735     if (i < count) {
736       size_t len = strlen (devices[i]) + strlen (a1) + 16;
737       device = safe_malloc (g, len);
738       snprintf (device, len, "%s%s", devices[i], &a1[1]);
739     }
740
741     free (a1);
742     guestfs___free_string_list (devices);
743   }
744   else {
745     /* Didn't match device pattern, return original spec unchanged. */
746     device = safe_strdup (g, spec);
747   }
748
749   return device;
750 }
751
752 /* XXX Handling of boot.ini in the Perl version was pretty broken.  It
753  * essentially didn't do anything for modern Windows guests.
754  * Therefore I've omitted all that code.
755  */
756 static int
757 check_windows_root (guestfs_h *g, struct inspect_fs *fs)
758 {
759   fs->type = OS_TYPE_WINDOWS;
760   fs->distro = OS_DISTRO_WINDOWS;
761
762   /* Try to find Windows systemroot using some common locations. */
763   const char *systemroots[] =
764     { "/windows", "/winnt", "/win32", "/win" };
765   size_t i;
766   char *systemroot = NULL;
767   for (i = 0;
768        systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0];
769        ++i) {
770     systemroot = resolve_windows_path_silently (g, systemroots[i]);
771   }
772
773   if (!systemroot) {
774     error (g, _("cannot resolve Windows %%SYSTEMROOT%%"));
775     return -1;
776   }
777
778   if (g->verbose)
779     fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot);
780
781   /* Freed by guestfs___free_inspect_info. */
782   fs->windows_systemroot = systemroot;
783
784   if (check_windows_arch (g, fs) == -1)
785     return -1;
786
787   if (check_windows_registry (g, fs) == -1)
788     return -1;
789
790   check_package_format (g, fs);
791   check_package_management (g, fs);
792
793   return 0;
794 }
795
796 static int
797 check_windows_arch (guestfs_h *g, struct inspect_fs *fs)
798 {
799   size_t len = strlen (fs->windows_systemroot) + 32;
800   char cmd_exe[len];
801   snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot);
802
803   char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe);
804   if (!cmd_exe_path)
805     return 0;
806
807   char *arch = guestfs_file_architecture (g, cmd_exe_path);
808   free (cmd_exe_path);
809
810   if (arch)
811     fs->arch = arch;        /* freed by guestfs___free_inspect_info */
812
813   return 0;
814 }
815
816 /* At the moment, pull just the ProductName and version numbers from
817  * the registry.  In future there is a case for making many more
818  * registry fields available to callers.
819  */
820 static int
821 check_windows_registry (guestfs_h *g, struct inspect_fs *fs)
822 {
823   TMP_TEMPLATE_ON_STACK (dir);
824 #define dir_len (strlen (dir))
825 #define software_hive_len (dir_len + 16)
826   char software_hive[software_hive_len];
827 #define cmd_len (dir_len + 16)
828   char cmd[cmd_len];
829
830   size_t len = strlen (fs->windows_systemroot) + 64;
831   char software[len];
832   snprintf (software, len, "%s/system32/config/software",
833             fs->windows_systemroot);
834
835   char *software_path = resolve_windows_path_silently (g, software);
836   if (!software_path)
837     /* If the software hive doesn't exist, just accept that we cannot
838      * find product_name etc.
839      */
840     return 0;
841
842   int ret = -1;
843   hive_h *h = NULL;
844   hive_value_h *values = NULL;
845
846   if (mkdtemp (dir) == NULL) {
847     perrorf (g, "mkdtemp");
848     goto out;
849   }
850
851   snprintf (software_hive, software_hive_len, "%s/software", dir);
852
853   if (guestfs_download (g, software_path, software_hive) == -1)
854     goto out;
855
856   h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
857   if (h == NULL) {
858     perrorf (g, "hivex_open");
859     goto out;
860   }
861
862   hive_node_h node = hivex_root (h);
863   const char *hivepath[] =
864     { "Microsoft", "Windows NT", "CurrentVersion" };
865   size_t i;
866   for (i = 0;
867        node != 0 && i < sizeof hivepath / sizeof hivepath[0];
868        ++i) {
869     node = hivex_node_get_child (h, node, hivepath[i]);
870   }
871
872   if (node == 0) {
873     perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion");
874     goto out;
875   }
876
877   values = hivex_node_values (h, node);
878
879   for (i = 0; values[i] != 0; ++i) {
880     char *key = hivex_value_key (h, values[i]);
881     if (key == NULL) {
882       perrorf (g, "hivex_value_key");
883       goto out;
884     }
885
886     if (STRCASEEQ (key, "ProductName")) {
887       fs->product_name = hivex_value_string (h, values[i]);
888       if (!fs->product_name) {
889         perrorf (g, "hivex_value_string");
890         free (key);
891         goto out;
892       }
893     }
894     else if (STRCASEEQ (key, "CurrentVersion")) {
895       char *version = hivex_value_string (h, values[i]);
896       if (!version) {
897         perrorf (g, "hivex_value_string");
898         free (key);
899         goto out;
900       }
901       char *major, *minor;
902       if (match2 (g, version, re_windows_version, &major, &minor)) {
903         fs->major_version = parse_unsigned_int (g, major);
904         free (major);
905         if (fs->major_version == -1) {
906           free (minor);
907           free (key);
908           free (version);
909           goto out;
910         }
911         fs->minor_version = parse_unsigned_int (g, minor);
912         free (minor);
913         if (fs->minor_version == -1) {
914           free (key);
915           free (version);
916           return -1;
917         }
918       }
919
920       free (version);
921     }
922
923     free (key);
924   }
925
926   ret = 0;
927
928  out:
929   if (h) hivex_close (h);
930   free (values);
931   free (software_path);
932
933   /* Free up the temporary directory.  Note the directory name cannot
934    * contain shell meta-characters because of the way it was
935    * constructed above.
936    */
937   snprintf (cmd, cmd_len, "rm -rf %s", dir);
938   ignore_value (system (cmd));
939 #undef dir_len
940 #undef software_hive_len
941 #undef cmd_len
942
943   return ret;
944 }
945
946 static char *
947 resolve_windows_path_silently (guestfs_h *g, const char *path)
948 {
949   guestfs_error_handler_cb old_error_cb = g->error_cb;
950   g->error_cb = NULL;
951   char *ret = guestfs_case_sensitive_path (g, path);
952   g->error_cb = old_error_cb;
953   return ret;
954 }
955
956 static int
957 extend_fses (guestfs_h *g)
958 {
959   size_t n = g->nr_fses + 1;
960   struct inspect_fs *p;
961
962   p = realloc (g->fses, n * sizeof (struct inspect_fs));
963   if (p == NULL) {
964     perrorf (g, "realloc");
965     return -1;
966   }
967
968   g->fses = p;
969   g->nr_fses = n;
970
971   memset (&g->fses[n-1], 0, sizeof (struct inspect_fs));
972
973   return 0;
974 }
975
976 /* Parse small, unsigned ints, as used in version numbers. */
977 static int
978 parse_unsigned_int (guestfs_h *g, const char *str)
979 {
980   long ret;
981   int r = xstrtol (str, NULL, 10, &ret, "");
982   if (r != LONGINT_OK) {
983     error (g, "could not parse integer in version number: %s", str);
984     return -1;
985   }
986   return ret;
987 }
988
989 /* At the moment, package format and package management is just a
990  * simple function of the distro and major_version fields, so these
991  * can never return an error.  We might be cleverer in future.
992  */
993 static void
994 check_package_format (guestfs_h *g, struct inspect_fs *fs)
995 {
996   switch (fs->distro) {
997   case OS_DISTRO_FEDORA:
998   case OS_DISTRO_MEEGO:
999   case OS_DISTRO_REDHAT_BASED:
1000   case OS_DISTRO_RHEL:
1001     fs->package_format = OS_PACKAGE_FORMAT_RPM;
1002     break;
1003
1004   case OS_DISTRO_DEBIAN:
1005   case OS_DISTRO_UBUNTU:
1006     fs->package_format = OS_PACKAGE_FORMAT_DEB;
1007     break;
1008
1009   case OS_DISTRO_ARCHLINUX:
1010     fs->package_format = OS_PACKAGE_FORMAT_PACMAN;
1011     break;
1012   case OS_DISTRO_GENTOO:
1013     fs->package_format = OS_PACKAGE_FORMAT_EBUILD;
1014     break;
1015   case OS_DISTRO_PARDUS:
1016     fs->package_format = OS_PACKAGE_FORMAT_PISI;
1017     break;
1018
1019   case OS_DISTRO_WINDOWS:
1020   case OS_DISTRO_UNKNOWN:
1021   default:
1022     fs->package_format = OS_PACKAGE_FORMAT_UNKNOWN;
1023     break;
1024   }
1025 }
1026
1027 static void
1028 check_package_management (guestfs_h *g, struct inspect_fs *fs)
1029 {
1030   switch (fs->distro) {
1031   case OS_DISTRO_FEDORA:
1032   case OS_DISTRO_MEEGO:
1033     fs->package_management = OS_PACKAGE_MANAGEMENT_YUM;
1034     break;
1035
1036   case OS_DISTRO_REDHAT_BASED:
1037   case OS_DISTRO_RHEL:
1038     if (fs->major_version >= 5)
1039       fs->package_management = OS_PACKAGE_MANAGEMENT_YUM;
1040     else
1041       fs->package_management = OS_PACKAGE_MANAGEMENT_UP2DATE;
1042     break;
1043
1044   case OS_DISTRO_DEBIAN:
1045   case OS_DISTRO_UBUNTU:
1046     fs->package_management = OS_PACKAGE_MANAGEMENT_APT;
1047     break;
1048
1049   case OS_DISTRO_ARCHLINUX:
1050     fs->package_management = OS_PACKAGE_MANAGEMENT_PACMAN;
1051     break;
1052   case OS_DISTRO_GENTOO:
1053     fs->package_management = OS_PACKAGE_MANAGEMENT_PORTAGE;
1054     break;
1055   case OS_DISTRO_PARDUS:
1056     fs->package_management = OS_PACKAGE_MANAGEMENT_PISI;
1057     break;
1058
1059   case OS_DISTRO_WINDOWS:
1060   case OS_DISTRO_UNKNOWN:
1061   default:
1062     fs->package_management = OS_PACKAGE_MANAGEMENT_UNKNOWN;
1063     break;
1064   }
1065 }
1066
1067 static struct inspect_fs *
1068 search_for_root (guestfs_h *g, const char *root)
1069 {
1070   if (g->nr_fses == 0) {
1071     error (g, _("no inspection data: call guestfs_inspect_os first"));
1072     return NULL;
1073   }
1074
1075   size_t i;
1076   struct inspect_fs *fs;
1077   for (i = 0; i < g->nr_fses; ++i) {
1078     fs = &g->fses[i];
1079     if (fs->is_root && STREQ (root, fs->device))
1080       return fs;
1081   }
1082
1083   error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"),
1084          root);
1085   return NULL;
1086 }
1087
1088 char **
1089 guestfs__inspect_get_roots (guestfs_h *g)
1090 {
1091   /* NB. Doesn't matter if g->nr_fses == 0.  We just return an empty
1092    * list in this case.
1093    */
1094
1095   size_t i;
1096   size_t count = 0;
1097   for (i = 0; i < g->nr_fses; ++i)
1098     if (g->fses[i].is_root)
1099       count++;
1100
1101   char **ret = calloc (count+1, sizeof (char *));
1102   if (ret == NULL) {
1103     perrorf (g, "calloc");
1104     return NULL;
1105   }
1106
1107   count = 0;
1108   for (i = 0; i < g->nr_fses; ++i) {
1109     if (g->fses[i].is_root) {
1110       ret[count] = safe_strdup (g, g->fses[i].device);
1111       count++;
1112     }
1113   }
1114   ret[count] = NULL;
1115
1116   return ret;
1117 }
1118
1119 char *
1120 guestfs__inspect_get_type (guestfs_h *g, const char *root)
1121 {
1122   struct inspect_fs *fs = search_for_root (g, root);
1123   if (!fs)
1124     return NULL;
1125
1126   char *ret;
1127   switch (fs->type) {
1128   case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break;
1129   case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break;
1130   case OS_TYPE_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
1131   }
1132
1133   return ret;
1134 }
1135
1136 char *
1137 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
1138 {
1139   struct inspect_fs *fs = search_for_root (g, root);
1140   if (!fs)
1141     return NULL;
1142
1143   return safe_strdup (g, fs->arch ? : "unknown");
1144 }
1145
1146 char *
1147 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
1148 {
1149   struct inspect_fs *fs = search_for_root (g, root);
1150   if (!fs)
1151     return NULL;
1152
1153   char *ret;
1154   switch (fs->distro) {
1155   case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break;
1156   case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break;
1157   case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break;
1158   case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break;
1159   case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); break;
1160   case OS_DISTRO_PARDUS: ret = safe_strdup (g, "pardus"); break;
1161   case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break;
1162   case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break;
1163   case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break;
1164   case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break;
1165   case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break;
1166   }
1167
1168   return ret;
1169 }
1170
1171 int
1172 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
1173 {
1174   struct inspect_fs *fs = search_for_root (g, root);
1175   if (!fs)
1176     return -1;
1177
1178   return fs->major_version;
1179 }
1180
1181 int
1182 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
1183 {
1184   struct inspect_fs *fs = search_for_root (g, root);
1185   if (!fs)
1186     return -1;
1187
1188   return fs->minor_version;
1189 }
1190
1191 char *
1192 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
1193 {
1194   struct inspect_fs *fs = search_for_root (g, root);
1195   if (!fs)
1196     return NULL;
1197
1198   return safe_strdup (g, fs->product_name ? : "unknown");
1199 }
1200
1201 char *
1202 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
1203 {
1204   struct inspect_fs *fs = search_for_root (g, root);
1205   if (!fs)
1206     return NULL;
1207
1208   if (!fs->windows_systemroot) {
1209     error (g, _("not a Windows guest, or systemroot could not be determined"));
1210     return NULL;
1211   }
1212
1213   return safe_strdup (g, fs->windows_systemroot);
1214 }
1215
1216 char **
1217 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
1218 {
1219   struct inspect_fs *fs = search_for_root (g, root);
1220   if (!fs)
1221     return NULL;
1222
1223   char **ret;
1224
1225   /* If no fstab information (Windows) return just the root. */
1226   if (fs->nr_fstab == 0) {
1227     ret = calloc (3, sizeof (char *));
1228     ret[0] = safe_strdup (g, "/");
1229     ret[1] = safe_strdup (g, root);
1230     ret[2] = NULL;
1231     return ret;
1232   }
1233
1234 #define CRITERION fs->fstab[i].mountpoint[0] == '/'
1235   size_t i, count = 0;
1236   for (i = 0; i < fs->nr_fstab; ++i)
1237     if (CRITERION)
1238       count++;
1239
1240   /* Hashtables have 2N+1 entries. */
1241   ret = calloc (2*count+1, sizeof (char *));
1242   if (ret == NULL) {
1243     perrorf (g, "calloc");
1244     return NULL;
1245   }
1246
1247   count = 0;
1248   for (i = 0; i < fs->nr_fstab; ++i)
1249     if (CRITERION) {
1250       ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint);
1251       ret[2*count+1] = safe_strdup (g, fs->fstab[i].device);
1252       count++;
1253     }
1254 #undef CRITERION
1255
1256   return ret;
1257 }
1258
1259 char **
1260 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
1261 {
1262   struct inspect_fs *fs = search_for_root (g, root);
1263   if (!fs)
1264     return NULL;
1265
1266   char **ret;
1267
1268   /* If no fstab information (Windows) return just the root. */
1269   if (fs->nr_fstab == 0) {
1270     ret = calloc (2, sizeof (char *));
1271     ret[0] = safe_strdup (g, root);
1272     ret[1] = NULL;
1273     return ret;
1274   }
1275
1276   ret = calloc (fs->nr_fstab + 1, sizeof (char *));
1277   if (ret == NULL) {
1278     perrorf (g, "calloc");
1279     return NULL;
1280   }
1281
1282   size_t i;
1283   for (i = 0; i < fs->nr_fstab; ++i)
1284     ret[i] = safe_strdup (g, fs->fstab[i].device);
1285
1286   return ret;
1287 }
1288
1289 char *
1290 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
1291 {
1292   struct inspect_fs *fs = search_for_root (g, root);
1293   if (!fs)
1294     return NULL;
1295
1296   char *ret;
1297   switch (fs->package_format) {
1298   case OS_PACKAGE_FORMAT_RPM: ret = safe_strdup (g, "rpm"); break;
1299   case OS_PACKAGE_FORMAT_DEB: ret = safe_strdup (g, "deb"); break;
1300   case OS_PACKAGE_FORMAT_PACMAN: ret = safe_strdup (g, "pacman"); break;
1301   case OS_PACKAGE_FORMAT_EBUILD: ret = safe_strdup (g, "ebuild"); break;
1302   case OS_PACKAGE_FORMAT_PISI: ret = safe_strdup (g, "pisi"); break;
1303   case OS_PACKAGE_FORMAT_UNKNOWN:
1304   default:
1305     ret = safe_strdup (g, "unknown");
1306     break;
1307   }
1308
1309   return ret;
1310 }
1311
1312 char *
1313 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
1314 {
1315   struct inspect_fs *fs = search_for_root (g, root);
1316   if (!fs)
1317     return NULL;
1318
1319   char *ret;
1320   switch (fs->package_management) {
1321   case OS_PACKAGE_MANAGEMENT_YUM: ret = safe_strdup (g, "yum"); break;
1322   case OS_PACKAGE_MANAGEMENT_UP2DATE: ret = safe_strdup (g, "up2date"); break;
1323   case OS_PACKAGE_MANAGEMENT_APT: ret = safe_strdup (g, "apt"); break;
1324   case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break;
1325   case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break;
1326   case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break;
1327   case OS_PACKAGE_MANAGEMENT_UNKNOWN:
1328   default:
1329     ret = safe_strdup (g, "unknown");
1330     break;
1331   }
1332
1333   return ret;
1334 }
1335
1336 #else /* no PCRE or hivex at compile time */
1337
1338 /* XXX These functions should be in an optgroup. */
1339
1340 #define NOT_IMPL(r)                                                     \
1341   error (g, _("inspection API not available since this version of libguestfs was compiled without PCRE or hivex libraries")); \
1342   return r
1343
1344 char **
1345 guestfs__inspect_os (guestfs_h *g)
1346 {
1347   NOT_IMPL(NULL);
1348 }
1349
1350 char **
1351 guestfs__inspect_get_roots (guestfs_h *g)
1352 {
1353   NOT_IMPL(NULL);
1354 }
1355
1356 char *
1357 guestfs__inspect_get_type (guestfs_h *g, const char *root)
1358 {
1359   NOT_IMPL(NULL);
1360 }
1361
1362 char *
1363 guestfs__inspect_get_arch (guestfs_h *g, const char *root)
1364 {
1365   NOT_IMPL(NULL);
1366 }
1367
1368 char *
1369 guestfs__inspect_get_distro (guestfs_h *g, const char *root)
1370 {
1371   NOT_IMPL(NULL);
1372 }
1373
1374 int
1375 guestfs__inspect_get_major_version (guestfs_h *g, const char *root)
1376 {
1377   NOT_IMPL(-1);
1378 }
1379
1380 int
1381 guestfs__inspect_get_minor_version (guestfs_h *g, const char *root)
1382 {
1383   NOT_IMPL(-1);
1384 }
1385
1386 char *
1387 guestfs__inspect_get_product_name (guestfs_h *g, const char *root)
1388 {
1389   NOT_IMPL(NULL);
1390 }
1391
1392 char *
1393 guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
1394 {
1395   NOT_IMPL(NULL);
1396 }
1397
1398 char **
1399 guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root)
1400 {
1401   NOT_IMPL(NULL);
1402 }
1403
1404 char **
1405 guestfs__inspect_get_filesystems (guestfs_h *g, const char *root)
1406 {
1407   NOT_IMPL(NULL);
1408 }
1409
1410 char *
1411 guestfs__inspect_get_package_format (guestfs_h *g, const char *root)
1412 {
1413   NOT_IMPL(NULL);
1414 }
1415
1416 char *
1417 guestfs__inspect_get_package_management (guestfs_h *g, const char *root)
1418 {
1419   NOT_IMPL(NULL);
1420 }
1421
1422 #endif /* no PCRE or hivex at compile time */
1423
1424 void
1425 guestfs___free_inspect_info (guestfs_h *g)
1426 {
1427   size_t i;
1428   for (i = 0; i < g->nr_fses; ++i) {
1429     free (g->fses[i].device);
1430     free (g->fses[i].product_name);
1431     free (g->fses[i].arch);
1432     free (g->fses[i].windows_systemroot);
1433     size_t j;
1434     for (j = 0; j < g->fses[i].nr_fstab; ++j) {
1435       free (g->fses[i].fstab[j].device);
1436       free (g->fses[i].fstab[j].mountpoint);
1437     }
1438     free (g->fses[i].fstab);
1439   }
1440   free (g->fses);
1441   g->nr_fses = 0;
1442   g->fses = NULL;
1443 }
1444
1445 /* In the Perl code this is a public function. */
1446 int
1447 guestfs___feature_available (guestfs_h *g, const char *feature)
1448 {
1449   /* If there's an error we should ignore it, so to do that we have to
1450    * temporarily replace the error handler with a null one.
1451    */
1452   guestfs_error_handler_cb old_error_cb = g->error_cb;
1453   g->error_cb = NULL;
1454
1455   const char *groups[] = { feature, NULL };
1456   int r = guestfs_available (g, (char * const *) groups);
1457
1458   g->error_cb = old_error_cb;
1459
1460   return r == 0 ? 1 : 0;
1461 }
1462
1463 #ifdef HAVE_PCRE
1464
1465 /* Match a regular expression which contains no captures.  Returns
1466  * true if it matches or false if it doesn't.
1467  */
1468 int
1469 guestfs___match (guestfs_h *g, const char *str, const pcre *re)
1470 {
1471   size_t len = strlen (str);
1472   int vec[30], r;
1473
1474   r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
1475   if (r == PCRE_ERROR_NOMATCH)
1476     return 0;
1477   if (r != 1) {
1478     /* Internal error -- should not happen. */
1479     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
1480              __FILE__, __func__, r, str);
1481     return 0;
1482   }
1483
1484   return 1;
1485 }
1486
1487 /* Match a regular expression which contains exactly one capture.  If
1488  * the string matches, return the capture, otherwise return NULL.  The
1489  * caller must free the result.
1490  */
1491 char *
1492 guestfs___match1 (guestfs_h *g, const char *str, const pcre *re)
1493 {
1494   size_t len = strlen (str);
1495   int vec[30], r;
1496
1497   r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
1498   if (r == PCRE_ERROR_NOMATCH)
1499     return NULL;
1500   if (r != 2) {
1501     /* Internal error -- should not happen. */
1502     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
1503              __FILE__, __func__, r, str);
1504     return NULL;
1505   }
1506
1507   return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
1508 }
1509
1510 /* Match a regular expression which contains exactly two captures. */
1511 int
1512 guestfs___match2 (guestfs_h *g, const char *str, const pcre *re,
1513                   char **ret1, char **ret2)
1514 {
1515   size_t len = strlen (str);
1516   int vec[30], r;
1517
1518   r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
1519   if (r == PCRE_ERROR_NOMATCH)
1520     return 0;
1521   if (r != 3) {
1522     /* Internal error -- should not happen. */
1523     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
1524              __FILE__, __func__, r, str);
1525     return 0;
1526   }
1527
1528   *ret1 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
1529   *ret2 = safe_strndup (g, &str[vec[4]], vec[5]-vec[4]);
1530
1531   return 1;
1532 }
1533
1534 /* Match a regular expression which contains exactly three captures. */
1535 int
1536 guestfs___match3 (guestfs_h *g, const char *str, const pcre *re,
1537                   char **ret1, char **ret2, char **ret3)
1538 {
1539   size_t len = strlen (str);
1540   int vec[30], r;
1541
1542   r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
1543   if (r == PCRE_ERROR_NOMATCH)
1544     return 0;
1545   if (r != 4) {
1546     /* Internal error -- should not happen. */
1547     fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
1548              __FILE__, __func__, r, str);
1549     return 0;
1550   }
1551
1552   *ret1 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
1553   *ret2 = safe_strndup (g, &str[vec[4]], vec[5]-vec[4]);
1554   *ret3 = safe_strndup (g, &str[vec[6]], vec[7]-vec[6]);
1555
1556   return 1;
1557 }
1558
1559 #endif /* HAVE_PCRE */