X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=blobdiff_plain;f=src%2Finspect.c;h=c7182b4ef5ebc61b170de879073b95e14507419d;hp=962fd0092ae3818ba7cd495fa2053363e801caa2;hb=3c1f762abed92f7a358f3bc93e3396d0606b18ad;hpb=9674a73c08e128d8a7a39a6be6140c373c0e61f0 diff --git a/src/inspect.c b/src/inspect.c index 962fd00..c7182b4 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -1,5 +1,5 @@ /* libguestfs - * Copyright (C) 2010 Red Hat Inc. + * Copyright (C) 2010-2011 Red Hat Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -23,8 +23,11 @@ #include #include #include +#include #include #include +#include +#include #ifdef HAVE_PCRE #include @@ -45,6 +48,31 @@ #if defined(HAVE_PCRE) && defined(HAVE_HIVEX) +/* Some limits on what we will read, for safety. */ + +/* Small text configuration files. + * + * The upper limit is for general files that we grep or download. The + * largest such file is probably "txtsetup.sif" from Windows CDs + * (~500K). This number has to be larger than any legitimate file and + * smaller than the protocol message size. + * + * The lower limit is for files parsed by Augeas on the daemon side, + * where Augeas is running in reduced memory and can potentially + * create a lot of metadata so we really need to be careful about + * those. + */ +#define MAX_SMALL_FILE_SIZE (2 * 1000 * 1000) +#define MAX_AUGEAS_FILE_SIZE (100 * 1000) + +/* Maximum Windows Registry hive that we will download to /tmp. Some + * registries can be legitimately very large. + */ +#define MAX_REGISTRY_SIZE (100 * 1000 * 1000) + +/* Maximum RPM or dpkg database we will download to /tmp. */ +#define MAX_PKG_DB_SIZE (10 * 1000 * 1000) + /* Compile all the regular expressions once when the shared library is * loaded. PCRE is thread safe so we're supposedly OK here if * multiple threads call into the libguestfs API functions below @@ -81,11 +109,11 @@ compile_regexps (void) COMPILE (re_fedora, "Fedora release (\\d+)", 0); COMPILE (re_rhel_old, - "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+).*Update (\\d+)", 0); + "(?:Red Hat|CentOS|Scientific Linux).*release (\\d+).*Update (\\d+)", 0); COMPILE (re_rhel, - "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0); + "(?:Red Hat|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0); COMPILE (re_rhel_no_minor, - "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)", 0); + "(?:Red Hat|CentOS|Scientific Linux).*release (\\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); @@ -110,7 +138,7 @@ free_regexps (void) } /* The main inspection code. */ -static int check_for_filesystem_on (guestfs_h *g, const char *device); +static int check_for_filesystem_on (guestfs_h *g, const char *device, int is_block, int is_partnum); char ** guestfs__inspect_os (guestfs_h *g) @@ -133,7 +161,7 @@ guestfs__inspect_os (guestfs_h *g) size_t i; for (i = 0; devices[i] != NULL; ++i) { - if (check_for_filesystem_on (g, devices[i]) == -1) { + if (check_for_filesystem_on (g, devices[i], 1, 0) == -1) { guestfs___free_string_list (devices); guestfs___free_inspect_info (g); return NULL; @@ -150,7 +178,7 @@ guestfs__inspect_os (guestfs_h *g) } for (i = 0; partitions[i] != NULL; ++i) { - if (check_for_filesystem_on (g, partitions[i]) == -1) { + if (check_for_filesystem_on (g, partitions[i], 0, i+1) == -1) { guestfs___free_string_list (partitions); guestfs___free_inspect_info (g); return NULL; @@ -168,7 +196,7 @@ guestfs__inspect_os (guestfs_h *g) } for (i = 0; lvs[i] != NULL; ++i) { - if (check_for_filesystem_on (g, lvs[i]) == -1) { + if (check_for_filesystem_on (g, lvs[i], 0, 0) == -1) { guestfs___free_string_list (lvs); guestfs___free_inspect_info (g); return NULL; @@ -191,25 +219,39 @@ guestfs__inspect_os (guestfs_h *g) /* Find out if 'device' contains a filesystem. If it does, add * another entry in g->fses. */ -static int check_filesystem (guestfs_h *g, const char *device); +static int check_filesystem (guestfs_h *g, const char *device, int is_block, int is_partnum); static int check_linux_root (guestfs_h *g, struct inspect_fs *fs); static int check_freebsd_root (guestfs_h *g, struct inspect_fs *fs); +static int check_installer_root (guestfs_h *g, struct inspect_fs *fs); static void check_architecture (guestfs_h *g, struct inspect_fs *fs); +static int check_hostname_unix (guestfs_h *g, struct inspect_fs *fs); +static int check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs); +static int check_hostname_freebsd (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); -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 check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs); +static int check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs); +static char *map_registry_disk_blob (guestfs_h *g, const char *blob); +static char *case_sensitive_path_silently (guestfs_h *g, const char *); +static int is_file_nocase (guestfs_h *g, const char *); +static int is_dir_nocase (guestfs_h *g, const char *); static int extend_fses (guestfs_h *g); static int parse_unsigned_int (guestfs_h *g, const char *str); +static int parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str); static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, const char *spec, const char *mp); static char *resolve_fstab_device (guestfs_h *g, const char *spec); static void check_package_format (guestfs_h *g, struct inspect_fs *fs); static void check_package_management (guestfs_h *g, struct inspect_fs *fs); +static int download_to_tmp (guestfs_h *g, const char *filename, const char *basename, int64_t max_size); +static int inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename, int (*f) (guestfs_h *, struct inspect_fs *)); +static char *first_line_of_file (guestfs_h *g, const char *filename); +static int first_egrep_of_file (guestfs_h *g, const char *filename, const char *eregex, int iflag, char **ret); static int -check_for_filesystem_on (guestfs_h *g, const char *device) +check_for_filesystem_on (guestfs_h *g, const char *device, + int is_block, int is_partnum) { /* Get vfs-type in order to check if it's a Linux(?) swap device. * If there's an error we should ignore it, so to do that we have to @@ -222,9 +264,9 @@ check_for_filesystem_on (guestfs_h *g, const char *device) int is_swap = vfs_type && STREQ (vfs_type, "swap"); - if (g->verbose) - fprintf (stderr, "check_for_filesystem_on: %s (%s)\n", - device, vfs_type ? vfs_type : "failed to get vfs type"); + debug (g, "check_for_filesystem_on: %s %d %d (%s)", + device, is_block, is_partnum, + vfs_type ? vfs_type : "failed to get vfs type"); if (is_swap) { free (vfs_type); @@ -245,7 +287,7 @@ check_for_filesystem_on (guestfs_h *g, const char *device) return 0; /* Do the rest of the checks. */ - r = check_filesystem (g, device); + r = check_filesystem (g, device, is_block, is_partnum); /* Unmount the filesystem. */ if (guestfs_umount_all (g) == -1) @@ -254,8 +296,15 @@ check_for_filesystem_on (guestfs_h *g, const char *device) return r; } +/* is_block and is_partnum are just hints: is_block is true if the + * filesystem is a whole block device (eg. /dev/sda). is_partnum + * is > 0 if the filesystem is a direct partition, and in this case + * it is the partition number counting from 1 + * (eg. /dev/sda1 => is_partnum == 1). + */ static int -check_filesystem (guestfs_h *g, const char *device) +check_filesystem (guestfs_h *g, const char *device, + int is_block, int is_partnum) { if (extend_fses (g) == -1) return -1; @@ -288,6 +337,7 @@ check_filesystem (guestfs_h *g, const char *device) fs->is_root = 1; fs->content = FS_CONTENT_FREEBSD_ROOT; + fs->format = OS_FORMAT_INSTALLED; if (check_freebsd_root (g, fs) == -1) return -1; } @@ -297,6 +347,7 @@ check_filesystem (guestfs_h *g, const char *device) guestfs_is_file (g, "/etc/fstab") > 0) { fs->is_root = 1; fs->content = FS_CONTENT_LINUX_ROOT; + fs->format = OS_FORMAT_INSTALLED; if (check_linux_root (g, fs) == -1) return -1; } @@ -319,23 +370,48 @@ check_filesystem (guestfs_h *g, const char *device) guestfs_is_dir (g, "/run") > 0 && guestfs_is_dir (g, "/spool") > 0) fs->content = FS_CONTENT_LINUX_VAR; - /* Windows root? */ - else if (guestfs_is_file (g, "/AUTOEXEC.BAT") > 0 || - guestfs_is_file (g, "/autoexec.bat") > 0 || - guestfs_is_dir (g, "/Program Files") > 0 || - guestfs_is_dir (g, "/WINDOWS") > 0 || - guestfs_is_dir (g, "/Windows") > 0 || - guestfs_is_dir (g, "/windows") > 0 || - guestfs_is_dir (g, "/WIN32") > 0 || - guestfs_is_dir (g, "/Win32") > 0 || - guestfs_is_dir (g, "/WINNT") > 0 || - guestfs_is_file (g, "/boot.ini") > 0 || - guestfs_is_file (g, "/ntldr") > 0) { + /* Windows root? + * Note that if a Windows guest has multiple disks and applications + * are installed on those other disks, then those other disks will + * contain "/Program Files" and "/System Volume Information". Those + * would *not* be Windows root disks. (RHBZ#674130) + */ + else if (is_file_nocase (g, "/AUTOEXEC.BAT") > 0 || + is_dir_nocase (g, "/WINDOWS") > 0 || + is_dir_nocase (g, "/WIN32") > 0 || + is_dir_nocase (g, "/WINNT") > 0 || + is_file_nocase (g, "/boot.ini") > 0 || + is_file_nocase (g, "/ntldr") > 0) { fs->is_root = 1; fs->content = FS_CONTENT_WINDOWS_ROOT; + fs->format = OS_FORMAT_INSTALLED; if (check_windows_root (g, fs) == -1) return -1; } + /* Windows volume with installed applications (but not root)? */ + else if (is_dir_nocase (g, "/System Volume Information") > 0 && + is_dir_nocase (g, "/Program Files") > 0) + fs->content = FS_CONTENT_WINDOWS_VOLUME_WITH_APPS; + /* Windows volume (but not root)? */ + else if (is_dir_nocase (g, "/System Volume Information") > 0) + fs->content = FS_CONTENT_WINDOWS_VOLUME; + /* Install CD/disk? Skip these checks if it's not a whole device + * (eg. CD) or the first partition (eg. bootable USB key). + */ + else if ((is_block || is_partnum == 1) && + (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0 || + guestfs_is_dir (g, "/EFI/BOOT") > 0 || + guestfs_is_file (g, "/images/install.img") > 0 || + guestfs_is_dir (g, "/.disk") > 0 || + guestfs_is_file (g, "/.discinfo") > 0 || + guestfs_is_file (g, "/i386/txtsetup.sif") > 0 || + guestfs_is_file (g, "/amd64/txtsetup.sif")) > 0) { + fs->is_root = 1; + fs->content = FS_CONTENT_INSTALLER; + fs->format = OS_FORMAT_INSTALLER; + if (check_installer_root (g, fs) == -1) + return -1; + } return 0; } @@ -345,21 +421,9 @@ 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) + fs->product_name = first_line_of_file (g, release_filename); + if (fs->product_name == NULL) return -1; - if (product_name[0] == NULL) { - error (g, _("%s: file is empty"), release_filename); - guestfs___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; } @@ -389,16 +453,46 @@ parse_major_minor (guestfs_h *g, struct inspect_fs *fs) * DISTRIB_RELEASE=10.04 # Version * DISTRIB_CODENAME=lucid * DISTRIB_DESCRIPTION="Ubuntu 10.04.1 LTS" # Product name - * In theory other distros could have this LSB file, but none do. + * + * [Ubuntu-derived ...] Linux Mint was found to have this: + * DISTRIB_ID=LinuxMint + * DISTRIB_RELEASE=10 + * DISTRIB_CODENAME=julia + * DISTRIB_DESCRIPTION="Linux Mint 10 Julia" + * Linux Mint also has /etc/linuxmint/info with more information, + * but we can use the LSB file. + * + * Mandriva has: + * LSB_VERSION=lsb-4.0-amd64:lsb-4.0-noarch + * DISTRIB_ID=MandrivaLinux + * DISTRIB_RELEASE=2010.1 + * DISTRIB_CODENAME=Henry_Farman + * DISTRIB_DESCRIPTION="Mandriva Linux 2010.1" + * Mandriva also has a normal release file called /etc/mandriva-release. */ static int parse_lsb_release (guestfs_h *g, struct inspect_fs *fs) { + const char *filename = "/etc/lsb-release"; + int64_t size; char **lines; size_t i; int r = 0; - lines = guestfs_head_n (g, 10, "/etc/lsb-release"); + /* Don't trust guestfs_head_n not to break with very large files. + * Check the file size is something reasonable first. + */ + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return -1; + if (size > MAX_SMALL_FILE_SIZE) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + return -1; + } + + lines = guestfs_head_n (g, 10, filename); if (lines == NULL) return -1; @@ -408,6 +502,16 @@ parse_lsb_release (guestfs_h *g, struct inspect_fs *fs) fs->distro = OS_DISTRO_UBUNTU; r = 1; } + else if (fs->distro == 0 && + STREQ (lines[i], "DISTRIB_ID=LinuxMint")) { + fs->distro = OS_DISTRO_LINUX_MINT; + r = 1; + } + else if (fs->distro == 0 && + STREQ (lines[i], "DISTRIB_ID=MandrivaLinux")) { + fs->distro = OS_DISTRO_MANDRIVA; + r = 1; + } else if (STRPREFIX (lines[i], "DISTRIB_RELEASE=")) { char *major, *minor; if (match2 (g, &lines[i][16], re_major_minor, &major, &minor)) { @@ -545,6 +649,15 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs) if (parse_major_minor (g, fs) == -1) return -1; } + else if (guestfs_exists (g, "/etc/slackware-version") > 0) { + fs->distro = OS_DISTRO_SLACKWARE; + + if (parse_release_file (g, fs, "/etc/slackware-version") == -1) + return -1; + + if (parse_major_minor (g, fs) == -1) + return -1; + } skip_release_checks:; @@ -560,7 +673,11 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs) * which filesystems are used by the operating system and how they * are mounted. */ - if (check_fstab (g, fs) == -1) + if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1) + return -1; + + /* Determine hostname. */ + if (check_hostname_unix (g, fs) == -1) return -1; return 0; @@ -570,8 +687,6 @@ check_linux_root (guestfs_h *g, struct inspect_fs *fs) static int check_freebsd_root (guestfs_h *g, struct inspect_fs *fs) { - int r; - fs->type = OS_TYPE_FREEBSD; /* FreeBSD has no authoritative version file. The version number is @@ -591,8 +706,361 @@ check_freebsd_root (guestfs_h *g, struct inspect_fs *fs) check_architecture (g, fs); /* We already know /etc/fstab exists because it's part of the test above. */ - if (check_fstab (g, fs) == -1) + if (inspect_with_augeas (g, fs, "/etc/fstab", check_fstab) == -1) + return -1; + + /* Determine hostname. */ + if (check_hostname_unix (g, fs) == -1) + return -1; + + return 0; +} + +/* Debian/Ubuntu install disks are easy ... + * + * These files are added by the debian-cd program, and it is worth + * looking at the source code to determine exact values, in + * particular '/usr/share/debian-cd/tools/start_new_disc' + * + * XXX Architecture? We could parse it out of the product name + * string, but that seems quite hairy. We could look for the names + * of packages. Also note that some Debian install disks are + * multiarch. + */ +static int +check_debian_installer_root (guestfs_h *g, struct inspect_fs *fs) +{ + fs->product_name = first_line_of_file (g, "/.disk/info"); + if (!fs->product_name) + return -1; + + fs->type = OS_TYPE_LINUX; + if (STRPREFIX (fs->product_name, "Ubuntu")) + fs->distro = OS_DISTRO_UBUNTU; + else if (STRPREFIX (fs->product_name, "Debian")) + fs->distro = OS_DISTRO_DEBIAN; + + (void) parse_major_minor (g, fs); + + if (guestfs_is_file (g, "/.disk/cd_type") > 0) { + char *cd_type = first_line_of_file (g, "/.disk/cd_type"); + if (!cd_type) + return -1; + + if (STRPREFIX (cd_type, "dvd/single") || + STRPREFIX (cd_type, "full_cd/single")) { + fs->is_multipart_disk = 0; + fs->is_netinst_disk = 0; + } + else if (STRPREFIX (cd_type, "dvd") || + STRPREFIX (cd_type, "full_cd")) { + fs->is_multipart_disk = 1; + fs->is_netinst_disk = 0; + } + else if (STRPREFIX (cd_type, "not_complete")) { + fs->is_multipart_disk = 0; + fs->is_netinst_disk = 1; + } + + free (cd_type); + } + + return 0; +} + +/* Take string which must look like "key = value" and find the value. + * There may or may not be spaces before and after the equals sign. + * This function is used by both check_fedora_installer_root and + * check_w2k3_installer_root. + */ +static const char * +find_value (const char *kv) +{ + const char *p; + + p = strchr (kv, '='); + if (!p) + abort (); + + do { + ++p; + } while (c_isspace (*p)); + + return p; +} + +/* Fedora CDs and DVD (not netinst). The /.treeinfo file contains + * an initial section somewhat like this: + * + * [general] + * version = 14 + * arch = x86_64 + * family = Fedora + * variant = Fedora + * discnum = 1 + * totaldiscs = 1 + */ +static int +check_fedora_installer_root (guestfs_h *g, struct inspect_fs *fs) +{ + char *str; + const char *v; + int r; + int discnum = 0, totaldiscs = 0; + + fs->type = OS_TYPE_LINUX; + + r = first_egrep_of_file (g, "/.treeinfo", + "^family = Fedora$", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + fs->distro = OS_DISTRO_FEDORA; + free (str); + } + + r = first_egrep_of_file (g, "/.treeinfo", + "^family = Red Hat Enterprise Linux$", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + fs->distro = OS_DISTRO_RHEL; + free (str); + } + + /* XXX should do major.minor before this */ + r = first_egrep_of_file (g, "/.treeinfo", + "^version = [[:digit:]]+", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + v = find_value (str); + fs->major_version = parse_unsigned_int_ignore_trailing (g, v); + free (str); + if (fs->major_version == -1) + return -1; + } + + r = first_egrep_of_file (g, "/.treeinfo", + "^arch = [-_[:alnum:]]+$", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + v = find_value (str); + fs->arch = safe_strdup (g, v); + free (str); + } + + r = first_egrep_of_file (g, "/.treeinfo", + "^discnum = [[:digit:]]+$", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + v = find_value (str); + discnum = parse_unsigned_int (g, v); + free (str); + if (discnum == -1) + return -1; + } + + r = first_egrep_of_file (g, "/.treeinfo", + "^totaldiscs = [[:digit:]]+$", 0, &str); + if (r == -1) + return -1; + if (r > 0) { + v = find_value (str); + totaldiscs = parse_unsigned_int (g, v); + free (str); + if (totaldiscs == -1) + return -1; + } + + fs->is_multipart_disk = totaldiscs > 0; + /* and what about discnum? */ + + return 0; +} + +/* Linux with /isolinux/isolinux.cfg. + * + * This file is not easily parsable so we have to do our best. + * Look for the "menu title" line which contains: + * menu title Welcome to Fedora 14! # since at least Fedora 10 + * menu title Welcome to Red Hat Enterprise Linux 6.0! + */ +static int +check_isolinux_installer_root (guestfs_h *g, struct inspect_fs *fs) +{ + char *str; + int r; + + fs->type = OS_TYPE_LINUX; + + r = first_egrep_of_file (g, "/isolinux/isolinux.cfg", + "^menu title Welcome to Fedora [[:digit:]]+", + 0, &str); + if (r == -1) + return -1; + if (r > 0) { + fs->distro = OS_DISTRO_FEDORA; + fs->major_version = parse_unsigned_int_ignore_trailing (g, &str[29]); + free (str); + if (fs->major_version == -1) + return -1; + } + + /* XXX parse major.minor */ + r = first_egrep_of_file (g, "/isolinux/isolinux.cfg", + "^menu title Welcome to Red Hat Enterprise Linux [[:digit:]]+", + 0, &str); + if (r == -1) + return -1; + if (r > 0) { + fs->distro = OS_DISTRO_RHEL; + fs->major_version = parse_unsigned_int_ignore_trailing (g, &str[47]); + free (str); + if (fs->major_version == -1) + return -1; + } + + return 0; +} + +/* Windows 2003 and similar versions. + * + * NB: txtsetup file contains Windows \r\n line endings, which guestfs_grep + * does not remove. We have to remove them by hand here. + */ +static void +trim_cr (char *str) +{ + size_t n = strlen (str); + if (n > 0 && str[n-1] == '\r') + str[n-1] = '\0'; +} + +static void +trim_quot (char *str) +{ + size_t n = strlen (str); + if (n > 0 && str[n-1] == '"') + str[n-1] = '\0'; +} + +static int +check_w2k3_installer_root (guestfs_h *g, struct inspect_fs *fs, + const char *txtsetup) +{ + char *str; + const char *v; + int r; + + fs->type = OS_TYPE_WINDOWS; + fs->distro = OS_DISTRO_WINDOWS; + + r = first_egrep_of_file (g, txtsetup, + "^productname[[:space:]]*=[[:space:]]*\"", 1, &str); + if (r == -1) + return -1; + if (r > 0) { + trim_cr (str); + trim_quot (str); + v = find_value (str); + fs->product_name = safe_strdup (g, v+1); + free (str); + } + + r = first_egrep_of_file (g, txtsetup, + "^majorversion[[:space:]]*=[[:space:]]*[[:digit:]]+", + 1, &str); + if (r == -1) + return -1; + if (r > 0) { + trim_cr (str); + v = find_value (str); + fs->major_version = parse_unsigned_int_ignore_trailing (g, v); + free (str); + if (fs->major_version == -1) + return -1; + } + + r = first_egrep_of_file (g, txtsetup, + "^minorversion[[:space:]]*=[[:space:]]*[[:digit:]]+", + 1, &str); + if (r == -1) + return -1; + if (r > 0) { + trim_cr (str); + v = find_value (str); + fs->minor_version = parse_unsigned_int_ignore_trailing (g, v); + free (str); + if (fs->minor_version == -1) + return -1; + } + + /* This is the windows systemroot that would be chosen on + * installation by default, although not necessarily the one that + * the user will finally choose. + */ + r = first_egrep_of_file (g, txtsetup, "^defaultpath[[:space:]]*=[[:space:]]*", + 1, &str); + if (r == -1) return -1; + if (r > 0) { + trim_cr (str); + v = find_value (str); + fs->windows_systemroot = safe_strdup (g, v); + free (str); + } + + return 0; +} + +/* The currently mounted device is very likely to be an installer. */ +static int +check_installer_root (guestfs_h *g, struct inspect_fs *fs) +{ + /* The presence of certain files indicates a live CD. + * + * XXX Fedora netinst contains a ~120MB squashfs called + * /images/install.img. However this is not a live CD (unlike the + * Fedora live CDs which contain the same, but larger file). We + * need to unpack this and look inside to tell the difference. + */ + if (guestfs_is_file (g, "/casper/filesystem.squashfs") > 0) + fs->is_live_disk = 1; + + /* Debian/Ubuntu. */ + if (guestfs_is_file (g, "/.disk/info") > 0) { + if (check_debian_installer_root (g, fs) == -1) + return -1; + } + + /* Fedora CDs and DVD (not netinst). */ + else if (guestfs_is_file (g, "/.treeinfo") > 0) { + if (check_fedora_installer_root (g, fs) == -1) + return -1; + } + + /* Linux with /isolinux/isolinux.cfg. */ + else if (guestfs_is_file (g, "/isolinux/isolinux.cfg") > 0) { + if (check_isolinux_installer_root (g, fs) == -1) + return -1; + } + + /* Windows 2003 64 bit */ + else if (guestfs_is_file (g, "/amd64/txtsetup.sif") > 0) { + fs->arch = safe_strdup (g, "x86_64"); + if (check_w2k3_installer_root (g, fs, "/amd64/txtsetup.sif") == -1) + return -1; + } + + /* Windows 2003 32 bit */ + else if (guestfs_is_file (g, "/i386/txtsetup.sif") > 0) { + fs->arch = safe_strdup (g, "i386"); + if (check_w2k3_installer_root (g, fs, "/i386/txtsetup.sif") == -1) + return -1; + } return 0; } @@ -623,39 +1091,124 @@ check_architecture (guestfs_h *g, struct inspect_fs *fs) } } -static int check_fstab_aug_open (guestfs_h *g, struct inspect_fs *fs); +/* Try several methods to determine the hostname from a Linux or + * FreeBSD guest. Note that type and distro have been set, so we can + * use that information to direct the search. + */ +static int +check_hostname_unix (guestfs_h *g, struct inspect_fs *fs) +{ + switch (fs->type) { + case OS_TYPE_LINUX: + /* Red Hat-derived would be in /etc/sysconfig/network, and + * Debian-derived in the file /etc/hostname. Very old Debian and + * SUSE use /etc/HOSTNAME. It's best to just look for each of + * these files in turn, rather than try anything clever based on + * distro. + */ + if (guestfs_is_file (g, "/etc/HOSTNAME")) { + fs->hostname = first_line_of_file (g, "/etc/HOSTNAME"); + if (fs->hostname == NULL) + return -1; + } + else if (guestfs_is_file (g, "/etc/hostname")) { + fs->hostname = first_line_of_file (g, "/etc/hostname"); + if (fs->hostname == NULL) + return -1; + } + else if (guestfs_is_file (g, "/etc/sysconfig/network")) { + if (inspect_with_augeas (g, fs, "/etc/sysconfig/network", + check_hostname_redhat) == -1) + return -1; + } + break; + + case OS_TYPE_FREEBSD: + /* /etc/rc.conf contains the hostname, but there is no Augeas lens + * for this file. + */ + if (guestfs_is_file (g, "/etc/rc.conf")) { + if (check_hostname_freebsd (g, fs) == -1) + return -1; + } + break; + + case OS_TYPE_WINDOWS: /* not here, see check_windows_system_registry */ + case OS_TYPE_UNKNOWN: + default: + /* nothing, keep GCC warnings happy */; + } + + return 0; +} +/* Parse the hostname from /etc/sysconfig/network. This must be called + * from the inspect_with_augeas wrapper. + */ static int -check_fstab (guestfs_h *g, struct inspect_fs *fs) +check_hostname_redhat (guestfs_h *g, struct inspect_fs *fs) { - int r; + char *hostname; + + hostname = guestfs_aug_get (g, "/files/etc/sysconfig/network/HOSTNAME"); + if (!hostname) + return -1; + + fs->hostname = hostname; /* freed by guestfs___free_inspect_info */ + return 0; +} + +/* Parse the hostname from /etc/rc.conf. On FreeBSD this file + * contains comments, blank lines and: + * hostname="freebsd8.example.com" + * ifconfig_re0="DHCP" + * keymap="uk.iso" + * sshd_enable="YES" + */ +static int +check_hostname_freebsd (guestfs_h *g, struct inspect_fs *fs) +{ + const char *filename = "/etc/rc.conf"; int64_t size; + char **lines; + size_t i; - /* Security: Refuse to do this if /etc/fstab is huge. */ - size = guestfs_filesize (g, "/etc/fstab"); - if (size == -1 || size > 100000) { - error (g, _("size of /etc/fstab unreasonable (%" PRIi64 " bytes)"), size); + /* Don't trust guestfs_read_lines not to break with very large files. + * Check the file size is something reasonable first. + */ + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return -1; + if (size > MAX_SMALL_FILE_SIZE) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); return -1; } - /* XXX What if !feature_available (g, "augeas")? */ - if (guestfs_aug_init (g, "/", 16|32) == -1) + lines = guestfs_read_lines (g, filename); + if (lines == NULL) return -1; - /* Tell Augeas to only load /etc/fstab (thanks Raphaël Pinson). */ - guestfs_aug_rm (g, "/augeas/load//incl[. != \"/etc/fstab\"]"); - guestfs_aug_load (g); - - r = check_fstab_aug_open (g, fs); - guestfs_aug_close (g); - if (r == -1) - return -1; + for (i = 0; lines[i] != NULL; ++i) { + if (STRPREFIX (lines[i], "hostname=\"") || + STRPREFIX (lines[i], "hostname='")) { + size_t len = strlen (lines[i]) - 10 - 1; + fs->hostname = safe_strndup (g, &lines[i][10], len); + break; + } else if (STRPREFIX (lines[i], "hostname=")) { + size_t len = strlen (lines[i]) - 9; + fs->hostname = safe_strndup (g, &lines[i][9], len); + break; + } + } + guestfs___free_string_list (lines); return 0; } static int -check_fstab_aug_open (guestfs_h *g, struct inspect_fs *fs) +check_fstab (guestfs_h *g, struct inspect_fs *fs) { char **lines = guestfs_aug_ls (g, "/files/etc/fstab"); if (lines == NULL) @@ -775,8 +1328,7 @@ add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, fs->fstab[n-1].device = device; fs->fstab[n-1].mountpoint = mountpoint; - if (g->verbose) - fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint); + debug (g, "fstab: device=%s mountpoint=%s", device, mountpoint); return 0; } @@ -874,7 +1426,7 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs) for (i = 0; systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0]; ++i) { - systemroot = resolve_windows_path_silently (g, systemroots[i]); + systemroot = case_sensitive_path_silently (g, systemroots[i]); } if (!systemroot) { @@ -882,8 +1434,7 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs) return -1; } - if (g->verbose) - fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot); + debug (g, "windows %%SYSTEMROOT%% = %s", systemroot); /* Freed by guestfs___free_inspect_info. */ fs->windows_systemroot = systemroot; @@ -891,12 +1442,17 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs) if (check_windows_arch (g, fs) == -1) return -1; - if (check_windows_registry (g, fs) == -1) + /* Product name and version. */ + if (check_windows_software_registry (g, fs) == -1) return -1; check_package_format (g, fs); check_package_management (g, fs); + /* Hostname. */ + if (check_windows_system_registry (g, fs) == -1) + return -1; + return 0; } @@ -907,7 +1463,7 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs) char cmd_exe[len]; snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot); - char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe); + char *cmd_exe_path = case_sensitive_path_silently (g, cmd_exe); if (!cmd_exe_path) return 0; @@ -925,21 +1481,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) +check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs) { - 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]; + const char *basename = "software"; + char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2]; + snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s", + g->tmpdir, basename); size_t len = strlen (fs->windows_systemroot) + 64; char software[len]; snprintf (software, len, "%s/system32/config/software", fs->windows_systemroot); - char *software_path = resolve_windows_path_silently (g, software); + char *software_path = case_sensitive_path_silently (g, software); if (!software_path) /* If the software hive doesn't exist, just accept that we cannot * find product_name etc. @@ -950,25 +1504,10 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs) hive_h *h = NULL; hive_value_h *values = NULL; - /* Security: Refuse to download registry if it is huge. */ - int64_t size = guestfs_filesize (g, software_path); - if (size == -1 || size > 100000000) { - error (g, _("size of %s unreasonable (%" PRIi64 " bytes)"), - software_path, size); - goto out; - } - - if (mkdtemp (dir) == NULL) { - perrorf (g, "mkdtemp"); - goto out; - } - - snprintf (software_hive, software_hive_len, "%s/software", dir); - - if (guestfs_download (g, software_path, software_hive) == -1) + if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1) goto out; - h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0); + h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0); if (h == NULL) { perrorf (g, "hivex_open"); goto out; @@ -1034,8 +1573,16 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs) free (version); } - - free (key); + else if (STRCASEEQ (key, "InstallationType")) { + fs->product_variant = hivex_value_string (h, values[i]); + if (!fs->product_variant) { + perrorf (g, "hivex_value_string"); + free (key); + goto out; + } + } + + free (key); } ret = 0; @@ -1045,21 +1592,260 @@ check_windows_registry (guestfs_h *g, struct inspect_fs *fs) free (values); free (software_path); - /* Free up the temporary directory. Note the directory name cannot - * contain shell meta-characters because of the way it was - * constructed above. + return ret; +} + +static int +check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) +{ + const char *basename = "system"; + char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2]; + snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s", + g->tmpdir, basename); + + size_t len = strlen (fs->windows_systemroot) + 64; + char system[len]; + snprintf (system, len, "%s/system32/config/system", + fs->windows_systemroot); + + char *system_path = case_sensitive_path_silently (g, system); + if (!system_path) + /* If the system hive doesn't exist, just accept that we cannot + * find hostname etc. + */ + return 0; + + int ret = -1; + hive_h *h = NULL; + hive_node_h root, node; + hive_value_h value, *values = NULL; + int32_t dword; + size_t i, count; + + if (download_to_tmp (g, system_path, basename, MAX_REGISTRY_SIZE) == -1) + goto out; + + h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0); + if (h == NULL) { + perrorf (g, "hivex_open"); + goto out; + } + + root = hivex_root (h); + if (root == 0) { + perrorf (g, "hivex_root"); + goto out; + } + + /* Get the CurrentControlSet. */ + errno = 0; + node = hivex_node_get_child (h, root, "Select"); + if (node == 0) { + if (errno != 0) + perrorf (g, "hivex_node_get_child"); + else + error (g, "hivex: could not locate HKLM\\SYSTEM\\Select"); + goto out; + } + + errno = 0; + value = hivex_node_get_value (h, node, "Current"); + if (value == 0) { + if (errno != 0) + perrorf (g, "hivex_node_get_value"); + else + error (g, "hivex: HKLM\\System\\Select Default entry not found."); + goto out; + } + + /* XXX Should check the type. */ + dword = hivex_value_dword (h, value); + fs->windows_current_control_set = safe_asprintf (g, "ControlSet%03d", dword); + + /* Get the drive mappings. + * This page explains the contents of HKLM\System\MountedDevices: + * http://www.goodells.net/multiboot/partsigs.shtml + */ + errno = 0; + node = hivex_node_get_child (h, root, "MountedDevices"); + if (node == 0) { + if (errno != 0) + perrorf (g, "hivex_node_get_child"); + else + error (g, "hivex: could not locate HKLM\\SYSTEM\\MountedDevices"); + goto out; + } + + values = hivex_node_values (h, node); + + /* Count how many DOS drive letter mappings there are. This doesn't + * ignore removable devices, so it overestimates, but that doesn't + * matter because it just means we'll allocate a few bytes extra. + */ + for (i = count = 0; values[i] != 0; ++i) { + char *key = hivex_value_key (h, values[i]); + if (key == NULL) { + perrorf (g, "hivex_value_key"); + goto out; + } + if (STRCASEEQLEN (key, "\\DosDevices\\", 12) && + c_isalpha (key[12]) && key[13] == ':') + count++; + free (key); + } + + fs->drive_mappings = calloc (2*count + 1, sizeof (char *)); + if (fs->drive_mappings == NULL) { + perrorf (g, "calloc"); + goto out; + } + + for (i = count = 0; values[i] != 0; ++i) { + char *key = hivex_value_key (h, values[i]); + if (key == NULL) { + perrorf (g, "hivex_value_key"); + goto out; + } + if (STRCASEEQLEN (key, "\\DosDevices\\", 12) && + c_isalpha (key[12]) && key[13] == ':') { + /* Get the binary value. Is it a fixed disk? */ + char *blob, *device; + size_t len; + hive_type type; + + blob = hivex_value_value (h, values[i], &type, &len); + if (blob != NULL && type == 3 && len == 12) { + /* Try to map the blob to a known disk and partition. */ + device = map_registry_disk_blob (g, blob); + if (device != NULL) { + fs->drive_mappings[count++] = safe_strndup (g, &key[12], 1); + fs->drive_mappings[count++] = device; + } + } + free (blob); + } + free (key); + } + + /* Get the hostname. */ + const char *hivepath[] = + { fs->windows_current_control_set, "Services", "Tcpip", "Parameters" }; + for (node = root, i = 0; + node != 0 && i < sizeof hivepath / sizeof hivepath[0]; + ++i) { + node = hivex_node_get_child (h, node, hivepath[i]); + } + + if (node == 0) { + perrorf (g, "hivex: cannot locate HKLM\\SYSTEM\\%s\\Services\\Tcpip\\Parameters", + fs->windows_current_control_set); + goto out; + } + + free (values); + values = hivex_node_values (h, node); + + for (i = 0; values[i] != 0; ++i) { + char *key = hivex_value_key (h, values[i]); + if (key == NULL) { + perrorf (g, "hivex_value_key"); + goto out; + } + + if (STRCASEEQ (key, "Hostname")) { + fs->hostname = hivex_value_string (h, values[i]); + if (!fs->hostname) { + perrorf (g, "hivex_value_string"); + free (key); + goto out; + } + } + /* many other interesting fields here ... */ + + free (key); + } + + ret = 0; + + out: + if (h) hivex_close (h); + free (values); + free (system_path); + + return ret; +} + +/* Windows Registry HKLM\SYSTEM\MountedDevices uses a blob of data + * to store partitions. This blob is described here: + * http://www.goodells.net/multiboot/partsigs.shtml + * The following function maps this blob to a libguestfs partition + * name, if possible. + */ +static char * +map_registry_disk_blob (guestfs_h *g, const char *blob) +{ + char **devices = NULL; + struct guestfs_partition_list *partitions = NULL; + char *diskid; + size_t i, j, len; + char *ret = NULL; + uint64_t part_offset; + + /* First 4 bytes are the disk ID. Search all devices to find the + * disk with this disk ID. + */ + devices = guestfs_list_devices (g); + if (devices == NULL) + goto out; + + for (i = 0; devices[i] != NULL; ++i) { + /* Read the disk ID. */ + diskid = guestfs_pread_device (g, devices[i], 4, 0x01b8, &len); + if (diskid == NULL) + continue; + if (len < 4) { + free (diskid); + continue; + } + if (memcmp (diskid, blob, 4) == 0) { /* found it */ + free (diskid); + goto found_disk; + } + free (diskid); + } + goto out; + + found_disk: + /* Next 8 bytes are the offset of the partition in bytes(!) given as + * a 64 bit little endian number. Luckily it's easy to get the + * partition byte offset from guestfs_part_list. */ - snprintf (cmd, cmd_len, "rm -rf %s", dir); - ignore_value (system (cmd)); -#undef dir_len -#undef software_hive_len -#undef cmd_len + part_offset = le64toh (* (uint64_t *) &blob[4]); + + partitions = guestfs_part_list (g, devices[i]); + if (partitions == NULL) + goto out; + for (j = 0; j < partitions->len; ++j) { + if (partitions->val[j].part_start == part_offset) /* found it */ + goto found_partition; + } + goto out; + + found_partition: + /* Construct the full device name. */ + ret = safe_asprintf (g, "%s%d", devices[i], partitions->val[j].part_num); + + out: + if (devices) + guestfs___free_string_list (devices); + if (partitions) + guestfs_free_partition_list (partitions); return ret; } static char * -resolve_windows_path_silently (guestfs_h *g, const char *path) +case_sensitive_path_silently (guestfs_h *g, const char *path) { guestfs_error_handler_cb old_error_cb = g->error_cb; g->error_cb = NULL; @@ -1069,6 +1855,34 @@ resolve_windows_path_silently (guestfs_h *g, const char *path) } static int +is_file_nocase (guestfs_h *g, const char *path) +{ + char *p; + int r; + + p = case_sensitive_path_silently (g, path); + if (!p) + return 0; + r = guestfs_is_file (g, p); + free (p); + return r > 0; +} + +static int +is_dir_nocase (guestfs_h *g, const char *path) +{ + char *p; + int r; + + p = case_sensitive_path_silently (g, path); + if (!p) + return 0; + r = guestfs_is_dir (g, p); + free (p); + return r > 0; +} + +static int extend_fses (guestfs_h *g) { size_t n = g->nr_fses + 1; @@ -1101,6 +1915,19 @@ parse_unsigned_int (guestfs_h *g, const char *str) return ret; } +/* Like parse_unsigned_int, but ignore trailing stuff. */ +static int +parse_unsigned_int_ignore_trailing (guestfs_h *g, const char *str) +{ + long ret; + int r = xstrtol (str, NULL, 10, &ret, NULL); + if (r != LONGINT_OK) { + error (g, _("could not parse integer in version number: %s"), str); + return -1; + } + return ret; +} + /* At the moment, package format and package management is just a * simple function of the distro and major_version fields, so these * can never return an error. We might be cleverer in future. @@ -1113,11 +1940,13 @@ check_package_format (guestfs_h *g, struct inspect_fs *fs) case OS_DISTRO_MEEGO: case OS_DISTRO_REDHAT_BASED: case OS_DISTRO_RHEL: + case OS_DISTRO_MANDRIVA: fs->package_format = OS_PACKAGE_FORMAT_RPM; break; case OS_DISTRO_DEBIAN: case OS_DISTRO_UBUNTU: + case OS_DISTRO_LINUX_MINT: fs->package_format = OS_PACKAGE_FORMAT_DEB; break; @@ -1131,6 +1960,7 @@ check_package_format (guestfs_h *g, struct inspect_fs *fs) fs->package_format = OS_PACKAGE_FORMAT_PISI; break; + case OS_DISTRO_SLACKWARE: case OS_DISTRO_WINDOWS: case OS_DISTRO_UNKNOWN: default: @@ -1158,6 +1988,7 @@ check_package_management (guestfs_h *g, struct inspect_fs *fs) case OS_DISTRO_DEBIAN: case OS_DISTRO_UBUNTU: + case OS_DISTRO_LINUX_MINT: fs->package_management = OS_PACKAGE_MANAGEMENT_APT; break; @@ -1170,7 +2001,11 @@ check_package_management (guestfs_h *g, struct inspect_fs *fs) case OS_DISTRO_PARDUS: fs->package_management = OS_PACKAGE_MANAGEMENT_PISI; break; + case OS_DISTRO_MANDRIVA: + fs->package_management = OS_PACKAGE_MANAGEMENT_URPMI; + break; + case OS_DISTRO_SLACKWARE: case OS_DISTRO_WINDOWS: case OS_DISTRO_UNKNOWN: default: @@ -1272,10 +2107,13 @@ guestfs__inspect_get_distro (guestfs_h *g, const char *root) case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break; case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break; case OS_DISTRO_GENTOO: ret = safe_strdup (g, "gentoo"); break; + case OS_DISTRO_LINUX_MINT: ret = safe_strdup (g, "linuxmint"); break; + case OS_DISTRO_MANDRIVA: ret = safe_strdup (g, "mandriva"); break; case OS_DISTRO_MEEGO: ret = safe_strdup (g, "meego"); 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_SLACKWARE: ret = safe_strdup (g, "slackware"); break; case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break; case OS_DISTRO_UBUNTU: ret = safe_strdup (g, "ubuntu"); break; case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; @@ -1315,6 +2153,16 @@ guestfs__inspect_get_product_name (guestfs_h *g, const char *root) } char * +guestfs__inspect_get_product_variant (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + return safe_strdup (g, fs->product_variant ? : "unknown"); +} + +char * guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root) { struct inspect_fs *fs = search_for_root (g, root); @@ -1329,6 +2177,69 @@ guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root) return safe_strdup (g, fs->windows_systemroot); } +char * +guestfs__inspect_get_windows_current_control_set (guestfs_h *g, + const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + if (!fs->windows_current_control_set) { + error (g, _("not a Windows guest, or CurrentControlSet could not be determined")); + return NULL; + } + + return safe_strdup (g, fs->windows_current_control_set); +} + +char * +guestfs__inspect_get_format (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + char *ret; + switch (fs->format) { + case OS_FORMAT_INSTALLED: ret = safe_strdup (g, "installed"); break; + case OS_FORMAT_INSTALLER: ret = safe_strdup (g, "installer"); break; + case OS_FORMAT_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; + } + + return ret; +} + +int +guestfs__inspect_is_live (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return -1; + + return fs->is_live_disk; +} + +int +guestfs__inspect_is_netinst (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return -1; + + return fs->is_netinst_disk; +} + +int +guestfs__inspect_is_multipart (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return -1; + + return fs->is_multipart_disk; +} + char ** guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root) { @@ -1402,6 +2313,42 @@ guestfs__inspect_get_filesystems (guestfs_h *g, const char *root) return ret; } +char ** +guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root) +{ + char **ret; + size_t i, count; + struct inspect_fs *fs; + + fs = search_for_root (g, root); + if (!fs) + return NULL; + + /* If no drive mappings, return an empty hashtable. */ + if (!fs->drive_mappings) + count = 0; + else { + for (count = 0; fs->drive_mappings[count] != NULL; count++) + ; + } + + ret = calloc (count+1, sizeof (char *)); + if (ret == NULL) { + perrorf (g, "calloc"); + return NULL; + } + + /* We need to make a deep copy of the hashtable since the caller + * will free it. + */ + for (i = 0; i < count; ++i) + ret[i] = safe_strdup (g, fs->drive_mappings[i]); + + ret[count] = NULL; + + return ret; +} + char * guestfs__inspect_get_package_format (guestfs_h *g, const char *root) { @@ -1440,6 +2387,7 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) case OS_PACKAGE_MANAGEMENT_PACMAN: ret = safe_strdup (g, "pacman"); break; case OS_PACKAGE_MANAGEMENT_PORTAGE: ret = safe_strdup (g, "portage"); break; case OS_PACKAGE_MANAGEMENT_PISI: ret = safe_strdup (g, "pisi"); break; + case OS_PACKAGE_MANAGEMENT_URPMI: ret = safe_strdup (g, "urpmi"); break; case OS_PACKAGE_MANAGEMENT_UNKNOWN: default: ret = safe_strdup (g, "unknown"); @@ -1449,19 +2397,696 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) return ret; } -#else /* no PCRE or hivex at compile time */ +char * +guestfs__inspect_get_hostname (guestfs_h *g, const char *root) +{ + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; -/* XXX These functions should be in an optgroup. */ + return safe_strdup (g, fs->hostname ? : "unknown"); +} -#define NOT_IMPL(r) \ - error (g, _("inspection API not available since this version of libguestfs was compiled without PCRE or hivex libraries")); \ - return r +#ifdef DB_DUMP +static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs); +#endif +static struct guestfs_application_list *list_applications_deb (guestfs_h *g, struct inspect_fs *fs); +static struct guestfs_application_list *list_applications_windows (guestfs_h *g, struct inspect_fs *fs); +static void add_application (guestfs_h *g, struct guestfs_application_list *, const char *name, const char *display_name, int32_t epoch, const char *version, const char *release, const char *install_path, const char *publisher, const char *url, const char *description); +static void sort_applications (struct guestfs_application_list *); -char ** -guestfs__inspect_os (guestfs_h *g) +/* Unlike the simple inspect-get-* calls, this one assumes that the + * disks are mounted up, and reads files from the mounted disks. + */ +struct guestfs_application_list * +guestfs__inspect_list_applications (guestfs_h *g, const char *root) { - NOT_IMPL(NULL); -} + struct inspect_fs *fs = search_for_root (g, root); + if (!fs) + return NULL; + + struct guestfs_application_list *ret = NULL; + + /* Presently we can only list applications for installed disks. It + * is possible in future to get lists of packages from installers. + */ + if (fs->format == OS_FORMAT_INSTALLED) { + switch (fs->type) { + case OS_TYPE_LINUX: + switch (fs->package_format) { + case OS_PACKAGE_FORMAT_RPM: +#ifdef DB_DUMP + ret = list_applications_rpm (g, fs); + if (ret == NULL) + return NULL; +#endif + break; + + case OS_PACKAGE_FORMAT_DEB: + ret = list_applications_deb (g, fs); + if (ret == NULL) + return NULL; + break; + + case OS_PACKAGE_FORMAT_PACMAN: + case OS_PACKAGE_FORMAT_EBUILD: + case OS_PACKAGE_FORMAT_PISI: + case OS_PACKAGE_FORMAT_UNKNOWN: + default: + /* nothing - keep GCC happy */; + } + break; + + case OS_TYPE_WINDOWS: + ret = list_applications_windows (g, fs); + if (ret == NULL) + return NULL; + break; + + case OS_TYPE_FREEBSD: + case OS_TYPE_UNKNOWN: + default: + /* nothing - keep GCC happy */; + } + } + + if (ret == NULL) { + /* Don't know how to do inspection. Not an error, return an + * empty list. + */ + ret = safe_malloc (g, sizeof *ret); + ret->len = 0; + ret->val = NULL; + } + + sort_applications (ret); + + return ret; +} + +#ifdef DB_DUMP +static struct guestfs_application_list * +list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) +{ + const char *basename = "rpm_Name"; + char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2]; + snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s", + g->tmpdir, basename); + + if (download_to_tmp (g, "/var/lib/rpm/Name", basename, MAX_PKG_DB_SIZE) == -1) + return NULL; + + struct guestfs_application_list *apps = NULL, *ret = NULL; +#define cmd_len (strlen (tmpdir_basename) + 64) + char cmd[cmd_len]; + FILE *pp = NULL; + char line[1024]; + size_t len; + + snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpdir_basename); + + debug (g, "list_applications_rpm: %s", cmd); + + pp = popen (cmd, "r"); + if (pp == NULL) { + perrorf (g, "popen: %s", cmd); + goto out; + } + + /* Ignore everything to end-of-header marker. */ + for (;;) { + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STREQ (line, "HEADER=END")) + break; + } + + /* Allocate 'apps' list. */ + apps = safe_malloc (g, sizeof *apps); + apps->len = 0; + apps->val = NULL; + + /* Read alternate lines until end of data marker. */ + for (;;) { + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STREQ (line, "DATA=END")) + break; + + char *p = line; + if (len > 0 && line[0] == ' ') + p = line+1; + /* Ignore any application name that contains non-printable chars. + * In the db_dump output these would be escaped with backslash, so + * we can just ignore any such line. + */ + if (strchr (p, '\\') == NULL) + add_application (g, apps, p, "", 0, "", "", "", "", "", ""); + + /* Discard next line. */ + if (fgets (line, sizeof line, pp) == NULL) { + error (g, _("unexpected end of output from db_dump command")); + goto out; + } + } + + /* Catch errors from the db_dump command. */ + if (pclose (pp) == -1) { + perrorf (g, "pclose: %s", cmd); + goto out; + } + pp = NULL; + + ret = apps; + + out: + if (ret == NULL && apps != NULL) + guestfs_free_application_list (apps); + if (pp) + pclose (pp); + + return ret; +} +#endif /* defined DB_DUMP */ + +static struct guestfs_application_list * +list_applications_deb (guestfs_h *g, struct inspect_fs *fs) +{ + const char *basename = "deb_status"; + char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2]; + snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s", + g->tmpdir, basename); + + if (download_to_tmp (g, "/var/lib/dpkg/status", basename, + MAX_PKG_DB_SIZE) == -1) + return NULL; + + struct guestfs_application_list *apps = NULL, *ret = NULL; + FILE *fp = NULL; + char line[1024]; + size_t len; + char *name = NULL, *version = NULL, *release = NULL; + int installed_flag = 0; + + fp = fopen (tmpdir_basename, "r"); + if (fp == NULL) { + perrorf (g, "fopen: %s", tmpdir_basename); + goto out; + } + + /* Allocate 'apps' list. */ + apps = safe_malloc (g, sizeof *apps); + apps->len = 0; + apps->val = NULL; + + /* Read the temporary file. Each package entry is separated by + * a blank line. + * XXX Strictly speaking this is in mailbox header format, so it + * would be possible for fields to spread across multiple lines, + * although for the short fields that we are concerned about this is + * unlikely and not seen in practice. + */ + while (fgets (line, sizeof line, fp) != NULL) { + len = strlen (line); + if (len > 0 && line[len-1] == '\n') { + line[len-1] = '\0'; + len--; + } + + if (STRPREFIX (line, "Package: ")) { + free (name); + name = safe_strdup (g, &line[9]); + } + else if (STRPREFIX (line, "Status: ")) { + installed_flag = strstr (&line[8], "installed") != NULL; + } + else if (STRPREFIX (line, "Version: ")) { + free (version); + free (release); + char *p = strchr (&line[9], '-'); + if (p) { + *p = '\0'; + version = safe_strdup (g, &line[9]); + release = safe_strdup (g, p+1); + } else { + version = safe_strdup (g, &line[9]); + release = NULL; + } + } + else if (STREQ (line, "")) { + if (installed_flag && name && version) + add_application (g, apps, name, "", 0, version, release ? : "", + "", "", "", ""); + free (name); + free (version); + free (release); + name = version = release = NULL; + installed_flag = 0; + } + } + + if (fclose (fp) == -1) { + perrorf (g, "fclose: %s", tmpdir_basename); + goto out; + } + fp = NULL; + + ret = apps; + + out: + if (ret == NULL && apps != NULL) + guestfs_free_application_list (apps); + if (fp) + fclose (fp); + free (name); + free (version); + free (release); + return ret; +} + +static void list_applications_windows_from_path (guestfs_h *g, hive_h *h, struct guestfs_application_list *apps, const char **path, size_t path_len); + +static struct guestfs_application_list * +list_applications_windows (guestfs_h *g, struct inspect_fs *fs) +{ + const char *basename = "software"; + char tmpdir_basename[strlen (g->tmpdir) + strlen (basename) + 2]; + snprintf (tmpdir_basename, sizeof tmpdir_basename, "%s/%s", + g->tmpdir, basename); + + size_t len = strlen (fs->windows_systemroot) + 64; + char software[len]; + snprintf (software, len, "%s/system32/config/software", + fs->windows_systemroot); + + char *software_path = case_sensitive_path_silently (g, software); + if (!software_path) + /* If the software hive doesn't exist, just accept that we cannot + * list windows apps. + */ + return 0; + + struct guestfs_application_list *ret = NULL; + hive_h *h = NULL; + + if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1) + goto out; + + free (software_path); + software_path = NULL; + + h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0); + if (h == NULL) { + perrorf (g, "hivex_open"); + goto out; + } + + /* Allocate apps list. */ + ret = safe_malloc (g, sizeof *ret); + ret->len = 0; + ret->val = NULL; + + /* Ordinary native applications. */ + const char *hivepath[] = + { "Microsoft", "Windows", "CurrentVersion", "Uninstall" }; + list_applications_windows_from_path (g, h, ret, hivepath, + sizeof hivepath / sizeof hivepath[0]); + + /* 32-bit emulated Windows apps running on the WOW64 emulator. + * http://support.microsoft.com/kb/896459 (RHBZ#692545). + */ + const char *hivepath2[] = + { "WOW6432node", "Microsoft", "Windows", "CurrentVersion", "Uninstall" }; + list_applications_windows_from_path (g, h, ret, hivepath2, + sizeof hivepath2 / sizeof hivepath2[0]); + + out: + if (h) hivex_close (h); + free (software_path); + + return ret; +} + +static void +list_applications_windows_from_path (guestfs_h *g, hive_h *h, + struct guestfs_application_list *apps, + const char **path, size_t path_len) +{ + hive_node_h *children = NULL; + hive_node_h node; + size_t i; + + node = hivex_root (h); + + for (i = 0; node != 0 && i < path_len; ++i) + node = hivex_node_get_child (h, node, path[i]); + + if (node == 0) + return; + + children = hivex_node_children (h, node); + if (children == NULL) + return; + + /* Consider any child node that has a DisplayName key. + * See also: + * http://nsis.sourceforge.net/Add_uninstall_information_to_Add/Remove_Programs#Optional_values + */ + for (i = 0; children[i] != 0; ++i) { + hive_value_h value; + char *name = NULL; + char *display_name = NULL; + char *version = NULL; + char *install_path = NULL; + char *publisher = NULL; + char *url = NULL; + char *comments = NULL; + + /* Use the node name as a proxy for the package name in Linux. The + * display name is not language-independent, so it cannot be used. + */ + name = hivex_node_name (h, children[i]); + if (name == NULL) + continue; + + value = hivex_node_get_value (h, children[i], "DisplayName"); + if (value) { + display_name = hivex_value_string (h, value); + if (display_name) { + value = hivex_node_get_value (h, children[i], "DisplayVersion"); + if (value) + version = hivex_value_string (h, value); + value = hivex_node_get_value (h, children[i], "InstallLocation"); + if (value) + install_path = hivex_value_string (h, value); + value = hivex_node_get_value (h, children[i], "Publisher"); + if (value) + publisher = hivex_value_string (h, value); + value = hivex_node_get_value (h, children[i], "URLInfoAbout"); + if (value) + url = hivex_value_string (h, value); + value = hivex_node_get_value (h, children[i], "Comments"); + if (value) + comments = hivex_value_string (h, value); + + add_application (g, apps, name, display_name, 0, + version ? : "", + "", + install_path ? : "", + publisher ? : "", + url ? : "", + comments ? : ""); + } + } + + free (name); + free (display_name); + free (version); + free (install_path); + free (publisher); + free (url); + free (comments); + } + + free (children); +} + +static void +add_application (guestfs_h *g, struct guestfs_application_list *apps, + const char *name, const char *display_name, int32_t epoch, + const char *version, const char *release, + const char *install_path, + const char *publisher, const char *url, + const char *description) +{ + apps->len++; + apps->val = safe_realloc (g, apps->val, + apps->len * sizeof (struct guestfs_application)); + apps->val[apps->len-1].app_name = safe_strdup (g, name); + apps->val[apps->len-1].app_display_name = safe_strdup (g, display_name); + apps->val[apps->len-1].app_epoch = epoch; + apps->val[apps->len-1].app_version = safe_strdup (g, version); + apps->val[apps->len-1].app_release = safe_strdup (g, release); + apps->val[apps->len-1].app_install_path = safe_strdup (g, install_path); + /* XXX Translated path is not implemented yet. */ + apps->val[apps->len-1].app_trans_path = safe_strdup (g, ""); + apps->val[apps->len-1].app_publisher = safe_strdup (g, publisher); + apps->val[apps->len-1].app_url = safe_strdup (g, url); + /* XXX The next two are not yet implemented for any package + * format, but we could easily support them for rpm and deb. + */ + apps->val[apps->len-1].app_source_package = safe_strdup (g, ""); + apps->val[apps->len-1].app_summary = safe_strdup (g, ""); + apps->val[apps->len-1].app_description = safe_strdup (g, description); +} + +/* Sort applications by name before returning the list. */ +static int +compare_applications (const void *vp1, const void *vp2) +{ + const struct guestfs_application *v1 = vp1; + const struct guestfs_application *v2 = vp2; + + return strcmp (v1->app_name, v2->app_name); +} + +static void +sort_applications (struct guestfs_application_list *apps) +{ + if (apps && apps->val) + qsort (apps->val, apps->len, sizeof (struct guestfs_application), + compare_applications); +} + +/* Download a guest file to a local temporary file. The file is + * downloaded into g->tmpdir, unless it already exists in g->tmpdir. + * The final name will be g->tmpdir + "/" + basename. Refuse to + * download the guest file if it is larger than max_size. The caller + * does not need to delete the temporary file after use: it will be + * deleted when the handle is cleaned up. + */ +static int +download_to_tmp (guestfs_h *g, const char *filename, + const char *basename, int64_t max_size) +{ + int tmpdirfd, fd, r = -1; + char buf[32]; + int64_t size; + + tmpdirfd = open (g->tmpdir, O_RDONLY); + if (tmpdirfd == -1) { + perrorf (g, _("%s: temporary directory not found"), g->tmpdir); + return -1; + } + + /* If the file has already been downloaded, return. */ + if (faccessat (tmpdirfd, basename, R_OK, 0) == 0) { + r = 0; + goto out; + } + + /* Check size of remote file. */ + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + goto out; + if (size > max_size) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + goto out; + } + + fd = openat (tmpdirfd, basename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0600); + if (fd == -1) { + perrorf (g, "openat: %s/%s", g->tmpdir, basename); + goto out; + } + + snprintf (buf, sizeof buf, "/dev/fd/%d", fd); + + if (guestfs_download (g, filename, buf) == -1) { + unlinkat (tmpdirfd, basename, 0); + close (fd); + goto out; + } + + if (close (fd) == -1) { + perrorf (g, "close: %s/%s", g->tmpdir, basename); + unlinkat (tmpdirfd, basename, 0); + goto out; + } + + r = 0; + out: + if (tmpdirfd >= 0) + close (tmpdirfd); + + return r; +} + +/* Call 'f' with Augeas opened and having parsed 'filename' (this file + * must exist). As a security measure, this bails if the file is too + * large for a reasonable configuration file. After the call to 'f' + * Augeas is closed. + */ +static int +inspect_with_augeas (guestfs_h *g, struct inspect_fs *fs, const char *filename, + int (*f) (guestfs_h *, struct inspect_fs *)) +{ + /* Security: Refuse to do this if filename is too large. */ + int64_t size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return -1; + if (size > MAX_AUGEAS_FILE_SIZE) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + return -1; + } + + /* If !feature_available (g, "augeas") then the next call will fail. + * Arguably we might want to fall back to a non-Augeas method in + * this case. + */ + if (guestfs_aug_init (g, "/", 16|32) == -1) + return -1; + + int r = -1; + + /* Tell Augeas to only load one file (thanks Raphaël Pinson). */ + char buf[strlen (filename) + 64]; + snprintf (buf, strlen (filename) + 64, "/augeas/load//incl[. != \"%s\"]", + filename); + if (guestfs_aug_rm (g, buf) == -1) + goto out; + + if (guestfs_aug_load (g) == -1) + goto out; + + r = f (g, fs); + + out: + guestfs_aug_close (g); + + return r; +} + +/* Get the first line of a small file, without any trailing newline + * character. + */ +static char * +first_line_of_file (guestfs_h *g, const char *filename) +{ + char **lines; + int64_t size; + char *ret; + + /* Don't trust guestfs_head_n not to break with very large files. + * Check the file size is something reasonable first. + */ + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return NULL; + if (size > MAX_SMALL_FILE_SIZE) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + return NULL; + } + + lines = guestfs_head_n (g, 1, filename); + if (lines == NULL) + return NULL; + if (lines[0] == NULL) { + error (g, _("%s: file is empty"), filename); + guestfs___free_string_list (lines); + return NULL; + } + /* lines[1] should be NULL because of '1' argument above ... */ + + ret = lines[0]; /* caller frees */ + free (lines); /* free the array */ + + return ret; +} + +/* Get the first matching line (using guestfs_egrep{,i}) of a small file, + * without any trailing newline character. + * + * Returns: 1 = returned a line (in *ret) + * 0 = no match + * -1 = error + */ +static int +first_egrep_of_file (guestfs_h *g, const char *filename, + const char *eregex, int iflag, char **ret) +{ + char **lines; + int64_t size; + size_t i; + + /* Don't trust guestfs_egrep not to break with very large files. + * Check the file size is something reasonable first. + */ + size = guestfs_filesize (g, filename); + if (size == -1) + /* guestfs_filesize failed and has already set error in handle */ + return -1; + if (size > MAX_SMALL_FILE_SIZE) { + error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"), + filename, size); + return -1; + } + + lines = (!iflag ? guestfs_egrep : guestfs_egrepi) (g, eregex, filename); + if (lines == NULL) + return -1; + if (lines[0] == NULL) { + guestfs___free_string_list (lines); + return 0; + } + + *ret = lines[0]; /* caller frees */ + + /* free up any other matches and the array itself */ + for (i = 1; lines[i] != NULL; ++i) + free (lines[i]); + free (lines); + + return 1; +} + +#else /* no PCRE or hivex at compile time */ + +/* XXX These functions should be in an optgroup. */ + +#define NOT_IMPL(r) \ + error (g, _("inspection API not available since this version of libguestfs was compiled without PCRE or hivex libraries")); \ + return r + +char ** +guestfs__inspect_os (guestfs_h *g) +{ + NOT_IMPL(NULL); +} char ** guestfs__inspect_get_roots (guestfs_h *g) @@ -1506,11 +3131,24 @@ guestfs__inspect_get_product_name (guestfs_h *g, const char *root) } char * +guestfs__inspect_get_product_variant (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + +char * guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root) { NOT_IMPL(NULL); } +char * +guestfs__inspect_get_windows_current_control_set (guestfs_h *g, + const char *root) +{ + NOT_IMPL(NULL); +} + char ** guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root) { @@ -1523,6 +3161,12 @@ guestfs__inspect_get_filesystems (guestfs_h *g, const char *root) NOT_IMPL(NULL); } +char ** +guestfs__inspect_get_drive_mappings (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + char * guestfs__inspect_get_package_format (guestfs_h *g, const char *root) { @@ -1535,6 +3179,42 @@ guestfs__inspect_get_package_management (guestfs_h *g, const char *root) NOT_IMPL(NULL); } +char * +guestfs__inspect_get_hostname (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + +struct guestfs_application_list * +guestfs__inspect_list_applications (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + +char * +guestfs__inspect_get_format (guestfs_h *g, const char *root) +{ + NOT_IMPL(NULL); +} + +int +guestfs__inspect_is_live (guestfs_h *g, const char *root) +{ + NOT_IMPL(-1); +} + +int +guestfs__inspect_is_netinst (guestfs_h *g, const char *root) +{ + NOT_IMPL(-1); +} + +int +guestfs__inspect_is_multipart (guestfs_h *g, const char *root) +{ + NOT_IMPL(-1); +} + #endif /* no PCRE or hivex at compile time */ void @@ -1544,14 +3224,19 @@ guestfs___free_inspect_info (guestfs_h *g) for (i = 0; i < g->nr_fses; ++i) { free (g->fses[i].device); free (g->fses[i].product_name); + free (g->fses[i].product_variant); free (g->fses[i].arch); + free (g->fses[i].hostname); free (g->fses[i].windows_systemroot); + free (g->fses[i].windows_current_control_set); size_t j; for (j = 0; j < g->fses[i].nr_fstab; ++j) { free (g->fses[i].fstab[j].device); free (g->fses[i].fstab[j].mountpoint); } free (g->fses[i].fstab); + if (g->fses[i].drive_mappings) + guestfs___free_string_list (g->fses[i].drive_mappings); } free (g->fses); g->nr_fses = 0; @@ -1592,7 +3277,7 @@ guestfs___match (guestfs_h *g, const char *str, const pcre *re) return 0; if (r != 1) { /* Internal error -- should not happen. */ - fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + warning (g, "%s: %s: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", __FILE__, __func__, r, str); return 0; } @@ -1615,7 +3300,7 @@ guestfs___match1 (guestfs_h *g, const char *str, const pcre *re) return NULL; if (r != 2) { /* Internal error -- should not happen. */ - fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"", __FILE__, __func__, r, str); return NULL; } @@ -1636,7 +3321,7 @@ guestfs___match2 (guestfs_h *g, const char *str, const pcre *re, return 0; if (r != 3) { /* Internal error -- should not happen. */ - fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"", __FILE__, __func__, r, str); return 0; } @@ -1660,7 +3345,7 @@ guestfs___match3 (guestfs_h *g, const char *str, const pcre *re, return 0; if (r != 4) { /* Internal error -- should not happen. */ - fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", + warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"", __FILE__, __func__, r, str); return 0; }