Look for the rpm library, and use it to query for the information
needed, such as:
- the list of installed packages
- the list of requires for a specified package
- the providers of a specified capability
- the list of files of a package
Also, rework the dependency resolution, using a queue to iterate on the
packages not resolved yet (thus resolving each package just once), and
caching the provider of each capability.
---
configure.ac | 4 +
src/Makefile.am | 7 +-
src/librpm-c.c | 463 ++++++++++++++++++++++++++++++++++++++++++++++++
src/librpm.ml | 51 ++++++
src/librpm.mli | 48 +++++
src/rpm.ml | 219 +++++++++++------------
src/supermin-link.sh.in | 2 +-
7 files changed, 677 insertions(+), 117 deletions(-)
create mode 100644 src/librpm-c.c
create mode 100644 src/librpm.ml
create mode 100644 src/librpm.mli
diff --git a/configure.ac b/configure.ac
index 65dab78..e604ea2 100644
--- a/configure.ac
+++ b/configure.ac
@@ -95,6 +95,10 @@ dnl For yum-rpm handler.
AC_PATH_PROG(RPM,[rpm],[no])
AC_PATH_PROG(RPM2CPIO,[rpm2cpio],[no])
AC_PATH_PROG(YUMDOWNLOADER,[yumdownloader],[no])
+PKG_CHECK_MODULES([LIBRPM], [rpm], [librpm=yes], [:])
+if test "x$librpm" = "xyes"; then
+ AC_DEFINE([HAVE_LIBRPM], [1], [Define if you have librpm])
+fi
dnl For Zypper handler.
AC_PATH_PROG(ZYPPER,[zypper],[no])
diff --git a/src/Makefile.am b/src/Makefile.am
index 90aa773..6261c86 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -42,6 +42,9 @@ SOURCES = \
realpath-c.c \
realpath.ml \
realpath.mli \
+ librpm-c.c \
+ librpm.ml \
+ librpm.mli \
config.ml \
utils.ml \
utils.mli \
@@ -66,6 +69,7 @@ SOURCES_ML = \
fnmatch.ml \
glob.ml \
realpath.ml \
+ librpm.ml \
config.ml \
utils.ml \
types.ml \
@@ -86,6 +90,7 @@ SOURCES_C = \
ext2init-c.c \
fnmatch-c.c \
glob-c.c \
+ librpm-c.c \
realpath-c.c
CLEANFILES = *~ *.cmi *.cmo *.cmx *.o supermin
@@ -98,7 +103,7 @@ bin_PROGRAMS = supermin
supermin_SOURCES = $(SOURCES_C)
supermin_CFLAGS = \
-I$(shell $(OCAMLC) -where) \
- $(EXT2FS_CFLAGS) $(COM_ERR_CFLAGS) \
+ $(EXT2FS_CFLAGS) $(COM_ERR_CFLAGS) $(LIBRPM_CFLAGS) \
-Wall $(WERROR_CFLAGS) \
-I$(top_srcdir)/lib -I../lib
diff --git a/src/librpm-c.c b/src/librpm-c.c
new file mode 100644
index 0000000..c3ec3cb
--- /dev/null
+++ b/src/librpm-c.c
@@ -0,0 +1,463 @@
+/* supermin 5
+ * Copyright (C) 2014 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 <stdlib.h>
+#include <string.h>
+#include <glob.h>
+#include <assert.h>
+
+#include <caml/alloc.h>
+#include <caml/callback.h>
+#include <caml/custom.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+#include <caml/unixsupport.h>
+
+#ifdef HAVE_LIBRPM
+
+#include <rpm/header.h>
+#include <rpm/rpmdb.h>
+#include <rpm/rpmlib.h>
+#include <rpm/rpmlog.h>
+#include <rpm/rpmts.h>
+
+static rpmlogCallback old_log_callback;
+
+static int
+supermin_rpm_log_callback (rpmlogRec rec, rpmlogCallbackData data)
+{
+ fprintf (stderr, "supermin: rpm: lib: %s%s",
+ rpmlogLevelPrefix (rpmlogRecPriority (rec)),
+ rpmlogRecMessage (rec));
+ return 0;
+}
+
+struct librpm_data
+{
+ rpmts ts;
+ int debug;
+};
+
+static void librpm_handle_closed (void) __attribute__((noreturn));
+
+static void
+librpm_handle_closed (void)
+{
+ caml_failwith ("librpm: function called on a closed handle");
+}
+
+static void
+librpm_raise_multiple_matches (int occurrences)
+{
+ caml_raise_with_arg (*caml_named_value ("librpm_multiple_matches"),
+ Val_int (occurrences));
+}
+
+#define Librpm_val(v) (*((struct librpm_data *)Data_custom_val(v)))
+#define Val_none Val_int(0)
+#define Some_val(v) Field(v,0)
+
+static void
+librpm_finalize (value rpmv)
+{
+ struct librpm_data data = Librpm_val (rpmv);
+
+ if (data.ts) {
+ rpmtsFree (data.ts);
+
+ rpmlogSetCallback (old_log_callback, NULL);
+ }
+}
+
+static struct custom_operations librpm_custom_operations = {
+ (char *) "librpm_custom_operations",
+ librpm_finalize,
+ custom_compare_default,
+ custom_hash_default,
+ custom_serialize_default,
+ custom_deserialize_default
+};
+
+static value
+Val_librpm (struct librpm_data *data)
+{
+ CAMLparam0 ();
+ CAMLlocal1 (rpmv);
+
+ rpmv = caml_alloc_custom (&librpm_custom_operations,
+ sizeof (struct librpm_data), 0, 1);
+ Librpm_val (rpmv) = *data;
+ CAMLreturn (rpmv);
+}
+
+value
+supermin_rpm_is_available (value unit)
+{
+ return Val_true;
+}
+
+value
+supermin_rpm_version (value unit)
+{
+ return caml_copy_string (RPMVERSION);
+}
+
+value
+supermin_rpm_open (value debugv)
+{
+ CAMLparam1 (debugv);
+ CAMLlocal1 (rpmv);
+ struct librpm_data data;
+ int res;
+ rpmlogLvl lvl;
+
+ data.debug = debugv == Val_none ? 0 : Int_val (Some_val (debugv));
+
+ switch (data.debug) {
+ case 3:
+ lvl = RPMLOG_INFO;
+ break;
+ case 2:
+ lvl = RPMLOG_NOTICE;
+ break;
+ case 1:
+ lvl = RPMLOG_WARNING;
+ break;
+ case 0:
+ default:
+ lvl = RPMLOG_ERR;
+ break;
+ }
+
+ rpmSetVerbosity (lvl);
+ old_log_callback = rpmlogSetCallback (supermin_rpm_log_callback, NULL);
+
+ res = rpmReadConfigFiles (NULL, NULL);
+ if (res == -1)
+ caml_failwith ("rpm_open: rpmReadConfigFiles failed");
+
+ data.ts = rpmtsCreate ();
+ if (data.ts == NULL)
+ caml_failwith ("rpm_open: rpmtsCreate failed");
+
+ rpmv = Val_librpm (&data);
+ CAMLreturn (rpmv);
+}
+
+value
+supermin_rpm_close (value rpmv)
+{
+ CAMLparam1 (rpmv);
+
+ librpm_finalize (rpmv);
+
+ /* So we don't double-free in the finalizer. */
+ Librpm_val (rpmv).ts = NULL;
+
+ CAMLreturn (Val_unit);
+}
+
+value
+supermin_rpm_installed (value rpmv, value pkgv)
+{
+ CAMLparam2 (rpmv, pkgv);
+ CAMLlocal2 (rv, v);
+ struct librpm_data data;
+ rpmdbMatchIterator iter;
+ int count, i;
+ Header h;
+
+ data = Librpm_val (rpmv);
+ if (data.ts == NULL)
+ librpm_handle_closed ();
+
+ iter = rpmtsInitIterator (data.ts, RPMTAG_NAME, String_val (pkgv), 0);
+ if (iter == NULL)
+ caml_raise_not_found ();
+
+ count = rpmdbGetIteratorCount (iter);
+ if (data.debug >= 2)
+ printf ("supermin: rpm: installed: %d occurrences for '%s'\n",
count, String_val (pkgv));
+
+ rv = caml_alloc (count, 0);
+ i = 0;
+
+ while ((h = rpmdbNextIterator (iter)) != NULL) {
+ HeaderIterator hi;
+ rpmtd td;
+ uint32_t *val;
+
+ v = caml_alloc (5, 0);
+ hi = headerInitIterator (h);
+ td = rpmtdNew ();
+ while (headerNext (hi, td) == 1) {
+ switch (rpmtdTag (td)) {
+ case RPMTAG_NAME:
+ Store_field (v, 0, caml_copy_string (rpmtdGetString (td)));
+ break;
+ case RPMTAG_EPOCH:
+ val = rpmtdGetUint32 (td);
+ Store_field (v, 1, Val_int ((int) *val));
+ break;
+ case RPMTAG_VERSION:
+ Store_field (v, 2, caml_copy_string (rpmtdGetString (td)));
+ break;
+ case RPMTAG_RELEASE:
+ Store_field (v, 3, caml_copy_string (rpmtdGetString (td)));
+ break;
+ case RPMTAG_ARCH:
+ Store_field (v, 4, caml_copy_string (rpmtdGetString (td)));
+ break;
+ }
+ rpmtdFreeData (td);
+ }
+ Store_field (rv, i, v);
+
+ rpmtdFree (td);
+ headerFreeIterator (hi);
+ ++i;
+ }
+
+ rpmdbFreeIterator (iter);
+
+ CAMLreturn (rv);
+}
+
+value
+supermin_rpm_pkg_requires (value rpmv, value pkgv)
+{
+ CAMLparam2 (rpmv, pkgv);
+ CAMLlocal1 (rv);
+ struct librpm_data data;
+ rpmdbMatchIterator iter;
+ int count, i;
+ Header h;
+ rpmtd td;
+
+ data = Librpm_val (rpmv);
+ if (data.ts == NULL)
+ librpm_handle_closed ();
+
+ iter = rpmtsInitIterator (data.ts, RPMDBI_LABEL, String_val (pkgv), 0);
+ if (iter == NULL)
+ caml_raise_not_found ();
+
+ count = rpmdbGetIteratorCount (iter);
+ if (data.debug >= 2)
+ printf ("supermin: rpm: pkg_requires: %d occurrences for '%s'\n",
count, String_val (pkgv));
+ if (count != 1)
+ librpm_raise_multiple_matches (count);
+
+ h = rpmdbNextIterator (iter);
+ assert (h != NULL);
+
+ td = rpmtdNew ();
+ i = headerGet (h, RPMTAG_REQUIRENAME, td, HEADERGET_MINMEM);
+ if (i != 1)
+ caml_failwith ("rpm_pkg_requires: headerGet failed");
+
+ rv = caml_alloc (rpmtdCount (td), 0);
+ for (i = 0; i < rpmtdCount (td); ++i)
+ Store_field (rv, i, caml_copy_string (rpmtdNextString (td)));
+
+ rpmtdFreeData (td);
+ rpmtdFree (td);
+
+ rpmdbFreeIterator (iter);
+
+ CAMLreturn (rv);
+}
+
+static rpmdbMatchIterator
+createProvidesIterator (rpmts ts, const char *what)
+{
+ rpmdbMatchIterator mi = NULL;
+
+ if (what[0] != '/') {
+ mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, what, 0);
+ if (mi != NULL)
+ return mi;
+ }
+ mi = rpmtsInitIterator(ts, RPMDBI_INSTFILENAMES, what, 0);
+ if (mi != NULL)
+ return mi;
+
+ mi = rpmtsInitIterator(ts, RPMDBI_PROVIDENAME, what, 0);
+
+ return mi;
+}
+
+value
+supermin_rpm_pkg_whatprovides (value rpmv, value pkgv)
+{
+ CAMLparam2 (rpmv, pkgv);
+ CAMLlocal1 (rv);
+ struct librpm_data data;
+ rpmdbMatchIterator iter;
+ int count, i;
+ Header h;
+
+ data = Librpm_val (rpmv);
+ if (data.ts == NULL)
+ librpm_handle_closed ();
+
+ iter = createProvidesIterator (data.ts, String_val (pkgv));
+ if (iter == NULL)
+ caml_raise_not_found ();
+
+ count = rpmdbGetIteratorCount (iter);
+ if (data.debug >= 2)
+ printf ("supermin: rpm: pkg_whatprovides: %d occurrences for
'%s'\n", count, String_val (pkgv));
+
+ rv = caml_alloc (count, 0);
+ i = 0;
+
+ while ((h = rpmdbNextIterator (iter)) != NULL) {
+ rpmtd td;
+ int ret;
+
+ td = rpmtdNew ();
+ ret = headerGet (h, RPMTAG_NAME, td, HEADERGET_MINMEM);
+ if (ret != 1)
+ caml_failwith ("rpm_pkg_whatprovides: headerGet failed");
+
+ Store_field (rv, i, caml_copy_string (rpmtdGetString (td)));
+
+ rpmtdFreeData (td);
+ rpmtdFree (td);
+ ++i;
+ }
+
+ rpmdbFreeIterator (iter);
+
+ CAMLreturn (rv);
+}
+
+value
+supermin_rpm_pkg_filelist (value rpmv, value pkgv)
+{
+ CAMLparam2 (rpmv, pkgv);
+ CAMLlocal2 (rv, v);
+ struct librpm_data data;
+ rpmdbMatchIterator iter;
+ int count, i;
+ Header h;
+ rpmfi fi;
+ const rpmfiFlags fiflags = RPMFI_NOHEADER | RPMFI_FLAGS_QUERY | RPMFI_NOFILEDIGESTS;
+
+ data = Librpm_val (rpmv);
+ if (data.ts == NULL)
+ librpm_handle_closed ();
+
+ iter = rpmtsInitIterator (data.ts, RPMDBI_LABEL, String_val (pkgv), 0);
+ if (iter == NULL)
+ caml_raise_not_found ();
+
+ count = rpmdbGetIteratorCount (iter);
+ if (data.debug >= 2)
+ printf ("supermin: rpm: pkg_filelist: %d occurrences for '%s'\n",
count, String_val (pkgv));
+ if (count != 1)
+ librpm_raise_multiple_matches (count);
+
+ h = rpmdbNextIterator (iter);
+ assert (h != NULL);
+
+ fi = rpmfiNew (data.ts, h, RPMTAG_BASENAMES, fiflags);
+
+ count = rpmfiFC (fi);
+ if (count < 0)
+ count = 0;
+
+ rv = caml_alloc (count, 0);
+ i = 0;
+
+ fi = rpmfiInit (fi, 0);
+ while (rpmfiNext (fi) >= 0) {
+ const char *fn;
+
+ v = caml_alloc (2, 0);
+ fn = rpmfiFN(fi);
+ Store_field (v, 0, caml_copy_string (fn));
+ if (rpmfiFFlags (fi) & RPMFILE_CONFIG)
+ Store_field (v, 1, Val_long (1)); /* FileConfig */
+ else
+ Store_field (v, 1, Val_long (0)); /* FileNormal */
+ Store_field (rv, i, v);
+ ++i;
+ }
+ rpmfiFree(fi);
+
+ rpmdbFreeIterator (iter);
+
+ CAMLreturn (rv);
+}
+
+#else
+
+value
+supermin_rpm_is_available (value unit)
+{
+ return Val_false;
+}
+
+value
+supermin_rpm_version (value unit)
+{
+ abort ();
+}
+
+value
+supermin_rpm_open (value debugv)
+{
+ abort ();
+}
+
+value
+supermin_rpm_close (value rpmv)
+{
+ abort ();
+}
+
+value
+supermin_rpm_installed (value rpmv, value pkgv)
+{
+ abort ();
+}
+
+value
+supermin_rpm_pkg_required (value rpmv, value pkgv)
+{
+ abort ();
+}
+
+value
+supermin_rpm_pkg_whatprovides (value rpmv, value pkgv)
+{
+ abort ();
+}
+
+value
+supermin_rpm_pkg_filelist (value rpmv, value pkgv)
+{
+ abort ();
+}
+
+#endif
diff --git a/src/librpm.ml b/src/librpm.ml
new file mode 100644
index 0000000..aa8d367
--- /dev/null
+++ b/src/librpm.ml
@@ -0,0 +1,51 @@
+(* supermin 5
+ * Copyright (C) 2014 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
+ *)
+
+external rpm_is_available : unit -> bool = "supermin_rpm_is_available"
+
+external rpm_version : unit -> string = "supermin_rpm_version"
+
+type t
+
+exception Multiple_matches of int
+
+external rpm_open : ?debug:int -> t = "supermin_rpm_open"
+external rpm_close : t -> unit = "supermin_rpm_close"
+
+type rpm_t = {
+ name : string;
+ epoch : int;
+ version : string;
+ release : string;
+ arch : string;
+}
+
+type rpmfile_t = {
+ filepath : string;
+ filetype : rpmfiletype_t;
+} and rpmfiletype_t =
+ | FileNormal
+ | FileConfig
+
+external rpm_installed : t -> string -> rpm_t array =
"supermin_rpm_installed"
+external rpm_pkg_requires : t -> string -> string array =
"supermin_rpm_pkg_requires"
+external rpm_pkg_whatprovides : t -> string -> string array =
"supermin_rpm_pkg_whatprovides"
+external rpm_pkg_filelist : t -> string -> rpmfile_t array =
"supermin_rpm_pkg_filelist"
+
+let () =
+ Callback.register_exception "librpm_multiple_matches" (Multiple_matches 0)
diff --git a/src/librpm.mli b/src/librpm.mli
new file mode 100644
index 0000000..880a038
--- /dev/null
+++ b/src/librpm.mli
@@ -0,0 +1,48 @@
+(* supermin 5
+ * Copyright (C) 2014 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
+ *)
+
+val rpm_is_available : unit -> bool
+
+val rpm_version : unit -> string
+
+type t
+
+exception Multiple_matches of int
+
+val rpm_open : ?debug:int -> t
+val rpm_close : t -> unit
+
+type rpm_t = {
+ name : string;
+ epoch : int;
+ version : string;
+ release : string;
+ arch : string;
+}
+
+type rpmfile_t = {
+ filepath : string;
+ filetype : rpmfiletype_t;
+} and rpmfiletype_t =
+ | FileNormal
+ | FileConfig
+
+val rpm_installed : t -> string -> rpm_t array
+val rpm_pkg_requires : t -> string -> string array
+val rpm_pkg_whatprovides : t -> string -> string array
+val rpm_pkg_filelist : t -> string -> rpmfile_t array
diff --git a/src/rpm.ml b/src/rpm.ml
index e0cdb39..3bac45f 100644
--- a/src/rpm.ml
+++ b/src/rpm.ml
@@ -21,9 +21,15 @@ open Printf
open Utils
open Package_handler
+open Librpm
+
+module StringSet = Set.Make (String)
+
+let stringset_of_list pkgs =
+ List.fold_left (fun set elem -> StringSet.add elem set) StringSet.empty pkgs
let fedora_detect () =
- Config.rpm <> "no" && Config.rpm2cpio <> "no"
&&
+ Config.rpm <> "no" && Config.rpm2cpio <> "no"
&& (rpm_is_available ()) &&
Config.yumdownloader <> "no" &&
try
(stat "/etc/redhat-release").st_kind = S_REG ||
@@ -31,12 +37,12 @@ let fedora_detect () =
with Unix_error _ -> false
let opensuse_detect () =
- Config.rpm <> "no" && Config.rpm2cpio <> "no"
&&
+ Config.rpm <> "no" && Config.rpm2cpio <> "no"
&& (rpm_is_available ()) &&
Config.zypper <> "no" &&
try (stat "/etc/SuSE-release").st_kind = S_REG with Unix_error _ ->
false
let mageia_detect () =
- Config.rpm <> "no" && Config.rpm2cpio <> "no"
&&
+ Config.rpm <> "no" && Config.rpm2cpio <> "no"
&& (rpm_is_available ()) &&
Config.urpmi <> "no" &&
Config.fakeroot <> "no" &&
try (stat "/etc/mageia-release").st_kind = S_REG with Unix_error _ ->
false
@@ -44,6 +50,14 @@ let mageia_detect () =
let settings = ref no_settings
let rpm_major, rpm_minor = ref 0, ref 0
let zypper_major, zypper_minor, zypper_patch = ref 0, ref 0, ref 0
+let t = ref None
+
+let get_rpm () =
+ match !t with
+ | None ->
+ eprintf "supermin: rpm: get_rpm called too early";
+ exit 1
+ | Some t -> t
let rec rpm_init s =
settings := s;
@@ -51,31 +65,26 @@ let rec rpm_init s =
(* Get RPM version. We have to adjust some RPM commands based on
* the version.
*)
- let cmd = sprintf "%s --version | awk '{print $3}'" Config.rpm in
- let lines = run_command_get_lines cmd in
+ let version = rpm_version () in
let major, minor =
- match lines with
+ match string_split "." version with
| [] ->
- eprintf "supermin: rpm --version command had no output\n";
+ eprintf "supermin: unable to parse empty rpm version string\n";
exit 1
- | line :: _ ->
- let line = string_split "." line in
- match line with
- | [] ->
- eprintf "supermin: unable to parse empty output of rpm --version\n";
- exit 1
- | [x] ->
- eprintf "supermin: unable to parse output of rpm --version: %s\n" x;
- exit 1
- | major :: minor :: _ ->
- try int_of_string major, int_of_string minor
- with Failure "int_of_string" ->
- eprintf "supermin: unable to parse output of rpm --version:
non-numeric\n";
- exit 1 in
+ | [x] ->
+ eprintf "supermin: unable to parse rpm version string: %s\n" x;
+ exit 1
+ | major :: minor :: _ ->
+ try int_of_string major, int_of_string minor
+ with Failure "int_of_string" ->
+ eprintf "supermin: unable to parse rpm version string: non-numeric,
%s\n" version;
+ exit 1 in
rpm_major := major;
rpm_minor := minor;
if !settings.debug >= 1 then
- printf "supermin: rpm: detected RPM version %d.%d\n" major minor
+ printf "supermin: rpm: detected RPM version %d.%d\n" major minor;
+
+ t := Some (rpm_open ~debug:!settings.debug)
and opensuse_init s =
rpm_init s;
@@ -115,13 +124,10 @@ and opensuse_init s =
if !settings.debug >= 1 then
printf "supermin: rpm: detected zypper version %d.%d.%d\n" major minor
patch
-type rpm_t = {
- name : string;
- epoch : int32;
- version : string;
- release : string;
- arch : string;
-}
+let rpm_fini () =
+ match !t with
+ | None -> ()
+ | Some t -> rpm_close t
(* Memo from package type to internal rpm_t. *)
let rpm_of_pkg, pkg_of_rpm = get_memo_functions ()
@@ -130,43 +136,8 @@ let rpm_of_pkg, pkg_of_rpm = get_memo_functions ()
let rpmh = Hashtbl.create 13
let rpm_package_of_string str =
- (* Parse an RPM name into the fields like name and version. Since
- * the package is installed (see check below), it's easier to use RPM
- * itself to do this parsing rather than haphazardly parsing it
- * ourselves. *)
- let parse_rpm str =
- let cmd =
- sprintf "%s --nosignature --nodigest -q --qf '%%{name} %%{epoch}
%%{version} %%{release} %%{arch}\\n' %s"
- Config.rpm
- (quote str) in
- let lines = run_command_get_lines cmd in
- let lines = List.map (string_split " ") lines in
- let rpms = filter_map (
- function
- | [ name; ("0"|"(none)"); version; release; arch ] ->
- Some { name = name;
- epoch = 0_l;
- version = version; release = release; arch = arch }
- | [ name; epoch; version; release; arch ] ->
- Some { name = name;
- epoch = Int32.of_string epoch;
- version = version; release = release; arch = arch }
- | xs ->
- (* grrr, RPM doesn't send errors to stderr *)
- None
- ) lines in
-
- if rpms = [] then (
- eprintf "supermin: no output from rpm command could be parsed when searching
for '%s'\nThe command was:\n %s\n"
- str cmd;
- exit 1
- );
-
- (* RPM will return multiple hits when either multiple versions or
- * multiple arches are installed at the same time. We are only
- * interested in the highest version with the best
- * architecture.
- *)
+ let query rpm =
+ let rpms = Array.to_list (rpm_installed (get_rpm ()) str) in
let cmp { version = v1; arch = a1 } { version = v2; arch = a2 } =
let i = compare_version v2 v1 in
if i <> 0 then i
@@ -174,12 +145,6 @@ let rpm_package_of_string str =
in
let rpms = List.sort cmp rpms in
List.hd rpms
-
- (* Check if an RPM is installed. *)
- and check_rpm_installed name =
- let cmd = sprintf "%s --nosignature --nodigest -q %s >/dev/null"
- Config.rpm (quote name) in
- 0 = Sys.command cmd
in
try
@@ -187,11 +152,8 @@ let rpm_package_of_string str =
with
Not_found ->
let r =
- if check_rpm_installed str then (
- let rpm = parse_rpm str in
- Some (pkg_of_rpm rpm)
- )
- else None in
+ try Some (pkg_of_rpm (query str))
+ with Not_found -> None in
Hashtbl.add rpmh str r;
r
@@ -212,10 +174,10 @@ let rpm_package_to_string pkg =
!rpm_major < 4 || (!rpm_major = 4 && !rpm_minor < 11) in
let rpm = rpm_of_pkg pkg in
- if is_rpm_lt_4_11 || rpm.epoch = 0_l then
+ if is_rpm_lt_4_11 || rpm.epoch = 0 then
sprintf "%s-%s-%s.%s" rpm.name rpm.version rpm.release rpm.arch
else
- sprintf "%s-%ld:%s-%s.%s"
+ sprintf "%s-%d:%s-%s.%s"
rpm.name rpm.epoch rpm.version rpm.release rpm.arch
let rpm_package_name pkg =
@@ -225,47 +187,74 @@ let rpm_package_name pkg =
let rpm_get_package_database_mtime () =
(lstat "/var/lib/rpm/Packages").st_mtime
+(* Memo of resolved provides. *)
+let rpm_providers = Hashtbl.create 13
+
let rpm_get_all_requires pkgs =
- let get pkgs =
- let cmd = sprintf "\
- %s --nosignature --nodigest -qR %s |
- awk '{print $1}' |
- xargs rpm --nosignature --nodigest -q --qf '%%{name}\\n' --whatprovides
|
- grep -v 'no package provides' |
- sort -u"
- Config.rpm
- (quoted_list (List.map rpm_package_to_string
- (PackageSet.elements pkgs))) in
- let lines = run_command_get_lines cmd in
- let lines = filter_map rpm_package_of_string lines in
- PackageSet.union pkgs (package_set_of_list lines)
- in
- (* The command above only gets one level of dependencies. We need
- * to keep iterating until we reach a fixpoint.
- *)
- let rec loop pkgs =
- let pkgs' = get pkgs in
- if PackageSet.equal pkgs pkgs' then pkgs
- else loop pkgs'
+ let get pkg =
+ let reqs =
+ try
+ rpm_pkg_requires (get_rpm ()) pkg
+ with
+ Multiple_matches _ as ex ->
+ match rpm_package_of_string pkg with
+ | None -> raise ex
+ | Some pkg -> rpm_pkg_requires (get_rpm ()) (rpm_package_to_string pkg)
in
+ let pkgs' = Array.fold_left (
+ fun set x ->
+ try
+ let provides =
+ try Hashtbl.find rpm_providers x
+ with Not_found -> rpm_pkg_whatprovides (get_rpm ()) x in
+ let newset = Array.fold_left (
+ fun newset p ->
+ match rpm_package_of_string p with
+ | None -> newset
+ | Some x -> StringSet.add p newset
+ ) StringSet.empty provides in
+ StringSet.union set newset
+ with Not_found -> set
+ ) StringSet.empty reqs in
+ pkgs'
in
- loop pkgs
+ let queue = Queue.create () in
+ let final = ref (stringset_of_list
+ (List.map rpm_package_name
+ (PackageSet.elements pkgs))) in
+ StringSet.iter (fun x -> Queue.push x queue) !final;
+ let resolved = ref StringSet.empty in
+ while not (Queue.is_empty queue) do
+ let current = Queue.pop queue in
+ if not (StringSet.mem current !resolved) then (
+ try
+ let expanded = get current in
+ let diff = StringSet.diff expanded !final in
+ if not (StringSet.is_empty diff) then (
+ final := StringSet.union !final diff;
+ StringSet.iter (fun x -> Queue.push x queue) diff;
+ )
+ with Not_found -> ();
+ resolved := StringSet.add current !resolved
+ )
+ done;
+ let pkgs' = filter_map rpm_package_of_string (StringSet.elements !final) in
+ package_set_of_list pkgs'
let rpm_get_all_files pkgs =
- let cmd = sprintf "\
- %s --nosignature --nodigest -q --qf
'[%%{FILENAMES}\\t%%{FILEFLAGS:fflags}\\n]' %s |
- grep '^/' |
- sort -u"
- Config.rpm
- (quoted_list (List.map rpm_package_to_string (PackageSet.elements pkgs))) in
- let lines = run_command_get_lines cmd in
- let lines = List.map (string_split "\t") lines in
+ let files_compare { filepath = a } { filepath = b } =
+ compare a b in
+ let files = List.map rpm_package_to_string (PackageSet.elements pkgs) in
+ let files = List.fold_right (
+ fun pkg xs ->
+ let files = Array.to_list (rpm_pkg_filelist (get_rpm ()) pkg) in
+ files @ xs
+ ) files [] in
+ let files = sort_uniq ~cmp:files_compare files in
List.map (
- function
- | [ path; flags ] ->
- let config = String.contains flags 'c' in
+ fun { filepath = path; filetype = flags } ->
+ let config = flags = FileConfig in
{ ft_path = path; ft_source_path = path; ft_config = config }
- | _ -> assert false
- ) lines
+ ) files
let rec fedora_download_all_packages pkgs dir =
let tdir = !settings.tmpdir // string_random8 () in
@@ -394,7 +383,7 @@ let () =
let fedora = {
ph_detect = fedora_detect;
ph_init = rpm_init;
- ph_fini = PHNoFini;
+ ph_fini = PHFini rpm_fini;
ph_package_of_string = rpm_package_of_string;
ph_package_to_string = rpm_package_to_string;
ph_package_name = rpm_package_name;
diff --git a/src/supermin-link.sh.in b/src/supermin-link.sh.in
index b2d71d9..29b84a1 100644
--- a/src/supermin-link.sh.in
+++ b/src/supermin-link.sh.in
@@ -21,4 +21,4 @@
# Hack automake to link 'supermin' binary properly. There is no other
# way to add the -cclib parameter to the end of the command line.
-exec "$@" -linkpkg -cclib '@EXT2FS_LIBS@ @COM_ERR_LIBS@'
+exec "$@" -linkpkg -cclib '@EXT2FS_LIBS@ @COM_ERR_LIBS@ @LIBRPM_LIBS@'
--
1.9.3