inspect: Get version and release of RPM packages.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Apr 2011 16:53:48 +0000 (17:53 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Apr 2011 17:19:42 +0000 (18:19 +0100)
This commit downloads the Packages RPM database allowing us to find
other details about installed RPM packages (via
inspect-list-applications).  This adds version and release.  Epoch
cannot yet be found.

This commit also updates the Fedora example image so that it contains
a dummy RPM Packages database with some data.

.gitignore
images/Makefile.am
images/guest-aux/fedora-packages.db.txt [new file with mode: 0644]
images/guest-aux/make-fedora-img.sh
inspector/example-fedora.xml
src/guestfs-internal.h
src/inspect_apps.c

index 6b96fb8..06903d4 100644 (file)
@@ -144,6 +144,7 @@ images/abssymlink
 images/debian.img
 images/fedora.img
 images/guest-aux/fedora-name.db
+images/guest-aux/fedora-packages.db
 images/hello.b64
 images/initrd
 images/initrd-x86_64.img
index d45e699..97fccce 100644 (file)
@@ -168,7 +168,9 @@ $(builddir)/test-grep.txt.gz: test-grep.txt
        mv $@-t $@
 
 # Make a (dummy) Fedora image.
-fedora.img: guest-aux/make-fedora-img.sh guest-aux/fedora-name.db
+fedora.img: guest-aux/make-fedora-img.sh \
+               guest-aux/fedora-name.db \
+               guest-aux/fedora-packages.db
        LIBGUESTFS_PATH=../appliance \
        LD_LIBRARY_PATH=../src/.libs \
        bash $<
@@ -176,6 +178,9 @@ fedora.img: guest-aux/make-fedora-img.sh guest-aux/fedora-name.db
 guest-aux/fedora-name.db: guest-aux/fedora-name.db.txt
        $(DB_LOAD) $@ < $<
 
+guest-aux/fedora-packages.db: guest-aux/fedora-packages.db.txt
+       $(DB_LOAD) $@ < $<
+
 # Make a (dummy) Debian image.
 debian.img: guest-aux/make-debian-img.sh
        LIBGUESTFS_PATH=../appliance \
diff --git a/images/guest-aux/fedora-packages.db.txt b/images/guest-aux/fedora-packages.db.txt
new file mode 100644 (file)
index 0000000..3939d6b
--- /dev/null
@@ -0,0 +1,13 @@
+VERSION=3
+format=print
+type=hash
+h_nelem=3
+db_pagesize=4096
+HEADER=END
+ !\0b\00\00
+ \00test1\001.0\001.fc14\00
+ 7\0b\00\00
+ \00test2\002.0\002.fc14\00
+ \dd\0c\00\00
+ \00test3\003.0\003.fc14\00
+DATA=END
index 8c38e51..8f198f6 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/bash -
 # libguestfs
-# Copyright (C) 2010 Red Hat Inc.
+# Copyright (C) 2010-2011 Red Hat Inc.
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -73,6 +73,7 @@ write /etc/fedora-release "Fedora release 14 (Phony)"
 write /etc/sysconfig/network "HOSTNAME=fedora.invalid"
 
 upload guest-aux/fedora-name.db /var/lib/rpm/Name
+upload guest-aux/fedora-packages.db /var/lib/rpm/Packages
 
 upload bin-x86_64-dynamic /bin/ls
 
index 797aa58..1444bb7 100644 (file)
     <applications>
       <application>
         <name>test1</name>
+        <version>1.0</version>
+        <release>1.fc14</release>
       </application>
       <application>
         <name>test2</name>
+        <version>2.0</version>
+        <release>2.fc14</release>
       </application>
       <application>
         <name>test3</name>
+        <version>3.0</version>
+        <release>3.fc14</release>
       </application>
     </applications>
   </operatingsystem>
index 1730f68..c6d7c87 100644 (file)
  */
 #define MAX_REGISTRY_SIZE    (100 * 1000 * 1000)
 
-/* Maximum RPM or dpkg database we will download to /tmp. */
-#define MAX_PKG_DB_SIZE       (10 * 1000 * 1000)
+/* Maximum RPM or dpkg database we will download to /tmp.  RPM
+ * 'Packages' database can get very large: 70 MB is roughly the
+ * standard size for a new Fedora install, and after lots of package
+ * installation/removal I have seen well over 100 MB databases.
+ */
+#define MAX_PKG_DB_SIZE       (300 * 1000 * 1000)
 
 /* Network configuration of the appliance.  Note these addresses are
  * only meaningful within the context of the running appliance.  QEMU
index 8ce09ab..d7634d8 100644 (file)
@@ -127,23 +127,136 @@ guestfs__inspect_list_applications (guestfs_h *g, const char *root)
 
 #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,
-               void *appsv)
+               void *listv)
 {
-  struct guestfs_application_list *apps = appsv;
+  struct rpm_names_list *list = listv;
   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';
 
-  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;
 }
@@ -151,26 +264,56 @@ read_rpm_name (guestfs_h *g,
 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);
+  const char *name = "rpm_Name";
+  char tmpdir_name[strlen (g->tmpdir) + strlen (name) + 2];
+  snprintf (tmpdir_name, sizeof tmpdir_name, "%s/%s",
+            g->tmpdir, name);
+
+  if (guestfs___download_to_tmp (g, "/var/lib/rpm/Name", name,
+                                 MAX_PKG_DB_SIZE) == -1)
+    return NULL;
+
+  const char *pkgs = "rpm_Packages";
+  char tmpdir_pkgs[strlen (g->tmpdir) + strlen (pkgs) + 2];
+  snprintf (tmpdir_pkgs, sizeof tmpdir_pkgs, "%s/%s",
+            g->tmpdir, pkgs);
 
-  if (guestfs___download_to_tmp (g, "/var/lib/rpm/Name", basename,
+  if (guestfs___download_to_tmp (g, "/var/lib/rpm/Packages", pkgs,
                                  MAX_PKG_DB_SIZE) == -1)
     return NULL;
 
+  /* Allocate interim structure to store names and links. */
+  struct rpm_names_list list;
+  list.names = NULL;
+  list.len = 0;
+
+  /* Read Name database. */
+  if (guestfs___read_db_dump (g, tmpdir_name, &list, read_rpm_name) == -1) {
+    free_rpm_names_list (&list);
+    return NULL;
+  }
+
+  /* Sort the names by link field for fast searching. */
+  qsort (list.names, list.len, sizeof (struct rpm_name), compare_links);
+
   /* Allocate 'apps' list. */
   struct guestfs_application_list *apps;
   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) {
+  /* Read Packages database. */
+  struct read_package_data data;
+  data.list = &list;
+  data.apps = apps;
+  if (guestfs___read_db_dump (g, tmpdir_pkgs, &data, read_package) == -1) {
+    free_rpm_names_list (&list);
     guestfs_free_application_list (apps);
     return NULL;
   }
 
+  free_rpm_names_list (&list);
+
   return apps;
 }