inspect: Move shared PCRE match functions to separate file.
[libguestfs.git] / src / inspect.c
index 154207b..8213936 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>
@@ -230,6 +232,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 *);
@@ -241,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);
@@ -1480,7 +1483,10 @@ 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];
@@ -1498,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;
@@ -1587,17 +1592,16 @@ 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];
@@ -1616,12 +1620,12 @@ 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)
+  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;
@@ -1658,6 +1662,71 @@ 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++;
+    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" };
@@ -1673,6 +1742,7 @@ check_windows_system_registry (guestfs_h *g, struct inspect_fs *fs)
     goto out;
   }
 
+  free (values);
   values = hivex_node_values (h, node);
 
   for (i = 0; values[i] != 0; ++i) {
@@ -1702,10 +1772,75 @@ 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;
 }
 
@@ -2178,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)
 {
@@ -2317,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);
 
@@ -2408,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;
 }
@@ -2418,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;
 
@@ -2431,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;
   }
 
@@ -2489,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;
@@ -2504,7 +2679,6 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   free (name);
   free (version);
   free (release);
-  unlink (tmpfile);
   return ret;
 }
 
@@ -2513,12 +2687,11 @@ static void list_applications_windows_from_path (guestfs_h *g, hive_h *h, struct
 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);
 
-  /* 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.
-   */
   size_t len = strlen (fs->windows_systemroot) + 64;
   char software[len];
   snprintf (software, len, "%s/system32/config/software",
@@ -2527,21 +2700,20 @@ list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
   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 *ret = NULL;
   hive_h *h = 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;
 
   free (software_path);
   software_path = NULL;
 
-  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;
@@ -2570,10 +2742,6 @@ list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
   if (h) hivex_close (h);
   free (software_path);
 
-  /* Delete the temporary file. */
-  unlink (software_local);
-#undef software_local_len
-
   return ret;
 }
 
@@ -2708,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
@@ -2972,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)
 {
@@ -3040,6 +3235,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;
@@ -3063,101 +3260,3 @@ guestfs___feature_available (guestfs_h *g, const char *feature)
 
   return r == 0 ? 1 : 0;
 }
-
-#ifdef HAVE_PCRE
-
-/* Match a regular expression which contains no captures.  Returns
- * true if it matches or false if it doesn't.
- */
-int
-guestfs___match (guestfs_h *g, const char *str, const pcre *re)
-{
-  size_t len = strlen (str);
-  int vec[30], r;
-
-  r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
-  if (r == PCRE_ERROR_NOMATCH)
-    return 0;
-  if (r != 1) {
-    /* Internal error -- should not happen. */
-    warning (g, "%s: %s: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
-             __FILE__, __func__, r, str);
-    return 0;
-  }
-
-  return 1;
-}
-
-/* Match a regular expression which contains exactly one capture.  If
- * the string matches, return the capture, otherwise return NULL.  The
- * caller must free the result.
- */
-char *
-guestfs___match1 (guestfs_h *g, const char *str, const pcre *re)
-{
-  size_t len = strlen (str);
-  int vec[30], r;
-
-  r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]);
-  if (r == PCRE_ERROR_NOMATCH)
-    return NULL;
-  if (r != 2) {
-    /* Internal error -- should not happen. */
-    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;
-  }
-
-  return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
-}
-
-/* Match a regular expression which contains exactly two captures. */
-int
-guestfs___match2 (guestfs_h *g, const char *str, const pcre *re,
-                  char **ret1, char **ret2)
-{
-  size_t len = strlen (str);
-  int vec[30], r;
-
-  r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
-  if (r == PCRE_ERROR_NOMATCH)
-    return 0;
-  if (r != 3) {
-    /* Internal error -- should not happen. */
-    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;
-  }
-
-  *ret1 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
-  *ret2 = safe_strndup (g, &str[vec[4]], vec[5]-vec[4]);
-
-  return 1;
-}
-
-/* Match a regular expression which contains exactly three captures. */
-int
-guestfs___match3 (guestfs_h *g, const char *str, const pcre *re,
-                  char **ret1, char **ret2, char **ret3)
-{
-  size_t len = strlen (str);
-  int vec[30], r;
-
-  r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30);
-  if (r == PCRE_ERROR_NOMATCH)
-    return 0;
-  if (r != 4) {
-    /* Internal error -- should not happen. */
-    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;
-  }
-
-  *ret1 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]);
-  *ret2 = safe_strndup (g, &str[vec[4]], vec[5]-vec[4]);
-  *ret3 = safe_strndup (g, &str[vec[6]], vec[7]-vec[6]);
-
-  return 1;
-}
-
-#endif /* HAVE_PCRE */