inspect: Cache downloaded files in the handle g->tmpdir.
[libguestfs.git] / src / inspect.c
index 3a8ede6..c7182b4 100644 (file)
 #include <stdint.h>
 #include <inttypes.h>
 #include <unistd.h>
+#include <fcntl.h>
 #include <string.h>
 #include <sys/stat.h>
+#include <errno.h>
+#include <endian.h>
 
 #ifdef HAVE_PCRE
 #include <pcre.h>
@@ -229,7 +232,8 @@ 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 *resolve_windows_path_silently (guestfs_h *g, const char *);
+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);
@@ -240,7 +244,7 @@ static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
 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, char *localtmp, int64_t max_size);
+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);
@@ -1422,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) {
@@ -1459,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;
 
@@ -1479,14 +1483,17 @@ check_windows_arch (guestfs_h *g, struct inspect_fs *fs)
 static int
 check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
 {
-  TMP_TEMPLATE_ON_STACK (software_local);
+  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.
@@ -1497,11 +1504,10 @@ check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
   hive_h *h = NULL;
   hive_value_h *values = NULL;
 
-  if (download_to_tmp (g, software_path, software_local,
-                       MAX_REGISTRY_SIZE) == -1)
+  if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1)
     goto out;
 
-  h = hivex_open (software_local, 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;
@@ -1586,24 +1592,23 @@ check_windows_software_registry (guestfs_h *g, struct inspect_fs *fs)
   free (values);
   free (software_path);
 
-  /* Free up the temporary file. */
-  unlink (software_local);
-#undef software_local_len
-
   return ret;
 }
 
 static int
 check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
 {
-  TMP_TEMPLATE_ON_STACK (system_local);
+  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 = resolve_windows_path_silently (g, system);
+  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.
@@ -1612,35 +1617,132 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
 
   int ret = -1;
   hive_h *h = NULL;
-  hive_value_h *values = 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, system_local, MAX_REGISTRY_SIZE) == -1)
+  if (download_to_tmp (g, system_path, basename, MAX_REGISTRY_SIZE) == -1)
     goto out;
 
-  h = hivex_open (system_local, 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;
   }
 
-  hive_node_h node = hivex_root (h);
-  /* XXX Don't hard-code ControlSet001.  The current control set would
-   * be another good thing to expose up through the inspection API.
+  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[] =
-    { "ControlSet001", "Services", "Tcpip", "Parameters" };
-  size_t i;
-  for (i = 0;
+    { 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\\ControlSet001\\Services\\Tcpip\\Parameters");
+    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) {
@@ -1670,15 +1772,80 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
   free (values);
   free (system_path);
 
-  /* Free up the temporary file. */
-  unlink (system_local);
-#undef system_local_len
+  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 *
-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;
@@ -1693,7 +1860,7 @@ is_file_nocase (guestfs_h *g, const char *path)
   char *p;
   int r;
 
-  p = resolve_windows_path_silently (g, path);
+  p = case_sensitive_path_silently (g, path);
   if (!p)
     return 0;
   r = guestfs_is_file (g, p);
@@ -1707,7 +1874,7 @@ is_dir_nocase (guestfs_h *g, const char *path)
   char *p;
   int r;
 
-  p = resolve_windows_path_silently (g, path);
+  p = case_sensitive_path_silently (g, path);
   if (!p)
     return 0;
   r = guestfs_is_dir (g, p);
@@ -2011,6 +2178,22 @@ guestfs__inspect_get_windows_systemroot (guestfs_h *g, const char *root)
 }
 
 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);
@@ -2130,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)
 {
@@ -2269,19 +2488,22 @@ guestfs__inspect_list_applications (guestfs_h *g, const char *root)
 static struct guestfs_application_list *
 list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
 {
-  TMP_TEMPLATE_ON_STACK (tmpfile);
+  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", tmpfile, MAX_PKG_DB_SIZE) == -1)
+  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 (tmpfile) + 64)
+#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'", tmpfile);
+  snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpdir_basename);
 
   debug (g, "list_applications_rpm: %s", cmd);
 
@@ -2360,8 +2582,6 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
     guestfs_free_application_list (apps);
   if (pp)
     pclose (pp);
-  unlink (tmpfile);
-#undef cmd_len
 
   return ret;
 }
