From: Richard W.M. Jones Date: Tue, 5 Apr 2011 13:03:08 +0000 (+0100) Subject: New API: inspect-get-drive-mappings X-Git-Tag: 1.9.17~4 X-Git-Url: http://git.annexia.org/?p=libguestfs.git;a=commitdiff_plain;h=13d8963d8c0203e1f72c519e5acf79ebf7cccb4c New API: inspect-get-drive-mappings This returns the drive mappings from the Windows Registry. virt-inspector displays the drive mappings, giving output similar to this: /dev/sda2 /dev/sdb1 --- diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index c59964a..26dc64e 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -928,6 +928,12 @@ which is the filesystem that would be mounted there Non-mounted devices such as swap devices are I returned in this list. +For operating systems like Windows which still use drive +letters, this call will only return an entry for the first +drive \"mounted on\" C. For information about the +mapping of drive letters to partitions, see +C. + Please read L for more details. See also C."); @@ -1467,6 +1473,42 @@ the case then an error is returned. Please read L for more details."); + ("inspect_get_drive_mappings", (RHashtable "drives", [Device "root"], []), -1, [], + [], + "get drive letter mappings", + "\ +This function should only be called with a root device string +as returned by C. + +This call is useful for Windows which uses a primitive system +of assigning drive letters (like \"C:\") to partitions. +This inspection API examines the Windows Registry to find out +how disks/partitions are mapped to drive letters, and returns +a hash table as in the example below: + + C => /dev/vda2 + E => /dev/vdb1 + F => /dev/vdc1 + +Note that keys are drive letters. For Windows, the key is +case insensitive and just contains the drive letter, without +the customary colon separator character. + +In future we may support other operating systems that also used drive +letters, but the keys for those might not be case insensitive +and might be longer than 1 character. For example in OS-9, +hard drives were named C, C

