Require PCRE library.
[libguestfs.git] / src / inspect_apps.c
index 8ce09ab..ba27247 100644 (file)
@@ -29,9 +29,7 @@
 #include <errno.h>
 #include <endian.h>
 
 #include <errno.h>
 #include <endian.h>
 
-#ifdef HAVE_PCRE
 #include <pcre.h>
 #include <pcre.h>
-#endif
 
 #ifdef HAVE_HIVEX
 #include <hivex.h>
 
 #ifdef HAVE_HIVEX
 #include <hivex.h>
@@ -46,7 +44,7 @@
 #include "guestfs-internal-actions.h"
 #include "guestfs_protocol.h"
 
 #include "guestfs-internal-actions.h"
 #include "guestfs_protocol.h"
 
-#if defined(HAVE_PCRE) && defined(HAVE_HIVEX)
+#if defined(HAVE_HIVEX)
 
 #ifdef DB_DUMP
 static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs);
 
 #ifdef DB_DUMP
 static struct guestfs_application_list *list_applications_rpm (guestfs_h *g, struct inspect_fs *fs);
@@ -127,23 +125,136 @@ guestfs__inspect_list_applications (guestfs_h *g, const char *root)
 
 #ifdef DB_DUMP
 
 
 #ifdef DB_DUMP
 
+/* This data comes from the Name database, and contains the application
+ * names and the first 4 bytes of the link field.
+ */
+struct rpm_names_list {
+  struct rpm_name *names;
+  size_t len;
+};
+struct rpm_name {
+  char *name;
+  char link[4];
+};
+
+static void
+free_rpm_names_list (struct rpm_names_list *list)
+{
+  size_t i;
+
+  for (i = 0; i < list->len; ++i)
+    free (list->names[i].name);
+  free (list->names);
+}
+
+static int
+compare_links (const void *av, const void *bv)
+{
+  const struct rpm_name *a = av;
+  const struct rpm_name *b = bv;
+  return memcmp (a->link, b->link, 4);
+}
+
 static int
 read_rpm_name (guestfs_h *g,
                const unsigned char *key, size_t keylen,
                const unsigned char *value, size_t valuelen,
 static int
 read_rpm_name (guestfs_h *g,
                const unsigned char *key, size_t keylen,
                const unsigned char *value, size_t valuelen,
-               void *appsv)
+               void *listv)
 {
 {
-  struct guestfs_application_list *apps = appsv;
+  struct rpm_names_list *list = listv;
   char *name;
 
   char *name;
 
+  /* Ignore bogus entries. */
+  if (keylen == 0 || valuelen < 4)
+    return 0;
+
   /* The name (key) field won't be NUL-terminated, so we must do that. */
   name = safe_malloc (g, keylen+1);
   memcpy (name, key, keylen);
   name[keylen] = '\0';
 
   /* The name (key) field won't be NUL-terminated, so we must do that. */
   name = safe_malloc (g, keylen+1);
   memcpy (name, key, keylen);
   name[keylen] = '\0';
 
-  add_application (g, apps, name, "", 0, "", "", "", "", "", "");
+  list->names = safe_realloc (g, list->names,
+                              (list->len + 1) * sizeof (struct rpm_name));
+  list->names[list->len].name = name;
+  memcpy (list->names[list->len].link, value, 4);
+  list->len++;
 
 
-  free (name);
+  return 0;
+}
+
+struct read_package_data {
+  struct rpm_names_list *list;
+  struct guestfs_application_list *apps;
+};
+
+static int
+read_package (guestfs_h *g,
+              const unsigned char *key, size_t keylen,
+              const unsigned char *value, size_t valuelen,
+              void *datav)
+{
+  struct read_package_data *data = datav;
+  struct rpm_name nkey, *entry;
+  char *p;
+  size_t len;
+  ssize_t max;
+  char *nul_name_nul, *version, *release;
+
+  /* This function reads one (key, value) pair from the Packages
+   * database.  The key is the link field (see struct rpm_name).  The
+   * value is a long binary string, but we can extract the version
+   * number from it as below.  First we have to look up the link field
+   * in the list of links (which is sorted by link field).
+   */
+
+  /* Ignore bogus entries. */
+  if (keylen < 4 || valuelen == 0)
+    return 0;
+
+  /* Look up the link (key) in the list. */
+  memcpy (nkey.link, key, 4);
+  entry = bsearch (&nkey, data->list->names, data->list->len,
+                   sizeof (struct rpm_name), compare_links);
+  if (!entry)
+    return 0;                   /* Not found - ignore it. */
+
+  /* We found a matching link entry, so that gives us the application
+   * name (entry->name).  Now we can get other data for this
+   * application out of the binary value string.  XXX This is a real
+   * hack.
+   */
+
+  /* Look for \0<name>\0 */
+  len = strlen (entry->name);
+  nul_name_nul = safe_malloc (g, len + 2);
+  nul_name_nul[0] = '\0';
+  memcpy (&nul_name_nul[1], entry->name, len);
+  nul_name_nul[len+1] = '\0';
+  p = memmem (value, valuelen, nul_name_nul, len+2);
+  free (nul_name_nul);
+  if (!p)
+    return 0;
+
+  /* Following that are \0-delimited version and release fields. */
+  p += len + 2; /* Note we have to skip \0 + name + \0. */
+  max = valuelen - (p - (char *) value);
+  if (max < 0)
+    max = 0;
+  version = safe_strndup (g, p, max);
+
+  len = strlen (version);
+  p += len + 1;
+  max = valuelen - (p - (char *) value);
+  if (max < 0)
+    max = 0;
+  release = safe_strndup (g, p, max);
+
+  /* Add the application and what we know. */
+  add_application (g, data->apps, entry->name, "", 0, version, release,
+                   "", "", "", "");
+
+  free (version);
+  free (release);
 
   return 0;
 }
 
   return 0;
 }
