Version 1.11.17.
[libguestfs.git] / src / inspect_fs_unix.c
1 /* libguestfs
2  * Copyright (C) 2010-2011 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 <fcntl.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <endian.h>
31
32 #ifdef HAVE_PCRE
33 #include <pcre.h>
34 #endif
35
36 #ifdef HAVE_HIVEX
37 #include <hivex.h>
38 #endif
39
40 #include "c-ctype.h"
41 #include "ignore-value.h"
42 #include "xstrtol.h"
43
44 #include "guestfs.h"
45 #include "guestfs-internal.h"
46 #include "guestfs-internal-actions.h"
47 #include "guestfs_protocol.h"
48
49 #if defined(HAVE_PCRE) && defined(HAVE_HIVEX)
50
51 /* Compile all the regular expressions once when the shared library is
52  * loaded.  PCRE is thread safe so we're supposedly OK here if
53  * multiple threads call into the libguestfs API functions below
54  * simultaneously.
55  */
56 static pcre *re_fedora;
57 static pcre *re_rhel_old;
58 static pcre *re_rhel;
59 static pcre *re_rhel_no_minor;
60 static pcre *re_centos_old;
61 static pcre *re_centos;
62 static pcre *re_centos_no_minor;
63 static pcre *re_scientific_linux_old;
64 static pcre *re_scientific_linux;
65 static pcre *re_scientific_linux_no_minor;
66 static pcre *re_major_minor;
67 static pcre *re_aug_seq;
68 static pcre *re_xdev;
69 static pcre *re_first_partition;
70 static pcre *re_freebsd;
71
72 static void compile_regexps (void) __attribute__((constructor));
73 static void free_regexps (void) __attribute__((destructor));
74
75 static void
76 compile_regexps (void)
77 {
78   const char *err;
79   int offset;
80
81 #define COMPILE(re,pattern,options)                                     \
82   do {                                                                  \
83     re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
84     if (re == NULL) {                                                   \
85       ignore_value (write (2, err, strlen (err)));                      \
86       abort ();                                                         \
87     }                                                                   \
88   } while (0)
89
90   COMPILE (re_fedora, "Fedora release (\\d+)", 0);
91   COMPILE (re_rhel_old,
92            "Red Hat.*release (\\d+).*Update (\\d+)", 0);
93   COMPILE (re_rhel,
94            "Red Hat.*release (\\d+)\\.(\\d+)", 0);
95   COMPILE (re_rhel_no_minor,
96            "Red Hat.*release (\\d+)", 0);
97   COMPILE (re_centos_old,
98            "CentOS.*release (\\d+).*Update (\\d+)", 0);
99   COMPILE (re_centos,
100            "CentOS.*release (\\d+)\\.(\\d+)", 0);
101   COMPILE (re_centos_no_minor,
102            "CentOS.*release (\\d+)", 0);
103   COMPILE (re_scientific_linux_old,
104            "Scientific Linux.*release (\\d+).*Update (\\d+)", 0);
105   COMPILE (re_scientific_linux,
106            "Scientific Linux.*release (\\d+)\\.(\\d+)", 0);
107   COMPILE (re_scientific_linux_no_minor,
108            "Scientific Linux.*release (\\d+)", 0);
109   COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0);
110   COMPILE (re_aug_seq, "/\\d+$", 0);
111   COMPILE (re_xdev, "^/dev/(?:h|s|v|xv)d([a-z]\\d*)$", 0);
112   COMPILE (re_freebsd, "^/dev/ad(\\d+)s(\\d+)([a-z])$", 0);
113 }
114
115 static void
116 free_regexps (void)
117 {
118   pcre_free (re_fedora);
119   pcre_free (re_rhel_old);
120   pcre_free (re_rhel);
121   pcre_free (re_rhel_no_minor);
122   pcre_free (re_centos_old);
123   pcre_free (re_centos);
124   pcre_free (re_centos_no_minor);
125   pcre_free (re_scientific_linux_old);
126   pcre_free (re_scientific_linux);
127   pcre_free (re_scientific_linux_no_minor);
128   pcre_free (re_major_minor);
129   pcre_free (re_aug_seq);
130   pcre_free (re_xdev);
131   pcre_free (re_freebsd);
132 }
133
134 static void check_architecture (guestfs_h *g, struct inspect_fs *fs);
135 static int check_hostname_unix (guestfs_h *g, struct inspect_fs *fs);
136 static int check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs);
137 static int check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs);
138 static int check_fstab (guestfs_h *g, struct inspect_fs *fs);
139 static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
140                             const char *spec, const char *mp);
141 static char *resolve_fstab_device (guestfs_h *g, const char *spec);
142 static int inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename, int (*f) (guestfs_h *, struct inspect_fs *));
143
144 /* Set fs->product_name to the first line of the release file. */
145 static int
146 parse_release_file (guestfs_h *g, struct inspect_fs *fs,
147                     const char *release_filename)
148 {
149   fs->product_name = guestfs___first_line_of_file (g, release_filename);
150   if (fs->product_name == NULL)
151     return -1;
152   return 0;
153 }
154
155 /* Ubuntu has /etc/lsb-release containing:
156  *   DISTRIB_ID=Ubuntu                                # Distro
157  *   DISTRIB_RELEASE=10.04                            # Version
158  *   DISTRIB_CODENAME=lucid
159  *   DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS"         # Product name
160  *
161  * [Ubuntu-derived ...] Linux Mint was found to have this:
162  *   DISTRIB_ID=LinuxMint
163  *   DISTRIB_RELEASE=10
164  *   DISTRIB_CODENAME=julia
165  *   DISTRIB_DESCRIPTION="Linux Mint 10 Julia"
166  * Linux Mint also has /etc/linuxmint/info with more information,
167  * but we can use the LSB file.
168  *
169  * Mandriva has:
170  *   LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch
171  *   DISTRIB_ID=MandrivaLinux
172  *   DISTRIB_RELEASE=2010.1
173  *   DISTRIB_CODENAME=Henry_Farman
174  *   DISTRIB_DESCRIPTION="Mandriva Linux 2010.1"
175  * Mandriva also has a normal release file called /etc/mandriva-release.
176  */
177 static int
178 parse_lsb_release (guestfs_h *g, struct inspect_fs *fs)
179 {
180   const char *filename = "/etc/lsb-release";
181   int64_t size;
182   char **lines;
183   size_t i;
184   int r = 0;
185
186   /* Don't trust guestfs_head_n not to break with very large files.
187    * Check the file size is something reasonable first.
188    */
189   size = guestfs_filesize (g, filename);
190   if (size == -1)
191     /* guestfs_filesize failed and has already set error in handle */
192     return -1;
193   if (size > MAX_SMALL_FILE_SIZE) {
194     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
195            filename, size);
196     return -1;
197   }
198
199   lines = guestfs_head_n (g, 10, filename);
200   if (lines == NULL)
201     return -1;
202
203   for (i = 0; lines[i] != NULL; ++i) {
204     if (fs->distro == 0 &&
205         STREQ (lines[i], "DISTRIB_ID=Ubuntu")) {
206       fs->distro = OS_DISTRO_UBUNTU;
207       r = 1;
208     }
209     else if (fs->distro == 0 &&
210              STREQ (lines[i], "DISTRIB_ID=LinuxMint")) {
211       fs->distro = OS_DISTRO_LINUX_MINT;
212       r = 1;
213     }
214     else if (fs->distro == 0 &&
215              STREQ (lines[i], "DISTRIB_ID=MandrivaLinux")) {
216       fs->distro = OS_DISTRO_MANDRIVA;
217       r = 1;
218     }
219     else if (STRPREFIX (lines[i], "DISTRIB_RELEASE=")) {
220       char *major, *minor;
221       if (match2 (g, &lines[i][16], re_major_minor, &major, &minor)) {
222         fs->major_version = guestfs___parse_unsigned_int (g, major);
223         free (major);
224         if (fs->major_version == -1) {
225           free (minor);
226           guestfs___free_string_list (lines);
227           return -1;
228         }
229         fs->minor_version = guestfs___parse_unsigned_int (g, minor);
230         free (minor);
231         if (fs->minor_version == -1) {
232           guestfs___free_string_list (lines);
233           return -1;
234         }
235       }
236     }
237     else if (fs->product_name == NULL &&
238              (STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=\"") ||
239               STRPREFIX (lines[i], "DISTRIB_DESCRIPTION='"))) {
240       size_t len = strlen (lines[i]) - 21 - 1;
241       fs->product_name = safe_strndup (g, &lines[i][21], len);
242       r = 1;
243     }
244     else if (fs->product_name == NULL &&
245              STRPREFIX (lines[i], "DISTRIB_DESCRIPTION=")) {
246       size_t len = strlen (lines[i]) - 20;
247       fs->product_name = safe_strndup (g, &lines[i][20], len);
248       r = 1;
249     }
250   }
251
252   guestfs___free_string_list (lines);
253   return r;
254 }
255
256 /* The currently mounted device is known to be a Linux root.  Try to
257  * determine from this the distro, version, etc.  Also parse
258  * /etc/fstab to determine the arrangement of mountpoints and
259  * associated devices.
260  */
261 int
262 guestfs___check_linux_root (guestfs_h *g, struct inspect_fs *fs)
263 {
264   int r;
265
266   fs->type = OS_TYPE_LINUX;
267
268   if (guestfs_exists (g, "/etc/lsb-release") > 0) {
269     r = parse_lsb_release (g, fs);
270     if (r == -1)        /* error */
271       return -1;
272     if (r == 1)         /* ok - detected the release from this file */
273       goto skip_release_checks;
274   }
275
276   if (guestfs_exists (g, "/etc/redhat-release") > 0) {
277     fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */
278
279     if (parse_release_file (g, fs, "/etc/redhat-release") == -1)
280       return -1;
281
282     char *major, *minor;
283     if ((major = match1 (g, fs->product_name, re_fedora)) != NULL) {
284       fs->distro = OS_DISTRO_FEDORA;
285       fs->major_version = guestfs___parse_unsigned_int (g, major);
286       free (major);
287       if (fs->major_version == -1)
288         return -1;
289     }
290     else if (match2 (g, fs->product_name, re_rhel_old, &major, &minor) ||
291              match2 (g, fs->product_name, re_rhel, &major, &minor)) {
292       fs->distro = OS_DISTRO_RHEL;
293       fs->major_version = guestfs___parse_unsigned_int (g, major);
294       free (major);
295       if (fs->major_version == -1) {
296         free (minor);
297         return -1;
298       }
299       fs->minor_version = guestfs___parse_unsigned_int (g, minor);
300       free (minor);
301       if (fs->minor_version == -1)
302         return -1;
303     }
304     else if ((major = match1 (g, fs->product_name, re_rhel_no_minor)) != NULL) {
305       fs->distro = OS_DISTRO_RHEL;
306       fs->major_version = guestfs___parse_unsigned_int (g, major);
307       free (major);
308       if (fs->major_version == -1)
309         return -1;
310       fs->minor_version = 0;
311     }
312     else if (match2 (g, fs->product_name, re_centos_old, &major, &minor) ||
313              match2 (g, fs->product_name, re_centos, &major, &minor)) {
314       fs->distro = OS_DISTRO_CENTOS;
315       fs->major_version = guestfs___parse_unsigned_int (g, major);
316       free (major);
317       if (fs->major_version == -1) {
318         free (minor);
319         return -1;
320       }
321       fs->minor_version = guestfs___parse_unsigned_int (g, minor);
322       free (minor);
323       if (fs->minor_version == -1)
324         return -1;
325     }
326     else if ((major = match1 (g, fs->product_name, re_centos_no_minor)) != NULL) {
327       fs->distro = OS_DISTRO_CENTOS;
328       fs->major_version = guestfs___parse_unsigned_int (g, major);
329       free (major);
330       if (fs->major_version == -1)
331         return -1;
332       fs->minor_version = 0;
333     }
334     else if (match2 (g, fs->product_name, re_scientific_linux_old, &major, &minor) ||
335              match2 (g, fs->product_name, re_scientific_linux, &major, &minor)) {
336       fs->distro = OS_DISTRO_SCIENTIFIC_LINUX;
337       fs->major_version = guestfs___parse_unsigned_int (g, major);
338       free (major);
339       if (fs->major_version == -1) {
340         free (minor);
341         return -1;
342       }
343       fs->minor_version = guestfs___parse_unsigned_int (g, minor);
344       free (minor);
345       if (fs->minor_version == -1)
346         return -1;
347     }
348     else if ((major = match1 (g, fs->product_name, re_scientific_linux_no_minor)) != NULL) {
349       fs->distro = OS_DISTRO_SCIENTIFIC_LINUX;
350       fs->major_version = guestfs___parse_unsigned_int (g, major);
351       free (major);
352       if (fs->major_version == -1)
353         return -1;
354       fs->minor_version = 0;
355     }
356   }
357   else if (guestfs_exists (g, "/etc/debian_version") > 0) {
358     fs->distro = OS_DISTRO_DEBIAN;
359
360     if (parse_release_file (g, fs, "/etc/debian_version") == -1)
361       return -1;
362
363     if (guestfs___parse_major_minor (g, fs) == -1)
364       return -1;
365   }
366   else if (guestfs_exists (g, "/etc/pardus-release") > 0) {
367     fs->distro = OS_DISTRO_PARDUS;
368
369     if (parse_release_file (g, fs, "/etc/pardus-release") == -1)
370       return -1;
371
372     if (guestfs___parse_major_minor (g, fs) == -1)
373       return -1;
374   }
375   else if (guestfs_exists (g, "/etc/arch-release") > 0) {
376     fs->distro = OS_DISTRO_ARCHLINUX;
377
378     /* /etc/arch-release file is empty and I can't see a way to
379      * determine the actual release or product string.
380      */
381   }
382   else if (guestfs_exists (g, "/etc/gentoo-release") > 0) {
383     fs->distro = OS_DISTRO_GENTOO;
384
385     if (parse_release_file (g, fs, "/etc/gentoo-release") == -1)
386       return -1;
387
388     if (guestfs___parse_major_minor (g, fs) == -1)
389       return -1;
390   }
391   else if (guestfs_exists (g, "/etc/meego-release") > 0) {
392     fs->distro = OS_DISTRO_MEEGO;
393
394     if (parse_release_file (g, fs, "/etc/meego-release") == -1)
395       return -1;
396
397     if (guestfs___parse_major_minor (g, fs) == -1)
398       return -1;
399   }
400   else if (guestfs_exists (g, "/etc/slackware-version") > 0) {
401     fs->distro = OS_DISTRO_SLACKWARE;
402
403     if (parse_release_file (g, fs, "/etc/slackware-version") == -1)
404       return -1;
405
406     if (guestfs___parse_major_minor (g, fs) == -1)
407       return -1;
408   }
409
410  skip_release_checks:;
411
412   /* Determine the architecture. */
413   check_architecture (g, fs);
414
415   /* We already know /etc/fstab exists because it's part of the test
416    * for Linux root above.  We must now parse this file to determine
417    * which filesystems are used by the operating system and how they
418    * are mounted.
419    */
420   if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1)
421     return -1;
422
423   /* Determine hostname. */
424   if (check_hostname_unix (g, fs) == -1)
425     return -1;
426
427   return 0;
428 }
429
430 /* The currently mounted device is known to be a FreeBSD root. */
431 int
432 guestfs___check_freebsd_root (guestfs_h *g, struct inspect_fs *fs)
433 {
434   fs->type = OS_TYPE_FREEBSD;
435
436   /* FreeBSD has no authoritative version file.  The version number is
437    * in /etc/motd, which the system administrator might edit, but
438    * we'll use that anyway.
439    */
440
441   if (guestfs_exists (g, "/etc/motd") > 0) {
442     if (parse_release_file (g, fs, "/etc/motd") == -1)
443       return -1;
444
445     if (guestfs___parse_major_minor (g, fs) == -1)
446       return -1;
447   }
448
449   /* Determine the architecture. */
450   check_architecture (g, fs);
451
452   /* We already know /etc/fstab exists because it's part of the test above. */
453   if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1)
454     return -1;
455
456   /* Determine hostname. */
457   if (check_hostname_unix (g, fs) == -1)
458     return -1;
459
460   return 0;
461 }
462
463 static void
464 check_architecture (guestfs_h *g, struct inspect_fs *fs)
465 {
466   const char *binaries[] =
467     { "/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh" };
468   size_t i;
469
470   for (i = 0; i < sizeof binaries / sizeof binaries[0]; ++i) {
471     if (guestfs_is_file (g, binaries[i]) > 0) {
472       /* Ignore errors from file_architecture call. */
473       guestfs_error_handler_cb old_error_cb = g->error_cb;
474       g->error_cb = NULL;
475       char *arch = guestfs_file_architecture (g, binaries[i]);
476       g->error_cb = old_error_cb;
477
478       if (arch) {
479         /* String will be owned by handle, freed by
480          * guestfs___free_inspect_info.
481          */
482         fs->arch = arch;
483         break;
484       }
485     }
486   }
487 }
488
489 /* Try several methods to determine the hostname from a Linux or
490  * FreeBSD guest.  Note that type and distro have been set, so we can
491  * use that information to direct the search.
492  */
493 static int
494 check_hostname_unix (guestfs_h *g, struct inspect_fs *fs)
495 {
496   switch (fs->type) {
497   case OS_TYPE_LINUX:
498     /* Red Hat-derived would be in /etc/sysconfig/network, and
499      * Debian-derived in the file /etc/hostname.  Very old Debian and
500      * SUSE use /etc/HOSTNAME.  It's best to just look for each of
501      * these files in turn, rather than try anything clever based on
502      * distro.
503      */
504     if (guestfs_is_file (g, "/etc/HOSTNAME")) {
505       fs->hostname = guestfs___first_line_of_file (g, "/etc/HOSTNAME");
506       if (fs->hostname == NULL)
507         return -1;
508     }
509     else if (guestfs_is_file (g, "/etc/hostname")) {
510       fs->hostname = guestfs___first_line_of_file (g, "/etc/hostname");
511       if (fs->hostname == NULL)
512         return -1;
513     }
514     else if (guestfs_is_file (g, "/etc/sysconfig/network")) {
515       if (inspect_with_augeas (g, fs, "/etc/sysconfig/network",
516                                check_hostname_redhat) == -1)
517         return -1;
518     }
519     break;
520
521   case OS_TYPE_FREEBSD:
522     /* /etc/rc.conf contains the hostname, but there is no Augeas lens
523      * for this file.
524      */
525     if (guestfs_is_file (g, "/etc/rc.conf")) {
526       if (check_hostname_freebsd (g, fs) == -1)
527         return -1;
528     }
529     break;
530
531   case OS_TYPE_WINDOWS: /* not here, see check_windows_system_registry */
532   case OS_TYPE_UNKNOWN:
533   default:
534     /* nothing, keep GCC warnings happy */;
535   }
536
537   return 0;
538 }
539
540 /* Parse the hostname from /etc/sysconfig/network.  This must be called
541  * from the inspect_with_augeas wrapper.
542  */
543 static int
544 check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs)
545 {
546   char *hostname;
547
548   hostname = guestfs_aug_get (g, "/files/etc/sysconfig/network/HOSTNAME");
549   if (!hostname)
550     return -1;
551
552   fs->hostname = hostname;  /* freed by guestfs___free_inspect_info */
553   return 0;
554 }
555
556 /* Parse the hostname from /etc/rc.conf.  On FreeBSD this file
557  * contains comments, blank lines and:
558  *   hostname="freebsd8.example.com"
559  *   ifconfig_re0="DHCP"
560  *   keymap="uk.iso"
561  *   sshd_enable="YES"
562  */
563 static int
564 check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs)
565 {
566   const char *filename = "/etc/rc.conf";
567   int64_t size;
568   char **lines;
569   size_t i;
570
571   /* Don't trust guestfs_read_lines not to break with very large files.
572    * Check the file size is something reasonable first.
573    */
574   size = guestfs_filesize (g, filename);
575   if (size == -1)
576     /* guestfs_filesize failed and has already set error in handle */
577     return -1;
578   if (size > MAX_SMALL_FILE_SIZE) {
579     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
580            filename, size);
581     return -1;
582   }
583
584   lines = guestfs_read_lines (g, filename);
585   if (lines == NULL)
586     return -1;
587
588   for (i = 0; lines[i] != NULL; ++i) {
589     if (STRPREFIX (lines[i], "hostname=\"") ||
590         STRPREFIX (lines[i], "hostname='")) {
591       size_t len = strlen (lines[i]) - 10 - 1;
592       fs->hostname = safe_strndup (g, &lines[i][10], len);
593       break;
594     } else if (STRPREFIX (lines[i], "hostname=")) {
595       size_t len = strlen (lines[i]) - 9;
596       fs->hostname = safe_strndup (g, &lines[i][9], len);
597       break;
598     }
599   }
600
601   guestfs___free_string_list (lines);
602   return 0;
603 }
604
605 static int
606 check_fstab (guestfs_h *g, struct inspect_fs *fs)
607 {
608   char **lines = guestfs_aug_ls (g, "/files/etc/fstab");
609   if (lines == NULL)
610     return -1;
611
612   if (lines[0] == NULL) {
613     error (g, _("could not parse /etc/fstab or empty file"));
614     guestfs___free_string_list (lines);
615     return -1;
616   }
617
618   size_t i;
619   char augpath[256];
620   for (i = 0; lines[i] != NULL; ++i) {
621     /* Ignore comments.  Only care about sequence lines which
622      * match m{/\d+$}.
623      */
624     if (match (g, lines[i], re_aug_seq)) {
625       snprintf (augpath, sizeof augpath, "%s/spec", lines[i]);
626       char *spec = guestfs_aug_get (g, augpath);
627       if (spec == NULL) {
628         guestfs___free_string_list (lines);
629         return -1;
630       }
631
632       snprintf (augpath, sizeof augpath, "%s/file", lines[i]);
633       char *mp = guestfs_aug_get (g, augpath);
634       if (mp == NULL) {
635         guestfs___free_string_list (lines);
636         free (spec);
637         return -1;
638       }
639
640       int r = add_fstab_entry (g, fs, spec, mp);
641       free (spec);
642       free (mp);
643
644       if (r == -1) {
645         guestfs___free_string_list (lines);
646         return -1;
647       }
648     }
649   }
650
651   guestfs___free_string_list (lines);
652   return 0;
653 }
654
655 /* Add a filesystem and possibly a mountpoint entry for
656  * the root filesystem 'fs'.
657  *
658  * 'spec' is the fstab spec field, which might be a device name or a
659  * pseudodevice or 'UUID=...' or 'LABEL=...'.
660  *
661  * 'mp' is the mount point, which could also be 'swap' or 'none'.
662  */
663 static int
664 add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
665                  const char *spec, const char *mp)
666 {
667   /* Ignore certain mountpoints. */
668   if (STRPREFIX (mp, "/dev/") ||
669       STREQ (mp, "/dev") ||
670       STRPREFIX (mp, "/media/") ||
671       STRPREFIX (mp, "/proc/") ||
672       STREQ (mp, "/proc") ||
673       STRPREFIX (mp, "/selinux/") ||
674       STREQ (mp, "/selinux") ||
675       STRPREFIX (mp, "/sys/") ||
676       STREQ (mp, "/sys"))
677     return 0;
678
679   /* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives. */
680   if ((STRPREFIX (spec, "/dev/fd") && c_isdigit (spec[7])) ||
681       STREQ (spec, "/dev/floppy") ||
682       STREQ (spec, "/dev/cdrom"))
683     return 0;
684
685   /* Resolve UUID= and LABEL= to the actual device. */
686   char *device = NULL;
687   if (STRPREFIX (spec, "UUID="))
688     device = guestfs_findfs_uuid (g, &spec[5]);
689   else if (STRPREFIX (spec, "LABEL="))
690     device = guestfs_findfs_label (g, &spec[6]);
691   /* Ignore "/.swap" (Pardus) and pseudo-devices like "tmpfs". */
692   else if (STRPREFIX (spec, "/dev/"))
693     /* Resolve guest block device names. */
694     device = resolve_fstab_device (g, spec);
695
696   /* If we haven't resolved the device successfully by this point,
697    * we don't care, just ignore it.
698    */
699   if (device == NULL)
700     return 0;
701
702   char *mountpoint = safe_strdup (g, mp);
703
704   /* Add this to the fstab entry in 'fs'.
705    * Note these are further filtered by guestfs_inspect_get_mountpoints
706    * and guestfs_inspect_get_filesystems.
707    */
708   size_t n = fs->nr_fstab + 1;
709   struct inspect_fstab_entry *p;
710
711   p = realloc (fs->fstab, n * sizeof (struct inspect_fstab_entry));
712   if (p == NULL) {
713     perrorf (g, "realloc");
714     free (device);
715     free (mountpoint);
716     return -1;
717   }
718
719   fs->fstab = p;
720   fs->nr_fstab = n;
721
722   /* These are owned by the handle and freed by guestfs___free_inspect_info. */
723   fs->fstab[n-1].device = device;
724   fs->fstab[n-1].mountpoint = mountpoint;
725
726   debug (g, "fstab: device=%s mountpoint=%s", device, mountpoint);
727
728   return 0;
729 }
730
731 /* Resolve block device name to the libguestfs device name, eg.
732  * /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV.  This
733  * assumes that disks were added in the same order as they appear to
734  * the real VM, which is a reasonable assumption to make.  Return
735  * anything we don't recognize unchanged.
736  */
737 static char *
738 resolve_fstab_device (guestfs_h *g, const char *spec)
739 {
740   char *a1;
741   char *device = NULL;
742   char *bsddisk, *bsdslice, *bsdpart;
743
744   if (STRPREFIX (spec, "/dev/mapper/")) {
745     /* LVM2 does some strange munging on /dev/mapper paths for VGs and
746      * LVs which contain '-' character:
747      *
748      * ><fs> lvcreate LV--test VG--test 32
749      * ><fs> debug ls /dev/mapper
750      * VG----test-LV----test
751      *
752      * This makes it impossible to reverse those paths directly, so
753      * we have implemented lvm_canonical_lv_name in the daemon.
754      */
755     device = guestfs_lvm_canonical_lv_name (g, spec);
756   }
757   else if ((a1 = match1 (g, spec, re_xdev)) != NULL) {
758     char **devices = guestfs_list_devices (g);
759     if (devices == NULL)
760       return NULL;
761
762     size_t count;
763     for (count = 0; devices[count] != NULL; count++)
764       ;
765
766     size_t i = a1[0] - 'a'; /* a1[0] is always [a-z] because of regex. */
767     if (i < count) {
768       size_t len = strlen (devices[i]) + strlen (a1) + 16;
769       device = safe_malloc (g, len);
770       snprintf (device, len, "%s%s", devices[i], &a1[1]);
771     }
772
773     free (a1);
774     guestfs___free_string_list (devices);
775   }
776   else if (match3 (g, spec, re_freebsd, &bsddisk, &bsdslice, &bsdpart)) {
777     /* FreeBSD disks are organized quite differently.  See:
778      * http://www.freebsd.org/doc/handbook/disk-organization.html
779      * FreeBSD "partitions" are exposed as quasi-extended partitions
780      * numbered from 5 in Linux.  I have no idea what happens when you
781      * have multiple "slices" (the FreeBSD term for MBR partitions).
782      */
783     int disk = guestfs___parse_unsigned_int (g, bsddisk);
784     int slice = guestfs___parse_unsigned_int (g, bsdslice);
785     int part = bsdpart[0] - 'a' /* counting from 0 */;
786     free (bsddisk);
787     free (bsdslice);
788     free (bsdpart);
789
790     if (disk == -1 || disk > 26 ||
791         slice <= 0 || slice > 1 /* > 4 .. see comment above */ ||
792         part < 0 || part >= 26)
793       goto out;
794
795     device = safe_asprintf (g, "/dev/sd%c%d", disk + 'a', part + 5);
796   }
797
798  out:
799   /* Didn't match device pattern, return original spec unchanged. */
800   if (device == NULL)
801     device = safe_strdup (g, spec);
802
803   return device;
804 }
805
806 /* Call 'f' with Augeas opened and having parsed 'filename' (this file
807  * must exist).  As a security measure, this bails if the file is too
808  * large for a reasonable configuration file.  After the call to 'f'
809  * Augeas is closed.
810  */
811 static int
812 inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename,
813                      int (*f) (guestfs_h *, struct inspect_fs *))
814 {
815   /* Security: Refuse to do this if filename is too large. */
816   int64_t size = guestfs_filesize (g, filename);
817   if (size == -1)
818     /* guestfs_filesize failed and has already set error in handle */
819     return -1;
820   if (size > MAX_AUGEAS_FILE_SIZE) {
821     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
822            filename, size);
823     return -1;
824   }
825
826   /* If !feature_available (g, "augeas") then the next call will fail.
827    * Arguably we might want to fall back to a non-Augeas method in
828    * this case.
829    */
830   if (guestfs_aug_init (g, "/", 16|32) == -1)
831     return -1;
832
833   int r = -1;
834
835   /* Tell Augeas to only load one file (thanks RaphaĆ«l Pinson). */
836   char buf[strlen (filename) + 64];
837   snprintf (buf, strlen (filename) + 64, "/augeas/load//incl[. != \"%s\"]",
838             filename);
839   if (guestfs_aug_rm (g, buf) == -1)
840     goto out;
841
842   if (guestfs_aug_load (g) == -1)
843     goto out;
844
845   r = f (g, fs);
846
847  out:
848   guestfs_aug_close (g);
849
850   return r;
851 }
852
853 #endif /* defined(HAVE_PCRE) && defined(HAVE_HIVEX) */