inspect: Abstract out db_dump code for listing RPM applications.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Apr 2011 12:28:02 +0000 (13:28 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 14 Apr 2011 12:28:02 +0000 (13:28 +0100)
There are two changes here:

(1) The code for listing RPM applications ran db_dump and parsed the
output.  We abstract out that parsing code into a separate reusable
module (src/dbdump.c).

(2) The old db_dump parsing code used db_dump -p (printable) format.
Instead use db_dump -k (hex) format so we can read binary fields.

po/POTFILES.in
src/Makefile.am
src/dbdump.c [new file with mode: 0644]
src/guestfs-internal.h
src/inspect_apps.c

index 3077ebe..663490c 100644 (file)
@@ -132,6 +132,7 @@ ruby/ext/guestfs/_guestfs.c
 src/actions.c
 src/appliance.c
 src/bindtests.c
+src/dbdump.c
 src/errnostring.c
 src/errnostring_gperf.c
 src/events.c
index 6aea6cc..32ac7df 100644 (file)
@@ -124,6 +124,7 @@ libguestfs_la_SOURCES = \
        actions.c \
        appliance.c \
        bindtests.c \
+       dbdump.c \
        events.c \
        filearch.c \
        inspect.c \
diff --git a/src/dbdump.c b/src/dbdump.c
new file mode 100644 (file)
index 0000000..f9d06aa
--- /dev/null
@@ -0,0 +1,220 @@
+/* libguestfs
+ * Copyright (C) 2010-2011 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#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>
+#endif
+
+#ifdef HAVE_HIVEX
+#include <hivex.h>
+#endif
+
+#include "c-ctype.h"
+#include "ignore-value.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+#if defined(HAVE_PCRE) && defined(HAVE_HIVEX) && defined(DB_DUMP)
+
+static unsigned char *convert_hex_to_binary (guestfs_h *g, const char *hex, size_t hexlen, size_t *binlen_rtn);
+
+/* This helper function is specialized to just reading the hash-format
+ * output from db_dump/db4_dump.  It's just enough to support the RPM
+ * database format.  Note that the filename must not contain any shell
+ * characters (this is guaranteed by the caller).
+ */
+int
+guestfs___read_db_dump (guestfs_h *g,
+                        const char *dumpfile, void *opaque,
+                        guestfs___db_dump_callback callback)
+{
+#define cmd_len (strlen (dumpfile) + 64)
+  char cmd[cmd_len];
+  FILE *pp = NULL;
+  char *line = NULL;
+  size_t len = 0;
+  ssize_t linelen;
+  unsigned char *key = NULL, *value = NULL;
+  size_t keylen, valuelen;
+  int ret = -1;
+
+  snprintf (cmd, cmd_len, DB_DUMP " -k '%s'", dumpfile);
+
+  debug (g, "read_db_dump command: %s", cmd);
+
+  pp = popen (cmd, "r");
+  if (pp == NULL) {
+    perrorf (g, "popen: %s", cmd);
+    goto out;
+  }
+
+  /* Ignore everything to end-of-header marker. */
+  while ((linelen = getline (&line, &len, pp)) != -1) {
+    if (STRPREFIX (line, "HEADER=END"))
+      break;
+  }
+
+  if (linelen == -1) {
+    error (g, _("unexpected end of output from db_dump command before end of header"));
+    goto out;
+  }
+
+  /* Now read the key, value pairs.  They are prefixed with a space and
+   * printed as hex strings, so convert those strings to binary.  Pass
+   * the strings up to the callback function.
+   */
+  while ((linelen = getline (&line, &len, pp)) != -1) {
+    if (STRPREFIX (line, "DATA=END"))
+      break;
+
+    if (linelen < 1 || line[0] != ' ') {
+      error (g, _("unexpected line from db_dump command, no space prefix"));
+      goto out;
+    }
+
+    if ((key = convert_hex_to_binary (g, &line[1], linelen-1,
+                                      &keylen)) == NULL)
+      goto out;
+
+    if ((linelen = getline (&line, &len, pp)) == -1)
+      break;
+
+    if (linelen < 1 || line[0] != ' ') {
+      error (g, _("unexpected line from db_dump command, no space prefix"));
+      goto out;
+    }
+
+    if ((value = convert_hex_to_binary (g, &line[1], linelen-1,
+                                        &valuelen)) == NULL)
+      goto out;
+
+    if (callback (g, key, keylen, value, valuelen, opaque) == -1)
+      goto out;
+
+    free (key);
+    free (value);
+    key = value = NULL;
+  }
+
+  if (linelen == -1) {
+    error (g, _("unexpected end of output from db_dump command before end of data"));
+    goto out;
+  }
+
+  /* Catch errors from the db_dump command. */
+  if (pclose (pp) == -1) {
+    perrorf (g, "pclose: %s", cmd);
+    goto out;
+  }
+  pp = NULL;
+
+  ret = 0;
+
+ out:
+  if (pp)
+    pclose (pp);
+
+  free (line);
+  free (key);
+  free (value);
+
+  return ret;
+#undef cmd_len
+}
+
+static int
+convert_hex_octet (const char *h)
+{
+  int r;
+
+  switch (h[0]) {
+  case 'a'...'f':
+    r = (h[0] - 'a' + 10) << 4;
+    break;
+  case 'A'...'F':
+    r = (h[0] - 'A' + 10) << 4;
+    break;
+  case '0'...'9':
+    r = (h[0] - '0') << 4;
+    break;
+  default:
+    return -1;
+  }
+
+  switch (h[1]) {
+  case 'a'...'f':
+    r |= h[1] - 'a' + 10;
+    break;
+  case 'A'...'F':
+    r |= h[1] - 'A' + 10;
+    break;
+  case '0'...'9':
+    r |= h[1] - '0';
+    break;
+  default:
+    return -1;
+  }
+
+  return r;
+}
+
+static unsigned char *
+convert_hex_to_binary (guestfs_h *g, const char *hex, size_t hexlen,
+                       size_t *binlen_rtn)
+{
+  unsigned char *bin;
+  size_t binlen;
+  size_t i, o;
+  int b;
+
+  if (hexlen > 0 && hex[hexlen-1] == '\n')
+    hexlen--;
+
+  binlen = hexlen / 2;
+  bin = safe_malloc (g, binlen);
+
+  for (i = o = 0; i+1 < hexlen && o < binlen; i += 2, ++o) {
+    b = convert_hex_octet (&hex[i]);
+    if (b >= 0)
+      bin[o] = b;
+    else {
+      error (g, _("unexpected non-hex digits in output of db_dump command"));
+      free (bin);
+      return NULL;
+    }
+  }
+
+  *binlen_rtn = binlen;
+  return bin;
+}
+
+#endif /* defined(HAVE_PCRE) && defined(HAVE_HIVEX) && defined(DB_DUMP) */
index b75aee8..1730f68 100644 (file)
@@ -355,6 +355,8 @@ extern int guestfs___parse_unsigned_int_ignore_trailing (guestfs_h *g, const cha
 extern int guestfs___parse_major_minor (guestfs_h *g, struct inspect_fs *fs);
 extern char *guestfs___first_line_of_file (guestfs_h *g, const char *filename);
 extern int guestfs___first_egrep_of_file (guestfs_h *g, const char *filename, const char *eregex, int iflag, char **ret);
+typedef int (*guestfs___db_dump_callback) (guestfs_h *g, const unsigned char *key, size_t keylen, const unsigned char *value, size_t valuelen, void *opaque);
+extern int guestfs___read_db_dump (guestfs_h *g, const char *dumpfile, void *opaque, guestfs___db_dump_callback callback);
 extern int guestfs___check_installer_root (guestfs_h *g, struct inspect_fs *fs);
 extern int guestfs___check_linux_root (guestfs_h *g, struct inspect_fs *fs);
 extern int guestfs___check_freebsd_root (guestfs_h *g, struct inspect_fs *fs);
index df99df9..8ce09ab 100644 (file)
@@ -126,6 +126,28 @@ guestfs__inspect_list_applications (guestfs_h *g, const char *root)
 }
 
 #ifdef DB_DUMP
+
+static int
+read_rpm_name (guestfs_h *g,
+               const unsigned char *key, size_t keylen,
+               const unsigned char *value, size_t valuelen,
+               void *appsv)
+{
+  struct guestfs_application_list *apps = appsv;
+  char *name;
+
+  /* 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, "", "", "", "", "", "");
+
+  free (name);
+
+  return 0;
+}
+
 static struct guestfs_application_list *
 list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
 {
@@ -138,95 +160,20 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
                                  MAX_PKG_DB_SIZE) == -1)
     return NULL;
 
-  struct guestfs_application_list *apps = NULL, *ret = NULL;
-#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'", tmpdir_basename);
-
-  debug (g, "list_applications_rpm: %s", cmd);
-
-  pp = popen (cmd, "r");
-  if (pp == NULL) {
-    perrorf (g, "popen: %s", cmd);
-    goto out;
-  }
-
-  /* Ignore everything to end-of-header marker. */
-  for (;;) {
-    if (fgets (line, sizeof line, pp) == NULL) {
-      error (g, _("unexpected end of output from db_dump command"));
-      goto out;
-    }
-
-    len = strlen (line);
-    if (len > 0 && line[len-1] == '\n') {
-      line[len-1] = '\0';
-      len--;
-    }
-
-    if (STREQ (line, "HEADER=END"))
-      break;
-  }
-
   /* Allocate 'apps' list. */
+  struct guestfs_application_list *apps;
   apps = safe_malloc (g, sizeof *apps);
   apps->len = 0;
   apps->val = NULL;
 
-  /* Read alternate lines until end of data marker. */
-  for (;;) {
-    if (fgets (line, sizeof line, pp) == NULL) {
-      error (g, _("unexpected end of output from db_dump command"));
-      goto out;
-    }
-
-    len = strlen (line);
-    if (len > 0 && line[len-1] == '\n') {
-      line[len-1] = '\0';
-      len--;
-    }
-
-    if (STREQ (line, "DATA=END"))
-      break;
-
-    char *p = line;
-    if (len > 0 && line[0] == ' ')
-      p = line+1;
-    /* Ignore any application name that contains non-printable chars.
-     * In the db_dump output these would be escaped with backslash, so
-     * we can just ignore any such line.
-     */
-    if (strchr (p, '\\') == NULL)
-      add_application (g, apps, p, "", 0, "", "", "", "", "", "");
-
-    /* Discard next line. */
-    if (fgets (line, sizeof line, pp) == NULL) {
-      error (g, _("unexpected end of output from db_dump command"));
-      goto out;
-    }
-  }
-
-  /* Catch errors from the db_dump command. */
-  if (pclose (pp) == -1) {
-    perrorf (g, "pclose: %s", cmd);
-    goto out;
-  }
-  pp = NULL;
-
-  ret = apps;
-
- out:
-  if (ret == NULL && apps != NULL)
+  if (guestfs___read_db_dump (g, tmpdir_basename, apps, read_rpm_name) == -1) {
     guestfs_free_application_list (apps);
-  if (pp)
-    pclose (pp);
+    return NULL;
+  }
 
-  return ret;
+  return apps;
 }
+
 #endif /* defined DB_DUMP */
 
 static struct guestfs_application_list *