/* libguestfs * 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 * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_PCRE #include #endif #ifdef HAVE_HIVEX #include #endif #include "c-ctype.h" #include "ignore-value.h" #include "xstrtol.h" #include "guestfs.h" #include "guestfs-internal.h" #include "guestfs-internal-actions.h" #include "guestfs_protocol.h" #if defined(HAVE_PCRE) && defined(HAVE_HIVEX) /* 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 * simultaneously. */ static pcre *re_windows_version; static void compile_regexps (void) __attribute__((constructor)); static void free_regexps (void) __attribute__((destructor)); static void compile_regexps (void) { const char *err; int offset; #define COMPILE(re,pattern,options) \ do { \ re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ if (re == NULL) { \ ignore_value (write (2, err, strlen (err))); \ abort (); \ } \ } while (0) COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0); } static void free_regexps (void) { pcre_free (re_windows_version); } static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs); 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); /* XXX Handling of boot.ini in the Perl version was pretty broken. It * essentially didn't do anything for modern Windows guests. * Therefore I've omitted all that code. */ int guestfs___check_windows_root (guestfs_h *g, struct inspect_fs *fs) { fs->type = OS_TYPE_WINDOWS; fs->distro = OS_DISTRO_WINDOWS; /* Try to find Windows systemroot using some common locations. */ const char *systemroots[] = { "/windows", "/winnt", "/win32", "/win" }; size_t i; char *systemroot = NULL; for (i = 0; systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0]; ++i) { systemroot = guestfs___case_sensitive_path_silently (g, systemroots[i]); } if (!systemroot) { error (g, _("cannot resolve Windows %%SYSTEMROOT%%")); return -1; } debug (g, "windows %%SYSTEMROOT%% = %s", systemroot); /* Freed by guestfs___free_inspect_info. */ fs->windows_systemroot = systemroot; if (check_windows_arch (g, fs) == -1) return -1; /* Product name and version. */ if (check_windows_software_registry (g, fs) == -1) return -1; /* Hostname. */ if (check_windows_system_registry (g, fs) == -1) return -1; return 0; } static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs) { size_t len = strlen (fs->windows_systemroot) + 32; char cmd_exe[len]; snprintf (cmd_exe, len, "%s/system32/cmd.exe", fs->windows_systemroot); char *cmd_exe_path = guestfs___case_sensitive_path_silently (g, cmd_exe); if (!cmd_exe_path) return 0; char *arch = guestfs_file_architecture (g, cmd_exe_path); free (cmd_exe_path); if (arch) fs->arch = arch; /* freed by guestfs___free_inspect_info */ return 0; } /* At the moment, pull just the ProductName and version numbers from * the registry. In future there is a case for making many more * registry fields available to callers. */ static int check_windows_software_registry (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 = guestfs___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. */ return 0; int ret = -1; hive_h *h = NULL; hive_value_h *values = NULL; if (guestfs___download_to_tmp (g, software_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; } hive_node_h node = hivex_root (h); const char *hivepath[] = { "Microsoft", "Windows NT", "CurrentVersion" }; size_t i; for (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\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); goto out; } 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, "ProductName")) { fs->product_name = hivex_value_string (h, values[i]); if (!fs->product_name) { perrorf (g, "hivex_value_string"); free (key); goto out; } } else if (STRCASEEQ (key, "CurrentVersion")) { char *version = hivex_value_string (h, values[i]); if (!version) { perrorf (g, "hivex_value_string"); free (key); goto out; } char *major, *minor; if (match2 (g, version, re_windows_version, &major, &minor)) { fs->major_version = guestfs___parse_unsigned_int (g, major); free (major); if (fs->major_version == -1) { free (minor); free (key); free (version); goto out; } fs->minor_version = guestfs___parse_unsigned_int (g, minor); free (minor); if (fs->minor_version == -1) { free (key); free (version); goto out; } } free (version); } 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; out: if (h) hivex_close (h); free (values); free (software_path); 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 = guestfs___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 (guestfs___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. */ 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; } char * guestfs___case_sensitive_path_silently (guestfs_h *g, const char *path) { guestfs_error_handler_cb old_error_cb = g->error_cb; g->error_cb = NULL; char *ret = guestfs_case_sensitive_path (g, path); g->error_cb = old_error_cb; return ret; } #endif /* defined(HAVE_PCRE) && defined(HAVE_HIVEX) */