1065 lines
32 KiB
Diff
1065 lines
32 KiB
Diff
From 2ff8ad25680373997c2975d7ca51f3af7115eb01 Mon Sep 17 00:00:00 2001
|
||
From: "Richard W.M. Jones" <rjones@redhat.com>
|
||
Date: Fri, 26 Mar 2021 12:19:28 +0000
|
||
Subject: [PATCH] inspection: Fix inspection of recent RPM guests using
|
||
non-BDB.
|
||
|
||
Recent RPM-based guests have switched from using Berkeley DB (BDB) to
|
||
sqlite. In order to inspect these guests (and earlier ones) we need
|
||
to stop using the hokey parsing of the BDB and use librpm APIs
|
||
instead.
|
||
|
||
This commit adds a new internal API so we can call librpm from the
|
||
daemon, and changes the library part to use the new API for RPM-based
|
||
guests.
|
||
|
||
This change removes the requirement for BDB tools like db_dump.
|
||
|
||
See also:
|
||
http://lists.rpm.org/pipermail/rpm-ecosystem/2021-March/000751.html
|
||
http://lists.rpm.org/pipermail/rpm-ecosystem/2021-March/000754.html
|
||
https://blog.fpmurphy.com/2011/08/programmatically-retrieve-rpm-package-details.html
|
||
|
||
This breaks the virt-inspector test (now in the separate guestfs-tools
|
||
repository). However this is not a bug in libguestfs, but a bug in
|
||
the phoney Fedora guest that we use for testing - we created a
|
||
BDB-style RPM database which was supposed to be just enough to make
|
||
the old code work. The new code using real librpm needs
|
||
/usr/lib/rpm/rpmrc (not present in the phoney image) and also cannot
|
||
parse the phoney database, so we will need to separately rework that
|
||
test.
|
||
|
||
Thanks: Panu Matilainen
|
||
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1766487
|
||
Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1409024
|
||
(cherry picked from commit c9ee831affed55abe0f928134cbbd2ed83b2f510)
|
||
---
|
||
.gitignore | 1 +
|
||
appliance/packagelist.in | 2 +
|
||
daemon/Makefile.am | 8 +-
|
||
daemon/rpm-c.c | 161 ++++++++++++++++++++
|
||
daemon/rpm.ml | 58 +++++++
|
||
docs/C_SOURCE_FILES | 2 +-
|
||
docs/guestfs-building.pod | 9 +-
|
||
generator/actions_inspection.ml | 10 ++
|
||
generator/proc_nr.ml | 1 +
|
||
lib/MAX_PROC_NR | 2 +-
|
||
lib/Makefile.am | 1 -
|
||
lib/dbdump.c | 229 ----------------------------
|
||
lib/guestfs-internal.h | 4 -
|
||
lib/inspect-apps.c | 259 +-------------------------------
|
||
m4/guestfs-daemon.m4 | 8 +
|
||
m4/guestfs-progs.m4 | 10 --
|
||
16 files changed, 258 insertions(+), 507 deletions(-)
|
||
create mode 100644 daemon/rpm-c.c
|
||
create mode 100644 daemon/rpm.ml
|
||
delete mode 100644 lib/dbdump.c
|
||
|
||
diff --git a/.gitignore b/.gitignore
|
||
index 7a4696b90..c8b53cac9 100644
|
||
--- a/.gitignore
|
||
+++ b/.gitignore
|
||
@@ -176,6 +176,7 @@ Makefile.in
|
||
/daemon/optgroups.mli
|
||
/daemon/parted.mli
|
||
/daemon/realpath.mli
|
||
+/daemon/rpm.mli
|
||
/daemon/stamp-guestfsd.pod
|
||
/daemon/statvfs.mli
|
||
/daemon/structs-cleanups.c
|
||
diff --git a/appliance/packagelist.in b/appliance/packagelist.in
|
||
index 25f2645d2..15af4284a 100644
|
||
--- a/appliance/packagelist.in
|
||
+++ b/appliance/packagelist.in
|
||
@@ -41,6 +41,7 @@ ifelse(REDHAT,1,
|
||
openssh-clients
|
||
policycoreutils
|
||
reiserfs-utils
|
||
+ rpm-libs
|
||
syslinux-extlinux
|
||
systemd dnl for /sbin/reboot and udevd
|
||
vim-minimal
|
||
@@ -71,6 +72,7 @@ dnl iproute has been renamed to iproute2
|
||
isc-dhcp-client
|
||
ldmtool
|
||
libc-bin
|
||
+ librpm9
|
||
linux-image
|
||
dnl syslinux 'suggests' mtools, but in reality it's a hard dependency:
|
||
mtools
|
||
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
|
||
index df9dcc4ee..7f2d13414 100644
|
||
--- a/daemon/Makefile.am
|
||
+++ b/daemon/Makefile.am
|
||
@@ -57,6 +57,7 @@ generator_built = \
|
||
optgroups.mli \
|
||
parted.mli \
|
||
realpath.mli \
|
||
+ rpm.mli \
|
||
statvfs.mli \
|
||
structs.ml \
|
||
structs.mli
|
||
@@ -167,6 +168,7 @@ guestfsd_SOURCES = \
|
||
proto.c \
|
||
readdir.c \
|
||
rename.c \
|
||
+ rpm-c.c \
|
||
rsync.c \
|
||
scrub.c \
|
||
selinux.c \
|
||
@@ -231,6 +233,7 @@ guestfsd_LDADD = \
|
||
$(LIB_CLOCK_GETTIME) \
|
||
$(LIBINTL) \
|
||
$(PCRE_LIBS) \
|
||
+ $(LIBRPM_LIBS) \
|
||
$(TSK_LIBS) \
|
||
$(RPC_LIBS) \
|
||
$(YARA_LIBS) \
|
||
@@ -260,7 +263,8 @@ guestfsd_CFLAGS = \
|
||
$(HIVEX_CFLAGS) \
|
||
$(SD_JOURNAL_CFLAGS) \
|
||
$(JANSSON_CFLAGS) \
|
||
- $(PCRE_CFLAGS)
|
||
+ $(PCRE_CFLAGS) \
|
||
+ $(LIBRPM_CFLAGS)
|
||
|
||
# Parts of the daemon are written in OCaml. These are linked into a
|
||
# library and then linked to the daemon. See
|
||
@@ -298,6 +302,7 @@ SOURCES_MLI = \
|
||
optgroups.mli \
|
||
parted.mli \
|
||
realpath.mli \
|
||
+ rpm.mli \
|
||
statvfs.mli \
|
||
structs.mli \
|
||
sysroot.mli \
|
||
@@ -338,6 +343,7 @@ SOURCES_ML = \
|
||
inspect_fs_windows.ml \
|
||
inspect_fs.ml \
|
||
inspect.ml \
|
||
+ rpm.ml \
|
||
callbacks.ml \
|
||
daemon.ml
|
||
|
||
diff --git a/daemon/rpm-c.c b/daemon/rpm-c.c
|
||
new file mode 100644
|
||
index 000000000..92a3abf58
|
||
--- /dev/null
|
||
+++ b/daemon/rpm-c.c
|
||
@@ -0,0 +1,161 @@
|
||
+/* libguestfs - the guestfsd daemon
|
||
+ * Copyright (C) 2021 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
|
||
+ * the Free Software Foundation; either version 2 of the License, or
|
||
+ * (at your option) any later version.
|
||
+ *
|
||
+ * This program 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 General Public License for more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License
|
||
+ * along with this program; 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 <stdbool.h>
|
||
+#include <inttypes.h>
|
||
+#include <string.h>
|
||
+#include <unistd.h>
|
||
+
|
||
+#include <caml/alloc.h>
|
||
+#include <caml/fail.h>
|
||
+#include <caml/memory.h>
|
||
+#include <caml/mlvalues.h>
|
||
+
|
||
+#ifdef HAVE_LIBRPM
|
||
+#include <rpm/rpmlib.h>
|
||
+#include <rpm/header.h>
|
||
+#include <rpm/rpmts.h>
|
||
+#include <rpm/rpmdb.h>
|
||
+#endif
|
||
+
|
||
+#include "daemon.h"
|
||
+#include "actions.h"
|
||
+
|
||
+/* Very lightweight OCaml bindings for librpm. */
|
||
+
|
||
+#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
|
||
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
|
||
+
|
||
+#ifndef HAVE_LIBRPM
|
||
+
|
||
+value __attribute__((noreturn))
|
||
+guestfs_int_daemon_rpm_init (value unitv)
|
||
+{
|
||
+ CAMLparam1 (unitv);
|
||
+ caml_failwith ("no support for RPM guests because "
|
||
+ "librpm was missing at compile time");
|
||
+}
|
||
+
|
||
+value __attribute__((noreturn))
|
||
+guestfs_int_daemon_rpm_start_iterator (value unitv)
|
||
+{
|
||
+ guestfs_int_daemon_rpm_init (unitv);
|
||
+}
|
||
+
|
||
+value __attribute__((noreturn))
|
||
+guestfs_int_daemon_rpm_next_application (value unitv)
|
||
+{
|
||
+ guestfs_int_daemon_rpm_init (unitv);
|
||
+}
|
||
+
|
||
+value __attribute__((noreturn))
|
||
+guestfs_int_daemon_rpm_end_iterator (value unitv)
|
||
+{
|
||
+ guestfs_int_daemon_rpm_init (unitv);
|
||
+}
|
||
+
|
||
+#else /* HAVE_LIBRPM */
|
||
+
|
||
+value
|
||
+guestfs_int_daemon_rpm_init (value unitv)
|
||
+{
|
||
+ CAMLparam1 (unitv);
|
||
+ rpmReadConfigFiles (NULL, NULL);
|
||
+ CAMLreturn (Val_unit);
|
||
+}
|
||
+
|
||
+static rpmts ts;
|
||
+static rpmdbMatchIterator iter;
|
||
+
|
||
+value
|
||
+guestfs_int_daemon_rpm_start_iterator (value unitv)
|
||
+{
|
||
+ CAMLparam1 (unitv);
|
||
+ ts = rpmtsCreate ();
|
||
+ iter = rpmtsInitIterator (ts, RPMDBI_PACKAGES, NULL, 0);
|
||
+ CAMLreturn (Val_unit);
|
||
+}
|
||
+
|
||
+value
|
||
+guestfs_int_daemon_rpm_next_application (value unitv)
|
||
+{
|
||
+ CAMLparam1 (unitv);
|
||
+ CAMLlocal2 (rv, sv);
|
||
+ Header h;
|
||
+ guestfs_int_application2 app = { 0 };
|
||
+
|
||
+ h = rpmdbNextIterator (iter);
|
||
+ if (h == NULL) caml_raise_not_found ();
|
||
+
|
||
+ h = headerLink (h);
|
||
+ app.app2_name = headerFormat (h, "%{NAME}", NULL);
|
||
+ // XXXapp.app2_epoch = headerFormat (h, "%{NAME}", NULL);
|
||
+ app.app2_version = headerFormat (h, "%{VERSION}", NULL);
|
||
+ app.app2_release = headerFormat (h, "%{RELEASE}", NULL);
|
||
+ app.app2_arch = headerFormat (h, "%{ARCH}", NULL);
|
||
+ app.app2_url = headerFormat (h, "%{URL}", NULL);
|
||
+ app.app2_summary = headerFormat (h, "%{SUMMARY}", NULL);
|
||
+ app.app2_description = headerFormat (h, "%{DESCRIPTION}", NULL);
|
||
+ headerFree (h);
|
||
+
|
||
+ /* Convert this to an OCaml struct. Any NULL fields must be turned
|
||
+ * into empty string.
|
||
+ */
|
||
+ rv = caml_alloc (17, 0);
|
||
+#define TO_CAML_STRING(i, name) \
|
||
+ sv = caml_copy_string (app.name ? app.name : ""); \
|
||
+ Store_field (rv, i, sv); \
|
||
+ free (app.name)
|
||
+
|
||
+ TO_CAML_STRING (0, app2_name);
|
||
+ TO_CAML_STRING (1, app2_display_name);
|
||
+ sv = caml_copy_int32 (app.app2_epoch);
|
||
+ Store_field (rv, 2, sv);
|
||
+ TO_CAML_STRING (3, app2_version);
|
||
+ TO_CAML_STRING (4, app2_release);
|
||
+ TO_CAML_STRING (5, app2_arch);
|
||
+ TO_CAML_STRING (6, app2_install_path);
|
||
+ TO_CAML_STRING (7, app2_trans_path);
|
||
+ TO_CAML_STRING (8, app2_publisher);
|
||
+ TO_CAML_STRING (9, app2_url);
|
||
+ TO_CAML_STRING (10, app2_source_package);
|
||
+ TO_CAML_STRING (11, app2_summary);
|
||
+ TO_CAML_STRING (12, app2_description);
|
||
+ TO_CAML_STRING (13, app2_spare1);
|
||
+ TO_CAML_STRING (14, app2_spare2);
|
||
+ TO_CAML_STRING (15, app2_spare3);
|
||
+ TO_CAML_STRING (16, app2_spare4);
|
||
+#undef TO_CAML_STRING
|
||
+
|
||
+ CAMLreturn (rv);
|
||
+}
|
||
+
|
||
+value
|
||
+guestfs_int_daemon_rpm_end_iterator (value unitv)
|
||
+{
|
||
+ CAMLparam1 (unitv);
|
||
+ rpmdbFreeIterator (iter);
|
||
+ rpmtsFree (ts);
|
||
+ CAMLreturn (Val_unit);
|
||
+}
|
||
+
|
||
+#endif /* HAVE_LIBRPM */
|
||
diff --git a/daemon/rpm.ml b/daemon/rpm.ml
|
||
new file mode 100644
|
||
index 000000000..f61ce41c5
|
||
--- /dev/null
|
||
+++ b/daemon/rpm.ml
|
||
@@ -0,0 +1,58 @@
|
||
+(* guestfs-inspection
|
||
+ * Copyright (C) 2009-2021 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
|
||
+ * the Free Software Foundation; either version 2 of the License, or
|
||
+ * (at your option) any later version.
|
||
+ *
|
||
+ * This program 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 General Public License for more details.
|
||
+ *
|
||
+ * You should have received a copy of the GNU General Public License along
|
||
+ * with this program; if not, write to the Free Software Foundation, Inc.,
|
||
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||
+ *)
|
||
+
|
||
+open Printf
|
||
+
|
||
+open Std_utils
|
||
+
|
||
+external rpm_init : unit -> unit = "guestfs_int_daemon_rpm_init"
|
||
+external rpm_start_iterator : unit -> unit = "guestfs_int_daemon_rpm_start_iterator"
|
||
+external rpm_next_application : unit -> Structs.application2 = "guestfs_int_daemon_rpm_next_application"
|
||
+external rpm_end_iterator : unit -> unit = "guestfs_int_daemon_rpm_end_iterator"
|
||
+
|
||
+(* librpm is troublesome when run from the main process. In
|
||
+ * particular it holds open some glibc NSS files. Therefore we fork
|
||
+ * before doing the chroot and any librpm operations.
|
||
+ *
|
||
+ * We could also consider in future limiting the time taken to run the
|
||
+ * subprocess since it's unclear that parsing RPM config files from
|
||
+ * the guest in particular is safe.
|
||
+ *)
|
||
+let rec internal_list_rpm_applications () =
|
||
+ let chroot = Chroot.create ~name:"librpm" () in
|
||
+ let apps = Chroot.f chroot list_rpm_applications () in
|
||
+ eprintf "librpm returned %d installed packages\n%!" (List.length apps);
|
||
+ apps
|
||
+
|
||
+and list_rpm_applications () =
|
||
+ rpm_init ();
|
||
+ rpm_start_iterator ();
|
||
+ let ret = ref [] in
|
||
+ let rec loop () =
|
||
+ try
|
||
+ let app = rpm_next_application () in
|
||
+ List.push_front app ret;
|
||
+ loop ()
|
||
+ with Not_found -> ()
|
||
+ in
|
||
+ loop ();
|
||
+ rpm_end_iterator ();
|
||
+ List.sort
|
||
+ (fun { Structs.app2_name = n1 } { Structs.app2_name = n2 } ->
|
||
+ compare n1 n2)
|
||
+ !ret
|
||
diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES
|
||
index 831b7e25a..8b6aa8896 100644
|
||
--- a/docs/C_SOURCE_FILES
|
||
+++ b/docs/C_SOURCE_FILES
|
||
@@ -149,6 +149,7 @@ daemon/pingdaemon.c
|
||
daemon/proto.c
|
||
daemon/readdir.c
|
||
daemon/rename.c
|
||
+daemon/rpm-c.c
|
||
daemon/rsync.c
|
||
daemon/scrub.c
|
||
daemon/selinux-relabel.c
|
||
@@ -293,7 +294,6 @@ lib/command.c
|
||
lib/conn-socket.c
|
||
lib/copy-in-out.c
|
||
lib/create.c
|
||
-lib/dbdump.c
|
||
lib/drives.c
|
||
lib/errors.c
|
||
lib/event-string.c
|
||
diff --git a/docs/guestfs-building.pod b/docs/guestfs-building.pod
|
||
index 28c761ce4..4d75772d0 100644
|
||
--- a/docs/guestfs-building.pod
|
||
+++ b/docs/guestfs-building.pod
|
||
@@ -210,11 +210,6 @@ eg. F</etc/libguestfs-tools.conf>.
|
||
Optional. Used by the L<libvirt backend|guestfs(3)/BACKEND> to
|
||
securely confine the appliance (sVirt).
|
||
|
||
-=item Berkeley DB utils (db_dump, db_load, etc)
|
||
-
|
||
-Optional. Usually found in a package called C<db-utils>,
|
||
-C<db4-utils>, C<db4.X-utils> etc.
|
||
-
|
||
=item systemtap
|
||
|
||
Optional. For userspace probes.
|
||
@@ -252,6 +247,10 @@ Optional. Render icons from guests.
|
||
|
||
Optional. Render icons from Windows guests.
|
||
|
||
+=item librpm
|
||
+
|
||
+Optional. To parse the list of applications from RPM-based guests.
|
||
+
|
||
=item Perl C<Expect>
|
||
|
||
Optional. Perl module used to test L<virt-rescue(1)>.
|
||
diff --git a/generator/actions_inspection.ml b/generator/actions_inspection.ml
|
||
index e2098cb00..690afd460 100644
|
||
--- a/generator/actions_inspection.ml
|
||
+++ b/generator/actions_inspection.ml
|
||
@@ -607,6 +607,16 @@ Please read L<guestfs(3)/INSPECTION> for more details.
|
||
See also C<guestfs_inspect_get_mountpoints>,
|
||
C<guestfs_inspect_get_filesystems>." };
|
||
|
||
+ { defaults with
|
||
+ name = "internal_list_rpm_applications"; added = (1, 45, 3);
|
||
+ style = RStructList ("applications2", "application2"), [], [];
|
||
+ visibility = VInternal;
|
||
+ impl = OCaml "Rpm.internal_list_rpm_applications";
|
||
+ shortdesc = "get applications from RPM guest";
|
||
+ longdesc = "\
|
||
+This internal function is used by C<guestfs_inspect_list_applications2>
|
||
+to list the applications for RPM guests."};
|
||
+
|
||
]
|
||
|
||
let non_daemon_functions = [
|
||
diff --git a/generator/proc_nr.ml b/generator/proc_nr.ml
|
||
index 57976be36..6b6cb7353 100644
|
||
--- a/generator/proc_nr.ml
|
||
+++ b/generator/proc_nr.ml
|
||
@@ -514,6 +514,7 @@ let proc_nr = [
|
||
507, "luks_uuid";
|
||
508, "cryptsetup_open";
|
||
509, "cryptsetup_close";
|
||
+510, "internal_list_rpm_applications";
|
||
]
|
||
|
||
(* End of list. If adding a new entry, add it at the end of the list
|
||
diff --git a/lib/MAX_PROC_NR b/lib/MAX_PROC_NR
|
||
index 77afe238f..2bc4cd64b 100644
|
||
--- a/lib/MAX_PROC_NR
|
||
+++ b/lib/MAX_PROC_NR
|
||
@@ -1 +1 @@
|
||
-509
|
||
+510
|
||
diff --git a/lib/Makefile.am b/lib/Makefile.am
|
||
index f9cc53df1..7f36ae515 100644
|
||
--- a/lib/Makefile.am
|
||
+++ b/lib/Makefile.am
|
||
@@ -81,7 +81,6 @@ libguestfs_la_SOURCES = \
|
||
conn-socket.c \
|
||
copy-in-out.c \
|
||
create.c \
|
||
- dbdump.c \
|
||
drives.c \
|
||
errors.c \
|
||
event-string.c \
|
||
diff --git a/lib/dbdump.c b/lib/dbdump.c
|
||
deleted file mode 100644
|
||
index 7c17ce6b3..000000000
|
||
--- a/lib/dbdump.c
|
||
+++ /dev/null
|
||
@@ -1,229 +0,0 @@
|
||
-/* libguestfs
|
||
- * Copyright (C) 2010-2012 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 <string.h>
|
||
-#include <sys/wait.h>
|
||
-#include <libintl.h>
|
||
-
|
||
-#ifdef HAVE_ENDIAN_H
|
||
-#include <endian.h>
|
||
-#endif
|
||
-
|
||
-#include "guestfs.h"
|
||
-#include "guestfs-internal.h"
|
||
-
|
||
-#if defined(DB_DUMP)
|
||
-
|
||
-static void read_db_dump_line (guestfs_h *g, void *datav, const char *line, size_t len);
|
||
-static unsigned char *convert_hex_to_binary (guestfs_h *g, const char *hex, size_t hexlen, size_t *binlen_rtn);
|
||
-
|
||
-struct cb_data {
|
||
- guestfs_int_db_dump_callback callback;
|
||
- void *opaque;
|
||
- enum { reading_header,
|
||
- reading_key, reading_value,
|
||
- reading_finished,
|
||
- reading_failed } state;
|
||
- unsigned char *key;
|
||
- size_t keylen;
|
||
-};
|
||
-
|
||
-/* 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.
|
||
- */
|
||
-int
|
||
-guestfs_int_read_db_dump (guestfs_h *g,
|
||
- const char *dumpfile, void *opaque,
|
||
- guestfs_int_db_dump_callback callback)
|
||
-{
|
||
- struct cb_data data;
|
||
- CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g);
|
||
- int r;
|
||
-
|
||
- data.callback = callback;
|
||
- data.opaque = opaque;
|
||
- data.state = reading_header;
|
||
- data.key = NULL;
|
||
-
|
||
- guestfs_int_cmd_add_arg (cmd, DB_DUMP);
|
||
- guestfs_int_cmd_add_arg (cmd, "-k");
|
||
- guestfs_int_cmd_add_arg (cmd, dumpfile);
|
||
- guestfs_int_cmd_set_stdout_callback (cmd, read_db_dump_line, &data, 0);
|
||
-
|
||
- r = guestfs_int_cmd_run (cmd);
|
||
- free (data.key);
|
||
-
|
||
- if (r == -1)
|
||
- return -1;
|
||
- if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
||
- guestfs_int_external_command_failed (g, r, DB_DUMP, NULL);
|
||
- return -1;
|
||
- }
|
||
- if (data.state != reading_finished) {
|
||
- error (g, _("%s: unexpected error or end of output"), DB_DUMP);
|
||
- return -1;
|
||
- }
|
||
-
|
||
- return 0;
|
||
-}
|
||
-
|
||
-static void
|
||
-read_db_dump_line (guestfs_h *g, void *datav, const char *line, size_t len)
|
||
-{
|
||
- struct cb_data *data = datav;
|
||
-
|
||
- switch (data->state) {
|
||
- case reading_finished:
|
||
- case reading_failed:
|
||
- return;
|
||
-
|
||
- case reading_header:
|
||
- /* Ignore everything to end-of-header marker. */
|
||
- if (STRPREFIX (line, "HEADER=END"))
|
||
- data->state = reading_key;
|
||
- return;
|
||
-
|
||
- /* Read the key, value pairs using a state machine. 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.
|
||
- */
|
||
- case reading_key:
|
||
- if (STRPREFIX (line, "DATA=END")) {
|
||
- data->state = reading_finished;
|
||
- return;
|
||
- }
|
||
-
|
||
- if (len < 1 || line[0] != ' ') {
|
||
- debug (g, _("unexpected line from db_dump command, no space prefix"));
|
||
- data->state = reading_failed;
|
||
- return;
|
||
- }
|
||
-
|
||
- data->key = convert_hex_to_binary (g, &line[1], len-1, &data->keylen);
|
||
- if (data->key == NULL) {
|
||
- data->state = reading_failed;
|
||
- return;
|
||
- }
|
||
-
|
||
- data->state = reading_value;
|
||
- return;
|
||
-
|
||
- case reading_value: {
|
||
- CLEANUP_FREE unsigned char *value = NULL;
|
||
- size_t valuelen;
|
||
-
|
||
- if (len < 1 || line[0] != ' ') {
|
||
- debug (g, _("unexpected line from db_dump command, no space prefix"));
|
||
- data->state = reading_failed;
|
||
- return;
|
||
- }
|
||
-
|
||
- value = convert_hex_to_binary (g, &line[1], len-1, &valuelen);
|
||
- if (value == NULL) {
|
||
- data->state = reading_failed;
|
||
- return;
|
||
- }
|
||
-
|
||
- if (data->callback (g, data->key, data->keylen,
|
||
- value, valuelen, data->opaque) == -1) {
|
||
- data->state = reading_failed;
|
||
- return;
|
||
- }
|
||
-
|
||
- free (data->key);
|
||
- data->key = NULL;
|
||
-
|
||
- data->state = reading_key;
|
||
- return;
|
||
- }
|
||
- }
|
||
-}
|
||
-
|
||
-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(DB_DUMP) */
|
||
diff --git a/lib/guestfs-internal.h b/lib/guestfs-internal.h
|
||
index d7ec7215d..4799ee0a1 100644
|
||
--- a/lib/guestfs-internal.h
|
||
+++ b/lib/guestfs-internal.h
|
||
@@ -719,10 +719,6 @@ extern int guestfs_int_set_backend (guestfs_h *g, const char *method);
|
||
/* inspect.c */
|
||
extern char *guestfs_int_download_to_tmp (guestfs_h *g, const char *filename, const char *extension, uint64_t max_size);
|
||
|
||
-/* dbdump.c */
|
||
-typedef int (*guestfs_int_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_int_read_db_dump (guestfs_h *g, const char *dumpfile, void *opaque, guestfs_int_db_dump_callback callback);
|
||
-
|
||
/* lpj.c */
|
||
extern int guestfs_int_get_lpj (guestfs_h *g);
|
||
|
||
diff --git a/lib/inspect-apps.c b/lib/inspect-apps.c
|
||
index dbc9d968c..da0003672 100644
|
||
--- a/lib/inspect-apps.c
|
||
+++ b/lib/inspect-apps.c
|
||
@@ -47,22 +47,12 @@
|
||
|
||
/* Some limits on what the inspection code will read, for safety. */
|
||
|
||
-/* Maximum RPM 'Packages' file we will download to /tmp. This file
|
||
- * 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 400 MB databases.
|
||
- */
|
||
-#define MAX_RPM_PACKAGES_SIZE (500 * 1000 * 1000)
|
||
-/* Maximum RPM 'Name' file we will download to /tmp. */
|
||
-#define MAX_RPM_NAME_SIZE (50 * 1000 * 1000)
|
||
/* Maximum dpkg 'status' file we will download to /tmp. */
|
||
#define MAX_DPKG_STATUS_SIZE (50 * 1000 * 1000)
|
||
/* Maximum APK 'installed' file we will download to /tmp. */
|
||
#define MAX_APK_INSTALLED_SIZE (50 * 1000 * 1000)
|
||
|
||
-#ifdef DB_DUMP
|
||
static struct guestfs_application2_list *list_applications_rpm (guestfs_h *g, const char *root);
|
||
-#endif
|
||
static struct guestfs_application2_list *list_applications_deb (guestfs_h *g, const char *root);
|
||
static struct guestfs_application2_list *list_applications_pacman (guestfs_h *g, const char *root);
|
||
static struct guestfs_application2_list *list_applications_apk (guestfs_h *g, const char *root);
|
||
@@ -136,11 +126,9 @@ guestfs_impl_inspect_list_applications2 (guestfs_h *g, const char *root)
|
||
|
||
if (STREQ (type, "linux") || STREQ (type, "hurd")) {
|
||
if (STREQ (package_format, "rpm")) {
|
||
-#ifdef DB_DUMP
|
||
ret = list_applications_rpm (g, root);
|
||
if (ret == NULL)
|
||
return NULL;
|
||
-#endif
|
||
}
|
||
else if (STREQ (package_format, "deb")) {
|
||
ret = list_applications_deb (g, root);
|
||
@@ -178,254 +166,15 @@ guestfs_impl_inspect_list_applications2 (guestfs_h *g, const char *root)
|
||
return ret;
|
||
}
|
||
|
||
-#ifdef DB_DUMP
|
||
-
|
||
-/* This data comes from the Name database, and contains the application
|
||
- * names and the first 4 bytes of each 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 *listv)
|
||
-{
|
||
- struct rpm_names_list *list = listv;
|
||
- const unsigned char *link_p;
|
||
- char *name;
|
||
-
|
||
- /* Ignore bogus entries. */
|
||
- if (keylen == 0 || valuelen < 4)
|
||
- return 0;
|
||
-
|
||
- /* A name entry will have as many links as installed instances of
|
||
- * that package. For example, if glibc.i686 and glibc.x86_64 are
|
||
- * both installed, then there will be a link for each Packages
|
||
- * entry. Add an entry onto list for all installed instances.
|
||
- */
|
||
- for (link_p = value; link_p < value + valuelen; link_p += 8) {
|
||
- name = safe_strndup (g, (const char *) key, keylen);
|
||
-
|
||
- 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, link_p, 4);
|
||
- list->len++;
|
||
- }
|
||
-
|
||
- return 0;
|
||
-}
|
||
-
|
||
-#pragma GCC diagnostic push
|
||
-#pragma GCC diagnostic ignored "-Wcast-align"
|
||
-
|
||
-/* tag constants, see rpmtag.h in RPM for complete list */
|
||
-#define RPMTAG_VERSION 1001
|
||
-#define RPMTAG_RELEASE 1002
|
||
-#define RPMTAG_EPOCH 1003
|
||
-#define RPMTAG_ARCH 1022
|
||
-#define RPMTAG_URL 1020
|
||
-#define RPMTAG_SUMMARY 1004
|
||
-#define RPMTAG_DESCRIPTION 1005
|
||
-
|
||
-static char *
|
||
-get_rpm_header_tag (guestfs_h *g, const unsigned char *header_start,
|
||
- size_t header_len, uint32_t tag, char type)
|
||
-{
|
||
- uint32_t num_fields, offset;
|
||
- const unsigned char *cursor = header_start + 8, *store, *header_end;
|
||
- size_t max_len;
|
||
- char iv[4];
|
||
-
|
||
- /* This function parses the RPM header structure to pull out various
|
||
- * tag strings (version, release, arch, etc.). For more detail on the
|
||
- * header format, see:
|
||
- * http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html#S2-RPM-FILE-FORMAT-HEADER
|
||
- */
|
||
-
|
||
- /* The minimum header size that makes sense here is 24 bytes. Four
|
||
- * bytes for number of fields, followed by four bytes denoting the
|
||
- * size of the store, then 16 bytes for the first index entry.
|
||
- */
|
||
- if (header_len < 24)
|
||
- return NULL;
|
||
-
|
||
- num_fields = be32toh (*(uint32_t *) header_start);
|
||
- store = header_start + 8 + (16 * num_fields);
|
||
-
|
||
- /* The first byte *after* the buffer. If you are here, you've gone
|
||
- * too far! */
|
||
- header_end = header_start + header_len;
|
||
-
|
||
- while (cursor < store && cursor <= header_end - 16) {
|
||
- if (be32toh (*(uint32_t *) cursor) == tag) {
|
||
- offset = be32toh(*(uint32_t *) (cursor + 8));
|
||
-
|
||
- if (store + offset >= header_end)
|
||
- return NULL;
|
||
- max_len = header_end - (store + offset);
|
||
-
|
||
- switch (type) {
|
||
- case 's':
|
||
- return safe_strndup (g, (const char *) (store + offset), max_len);
|
||
-
|
||
- case 'i':
|
||
- memset (iv, 0, sizeof iv);
|
||
- memcpy (iv, (void *) (store + offset),
|
||
- max_len > sizeof iv ? sizeof iv : max_len);
|
||
- return safe_memdup (g, iv, sizeof iv);
|
||
-
|
||
- default:
|
||
- abort ();
|
||
- }
|
||
- }
|
||
- cursor += 16;
|
||
- }
|
||
-
|
||
- return NULL;
|
||
-}
|
||
-
|
||
-struct read_package_data {
|
||
- struct rpm_names_list *list;
|
||
- struct guestfs_application2_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;
|
||
- CLEANUP_FREE char *version = NULL, *release = NULL,
|
||
- *epoch_str = NULL, *arch = NULL, *url = NULL, *summary = NULL,
|
||
- *description = NULL;
|
||
- int32_t epoch;
|
||
-
|
||
- /* 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 header data
|
||
- * 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.
|
||
- */
|
||
-
|
||
- version = get_rpm_header_tag (g, value, valuelen, RPMTAG_VERSION, 's');
|
||
- release = get_rpm_header_tag (g, value, valuelen, RPMTAG_RELEASE, 's');
|
||
- epoch_str = get_rpm_header_tag (g, value, valuelen, RPMTAG_EPOCH, 'i');
|
||
- arch = get_rpm_header_tag (g, value, valuelen, RPMTAG_ARCH, 's');
|
||
- url = get_rpm_header_tag (g, value, valuelen, RPMTAG_URL, 's');
|
||
- summary = get_rpm_header_tag (g, value, valuelen, RPMTAG_SUMMARY, 's');
|
||
- description = get_rpm_header_tag (g, value, valuelen, RPMTAG_DESCRIPTION, 's');
|
||
-
|
||
- /* The epoch is stored as big-endian integer. */
|
||
- if (epoch_str)
|
||
- epoch = be32toh (*(int32_t *) epoch_str);
|
||
- else
|
||
- epoch = 0;
|
||
-
|
||
- /* Add the application and what we know. */
|
||
- if (version && release)
|
||
- add_application (g, data->apps, entry->name, "", epoch, version, release,
|
||
- arch ? arch : "", "", "", url ? : "", "",
|
||
- summary ? : "", description ? : "");
|
||
-
|
||
- return 0;
|
||
-}
|
||
-
|
||
-#pragma GCC diagnostic pop
|
||
-
|
||
static struct guestfs_application2_list *
|
||
list_applications_rpm (guestfs_h *g, const char *root)
|
||
{
|
||
- CLEANUP_FREE char *Name = NULL, *Packages = NULL;
|
||
- struct rpm_names_list list = { .names = NULL, .len = 0 };
|
||
- struct guestfs_application2_list *apps = NULL;
|
||
- struct read_package_data data;
|
||
-
|
||
- Name = guestfs_int_download_to_tmp (g, "/var/lib/rpm/Name", NULL,
|
||
- MAX_RPM_NAME_SIZE);
|
||
- if (Name == NULL)
|
||
- goto error;
|
||
-
|
||
- Packages = guestfs_int_download_to_tmp (g, "/var/lib/rpm/Packages", NULL,
|
||
- MAX_RPM_PACKAGES_SIZE);
|
||
- if (Packages == NULL)
|
||
- goto error;
|
||
-
|
||
- /* Read Name database. */
|
||
- if (guestfs_int_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. */
|
||
- apps = safe_malloc (g, sizeof *apps);
|
||
- apps->len = 0;
|
||
- apps->val = NULL;
|
||
-
|
||
- /* Read Packages database. */
|
||
- data.list = &list;
|
||
- data.apps = apps;
|
||
- if (guestfs_int_read_db_dump (g, Packages, &data, read_package) == -1)
|
||
- goto error;
|
||
-
|
||
- free_rpm_names_list (&list);
|
||
-
|
||
- return apps;
|
||
-
|
||
- error:
|
||
- free_rpm_names_list (&list);
|
||
- guestfs_free_application2_list (apps);
|
||
-
|
||
- return NULL;
|
||
+ /* We don't need the ‘root’ parameter here. The caller is supposed
|
||
+ * to have mounted the guest up before calling the public API.
|
||
+ */
|
||
+ return guestfs_internal_list_rpm_applications (g);
|
||
}
|
||
|
||
-#endif /* defined DB_DUMP */
|
||
-
|
||
static struct guestfs_application2_list *
|
||
list_applications_deb (guestfs_h *g, const char *root)
|
||
{
|
||
diff --git a/m4/guestfs-daemon.m4 b/m4/guestfs-daemon.m4
|
||
index aa90268b4..1728249a5 100644
|
||
--- a/m4/guestfs-daemon.m4
|
||
+++ b/m4/guestfs-daemon.m4
|
||
@@ -102,6 +102,14 @@ PKG_CHECK_MODULES([HIVEX], [hivex],[
|
||
[AC_MSG_FAILURE([hivex library is required])])
|
||
AM_CONDITIONAL([HAVE_HIVEX],[test "x$HIVEX_LIBS" != "x"])
|
||
|
||
+dnl librpm library (optional)
|
||
+PKG_CHECK_MODULES([LIBRPM], [rpm >= 4.6.0],[
|
||
+ AC_SUBST([LIBRPM_CFLAGS])
|
||
+ AC_SUBST([LIBRPM_LIBS])
|
||
+ AC_DEFINE([HAVE_LIBRPM],[1],[librpm library found at compile time.])
|
||
+],[AC_MSG_WARN([librpm library not found])]
|
||
+)
|
||
+
|
||
dnl systemd journal library (optional)
|
||
PKG_CHECK_MODULES([SD_JOURNAL], [libsystemd],[
|
||
AC_SUBST([SD_JOURNAL_CFLAGS])
|
||
diff --git a/m4/guestfs-progs.m4 b/m4/guestfs-progs.m4
|
||
index bf1f83c9d..f90bda04e 100644
|
||
--- a/m4/guestfs-progs.m4
|
||
+++ b/m4/guestfs-progs.m4
|
||
@@ -63,16 +63,6 @@ AC_CHECK_PROG([PO4A_GETTEXTIZE],[po4a-gettextize],[po4a-gettextize],[no])
|
||
AC_CHECK_PROG([PO4A_TRANSLATE],[po4a-translate],[po4a-translate],[no])
|
||
AM_CONDITIONAL([HAVE_PO4A], [test "x$PO4A_GETTEXTIZE" != "xno" && test "x$PO4A_TRANSLATE" != "xno"])
|
||
|
||
-dnl Check for db_dump, db_load (optional).
|
||
-GUESTFS_FIND_DB_TOOL([DB_DUMP], [dump])
|
||
-GUESTFS_FIND_DB_TOOL([DB_LOAD], [load])
|
||
-if test "x$DB_DUMP" != "xno"; then
|
||
- AC_DEFINE_UNQUOTED([DB_DUMP],["$DB_DUMP"],[Name of db_dump program.])
|
||
-fi
|
||
-if test "x$DB_LOAD" != "xno"; then
|
||
- AC_DEFINE_UNQUOTED([DB_LOAD],["$DB_LOAD"],[Name of db_load program.])
|
||
-fi
|
||
-
|
||
dnl Check for netpbm programs (optional).
|
||
AC_PATH_PROGS([PBMTEXT],[pbmtext],[no])
|
||
AC_PATH_PROGS([PNMTOPNG],[pnmtopng],[no])
|
||
--
|
||
2.18.4
|
||
|