ff5463405579cf7ff7ddbc87fa41aa6df1f6d85e
[libguestfs.git] / src / inspect_fs.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_first_partition;
57 static pcre *re_major_minor;
58
59 static void compile_regexps (void) __attribute__((constructor));
60 static void free_regexps (void) __attribute__((destructor));
61
62 static void
63 compile_regexps (void)
64 {
65   const char *err;
66   int offset;
67
68 #define COMPILE(re,pattern,options)                                     \
69   do {                                                                  \
70     re = pcre_compile ((pattern), (options), &err, &offset, NULL);      \
71     if (re == NULL) {                                                   \
72       ignore_value (write (2, err, strlen (err)));                      \
73       abort ();                                                         \
74     }                                                                   \
75   } while (0)
76
77   COMPILE (re_first_partition, "^/dev/(?:h|s|v)d.1$", 0);
78   COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0);
79 }
80
81 static void
82 free_regexps (void)
83 {
84   pcre_free (re_first_partition);
85   pcre_free (re_major_minor);
86 }
87
88 static int check_filesystem (guestfs_h *g, const char *device, int is_block, int is_partnum);
89 static void check_package_format (guestfs_h *g, struct inspect_fs *fs);
90 static void check_package_management (guestfs_h *g, struct inspect_fs *fs);
91 static int extend_fses (guestfs_h *g);
92 static int is_file_nocase (guestfs_h *g, const char *);
93 static int is_dir_nocase (guestfs_h *g, const char *);
94
95 /* Find out if 'device' contains a filesystem.  If it does, add
96  * another entry in g->fses.
97  */
98 int
99 guestfs___check_for_filesystem_on (guestfs_h *g, const char *device,
100                                    int is_block, int is_partnum)
101 {
102   /* Get vfs-type in order to check if it's a Linux(?) swap device.
103    * If there's an error we should ignore it, so to do that we have to
104    * temporarily replace the error handler with a null one.
105    */
106   guestfs_error_handler_cb old_error_cb = g->error_cb;
107   g->error_cb = NULL;
108   char *vfs_type = guestfs_vfs_type (g, device);
109   g->error_cb = old_error_cb;
110
111   int is_swap = vfs_type && STREQ (vfs_type, "swap");
112
113   debug (g, "check_for_filesystem_on: %s %d %d (%s)",
114          device, is_block, is_partnum,
115          vfs_type ? vfs_type : "failed to get vfs type");
116
117   if (is_swap) {
118     free (vfs_type);
119     if (extend_fses (g) == -1)
120       return -1;
121     g->fses[g->nr_fses-1].is_swap = 1;
122     return 0;
123   }
124
125   /* Try mounting the device.  As above, ignore errors. */
126   g->error_cb = NULL;
127   int r = guestfs_mount_ro (g, device, "/");
128   if (r == -1 && vfs_type && STREQ (vfs_type, "ufs")) /* Hack for the *BSDs. */
129     r = guestfs_mount_vfs (g, "ro,ufstype=ufs2", "ufs", device, "/");
130   free (vfs_type);
131   g->error_cb = old_error_cb;
132   if (r == -1)
133     return 0;
134
135   /* Do the rest of the checks. */
136   r = check_filesystem (g, device, is_block, is_partnum);
137
138   /* Unmount the filesystem. */
139   if (guestfs_umount_all (g) == -1)
140     return -1;
141
142   return r;
143 }
144
145 /* is_block and is_partnum are just hints: is_block is true if the
146  * filesystem is a whole block device (eg. /dev/sda).  is_partnum
147  * is > 0 if the filesystem is a direct partition, and in this case
148  * it is the partition number counting from 1
149  * (eg. /dev/sda1 => is_partnum == 1).
150  */
151 static int
152 check_filesystem (guestfs_h *g, const char *device,
153                   int is_block, int is_partnum)
154 {
155   if (extend_fses (g) == -1)
156     return -1;
157
158   struct inspect_fs *fs = &g->fses[g->nr_fses-1];
159
160   fs->device = safe_strdup (g, device);
161   fs->is_mountable = 1;
162
163   /* Optimize some of the tests by avoiding multiple tests of the same thing. */
164   int is_dir_etc = guestfs_is_dir (g, "/etc") > 0;
165   int is_dir_bin = guestfs_is_dir (g, "/bin") > 0;
166   int is_dir_share = guestfs_is_dir (g, "/share") > 0;
167
168   /* Grub /boot? */
169   if (guestfs_is_file (g, "/grub/menu.lst") > 0 ||
170       guestfs_is_file (g, "/grub/grub.conf") > 0)
171     fs->content = FS_CONTENT_LINUX_BOOT;
172   /* FreeBSD root? */
173   else if (is_dir_etc &&
174            is_dir_bin &&
175            guestfs_is_file (g, "/etc/freebsd-update.conf") > 0 &&
176            guestfs_is_file (g, "/etc/fstab") > 0) {
177     /* Ignore /dev/sda1 which is a shadow of the real root filesystem
178      * that is probably /dev/sda5 (see:
179      * http://www.freebsd.org/doc/handbook/disk-organization.html)
180      */
181     if (match (g, device, re_first_partition))
182       return 0;
183
184     fs->is_root = 1;
185     fs->content = FS_CONTENT_FREEBSD_ROOT;
186     fs->format = OS_FORMAT_INSTALLED;
187     if (guestfs___check_freebsd_root (g, fs) == -1)
188       return -1;
189   }
190   /* Linux root? */
191   else if (is_dir_etc &&
192            is_dir_bin &&
193            guestfs_is_file (g, "/etc/fstab") > 0) {
194     fs->is_root = 1;
195     fs->content = FS_CONTENT_LINUX_ROOT;
196     fs->format = OS_FORMAT_INSTALLED;
197     if (guestfs___check_linux_root (g, fs) == -1)
198       return -1;
199   }
200   /* Linux /usr/local? */
201   else if (is_dir_etc &&
202            is_dir_bin &&
203            is_dir_share &&
204            guestfs_exists (g, "/local") == 0 &&
205            guestfs_is_file (g, "/etc/fstab") == 0)
206     fs->content = FS_CONTENT_LINUX_USR_LOCAL;
207   /* Linux /usr? */
208   else if (is_dir_etc &&
209            is_dir_bin &&
210            is_dir_share &&
211            guestfs_exists (g, "/local") > 0 &&
212            guestfs_is_file (g, "/etc/fstab") == 0)
213     fs->content = FS_CONTENT_LINUX_USR;
214   /* Linux /var? */
215   else if (guestfs_is_dir (g, "/log") > 0 &&
216            guestfs_is_dir (g, "/run") > 0 &&
217            guestfs_is_dir (g, "/spool") > 0)
218     fs->content = FS_CONTENT_LINUX_VAR;
219   /* Windows root?
220    * Note that if a Windows guest has multiple disks and applications
221    * are installed on those other disks, then those other disks will
222    * contain "/Program Files" and "/System Volume Information".  Those
223    * would *not* be Windows root disks.  (RHBZ#674130)
224    */
225   else if (is_file_nocase (g, "/AUTOEXEC.BAT") > 0 ||
226            is_dir_nocase (g, "/WINDOWS/SYSTEM32") > 0 ||
227            is_dir_nocase (g, "/WIN32/SYSTEM32") > 0 ||
228            is_dir_nocase (g, "/WINNT/SYSTEM32") > 0 ||
229            is_file_nocase (g, "/boot.ini") > 0 ||
230            is_file_nocase (g, "/ntldr") > 0) {
231     fs->is_root = 1;
232     fs->content = FS_CONTENT_WINDOWS_ROOT;
233     fs->format = OS_FORMAT_INSTALLED;
234     if (guestfs___check_windows_root (g, fs) == -1)
235       return -1;
236   }
237   /* Windows volume with installed applications (but not root)? */
238   else if (is_dir_nocase (g, "/System Volume Information") > 0 &&
239            is_dir_nocase (g, "/Program Files") > 0)
240     fs->content = FS_CONTENT_WINDOWS_VOLUME_WITH_APPS;
241   /* Windows volume (but not root)? */
242   else if (is_dir_nocase (g, "/System Volume Information") > 0)
243     fs->content = FS_CONTENT_WINDOWS_VOLUME;
244   /* Install CD/disk?  Skip these checks if it's not a whole device
245    * (eg. CD) or the first partition (eg. bootable USB key).
246    */
247   else if ((is_block || is_partnum == 1) &&
248            (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0 ||
249             guestfs_is_dir (g, "/EFI/BOOT") > 0 ||
250             guestfs_is_file (g, "/images/install.img") > 0 ||
251             guestfs_is_dir (g, "/.disk") > 0 ||
252             guestfs_is_file (g, "/.discinfo") > 0 ||
253             guestfs_is_file (g, "/i386/txtsetup.sif") > 0 ||
254             guestfs_is_file (g, "/amd64/txtsetup.sif")) > 0) {
255     fs->is_root = 1;
256     fs->content = FS_CONTENT_INSTALLER;
257     fs->format = OS_FORMAT_INSTALLER;
258     if (guestfs___check_installer_root (g, fs) == -1)
259       return -1;
260   }
261
262   /* The above code should have set fs->type and fs->distro fields, so
263    * we can now guess the package management system.
264    */
265   check_package_format (g, fs);
266   check_package_management (g, fs);
267
268   return 0;
269 }
270
271 static int
272 extend_fses (guestfs_h *g)
273 {
274   size_t n = g->nr_fses + 1;
275   struct inspect_fs *p;
276
277   p = realloc (g->fses, n * sizeof (struct inspect_fs));
278   if (p == NULL) {
279     perrorf (g, "realloc");
280     return -1;
281   }
282
283   g->fses = p;
284   g->nr_fses = n;
285
286   memset (&g->fses[n-1], 0, sizeof (struct inspect_fs));
287
288   return 0;
289 }
290
291 static int
292 is_file_nocase (guestfs_h *g, const char *path)
293 {
294   char *p;
295   int r;
296
297   p = guestfs___case_sensitive_path_silently (g, path);
298   if (!p)
299     return 0;
300   r = guestfs_is_file (g, p);
301   free (p);
302   return r > 0;
303 }
304
305 static int
306 is_dir_nocase (guestfs_h *g, const char *path)
307 {
308   char *p;
309   int r;
310
311   p = guestfs___case_sensitive_path_silently (g, path);
312   if (!p)
313     return 0;
314   r = guestfs_is_dir (g, p);
315   free (p);
316   return r > 0;
317 }
318
319 /* Parse small, unsigned ints, as used in version numbers. */
320 int
321 guestfs___parse_unsigned_int (guestfs_h *g, const char *str)
322 {
323   long ret;
324   int r = xstrtol (str, NULL, 10, &ret, "");
325   if (r != LONGINT_OK) {
326     error (g, _("could not parse integer in version number: %s"), str);
327     return -1;
328   }
329   return ret;
330 }
331
332 /* Like parse_unsigned_int, but ignore trailing stuff. */
333 int
334 guestfs___parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str)
335 {
336   long ret;
337   int r = xstrtol (str, NULL, 10, &ret, NULL);
338   if (r != LONGINT_OK) {
339     error (g, _("could not parse integer in version number: %s"), str);
340     return -1;
341   }
342   return ret;
343 }
344
345 /* Parse generic MAJOR.MINOR from the fs->product_name string. */
346 int
347 guestfs___parse_major_minor (guestfs_h *g, struct inspect_fs *fs)
348 {
349   char *major, *minor;
350
351   if (match2 (g, fs->product_name, re_major_minor, &major, &minor)) {
352     fs->major_version = guestfs___parse_unsigned_int (g, major);
353     free (major);
354     if (fs->major_version == -1) {
355       free (minor);
356       return -1;
357     }
358     fs->minor_version = guestfs___parse_unsigned_int (g, minor);
359     free (minor);
360     if (fs->minor_version == -1)
361       return -1;
362   }
363   return 0;
364 }
365
366 /* At the moment, package format and package management is just a
367  * simple function of the distro and major_version fields, so these
368  * can never return an error.  We might be cleverer in future.
369  */
370 static void
371 check_package_format (guestfs_h *g, struct inspect_fs *fs)
372 {
373   switch (fs->distro) {
374   case OS_DISTRO_FEDORA:
375   case OS_DISTRO_MEEGO:
376   case OS_DISTRO_REDHAT_BASED:
377   case OS_DISTRO_RHEL:
378   case OS_DISTRO_MANDRIVA:
379   case OS_DISTRO_CENTOS:
380   case OS_DISTRO_SCIENTIFIC_LINUX:
381     fs->package_format = OS_PACKAGE_FORMAT_RPM;
382     break;
383
384   case OS_DISTRO_DEBIAN:
385   case OS_DISTRO_UBUNTU:
386   case OS_DISTRO_LINUX_MINT:
387     fs->package_format = OS_PACKAGE_FORMAT_DEB;
388     break;
389
390   case OS_DISTRO_ARCHLINUX:
391     fs->package_format = OS_PACKAGE_FORMAT_PACMAN;
392     break;
393   case OS_DISTRO_GENTOO:
394     fs->package_format = OS_PACKAGE_FORMAT_EBUILD;
395     break;
396   case OS_DISTRO_PARDUS:
397     fs->package_format = OS_PACKAGE_FORMAT_PISI;
398     break;
399
400   case OS_DISTRO_SLACKWARE:
401   case OS_DISTRO_WINDOWS:
402   case OS_DISTRO_UNKNOWN:
403   default:
404     fs->package_format = OS_PACKAGE_FORMAT_UNKNOWN;
405     break;
406   }
407 }
408
409 static void
410 check_package_management (guestfs_h *g, struct inspect_fs *fs)
411 {
412   switch (fs->distro) {
413   case OS_DISTRO_FEDORA:
414   case OS_DISTRO_MEEGO:
415     fs->package_management = OS_PACKAGE_MANAGEMENT_YUM;
416     break;
417
418   case OS_DISTRO_REDHAT_BASED:
419   case OS_DISTRO_RHEL:
420   case OS_DISTRO_CENTOS:
421   case OS_DISTRO_SCIENTIFIC_LINUX:
422     if (fs->major_version >= 5)
423       fs->package_management = OS_PACKAGE_MANAGEMENT_YUM;
424     else
425       fs->package_management = OS_PACKAGE_MANAGEMENT_UP2DATE;
426     break;
427
428   case OS_DISTRO_DEBIAN:
429   case OS_DISTRO_UBUNTU:
430   case OS_DISTRO_LINUX_MINT:
431     fs->package_management = OS_PACKAGE_MANAGEMENT_APT;
432     break;
433
434   case OS_DISTRO_ARCHLINUX:
435     fs->package_management = OS_PACKAGE_MANAGEMENT_PACMAN;
436     break;
437   case OS_DISTRO_GENTOO:
438     fs->package_management = OS_PACKAGE_MANAGEMENT_PORTAGE;
439     break;
440   case OS_DISTRO_PARDUS:
441     fs->package_management = OS_PACKAGE_MANAGEMENT_PISI;
442     break;
443   case OS_DISTRO_MANDRIVA:
444     fs->package_management = OS_PACKAGE_MANAGEMENT_URPMI;
445     break;
446
447   case OS_DISTRO_SLACKWARE:
448   case OS_DISTRO_WINDOWS:
449   case OS_DISTRO_UNKNOWN:
450   default:
451     fs->package_management = OS_PACKAGE_MANAGEMENT_UNKNOWN;
452     break;
453   }
454 }
455
456 /* Get the first line of a small file, without any trailing newline
457  * character.
458  */
459 char *
460 guestfs___first_line_of_file (guestfs_h *g, const char *filename)
461 {
462   char **lines;
463   int64_t size;
464   char *ret;
465
466   /* Don't trust guestfs_head_n not to break with very large files.
467    * Check the file size is something reasonable first.
468    */
469   size = guestfs_filesize (g, filename);
470   if (size == -1)
471     /* guestfs_filesize failed and has already set error in handle */
472     return NULL;
473   if (size > MAX_SMALL_FILE_SIZE) {
474     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
475            filename, size);
476     return NULL;
477   }
478
479   lines = guestfs_head_n (g, 1, filename);
480   if (lines == NULL)
481     return NULL;
482   if (lines[0] == NULL) {
483     error (g, _("%s: file is empty"), filename);
484     guestfs___free_string_list (lines);
485     return NULL;
486   }
487   /* lines[1] should be NULL because of '1' argument above ... */
488
489   ret = lines[0];               /* caller frees */
490   free (lines);                 /* free the array */
491
492   return ret;
493 }
494
495 /* Get the first matching line (using guestfs_egrep{,i}) of a small file,
496  * without any trailing newline character.
497  *
498  * Returns: 1 = returned a line (in *ret)
499  *          0 = no match
500  *          -1 = error
501  */
502 int
503 guestfs___first_egrep_of_file (guestfs_h *g, const char *filename,
504                                const char *eregex, int iflag, char **ret)
505 {
506   char **lines;
507   int64_t size;
508   size_t i;
509
510   /* Don't trust guestfs_egrep not to break with very large files.
511    * Check the file size is something reasonable first.
512    */
513   size = guestfs_filesize (g, filename);
514   if (size == -1)
515     /* guestfs_filesize failed and has already set error in handle */
516     return -1;
517   if (size > MAX_SMALL_FILE_SIZE) {
518     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
519            filename, size);
520     return -1;
521   }
522
523   lines = (!iflag ? guestfs_egrep : guestfs_egrepi) (g, eregex, filename);
524   if (lines == NULL)
525     return -1;
526   if (lines[0] == NULL) {
527     guestfs___free_string_list (lines);
528     return 0;
529   }
530
531   *ret = lines[0];              /* caller frees */
532
533   /* free up any other matches and the array itself */
534   for (i = 1; lines[i] != NULL; ++i)
535     free (lines[i]);
536   free (lines);
537
538   return 1;
539 }
540
541 #endif /* defined(HAVE_PCRE) && defined(HAVE_HIVEX) */