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