@@ -151,27 +262,53 @@ read_rpm_name (guestfs_h *g,
 static struct guestfs_application_list *
 list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
 {
 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);
+  char *Name = NULL, *Packages = NULL;
+  struct rpm_names_list list = { .names = NULL, .len = 0 };
+  struct guestfs_application_list *apps = NULL;
 
 
-  if (guestfs___download_to_tmp (g, "/var/lib/rpm/Name", basename,
-                                 MAX_PKG_DB_SIZE) == -1)
-    return NULL;
+  Name = guestfs___download_to_tmp (g, fs,
+                                    "/var/lib/rpm/Name", "rpm_Name",
+                                    MAX_PKG_DB_SIZE);
+  if (Name == NULL)
+    goto error;
+
+  Packages = guestfs___download_to_tmp (g, fs,
+                                        "/var/lib/rpm/Packages", "rpm_Packages",
+                                        MAX_PKG_DB_SIZE);
+  if (Packages == NULL)
+    goto error;
+
+  /* Read Name database. */
+  if (guestfs___read_db_dump (g, Name, &list, read_rpm_name) == -1)
+    goto error;
+
+  /* Sort the names by link field for fast searching. */
+  qsort (list.names, list.len, sizeof (struct rpm_name), compare_links);
 
   /* Allocate 'apps' list. */
 
   /* Allocate 'apps' list. */
-  struct guestfs_application_list *apps;
   apps = safe_malloc (g, sizeof *apps);
   apps->len = 0;
   apps->val = NULL;
 
   apps = safe_malloc (g, sizeof *apps);
   apps->len = 0;
   apps->val = NULL;
 
-  if (guestfs___read_db_dump (g, tmpdir_basename, apps, read_rpm_name) == -1) {
-    guestfs_free_application_list (apps);
-    return NULL;
-  }
+  /* Read Packages database. */
+  struct read_package_data data = { .list = &list, .apps = apps };
+  if (guestfs___read_db_dump (g, Packages, &data, read_package) == -1)
+    goto error;
+
+  free (Name);
+  free (Packages);
+  free_rpm_names_list (&list);
 
   return apps;
 
   return apps;
+
+ error:
+  free (Name);
+  free (Packages);
+  free_rpm_names_list (&list);
+  if (apps != NULL)
+    guestfs_free_application_list (apps);
+
+  return NULL;
 }
 
 #endif /* defined DB_DUMP */
 }
 
 #endif /* defined DB_DUMP */