etc. + +For Windows guests, currently only hard drive mappings are +returned. Removable disks (eg. DVD-ROMs) are ignored. + +For guests that do not use drive mappings, or if the drive mappings +could not be determined, this returns an empty hash table. + +Please read L for more details. +See also C, +C."); + ] (* daemon_functions are any functions which cause some action diff --git a/images/guest-aux/make-windows-img.sh b/images/guest-aux/make-windows-img.sh index 15cb7c5..f659f4b 100755 --- a/images/guest-aux/make-windows-img.sh +++ b/images/guest-aux/make-windows-img.sh @@ -45,6 +45,9 @@ part-init /dev/sda mbr part-add /dev/sda p 64 524287 part-add /dev/sda p 524288 -64 +# Disk ID. +pwrite-device /dev/sda "1234" 0x01b8 + # Phony boot loader filesystem. mkfs ntfs /dev/sda1 diff --git a/images/guest-aux/windows-system b/images/guest-aux/windows-system index 7d935b0..3a23214 100755 Binary files a/images/guest-aux/windows-system and b/images/guest-aux/windows-system differ diff --git a/images/guest-aux/windows-system.reg b/images/guest-aux/windows-system.reg index 6cb5d32..5478d2d 100644 --- a/images/guest-aux/windows-system.reg +++ b/images/guest-aux/windows-system.reg @@ -4,6 +4,9 @@ "Failed"=dword:00000000 "LastKnownGood"=dword:00000002 +[HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices] +"\\DosDevices\\C:"=hex(3):31,32,33,34,00,00,00,10,00,00,00,00 + [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001] [HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services] diff --git a/inspector/example-windows.xml b/inspector/example-windows.xml index 8e53159..8b3b8a7 100644 --- a/inspector/example-windows.xml +++ b/inspector/example-windows.xml @@ -20,6 +20,9 @@ ntfs + + /dev/sda2 + test1 diff --git a/inspector/virt-inspector.c b/inspector/virt-inspector.c index d016b2d..7724be6 100644 --- a/inspector/virt-inspector.c +++ b/inspector/virt-inspector.c @@ -51,6 +51,7 @@ static void output_roots (xmlTextWriterPtr xo, char **roots); static void output_root (xmlTextWriterPtr xo, char *root); static void output_mountpoints (xmlTextWriterPtr xo, char *root); static void output_filesystems (xmlTextWriterPtr xo, char *root); +static void output_drive_mappings (xmlTextWriterPtr xo, char *root); static void output_applications (xmlTextWriterPtr xo, char *root); static void canonicalize (char *dev); static void free_strings (char **argv); @@ -458,6 +459,8 @@ output_root (xmlTextWriterPtr xo, char *root) output_filesystems (xo, root); + output_drive_mappings (xo, root); + output_applications (xo, root); XMLERROR (-1, xmlTextWriterEndElement (xo)); @@ -473,6 +476,15 @@ compare_keys (const void *p1, const void *p2) } static int +compare_keys_nocase (const void *p1, const void *p2) +{ + const char *key1 = * (char * const *) p1; + const char *key2 = * (char * const *) p2; + + return strcasecmp (key1, key2); +} + +static int compare_keys_len (const void *p1, const void *p2) { const char *key1 = * (char * const *) p1; @@ -583,6 +595,48 @@ output_filesystems (xmlTextWriterPtr xo, char *root) } static void +output_drive_mappings (xmlTextWriterPtr xo, char *root) +{ + char **drive_mappings = NULL; + size_t i; + + DISABLE_GUESTFS_ERRORS_FOR ( + drive_mappings = guestfs_inspect_get_drive_mappings (g, root); + ); + if (drive_mappings == NULL) + return; + + if (drive_mappings[0] == NULL) { + free_strings (drive_mappings); + return; + } + + /* Sort by key. */ + qsort (drive_mappings, + count_strings (drive_mappings) / 2, 2 * sizeof (char *), + compare_keys_nocase); + + XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "drive_mappings")); + + for (i = 0; drive_mappings[i] != NULL; i += 2) { + canonicalize (drive_mappings[i+1]); + + XMLERROR (-1, + xmlTextWriterStartElement (xo, BAD_CAST "drive_mapping")); + XMLERROR (-1, + xmlTextWriterWriteAttribute (xo, BAD_CAST "name", + BAD_CAST drive_mappings[i])); + XMLERROR (-1, + xmlTextWriterWriteString (xo, BAD_CAST drive_mappings[i+1])); + XMLERROR (-1, xmlTextWriterEndElement (xo)); + } + + XMLERROR (-1, xmlTextWriterEndElement (xo)); + + free_strings (drive_mappings); +} + +static void output_applications (xmlTextWriterPtr xo, char *root) { struct guestfs_application_list *apps; diff --git a/inspector/virt-inspector.rng b/inspector/virt-inspector.rng index 7a822e6..669e8bc 100644 --- a/inspector/virt-inspector.rng +++ b/inspector/virt-inspector.rng @@ -47,6 +47,7 @@ + @@ -83,6 +84,18 @@ + + + + + + + + + + + + diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index b29fa57..46576bb 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -252,6 +252,7 @@ struct inspect_fs { char *hostname; char *windows_systemroot; char *windows_current_control_set; + char **drive_mappings; enum inspect_os_format format; int is_live_disk; int is_netinst_disk; diff --git a/src/inspect.c b/src/inspect.c index 154207b..be98b0d 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -26,6 +26,7 @@ #include #include #include +#include #ifdef HAVE_PCRE #include @@ -230,6 +231,7 @@ 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_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 *); @@ -1616,7 +1618,7 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) hive_node_h root, node; hive_value_h value, *values = NULL; int32_t dword; - size_t i; + size_t i, count; if (download_to_tmp (g, system_path, system_local, MAX_REGISTRY_SIZE) == -1) goto out; @@ -1658,6 +1660,69 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) 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++; + } + + 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); + } + } + /* Get the hostname. */ const char *hivepath[] = { fs->windows_current_control_set, "Services", "Tcpip", "Parameters" }; @@ -1709,6 +1774,75 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs) 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; +} + static char * case_sensitive_path_silently (guestfs_h *g, const char *path) { @@ -2178,6 +2312,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) { @@ -2972,6 +3142,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) { @@ -3040,6 +3216,8 @@ guestfs___free_inspect_info (guestfs_h *g) 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;