@@ -2370,9 +2590,12 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
 static struct guestfs_application_list *
 list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
 {
-  TMP_TEMPLATE_ON_STACK (tmpfile);
+  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", tmpfile,
+  if (download_to_tmp (g, "/var/lib/dpkg/status", basename,
                        MAX_PKG_DB_SIZE) == -1)
     return NULL;
 
@@ -2383,9 +2606,9 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   char *name = NULL, *version = NULL, *release = NULL;
   int installed_flag = 0;
 
-  fp = fopen (tmpfile, "r");
+  fp = fopen (tmpdir_basename, "r");
   if (fp == NULL) {
-    perrorf (g, "fopen: %s", tmpfile);
+    perrorf (g, "fopen: %s", tmpdir_basename);
     goto out;
   }
 
@@ -2441,7 +2664,7 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   }
 
   if (fclose (fp) == -1) {
-    perrorf (g, "fclose: %s", tmpfile);
+    perrorf (g, "fclose: %s", tmpdir_basename);
     goto out;
   }
   fp = NULL;
@@ -2456,70 +2679,92 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   free (name);
   free (version);
   free (release);
-  unlink (tmpfile);
   return ret;
 }
 
-/* XXX We already download the SOFTWARE hive when doing general
- * inspection.  We could avoid this second download of the same file
- * by caching these entries in the handle.
- */
+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)
 {
-  TMP_TEMPLATE_ON_STACK (software_local);
+  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.
+     * list windows apps.
      */
     return 0;
 
-  struct guestfs_application_list *apps = NULL, *ret = NULL;
+  struct guestfs_application_list *ret = NULL;
   hive_h *h = NULL;
-  hive_node_h *children = NULL;
 
-  if (download_to_tmp (g, software_path, software_local,
-                       MAX_REGISTRY_SIZE) == -1)
+  if (download_to_tmp (g, software_path, basename, MAX_REGISTRY_SIZE) == -1)
     goto out;
 
-  h = hivex_open (software_local, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+  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;
   }
 
-  hive_node_h node = hivex_root (h);
+  /* 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;
-  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\\CurrentVersion\\Uninstall");
-    goto out;
-  }
+  node = hivex_root (h);
 
-  children = hivex_node_children (h, node);
-  if (children == NULL) {
-    perrorf (g, "hivex_node_children");
-    goto out;
-  }
+  for (i = 0; node != 0 && i < path_len; ++i)
+    node = hivex_node_get_child (h, node, path[i]);
 
-  /* Allocate 'apps' list. */
-  apps = safe_malloc (g, sizeof *apps);
-  apps->len = 0;
-  apps->val = NULL;
+  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:
@@ -2539,10 +2784,8 @@ list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
      * display name is not language-independent, so it cannot be used.
      */
     name = hivex_node_name (h, children[i]);
-    if (name == NULL) {
-      perrorf (g, "hivex_node_get_name");
-      goto out;
-    }
+    if (name == NULL)
+      continue;
 
     value = hivex_node_get_value (h, children[i], "DisplayName");
     if (value) {
@@ -2583,20 +2826,7 @@ list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
     free (comments);
   }
 
-  ret = apps;
-
- out:
-  if (ret == NULL && apps != NULL)
-    guestfs_free_application_list (apps);
-  if (h) hivex_close (h);
   free (children);
-  free (software_path);
-
-  /* Free up the temporary file. */
-  unlink (software_local);
-#undef software_local_len
-
-  return ret;
 }
 
 static void
@@ -2646,49 +2876,70 @@ sort_applications (struct guestfs_application_list *apps)
            compare_applications);
 }
 
-/* Download to a guest file to a local temporary file.  Refuse to
+/* 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
- * is responsible for deleting the temporary file after use.
+ * 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,
-                 char *localtmp, int64_t max_size)
+                 const char *basename, int64_t max_size)
 {
-  int fd;
+  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 */
-    return -1;
+    goto out;
   if (size > max_size) {
     error (g, _("size of %s is unreasonably large (%" PRIi64 " bytes)"),
            filename, size);
-    return -1;
+    goto out;
   }
 
-  fd = mkstemp (localtmp);
+  fd = openat (tmpdirfd, basename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY, 0600);
   if (fd == -1) {
-    perrorf (g, "mkstemp");
-    return -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);
-    unlink (localtmp);
-    return -1;
+    goto out;
   }
 
   if (close (fd) == -1) {
-    perrorf (g, "close: %s", localtmp);
-    unlink (localtmp);
-    return -1;
+    perrorf (g, "close: %s/%s", g->tmpdir, basename);
+    unlinkat (tmpdirfd, basename, 0);
+    goto out;
   }
 
-  return 0;
+  r = 0;
+ out:
+  if (tmpdirfd >= 0)
+    close (tmpdirfd);
+
+  return r;
 }
 
 /* Call 'f' with Augeas opened and having parsed 'filename' (this file
@@ -2891,6 +3142,13 @@ 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)
 {
@@ -2903,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)
 {
@@ -2964,12 +3228,15 @@ guestfs___free_inspect_info (guestfs_h *g)
     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;