@@ -179,13 +316,10 @@ 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)
 {
 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 (guestfs___download_to_tmp (g, "/var/lib/dpkg/status", basename,
-                                 MAX_PKG_DB_SIZE) == -1)
+  char *status = NULL;
+  status = guestfs___download_to_tmp (g, fs, "/var/lib/dpkg/status", "status",
+                                      MAX_PKG_DB_SIZE);
+  if (status == NULL)
     return NULL;
 
   struct guestfs_application_list *apps = NULL, *ret = NULL;
     return NULL;
 
   struct guestfs_application_list *apps = NULL, *ret = NULL;
@@ -195,9 +329,9 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   char *name = NULL, *version = NULL, *release = NULL;
   int installed_flag = 0;
 
   char *name = NULL, *version = NULL, *release = NULL;
   int installed_flag = 0;
 
-  fp = fopen (tmpdir_basename, "r");
+  fp = fopen (status, "r");
   if (fp == NULL) {
   if (fp == NULL) {
-    perrorf (g, "fopen: %s", tmpdir_basename);
+    perrorf (g, "fopen: %s", status);
     goto out;
   }
 
     goto out;
   }
 
@@ -253,7 +387,7 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   }
 
   if (fclose (fp) == -1) {
   }
 
   if (fclose (fp) == -1) {
-    perrorf (g, "fclose: %s", tmpdir_basename);
+    perrorf (g, "fclose: %s", status);
     goto out;
   }
   fp = NULL;
     goto out;
   }
   fp = NULL;
@@ -268,6 +402,7 @@ list_applications_deb (guestfs_h *g, struct inspect_fs *fs)
   free (name);
   free (version);
   free (release);
   free (name);
   free (version);
   free (release);
+  free (status);
   return ret;
 }
 
   return ret;
 }
 
@@ -276,34 +411,31 @@ 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)
 {
 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 = guestfs___case_sensitive_path_silently (g, software);
   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
-     * list windows apps.
-     */
-    return 0;
+  if (!software_path) {
+    /* Missing software hive is a problem. */
+    error (g, "no HKLM\\SOFTWARE hive found in the guest");
+    return NULL;
+  }
 
 
+  char *software_hive = NULL;
   struct guestfs_application_list *ret = NULL;
   hive_h *h = NULL;
 
   struct guestfs_application_list *ret = NULL;
   hive_h *h = NULL;
 
-  if (guestfs___download_to_tmp (g, software_path, basename,
-                                 MAX_REGISTRY_SIZE) == -1)
+  software_hive = guestfs___download_to_tmp (g, fs, software_path, "software",
+                                             MAX_REGISTRY_SIZE);
+  if (software_hive == NULL)
     goto out;
 
   free (software_path);
   software_path = NULL;
 
     goto out;
 
   free (software_path);
   software_path = NULL;
 
-  h = hivex_open (tmpdir_basename, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
+  h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0);
   if (h == NULL) {
     perrorf (g, "hivex_open");
     goto out;
   if (h == NULL) {
     perrorf (g, "hivex_open");
     goto out;
@@ -331,6 +463,7 @@ list_applications_windows (guestfs_h *g, struct inspect_fs *fs)
  out:
   if (h) hivex_close (h);
   free (software_path);
  out:
   if (h) hivex_close (h);
   free (software_path);
+  free (software_hive);
 
   return ret;
 }
 
   return ret;
 }
@@ -466,12 +599,12 @@ sort_applications (struct guestfs_application_list *apps)
            compare_applications);
 }
 
            compare_applications);
 }
 
-#else /* no PCRE or hivex at compile time */
+#else /* no hivex at compile time */
 
 /* XXX These functions should be in an optgroup. */
 
 #define NOT_IMPL(r)                                                     \
 
 /* 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")); \
+  error (g, _("inspection API not available since this version of libguestfs was compiled without the hivex library")); \
   return r
 
 struct guestfs_application_list *
   return r
 
 struct guestfs_application_list *
@@ -480,4 +613,4 @@ guestfs__inspect_list_applications (guestfs_h *g, const char *root)
   NOT_IMPL(NULL);
 }
 
   NOT_IMPL(NULL);
 }
 
-#endif /* no PCRE or hivex at compile time */
+#endif /* no hivex at compile time */