From d95874db3dc6c415061b86275d03770b4f28ffbb Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 14 Apr 2011 17:53:48 +0100 Subject: [PATCH] inspect: Get version and release of RPM packages. 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 | 1 + images/Makefile.am | 7 +- images/guest-aux/fedora-packages.db.txt | 13 +++ images/guest-aux/make-fedora-img.sh | 3 +- inspector/example-fedora.xml | 6 ++ src/guestfs-internal.h | 8 +- src/inspect_apps.c | 163 ++++++++++++++++++++++++++++++-- 7 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 images/guest-aux/fedora-packages.db.txt diff --git a/.gitignore b/.gitignore index 6b96fb8..06903d4 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/images/Makefile.am b/images/Makefile.am index d45e699..97fccce 100644 --- a/images/Makefile.am +++ b/images/Makefile.am @@ -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 index 0000000..3939d6b --- /dev/null +++ b/images/guest-aux/fedora-packages.db.txt @@ -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 diff --git a/images/guest-aux/make-fedora-img.sh b/images/guest-aux/make-fedora-img.sh index 8c38e51..8f198f6 100755 --- a/images/guest-aux/make-fedora-img.sh +++ b/images/guest-aux/make-fedora-img.sh @@ -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 diff --git a/inspector/example-fedora.xml b/inspector/example-fedora.xml index 797aa58..1444bb7 100644 --- a/inspector/example-fedora.xml +++ b/inspector/example-fedora.xml @@ -30,12 +30,18 @@ test1 + 1.0 + 1.fc14 test2 + 2.0 + 2.fc14 test3 + 3.0 + 3.fc14 diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 1730f68..c6d7c87 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -82,8 +82,12 @@ */ #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 diff --git a/src/inspect_apps.c b/src/inspect_apps.c index 8ce09ab..d7634d8 100644 --- a/src/inspect_apps.c +++ b/src/inspect_apps.c @@ -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\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; } -- 1.8.3.1