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