X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Finspect.c;h=dbb194b969cfa426dca7e69f6f9016e2cae12948;hp=d1bb7bb42802d88daf6f6d53d1c83e277e1223f5;hb=b4618fb060b40ba70f2be28b1b1ad625722a7edf;hpb=8289aa1ad68ec94c87fc4d538f638d8816052d92 diff --git a/src/inspect.c b/src/inspect.c index d1bb7bb..dbb194b 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -31,6 +31,7 @@ #include #include +#include "c-ctype.h" #include "ignore-value.h" #include "xstrtol.h" @@ -51,12 +52,14 @@ static pcre *re_fedora; static pcre *re_rhel_old; static pcre *re_rhel; static pcre *re_rhel_no_minor; -static pcre *re_debian; +static pcre *re_major_minor; static pcre *re_aug_seq; static pcre *re_xdev; static pcre *re_windows_version; static void compile_regexps (void) __attribute__((constructor)); +static void free_regexps (void) __attribute__((destructor)); + static void compile_regexps (void) { @@ -82,12 +85,28 @@ compile_regexps (void) "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0); COMPILE (re_rhel_no_minor, "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)", 0); - COMPILE (re_debian, "(\\d+)\\.(\\d+)", 0); + COMPILE (re_major_minor, "(\\d+)\\.(\\d+)", 0); COMPILE (re_aug_seq, "/\\d+$", 0); COMPILE (re_xdev, "^/dev/(?:h|s|v|xv)d([a-z]\\d*)$", 0); COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0); } +static void +free_regexps (void) +{ + pcre_free (re_file_elf); + pcre_free (re_file_win64); + pcre_free (re_elf_ppc64); + pcre_free (re_fedora); + pcre_free (re_rhel_old); + pcre_free (re_rhel); + pcre_free (re_rhel_no_minor); + pcre_free (re_major_minor); + pcre_free (re_aug_seq); + pcre_free (re_xdev); + pcre_free (re_windows_version); +} + /* Match a regular expression which contains no captures. Returns * true if it matches or false if it doesn't. */ @@ -209,6 +228,15 @@ is_regular_file (const char *filename) static char * cpio_arch (guestfs_h *g, const char *file, const char *path) { + TMP_TEMPLATE_ON_STACK (dir); +#define dir_len (strlen (dir)) +#define initrd_len (dir_len + 16) + char initrd[initrd_len]; +#define cmd_len (dir_len + 256) + char cmd[cmd_len]; +#define bin_len (dir_len + 32) + char bin[bin_len]; + char *ret = NULL; const char *method; @@ -219,20 +247,16 @@ cpio_arch (guestfs_h *g, const char *file, const char *path) else method = "cat"; - char dir[] = "/tmp/initrd.XXXXXX"; -#define dir_len (sizeof dir) if (mkdtemp (dir) == NULL) { perrorf (g, "mkdtemp"); goto out; } - char dir_initrd[dir_len + 16]; - snprintf (dir_initrd, dir_len + 16, "%s/initrd", dir); - if (guestfs_download (g, path, dir_initrd) == -1) + snprintf (initrd, initrd_len, "%s/initrd", dir); + if (guestfs_download (g, path, initrd) == -1) goto out; - char cmd[dir_len + 256]; - snprintf (cmd, dir_len + 256, + snprintf (cmd, cmd_len, "cd %s && %s initrd | cpio --quiet -id " INITRD_BINARIES1, dir, method); int r = system (cmd); @@ -241,11 +265,10 @@ cpio_arch (guestfs_h *g, const char *file, const char *path) goto out; } - char bin[dir_len + 32]; const char *bins[] = INITRD_BINARIES2; size_t i; for (i = 0; i < sizeof bins / sizeof bins[0]; ++i) { - snprintf (bin, dir_len + 32, "%s/%s", dir, bins[i]); + snprintf (bin, bin_len, "%s/%s", dir, bins[i]); if (is_regular_file (bin)) { int flags = g->verbose ? MAGIC_DEBUG : 0; @@ -287,11 +310,14 @@ cpio_arch (guestfs_h *g, const char *file, const char *path) * contain shell meta-characters because of the way it was * constructed above. */ - snprintf (cmd, dir_len + 256, "rm -rf %s", dir); + snprintf (cmd, cmd_len, "rm -rf %s", dir); ignore_value (system (cmd)); return ret; #undef dir_len +#undef initrd_len +#undef cmd_len +#undef bin_len } char * @@ -430,6 +456,7 @@ guestfs___free_inspect_info (guestfs_h *g) free (g->fses[i].device); free (g->fses[i].product_name); free (g->fses[i].arch); + free (g->fses[i].windows_systemroot); size_t j; for (j = 0; j < g->fses[i].nr_fstab; ++j) { free (g->fses[i].fstab[j].device); @@ -476,10 +503,8 @@ static int check_filesystem (guestfs_h *g, const char *device); static int check_linux_root (guestfs_h *g, struct inspect_fs *fs); static int check_fstab (guestfs_h *g, struct inspect_fs *fs); static int check_windows_root (guestfs_h *g, struct inspect_fs *fs); -static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs, - const char *systemroot); -static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs, - const char *systemroot); +static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs); +static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs); static char *resolve_windows_path_silently (guestfs_h *g, const char *); static int extend_fses (guestfs_h *g); static int parse_unsigned_int (guestfs_h *g, const char *str); @@ -597,6 +622,50 @@ check_filesystem (guestfs_h *g, const char *device) return 0; } +/* Set fs->product_name to the first line of the release file. */ +static int +parse_release_file (guestfs_h *g, struct inspect_fs *fs, + const char *release_filename) +{ + char **product_name = guestfs_head_n (g, 1, release_filename); + if (product_name == NULL) + return -1; + if (product_name[0] == NULL) { + error (g, "%s: file is empty", release_filename); + free_string_list (product_name); + return -1; + } + + /* Note that this string becomes owned by the handle and will + * be freed by guestfs___free_inspect_info. + */ + fs->product_name = product_name[0]; + free (product_name); + + return 0; +} + +/* Parse generic MAJOR.MINOR from the fs->product_name string. */ +static int +parse_major_minor (guestfs_h *g, struct inspect_fs *fs) +{ + char *major, *minor; + + if (match2 (g, fs->product_name, re_major_minor, &major, &minor)) { + fs->major_version = parse_unsigned_int (g, major); + free (major); + if (fs->major_version == -1) { + free (minor); + return -1; + } + fs->minor_version = parse_unsigned_int (g, minor); + free (minor); + if (fs->minor_version == -1) + return -1; + } + return 0; +} + /* The currently mounted device is known to be a Linux root. Try to * determine from this the distro, version, etc. Also parse * /etc/fstab to determine the arrangement of mountpoints and @@ -610,20 +679,8 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs) if (guestfs_exists (g, "/etc/redhat-release") > 0) { fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */ - char **product_name = guestfs_head_n (g, 1, "/etc/redhat-release"); - if (product_name == NULL) - return -1; - if (product_name[0] == NULL) { - error (g, "/etc/redhat-release file is empty"); - free_string_list (product_name); + if (parse_release_file (g, fs, "/etc/redhat-release") == -1) return -1; - } - - /* Note that this string becomes owned by the handle and will - * be freed by guestfs___free_inspect_info. - */ - fs->product_name = product_name[0]; - free (product_name); char *major, *minor; if ((major = match1 (g, fs->product_name, re_fedora)) != NULL) { @@ -659,34 +716,27 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs) else if (guestfs_exists (g, "/etc/debian_version") > 0) { fs->distro = OS_DISTRO_DEBIAN; - char **product_name = guestfs_head_n (g, 1, "/etc/debian_version"); - if (product_name == NULL) + if (parse_release_file (g, fs, "/etc/debian_version") == -1) return -1; - if (product_name[0] == NULL) { - error (g, "/etc/debian_version file is empty"); - free_string_list (product_name); + + if (parse_major_minor (g, fs) == -1) return -1; - } + } + else if (guestfs_exists (g, "/etc/pardus-release") > 0) { + fs->distro = OS_DISTRO_PARDUS; - /* Note that this string becomes owned by the handle and will - * be freed by guestfs___free_inspect_info. - */ - fs->product_name = product_name[0]; - free (product_name); + if (parse_release_file (g, fs, "/etc/pardus-release") == -1) + return -1; - char *major, *minor; - if (match2 (g, fs->product_name, re_debian, &major, &minor)) { - fs->major_version = parse_unsigned_int (g, major); - free (major); - if (fs->major_version == -1) { - free (minor); - return -1; - } - fs->minor_version = parse_unsigned_int (g, minor); - free (minor); - if (fs->minor_version == -1) - return -1; - } + if (parse_major_minor (g, fs) == -1) + return -1; + } + else if (guestfs_exists (g, "/etc/arch-release") > 0) { + fs->distro = OS_DISTRO_ARCHLINUX; + + /* /etc/arch-release file is empty and I can't see a way to + * determine the actual release or product string. + */ } /* Determine the architecture. */ @@ -806,17 +856,24 @@ add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, STREQ (mp, "/sys")) return 0; + /* Ignore /dev/fd (floppy disks) (RHBZ#642929) and CD-ROM drives. */ + if ((STRPREFIX (spec, "/dev/fd") && c_isdigit (spec[7])) || + STREQ (spec, "/dev/floppy") || + STREQ (spec, "/dev/cdrom")) + return 0; + /* Resolve UUID= and LABEL= to the actual device. */ char *device = NULL; if (STRPREFIX (spec, "UUID=")) device = guestfs_findfs_uuid (g, &spec[5]); else if (STRPREFIX (spec, "LABEL=")) device = guestfs_findfs_label (g, &spec[6]); - /* Resolve guest block device names. */ - else if (spec[0] == '/') + /* Ignore "/.swap" (Pardus) and pseudo-devices like "tmpfs". */ + else if (STRPREFIX (spec, "/dev/")) + /* Resolve guest block device names. */ device = resolve_fstab_device (g, spec); - /* Also ignore pseudo-devices completely, like spec == "tmpfs". - * If we haven't resolved the device successfully by this point, + + /* If we haven't resolved the device successfully by this point, * we don't care, just ignore it. */ if (device == NULL) @@ -853,39 +910,54 @@ add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, } /* Resolve block device name to the libguestfs device name, eg. - * /dev/xvdb1 => /dev/vdb1. This assumes that disks were added in the - * same order as they appear to the real VM, which is a reasonable - * assumption to make. Return things like LV names unchanged (or - * anything we don't recognize). + * /dev/xvdb1 => /dev/vdb1; and /dev/mapper/VG-LV => /dev/VG/LV. This + * assumes that disks were added in the same order as they appear to + * the real VM, which is a reasonable assumption to make. Return + * anything we don't recognize unchanged. */ static char * resolve_fstab_device (guestfs_h *g, const char *spec) { - char **devices = guestfs_list_devices (g); - if (devices == NULL) - return NULL; + char *a1; + char *device = NULL; - size_t count; - for (count = 0; devices[count] != NULL; count++) - ; + if (STRPREFIX (spec, "/dev/mapper/")) { + /* LVM2 does some strange munging on /dev/mapper paths for VGs and + * LVs which contain '-' character: + * + * > lvcreate LV--test VG--test 32 + * > debug ls /dev/mapper + * VG----test-LV----test + * + * This makes it impossible to reverse those paths directly, so + * we have implemented lvm_canonical_lv_name in the daemon. + */ + device = guestfs_lvm_canonical_lv_name (g, spec); + } + else if ((a1 = match1 (g, spec, re_xdev)) != NULL) { + char **devices = guestfs_list_devices (g); + if (devices == NULL) + return NULL; + + size_t count; + for (count = 0; devices[count] != NULL; count++) + ; - char *device = NULL; - char *a1 = match1 (g, spec, re_xdev); - if (a1) { size_t i = a1[0] - 'a'; /* a1[0] is always [a-z] because of regex. */ if (i < count) { size_t len = strlen (devices[i]) + strlen (a1) + 16; device = safe_malloc (g, len); snprintf (device, len, "%s%s", devices[i], &a1[1]); } - } else { + + free (a1); + free_string_list (devices); + } + else { /* Didn't match device pattern, return original spec unchanged. */ device = safe_strdup (g, spec); } - free (a1); - free_string_list (devices); - return device; } @@ -915,34 +987,27 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs) return -1; } - /* XXX There is a case for exposing systemroot and many variables - * from the registry through the libguestfs API. - */ - if (g->verbose) fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot); - if (check_windows_arch (g, fs, systemroot) == -1) { - free (systemroot); + /* Freed by guestfs___free_inspect_info. */ + fs->windows_systemroot = systemroot; + + if (check_windows_arch (g, fs) == -1) return -1; - } - if (check_windows_registry (g, fs, systemroot) == -1) { - free (systemroot); + if (check_windows_registry (g, fs) == -1) return -1; - } - free (systemroot); return 0; } static int -check_windows_arch (guestfs_h *g, struct inspect_fs *fs, - const char *systemroot) +check_windows_arch (guestfs_h *g, struct inspect_fs *fs) { - size_t len = strlen (systemroot) + 32; + size_t len = strlen (fs->windows_systemroot) + 32; char cmd_exe[len]; - snprintf (cmd_exe, len, "%s/system32/cmd.exe", systemroot); + snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot); char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe); if (!cmd_exe_path) @@ -962,12 +1027,19 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs, * registry fields available to callers. */ static int -check_windows_registry (guestfs_h *g, struct inspect_fs *fs, - const char *systemroot) +check_windows_registry (guestfs_h *g, struct inspect_fs *fs) { - size_t len = strlen (systemroot) + 64; + TMP_TEMPLATE_ON_STACK (dir); +#define dir_len (strlen (dir)) +#define software_hive_len (dir_len + 16) + char software_hive[software_hive_len]; +#define cmd_len (dir_len + 16) + char cmd[cmd_len]; + + size_t len = strlen (fs->windows_systemroot) + 64; char software[len]; - snprintf (software, len, "%s/system32/config/software", systemroot); + snprintf (software, len, "%s/system32/config/software", + fs->windows_systemroot); char *software_path = resolve_windows_path_silently (g, software); if (!software_path) @@ -980,15 +1052,12 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs, hive_h *h = NULL; hive_value_h *values = NULL; - char dir[] = "/tmp/winreg.XXXXXX"; -#define dir_len 18 if (mkdtemp (dir) == NULL) { perrorf (g, "mkdtemp"); goto out; } - char software_hive[dir_len + 16]; - snprintf (software_hive, dir_len + 16, "%s/software", dir); + snprintf (software_hive, software_hive_len, "%s/software", dir); if (guestfs_download (g, software_path, software_hive) == -1) goto out; @@ -1074,10 +1143,11 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs, * contain shell meta-characters because of the way it was * constructed above. */ - char cmd[dir_len + 16]; - snprintf (cmd, dir_len + 16, "rm -rf %s", dir); + snprintf (cmd, cmd_len, "rm -rf %s", dir); ignore_value (system (cmd)); #undef dir_len +#undef software_hive_len +#undef cmd_len return ret; } @@ -1182,8 +1252,10 @@ guestfs__inspect_get_distro (guestfs_h *g, const char *root) char *ret; switch (fs->distro) { + case OS_DISTRO_ARCHLINUX: ret = safe_strdup (g, "archlinux"); break; case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break; case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break; + case OS_DISTRO_PARDUS: ret = safe_strdup (g, "pardus"); break; case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break; case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break; case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break; @@ -1193,7 +1265,7 @@ guestfs__inspect_get_distro (guestfs_h *g, const char *root) return ret; } -int +int guestfs__inspect_get_major_version (guestfs_h *g, const char *root) { struct inspect_fs *fs = search_for_root (g, root); @@ -1203,7 +1275,7 @@ guestfs__inspect_get_major_version (guestfs_h *g, const char *root) return fs->major_version; } -int +int guestfs__inspect_get_minor_version (guestfs_h *g, const char *root) { struct inspect_fs *fs = search_for_root (g, root); @@ -1223,6 +1295,21 @@ guestfs__inspect_get_product_name (guestfs_h *g, const char *root) return safe_strdup (g, fs->product_name ? : "unknown"); } +char * +guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + if (!fs->windows_systemroot) { + error (g, _("not a Windows guest, or systemroot could not be determined")); + return NULL; + } + + return safe_strdup (g, fs->windows_systemroot); +} + char ** guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root) { @@ -1295,3 +1382,140 @@ guestfs__inspect_get_filesystems (guestfs_h *g, const char *root) return ret; } + +/* List filesystems. + * + * The current implementation just uses guestfs_vfs_type and doesn't + * try mounting anything, but we reserve the right in future to try + * mounting filesystems. + */ + +static void remove_from_list (char **list, const char *item); +static void check_with_vfs_type (guestfs_h *g, const char *dev, char ***ret, size_t *ret_size); + +char ** +guestfs__list_filesystems (guestfs_h *g) +{ + size_t i; + char **ret; + size_t ret_size; + + ret = safe_malloc (g, sizeof (char *)); + ret[0] = NULL; + ret_size = 0; + + /* Look to see if any devices directly contain filesystems + * (RHBZ#590167). However vfs-type will fail to tell us anything + * useful about devices which just contain partitions, so we also + * get the list of partitions and exclude the corresponding devices + * by using part-to-dev. + */ + char **devices; + devices = guestfs_list_devices (g); + if (devices == NULL) { + free_string_list (ret); + return NULL; + } + char **partitions; + partitions = guestfs_list_partitions (g); + if (partitions == NULL) { + free_string_list (devices); + free_string_list (ret); + return NULL; + } + + for (i = 0; partitions[i] != NULL; ++i) { + char *dev = guestfs_part_to_dev (g, partitions[i]); + if (dev) + remove_from_list (devices, dev); + free (dev); + } + + /* Use vfs-type to check for filesystems on devices. */ + for (i = 0; devices[i] != NULL; ++i) + check_with_vfs_type (g, devices[i], &ret, &ret_size); + free_string_list (devices); + + /* Use vfs-type to check for filesystems on partitions. */ + for (i = 0; partitions[i] != NULL; ++i) + check_with_vfs_type (g, partitions[i], &ret, &ret_size); + free_string_list (partitions); + + if (feature_available (g, "lvm2")) { + /* Use vfs-type to check for filesystems on LVs. */ + char **lvs; + lvs = guestfs_lvs (g); + if (lvs == NULL) { + free_string_list (ret); + return NULL; + } + + for (i = 0; lvs[i] != NULL; ++i) + check_with_vfs_type (g, lvs[i], &ret, &ret_size); + free_string_list (lvs); + } + + return ret; +} + +/* If 'item' occurs in 'list', remove and free it. */ +static void +remove_from_list (char **list, const char *item) +{ + size_t i; + + for (i = 0; list[i] != NULL; ++i) + if (STREQ (list[i], item)) { + free (list[i]); + for (; list[i+1] != NULL; ++i) + list[i] = list[i+1]; + list[i] = NULL; + return; + } +} + +/* Use vfs-type to look for a filesystem of some sort on 'dev'. + * Apart from some types which we ignore, add the result to the + * 'ret' string list. + */ +static void +check_with_vfs_type (guestfs_h *g, const char *device, + char ***ret, size_t *ret_size) +{ + char *v; + + guestfs_error_handler_cb old_error_cb = g->error_cb; + g->error_cb = NULL; + char *vfs_type = guestfs_vfs_type (g, device); + g->error_cb = old_error_cb; + + if (!vfs_type) + v = safe_strdup (g, "unknown"); + else { + /* Ignore all "*_member" strings. In libblkid these are returned + * for things which are members of some RAID or LVM set, most + * importantly "LVM2_member" which is a PV. + */ + size_t n = strlen (vfs_type); + if (n >= 7 && STREQ (&vfs_type[n-7], "_member")) { + free (vfs_type); + return; + } + + /* Ignore LUKS-encrypted partitions. These are also containers. */ + if (STREQ (vfs_type, "crypto_LUKS")) { + free (vfs_type); + return; + } + + v = vfs_type; + } + + /* Extend the return array. */ + size_t i = *ret_size; + *ret_size += 2; + *ret = safe_realloc (g, *ret, (*ret_size + 1) * sizeof (char *)); + (*ret)[i] = safe_strdup (g, device); + (*ret)[i+1] = v; + (*ret)[i+2] = NULL; +}